Files
qt5-legatus-legacy/Lts/Viewer/SituationMap/MathUtils/curve.cpp
T
2026-08-13 18:33:10 +02:00

737 lines
22 KiB
C++
Executable File

/**
**
** @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 <Common/exception.h>
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<QPointF> & vertexVector, bool closedCurve )
{
SmoothLine::vertexVector( vertexVector, closedCurve );
} /* BezierSpline::SmoothLine */
void SmoothLine::vertexVector( const QVector<QPointF> & 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<QPointF>::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<QPointF> & vertexVector
, const QVector<QPointF> & tangentVector, bool closedCurve )
{
HermiteSpline::vertexVector(vertexVector, tangentVector, closedCurve );
} /* HermiteSpline::HermiteSpline */
void HermiteSpline::vertexVector( const QVector<QPointF> & vertexVector
, const QVector<QPointF> & 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<QPointF>::const_iterator itv = _vertexVector.begin();
QVector<QPointF>::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<QPointF> & ctrlPntVector, bool closedCurve )
: HermiteSpline()
{
controlPointVector(ctrlPntVector,closedCurve);
} /* BezierSpline::BezierSpline */
void BezierSpline::controlPointVector( const QVector<QPointF> & ctrlPntVector, bool closedCurve )
{
if (ctrlPntVector.size() < 4) return;
QVector<QPointF> vertexVector;
QVector<QPointF> tangentVector;
QVector<QPointF>::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<QPointF>&, const QVector<QPointF>&, 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