#define APPROXIMATION_EPSILON 1.0e-09
#define VERYSMALL 1.0e-20
#define MAXIMUM_ITERATIONS 1000
//simply clamps a value between 0 .. 1
float ClampToZeroOne(float value) {
if (value < .0f)
return .0f;
else if (value > 1.0f)
return 1.0f;
else
return value;
}
/**
* Returns the approximated parameter of a parametric curve for the value X
* @param atX At which value should the parameter be evaluated
* @param P0_X The first interpolation point of a curve segment
* @param C0_X The first control point of a curve segment
* @param C1_X The second control point of a curve segment
* @param P1_x The second interpolation point of a curve segment
* @return The parametric argument that is used to retrieve atX using the parametric function representation of this curve
*/
float ApproximateCubicBezierParameter (
float atX, float P0_X, float C0_X, float C1_X, float P1_X ) {
if (atX - P0_X < VERYSMALL)
return 0.0;
if (P1_X - atX < VERYSMALL)
return 1.0;
long iterationStep = 0;
float u = 0.0f; float v = 1.0f;
//iteratively apply subdivision to approach value atX
while (iterationStep < MAXIMUM_ITERATIONS) {
// de Casteljau Subdivision.
double a = (P0_X + C0_X)*0.5f;
double b = (C0_X + C1_X)*0.5f;
double c = (C1_X + P1_X)*0.5f;
double d = (a + b)*0.5f;
double e = (b + c)*0.5f;
double f = (d + e)*0.5f; //this one is on the curve!
//The curve point is close enough to our wanted atX
if (fabs(f - atX) < APPROXIMATION_EPSILON)
return ClampToZeroOne((u + v)*0.5f);
//dichotomy
if (f < atX) {
P0_X = f;
C0_X = e;
C1_X = c;
u = (u + v)*0.5f;
} else {
C0_X = a; C1_X = d; P1_X = f; v = (u + v)*0.5f;
}
iterationStep++;
}
return ClampToZeroOne((u + v)*0.5f);
}