|
|
//====== Copyright � 1996-2009, Valve Corporation, All rights reserved. =======
//
// A template implementation of a bezier curve class and associated helper
// functions.
//
//=============================================================================
#ifndef BEZIERCURVE_H
#define BEZIERCURVE_H
#ifdef _WIN32
#pragma once
#endif
const float ONE_THIRD = 1.0f / 3.0f; const float TWO_THIRDS = 2.0f / 3.0f;
//-----------------------------------------------------------------------------
// Generic order N Bezier curve evaluation. Evaluates the bezier curve at the
// specified 0 to 1 parameter and returns the result.
//-----------------------------------------------------------------------------
template< class POINT_TYPE, int ORDER > struct BezierEvaluateImpl { // This generic implementation performs an iterative set of lerps in order
// to compute the bezier evaluation for any order curve, it is not efficiently
// and here primarily to maintain generality. All order curve that are used
// with any frequency should have their own specialized implementations.
static void BezierEvaluate( const POINT_TYPE *pControlPoints, float t, POINT_TYPE &result ) { // This array is initialized with the control points and is then
// used to hold the intermediate results of each lerp in order
// to preserve the original set of control points.
POINT_TYPE points[ ORDER + 1 ];
for ( int i = 0; i <= ORDER; ++i ) { points[ i ] = pControlPoints[ i ]; }
for ( int i = 1; i <= ORDER; ++i ) { for ( int j = 0; j <= ( ORDER - i ); ++j ) { points[ j ] = ( ( 1.0f - t ) * points[ j ] ) + ( t * points[ j + 1 ] ); } }
result = points[ 0 ]; } };
//-----------------------------------------------------------------------------
// Partial specialization for linear evaluation.
//-----------------------------------------------------------------------------
template< class POINT_TYPE > struct BezierEvaluateImpl< POINT_TYPE, 1 > { static void BezierEvaluate( const POINT_TYPE *points, float t, POINT_TYPE &result ) { float u = 1.0f - t; result = ( u * points[ 0 ] ) + ( t * points[ 1 ] ); } };
//-----------------------------------------------------------------------------
// Partial specialization for quadratic bezier curve evaluation
//-----------------------------------------------------------------------------
template< class POINT_TYPE > struct BezierEvaluateImpl< POINT_TYPE, 2 > { static void BezierEvaluate( const POINT_TYPE *points, float t, POINT_TYPE &result ) { float u = 1.0f - t; float t2 = t * t; float u2 = u * u; result = ( u2 * points[ 0 ] ) + ( 2.0f * u * t * points[ 1 ] ) + ( t2 * points[ 2 ] ); } };
//-----------------------------------------------------------------------------
// Partial specialization for cubic Bezier curve evaluation.
//-----------------------------------------------------------------------------
template< class POINT_TYPE > struct BezierEvaluateImpl< POINT_TYPE, 3 > { static void BezierEvaluate( const POINT_TYPE *points, float t, POINT_TYPE &result ) { float u = 1.0f - t; float t2 = t * t; float t3 = t * t * t; float u2 = u * u; float u3 = u * u * u; result = ( u3 * points[ 0 ] ) + ( 3.0f * u2 * t * points[ 1 ] ) + ( 3.0f * u * t2 * points[ 2 ] ) + ( t3 * points[ 3 ] ); } };
//-----------------------------------------------------------------------------
// Evaluate the bezier curve of the specified order given a set of control
// points for the curve. Uses the BezierEvaluateImpl in order to allow
// template partial specialization for specific order curves.
//-----------------------------------------------------------------------------
template< class POINT_TYPE, int ORDER > void BezierEvaluate( const POINT_TYPE *pControlPoints, float t, POINT_TYPE &result ) { BezierEvaluateImpl< POINT_TYPE, ORDER >::BezierEvaluate( pControlPoints, t, result ); }
//-----------------------------------------------------------------------------
// Generic order N Bezier curve tangent evaluation. Evaluates the derivative of
// the Bezier curve at the specified 0 to 1 parameter and returns the result.
//-----------------------------------------------------------------------------
template< class POINT_TYPE, int ORDER > struct BezierTangentImpl { static void BezierTangent( const POINT_TYPE *pControlPoints, float t, POINT_TYPE &result ) { POINT_TYPE derPoints[ ORDER ]; for ( int i = 0; i < ORDER; ++i ) { derPoints[ i ] = ( pControlPoints[ i + 1 ] - pControlPoints[ i ] ) * (float)ORDER; } BezierEvaluate< POINT_TYPE, ORDER - 1 >( derPoints, t, result ); } };
//-----------------------------------------------------------------------------
// Partial specialization for linear Bezier curve tangent evaluation
//-----------------------------------------------------------------------------
template< class POINT_TYPE > struct BezierTangentImpl< POINT_TYPE, 1 > { static void BezierTangent( const POINT_TYPE *pControlPoints, float t, POINT_TYPE &result ) { POINT_TYPE derPoint; result = ( pControlPoints[ 1 ] - pControlPoints[ 0 ] ); } };
//-----------------------------------------------------------------------------
// Partial specialization for quadratic Bezier curve tangent evaluation
//-----------------------------------------------------------------------------
template< class POINT_TYPE > struct BezierTangentImpl< POINT_TYPE, 2 > { static void BezierTangent( const POINT_TYPE *pControlPoints, float t, POINT_TYPE &result ) { POINT_TYPE a = pControlPoints[ 0 ] + ( -2.0f * pControlPoints[ 1 ] ) + pControlPoints[ 2 ]; POINT_TYPE b = ( -2.0f * pControlPoints[ 0 ] ) + ( 2.0f * pControlPoints[ 1 ] ); result = ( 2.0f * a * t ) + b; } };
//-----------------------------------------------------------------------------
// Partial specialization for cubic Bezier curve tangent evaluation
//-----------------------------------------------------------------------------
template< class POINT_TYPE > struct BezierTangentImpl< POINT_TYPE, 3 > { static void BezierTangent( const POINT_TYPE *pControlPoints, float t, POINT_TYPE &result ) { POINT_TYPE a = (-1.0f * pControlPoints[ 0 ] ) + ( 3.0f * pControlPoints[ 1 ] ) + (-3.0f * pControlPoints[ 2 ] ) + pControlPoints[ 3 ]; POINT_TYPE b = ( 3.0f * pControlPoints[ 0 ] ) + (-6.0f * pControlPoints[ 1 ] ) + ( 3.0f * pControlPoints[ 2 ] ); POINT_TYPE c = (-3.0f * pControlPoints[ 0 ] ) + ( 3.0f * pControlPoints[ 1 ] ); result = ( 3.0f * a * t * t ) + ( 2.0f * b * t ) + c; } };
//-----------------------------------------------------------------------------
// Evaluate the derivative of the bezier curve in order to compute the tangent
// of the curve the the specified parameter. Uses BezierTangentImpl in order
// to allow template partial specialization for specific order curves.
//-----------------------------------------------------------------------------
template< class POINT_TYPE, int ORDER > void BezierTangent( const POINT_TYPE *pControlPoints, float t, POINT_TYPE &result ) { BezierTangentImpl< POINT_TYPE, ORDER >::BezierTangent( pControlPoints, t, result ); }
//-----------------------------------------------------------------------------
// The CBezierCurve represents an a order N bezier curve defined by control
// points of an arbitrary dimension. The class has template parameters for both
// the order ( ORDER ) and the point type ( POINT_TYPE ). In general the point
// type is expected to be vector, vector2d, or vector4d, but may work with other
// types if the appropriate operators are provided.
//
//-----------------------------------------------------------------------------
template < class POINT_TYPE, int ORDER > class CBezierCurve { protected:
static const int NUM_POINTS = ORDER + 1;
public:
// Default constructor, performs no initialization
CBezierCurve() {}
// Copy constructor
CBezierCurve( const CBezierCurve &source );
// Array constructor, initialize the bezier from an array of control points
explicit CBezierCurve( const POINT_TYPE controlPoints[ NUM_POINTS ] );
// Set all of the control points of the curve
void SetControlPoints( const POINT_TYPE controlPoints[ NUM_POINTS ] );
// Evaluate the curve at specified 0 to 1 parameter, returning the point on the curve
void Evaluate( float flParam, POINT_TYPE &point ) const;
// Compute the tangent vector at the specified parameter of the curve
void ComputeTangent( float flParam, POINT_TYPE &tangent ) const;
// Get the specified control point
const POINT_TYPE &ControlPoint( int index ) const { return m_ControlPoints[ index ]; }
protected:
POINT_TYPE m_ControlPoints[ NUM_POINTS ]; };
//-----------------------------------------------------------------------------
// Copy constructor
//-----------------------------------------------------------------------------
template< class POINT_TYPE, int ORDER > CBezierCurve< POINT_TYPE, ORDER >::CBezierCurve( const CBezierCurve &source ) { m_ControlPoints = source.m_ControlPoints; }
//-----------------------------------------------------------------------------
// Array constructor, initialize the bezier from an array of control points
//-----------------------------------------------------------------------------
template< class POINT_TYPE, int ORDER > CBezierCurve< POINT_TYPE, ORDER >::CBezierCurve( const POINT_TYPE controlPoints[ NUM_POINTS ] ) { SetControlPoints( controlPoints ); }
//-----------------------------------------------------------------------------
// Set all of the control points of the curve
//-----------------------------------------------------------------------------
template< class POINT_TYPE, int ORDER > void CBezierCurve< POINT_TYPE, ORDER >::SetControlPoints( const POINT_TYPE controlPoints[ NUM_POINTS ] ) { for ( int i = 0; i < NUM_POINTS; ++i ) { m_ControlPoints[ i ] = controlPoints[ i ]; } }
//-----------------------------------------------------------------------------
// Evaluate the bezier curve of the specified order given a set of control
// points for the curve
//-----------------------------------------------------------------------------
template< class POINT_TYPE, int ORDER > void CBezierCurve< POINT_TYPE, ORDER >::Evaluate( float t, POINT_TYPE &result ) const { BezierEvaluate< POINT_TYPE, ORDER >( m_ControlPoints, t, result ); }
//-----------------------------------------------------------------------------
// Compute the tangent vector at the specified parameter of the curve
//-----------------------------------------------------------------------------
template< class POINT_TYPE, int ORDER > void CBezierCurve< POINT_TYPE, ORDER>::ComputeTangent( float t, POINT_TYPE &tangent ) const { BezierTangent< POINT_TYPE, ORDER >( m_ControlPoints, t, tangent ); }
//-----------------------------------------------------------------------------
// The CCubicBezierCurve class represents a third order specialization of the
// generic CBezierCurve class and provided additional functionality which is
// implemented specifically for the cubic form of the bezier curve.
//-----------------------------------------------------------------------------
template < class POINT_TYPE > class CCubicBezierCurve : public CBezierCurve< POINT_TYPE, 3 > {
public:
// Default constructor, performs no initialization
CCubicBezierCurve() {}
// Array constructor, initialize the bezier from an array of control points
explicit CCubicBezierCurve( const POINT_TYPE controlPoints[ 4 ] );
// Compute the specified number of points along the curve
void ComputePoints( POINT_TYPE *pPoints, int numPoints ) const;
// Fit the curve to a set of data points
bool FitToPoints( const POINT_TYPE *pPoints, float *pParams, int nPoints, int nMaxSteps, float flMaxError );
private:
// Compute the bezier factor values used for evaluation that are independent of the parameter value
void ComputeFactors( POINT_TYPE &a, POINT_TYPE &b, POINT_TYPE &c, POINT_TYPE &d ) const;
// Perform a single step of the iterative point fitting process
bool FitToPointsStep( const POINT_TYPE *pPoints, float *pParams, int nPoints, bool bReparaterize );
// Calculate the control points of the curve that best fit the sample points with the specified parameters
void ComputeControlPointsForSamples( const POINT_TYPE *pPoints, float *pParams, int nNumPoints );
// Re-parameterize the provided set of points, finding parameter values which provide
// points on the curve closer to the sample points than the current parameter values.
void ReparameterizePoints( const POINT_TYPE *pPoints, float *pParams, int nNumPoints ) const;
// Compute the maximum squared distance between the specified points and the curve
float ComputeMaxError( const POINT_TYPE *pPoints, const float *pParams, int nNumPoints ) const;
// Compute the sum of the squared distance between the specified points and the curve
float ComputeErrorSum( const POINT_TYPE *pPoints, const float *pParams, int nNumPoints ) const;
};
//-----------------------------------------------------------------------------
// Array constructor, initialize the bezier from an array of control points
//-----------------------------------------------------------------------------
template< class POINT_TYPE > CCubicBezierCurve< POINT_TYPE >::CCubicBezierCurve( const POINT_TYPE controlPoints[ 4 ] ) { this->m_ControlPoints[ 0 ] = controlPoints[ 0 ]; this->m_ControlPoints[ 1 ] = controlPoints[ 1 ]; this->m_ControlPoints[ 2 ] = controlPoints[ 2 ]; this->m_ControlPoints[ 3 ] = controlPoints[ 3 ]; }
//-----------------------------------------------------------------------------
// Compute the specified number of points along the curve
//-----------------------------------------------------------------------------
template< class POINT_TYPE > void CCubicBezierCurve< POINT_TYPE >::ComputePoints( POINT_TYPE *pPoints, int numPoints ) const { // Must evaluate at least two points.
if ( numPoints <= 1 ) return; // Calculate the parameter increment for each step
const float flStep = 1.0f / ( numPoints - 1 );
// Compute the basis values that can be re-used for all of the point calculations
POINT_TYPE a, b, c, d; ComputeFactors( a, b, c, d );
float t = 0; for ( int i = 0; i < numPoints; ++i ) { pPoints[ i ] = ( a * t * t * t ) + ( b * t * t ) + ( c * t ) + d; t = t + flStep; } }
//-----------------------------------------------------------------------------
// Fit the curve to a set of data points
//-----------------------------------------------------------------------------
template < class POINT_TYPE > bool CCubicBezierCurve< POINT_TYPE >::FitToPoints( const POINT_TYPE *pPoints, float *pParams, int nPoints, int nMaxSteps, float flMaxError ) { if ( ( pPoints == NULL ) || ( pParams == NULL ) || ( nPoints < 2 ) ) return false; // Compute the max error distance, the provided max error is assumed
// to be a percentage of based on the length of the curve.
float flLengthSQ = pPoints[ 0 ].DistToSqr( pPoints[ nPoints - 1 ] );
// Do one initial step using chord length parameterization.
FitToPointsStep( pPoints, pParams, nPoints, false ); float flError = ComputeMaxError( pPoints, pParams, nPoints );
// Check to see if the error is reasonable enough to be solved by iteration.
float flMaxIterationError = flLengthSQ * 0.1f; if ( flError > flMaxIterationError ) { POINT_TYPE vSegment = pPoints[ nPoints - 1 ] - pPoints[ 0 ]; this->m_ControlPoints[ 0 ] = pPoints[ 0 ]; this->m_ControlPoints[ 1 ] = pPoints[ 0 ] + ( vSegment * ONE_THIRD ); this->m_ControlPoints[ 2 ] = pPoints[ 0 ] + ( vSegment * TWO_THIRDS ); this->m_ControlPoints[ 3 ] = pPoints[ nPoints - 1 ]; return false; }
// Iteratively improve the solution by re-evaluating the parameter values to make them match
// the sample points more closely and then re-fitting the curve using the least squares method.
int iStep = 0; float flMaxErrorDist = flLengthSQ * ( flMaxError * flMaxError );
while ( ( flError > flMaxErrorDist ) && ( iStep < nMaxSteps ) ) { FitToPointsStep( pPoints, pParams, nPoints, true ); flError = ComputeMaxError( pPoints, pParams, nPoints ); ++iStep; }
return ( flError <= flMaxErrorDist ); }
//-----------------------------------------------------------------------------
// Compute the bezier factor values used for evaluation that are independent of
// the parameter value
//-----------------------------------------------------------------------------
template < class POINT_TYPE > void CCubicBezierCurve< POINT_TYPE >::ComputeFactors( POINT_TYPE &a, POINT_TYPE &b, POINT_TYPE &c, POINT_TYPE &d ) const { const POINT_TYPE *pControlPoints = this->m_ControlPoints;
a = (-1.0f * pControlPoints[ 0 ] ) + ( 3.0f * pControlPoints[ 1 ] ) + (-3.0f * pControlPoints[ 2 ] ) + pControlPoints[ 3 ]; b = ( 3.0f * pControlPoints[ 0 ] ) + (-6.0f * pControlPoints[ 1 ] ) + ( 3.0f * pControlPoints[ 2 ] ); c = (-3.0f * pControlPoints[ 0 ] ) + ( 3.0f * pControlPoints[ 1 ] ); d = ( 1.0f * pControlPoints[ 0 ] ); }
//-----------------------------------------------------------------------------
// Perform a single step of the iterative point fitting process
//-----------------------------------------------------------------------------
template < class POINT_TYPE > bool CCubicBezierCurve< POINT_TYPE >::FitToPointsStep( const POINT_TYPE *pPoints, float *pParams, int nNumPoints, bool bReparameterize ) { POINT_TYPE *pControlPoints = this->m_ControlPoints;
if ( ( pPoints == NULL ) || ( pParams == NULL ) ) return false;
if ( nNumPoints < 2 ) return false;
if ( nNumPoints == 2) { pControlPoints[ 0 ] = pPoints[ 0 ]; pControlPoints[ 1 ] = pPoints[ 0 ] + ( pPoints[ 1 ] - pPoints[ 0 ] ) * ONE_THIRD; pControlPoints[ 2 ] = pPoints[ 0 ] + ( pPoints[ 1 ] - pPoints[ 0 ] ) * TWO_THIRDS; pControlPoints[ 3 ] = pPoints[ 1 ]; pParams[ 0 ] = 0.0f; pParams[ 1 ] = 1.0f; return true; }
if ( bReparameterize ) { ReparameterizePoints( pPoints, pParams, nNumPoints ); } else { // Chord length parameterization
float length = 0; pParams[ 0 ] = 0; for ( int i = 1; i < nNumPoints; ++i ) { float distance = pPoints[ i - 1 ].DistTo( pPoints[ i ] ); length += distance; pParams[ i ] = length; } for ( int i = 0; i < nNumPoints; ++i ) { pParams[ i ] = pParams[ i ] / length; } }
ComputeControlPointsForSamples( pPoints, pParams, nNumPoints );
return true; }
//-----------------------------------------------------------------------------
// Calculate the control points of the curve that best fit the sample points
// with the specified parameters.
//-----------------------------------------------------------------------------
template < class POINT_TYPE > void CCubicBezierCurve< POINT_TYPE >::ComputeControlPointsForSamples( const POINT_TYPE *pPoints, float *pParams, int nPoints ) { POINT_TYPE *pControlPoints = this->m_ControlPoints;
// Set end control points to the first and last sample points
pControlPoints[ 0 ] = pPoints[ 0 ]; pControlPoints[ 3 ] = pPoints[ nPoints - 1 ];
// Use the least squares method to calculate new controls points
float a1 = 0; float a2 = 0; float a12 = 0; POINT_TYPE c1; POINT_TYPE c2;
c1.Init(); c2.Init();
Assert( c1.IsZero() ); Assert( c2.IsZero() );
for ( int i = 0; i < nPoints; ++i ) { const POINT_TYPE &p = pPoints[ i ];
float t = pParams[ i ]; float t2 = t * t; float t3 = t * t2; float t4 = t * t3; float u = 1 - t; float u2 = u * u; float u3 = u * u2; float u4 = u * u3;
a1 += t2 * u4; a2 += t4 * u2; a12 += t3 * u3;
POINT_TYPE vP = p - (u3 * pControlPoints[ 0 ]) - ( t3 * pControlPoints[ 3 ] ); c1 += ( ( 3 * t * u2 ) * vP ); c2 += ( ( 3 * t2 * u ) * vP ); }
a1 = 9.0f * a1; a2 = 9.0f * a2; a12 = 9.0f * a12;
const float flFactorTolerance = 0.000001f; float flFactor = ( a1 * a2 - a12 * a12 ); if ( fabs( flFactor ) < flFactorTolerance ) { POINT_TYPE vSegment = pControlPoints[ 3 ] - pControlPoints[ 0 ]; pControlPoints[ 1 ] = pControlPoints[ 0 ] + vSegment * ONE_THIRD; pControlPoints[ 2 ] = pControlPoints[ 0 ] + vSegment * TWO_THIRDS; } else { pControlPoints[ 1 ] = ( a2 * c1 - a12 * c2 ) / flFactor; pControlPoints[ 2 ] = ( a1 * c2 - a12 * c1 ) / flFactor; }
// The subsequent re-parameterization relies on NewtonRaphson root finding which will
// fail if the tangents have an x delta of 0 or less, so ensure this does not happen.
float flMinStep = ( pControlPoints[ 3 ].x - pControlPoints[ 0 ].x ) * 0.0001f; float flMinX = pControlPoints[ 0 ].x + flMinStep; float flMaxX = pControlPoints[ 3 ].x - flMinStep; pControlPoints[ 1 ].x = MAX( flMinX, MIN( flMaxX, pControlPoints[ 1 ].x ) ); pControlPoints[ 2 ].x = MAX( flMinX, MIN( flMaxX, pControlPoints[ 2 ].x ) ); }
//-----------------------------------------------------------------------------
// Find a better parameter for the specified point using the NewtonRaphson
// method, or with simple iteration if the NewtonRaphson method fails.
//-----------------------------------------------------------------------------
template < class POINT_TYPE > void CCubicBezierCurve< POINT_TYPE >::ReparameterizePoints( const POINT_TYPE *pPoints, float *pParams, int nNumPoints ) const { const POINT_TYPE *pControlPoints = this->m_ControlPoints;
const float flTolerance = 0.0001f;
POINT_TYPE der1[ 3 ]; der1[ 0 ] = ( pControlPoints[ 1 ] - pControlPoints[ 0 ] ) * 3.0f; der1[ 1 ] = ( pControlPoints[ 2 ] - pControlPoints[ 1 ] ) * 3.0f; der1[ 2 ] = ( pControlPoints[ 3 ] - pControlPoints[ 2 ] ) * 3.0f;
POINT_TYPE der2[ 2 ]; der2[ 0 ] = ( der1[ 1 ] - der1[ 0 ] ) * 2.0f; der2[ 1 ] = ( der1[ 2 ] - der1[ 1 ] ) * 2.0f;
// Compute the basis values that can be re-used for all of the point calculations
POINT_TYPE b3a, b3b, b3c, b3d; ComputeFactors( b3a, b3b, b3c, b3d );
POINT_TYPE b2a = ( 1.0f * der1[ 0 ] ) + ( -2.0f * der1[ 1 ] ) + ( 1.0f * der1[ 2 ] ); POINT_TYPE b2b = ( -2.0f * der1[ 0 ] ) + ( 2.0f * der1[ 1 ] ); POINT_TYPE b2c = ( 1.0f * der1[ 0 ] );
POINT_TYPE b1a = der2[ 1 ] - der2[ 0 ]; POINT_TYPE b1b = der2[ 0 ];
float flPrevParam = 0; for ( int iPoint = 0; iPoint < nNumPoints; ++iPoint ) { float t = pParams[ iPoint ]; const POINT_TYPE &point = pPoints[ iPoint ];
POINT_TYPE curvePoint = ( b3a * t * t * t ) + ( b3b * t * t ) + ( b3c * t ) + b3d; POINT_TYPE der1Point = ( b2a * t * t ) + ( b2b * t ) + b2c; POINT_TYPE der2Point = ( b1a * t ) + b1b;
// Attempt to find a better parameter for the point
// using the NewtonRaphson root finding method.
POINT_TYPE vDelta = curvePoint - point; float flNumerator = vDelta.Dot( der1Point ); float flDenominator = vDelta.Dot( der2Point ) + der1Point.Dot( der1Point ); float flRootParam = ( flDenominator == 0.0f ) ? t : t - ( flNumerator / flDenominator );
// We are not interested in any solutions outside the 0 to 1 range, so
// clamp the result. This may give a result that is farther than the
// original parameter, in which case the original parameter will be used.
flRootParam = MAX( 0.0f, MIN( 1.0f, flRootParam ) );
// Evaluate the parameter returned by the root finding, to
// determine if it is actually better parameter for the point.
float rp = flRootParam; POINT_TYPE rootCurvePoint = ( b3a * rp * rp * rp ) + ( b3b * rp * rp ) + ( b3c * rp ) + b3d; float flDist = point.DistToSqr( curvePoint ); float flDistRoot = point.DistToSqr( rootCurvePoint ); // If the parameter returned by the root finding method gives a point on the
// curve that is closer to the sample point than the current parameter make
// the new parameter the value found by the root finding method.
float flNewParam = t; if ( flDistRoot <= flDist ) { flNewParam = flRootParam; } else if ( flDist > flTolerance ) { // If the root finding method failed, try to find a better parameter iteratively. This is
// basically a brute force method, but with a couple of observations which actually make it
// reasonable. First the direction to iterate from the current parameter can be deduced
// from the dot product of the vector from the point and the tangent of the curve. Second
// the range of iteration can be restricted such that values before the last parameter
// are not considered.
POINT_TYPE stepPoint; float flStepParam = t; float flStepDist = 0; float flBestStepParam = t; float flBestStepDist = flDist;
const int nMaxSteps = 10; const float flBaseStepSize = MAX( t - flPrevParam, 0.001f ) / ( float )nMaxSteps; float flStepSize = 0; int nStep = 0;
// The numerator of the root finding method is the dot product between the vector from the
// sample point to the point on the curve and the tangent of the curve. The tangent of the
// curve tells us which way the curve is going and we want to move the parameter along the
// curve in the way which is moving closer to the point, so if the dot product of the
// tangent and the vector from the point on the curve to the sample point is positive then
// moving in a positive direction along the curve will bring us closer to the sample point.
// However, the numerator value used the vector from the sample point to the curve point,
// so negative value implies a positive movement.
if ( flNumerator < 0 ) { flStepSize = flBaseStepSize; } else { flStepSize = -flBaseStepSize; }
// Starting with the current parameter, move the parameter by the calculated step interval
// and evaluate the result. Continue as long as the result is closer than the previous
// best result and the specified maximum number of steps has not been reached.
while ( nStep < nMaxSteps ) { flStepParam = MAX( 0.0f, MIN( 1.0f, flStepParam + flStepSize ) );
float sp = flStepParam; stepPoint = ( b3a * sp * sp * sp ) + ( b3b * sp * sp ) + ( b3c * sp ) + b3d; flStepDist = point.DistToSqr( stepPoint ); if ( flStepDist >= flBestStepDist ) break;
flBestStepParam = flStepParam; flBestStepDist = flStepDist; ++nStep; }
flNewParam = flBestStepParam; }
// Update the parameter to the new value which provides
// a closer point on the curve to the sample point.
Assert( flNewParam >= 0.0f ); Assert( flNewParam <= 1.0f ); pParams[ iPoint ] = flNewParam;
// Save the old parameter so it may be used by the next point
// to determine the iteration range if the root finding fails
flPrevParam = t; } }
//-----------------------------------------------------------------------------
// Compute the maximum squared distance between the specified points and the
// curve
//-----------------------------------------------------------------------------
template < class POINT_TYPE > float CCubicBezierCurve< POINT_TYPE >::ComputeMaxError( const POINT_TYPE *pPoints, const float *pParams, int nNumPoints ) const { float flMaxError = 0.0f;
POINT_TYPE a, b, c, d; ComputeFactors( a, b, c, d );
for ( int iPoint = 0; iPoint < nNumPoints; ++iPoint ) { const POINT_TYPE &samplePoint = pPoints[ iPoint ]; float t = pParams[ iPoint ];
POINT_TYPE curvePoint = ( a * t * t * t ) + ( b * t * t ) + ( c * t ) + d;
float flDistSQ = samplePoint.DistToSqr( curvePoint ); flMaxError = MAX( flDistSQ, flMaxError ); }
return flMaxError; }
//-----------------------------------------------------------------------------
// Compute the sum of the squared distance between the specified points and the
// curve
//-----------------------------------------------------------------------------
template < class POINT_TYPE > float CCubicBezierCurve< POINT_TYPE >::ComputeErrorSum( const POINT_TYPE *pPoints, const float *pParams, int nNumPoints ) const { float flErrorSum = 0.0f;
POINT_TYPE a, b, c, d; ComputeFactors( a, b, c, d );
for ( int iPoint = 0; iPoint < nNumPoints; ++iPoint ) { const POINT_TYPE &samplePoint = pPoints[ iPoint ]; float t = pParams[ iPoint ];
POINT_TYPE curvePoint = ( a * t * t * t ) + ( b * t * t ) + ( c * t ) + d;
float flDistSQ = samplePoint.DistToSqr( curvePoint ); flErrorSum += flDistSQ; }
return flErrorSum; }
#endif
|