| |
| |
| #include "tmathutil.h" |
| #include "tstrokeutil.h" |
| #include "tstrokeoutline.h" |
| #include "tcurves.h" |
| #include "tbezier.h" |
| #include "tzerofinder.h" |
| #include "tcurveutil.h" |
| #include "cornerdetector.h" |
| |
| #include <limits> |
| |
| #include "tstroke.h" |
| |
| |
| |
| #define USE_NEW_3D_ERROR_COMPUTE 1 |
| |
| |
| |
| |
| |
| using namespace TConsts; |
| using namespace std; |
| |
| |
| |
| typedef std::vector<double> DoubleArray; |
| typedef DoubleArray::iterator DoubleIt; |
| typedef std::vector<TThickQuadratic *> QuadStrokeChunkArray; |
| |
| static int numSaved = 0; |
| |
| namespace { |
| |
| |
| void extractStrokeControlPoints(const QuadStrokeChunkArray &curves, |
| vector<TThickPoint> &ctrlPnts) { |
| const TThickQuadratic *prev = curves[0]; |
| assert(prev); |
| const TThickQuadratic *curr; |
| |
| ctrlPnts.push_back(prev->getThickP0()); |
| ctrlPnts.push_back(prev->getThickP1()); |
| |
| for (UINT i = 1; i < curves.size(); ++i) { |
| curr = curves[i]; |
| assert(curr); |
| TThickPoint middlePnt = (prev->getThickP2() + curr->getThickP0()) * 0.5; |
| ctrlPnts.push_back(middlePnt); |
| ctrlPnts.push_back(curr->getThickP1()); |
| prev = curr; |
| } |
| |
| ctrlPnts.push_back(prev->getThickP2()); |
| } |
| |
| |
| inline TThickPoint adapter(const TThickPoint &tp) { return tp; } |
| |
| |
| |
| inline TThickPoint adapter(const TPointD &p) { return TThickPoint(p); } |
| |
| |
| |
| template <typename T> |
| void buildChunksFromControlPoints(QuadStrokeChunkArray &tq, |
| const vector<T> &v) { |
| TThickQuadratic *chunk; |
| T temp; |
| switch (v.size()) { |
| case 0: |
| tq.push_back(new TThickQuadratic); |
| break; |
| case 1: |
| temp = adapter(v.front()); |
| chunk = new TThickQuadratic(temp, temp, temp); |
| tq.push_back(chunk); |
| break; |
| case 2: { |
| TThickSegment s(adapter(v.front()), adapter(v.back())); |
| chunk = new TThickQuadratic(s.getThickP0(), s.getThickPoint(0.5), |
| s.getThickP1()); |
| tq.push_back(chunk); |
| } break; |
| default: |
| assert(v.size() & 1); |
| for (UINT i = 0; i < v.size() - 1; i += 2) { |
| chunk = new TThickQuadratic(adapter(v[i]), adapter(v[i + 1]), |
| adapter(v[i + 2])); |
| tq.push_back(chunk); |
| } |
| break; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| inline bool pairValuesAreEqual(const DoublePair &p) { |
| return p.first == p.second; |
| } |
| |
| |
| |
| |
| |
| |
| |
| void analyzeSolution(const vector<double> &coeff, |
| vector<DoublePair> &interval) { |
| |
| |
| |
| |
| vector<double> sol; |
| |
| |
| rootFinding(coeff, sol); |
| |
| if (isAlmostZero(coeff[2])) { |
| |
| if (isAlmostZero(coeff[1])) { |
| if (coeff[0] >= 0) interval.push_back(DoublePair(0.0, 1.0)); |
| return; |
| } |
| |
| double singleSol = -coeff[0] / coeff[1]; |
| |
| if (coeff[1] > 0) { |
| if (singleSol < 1) |
| interval.push_back(DoublePair(std::max(0.0, singleSol), 1.0)); |
| } else { |
| if (singleSol > 0) |
| interval.push_back(DoublePair(0.0, std::min(1.0, singleSol))); |
| } |
| return; |
| } |
| |
| |
| |
| |
| sort(sol.begin(), sol.end()); |
| |
| if (coeff[2] > 0) { |
| switch (sol.size()) { |
| case 0: |
| interval.push_back(DoublePair(0.0, 1.0)); |
| break; |
| |
| case 1: |
| interval.push_back(DoublePair(0.0, 1.0)); |
| break; |
| |
| case 2: |
| interval.push_back(DoublePair(0.0, std::min(std::max(sol[0], 0.0), 1.0))); |
| interval.push_back(DoublePair(std::max(std::min(sol[1], 1.0), 0.0), 1.0)); |
| break; |
| } |
| } else if (coeff[2] < 0 && sol.size() == 2) |
| interval.push_back(DoublePair(std::min(std::max(sol[0], 0.0), 1.0), |
| std::max(std::min(sol[1], 1.0), 0.0))); |
| |
| |
| std::vector<DoublePair>::iterator it = |
| std::remove_if(interval.begin(), interval.end(), pairValuesAreEqual); |
| |
| interval.erase(it, interval.end()); |
| } |
| |
| |
| |
| void floorNegativeThickness(TThickQuadratic *quad) { |
| assert(quad); |
| double val = quad->getThickP0().thick; |
| |
| if (val < 0 && isAlmostZero(val)) |
| quad->setThickP0(TThickPoint(quad->getP0(), 0.0)); |
| |
| val = quad->getThickP1().thick; |
| if (val < 0 && isAlmostZero(val)) |
| quad->setThickP1(TThickPoint(quad->getP1(), 0.0)); |
| |
| val = quad->getThickP2().thick; |
| if (val < 0 && isAlmostZero(val)) |
| quad->setThickP2(TThickPoint(quad->getP2(), 0.0)); |
| } |
| |
| |
| |
| |
| void roundNegativeThickess(QuadStrokeChunkArray &v) { |
| QuadStrokeChunkArray protoStroke, tempVectTQ; |
| |
| TThickQuadratic *tempTQ = 0, *tempTQ_1 = 0; |
| |
| int chunkCount = v.size(); |
| |
| vector<double> coeff; |
| double alpha, beta, gamma; |
| |
| vector<DoublePair> positiveIntervals; |
| |
| for (int i = 0; i < chunkCount; ++i) { |
| const TThickQuadratic &ttq = *v[i]; |
| |
| alpha = ttq.getThickP0().thick - 2 * ttq.getThickP1().thick + |
| ttq.getThickP2().thick; |
| beta = 2.0 * (ttq.getThickP1().thick - ttq.getThickP0().thick); |
| gamma = ttq.getThickP0().thick; |
| |
| coeff.push_back(gamma); |
| coeff.push_back(beta); |
| coeff.push_back(alpha); |
| |
| |
| |
| analyzeSolution(coeff, positiveIntervals); |
| |
| |
| |
| switch (positiveIntervals.size()) { |
| case 0: |
| tempTQ = new TThickQuadratic(ttq); |
| tempTQ->setThickP0(TThickPoint(tempTQ->getP0(), 0.0)); |
| tempTQ->setThickP1(TThickPoint(tempTQ->getP1(), 0.0)); |
| tempTQ->setThickP2(TThickPoint(tempTQ->getP2(), 0.0)); |
| protoStroke.push_back(tempTQ); |
| break; |
| case 1: |
| if (positiveIntervals[0].first == 0.0 && |
| positiveIntervals[0].second == 1.0) |
| protoStroke.push_back(new TThickQuadratic(ttq)); |
| else if (positiveIntervals[0].first == 0.0) { |
| tempTQ = new TThickQuadratic; |
| tempTQ_1 = new TThickQuadratic; |
| ttq.split(positiveIntervals[0].first, *tempTQ, *tempTQ_1); |
| tempTQ_1->setThickP0(TThickPoint(tempTQ_1->getP0(), 0.0)); |
| tempTQ_1->setThickP1(TThickPoint(tempTQ_1->getP1(), 0.0)); |
| tempTQ_1->setThickP2(TThickPoint(tempTQ_1->getP2(), 0.0)); |
| |
| floorNegativeThickness(tempTQ); |
| protoStroke.push_back(tempTQ); |
| protoStroke.push_back(tempTQ_1); |
| } else if (positiveIntervals[0].second == 1.0) { |
| tempTQ = new TThickQuadratic; |
| tempTQ_1 = new TThickQuadratic; |
| ttq.split(positiveIntervals[0].first, *tempTQ, *tempTQ_1); |
| tempTQ->setThickP0(TThickPoint(tempTQ->getP0(), 0.0)); |
| tempTQ->setThickP1(TThickPoint(tempTQ->getP1(), 0.0)); |
| tempTQ->setThickP2(TThickPoint(tempTQ->getP2(), 0.0)); |
| |
| protoStroke.push_back(tempTQ); |
| floorNegativeThickness(tempTQ_1); |
| protoStroke.push_back(tempTQ_1); |
| } else { |
| coeff.clear(); |
| coeff.push_back(positiveIntervals[0].first); |
| coeff.push_back(positiveIntervals[0].second); |
| split<TThickQuadratic>(ttq, coeff, tempVectTQ); |
| assert(tempVectTQ.size() == 3); |
| |
| tempVectTQ[0]->setThickP0(TThickPoint(tempVectTQ[0]->getP0(), 0.0)); |
| tempVectTQ[0]->setThickP1(TThickPoint(tempVectTQ[0]->getP1(), 0.0)); |
| tempVectTQ[0]->setThickP2(TThickPoint(tempVectTQ[0]->getP2(), 0.0)); |
| |
| |
| floorNegativeThickness(tempVectTQ[1]); |
| |
| tempVectTQ[2]->setThickP0(TThickPoint(tempVectTQ[2]->getP0(), 0.0)); |
| tempVectTQ[2]->setThickP1(TThickPoint(tempVectTQ[2]->getP1(), 0.0)); |
| tempVectTQ[2]->setThickP2(TThickPoint(tempVectTQ[2]->getP2(), 0.0)); |
| |
| copy(tempVectTQ.begin(), tempVectTQ.end(), back_inserter(protoStroke)); |
| tempVectTQ |
| .clear(); |
| |
| } |
| break; |
| case 2: |
| assert(positiveIntervals[0].first == 0.0); |
| assert(positiveIntervals[1].second == 1.0); |
| |
| coeff.clear(); |
| coeff.push_back(positiveIntervals[0].second); |
| coeff.push_back(positiveIntervals[1].first); |
| split<TThickQuadratic>(ttq, coeff, tempVectTQ); |
| assert(tempVectTQ.size() == 3); |
| |
| floorNegativeThickness(tempVectTQ[0]); |
| |
| tempVectTQ[1]->setThickP0(TThickPoint(tempVectTQ[1]->getP0(), 0.0)); |
| tempVectTQ[1]->setThickP1(TThickPoint(tempVectTQ[1]->getP1(), 0.0)); |
| tempVectTQ[1]->setThickP2(TThickPoint(tempVectTQ[1]->getP2(), 0.0)); |
| |
| floorNegativeThickness(tempVectTQ[2]); |
| |
| copy(tempVectTQ.begin(), tempVectTQ.end(), back_inserter(protoStroke)); |
| tempVectTQ.clear(); |
| |
| break; |
| } |
| |
| positiveIntervals.clear(); |
| coeff.clear(); |
| } |
| |
| swap(protoStroke, v); |
| clearPointerContainer(protoStroke); |
| } |
| |
| |
| |
| |
| |
| const int MAX_ROW = 10; |
| |
| inline void changeTQDirection(TThickQuadratic *tq) { |
| TThickPoint p = tq->getThickP2(); |
| tq->setThickP2(tq->getThickP0()); |
| tq->setThickP0(p); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| bool bfAreAlmostEqual(double x, double y) { |
| double x_y = (x > y) ? x - y : y - x; |
| return x_y < TConsts::epsilon; |
| } |
| |
| |
| |
| |
| |
| |
| template <class T> |
| void printContainer(const T &c, int maxRow = MAX_ROW) { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| |
| |
| |
| template <class T> |
| void printContainer(ostream &os, const T &c, int maxRow = MAX_ROW) { |
| if (maxRow <= 0) maxRow = MAX_ROW; |
| typename T::const_iterator cit; |
| cit = c.begin(); |
| os << '[' << c.size() << ']' << "=\n"; |
| int counter = 0; |
| for (; cit != c.end(); ++cit) { |
| if (++counter == maxRow - 1) { |
| os << '\n'; |
| counter = 0; |
| } |
| os << (*cit) << ' '; |
| } |
| } |
| |
| |
| |
| template <class T> |
| void printContainerOfPointer(ostream &os, const T &c, int maxRow = MAX_ROW) { |
| if (maxRow <= 0) maxRow = MAX_ROW; |
| typename T::const_iterator cit; |
| cit = c.begin(); |
| os << '[' << c.size() << ']' << " - "; |
| int counter = 0; |
| for (; cit != c.end(); ++cit) { |
| if (++counter == maxRow - 1) { |
| os << '\n'; |
| counter = 0; |
| } |
| os << **cit << ' '; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| template <class T> |
| T proportion(T a, T b, T c) { |
| assert(c != T(0)); |
| return a * b / c; |
| } |
| |
| |
| |
| |
| |
| |
| template <class T> |
| T proportion(T a, T b, T c, T offset) { |
| assert(c != T(0)); |
| return (a - offset) * b / c + offset; |
| } |
| |
| |
| |
| |
| |
| |
| template <typename type_, typename container_> |
| class TBackInserterPointer { |
| container_ &m_c; |
| |
| public: |
| explicit TBackInserterPointer(container_ &c) : m_c(c){}; |
| |
| TBackInserterPointer &operator=(const type_ *value) { |
| m_c.push_back(new type_(*value)); |
| return *this; |
| } |
| |
| TBackInserterPointer &operator=(const type_ &value) { |
| m_c.push_back(new type_(value)); |
| return *this; |
| }; |
| #ifdef MACOSX |
| typedef type_ value_type; |
| typedef type_ &reference; |
| typedef type_ *pointer; |
| typedef output_iterator_tag iterator_category; |
| typedef ptrdiff_t difference_type; |
| #endif |
| |
| TBackInserterPointer &operator*() { return *this; } |
| TBackInserterPointer &operator++() { return *this; } |
| TBackInserterPointer operator++(int val) { return *this; } |
| }; |
| |
| typedef TBackInserterPointer<TThickQuadratic, QuadStrokeChunkArray> |
| TThickQuadraticArrayInsertIterator; |
| |
| |
| |
| |
| |
| |
| struct computeOffset_ { |
| TQuadraticLengthEvaluator m_lengthEval; |
| double m_offset; |
| |
| computeOffset_(const TThickQuadratic *ttq, double offset) |
| : m_lengthEval(*ttq), m_offset(offset) {} |
| |
| double operator()(double par) { |
| return m_lengthEval.getLengthAt(par) - m_offset; |
| } |
| }; |
| |
| |
| |
| |
| |
| |
| struct computeSpeed_ { |
| const TThickQuadratic *ref_; |
| |
| computeSpeed_(const TThickQuadratic *ref) : ref_(ref) {} |
| |
| double operator()(double par) { return norm(ref_->getSpeed(par)); } |
| }; |
| |
| |
| } |
| |
| |
| |
| const BYTE TStroke::c_selected_flag = 0x1; |
| const BYTE TStroke::c_changed_region_flag = 0x2; |
| const BYTE TStroke::c_dirty_flag = 0x4; |
| |
| |
| |
| |
| |
| |
| |
| struct TStroke::Imp { |
| |
| |
| BYTE m_flag; |
| |
| |
| |
| bool m_isValidLength; |
| |
| |
| |
| bool m_isOutlineValid; |
| |
| |
| bool m_areDisabledComputeOfCaches; |
| |
| |
| TRectD m_bBox; |
| |
| |
| DoubleArray m_partialLengthArray; |
| |
| |
| DoubleArray m_parameterValueAtControlPoint; |
| |
| |
| QuadStrokeChunkArray m_centerLineArray; |
| |
| bool m_selfLoop; |
| |
| int m_negativeThicknessPoints; |
| double m_averageThickness; |
| double m_maxThickness; |
| |
| |
| |
| |
| |
| int m_id; |
| |
| int m_styleId; |
| TStrokeProp *m_prop; |
| |
| TStroke::OutlineOptions m_outlineOptions; |
| |
| |
| |
| Imp(); |
| |
| Imp(const vector<TPointD> &v); |
| |
| Imp(const vector<TThickPoint> &v); |
| |
| ~Imp() { |
| delete m_prop; |
| clearPointerContainer(m_centerLineArray); |
| |
| } |
| |
| void init(); |
| |
| |
| |
| inline double getW(int index) { |
| return ((int)m_parameterValueAtControlPoint.size() > index) |
| ? m_parameterValueAtControlPoint[index] |
| : m_parameterValueAtControlPoint.back(); |
| } |
| |
| TRectD computeCenterlineBBox(); |
| |
| |
| |
| |
| void computeMaxThickness(); |
| |
| TRectD computeSubBBox(double w0, double w1) const; |
| |
| inline QuadStrokeChunkArray &getTQArray() { return m_centerLineArray; } |
| |
| |
| |
| |
| void swapGeometry(TStroke::Imp &other) throw(); |
| |
| |
| void computeCacheVector(); |
| |
| |
| |
| |
| void computeParameterInControlPoint(); |
| |
| |
| |
| |
| |
| void updateParameterValue(double w, UINT chunk, TThickQuadratic *tq1, |
| TThickQuadratic *tq2); |
| |
| |
| |
| |
| |
| |
| |
| bool retrieveChunkAndItsParamameter(double w, int &chunk, double &t); |
| |
| |
| |
| |
| |
| |
| |
| bool retrieveChunkAndItsParamameterAtLength(double s, int &chunk, double &t); |
| |
| |
| |
| |
| |
| int retrieveChunkFromControlPointIndex(int n) { |
| assert(0 <= n && n < getControlPointCount()); |
| |
| if (n & 1) ++n; |
| |
| n >>= 1; |
| |
| return n ? n - 1 : n; |
| }; |
| |
| |
| |
| |
| DoublePair retrieveParametersFromChunk(UINT chunk) { |
| DoublePair outPar; |
| |
| int nFirst, nSecond; |
| |
| nFirst = chunk * 2; |
| nSecond = (chunk + 1) * 2; |
| |
| outPar.first = getW(nFirst); |
| outPar.second = getW(nSecond); |
| |
| return outPar; |
| } |
| |
| |
| |
| void print(ostream &os); |
| |
| |
| |
| inline int getChunkCount() const { return m_centerLineArray.size(); } |
| |
| |
| |
| inline int getControlPointCount() const { |
| UINT out = 2 * getChunkCount() + 1; |
| return out; |
| } |
| |
| |
| |
| TThickQuadratic *getChunk(int index) { |
| if (0 <= index && index < getChunkCount()) return m_centerLineArray[index]; |
| |
| return 0; |
| } |
| |
| private: |
| |
| Imp(const Imp &other); |
| Imp &operator=(const Imp &other); |
| }; |
| |
| |
| |
| namespace { |
| int maxStrokeId = 0; |
| } |
| |
| |
| |
| |
| |
| void TStroke::Imp::init() { |
| m_flag = c_dirty_flag; |
| |
| m_styleId = 1; |
| m_prop = 0; |
| |
| m_id = ++maxStrokeId; |
| m_isValidLength = false; |
| m_isOutlineValid = false; |
| m_areDisabledComputeOfCaches = false; |
| m_selfLoop = false; |
| m_averageThickness = 0; |
| m_maxThickness = -1; |
| m_negativeThicknessPoints = 0; |
| for (UINT j = 0; j < m_centerLineArray.size(); j++) { |
| if (m_centerLineArray[j]->getThickP0().thick <= 0) |
| m_negativeThicknessPoints++; |
| if (m_centerLineArray[j]->getThickP1().thick <= 0) |
| m_negativeThicknessPoints++; |
| } |
| if (!m_centerLineArray.empty() && |
| m_centerLineArray.back()->getThickP2().thick <= 0) |
| m_negativeThicknessPoints++; |
| |
| computeParameterInControlPoint(); |
| } |
| |
| |
| |
| TStroke::Imp::Imp() { init(); } |
| |
| |
| |
| TStroke::Imp::Imp(const std::vector<TThickPoint> &v) { |
| buildChunksFromControlPoints(m_centerLineArray, v); |
| roundNegativeThickess(m_centerLineArray); |
| |
| init(); |
| } |
| |
| |
| |
| TStroke::Imp::Imp(const std::vector<TPointD> &v) { |
| buildChunksFromControlPoints(m_centerLineArray, v); |
| roundNegativeThickess(m_centerLineArray); |
| |
| init(); |
| } |
| |
| |
| |
| void TStroke::Imp::swapGeometry(Imp &other) throw() { |
| std::swap(m_flag, other.m_flag); |
| std::swap(m_isValidLength, other.m_isValidLength); |
| std::swap(m_isOutlineValid, other.m_isOutlineValid); |
| std::swap(m_areDisabledComputeOfCaches, other.m_areDisabledComputeOfCaches); |
| std::swap(m_bBox, other.m_bBox); |
| std::swap(m_partialLengthArray, other.m_partialLengthArray); |
| std::swap(m_parameterValueAtControlPoint, |
| other.m_parameterValueAtControlPoint); |
| std::swap(m_centerLineArray, other.m_centerLineArray); |
| std::swap(m_selfLoop, other.m_selfLoop); |
| std::swap(m_negativeThicknessPoints, other.m_negativeThicknessPoints); |
| std::swap(m_averageThickness, other.m_averageThickness); |
| std::swap(m_maxThickness, other.m_maxThickness); |
| } |
| |
| |
| |
| void TStroke::Imp::computeMaxThickness() { |
| m_maxThickness = m_centerLineArray[0]->getThickP0().thick; |
| for (UINT i = 0; i < m_centerLineArray.size(); i++) |
| m_maxThickness = |
| std::max({m_maxThickness, m_centerLineArray[i]->getThickP1().thick, |
| m_centerLineArray[i]->getThickP2().thick}); |
| } |
| |
| void TStroke::Imp::computeCacheVector() { |
| |
| |
| if (!m_areDisabledComputeOfCaches && !m_isValidLength) { |
| if (getChunkCount() > 0) |
| { |
| |
| m_partialLengthArray.resize(getControlPointCount(), |
| (std::numeric_limits<double>::max)()); |
| |
| m_partialLengthArray[0] = 0.0; |
| |
| double length = 0.0; |
| int j = 0; |
| const TThickQuadratic *tq; |
| |
| TQuadraticLengthEvaluator lengthEvaluator; |
| |
| for (int i = 0; i < getChunkCount(); ++i) { |
| assert(j <= getControlPointCount()); |
| tq = getChunk(i); |
| lengthEvaluator.setQuad(*tq); |
| m_partialLengthArray[j++] = length; |
| m_partialLengthArray[j++] = length + lengthEvaluator.getLengthAt(0.5); |
| length += lengthEvaluator.getLengthAt(1.0); |
| } |
| |
| m_partialLengthArray[j++] = length; |
| assert(j == getControlPointCount()); |
| |
| |
| } |
| m_isValidLength = true; |
| } |
| } |
| |
| |
| |
| void TStroke::Imp::computeParameterInControlPoint() { |
| if (!m_areDisabledComputeOfCaches) { |
| |
| |
| |
| |
| if (!getChunkCount()) { |
| m_parameterValueAtControlPoint.clear(); |
| return; |
| } |
| |
| int controlPointCount = getControlPointCount(); |
| |
| m_parameterValueAtControlPoint.resize(controlPointCount, 0); |
| |
| |
| --controlPointCount; |
| |
| double val = 0.0; |
| |
| assert(controlPointCount >= 0.0); |
| |
| for (int i = 0; i <= controlPointCount; ++i) { |
| val = i / (double)controlPointCount; |
| m_parameterValueAtControlPoint[i] = val; |
| } |
| } |
| } |
| |
| |
| |
| void TStroke::Imp::updateParameterValue(double w, UINT chunk, |
| TThickQuadratic *tq1, |
| TThickQuadratic *tq2) { |
| DoublePair p = retrieveParametersFromChunk(chunk); |
| |
| UINT controlPointToErase = 2 * chunk + 1; |
| DoubleIt it = m_parameterValueAtControlPoint.begin(); |
| std::advance(it, controlPointToErase); |
| m_parameterValueAtControlPoint.erase(it); |
| |
| double normalizedParam = tq2->getT(tq2->getP1()); |
| |
| std::vector<double>::iterator first; |
| |
| normalizedParam = proportion(p.second, normalizedParam, 1.0, w); |
| |
| first = |
| std::upper_bound(m_parameterValueAtControlPoint.begin(), |
| m_parameterValueAtControlPoint.end(), normalizedParam); |
| |
| if (first != m_parameterValueAtControlPoint.end()) { |
| first = m_parameterValueAtControlPoint.insert(first, normalizedParam); |
| |
| first = m_parameterValueAtControlPoint.insert(first, w); |
| |
| normalizedParam = tq1->getT(tq1->getP1()); |
| normalizedParam = proportion(w, normalizedParam, 1.0, p.first); |
| |
| m_parameterValueAtControlPoint.insert(first, normalizedParam); |
| } |
| |
| |
| |
| |
| |
| } |
| |
| |
| |
| bool TStroke::getChunkAndTAtLength(double s, int &chunk, double &t) const { |
| return m_imp->retrieveChunkAndItsParamameterAtLength(s, chunk, t); |
| } |
| |
| |
| |
| bool TStroke::Imp::retrieveChunkAndItsParamameterAtLength(double s, int &chunk, |
| double &t) { |
| vector<double>::iterator first; |
| |
| |
| first = std::upper_bound(m_partialLengthArray.begin(), |
| m_partialLengthArray.end(), s); |
| |
| |
| if (first != m_partialLengthArray.end()) { |
| |
| int controlPointOffset = distance(m_partialLengthArray.begin(), first); |
| |
| |
| chunk = retrieveChunkFromControlPointIndex(controlPointOffset); |
| |
| if (first != m_partialLengthArray.begin() && s == *(first - 1)) { |
| controlPointOffset--; |
| if (controlPointOffset & 1) { |
| const DoublePair &p = retrieveParametersFromChunk(chunk); |
| t = proportion(1.0, getW(controlPointOffset) - p.first, |
| p.second - p.first); |
| } else |
| t = 0.0; |
| |
| return false; |
| } |
| |
| |
| double offset = (first == m_partialLengthArray.begin()) |
| ? s |
| : s - m_partialLengthArray[chunk * 2]; |
| |
| |
| |
| const double tol = TConsts::epsilon * 0.1; |
| int err; |
| |
| computeOffset_ op(getChunk(chunk), offset); |
| computeSpeed_ op2(getChunk(chunk)); |
| |
| if (!findZero_Newton(0.0, 1.0, op, op2, tol, tol, 100, t, err)) |
| t = -1; |
| |
| |
| |
| if (t == -1) { |
| if (s <= m_partialLengthArray[controlPointOffset]) t = 0.0; |
| t = 1.0; |
| } |
| |
| return false; |
| } |
| |
| if (s <= 0.0) { |
| chunk = 0; |
| t = 0.0; |
| } else if (s >= m_partialLengthArray.back()) { |
| chunk = getChunkCount() - 1; |
| t = 1.0; |
| } |
| |
| return false; |
| } |
| |
| |
| |
| bool TStroke::getChunkAndT(double w, int &chunk, double &t) const { |
| return m_imp->retrieveChunkAndItsParamameter(w, chunk, t); |
| } |
| |
| |
| |
| bool TStroke::Imp::retrieveChunkAndItsParamameter(double w, int &chunk, |
| double &t) { |
| vector<double>::iterator first; |
| |
| |
| first = std::lower_bound(m_parameterValueAtControlPoint.begin(), |
| m_parameterValueAtControlPoint.end(), w); |
| |
| |
| if (first == m_parameterValueAtControlPoint.end()) return true; |
| |
| |
| |
| |
| |
| |
| int controlPointOffset = |
| distance(m_parameterValueAtControlPoint.begin(), first); |
| |
| |
| chunk = retrieveChunkFromControlPointIndex(controlPointOffset); |
| |
| |
| DoublePair p = retrieveParametersFromChunk(chunk); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (w < p.first || w > p.second) { |
| t = (p.first + p.second) * 0.5; |
| } else |
| t = proportion(1.0, w - p.first, p.second - p.first); |
| |
| |
| |
| |
| |
| return false; |
| } |
| |
| |
| |
| TRectD TStroke::Imp::computeCenterlineBBox() { |
| UINT n = m_centerLineArray.size(); |
| if (m_centerLineArray.empty()) return TRectD(); |
| TQuadratic q(m_centerLineArray[0]->getP0(), m_centerLineArray[0]->getP1(), |
| m_centerLineArray[0]->getP2()); |
| TRectD bbox = q.getBBox(); |
| for (UINT i = 1; i < n; i++) { |
| q = TQuadratic(m_centerLineArray[i]->getP0(), m_centerLineArray[i]->getP1(), |
| m_centerLineArray[i]->getP2()); |
| bbox += q.getBBox(); |
| } |
| return bbox; |
| } |
| |
| |
| |
| TRectD TStroke::Imp::computeSubBBox(double w0, double w1) const { |
| if (m_centerLineArray.empty()) return TRectD(); |
| |
| int n = m_centerLineArray.size(); |
| |
| TRectD bBox; |
| const double eps = 0.000000001; |
| int i; |
| |
| if (w0 > w1) std::swap(w0, w1); |
| |
| double nw0 = w0 * n; |
| double nw1 = w1 * n; |
| |
| int i0 = (int)nw0; |
| int i1 = (int)nw1; |
| |
| double t0 = |
| nw0 - |
| (double)i0; |
| double t1 = nw1 - (double)i1; |
| |
| if (t0 < eps) |
| |
| { |
| i0--; |
| t0 = 1.0; |
| } |
| if (t1 > (1 - eps)) |
| |
| { |
| i1++; |
| t1 = 0.0; |
| } |
| |
| TThickQuadratic quadratic1, quadratic2, quadratic3; |
| |
| if (i0 == i1) |
| { |
| if (t0 < eps && t1 > (1 - eps)) return m_centerLineArray[i0]->getBBox(); |
| |
| if (t0 < eps) { |
| m_centerLineArray[i0]->split(t1, quadratic1, quadratic2); |
| return quadratic1.getBBox(); |
| } |
| |
| if (t1 > (1 - eps)) { |
| m_centerLineArray[i0]->split(t0, quadratic1, quadratic2); |
| return quadratic2.getBBox(); |
| } |
| |
| |
| |
| m_centerLineArray[i0]->split(t0, quadratic1, quadratic2); |
| quadratic2.split((t1 - t0) / (1 - t0), quadratic1, quadratic3); |
| |
| return quadratic1.getBBox(); |
| } |
| |
| |
| |
| |
| for (i = i0 + 1; i < i1; i++) bBox += m_centerLineArray[i]->getBBox(); |
| |
| |
| |
| if (i0 >= 0 && t0 < (1 - eps)) { |
| m_centerLineArray[i0]->split(t0, quadratic1, quadratic2); |
| bBox += quadratic2.getBBox(); |
| } |
| |
| if (i1 < n && t1 > eps) { |
| m_centerLineArray[i1]->split(t1, quadratic1, quadratic2); |
| bBox += quadratic1.getBBox(); |
| } |
| |
| return bBox; |
| } |
| |
| |
| |
| void TStroke::Imp::print(ostream &os) { |
| #if defined(_DEBUG) || defined(DEBUG) |
| |
| os << "m_isValidLength:" << m_isValidLength << endl; |
| |
| os << "m_isOutlineValid:" << m_isOutlineValid << endl; |
| |
| os << "m_areDisabledComputeOfCaches:" << m_areDisabledComputeOfCaches << endl; |
| |
| os << "m_bBox:" << m_bBox << endl; |
| |
| os << "m_partialLengthArray"; |
| printContainer(os, m_partialLengthArray); |
| os << endl; |
| |
| os << "m_parameterValueAtControlPoint"; |
| printContainer(os, m_parameterValueAtControlPoint); |
| os << endl; |
| |
| os << "m_centerLineArray"; |
| |
| printContainerOfPointer(os, m_centerLineArray); |
| os << endl; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #else |
| #endif |
| } |
| |
| |
| |
| |
| DEFINE_CLASS_CODE(TStroke, 15) |
| |
| |
| |
| |
| TStroke::TStroke() : TSmartObject(m_classCode) { |
| vector<TThickPoint> p(3); |
| p[0] = TThickPoint(0, 0, 0); |
| p[1] = p[0]; |
| p[2] = p[1]; |
| |
| m_imp.reset(new TStroke::Imp(p)); |
| |
| |
| |
| |
| |
| } |
| |
| |
| |
| |
| TStroke::TStroke(const vector<TThickPoint> &v) |
| : TSmartObject(m_classCode), m_imp(new TStroke::Imp(v)) {} |
| |
| |
| |
| TStroke::TStroke(const vector<TPointD> &v) |
| : TSmartObject(m_classCode), m_imp(new TStroke::Imp(v)) {} |
| |
| |
| |
| TStroke::~TStroke() {} |
| |
| |
| |
| TStroke::TStroke(const TStroke &other) |
| : TSmartObject(m_classCode), m_imp(new TStroke::Imp()) { |
| m_imp->m_bBox = other.getBBox(); |
| m_imp->m_isValidLength = other.m_imp->m_isValidLength; |
| m_imp->m_isOutlineValid = other.m_imp->m_isOutlineValid; |
| m_imp->m_areDisabledComputeOfCaches = |
| other.m_imp->m_areDisabledComputeOfCaches; |
| m_imp->m_flag = other.m_imp->m_flag; |
| m_imp->m_outlineOptions = other.m_imp->m_outlineOptions; |
| |
| |
| m_imp->m_centerLineArray.resize(other.m_imp->m_centerLineArray.size()); |
| int i; |
| for (i = 0; i < (int)other.m_imp->m_centerLineArray.size(); i++) |
| m_imp->m_centerLineArray[i] = |
| new TThickQuadratic(*other.m_imp->m_centerLineArray[i]); |
| |
| |
| |
| |
| |
| copy(other.m_imp->m_partialLengthArray.begin(), |
| other.m_imp->m_partialLengthArray.end(), |
| back_inserter<DoubleArray>(m_imp->m_partialLengthArray)); |
| copy(other.m_imp->m_parameterValueAtControlPoint.begin(), |
| other.m_imp->m_parameterValueAtControlPoint.end(), |
| back_inserter<DoubleArray>(m_imp->m_parameterValueAtControlPoint)); |
| |
| m_imp->m_styleId = other.m_imp->m_styleId; |
| m_imp->m_prop = |
| 0; |
| m_imp->m_selfLoop = other.m_imp->m_selfLoop; |
| m_imp->m_negativeThicknessPoints = other.m_imp->m_negativeThicknessPoints; |
| } |
| |
| |
| |
| TStroke &TStroke::operator=(const TStroke &other) { |
| TStroke temp(other); |
| swap(temp); |
| return *this; |
| } |
| |
| |
| |
| bool TStroke::getNearestW(const TPointD &p, double &outW, double &dist2, |
| bool checkBBox) const { |
| double outT; |
| int chunkIndex; |
| bool ret = getNearestChunk(p, outT, chunkIndex, dist2, checkBBox); |
| if (ret) outW = getW(chunkIndex, outT); |
| return ret; |
| } |
| |
| bool TStroke::getNearestChunk(const TPointD &p, double &outT, int &chunkIndex, |
| double &dist2, bool checkBBox) const { |
| dist2 = (numeric_limits<double>::max)(); |
| |
| for (UINT i = 0; i < m_imp->m_centerLineArray.size(); i++) { |
| if (checkBBox && |
| !m_imp->m_centerLineArray[i]->getBBox().enlarge(30).contains(p)) |
| continue; |
| |
| double t = (m_imp->m_centerLineArray)[i]->getT(p); |
| double dist = tdistance2((m_imp->m_centerLineArray)[i]->getPoint(t), p); |
| |
| if (dist < dist2) { |
| dist2 = dist; |
| chunkIndex = i; |
| outT = t; |
| } |
| } |
| |
| return dist2 < (numeric_limits<double>::max)(); |
| } |
| |
| |
| |
| |
| |
| int TStroke::getNearChunks(const TThickPoint &p, |
| vector<TThickPoint> &pointsOnStroke, |
| bool checkBBox) const { |
| int currIndex = -100; |
| double currDist2 = 100000; |
| |
| for (UINT i = 0; i < m_imp->m_centerLineArray.size(); i++) { |
| TThickQuadratic *q = m_imp->m_centerLineArray[i]; |
| |
| if (checkBBox && !q->getBBox().enlarge(30).contains(p)) continue; |
| |
| double t = q->getT(p); |
| TThickPoint p1 = q->getThickPoint(t); |
| double dist2 = tdistance2(p1, p); |
| |
| if (dist2 < (p1.thick + p.thick + 5) * (p1.thick + p.thick + 5)) { |
| if (!pointsOnStroke.empty() && |
| areAlmostEqual(p1, pointsOnStroke.back(), 1e-3)) |
| continue; |
| |
| if (currIndex == i - 1) { |
| if (dist2 < currDist2) |
| pointsOnStroke.pop_back(); |
| else |
| continue; |
| } |
| |
| currIndex = i; |
| currDist2 = dist2; |
| pointsOnStroke.push_back(p1); |
| } |
| } |
| |
| return pointsOnStroke.size(); |
| } |
| |
| |
| |
| void TStroke::getControlPoints(vector<TThickPoint> &v) const { |
| assert(v.empty()); |
| v.resize(m_imp->m_centerLineArray.size() * 2 + 1); |
| |
| v[0] = m_imp->m_centerLineArray[0]->getThickP0(); |
| |
| for (UINT i = 0; i < m_imp->m_centerLineArray.size(); i++) { |
| TThickQuadratic *q = m_imp->m_centerLineArray[i]; |
| v[2 * i + 1] = q->getThickP1(); |
| v[2 * i + 2] = q->getThickP2(); |
| } |
| } |
| |
| TThickPoint TStroke::getControlPoint(int n) const { |
| if (n <= 0) return m_imp->m_centerLineArray.front()->getThickP0(); |
| |
| if (n >= getControlPointCount()) |
| return m_imp->m_centerLineArray.back()->getThickP2(); |
| |
| |
| |
| |
| int chunkNumber = tceil((n - 1) * 0.5); |
| assert(chunkNumber <= getChunkCount()); |
| |
| int pointOffset = n - chunkNumber * 2; |
| |
| if (chunkNumber == getChunkCount()) |
| return getChunk(chunkNumber - 1)->getThickP2(); |
| |
| switch (pointOffset) { |
| case 0: |
| return getChunk(chunkNumber)->getThickP0(); |
| case 1: |
| return getChunk(chunkNumber)->getThickP1(); |
| case 2: |
| return getChunk(chunkNumber)->getThickP2(); |
| } |
| |
| assert("Not yet finished" && false); |
| return getControlPoint(0); |
| } |
| |
| |
| |
| TThickPoint TStroke::getControlPointAtParameter(double w) const { |
| if (w <= 0) return m_imp->m_centerLineArray.front()->getThickP0(); |
| |
| if (w >= 1.0) return m_imp->m_centerLineArray.back()->getThickP2(); |
| |
| vector<double>::iterator it_begin = |
| m_imp->m_parameterValueAtControlPoint.begin(), |
| first, |
| it_end = m_imp->m_parameterValueAtControlPoint.end(); |
| |
| |
| first = std::lower_bound(it_begin, it_end, w); |
| |
| assert(first != it_end); |
| |
| |
| |
| |
| if (first == it_begin) |
| return getControlPoint(0); |
| else if ((*first - w) <= w - *(first - 1)) |
| return getControlPoint(distance(it_begin, first)); |
| else |
| return getControlPoint(distance(it_begin, first - 1)); |
| } |
| |
| |
| |
| int TStroke::getControlPointIndexAfterParameter(double w) const { |
| const vector<double>::const_iterator |
| begin = m_imp->m_parameterValueAtControlPoint.begin(), |
| end = m_imp->m_parameterValueAtControlPoint.end(); |
| |
| vector<double>::const_iterator it = std::upper_bound(begin, end, w); |
| |
| if (it == end) |
| return getControlPointCount(); |
| else |
| return std::distance(begin, it); |
| } |
| |
| |
| |
| void TStroke::setControlPoint(int n, const TThickPoint &pos) { |
| assert(n >= 0); |
| assert(n < getControlPointCount()); |
| if (n < 0 || n >= getControlPointCount()) return; |
| |
| invalidate(); |
| |
| QuadStrokeChunkArray &chunkArray = m_imp->m_centerLineArray; |
| |
| if (getControlPoint(n).thick <= 0 && pos.thick > 0) |
| m_imp->m_negativeThicknessPoints--; |
| else if (getControlPoint(n).thick > 0 && pos.thick <= 0) |
| m_imp->m_negativeThicknessPoints++; |
| |
| if (n == 0) { |
| chunkArray[0]->setThickP0(pos); |
| |
| return; |
| } |
| |
| int chunkNumber = tceil((n - 1) * 0.5); |
| assert(chunkNumber <= getChunkCount()); |
| |
| int pointOffset = n - chunkNumber * 2; |
| |
| if (chunkNumber == getChunkCount()) |
| { |
| chunkArray[chunkNumber - 1]->setThickP2(pos); |
| |
| return; |
| } |
| |
| if (0 == pointOffset) { |
| chunkArray[chunkNumber]->setThickP0(pos); |
| |
| if (chunkNumber >= 1) { |
| chunkNumber--; |
| chunkArray[chunkNumber]->setThickP2(pos); |
| } |
| } else if (1 == pointOffset) |
| chunkArray[chunkNumber]->setThickP1(pos); |
| else if (2 == pointOffset) { |
| chunkArray[chunkNumber]->setThickP2(pos); |
| |
| if (chunkNumber < getChunkCount() - 1) { |
| chunkNumber++; |
| chunkArray[chunkNumber]->setThickP0(pos); |
| } |
| } |
| |
| } |
| |
| |
| |
| |
| void TStroke::reshape(const TThickPoint pos[], int count) { |
| |
| assert(count >= 3); |
| assert(count & 1); |
| QuadStrokeChunkArray &chunkArray = m_imp->m_centerLineArray; |
| clearPointerContainer(chunkArray); |
| |
| m_imp->m_negativeThicknessPoints = 0; |
| for (int i = 0; i < count - 1; i += 2) { |
| chunkArray.push_back(new TThickQuadratic(pos[i], pos[i + 1], pos[i + 2])); |
| if (pos[i].thick <= 0) m_imp->m_negativeThicknessPoints++; |
| if (pos[i + 1].thick <= 0) m_imp->m_negativeThicknessPoints++; |
| } |
| if (pos[count - 1].thick <= 0) m_imp->m_negativeThicknessPoints++; |
| |
| invalidate(); |
| |
| m_imp->computeParameterInControlPoint(); |
| } |
| |
| |
| |
| double TStroke::getApproximateLength(double w0, double w1, double error) const { |
| m_imp->computeCacheVector(); |
| |
| assert((int)m_imp->m_partialLengthArray.size() == getControlPointCount()); |
| |
| if (w0 == w1) return 0.0; |
| |
| w0 = min(max(0.0, w0), 1.0); |
| w1 = min(max(0.0, w1), 1.0); |
| |
| if (w0 > w1) std::swap(w0, w1); |
| |
| |
| if (0.0 == w0) { |
| vector<double>::iterator first; |
| |
| |
| first = std::upper_bound(m_imp->m_parameterValueAtControlPoint.begin(), |
| m_imp->m_parameterValueAtControlPoint.end(), |
| w1 - TConsts::epsilon); |
| |
| if (first != m_imp->m_parameterValueAtControlPoint.end() && |
| *first < w1 + TConsts::epsilon) { |
| int offset = |
| distance(m_imp->m_parameterValueAtControlPoint.begin(), first); |
| return m_imp->m_partialLengthArray[offset]; |
| } |
| } |
| |
| int firstChunk, secondChunk; |
| double firstT, secondT; |
| |
| |
| bool val1 = m_imp->retrieveChunkAndItsParamameter(w0, firstChunk, firstT); |
| assert(val1); |
| |
| bool val2 = m_imp->retrieveChunkAndItsParamameter(w1, secondChunk, secondT); |
| assert(val2); |
| |
| if (firstChunk == secondChunk) |
| return getChunk(firstChunk)->getApproximateLength(firstT, secondT, error); |
| |
| double totalLength = 0; |
| |
| totalLength += getChunk(firstChunk)->getApproximateLength(firstT, 1, error); |
| |
| |
| for (int i = firstChunk + 1; i != secondChunk; i++) |
| totalLength += getChunk(i)->getApproximateLength(0.0, 1.0, error); |
| |
| totalLength += |
| getChunk(secondChunk)->getApproximateLength(0.0, secondT, error); |
| |
| return totalLength; |
| } |
| |
| |
| |
| double TStroke::getLength(double w0, double w1) const { |
| if (w0 == w1) return 0.0; |
| |
| |
| w0 = min(max(0.0, w0), 1.0); |
| w1 = min(max(0.0, w1), 1.0); |
| |
| if (w0 > w1) std::swap(w0, w1); |
| |
| |
| int chunk; |
| double t; |
| |
| bool ok = !m_imp->retrieveChunkAndItsParamameter(w1, chunk, t); |
| assert(ok); |
| |
| double s1 = getLength(chunk, t); |
| |
| if (w0 == 0.0) return s1; |
| |
| |
| ok = !m_imp->retrieveChunkAndItsParamameter(w0, chunk, t); |
| assert(ok); |
| |
| return s1 - getLength(chunk, t); |
| } |
| |
| |
| |
| double TStroke::getLength(int chunk, double t) const { |
| |
| m_imp->computeCacheVector(); |
| assert((int)m_imp->m_partialLengthArray.size() == getControlPointCount()); |
| |
| if (t == 1.0) ++chunk, t = 0.0; |
| |
| double s = m_imp->m_partialLengthArray[chunk << 1]; |
| if (t > 0.0) s += getChunk(chunk)->getLength(t); |
| |
| return s; |
| } |
| |
| |
| |
| void TStroke::invalidate() { |
| m_imp->m_maxThickness = -1; |
| m_imp->m_isOutlineValid = false; |
| m_imp->m_isValidLength = false; |
| m_imp->m_flag = m_imp->m_flag | c_dirty_flag; |
| if (m_imp->m_prop) m_imp->m_prop->notifyStrokeChange(); |
| } |
| |
| |
| |
| |
| |
| |
| void TStroke::insertControlPointsAtLength(double s) { |
| if (0 > s || s > getLength()) return; |
| |
| int chunk; |
| double t; |
| |
| |
| if (!m_imp->retrieveChunkAndItsParamameterAtLength(s, chunk, t)) { |
| if (isAlmostZero(t) || areAlmostEqual(t, 1)) return; |
| |
| |
| TThickQuadratic *tqfirst = new TThickQuadratic, |
| *tqsecond = new TThickQuadratic; |
| |
| getChunk(chunk)->split(t, *tqfirst, *tqsecond); |
| |
| double parameterInStroke; |
| |
| if (0 == chunk) |
| parameterInStroke = m_imp->getW(2) * t; |
| else |
| parameterInStroke = |
| t * m_imp->getW((chunk + 1) * 2) + (1 - t) * m_imp->getW(chunk * 2); |
| |
| m_imp->updateParameterValue(parameterInStroke, chunk, tqfirst, tqsecond); |
| |
| |
| QuadStrokeChunkArray::iterator it = m_imp->m_centerLineArray.begin(); |
| |
| |
| advance(it, chunk); |
| delete *it; |
| it = m_imp->m_centerLineArray.erase(it); |
| |
| |
| it = m_imp->m_centerLineArray.insert(it, tqsecond); |
| it = m_imp->m_centerLineArray.insert(it, tqfirst); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| invalidate(); |
| } |
| |
| |
| |
| void TStroke::insertControlPoints(double w) { |
| if (0.0 > w || w > 1.0) return; |
| |
| int chunk; |
| double tOfDivision = -1; |
| |
| if (m_imp->retrieveChunkAndItsParamameter(w, chunk, tOfDivision)) return; |
| |
| if (isAlmostZero(tOfDivision) || areAlmostEqual(tOfDivision, 1)) return; |
| |
| assert(0 <= chunk && chunk < getChunkCount()); |
| assert(0 <= tOfDivision && tOfDivision <= 1.0); |
| |
| |
| TThickQuadratic *tqfirst = new TThickQuadratic, |
| *tqsecond = new TThickQuadratic; |
| |
| getChunk(chunk)->split(tOfDivision, *tqfirst, *tqsecond); |
| |
| m_imp->updateParameterValue(w, chunk, tqfirst, tqsecond); |
| |
| |
| QuadStrokeChunkArray::iterator it = m_imp->m_centerLineArray.begin(); |
| |
| |
| advance(it, chunk); |
| delete *it; |
| it = m_imp->m_centerLineArray.erase(it); |
| |
| |
| it = m_imp->m_centerLineArray.insert(it, tqsecond); |
| m_imp->m_centerLineArray.insert(it, tqfirst); |
| |
| invalidate(); |
| m_imp->computeCacheVector(); |
| |
| m_imp->m_negativeThicknessPoints = 0; |
| for (UINT j = 0; j < m_imp->m_centerLineArray.size(); j++) { |
| if (m_imp->m_centerLineArray[j]->getThickP0().thick <= 0) |
| m_imp->m_negativeThicknessPoints++; |
| if (m_imp->m_centerLineArray[j]->getThickP1().thick <= 0) |
| m_imp->m_negativeThicknessPoints++; |
| } |
| if (!m_imp->m_centerLineArray.empty() && |
| m_imp->m_centerLineArray.back()->getThickP2().thick <= 0) |
| m_imp->m_negativeThicknessPoints++; |
| } |
| |
| |
| |
| void TStroke::reduceControlPoints(double error, vector<int> corners) { |
| double step, quadLen; |
| vector<TThickPoint> tempVect, controlPoints; |
| TStroke *tempStroke = 0; |
| double missedLen = 0; |
| UINT cp, nextQuad, quadI, cornI, cpSize, size; |
| |
| const TThickQuadratic *quad = m_imp->m_centerLineArray.front(); |
| |
| size = corners.size(); |
| assert(size > 1); |
| if (size < 2) { |
| |
| corners.resize(2); |
| corners[0] = 0; |
| corners[1] = m_imp->m_centerLineArray.size(); |
| } |
| |
| |
| for (cornI = 0; cornI < size - 1; ++cornI) { |
| tempVect.clear(); |
| |
| nextQuad = corners[cornI + 1]; |
| if (nextQuad > m_imp->m_centerLineArray.size()) { |
| assert(!"bad quadric index"); |
| return; |
| } |
| if (corners[cornI] >= (int)m_imp->m_centerLineArray.size()) { |
| assert(!"bad quadric index"); |
| return; |
| } |
| |
| for (quadI = corners[cornI]; quadI < nextQuad; quadI++) { |
| quad = getChunk(quadI); |
| quadLen = quad->getLength(); |
| |
| missedLen += quadLen; |
| if (quadLen && (missedLen > 1 || quadI == 0 || |
| quadI == nextQuad - 1)) |
| { |
| missedLen = 0; |
| step = 1.0 / quadLen; |
| |
| |
| |
| |
| |
| |
| |
| for (double t = 0; t < 1.0; t += step) |
| tempVect.push_back(quad->getThickPoint(t)); |
| } |
| } |
| tempVect.push_back(quad->getThickP2()); |
| tempStroke = TStroke::interpolate(tempVect, error, false); |
| |
| cpSize = tempStroke->getControlPointCount(); |
| for (cp = 0; cp < cpSize - 1; cp++) { |
| controlPoints.push_back(tempStroke->getControlPoint(cp)); |
| } |
| delete tempStroke; |
| tempStroke = 0; |
| } |
| controlPoints.push_back(m_imp->m_centerLineArray.back()->getThickP2()); |
| |
| #ifdef _DEBUG |
| cpSize = controlPoints.size(); |
| for (cp = 1; cp < cpSize; cp++) |
| assert(!(controlPoints[cp - 1] == controlPoints[cp])); |
| #endif |
| |
| reshape(&(controlPoints[0]), controlPoints.size()); |
| invalidate(); |
| } |
| |
| |
| |
| void TStroke::reduceControlPoints(double error) { |
| vector<int> corners; |
| corners.push_back(0); |
| detectCorners(this, 10, corners); |
| corners.push_back(getChunkCount()); |
| reduceControlPoints(error, corners); |
| } |
| |
| |
| |
| double TStroke::getAverageThickness() const { |
| return m_imp->m_averageThickness; |
| } |
| |
| double TStroke::getMaxThickness() { |
| if (m_imp->m_maxThickness == -1) m_imp->computeMaxThickness(); |
| return m_imp->m_maxThickness; |
| } |
| |
| void TStroke::setAverageThickness(double thickness) { |
| m_imp->m_averageThickness = thickness; |
| } |
| |
| bool TStroke::operator==(const TStroke &s) const { |
| if (getChunkCount() != s.getChunkCount()) return false; |
| int i; |
| for (i = 0; i < getChunkCount(); i++) { |
| const TThickQuadratic *chanck = getChunk(i); |
| const TThickQuadratic *sChanck = s.getChunk(i); |
| if (chanck->getThickP0() != sChanck->getThickP0() || |
| chanck->getThickP1() != sChanck->getThickP1() || |
| chanck->getThickP2() != sChanck->getThickP2()) |
| return false; |
| } |
| return true; |
| } |
| |
| |
| |
| int TStroke::getChunkCount() const { return m_imp->getChunkCount(); } |
| |
| |
| |
| int TStroke::getControlPointCount() const { |
| return m_imp->getControlPointCount(); |
| } |
| |
| |
| |
| const TThickQuadratic *TStroke::getChunk(int index) const { |
| return m_imp->getChunk(index); |
| } |
| |
| |
| |
| TRectD TStroke::getBBox(double w0, double w1) const { |
| if (w0 > w1) std::swap(w0, w1); |
| |
| if (w0 != 0.0 || w1 != 1.0) return m_imp->computeSubBBox(w0, w1); |
| |
| if (m_imp->m_flag & c_dirty_flag) ((TStroke *)this)->computeBBox(); |
| |
| return m_imp->m_bBox; |
| } |
| |
| |
| |
| void TStroke::computeBBox() { |
| m_imp->m_bBox = TOutlineUtil::computeBBox(*this); |
| m_imp->m_flag &= ~c_dirty_flag; |
| } |
| |
| |
| |
| TRectD TStroke::getCenterlineBBox() const { |
| return m_imp->computeCenterlineBBox(); |
| } |
| |
| |
| |
| void TStroke::disableComputeOfCaches() { |
| m_imp->m_areDisabledComputeOfCaches = true; |
| } |
| |
| |
| |
| void TStroke::enableComputeOfCaches() { |
| m_imp->m_areDisabledComputeOfCaches = false; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| TThickPoint TStroke::getThickPointAtLength(double s) const { |
| assert(!m_imp->m_centerLineArray.empty()); |
| |
| if (s <= 0) return getControlPoint(0); |
| |
| if (s >= getLength()) return getControlPoint(getControlPointCount() - 1); |
| |
| int chunk; |
| double tOfDivision; |
| |
| bool error = |
| m_imp->retrieveChunkAndItsParamameterAtLength(s, chunk, tOfDivision); |
| |
| if (error) |
| error = |
| m_imp->retrieveChunkAndItsParamameterAtLength(s, chunk, tOfDivision); |
| |
| assert(!error); |
| |
| if (error) return getControlPoint(0); |
| |
| return getChunk(chunk)->getThickPoint(tOfDivision); |
| } |
| |
| |
| |
| TThickPoint TStroke::getThickPoint(double w) const { |
| assert(!m_imp->m_centerLineArray.empty()); |
| |
| if (w < 0) return getControlPoint(0); |
| |
| if (w > 1.0) return getControlPoint(getControlPointCount() - 1); |
| |
| int chunk = 0; |
| double t = 0; |
| |
| bool error = m_imp->retrieveChunkAndItsParamameter(w, chunk, t); |
| |
| assert(!error); |
| |
| if (error) return getControlPoint(0); |
| |
| return getChunk(chunk)->getThickPoint(t); |
| } |
| |
| |
| |
| double TStroke::getParameterAtLength(double s) const { |
| if (s <= 0) |
| return 0; |
| else if (s >= getLength()) |
| return 1; |
| |
| int chunk; |
| double t; |
| |
| if (!m_imp->retrieveChunkAndItsParamameterAtLength(s, chunk, t)) { |
| DoublePair p = m_imp->retrieveParametersFromChunk(chunk); |
| |
| return proportion(p.second, t, 1.0, p.first); |
| } else if (chunk < (int)getChunkCount() && t == -1) |
| return getParameterAtControlPoint(2 * chunk); |
| |
| return 1.0; |
| } |
| |
| |
| |
| double TStroke::getParameterAtControlPoint(int n) const { |
| double out = -1; |
| |
| if (0 <= n && n < getControlPointCount()) out = m_imp->getW(n); |
| |
| |
| |
| |
| if (0.0 > out) return 0.0; |
| if (out > 1.0) return 1.0; |
| return out; |
| } |
| |
| |
| |
| double TStroke::getW(int chunkIndex, double t) const { |
| DoublePair parRange = m_imp->retrieveParametersFromChunk(chunkIndex); |
| |
| double w = proportion(parRange.second, t, 1.0, parRange.first); |
| |
| assert(0 <= w && w <= 1.0); |
| |
| return w; |
| } |
| |
| |
| |
| double TStroke::getW(const TPointD &p) const { |
| int chunkIndex; |
| |
| double tOfchunk, distance2 = (numeric_limits<double>::max)(); |
| |
| |
| getNearestChunk(p, tOfchunk, chunkIndex, distance2, false); |
| |
| assert(0 <= chunkIndex && chunkIndex <= getChunkCount()); |
| |
| DoublePair parRange = m_imp->retrieveParametersFromChunk(chunkIndex); |
| |
| double t = proportion(parRange.second, tOfchunk, 1.0, parRange.first); |
| |
| assert(0 <= t && t <= 1.0); |
| |
| return t; |
| } |
| |
| |
| |
| bool TStroke::getSpeedTwoValues(double w, TPointD &speed0, |
| TPointD &speed1) const { |
| bool ret = false; |
| |
| assert(!m_imp->m_centerLineArray.empty()); |
| |
| if (w < 0) { |
| speed0 = m_imp->m_centerLineArray.front()->getSpeed(0.0); |
| return ret; |
| } |
| |
| if (w > 1.0) { |
| speed0 = m_imp->m_centerLineArray.back()->getSpeed(1.0); |
| return ret; |
| } |
| |
| int chunk; |
| double t; |
| |
| bool error = m_imp->retrieveChunkAndItsParamameter(w, chunk, t); |
| |
| assert(!error); |
| |
| if (error) { |
| speed0 = m_imp->m_centerLineArray.front()->getSpeed(0.0); |
| speed1 = -speed0; |
| return ret; |
| } |
| |
| speed0 = getChunk(chunk)->getSpeed(t); |
| speed1 = -speed0; |
| |
| if (areAlmostEqual(t, 0.0, 1e-9) && chunk > 0 && |
| (speed1 = -getChunk(chunk - 1)->getSpeed(1.0)) != -speed0) |
| ret = true; |
| else if (areAlmostEqual(t, 1.0, 1e-9) && chunk < getChunkCount() - 1 && |
| (speed1 = -getChunk(chunk + 1)->getSpeed(0.0)) != -speed0) { |
| TPointD aux = -speed0; |
| speed0 = -speed1; |
| speed1 = aux; |
| ret = true; |
| } |
| |
| if (speed0 == TPointD()) |
| { |
| while (speed0 == TPointD()) { |
| speed0 = getChunk(chunk--)->getSpeed(1.0); |
| if (chunk <= 0) break; |
| } |
| chunk = 0; |
| while (speed0 == TPointD()) { |
| speed0 = getChunk(chunk++)->getSpeed(0.0); |
| if (chunk >= getChunkCount() - 1) break; |
| } |
| |
| if (speed0 == TPointD()) { |
| if (getChunkCount() == 1) { |
| const TThickQuadratic *q = getChunk(0); |
| |
| if (q->getP0() == q->getP1() && q->getP1() != q->getP2()) |
| speed0 = TSegment(q->getP1(), q->getP2()).getSpeed(t); |
| else if (q->getP1() == q->getP2() && q->getP0() != q->getP1()) |
| speed0 = TSegment(q->getP0(), q->getP1()).getSpeed(t); |
| else |
| assert(speed0 != TPointD()); |
| } else |
| assert(speed0 != TPointD()); |
| } |
| } |
| return ret; |
| } |
| |
| |
| TPointD TStroke::getSpeed(double w, bool outSpeed) const { |
| assert(!m_imp->m_centerLineArray.empty()); |
| |
| if (w < 0) return m_imp->m_centerLineArray.front()->getSpeed(0.0); |
| |
| if (w > 1.0) return m_imp->m_centerLineArray.back()->getSpeed(1.0); |
| |
| int chunk; |
| double t; |
| |
| bool error = m_imp->retrieveChunkAndItsParamameter(w, chunk, t); |
| if (t == 1 && outSpeed && chunk < getChunkCount() - 1) { |
| chunk++; |
| t = 0; |
| } |
| assert(!error); |
| |
| if (error) return m_imp->m_centerLineArray.front()->getSpeed(0.0); |
| |
| TPointD speed = getChunk(chunk)->getSpeed(t); |
| |
| if (speed == TPointD()) |
| { |
| while (speed == TPointD()) { |
| speed = getChunk(chunk--)->getSpeed(1.0); |
| if (chunk <= 0) break; |
| } |
| chunk = 0; |
| while (speed == TPointD()) { |
| speed = getChunk(chunk++)->getSpeed(0.0); |
| if (chunk >= getChunkCount() - 1) break; |
| } |
| if (speed == TPointD()) { |
| if (getChunkCount() == 1) { |
| const TThickQuadratic *q = getChunk(0); |
| |
| if (q->getP0() == q->getP1() && q->getP1() != q->getP2()) |
| return TSegment(q->getP1(), q->getP2()).getSpeed(t); |
| else if (q->getP1() == q->getP2() && q->getP0() != q->getP1()) |
| return TSegment(q->getP0(), q->getP1()).getSpeed(t); |
| else |
| assert(speed != TPointD()); |
| } else |
| assert(speed != TPointD()); |
| } |
| } |
| return speed; |
| } |
| |
| |
| |
| TPointD TStroke::getSpeedAtLength(double s) const { |
| double t = getParameterAtLength(s); |
| return getSpeed(t); |
| } |
| |
| |
| |
| double TStroke::getLengthAtControlPoint(int n) const { |
| m_imp->computeCacheVector(); |
| |
| if (n >= getControlPointCount()) return m_imp->m_partialLengthArray.back(); |
| |
| if (n <= 0) return m_imp->m_partialLengthArray.front(); |
| |
| return m_imp->m_partialLengthArray[n]; |
| } |
| |
| |
| |
| void TStroke::split(double w, TStroke &f, TStroke &s) const { |
| int chunk; |
| double t; |
| f.m_imp->m_maxThickness = -1; |
| s.m_imp->m_maxThickness = -1; |
| if (!m_imp->retrieveChunkAndItsParamameter(w, chunk, t)) { |
| assert(0 <= chunk && chunk < getChunkCount()); |
| assert(0 <= w && w <= 1.0); |
| assert(0 <= t && t <= 1.0); |
| |
| QuadStrokeChunkArray &chunkArray = m_imp->m_centerLineArray; |
| |
| |
| TThickQuadratic *tq1 = new TThickQuadratic, *tq2 = new TThickQuadratic; |
| |
| chunkArray[chunk]->split(t, *tq1, *tq2); |
| |
| |
| QuadStrokeChunkArray vTQ; |
| |
| |
| int i; |
| for (i = 0; i < chunk; ++i) vTQ.push_back(chunkArray[i]); |
| |
| |
| if (tq1->getLength() != 0.0 || w == 0.0 || vTQ.empty()) vTQ.push_back(tq1); |
| |
| |
| TStroke *ts1 = TStroke::create(vTQ); |
| if (!ts1) ts1 = new TStroke; |
| ts1->swapGeometry(f); |
| |
| |
| vTQ.clear(); |
| |
| |
| if (tq2->getLength() != 0.0 || w == 1.0 || getChunkCount() == 0) |
| vTQ.push_back(tq2); |
| |
| for (i = chunk + 1; i < getChunkCount(); ++i) vTQ.push_back(chunkArray[i]); |
| |
| TStroke *ts2 = TStroke::create(vTQ); |
| if (!ts2) ts2 = new TStroke; |
| ts2->swapGeometry(s); |
| |
| |
| f.setStyle(getStyle()); |
| s.setStyle(getStyle()); |
| f.outlineOptions() = s.outlineOptions() = outlineOptions(); |
| |
| delete ts2; |
| delete ts1; |
| |
| delete tq1; |
| delete tq2; |
| if (f.getControlPointCount() == 3 && |
| f.getControlPoint(0) != |
| f.getControlPoint(2)) |
| |
| |
| |
| f.insertControlPoints(0.5); |
| if (s.getControlPointCount() == 3 && |
| s.getControlPoint(0) != |
| s.getControlPoint(2)) |
| |
| |
| |
| s.insertControlPoints(0.5); |
| } |
| } |
| |
| |
| |
| void TStroke::print(ostream &os) const { |
| |
| const TThickQuadratic *q; |
| |
| os << "Punti di controllo\n"; |
| for (int i = 0; i < getChunkCount(); ++i) { |
| os << "quad #" << i << ":" << endl; |
| q = getChunk(i); |
| |
| os << " P0:" << q->getThickP0().x << ", " << q->getThickP0().y << ", " |
| << q->getThickP0().thick << endl; |
| os << " P1:" << q->getThickP1().x << ", " << q->getThickP1().y << ", " |
| << q->getThickP1().thick << endl; |
| assert(i == getChunkCount() - 1 || |
| (getChunk(i)->getThickP2() == getChunk(i + 1)->getThickP0())); |
| } |
| |
| q = getChunk(getChunkCount() - 1); |
| |
| os << " P2:" << q->getThickP2().x << ", " << q->getThickP2().y << ", " |
| << q->getThickP2().thick << endl; |
| } |
| |
| |
| |
| void TStroke::transform(const TAffine &aff, bool doChangeThickness) { |
| for (UINT i = 0; i < m_imp->m_centerLineArray.size(); ++i) { |
| TThickQuadratic &ref = *m_imp->m_centerLineArray[i]; |
| ref = transformQuad(aff, ref, doChangeThickness); |
| |
| if (doChangeThickness) { |
| double det = aff.det(); |
| if (det == 0) m_imp->m_negativeThicknessPoints = getControlPointCount(); |
| if (m_imp->m_maxThickness != -1) m_imp->m_maxThickness *= sqrt(fabs(det)); |
| |
| |
| } |
| } |
| |
| invalidate(); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| TStrokeProp *TStroke::getProp() const { |
| #if !defined(DISEGNO_OUTLINE) |
| if (!m_imp->m_styleId) return 0; |
| #endif |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| return m_imp->m_prop; |
| } |
| |
| |
| |
| void TStroke::setProp(TStrokeProp *prop) { |
| assert(prop); |
| delete m_imp->m_prop; |
| m_imp->m_prop = prop; |
| } |
| |
| |
| |
| int TStroke::getStyle() const { return m_imp->m_styleId; } |
| |
| |
| |
| void TStroke::setStyle(int styleId) { |
| m_imp->m_styleId = styleId; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| |
| |
| |
| TStroke &TStroke::changeDirection() { |
| UINT chunkCount = getChunkCount(); |
| UINT to = tfloor(chunkCount * 0.5); |
| UINT i; |
| |
| if (chunkCount & 1) changeTQDirection(m_imp->m_centerLineArray[to]); |
| |
| --chunkCount; |
| |
| for (i = 0; i < to; ++i) { |
| changeTQDirection(m_imp->m_centerLineArray[i]); |
| changeTQDirection(m_imp->m_centerLineArray[chunkCount - i]); |
| TThickQuadratic *q1 = m_imp->m_centerLineArray[i]; |
| m_imp->m_centerLineArray[i] = m_imp->m_centerLineArray[chunkCount - i]; |
| m_imp->m_centerLineArray[chunkCount - i] = q1; |
| } |
| invalidate(); |
| |
| return *this; |
| } |
| |
| |
| |
| void TStroke::setFlag(TStrokeFlag flag, bool status) { |
| if (status) |
| m_imp->m_flag |= flag; |
| else |
| m_imp->m_flag &= ~flag; |
| } |
| |
| |
| |
| bool TStroke::getFlag(TStrokeFlag flag) const { |
| return (m_imp->m_flag & flag) != 0; |
| } |
| |
| |
| |
| void TStroke::swap(TStroke &ref) { |
| std::swap(m_imp, ref.m_imp); |
| |
| |
| if (m_imp->m_prop) m_imp->m_prop->setStroke(this); |
| if (ref.m_imp->m_prop) ref.m_imp->m_prop->setStroke(&ref); |
| |
| |
| |
| std::swap(m_imp->m_id, ref.m_imp->m_id); |
| } |
| |
| |
| |
| void TStroke::swapGeometry(TStroke &ref) { m_imp->swapGeometry(*ref.m_imp); } |
| |
| |
| |
| int TStroke::getId() const { return m_imp->m_id; } |
| |
| |
| |
| void TStroke::setId(int id) { m_imp->m_id = id; } |
| |
| |
| |
| |
| TThickPoint TStroke::getCentroid() const { |
| double totalLen = getLength(); |
| |
| if (totalLen == 0) return getControlPoint(0); |
| |
| double step = totalLen * 0.1; |
| double len = 0; |
| if (step > 10.0) step = 10.0; |
| int count = 0; |
| TThickPoint point; |
| for (; len <= totalLen; len += step) { |
| count++; |
| point += getThickPointAtLength(len); |
| } |
| return point * (1.0 / (double)count); |
| } |
| |
| |
| |
| void TStroke::setSelfLoop(bool loop) { |
| if (loop) { |
| |
| |
| const int cpCount = this->getControlPointCount(); |
| |
| TThickPoint p, p0 = this->getControlPoint(0), |
| pn = this->getControlPoint(cpCount - 1); |
| |
| p = (p0 + pn) * 0.5; |
| |
| this->setControlPoint(0, p); |
| this->setControlPoint(cpCount - 1, p); |
| } |
| m_imp->m_selfLoop = loop; |
| } |
| |
| |
| |
| bool TStroke::isSelfLoop() const { return m_imp->m_selfLoop; } |
| |
| |
| |
| bool TStroke::isCenterLine() const { |
| assert(m_imp->m_negativeThicknessPoints <= getControlPointCount()); |
| |
| return m_imp->m_negativeThicknessPoints == getControlPointCount(); |
| } |
| |
| |
| |
| TStroke::OutlineOptions &TStroke::outlineOptions() { |
| return m_imp->m_outlineOptions; |
| } |
| |
| |
| |
| const TStroke::OutlineOptions &TStroke::outlineOptions() const { |
| return m_imp->m_outlineOptions; |
| } |
| |
| |
| |
| |
| |
| |
| |
| TStroke *joinStrokes(const TStroke *s0, const TStroke *s1) { |
| TStroke *newStroke; |
| |
| if (s0 == s1) { |
| newStroke = new TStroke(*s0); |
| newStroke->setSelfLoop(); |
| return newStroke; |
| } |
| int i; |
| vector<TThickPoint> v; |
| for (i = 0; i < s0->getControlPointCount(); i++) |
| v.push_back(s0->getControlPoint(i)); |
| if (areAlmostEqual(v.back(), s1->getControlPoint(0), 1e-3)) |
| for (i = 1; i < s1->getControlPointCount(); i++) |
| v.push_back(s1->getControlPoint(i)); |
| else if (areAlmostEqual(v.back(), |
| s1->getControlPoint(s1->getControlPointCount() - 1), |
| 1e-3)) |
| for (i = s1->getControlPointCount() - 2; i >= 0; i--) |
| v.push_back(s1->getControlPoint(i)); |
| else |
| assert(false); |
| |
| newStroke = new TStroke(v); |
| newStroke->setStyle(s0->getStyle()); |
| newStroke->outlineOptions() = s0->outlineOptions(); |
| |
| return newStroke; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| |
| namespace { |
| |
| int local_intersect(const TStroke &stroke, const TSegment &segment, |
| std::vector<DoublePair> &intersections, |
| bool strokeIsFirst) { |
| const TThickQuadratic *chunk; |
| for (int i = 0; i < stroke.getChunkCount(); i++) { |
| std::vector<DoublePair> localIntersections; |
| chunk = stroke.getChunk(i); |
| |
| if (intersect(*chunk, segment, localIntersections)) { |
| for (UINT j = 0; j < localIntersections.size(); j++) { |
| double strokePar = localIntersections[j].first; |
| |
| strokePar = stroke.getW(chunk->getPoint(strokePar)); |
| |
| |
| DoublePair sol( |
| strokeIsFirst ? strokePar : localIntersections[j].second, |
| strokeIsFirst ? localIntersections[j].second : strokePar); |
| |
| std::vector<DoublePair>::iterator it_end = intersections.end(); |
| |
| if (it_end == std::find(intersections.begin(), it_end, sol)) |
| intersections.push_back(sol); |
| } |
| } |
| } |
| return intersections.size(); |
| } |
| |
| |
| |
| bool almostOverlaps(const TRectD &r0, const TRectD &r1) { |
| if (r0.overlaps(r1)) return true; |
| |
| if ((r0.x1 < r1.x0) && (areAlmostEqual(r0.x1, r1.x0, 1e-5))) |
| return true; |
| else if ((r1.x1 < r0.x0) && (areAlmostEqual(r1.x1, r0.x0, 1e-5))) |
| return true; |
| if ((r0.y1 < r1.y0) && (areAlmostEqual(r0.y1, r1.y0, 1e-5))) |
| return true; |
| else if ((r1.y1 < r0.y0) && (areAlmostEqual(r1.y1, r0.y0, 1e-5))) |
| return true; |
| |
| return false; |
| } |
| |
| |
| |
| bool greaterThanOneAndLesserThanZero(double val) { |
| if (val > 1.0 || val < 0.0) return true; |
| return false; |
| } |
| } |
| |
| |
| |
| int intersect(const TStroke *s1, const TStroke *s2, |
| std::vector<DoublePair> &intersections, bool checkBBox) { |
| UINT k = 0; |
| |
| intersections.clear(); |
| |
| if (checkBBox && !s1->getBBox().overlaps(s2->getBBox())) return 0; |
| |
| for (int i = 0; i < s1->getChunkCount(); i++) { |
| const TQuadratic *q1 = s1->getChunk(i); |
| if (q1->getP0() == q1->getP2() && q1->getP1() == q1->getP2()) continue; |
| int j = 0; |
| if (s1 == |
| s2) |
| |
| |
| { |
| j = i + 2; |
| |
| while (j <= s2->getChunkCount()) { |
| const TQuadratic *qAux = s2->getChunk(j - 1); |
| if (qAux->getP0() != qAux->getP2() || qAux->getP1() != qAux->getP2()) |
| break; |
| j++; |
| } |
| } |
| |
| for (; j < s2->getChunkCount(); j++) { |
| const TQuadratic *q2 = s2->getChunk(j); |
| if (q2->getP0() == q2->getP2() && q2->getP1() == q2->getP2()) continue; |
| |
| if (!almostOverlaps(q1->getBBox(), q2->getBBox())) continue; |
| #ifdef CHECK_DEGLI_ESTREMI |
| vector<DoublePair> quadIntersections1; |
| if (i == 0 || i == s1->getChunkCount() - 1) && (j==0 || j==s2->getChunkCount()-1)) |
| { |
| TPointD pp1 = (i == 0 ? q1->getP0() : q1->getP2()); |
| TPointD pp2 = (j == 0 ? q2->getP0() : q2->getP2()); |
| if (areAlmostEqual(pp1, pp2)) { |
| intersections.push_back( |
| DoublePair(i == 0 ? 0.0 : 1.0, j == 0 ? 0.0 : 1.0)); |
| k++; |
| continue; |
| } |
| if (s1->getChunkCount() == 1 && s2->getChunkCount() == 1) { |
| TPointD pp1 = q1->getP2(); |
| TPointD pp2 = q2->getP2(); |
| if (areAlmostEqual(pp1, pp2)) { |
| intersections.push_back(DoublePair(1.0, 1.0)); |
| k++; |
| continue; |
| } |
| } |
| if (s1->getChunkCount() == 1) { |
| TPointD pp1 = q1->getP2(); |
| TPointD pp2 = (j == 0 ? q2->getP0() : q2->getP2()); |
| if (areAlmostEqual(pp1, pp2)) { |
| intersections.push_back(DoublePair(1.0, j == 0 ? 0.0 : 1.0)); |
| k++; |
| continue; |
| } |
| } |
| if (s2->getChunkCount() == 1) { |
| TPointD pp1 = (i == 0 ? q1->getP0() : q1->getP2()); |
| TPointD pp2 = q2->getP2(); |
| if (areAlmostEqual(pp1, pp2)) { |
| intersections.push_back(DoublePair(i == 0 ? 0.0 : 1.0, 1.0)); |
| k++; |
| continue; |
| } |
| } |
| } |
| #endif |
| |
| |
| |
| |
| |
| if (*q1 == *q2 || |
| (q1->getP0() == q2->getP2() && q1->getP1() == q2->getP1() && |
| q1->getP2() == q2->getP0())) { |
| |
| continue; |
| } |
| |
| |
| vector<DoublePair> quadIntersections; |
| if (intersect(*q1, *q2, quadIntersections)) |
| for (int h = 0; h < (int)quadIntersections.size(); h++) { |
| DoublePair res(getWfromChunkAndT(s1, i, quadIntersections[h].first), |
| getWfromChunkAndT(s2, j, quadIntersections[h].second)); |
| |
| if (areAlmostEqual(quadIntersections[h].first, 0) || |
| areAlmostEqual(quadIntersections[h].first, 1) || |
| areAlmostEqual(quadIntersections[h].second, 0) || |
| areAlmostEqual(quadIntersections[h].second, 1)) { |
| int q = 0; |
| for (q = 0; q < (int)intersections.size(); q++) |
| if (areAlmostEqual(intersections[q].first, res.first, 1e-8) && |
| areAlmostEqual(intersections[q].second, res.second, 1e-8)) |
| break; |
| if (q < (int)intersections.size()) continue; |
| } |
| intersections.push_back(res); |
| |
| |
| k++; |
| } |
| } |
| } |
| if (s1 == s2 && |
| (s1->isSelfLoop() || s1->getPoint(0.0) == s1->getPoint(1.0))) { |
| int i; |
| for (i = 0; i < (int)intersections.size(); i++) { |
| assert(!(areAlmostEqual(intersections[i].first, 1.0, 1e-1) && |
| areAlmostEqual(intersections[i].second, 0.0, 1e-1))); |
| if (areAlmostEqual(intersections[i].first, 0.0, 1e-1) && |
| areAlmostEqual(intersections[i].second, 1.0, 1e-1)) |
| break; |
| } |
| if (i == (int)intersections.size()) { |
| intersections.push_back(DoublePair(0.0, 1.0)); |
| k++; |
| } |
| } |
| |
| return k; |
| } |
| |
| |
| |
| int intersect(const TSegment &segment, const TStroke &stroke, |
| std::vector<DoublePair> &intersections) { |
| return local_intersect(stroke, segment, intersections, false); |
| } |
| |
| |
| |
| int intersect(const TStroke &stroke, const TSegment &segment, |
| std::vector<DoublePair> &intersections) { |
| return local_intersect(stroke, segment, intersections, true); |
| } |
| |
| |
| |
| int intersect(const TStroke &stroke, const TPointD ¢er, double radius, |
| vector<double> &intersections) { |
| |
| |
| |
| |
| |
| |
| |
| |
| const int a = 2; |
| const int b = 1; |
| const int c = 0; |
| |
| vector<TPointD> bez(3); |
| vector<TPointD> pol(3); |
| vector<double> coeff(5); |
| |
| for (int chunk = 0; chunk < stroke.getChunkCount(); ++chunk) { |
| const TThickQuadratic *tq = stroke.getChunk(chunk); |
| |
| bez[0] = tq->getP0(); |
| bez[1] = tq->getP1(); |
| bez[2] = tq->getP2(); |
| |
| bezier2poly(bez, pol); |
| |
| pol[c] -= center; |
| |
| coeff[4] = sq(pol[a].x) + sq(pol[a].y); |
| coeff[3] = 2.0 * (pol[a].x * pol[b].x + pol[a].y * pol[b].y); |
| coeff[2] = 2.0 * (pol[a].x * pol[c].x + pol[a].y * pol[c].y) + |
| sq(pol[b].x) + sq(pol[b].y); |
| coeff[1] = 2.0 * (pol[b].x * pol[c].x + pol[b].y * pol[c].y); |
| coeff[0] = sq(pol[c].x) + sq(pol[c].y) - sq(radius); |
| |
| vector<double> sol; |
| rootFinding(coeff, sol); |
| |
| sol.erase( |
| remove_if(sol.begin(), sol.end(), greaterThanOneAndLesserThanZero), |
| sol.end()); |
| |
| for (UINT j = 0; j < sol.size(); ++j) |
| intersections.push_back(getWfromChunkAndT(&stroke, chunk, sol[j])); |
| } |
| |
| #if defined(DEBUG) || defined(_DEBUG) |
| |
| |
| |
| |
| |
| |
| |
| vector<double> test; |
| |
| adjacent_difference(intersections.begin(), intersections.end(), |
| back_inserter(test)); |
| |
| while (!test.empty()) { |
| assert(test.back() >= 0); |
| test.pop_back(); |
| } |
| |
| #endif |
| |
| return intersections.size(); |
| } |
| |
| |
| |
| void splitStroke(const TStroke &tq, const vector<double> &pars, |
| std::vector<TStroke *> &v) { |
| if (pars.empty()) return; |
| |
| UINT i, vSize = pars.size(); |
| |
| vector<double> length(vSize); |
| |
| |
| for (i = 0; i < vSize; ++i) length[i] = tq.getLength(pars[i]); |
| |
| std::adjacent_difference(length.begin(), length.end(), length.begin()); |
| |
| TStroke *q1, q2, q3; |
| |
| q1 = new TStroke(); |
| tq.split(pars[0], *q1, q2); |
| |
| assert(areAlmostEqual(q1->getLength(), length[0], 1e-4)); |
| v.push_back(q1); |
| |
| for (i = 1; i < vSize; ++i) { |
| q1 = new TStroke(); |
| double par = q2.getParameterAtLength(length[i]); |
| assert(0 <= par && par <= 1.0); |
| q2.split(par, *q1, q3); |
| |
| assert(areAlmostEqual(q1->getLength(), length[i], 1e-4)); |
| v.push_back(q1); |
| q2 = q3; |
| } |
| |
| v.push_back(new TStroke(q2)); |
| } |
| |
| |
| |
| void detectCorners(const TStroke *stroke, double minDegree, |
| std::vector<int> &corners) { |
| const double minSin = fabs(sin(minDegree * M_PI_180)); |
| |
| const TThickQuadratic *quad1 = 0; |
| const TThickQuadratic *quad2 = 0; |
| UINT quadCount1 = stroke->getChunkCount(); |
| TPointD speed1, speed2; |
| |
| TPointD tan1, tan2; |
| quad1 = stroke->getChunk(0); |
| for (UINT j = 1; j < quadCount1; j++) { |
| quad2 = stroke->getChunk(j); |
| |
| speed1 = quad1->getSpeed(1); |
| speed2 = quad2->getSpeed(0); |
| if (!(speed1 == TPointD() || speed2 == TPointD())) { |
| tan1 = normalize(speed1); |
| tan2 = normalize(speed2); |
| if (tan1 * tan2 < 0 || fabs(cross(tan1, tan2)) >= minSin) |
| corners.push_back(j); |
| } |
| quad1 = quad2; |
| } |
| } |
| |
| |
| |
| |
| namespace { |
| |
| |
| |
| bool removeNullQuadratic(TStroke *stroke, bool checkThickness = true) { |
| vector<TThickPoint> points; |
| UINT i, qCount = stroke->getChunkCount(); |
| const TThickQuadratic *q; |
| TThickPoint p1, p2, p3; |
| |
| |
| if (qCount == 1) return false; |
| |
| bool check = false; |
| |
| for (i = 0; i != qCount; i++) { |
| q = stroke->getChunk(i); |
| p1 = q->getThickP0(); |
| p2 = q->getThickP1(); |
| p3 = q->getThickP2(); |
| |
| if (areAlmostEqual(p1.x, p2.x) && areAlmostEqual(p2.x, p3.x) && |
| areAlmostEqual(p1.y, p2.y) && areAlmostEqual(p2.y, p3.y) && |
| (!checkThickness || (areAlmostEqual(p1.thick, p2.thick) && |
| areAlmostEqual(p2.thick, p3.thick)))) { |
| |
| check = true; |
| } else { |
| points.push_back(p1); |
| points.push_back(p2); |
| } |
| } |
| |
| if (check) { |
| points.push_back(p3); |
| stroke->reshape(&(points[0]), points.size()); |
| } |
| |
| return check; |
| } |
| |
| |
| |
| inline T3DPointD thickPntTo3DPnt(const TThickPoint &p0) { |
| return T3DPointD(p0.x, p0.y, p0.thick); |
| } |
| |
| |
| |
| |
| void convert(const vector<TThickPoint> &from, vector<T3DPointD> &to) { |
| to.resize(from.size()); |
| transform(from.begin(), from.end(), to.begin(), thickPntTo3DPnt); |
| } |
| |
| typedef vector<TThickCubic *> TThickCubicArray; |
| typedef vector<TThickQuadratic *> QuadStrokeChunkArray; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| double *chordLengthParameterize3D(const T3DPointD *pointsArrayBegin, int size) { |
| double *u = new double[size]; |
| u[0] = 0.0; |
| int i; |
| for (i = 1; i < size; i++) |
| u[i] = u[i - 1] + |
| tdistance(*(pointsArrayBegin + i), *(pointsArrayBegin + i - 1)); |
| for (i = 1; i < size; i++) { |
| assert(!isAlmostZero(u[size - 1])); |
| u[i] = u[i] / u[size - 1]; |
| } |
| return u; |
| } |
| |
| |
| |
| |
| |
| |
| |
| double computeMaxError3D(const TThickCubic &cubic, |
| const T3DPointD *pointsArrayBegin, int size, double *u, |
| int &splitPoint) { |
| double err, maxErr = 0; |
| splitPoint = 0; |
| |
| for (int i = 1; i < size - 1; i++) { |
| #ifdef USE_NEW_3D_ERROR_COMPUTE |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const TThickPoint &A(cubic.getThickPoint(u[i])); |
| const T3DPointD &B(pointsArrayBegin[i]); |
| |
| err = sqrt(sq(B.x - A.x) + sq(B.y - A.y)) + fabs(B.z - A.thick); |
| |
| #else |
| |
| |
| |
| |
| |
| |
| T3DPointD delta = |
| thickPntTo3DPnt(cubic.getThickPoint(u[i])) - *(pointsArrayBegin + i); |
| |
| double thick = cubic.getThickPoint(u[i]).thick; |
| if (thick <= 2.0) thick = 2.0; |
| |
| err = norm2(TPointD(delta.x, delta.y)) / thick; |
| |
| if (fabs(delta.z) > 2.0) err = err + fabs(delta.z); |
| |
| #endif |
| |
| if (err >= maxErr) { |
| maxErr = err; |
| splitPoint = i; |
| } |
| } |
| |
| return maxErr; |
| } |
| |
| |
| double NewtonRaphsonRootFind3D(const TThickCubic &cubic, const T3DPointD &p3D, |
| double u) { |
| TPointD qU = cubic.getPoint(u); |
| TPointD q1U = cubic.getSpeed(u); |
| TPointD q2U = cubic.getAcceleration(u); |
| |
| TPointD p(p3D.x, p3D.y); |
| |
| return u - ((qU - p) * q1U) / (norm2(q1U) + (qU - p) * q2U); |
| } |
| |
| |
| int compareDouble(const void *e1, const void *e2) { |
| return (*(double *)e1 < *(double *)e2) |
| ? -1 |
| : (*(double *)e1 == *(double *)e2) ? 0 : 1; |
| } |
| |
| |
| double *reparameterize3D(const TThickCubic &cubic, |
| const T3DPointD *pointsArrayBegin, int size, |
| double *u) { |
| double *uPrime = new double[size]; |
| |
| for (int i = 0; i < size; i++) { |
| uPrime[i] = NewtonRaphsonRootFind3D(cubic, *(pointsArrayBegin + i), u[i]); |
| if (!std::isfinite(uPrime[i])) { |
| delete[] uPrime; |
| return NULL; |
| } |
| } |
| |
| qsort(uPrime, size, sizeof(double), compareDouble); |
| |
| |
| if (uPrime[0] < 0.0 || uPrime[size - 1] > 1.0) { |
| delete[] uPrime; |
| return NULL; |
| } |
| |
| assert(uPrime[0] >= 0.0); |
| assert(uPrime[size - 1] <= 1.0); |
| |
| return uPrime; |
| } |
| |
| |
| inline double B1(double u) { |
| double tmp = 1.0 - u; |
| return 3 * u * tmp * tmp; |
| } |
| inline double B2(double u) { return 3 * u * u * (1 - u); } |
| inline double B0plusB1(double u) { |
| double tmp = 1.0 - u; |
| return tmp * tmp * (1.0 + 2.0 * u); |
| } |
| inline double B2plusB3(double u) { return u * u * (3.0 - 2.0 * u); } |
| |
| } |
| |
| |
| |
| |
| class TCubicStroke { |
| private: |
| |
| TThickCubic *generateCubic3D(const T3DPointD pointsArrayBegin[], |
| const double uPrime[], int size, |
| const T3DPointD &tangentLeft, |
| const T3DPointD &tangentRight); |
| |
| |
| void fitCubic3D(const T3DPointD pointsArrayBegin[], int size, |
| const T3DPointD &tangentLeft, const T3DPointD &tangentRight, |
| double error); |
| |
| |
| TRectD m_bBox; |
| |
| public: |
| |
| vector<TThickCubic *> *m_cubicChunkArray; |
| |
| |
| TCubicStroke(); |
| |
| |
| |
| |
| TCubicStroke(const TCubicStroke &stroke); |
| |
| |
| |
| |
| |
| TCubicStroke(const vector<T3DPointD> &pointsArray3D, double error, |
| bool doDetectCorners = true); |
| |
| |
| |
| |
| |
| |
| |
| ~TCubicStroke(); |
| }; |
| |
| namespace { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| void doComputeQuadraticsFromCubic(const TThickCubic &cubic, int splits, |
| vector<TThickQuadratic *> &chunkArray) { |
| TThickPoint p0r, p1r, p2r, p3r, p0l, p1l, p2l, p3l; |
| |
| p0l = cubic.getThickP0(); |
| p1l = cubic.getThickP1(); |
| p2l = cubic.getThickP2(); |
| p3l = cubic.getThickP3(); |
| |
| p3l = 0.5 * (p3l + p2l); |
| p2l = 0.5 * (p2l + p1l); |
| p1l = 0.5 * (p1l + p0l); |
| p3l = 0.5 * (p3l + p2l); |
| p2l = 0.5 * (p2l + p1l); |
| p3l = 0.5 * (p3l + p2l); |
| |
| |
| p0r = cubic.getThickP0(); |
| p1r = cubic.getThickP1(); |
| p2r = cubic.getThickP2(); |
| p3r = cubic.getThickP3(); |
| |
| p0r = 0.5 * (p0r + p1r); |
| p1r = 0.5 * (p1r + p2r); |
| p2r = 0.5 * (p2r + p3r); |
| p0r = 0.5 * (p0r + p1r); |
| p1r = 0.5 * (p1r + p2r); |
| p0r = 0.5 * (p0r + p1r); |
| |
| if (splits > 0) { |
| TThickCubic cubic1(cubic); |
| TThickCubic tmp_1(p0l, p1l, p2l, p3l); |
| std::swap(cubic1, tmp_1); |
| doComputeQuadraticsFromCubic(cubic1, splits - 1, chunkArray); |
| TThickCubic tmp_2(p0r, p1r, p2r, p3r); |
| std::swap(cubic1, tmp_2); |
| doComputeQuadraticsFromCubic(cubic1, splits - 1, chunkArray); |
| } else { |
| TThickQuadratic *chunkL = |
| new TThickQuadratic(p0l, 0.25 * (3 * (p1l + p2l) - (p0l + p3l)), p3l); |
| TThickQuadratic *chunkR = |
| new TThickQuadratic(p0r, 0.25 * (3 * (p1r + p2r) - (p0r + p3r)), p3r); |
| |
| |
| chunkArray.push_back(chunkL); |
| chunkArray.push_back(chunkR); |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| void computeQuadraticsFromCubic(const TThickCubic &cubic, double error, |
| vector<TThickQuadratic *> &chunkArray) { |
| const double T = 0.21132486540518711775; |
| const double S = (1 - T); |
| int splits; |
| double dist2 = tdistance2(cubic.getP1(), cubic.getP2()); |
| if (dist2 < 2) { |
| chunkArray.push_back(new TThickQuadratic( |
| cubic.getThickP0(), 0.5 * (cubic.getThickP1() + cubic.getThickP2()), |
| cubic.getThickP3())); |
| return; |
| } |
| |
| TPointD dp = ((S * S * S) - (S * S - S * T / 2)) * cubic.getP0() + |
| ((3 * S * S * T) - (S * T * 3 / 2)) * cubic.getP1() + |
| ((3 * S * T * T) - (S * T * 3 / 2)) * cubic.getP2() + |
| ((T * T * T) - (T * T - S * T / 2)) * cubic.getP3(); |
| |
| double dist_sq = norm2(dp); |
| |
| for (splits = 0; dist_sq > error; splits++) dist_sq *= 0.125 * 0.125; |
| |
| if (splits == 0) { |
| TPointD p0 = cubic.getP0(); |
| TPointD p1 = cubic.getP1(); |
| TPointD p2 = cubic.getP2(); |
| TPointD p3 = cubic.getP3(); |
| |
| TPointD side01 = p1 - p0; |
| TPointD side32 = p2 - p3; |
| |
| |
| |
| |
| |
| |
| |
| double det = |
| -side01.x * side32.y + side01.y * side32.x; |
| if (!isAlmostZero(det)) |
| |
| { |
| TPointD side03 = p3 - p0; |
| double det01 = -side03.x * side32.y + side03.y * side32.x; |
| double det32 = side01.x * side03.y - side01.y * side03.x; |
| double t01 = det01 / det; |
| double t32 = det32 / det; |
| |
| #if 0 |
| TPointD p01 = p0 + t01*(p1 - p0); |
| TPointD p32 = p3 + t32*(p2 - p3); |
| #endif |
| |
| |
| if (t01 >= 1 && t32 >= 1) { |
| double norm2_side0p = norm2(t01 * side01); |
| double norm2_side3p = norm2(t32 * side32); |
| if (!isAlmostZero(norm2_side0p, 1e-20) && |
| !isAlmostZero(norm2_side3p, |
| 1e-20)) { |
| |
| double norm2_side03 = norm2(side03); |
| double tmp = norm2_side0p + norm2_side3p - norm2_side03; |
| |
| |
| double cs_sign = |
| tmp >= 0 ? 1 : -1; |
| |
| |
| |
| |
| double cs2 = sq(tmp) / (4 * norm2_side0p * norm2_side3p); |
| |
| assert(areAlmostEqual( |
| tsign(cs_sign) * sqrt(cs2), |
| tmp / (2 * sqrt(norm2_side0p) * sqrt(norm2_side3p)))); |
| if (cs_sign < 0 || cs2 < 0.969846) |
| { |
| |
| |
| assert(tsign(cs_sign) * sqrt(cs2) < cos(10 * M_PI_180)); |
| TPointD intersection = |
| p0 + t01 * (p1 - p0); |
| TThickPoint p( |
| intersection.x, intersection.y, |
| 0.5 * (cubic.getThickP1().thick + |
| cubic.getThickP2() |
| .thick)); |
| chunkArray.push_back( |
| new TThickQuadratic(cubic.getThickP0(), p, cubic.getThickP3())); |
| |
| #ifdef _DEBUG |
| TThickQuadratic *lastTq = chunkArray.back(); |
| TThickPoint pDeb = lastTq->getThickP0(); |
| assert(std::isfinite(pDeb.x)); |
| assert(std::isfinite(pDeb.y)); |
| assert(std::isfinite(pDeb.thick)); |
| pDeb = lastTq->getThickP1(); |
| assert(std::isfinite(pDeb.x)); |
| assert(std::isfinite(pDeb.y)); |
| assert(std::isfinite(pDeb.thick)); |
| pDeb = lastTq->getThickP2(); |
| assert(std::isfinite(pDeb.x)); |
| assert(std::isfinite(pDeb.y)); |
| assert(std::isfinite(pDeb.thick)); |
| #endif |
| numSaved++; |
| return; |
| } |
| } |
| } |
| } else { |
| TPointD side03 = p3 - p0; |
| double det01 = -side03.x * side32.y + side03.y * side32.x; |
| if (isAlmostZero(det01)) { |
| |
| chunkArray.push_back(new TThickQuadratic( |
| cubic.getThickP0(), (cubic.getThickP1() + cubic.getThickP2()) * 0.5, |
| cubic.getThickP3())); |
| numSaved++; |
| return; |
| } |
| } |
| |
| |
| splits++; |
| } |
| doComputeQuadraticsFromCubic(cubic, splits - 1, chunkArray); |
| } |
| |
| |
| |
| |
| |
| TStroke *computeQuadStroke(const TCubicStroke &cubic) { |
| vector<TThickQuadratic *> chunkArray; |
| |
| for (UINT i = 0; i < cubic.m_cubicChunkArray->size(); i++) { |
| TThickCubic tmp(*(*cubic.m_cubicChunkArray)[i]); |
| |
| #ifdef _DEBUG |
| { |
| TThickPoint p = tmp.getThickP0(); |
| assert(std::isfinite(p.x)); |
| assert(std::isfinite(p.y)); |
| assert(std::isfinite(p.thick)); |
| p = tmp.getThickP1(); |
| assert(std::isfinite(p.x)); |
| assert(std::isfinite(p.y)); |
| assert(std::isfinite(p.thick)); |
| p = tmp.getThickP2(); |
| assert(std::isfinite(p.x)); |
| assert(std::isfinite(p.y)); |
| assert(std::isfinite(p.thick)); |
| p = tmp.getThickP3(); |
| assert(std::isfinite(p.x)); |
| assert(std::isfinite(p.y)); |
| assert(std::isfinite(p.thick)); |
| } |
| #endif |
| |
| |
| |
| |
| computeQuadraticsFromCubic(tmp, 2.0 , chunkArray); |
| } |
| |
| TStroke *outStroke = TStroke::create(chunkArray); |
| |
| clearPointerContainer(chunkArray); |
| |
| return outStroke; |
| } |
| |
| } |
| |
| |
| |
| |
| |
| void computeQuadraticsFromCubic(const TThickPoint &p0, const TThickPoint &p1, |
| const TThickPoint &p2, const TThickPoint &p3, |
| double error, |
| vector<TThickQuadratic *> &chunkArray) { |
| computeQuadraticsFromCubic(TThickCubic(p0, p1, p2, p3), error, chunkArray); |
| } |
| |
| |
| |
| TCubicStroke::TCubicStroke() : m_bBox() { |
| m_cubicChunkArray = new TThickCubicArray(); |
| } |
| |
| |
| |
| TCubicStroke::TCubicStroke(const TCubicStroke &stroke) |
| : m_bBox(stroke.m_bBox), m_cubicChunkArray(stroke.m_cubicChunkArray) { |
| m_cubicChunkArray = new TThickCubicArray(*stroke.m_cubicChunkArray); |
| } |
| |
| |
| |
| TCubicStroke::~TCubicStroke() { |
| if (m_cubicChunkArray) { |
| while (!m_cubicChunkArray->empty()) { |
| delete m_cubicChunkArray->back(); |
| m_cubicChunkArray->pop_back(); |
| } |
| |
| delete m_cubicChunkArray; |
| } |
| } |
| |
| |
| |
| TCubicStroke::TCubicStroke(const vector<T3DPointD> &pointsArray3D, double error, |
| bool doDetectCorners) { |
| vector<int> corners; |
| corners.push_back(0); |
| if (doDetectCorners) detectCorners(pointsArray3D, 3, 3, 15, 100, corners); |
| corners.push_back(pointsArray3D.size() - 1); |
| |
| #ifndef USE_NEW_3D_ERROR_COMPUTE |
| error *= error; |
| #endif |
| |
| m_cubicChunkArray = new vector<TThickCubic *>(); |
| |
| for (int i = 1; i < (int)corners.size(); i++) { |
| int size = corners[i] - corners[i - 1] + 1; |
| int firstPoint = corners[i - 1]; |
| T3DPointD tanLeft, tanRight; |
| assert(size > 0); |
| if (size > 1) |
| |
| { |
| tanLeft = -pointsArray3D[firstPoint + 1] + pointsArray3D[firstPoint]; |
| tanRight = pointsArray3D[firstPoint + size - 2] - |
| pointsArray3D[firstPoint + size - 1]; |
| |
| if (norm2(tanLeft) > 0) tanLeft = normalize(tanLeft); |
| |
| if (norm2(tanRight) > 0) tanRight = normalize(tanRight); |
| |
| fitCubic3D(&pointsArray3D[firstPoint], size, tanLeft, tanRight, error); |
| } else if (pointsArray3D.size() == 1) { |
| |
| |
| assert(size == 1); |
| assert(corners.size() == 2); |
| assert(corners[0] == 0); |
| assert(corners[1] == 0); |
| m_cubicChunkArray->push_back( |
| new TThickCubic(pointsArray3D[0], pointsArray3D[0], pointsArray3D[0], |
| pointsArray3D[0])); |
| } |
| } |
| } |
| |
| |
| |
| void TCubicStroke::fitCubic3D(const T3DPointD pointsArrayBegin[], int size, |
| const T3DPointD &tangentLeft, |
| const T3DPointD &tangentRight, double error) { |
| int maxIterations = 4; |
| |
| if (size == 2) { |
| double dist = tdistance(*pointsArrayBegin, *(pointsArrayBegin + 1)) / 3.0; |
| TThickCubic *strokeCubicChunk = new TThickCubic( |
| *pointsArrayBegin, *pointsArrayBegin - dist * tangentLeft, |
| *(pointsArrayBegin + 1) + dist * tangentRight, *(pointsArrayBegin + 1)); |
| |
| m_cubicChunkArray->push_back(strokeCubicChunk); |
| return; |
| } |
| |
| double *u = chordLengthParameterize3D(pointsArrayBegin, size); |
| TThickCubic *cubic = |
| generateCubic3D(pointsArrayBegin, u, size, tangentLeft, tangentRight); |
| |
| int splitPoint; |
| double maxError = |
| computeMaxError3D(*cubic, pointsArrayBegin, size, u, splitPoint); |
| |
| if (maxError < error) { |
| delete[] u; |
| m_cubicChunkArray->push_back(cubic); |
| return; |
| } |
| |
| |
| { |
| double *uPrime = NULL; |
| for (int i = 0; i < maxIterations; i++) { |
| |
| uPrime = reparameterize3D(*cubic, pointsArrayBegin, size, u); |
| if (!uPrime) break; |
| |
| delete cubic; |
| cubic = generateCubic3D(pointsArrayBegin, uPrime, size, tangentLeft, |
| tangentRight); |
| maxError = |
| computeMaxError3D(*cubic, pointsArrayBegin, size, uPrime, splitPoint); |
| if (maxError < error) { |
| delete[] uPrime; |
| delete[] u; |
| m_cubicChunkArray->push_back(cubic); |
| return; |
| } |
| delete[] u; |
| u = uPrime; |
| } |
| } |
| |
| delete[] u; |
| delete cubic; |
| |
| T3DPointD centralTangent; |
| if (*(pointsArrayBegin + splitPoint - 1) == |
| *(pointsArrayBegin + splitPoint + 1)) |
| centralTangent = normalize(*(pointsArrayBegin + splitPoint) - |
| *(pointsArrayBegin + splitPoint + 1)); |
| else |
| centralTangent = normalize(*(pointsArrayBegin + splitPoint - 1) - |
| *(pointsArrayBegin + splitPoint + 1)); |
| |
| fitCubic3D(pointsArrayBegin, splitPoint + 1, tangentLeft, centralTangent, |
| error); |
| |
| fitCubic3D(pointsArrayBegin + splitPoint, size - splitPoint, centralTangent, |
| tangentRight, error); |
| } |
| |
| |
| |
| TThickCubic *TCubicStroke::generateCubic3D(const T3DPointD pointsArrayBegin[], |
| const double uPrime[], int size, |
| const T3DPointD &tangentLeft, |
| const T3DPointD &tangentRight) { |
| double X[2], C[2][2]; |
| int i; |
| |
| T3DPointD p0 = *pointsArrayBegin; |
| T3DPointD p3 = *(pointsArrayBegin + size - 1); |
| |
| C[0][0] = C[0][1] = X[0] = 0; |
| C[1][0] = C[1][1] = X[1] = 0; |
| |
| for (i = 0; i < size; i++) { |
| const T3DPointD A[2] = { |
| tangentLeft * B1(uPrime[i]), tangentRight * B2(uPrime[i]), |
| }; |
| |
| C[0][0] += A[0] * A[0]; |
| C[0][1] += A[0] * A[1]; |
| C[1][1] += A[1] * A[1]; |
| |
| C[1][0] = C[0][1]; |
| |
| T3DPointD tmp = *(pointsArrayBegin + i) - (B0plusB1(uPrime[i]) * p0) + |
| B2plusB3(uPrime[i]) * p3; |
| X[0] += A[0] * tmp; |
| X[1] += A[1] * tmp; |
| } |
| |
| double detC0C1 = C[0][0] * C[1][1] - C[0][1] * C[1][0]; |
| double detC0X = X[1] * C[0][0] - X[0] * C[0][1]; |
| double detXC1 = X[0] * C[1][1] - X[1] * C[0][1]; |
| |
| if (isAlmostZero(detC0C1)) detC0C1 = C[0][0] * C[1][1] * 10e-12; |
| |
| double alphaL = detXC1 / detC0C1; |
| double alphaR = detC0X / detC0C1; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| double xmin = (numeric_limits<double>::max)(); |
| double ymin = (numeric_limits<double>::max)(); |
| double xmax = -(numeric_limits<double>::max)(); |
| double ymax = -(numeric_limits<double>::max)(); |
| double thickmin = (numeric_limits<double>::max)(); |
| double thickmax = -(numeric_limits<double>::max)(); |
| |
| for (i = 0; i < size; i++) { |
| if (pointsArrayBegin[i].x < xmin) xmin = pointsArrayBegin[i].x; |
| if (pointsArrayBegin[i].x > xmax) xmax = pointsArrayBegin[i].x; |
| if (pointsArrayBegin[i].y < ymin) ymin = pointsArrayBegin[i].y; |
| if (pointsArrayBegin[i].y > ymax) ymax = pointsArrayBegin[i].y; |
| if (pointsArrayBegin[i].z < thickmin) thickmin = pointsArrayBegin[i].z; |
| if (pointsArrayBegin[i].z > thickmax) thickmax = pointsArrayBegin[i].z; |
| } |
| |
| double lx = xmax - xmin; |
| assert(lx >= 0); |
| double ly = ymax - ymin; |
| assert(ly >= 0); |
| double lt = thickmax - thickmin; |
| assert(lt >= 0); |
| |
| xmin -= lx; |
| xmax += lx; |
| ymin -= ly; |
| ymax += ly; |
| thickmin -= lt; |
| thickmax += lt; |
| |
| TRectD bbox(xmin, ymin, xmax, ymax); |
| |
| if (alphaL < 0.0 || alphaR < 0.0) |
| alphaL = alphaR = sqrt(tdistance2(p0, p3)) / 3.0; |
| |
| T3DPointD p1 = p0 - tangentLeft * alphaL; |
| T3DPointD p2 = p3 + tangentRight * alphaR; |
| |
| if (!bbox.contains(TPointD(p1.x, p1.y)) || |
| !bbox.contains(TPointD(p2.x, p2.y))) { |
| alphaL = alphaR = sqrt(tdistance2(p0, p3)) / 3.0; |
| p1 = p0 - tangentLeft * alphaL; |
| p2 = p3 + tangentRight * alphaR; |
| } |
| |
| if (p1.z < thickmin) |
| p1.z = thickmin; |
| else if (p1.z > thickmax) |
| p1.z = thickmax; |
| |
| if (p2.z < thickmin) |
| p2.z = thickmin; |
| else if (p2.z > thickmax) |
| p2.z = thickmax; |
| |
| TThickCubic *thickCubic = new TThickCubic( |
| TThickPoint(p0.x, p0.y, p0.z), TThickPoint(p1.x, p1.y, p1.z), |
| TThickPoint(p2.x, p2.y, p2.z), TThickPoint(p3.x, p3.y, p3.z)); |
| |
| return thickCubic; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| TStroke *TStroke::interpolate(const vector<TThickPoint> &points, double error, |
| bool findCorners) { |
| vector<T3DPointD> pointsArray3D; |
| convert(points, pointsArray3D); |
| |
| TCubicStroke cubicStroke(pointsArray3D, error, findCorners); |
| numSaved = 0; |
| |
| |
| TStroke *stroke = computeQuadStroke(cubicStroke); |
| |
| #ifdef _DEBUG |
| |
| UINT cpIndex = 0; |
| TThickPoint p; |
| for (; cpIndex != (UINT)stroke->getControlPointCount(); cpIndex++) { |
| p = stroke->getControlPoint(cpIndex); |
| assert(std::isfinite(p.x)); |
| assert(std::isfinite(p.y)); |
| assert(std::isfinite(p.thick)); |
| } |
| #endif |
| |
| removeNullQuadratic(stroke); |
| stroke->invalidate(); |
| |
| return stroke; |
| } |
| |
| |
| |
| |
| |
| TStroke *TStroke::create(const vector<TThickQuadratic *> &curves) { |
| if (curves.empty()) return 0; |
| |
| |
| vector<TThickPoint> ctrlPnts; |
| |
| extractStrokeControlPoints(curves, ctrlPnts); |
| |
| TStroke *stroke = new TStroke(ctrlPnts); |
| |
| stroke->invalidate(); |
| |
| return stroke; |
| } |
| |
| |
| |
| TStrokeProp::TStrokeProp(const TStroke *stroke) |
| : m_stroke(stroke), m_strokeChanged(true), m_mutex() {} |
| |
| |
| |
| TStroke::OutlineOptions::OutlineOptions() |
| : m_capStyle(ROUND_CAP) |
| , m_joinStyle(ROUND_JOIN) |
| , m_miterLower(0.0) |
| , m_miterUpper(4.0) {} |
| |
| |
| |
| TStroke::OutlineOptions::OutlineOptions(UCHAR capStyle, UCHAR joinStyle, |
| double lower, double upper) |
| : m_capStyle(capStyle) |
| , m_joinStyle(joinStyle) |
| , m_miterLower(lower) |
| , m_miterUpper(upper) {} |
| |