📄 bezier.cc
字号:
#include <stddef.h> // for size_t// #include <sys/stdtypes.h> // for size_t, on some systems#include "Bezier.h"#include <math.h>/* The value of 1.0 / (1L<<23) is float machine epsilon. */#ifdef FLOAT_ACCURACY#define INV_EPS (1L<<23)#else/* The value of 1.0 / (1L<<14) is enough for most applications */#define INV_EPS (1L<<14)#endif#define log2(x) (log(x)/log(2.))extern "C" void qsort( char *base, int nel, size_t width, int (*compar)(void *, void *));int compare_doubles( void *a, void *b ) { register double *A = (double *)a, *B = (double *)b; return( *A > *B )?1:(*A < *B ? -1 : 0 ); }void Sort( double *array, int length ) { qsort( (char *)array, length, sizeof( double ), compare_doubles ); } /* * Split the curve at the midpoint, returning an array with the two parts * Temporary storage is minimized by using part of the storage for the result * to hold an intermediate value until it is no longer needed. */#define left r[0]#define right r[1]Bezier *Bezier::Split() { Bezier *r = new Bezier[2]; (left.p0 = p0)->refcount++; (right.p3 = p3)->refcount++; left.p1 = new point( p0, p1, 1); right.p2 = new point( p2, p3, 1); right.p1 = new point( p1, p2, 1); // temporary holding spot left.p2 = new point ( left.p1, right.p1, 1); *right.p1 = mid( right.p1, right.p2 ); // Real value this time left.p3 = right.p0 = new point( left.p2, right.p1, 2 ); return r; }#undef left#undef right /** Test the bounding boxes of two Bezier curves for interference.* Several observations:* First, it is cheaper to compute the bounding box of the second curve* and test its bounding box for interference than to use a more direct* approach of comparing all control points of the second curve with * the various edges of the bounding box of the first curve to test* for interference.* Second, after a few subdivisions it is highly probable that two corners* of the bounding box of a given Bezier curve are the first and last * control point. Once this happens once, it happens for all subsequent* subcurves. It might be worth putting in a test and then short-circuit* code for further subdivision levels.* Third, in the final comparison (the interference test) the comparisons* should both permit equality. We want to find intersections even if they* occur at the ends of segments.* Finally, there are tighter bounding boxes that can be derived. It isn't* clear whether the higher probability of rejection (and hence fewer* subdivisions and tests) is worth the extra work.*/int IntersectBB( Bezier a, Bezier b ) { // Compute bounding box for a double minax, maxax, minay, maxay; if( a.p0->x > a.p3->x ) // These are the most likely to be extremal minax = a.p3->x, maxax = a.p0->x; else minax = a.p0->x, maxax = a.p3->x; if( a.p2->x < minax ) minax = a.p2->x; else if( a.p2->x > maxax ) maxax = a.p2->x; if( a.p1->x < minax ) minax = a.p1->x; else if( a.p1->x > maxax ) maxax = a.p1->x; if( a.p0->y > a.p3->y ) minay = a.p3->y, maxay = a.p0->y; else minay = a.p0->y, maxay = a.p3->y; if( a.p2->y < minay ) minay = a.p2->y; else if( a.p2->y > maxay ) maxay = a.p2->y; if( a.p1->y < minay ) minay = a.p1->y; else if( a.p1->y > maxay ) maxay = a.p1->y; // Compute bounding box for b double minbx, maxbx, minby, maxby; if( b.p0->x > b.p3->x ) minbx = b.p3->x, maxbx = b.p0->x; else minbx = b.p0->x, maxbx = b.p3->x; if( b.p2->x < minbx ) minbx = b.p2->x; else if( b.p2->x > maxbx ) maxbx = b.p2->x; if( b.p1->x < minbx ) minbx = b.p1->x; else if( b.p1->x > maxbx ) maxbx = b.p1->x; if( b.p0->y > b.p3->y ) minby = b.p3->y, maxby = b.p0->y; else minby = b.p0->y, maxby = b.p3->y; if( b.p2->y < minby ) minby = b.p2->y; else if( b.p2->y > maxby ) maxby = b.p2->y; if( b.p1->y < minby ) minby = b.p1->y; else if( b.p1->y > maxby ) maxby = b.p1->y; // Test bounding box of b against bounding box of a if( ( minax > maxbx ) || ( minay > maxby ) // Not >= : need boundary case || ( minbx > maxax ) || ( minby > maxay ) ) return 0; // they don't intersect else return 1; // they intersect } /* * Recursively intersect two curves keeping track of their real parameters * and depths of intersection.* The results are returned in a 2-D array of doubles indicating the parameters* for which intersections are found. The parameters are in the order the* intersections were found, which is probably not in sorted order.* When an intersection is found, the parameter value for each of the two * is stored in the index elements array, and the index is incremented.* * If either of the curves has subdivisions left before it is straight* (depth > 0)* that curve (possibly both) is (are) subdivided at its (their) midpoint(s).* the depth(s) is (are) decremented, and the parameter value(s) corresponding* to the midpoints(s) is (are) computed.* Then each of the subcurves of one curve is intersected with each of the * subcurves of the other curve, first by testing the bounding boxes for* interference. If there is any bounding box interference, the corresponding* subcurves are recursively intersected.* * If neither curve has subdivisions left, the line segments from the first* to last control point of each segment are intersected. (Actually the * only the parameter value corresponding to the intersection point is found).** The apriori flatness test is probably more efficient than testing at each* level of recursion, although a test after three or four levels would* probably be worthwhile, since many curves become flat faster than their * asymptotic rate for the first few levels of recursion.** The bounding box test fails much more frequently than it succeeds, providing* substantial pruning of the search space.** Each (sub)curve is subdivided only once, hence it is not possible that for * one final line intersection test the subdivision was at one level, while* for another final line intersection test the subdivision (of the same curve)* was at another. Since the line segments share endpoints, the intersection* is robust: a near-tangential intersection will yield zero or two* intersections.*/void RecursivelyIntersect( Bezier a, double t0, double t1, int deptha, Bezier b, double u0, double u1, int depthb, double **parameters, int &index ) { if( deptha > 0 ) { Bezier *A = a.Split(); double tmid = (t0+t1)*0.5; deptha--; if( depthb > 0 ) { Bezier *B = b.Split(); double umid = (u0+u1)*0.5; depthb--; if( IntersectBB( A[0], B[0] ) ) RecursivelyIntersect( A[0], t0, tmid, deptha, B[0], u0, umid, depthb, parameters, index ); if( IntersectBB( A[1], B[0] ) ) RecursivelyIntersect( A[1], tmid, t1, deptha, B[0], u0, umid, depthb, parameters, index ); if( IntersectBB( A[0], B[1] ) ) RecursivelyIntersect( A[0], t0, tmid, deptha, B[1], umid, u1, depthb, parameters, index ); if( IntersectBB( A[1], B[1] ) ) RecursivelyIntersect( A[1], tmid, t1, deptha, B[1], umid, u1, depthb, parameters, index ); } else { if( IntersectBB( A[0], b ) ) RecursivelyIntersect( A[0], t0, tmid, deptha, b, u0, u1, depthb, parameters, index ); if( IntersectBB( A[1], b ) ) RecursivelyIntersect( A[1], tmid, t1, deptha, b, u0, u1, depthb, parameters, index ); } } else if( depthb > 0 ) { Bezier *B = b.Split(); double umid = (u0 + u1)*0.5; depthb--; if( IntersectBB( a, B[0] ) ) RecursivelyIntersect( a, t0, t1, deptha, B[0], u0, umid, depthb, parameters, index ); if( IntersectBB( a, B[1] ) ) RecursivelyIntersect( a, t0, t1, deptha, B[0], umid, u1, depthb, parameters, index ); } else // Both segments are fully subdivided; now do line segments { double xlk = a.p3->x - a.p0->x; double ylk = a.p3->y - a.p0->y; double xnm = b.p3->x - b.p0->x; double ynm = b.p3->y - b.p0->y; double xmk = b.p0->x - a.p0->x; double ymk = b.p0->y - a.p0->y; double det = xnm * ylk - ynm * xlk; if( 1.0 + det == 1.0 ) return; else { double detinv = 1.0 / det; double s = ( xnm * ymk - ynm *xmk ) * detinv; double t = ( xlk * ymk - ylk * xmk ) * detinv; if( ( s < 0.0 ) || ( s > 1.0 ) || ( t < 0.0 ) || ( t > 1.0 ) ) return; parameters[0][index] = t0 + s * ( t1 - t0 ); parameters[1][index] = u0 + t * ( u1 - u0 ); index++; } } }inline double log4( double x ) { return 0.5 * log2( x ); } /* * Wang's theorem is used to estimate the level of subdivision required, * but only if the bounding boxes interfere at the top level. * Assuming there is a possible intersection, RecursivelyIntersect is * used to find all the parameters corresponding to intersection points. * these are then sorted and returned in an array. */double ** FindIntersections( Bezier a, Bezier b ) { double **parameters = new double *[2]; parameters[0] = new double[9]; parameters[1] = new double[9]; int index = 0; if( IntersectBB( a, b ) ) { vector la1 = vabs( ( *(a.p2) - *(a.p1) ) - ( *(a.p1) - *(a.p0) ) ); vector la2 = vabs( ( *(a.p3) - *(a.p2) ) - ( *(a.p2) - *(a.p1) ) ); vector la; if( la1.x > la2.x ) la.x = la1.x; else la.x = la2.x; if( la1.y > la2.y ) la.y = la1.y; else la.y = la2.y; vector lb1 = vabs( ( *(b.p2) - *(b.p1) ) - ( *(b.p1) - *(b.p0) ) ); vector lb2 = vabs( ( *(b.p3) - *(b.p2) ) - ( *(b.p2) - *(b.p1) ) ); vector lb; if( lb1.x > lb2.x ) lb.x = lb1.x; else lb.x = lb2.x; if( lb1.y > lb2.y ) lb.y = lb1.y; else lb.y = lb2.y; double l0; if( la.x > la.y ) l0 = la.x; else l0 = la.y; int ra; if( l0 * 0.75 * M_SQRT2 + 1.0 == 1.0 ) ra = 0; else ra = (int)ceil( log4( M_SQRT2 * 6.0 / 8.0 * INV_EPS * l0 ) ); if( lb.x > lb.y ) l0 = lb.x; else l0 = lb.y; int rb; if( l0 * 0.75 * M_SQRT2 + 1.0 == 1.0 ) rb = 0; else rb = (int)ceil(log4( M_SQRT2 * 6.0 / 8.0 * INV_EPS * l0 ) ); RecursivelyIntersect( a, 0., 1., ra, b, 0., 1., rb, parameters, index ); } if( index < 9 ) parameters[0][index] = parameters[1][index] = -1.0; Sort( parameters[0], index ); Sort( parameters[1], index ); return parameters; }voidBezier::ParameterSplitLeft( double t, Bezier &left ) { left.p0 = p0; left.p1 = new point( *p0 + t * ( *p1 - *p0 ) ); left.p2 = new point( *p1 + t * ( *p2 - *p1 ) ); // temporary holding spot p2->refcount--; p2 = new point( *p2 + t * ( *p3 - *p2 ) ); p1->refcount--; p1 = new point( *(left.p2) + t * ( *p2 - *(left.p2) ) ); *(left.p2) = ( *(left.p1) + t * ( *(left.p2) - *(left.p1) ) ); (left.p3 = p0 = new point(*(left.p2) + t * (*(p1)-*(left.p2))))->refcount++; left.p0->refcount++; left.p1->refcount++; left.p2->refcount++; left.p3->refcount++; } /* * Intersect two curves, returning an array of two arrays of curves. * The first array of curves corresponds to `this' curve, the second * corresponds to curve B, passed in. * The intersection parameter values are computed by FindIntersections, * and they come back in the range 0..1, using the original parameterization. * Once one segment has been removed, ie the curve is split at splitT, the * parameterization of the second half is from 0..1, so the parameter for * the next split point, if any, must be adjusted. * If we split at t[i], the split point at t[i+1] is * ( t[i+1] - t[i] ) / ( t - t[i] ) of the way to the end from the new * start point. */Bezier **Bezier::Intersect( Bezier B ) { // The return from FindIntersections will decrement all refcounts. // (a c++-ism) B.p0->refcount++; B.p1->refcount++; B.p2->refcount++; B.p3->refcount++; Bezier **rvalue = new Bezier *[2]; rvalue[0] = new Bezier[10]; rvalue[1] = new Bezier[10]; double **t = FindIntersections( *this, B ); int index = 0; if( t[0][0] > -0.5 ) { ParameterSplitLeft( t[0][0], rvalue[0][0] ); B.ParameterSplitLeft( t[1][0], rvalue[1][0] ); index++; while( t[0][index] > -0.5 && index < 9 ) { double splitT = (t[0][index] - t[0][index-1])/(1.0 - t[0][index-1]); ParameterSplitLeft( splitT, rvalue[0][index] ); splitT = (t[1][index] - t[1][index-1])/(1.0 - t[1][index-1]); B.ParameterSplitLeft( splitT, rvalue[1][index] ); index++; } } rvalue[0][index] = *this; rvalue[1][index] = B; return rvalue; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -