/***************************************************************************** * Qwt Examples - Copyright (C) 2002 Uwe Rathmann * This file may be used under the terms of the 3-clause BSD License *****************************************************************************/ #include #include #include #include #include #include #include #include #define DEBUG_ERRORS 1 static inline bool fuzzyCompare( double a, double b ) { return ( qFuzzyIsNull(a) && qFuzzyIsNull(b) ) || qFuzzyCompare(a, b); } static QwtSplinePolynomial polynomialAt( int index, const QPolygonF& points, const QVector< double >& m ) { return QwtSplinePolynomial::fromSlopes( points[index], m[index], points[index + 1], m[index + 1] ); } class CubicSpline : public QwtSplineCubic { public: CubicSpline( const QString& name ) : d_name( name ) { } void setBoundaryConditions( int condition, double valueBegin, double valueEnd ) { const QwtSpline::BoundaryCondition c = static_cast< QwtSpline::BoundaryCondition >( condition ); setBoundaryCondition( QwtSpline::AtBeginning, c ); setBoundaryValue( QwtSpline::AtEnd, valueBegin ); setBoundaryCondition( QwtSpline::AtEnd, c ); setBoundaryValue( QwtSpline::AtEnd, valueEnd ); } QString name() const { return d_name; } private: const QString d_name; }; class LocalSpline : public QwtSplineLocal { public: LocalSpline( QwtSplineLocal::Type type, const QString& name ) : QwtSplineLocal( type ), d_name( name ) { } void setBoundaryConditions( int condition, double valueBegin, double valueEnd ) { const QwtSpline::BoundaryCondition c = static_cast< QwtSpline::BoundaryCondition >( condition ); setBoundaryCondition( QwtSpline::AtBeginning, c ); setBoundaryValue( QwtSpline::AtEnd, valueBegin ); setBoundaryCondition( QwtSpline::AtEnd, c ); setBoundaryValue( QwtSpline::AtEnd, valueEnd ); } QString name() const { return d_name; } private: const QString d_name; }; class SplineTester { public: enum Type { Cardinal, Akima, Cubic }; void testSplineC1( const LocalSpline* spline, const QPolygonF& points ) const; void testSplineC2( const CubicSpline* spline, const QPolygonF& points ) const; private: bool verifyBoundary( const QwtSpline*, QwtSpline::BoundaryPosition, const QPolygonF& points, const QVector< double >& m ) const; int verifyNodesCV( const QPolygonF& p, const QVector< double >& cv ) const; int verifyNodesM( const QPolygonF& p, const QVector< double >& m ) const; }; void SplineTester::testSplineC1( const LocalSpline* spline, const QPolygonF& points ) const { const QVector< double > m = spline->slopes( points ); if ( m.size() != points.size() ) { qDebug() << qPrintable( spline->name() ) << "(" << points.size() << "):" << "not implemented"; return; } const bool okStart = verifyBoundary( spline, QwtSpline::AtBeginning, points, m ); const bool okEnd = verifyBoundary( spline, QwtSpline::AtEnd, points, m ); if ( !okStart || !okEnd ) { qDebug() << qPrintable( spline->name() ) << "(" << points.size() << "):" << false; #if DEBUG_ERRORS > 0 if ( !okStart ) qDebug() << " invalid start condition"; if ( !okEnd ) qDebug() << " invalid end condition"; #endif } } void SplineTester::testSplineC2( const CubicSpline* spline, const QPolygonF& points ) const { const QVector< double > m = spline->slopes( points ); const QVector< double > cv = spline->curvatures( points ); if ( m.size() != points.size() ) { qDebug() << qPrintable( spline->name() ) << "(" << points.size() << "):" << "not implemented"; return; } const bool okStart = verifyBoundary( spline, QwtSpline::AtBeginning, points, m ); const int numErrorsM = verifyNodesM( points, m ); const int numErrorsCV = verifyNodesCV( points, cv ); const bool okEnd = verifyBoundary( spline, QwtSpline::AtEnd, points, m ); if ( !okStart || numErrorsM > 0 || numErrorsCV > 0 || !okEnd ) { qDebug() << qPrintable( spline->name() ) << "(" << points.size() << "):" << false; #if DEBUG_ERRORS > 0 if ( !okStart ) qDebug() << " invalid start condition"; if ( numErrorsM > 0 ) qDebug() << " invalid node conditions ( slope )" << numErrorsM; if ( numErrorsCV > 0 ) qDebug() << " invalid node conditions ( curvature )" << numErrorsCV; if ( !okEnd ) qDebug() << " invalid end condition"; #endif } } bool SplineTester::verifyBoundary( const QwtSpline* spline, QwtSpline::BoundaryPosition pos, const QPolygonF& points, const QVector< double >& m ) const { const bool isC2 = dynamic_cast< const QwtSplineC2* >( spline ); const int n = points.size(); const QwtSplinePolynomial polynomBegin = polynomialAt( 0, points, m ); const QwtSplinePolynomial polynomEnd = polynomialAt( n - 2, points, m ); bool ok = false; if ( spline->boundaryType() != QwtSpline::ConditionalBoundaries ) { // periodic or closed const double dx = points[n - 1].x() - points[n - 2].x(); ok = fuzzyCompare( polynomEnd.slopeAt( dx ), polynomBegin.slopeAt( 0.0 ) ); if ( ok && isC2 ) { ok = fuzzyCompare( polynomEnd.curvatureAt( dx ), polynomBegin.curvatureAt( 0.0 ) ); } return ok; } switch( spline->boundaryCondition( pos ) ) { case QwtSpline::Clamped1: { const double mt = ( pos == QwtSpline::AtBeginning ) ? m.first() : m.last(); ok = fuzzyCompare( mt, spline->boundaryValue( pos ) ); break; } case QwtSpline::Clamped2: { double cv; if ( pos == QwtSpline::AtBeginning ) { cv = polynomBegin.curvatureAt( 0.0 ); } else { const double dx = points[n - 1].x() - points[n - 2].x(); cv = polynomEnd.curvatureAt( dx ); } ok = fuzzyCompare( cv, spline->boundaryValue( pos ) ); break; } case QwtSpline::Clamped3: { double c3; if ( pos == QwtSpline::AtBeginning ) c3 = polynomBegin.c3; else c3 = polynomEnd.c3; ok = fuzzyCompare( 6.0 * c3, spline->boundaryValue( pos ) ); break; } case QwtSpline::LinearRunout: { const double ratio = spline->boundaryValue( pos ); if ( pos == QwtSpline::AtBeginning ) { const double s = ( points[1].y() - points[0].y() ) / ( points[1].x() - points[0].x() ); ok = fuzzyCompare( m[0], s - ratio * ( s - m[1] ) ); } else { const double s = ( points[n - 1].y() - points[n - 2].y() ) / ( points[n - 1].x() - points[n - 2].x() ); ok = fuzzyCompare( m[n - 1], s - ratio * ( s - m[n - 2] ) ); } break; } case QwtSplineC2::CubicRunout: { if ( pos == QwtSpline::AtBeginning ) { const QwtSplinePolynomial polynomBegin2 = polynomialAt( 1, points, m ); const double cv0 = polynomBegin.curvatureAt( 0.0 ); const double cv1 = polynomBegin2.curvatureAt( 0.0 ); const double cv2 = polynomBegin2.curvatureAt( points[2].x() - points[1].x() ); ok = fuzzyCompare( cv0, 2 * cv1 - cv2 ); } else { const QwtSplinePolynomial polynomEnd2 = polynomialAt( n - 3, points, m ); const double cv0 = polynomEnd.curvatureAt( points[n - 1].x() - points[n - 2].x() ); const double cv1 = polynomEnd.curvatureAt( 0.0 ); const double cv2 = polynomEnd2.curvatureAt( 0.0 ); ok = fuzzyCompare( cv0, 2 * cv1 - cv2 ); } break; } case QwtSplineC2::NotAKnot: { if ( pos == QwtSpline::AtBeginning ) { const QwtSplinePolynomial polynomBegin2 = polynomialAt( 1, points, m ); ok = fuzzyCompare( polynomBegin.c3, polynomBegin2.c3 ); } else { const QwtSplinePolynomial polynomEnd2 = polynomialAt( n - 3, points, m ); ok = fuzzyCompare( polynomEnd.c3, polynomEnd2.c3 ); } break; } } return ok; } int SplineTester::verifyNodesCV( const QPolygonF& points, const QVector< double >& cv ) const { const int n = points.size(); int numErrors = 0; double h1 = points[1].x() - points[0].x(); double s1 = ( points[1].y() - points[0].y() ) / h1; for ( int i = 1; i < n - 2; i++ ) { const double h2 = points[i + 1].x() - points[i].x(); const double s2 = ( points[i + 1].y() - points[i].y() ) / h2; const double v = ( h1 + h2 ) * cv[i] + 0.5 * ( h1 * cv[i - 1] + h2 * cv[i + 1] ); if ( !fuzzyCompare( v, 3 * ( s2 - s1 ) ) ) { #if DEBUG_ERRORS > 1 qDebug() << "invalid node condition (cv)" << i << cv[i - 1] << cv[i] << cv[i + 1] << v - 3 * ( s2 - s1 ); #endif numErrors++; } h1 = h2; s1 = s2; } return numErrors; } int SplineTester::verifyNodesM( const QPolygonF& points, const QVector< double >& m ) const { const int n = points.size(); int numErrors = 0; double dx1 = points[1].x() - points[0].x(); QwtSplinePolynomial polynomial1 = QwtSplinePolynomial::fromSlopes( dx1, points[1].y() - points[0].y(), m[0], m[1] ); for ( int i = 1; i < n - 1; i++ ) { const double dx2 = points[i + 1].x() - points[i].x(); const double dy2 = points[i + 1].y() - points[i].y(); const QwtSplinePolynomial polynomial2 = QwtSplinePolynomial::fromSlopes( dx2, dy2, m[i], m[i + 1] ); const double cv1 = polynomial1.curvatureAt( dx1 ); const double cv2 = polynomial2.curvatureAt( 0.0 ); if ( !fuzzyCompare( cv1, cv2 ) ) { #if DEBUG_ERRORS > 1 qDebug() << "invalid node condition (m)" << i << cv1 << cv1 << cv2 - cv1; #endif numErrors++; } dx1 = dx2; polynomial1 = polynomial2; } return numErrors; } static void testSplines( SplineTester::Type splineType, const QPolygonF& points ) { struct Condition { const char* name; int condition; double valueBegin; double valueEnd; } conditions[] = { { "Periodic", -1, 0.0, 0.0 }, { "Clamped1", QwtSpline::Clamped1, 0.5, 1.0 }, { "Clamped2", QwtSpline::Clamped2, 0.4, -0.8 }, { "Clamped3", QwtSpline::Clamped3, 0.03, 0.01 }, { "Linear Runout", QwtSpline::LinearRunout, 0.3, 0.7 }, { "Cubic Runout", QwtSplineC2::CubicRunout, 0.0, 0.0 }, { "Not A Knot", QwtSplineC2::NotAKnot, 0.0, 0.0 } }; for ( uint i = 0; i < sizeof( conditions ) / sizeof( conditions[0] ); i++ ) { const Condition& c = conditions[i]; if ( conditions[i].condition == QwtSplineC2::NotAKnot && points.size() < 4 ) continue; if ( splineType != SplineTester::Cubic ) { LocalSpline* spline; if ( splineType == SplineTester::Cardinal ) { const QString name = QString( c.name ) + " Spline Cardinal"; spline = new LocalSpline( QwtSplineLocal::Cardinal, name ); } else { const QString name = QString( c.name ) + " Spline Akima"; spline = new LocalSpline( QwtSplineLocal::Akima, name ); } if ( c.condition < 0 ) { spline->setBoundaryType( QwtSpline::PeriodicPolygon ); } else { if ( c.condition == QwtSplineC2::NotAKnot || c.condition == QwtSplineC2::CubicRunout ) { // requires C2 continuity continue; } spline->setBoundaryType( QwtSpline::ConditionalBoundaries ); spline->setBoundaryConditions( c.condition, conditions[i].valueBegin, c.valueEnd ); } SplineTester tester; tester.testSplineC1( spline, points ); delete spline; } else { const QString name = QString( c.name ) + " Spline Cubic"; CubicSpline spline( name ); if ( conditions[i].condition < 0 ) { spline.setBoundaryType( QwtSpline::PeriodicPolygon ); } else { spline.setBoundaryType( QwtSpline::ConditionalBoundaries ); spline.setBoundaryConditions( conditions[i].condition, conditions[i].valueBegin, conditions[i].valueEnd ); } SplineTester tester; tester.testSplineC2( &spline, points ); } } } static void testSplines( const QPolygonF& points ) { testSplines( SplineTester::Cardinal, points ); testSplines( SplineTester::Akima, points ); testSplines( SplineTester::Cubic, points ); } static void testSplines() { QPolygonF points; // 3 points points << QPointF( 10, 50 ) << QPointF( 60, 30 ) << QPointF( 82, 50 ); testSplines( points ); // 4 points points.clear(); points << QPointF( 10, 50 ) << QPointF( 60, 30 ) << QPointF( 70, 5 ) << QPointF( 82, 50 ); testSplines( points ); // 5 points points.clear(); points << QPointF( 10, 50 ) << QPointF( 20, 20 ) << QPointF( 60, 30 ) << QPointF( 70, 5 ) << QPointF( 82, 50 ); testSplines( points ); // 12 points points.clear(); points << QPointF( 10, 50 ) << QPointF( 20, 90 ) << QPointF( 25, 60 ) << QPointF( 35, 38 ) << QPointF( 42, 40 ) << QPointF( 55, 60 ) << QPointF( 60, 50 ) << QPointF( 65, 80 ) << QPointF( 73, 30 ) << QPointF( 82, 30 ) << QPointF( 87, 40 ) << QPointF( 95, 50 ); testSplines( points ); // many points points.clear(); const double x1 = 10.0; const double x2 = 1000.0; const double y1 = -10000.0; const double y2 = 10000.0; points += QPointF( x1, y1 ); const int n = 100; const double dx = ( x2 - x1 ) / n; const int mod = y2 - y1; for ( int i = 1; i < n - 1; i++ ) { const double r = qwtRand() % mod; points += QPointF( x1 + i * dx, y1 + r ); } points += QPointF( x2, y1 ); testSplines( points ); } static void testPaths( const char* prompt, const QwtSpline& spline, const QPolygonF& points1, const QPolygonF& points2 ) { const QPainterPath path1 = spline.painterPath( points1 ); const QPainterPath path2 = spline.painterPath( points2 ); if ( path1 != path2 ) { QString txt( "Parametric Spline" ); if ( spline.boundaryType() != QwtSpline::ConditionalBoundaries ) txt += "(closed)"; txt += ": "; txt += prompt; txt += " => failed."; qDebug() << qPrintable( txt ); } } static void testDuplicates() { QwtSplineLocal spline( QwtSplineLocal::Cardinal ); spline.setParametrization( QwtSplineParametrization::ParameterChordal ); QPolygonF points; points += QPointF( 1, 6 ); points += QPointF( 2, 7 ); points += QPointF( 3, 5); points += QPointF( 2, 4 ); points += QPointF( 0, 3 ); // inserting duplicates QPolygonF points1; for ( int i = 0; i < points.size(); i++ ) { points1 += points[i]; points1 += points[i]; } testPaths( "Duplicates", spline, points, points1 ); spline.setBoundaryType( QwtSpline::ClosedPolygon ); testPaths( "Duplicates", spline, points, points1 ); QPolygonF points2 = points; points2.append( points[0] ); testPaths( "First point also at End", spline, points, points2 ); QPolygonF points3 = points; points3.prepend( points.first() ); testPaths( "First point twice", spline, points, points3 ); QPolygonF points4 = points; points4.append( points.last() ); testPaths( "Last point twice", spline, points, points4 ); } int main() { testSplines(); testDuplicates(); }