/** ** ** @RDE_HEADER@ ** Rasterize functional path geometries to polylines ** $Id$ ** $HeadURL$ ** $Author$ ** ** ----------------------------------------------------------------------------------------------- ** ** Hardware-Environment: Intel-PC ** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 ** ** Language ........: C++ 11 ** ** ----------------------------------------------------------------------------------------------- ** ** Author ...........: Jörg Turla, SPD G, Tel. 3132 ** ** ----------------------------------------------------------------------------------------------- ** ** Notes .........: ** ** 12.08.2015, JT : First edition for use with Qt5 ** ** ----------------------------------------------------------------------------------------------- */ #include "curve.h" #include "path_rasterer.h" #include namespace SituationMap { namespace MathUtils { static QPointF calcBezierVertex( double t, const QPointF control[4] ) { double u, u2, u3, t2, t3; double xx, yy; t2 = t*t; t3 = t2*t; u = 1.0 - t; u2 = u*u; u3 = u2*u; xx = control[0].x()*u3 + 3.0*(control[1].x()*t*u2 + control[2].x()*t2*u) + control[3].x()*t3; yy = control[0].y()*u3 + 3.0*(control[1].y()*t*u2 + control[2].y()*t2*u) + control[3].y()*t3; return QPointF(xx,yy); } /* calcBezierVertex */ static QPointF calcBezierTangent( double t, const QPointF control[4] ) { double t2(t*t); double u(1.0-t); double u2(u*u); double xx(3.0*((control[1].x()-control[0].x())*u2 + (control[2].x()-control[1].x())*t*u*2.0 + (control[3].x()-control[2].x())*t2)); double yy(3.0*((control[1].y()-control[0].y())*u2 + (control[2].y()-control[1].y())*t*u*2.0 + (control[3].y()-control[2].y())*t2)); return QPointF(xx,yy); } /* calcBezierTangent */ static double hypotBezierTangent( double t, const QPointF control[4] ) { QPointF p(calcBezierTangent(t,control)); return hypot(p.x(),p.y()); } /* hypotBezierTangent */ static double simpsonApproximation( const QPointF control[4], double a, double b ) { const unsigned int NLenSteps = 800; double t( b-a ); double h( t/NLenSteps ); double sum( 0.0 ); // Zusammengesetzte Integration nach der Simpson-Regel double s1( 0.0 ); double s2( 0.0 ); for (unsigned int i = 0; i < NLenSteps; i++) { s1 += hypotBezierTangent( a+h*i, control ); s2 += hypotBezierTangent( a+h*(double(i)-0.5), control ); } s2 += hypotBezierTangent( a+h*(double(NLenSteps)-0.5), control ); sum += h/6.0 *( hypotBezierTangent(a,control)+hypotBezierTangent(b,control)+2.0*s1+4.0*s2); return sum; } /* simpsonApproximation */ static inline void buildControlPoints( const QPointF & p_i, const QPointF & p_i1, const QPointF & p_i2, QPointF control[4], bool startSegm = false, bool endSegm = false ) { if (startSegm) { control[0] = p_i; control[1] = p_i*0.333 + p_i1*0.667; } else { control[0] = p_i*0.5 + p_i1*0.5; control[1] = p_i*0.167 + p_i1*0.833; } if (endSegm) { control[2] = p_i1*0.667 + p_i2*0.333; control[3] = p_i2; } else { control[2] = p_i1*0.833 + p_i2*0.167; control[3] = p_i1*0.5 + p_i2*0.5; } } /* buildControlPoints */ SmoothLine::SmoothLine(const QVector & vertexVector, bool closedCurve ) { SmoothLine::vertexVector( vertexVector, closedCurve ); } /* BezierSpline::SmoothLine */ void SmoothLine::vertexVector( const QVector & vertexVector, bool closedCurve ) { _closedCurve = closedCurve; _vertexVector = vertexVector; } /* SmoothLine::vertexVector */ qreal SmoothLine::maximumCurveParameter() const { if (_vertexVector.size() > 2) { return (_closedCurve) ? (qreal)_vertexVector.size() : (qreal)_vertexVector.size() - 2.0; } return 0; } /* SmoothLine::maximumCurveParameter */ QPointF SmoothLine::positionOnCurve( qreal t ) const { if (_vertexVector.size() < 3) { if (_vertexVector.size() > 1) { // It's just a straight line if (t < 0.0) t = 1.0-t; if ((t >= 0.0) && (t <= 1.0)) { const QPointF & v0 = _vertexVector[0]; const QPointF & v1 = _vertexVector[1]; return v0 + (v1-v0)*t; } } } else { int maxSegm = _vertexVector.size() - ((_closedCurve) ? 1 : 3); if (t < 0.0) t = double(maxSegm+1) - t; int segment( floor(t) ); if ((segment >= 0) && (segment <= maxSegm)) { QPointF control[4]; QPointF p[3]; if ((_closedCurve) && (segment == (maxSegm-1))) { p[0] = _vertexVector[_vertexVector.size()-2]; p[1] = _vertexVector.back(); p[2] = _vertexVector[0]; buildControlPoints( p[0], p[1], p[2], control, false, false ); } else if ((_closedCurve) && (segment == maxSegm)) { p[0] = _vertexVector.back(); p[1] = _vertexVector[0]; p[2] = _vertexVector[1]; buildControlPoints( p[0], p[1], p[2], control, false, false ); } else { p[0] = _vertexVector[segment ]; p[1] = _vertexVector[segment+1]; p[2] = _vertexVector[segment+2]; buildControlPoints( p[0], p[1], p[2], control, ((segment == 0) && (!_closedCurve)), (segment == maxSegm) ); } return calcBezierVertex( t-double(segment), control ); } else { if (t == (maxSegm+1)) { QPointF control[4]; QPointF p[3]; if (_closedCurve) { p[0] = _vertexVector[0]; p[1] = _vertexVector[1]; p[2] = _vertexVector[2]; buildControlPoints( p[0], p[1], p[2], control, true, false ); return calcBezierVertex( 0.0, control ); } else { p[0] = _vertexVector[maxSegm ]; p[1] = _vertexVector[maxSegm+1]; p[2] = _vertexVector[maxSegm+2]; buildControlPoints( p[0], p[1], p[2], control, (maxSegm == 0), true ); return calcBezierVertex( 1.0, control ); } } } } return QPointF(0.0,0.0); } /* SmoothLine::positionOnCurve */ QPointF SmoothLine::directionOnCurve( qreal t ) const { if (_vertexVector.size() < 3) { if (_vertexVector.size() >1) { // It's just a straight line if (t < 0.0) t = 1.0-t; if ((t >= 0.0) && (t <= 1.0)) { return (_vertexVector[1]-_vertexVector[0]); } } } else { int maxSegm = _vertexVector.size() - ((_closedCurve) ? 1 : 3); if (t < 0.0) t = double(maxSegm+1) - t; int segment( floor(t) ); if ((segment >= 0) && (segment <= maxSegm)) { QPointF control[4]; QPointF p[3]; if ((_closedCurve) && (segment == (maxSegm-1))) { p[0] = _vertexVector[_vertexVector.size()-2]; p[1] = _vertexVector.back(); p[2] = _vertexVector[0]; buildControlPoints( p[0], p[1], p[2], control, false, false ); } else if ((_closedCurve) && (segment == maxSegm)) { p[0] = _vertexVector.back(); p[1] = _vertexVector[0]; p[2] = _vertexVector[1]; buildControlPoints( p[0], p[1], p[2], control, false, false ); } else { p[0] = _vertexVector[segment ]; p[1] = _vertexVector[segment+1]; p[2] = _vertexVector[segment+2]; buildControlPoints( p[0], p[1], p[2], control, ((segment == 0) && (!_closedCurve)), (segment == maxSegm) ); } return calcBezierTangent( t-double(segment), control ); } else { if (t == (maxSegm+1)) { QPointF control[4]; QPointF p[3]; if (_closedCurve) { p[0] = _vertexVector[0]; p[1] = _vertexVector[1]; p[2] = _vertexVector[2]; buildControlPoints( p[0], p[1], p[2], control, true, false ); return calcBezierTangent( 0.0, control ); } else { p[0] = _vertexVector[maxSegm ]; p[1] = _vertexVector[maxSegm+1]; p[2] = _vertexVector[maxSegm+2]; buildControlPoints( p[0], p[1], p[2], control, (maxSegm == 0), true ); return calcBezierTangent( 1.0, control ); } } } } return QPointF(0.0,0.0); } /* SmoothLine::directionOnCurve */ qreal SmoothLine::arcLength( qreal t) const { if (t < 0.0) return 0.0; QPointF control[4]; QPointF p[3]; QVector::const_iterator it = _vertexVector.begin(); if (_closedCurve) { int i = 0; p[i++] = _vertexVector.back(); while ((i<3) && (it != _vertexVector.end())) { p[i] = *it; ++it; ++i; } buildControlPoints( p[0], p[1], p[2], control, false, false ); } else { int i = 0; while ((i<3) && (it != _vertexVector.end())) { p[i] = *it; ++it; ++i; } buildControlPoints( p[0], p[1], p[2], control, true, (it == _vertexVector.end()) ); } if (t < 1.0) return simpsonApproximation( control, 0.0, t ); double sum = simpsonApproximation( control, 0.0, 1.0 ); t -= 1.0; while (it != _vertexVector.end()) { p[0] = p[1]; p[1] = p[2]; p[2] = *it; ++it; if (_closedCurve) buildControlPoints( p[0], p[1], p[2], control, false, false ); else buildControlPoints( p[0], p[1], p[2], control, false, (it == _vertexVector.end()) ); if (t < 1.0) { sum += simpsonApproximation( control, 0.0, t ); break; } else sum += simpsonApproximation( control, 0.0, 1.0 ); } if ((t > 0.0) && (_closedCurve)) { p[0] = p[1]; p[1] = p[2]; p[2] = _vertexVector.front(); buildControlPoints( p[0], p[1], p[2], control, false, false ); sum += simpsonApproximation( control, 0.0, t ); } return sum; } /* SmoothLine::arcLength */ HermiteSpline::HermiteSpline( const QVector & vertexVector , const QVector & tangentVector, bool closedCurve ) { HermiteSpline::vertexVector(vertexVector, tangentVector, closedCurve ); } /* HermiteSpline::HermiteSpline */ void HermiteSpline::vertexVector( const QVector & vertexVector , const QVector & tangentVector, bool closedCurve ) { _closedCurve = closedCurve; _vertexVector = vertexVector; _tangentVector = tangentVector; int countOfTangents = (_closedCurve) ? _vertexVector.size()*2 : (_vertexVector.size()-1) * 2; if (_tangentVector.size() >= countOfTangents) { // fatal error throw SituationMap::Exception( __FILE__, __LINE__, "No enough tangents defined to calculate spline. Each spline segment must have defined 2 tangents per vertex, excepting the border segments where only 1 tangent must be defined."); } } /* HermiteSpline::vertexVector */ qreal HermiteSpline::maximumCurveParameter() const { if (_vertexVector.size() < 2) return 0.0; return (_closedCurve) ? _vertexVector.size() : (_vertexVector.size() - 1.0); } /* HermiteSpline::maximumCurveParameter */ QPointF HermiteSpline::positionOnCurve( qreal t ) const { if (_vertexVector.size() < 2) return QPointF(0.0,0.0); int maxSegm = _vertexVector.size() - ((_closedCurve) ? 1 : 2); if (t < 0.0) t = double(maxSegm+1) - t; int segment( floor(t) ); if ((segment >= 0) && (segment <= maxSegm)) { QPointF control[4]; if ((_closedCurve) && (segment == maxSegm)) { control[0] = _vertexVector.back(); control[3] = _vertexVector.front(); control[1] = control[0] + _tangentVector[_tangentVector.size()-2]/3.0; control[2] = control[3] - _tangentVector[_tangentVector.size()-1]/3.0; } else { control[0] = _vertexVector[segment]; control[3] = _vertexVector[segment+1]; control[1] = control[0] + _tangentVector[segment*2]/3.0; control[2] = control[3] - _tangentVector[segment*2+1]/3.0; } return calcBezierVertex( t-double(segment), control ); } return QPointF(0.0,0.0); } /* HermiteSpline::positionOnCurve */ QPointF HermiteSpline::directionOnCurve( qreal t ) const { if (_vertexVector.size() < 2) return QPointF(0.0,0.0); int maxSegm = _vertexVector.size() - ((_closedCurve) ? 1 : 2); if (t < 0.0) t = double(maxSegm+1) - t; int segment( floor(t) ); if ((segment >= 0) && (segment <= maxSegm)) { QPointF control[4]; if ((_closedCurve) && (segment == maxSegm)) { control[0] = _vertexVector.back(); control[3] = _vertexVector.front(); control[1] = control[0] + _tangentVector[_tangentVector.size()-2]/3.0; control[2] = control[3] - _tangentVector[_tangentVector.size()-1]/3.0; } else { control[0] = _vertexVector[segment]; control[3] = _vertexVector[segment+1]; control[1] = control[0] + _tangentVector[segment*2]/3.0; control[2] = control[3] - _tangentVector[segment*2+1]/3.0; } return calcBezierTangent( t-double(segment), control ); } return QPointF(0.0,0.0); } /* HermiteSpline::directionOnCurve */ qreal HermiteSpline::arcLength( qreal t ) const { if (t < 0.0) return 0.0; if (_vertexVector.size() < 2) return 0.0; double sum = 0.0; QPointF control[4]; QVector::const_iterator itv = _vertexVector.begin(); QVector::const_iterator itt = _vertexVector.begin(); if (_closedCurve) { if (2*_vertexVector.size() != _tangentVector.size()) return 0.0; } else { if (2*_vertexVector.size()-2 != _tangentVector.size()) return 0.0; } control[3] = *itv; ++itv; while (itv != _vertexVector.end()) { control[0] = control[3]; control[1] = control[0] + *itt / 3.0; ++itt; control[2] = *itv - *itt / 3.0; ++itt; control[3] = *itv; if (t < 1.0) return sum + simpsonApproximation( control, 0.0, t ); sum = simpsonApproximation( control, 0.0, 1.0 ); t -= 1.0; ++itv; } // while if (_closedCurve) { control[0] = control[3]; control[1] = control[0] + *itt / 3.0; ++itt; control[2] = _vertexVector.front() - *itt / 3.0; ++itt; control[3] = _vertexVector.front(); if (t < 1.0) return sum + simpsonApproximation( control, 0.0, t ); sum = simpsonApproximation( control, 0.0, 1.0 ); // t -= 1.0; // 'ScanBuild: 't is never read' } return sum; } /* HermiteSpline::arcLength */ BezierSpline::BezierSpline( const QVector & ctrlPntVector, bool closedCurve ) : HermiteSpline() { controlPointVector(ctrlPntVector,closedCurve); } /* BezierSpline::BezierSpline */ void BezierSpline::controlPointVector( const QVector & ctrlPntVector, bool closedCurve ) { if (ctrlPntVector.size() < 4) return; QVector vertexVector; QVector tangentVector; QVector::const_iterator it = ctrlPntVector.begin(); vertexVector.append( *it ); ++it; while (it != ctrlPntVector.end()) { // set first tangent tangentVector.append( ((*it) - vertexVector.back()) * 3.0 ); ++it; if (it == ctrlPntVector.end()) { throw SituationMap::Exception(__FILE__, __LINE__, "Missing bezier spline tangent control point." ); } ++it; if (it == ctrlPntVector.end()) { // error if (closedCurve) { tangentVector.append( ((it[-1]) - vertexVector.front()) * 3.0 ); } else throw SituationMap::Exception(__FILE__, __LINE__, "Missing bezier spline vertex control point." ); break; } else { vertexVector.append( *it ); tangentVector.append( ((it[-1]) - vertexVector.back()) * 3.0 ); ++it; } } // while HermiteSpline::vertexVector(vertexVector,tangentVector,closedCurve); } /* BezierSpline::controlPointVector */ void BezierSpline::vertexVector( const QVector&, const QVector&, bool ) { throw SituationMap::Exception(__FILE__, __LINE__, "Function is not defined for BezierSpline. Use controlPointVector instead." ); } /* BezierSpline::vertexVector */ Ellipse::Ellipse( QPointF center, qreal rx, qreal ry, qreal phi, qreal sectorStart, qreal sectorEnd ) : _center(center) , _radiusX(fabs(rx)) , _radiusY(fabs(ry)) , _sinPhi(sin(phi)) , _cosPhi(cos(phi)) , _phiSectorStart(sectorStart) , _phiSectorEnd(sectorEnd) { if (_radiusX < _radiusY) { // swap _radiusX and _radiusY _radiusX = fabs(ry); _radiusY = fabs(rx); // and rotate phi += MathConst::pi_half; _sinPhi = sin(phi); _cosPhi = cos(phi); } qreal a2 = _radiusX * _radiusX; qreal b2 = _radiusY * _radiusY; _eccentricity2 = (a2-b2) / a2; if (fabs(_phiSectorStart) > MathConst::pi_two) _phiSectorStart /= MathConst::pi_two; if (fabs(_phiSectorEnd) > MathConst::pi_two) _phiSectorEnd /= MathConst::pi_two; if (_phiSectorStart >= 0) { if (_phiSectorStart > _phiSectorEnd) _phiSectorStart -= MathConst::pi_two; } else { if (_phiSectorStart > _phiSectorEnd)_phiSectorEnd += MathConst::pi_two; } } /* Ellipse::Ellipse */ qreal Ellipse::maximumCurveParameter() const { return _phiSectorEnd; } qreal Ellipse::minimumCurveParameter() const { return _phiSectorStart; } QPointF Ellipse::positionOnCurve(qreal t) const { // Using ellipse equation in polar coordinates relative to center point(0,0) // because in this case the parameter t is identical with polar angle. qreal sin_t = sin(t); qreal cos_t = cos(t); /* qreal r = _radiusY / (1.0 - _eccentricity2 * cos_t*cos_t); qreal x = cos_t*r; qreal y = sin_t*r; */ qreal x = cos_t*_radiusX; qreal y = sin_t*_radiusY; return _center + QPointF( x*_cosPhi - y*_sinPhi, y*_cosPhi + x*_sinPhi ); } /* Ellipse::positionOnCurve */ QPointF Ellipse::directionOnCurve(qreal t) const { qreal sin_t = sin(t); qreal cos_t = cos(t); if (cos_t == 0.0) return QPointF( -sin_t, 0.0 ); qreal tan_t = sin_t / cos_t; qreal tan_n = tan_t / 1.0 - _eccentricity2; // tan normal // normal QPointF( 1.0/sqrt(1+tan_n*tan_n), tan_n/sqrt(1+tan_n*tan_n) ) QPointF tangente( -tan_n/sqrt(1+tan_n*tan_n), 1.0/sqrt(1+tan_n*tan_n) ); return QPointF( tangente.x()*_cosPhi - tangente.y()*_sinPhi, tangente.y()*_cosPhi + tangente.x()*_sinPhi ); } /* Ellipse::directionOnCurve */ qreal Ellipse::arcLength(qreal t) const { return arcLength( 0.0, t ); } /* Ellipse::arcLength */ qreal Ellipse::arcLength(qreal tmin, qreal tmax ) const { Q_UNUSED(tmin); Q_UNUSED(tmax); // ToDo: calculate convergenting formula based on polar center points equation of ellipse if needed return 0.0; } /* Ellipse::arcLength */ class CurvePathRasterer : public PathRasterer { public: CurvePathRasterer() : PathRasterer(), _curveFunction(nullptr) {} explicit CurvePathRasterer( const Curve * splineFunction ) : PathRasterer(), _curveFunction(splineFunction) {} ~CurvePathRasterer() = default; void function( qreal t, QPointF & result ) override { if (_curveFunction) result = _curveFunction->positionOnCurve(t); else { throw SituationMap::Exception(__FILE__, __LINE__, "Curve function is undefined for PathRasterer." ); } } /* function */ protected: const Curve * _curveFunction; }; /* CurvePathRasterer */ PathRasterer * Ellipse::createPathRaster() const { return new CurvePathRasterer(this); } PathRasterer * SmoothLine::createPathRaster() const { return new CurvePathRasterer(this); } PathRasterer * HermiteSpline::createPathRaster() const { return new CurvePathRasterer(this); } PathRasterer * BezierSpline::createPathRaster() const { return new CurvePathRasterer(this); } } } // namespace SituationMap::MathUtils