• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 Solving the Nearest Point-on-Curve Problem
3 and
4 A Bezier Curve-Based Root-Finder
5 by Philip J. Schneider
6 from "Graphics Gems", Academic Press, 1990
7 */
8 
9  /*    point_on_curve.c    */
10 
11 #include <stdio.h>
12 #include <malloc.h>
13 #include <math.h>
14 #include "GraphicsGems.h"
15 
16 #define TESTMODE
17 
18 /*
19  *  Forward declarations
20  */
21 Point2  NearestPointOnCurve();
22 static    int    FindRoots();
23 static    Point2    *ConvertToBezierForm();
24 static    double    ComputeXIntercept();
25 static    int    ControlPolygonFlatEnough();
26 static    int    CrossingCount();
27 static    Point2    Bezier();
28 static    Vector2    V2ScaleII();
29 
30 int        MAXDEPTH = 64;    /*  Maximum depth for recursion */
31 
32 #define    EPSILON    (ldexp(1.0,-MAXDEPTH-1)) /*Flatness control value */
33 #define    DEGREE    3            /*  Cubic Bezier curve        */
34 #define    W_DEGREE 5            /*  Degree of eqn to find roots of */
35 
36 #ifdef TESTMODE
37 /*
38  *  main :
39  *    Given a cubic Bezier curve (i.e., its control points), and some
40  *    arbitrary point in the plane, find the point on the curve
41  *    closest to that arbitrary point.
42  */
main()43 main()
44 {
45 
46  static Point2 bezCurve[4] = {    /*  A cubic Bezier curve    */
47     { 0.0, 0.0 },
48     { 1.0, 2.0 },
49     { 3.0, 3.0 },
50     { 4.0, 2.0 },
51     };
52     static Point2 arbPoint = { 3.5, 2.0 }; /*Some arbitrary point*/
53     Point2    pointOnCurve;         /*  Nearest point on the curve */
54 
55     /*  Find the closest point */
56     pointOnCurve = NearestPointOnCurve(arbPoint, bezCurve);
57     printf("pointOnCurve : (%4.4f, %4.4f)\n", pointOnCurve.x,
58         pointOnCurve.y);
59 }
60 #endif /* TESTMODE */
61 
62 
63 /*
64  *  NearestPointOnCurve :
65  *      Compute the parameter value of the point on a Bezier
66  *        curve segment closest to some arbtitrary, user-input point.
67  *        Return the point on the curve at that parameter value.
68  *
69  */
70 Point2 NearestPointOnCurve(P, V)
71     Point2     P;            /* The user-supplied point      */
72     Point2     *V;            /* Control points of cubic Bezier */
73 {
74     Point2    *w;            /* Ctl pts for 5th-degree eqn    */
75     double     t_candidate[W_DEGREE];    /* Possible roots        */
76     int     n_solutions;        /* Number of roots found    */
77     double    t;            /* Parameter value of closest pt*/
78 
79     /*  Convert problem to 5th-degree Bezier form    */
80     w = ConvertToBezierForm(P, V);
81 
82     /* Find all possible roots of 5th-degree equation */
83     n_solutions = FindRoots(w, W_DEGREE, t_candidate, 0);
84     free((char *)w);
85 
86     /* Compare distances of P to all candidates, and to t=0, and t=1 */
87     {
88         double     dist, new_dist;
89         Point2     p;
90         Vector2  v;
91         int        i;
92 
93 
94     /* Check distance to beginning of curve, where t = 0    */
95         dist = V2SquaredLength(V2Sub(&P, &V[0], &v));
96             t = 0.0;
97 
98     /* Find distances for candidate points    */
99         for (i = 0; i < n_solutions; i++) {
100             p = Bezier(V, DEGREE, t_candidate[i],
101             (Point2 *)NULL, (Point2 *)NULL);
102             new_dist = V2SquaredLength(V2Sub(&P, &p, &v));
103             if (new_dist < dist) {
104                     dist = new_dist;
105                     t = t_candidate[i];
106             }
107         }
108 
109     /* Finally, look at distance to end point, where t = 1.0 */
110         new_dist = V2SquaredLength(V2Sub(&P, &V[DEGREE], &v));
111             if (new_dist < dist) {
112                 dist = new_dist;
113             t = 1.0;
114         }
115     }
116 
117     /*  Return the point on the curve at parameter value t */
118     printf("t : %4.12f\n", t);
119     return (Bezier(V, DEGREE, t, (Point2 *)NULL, (Point2 *)NULL));
120 }
121 
122 
123 /*
124  *  ConvertToBezierForm :
125  *        Given a point and a Bezier curve, generate a 5th-degree
126  *        Bezier-format equation whose solution finds the point on the
127  *      curve nearest the user-defined point.
128  */
129 static Point2 *ConvertToBezierForm(P, V)
130     Point2     P;            /* The point to find t for    */
131     Point2     *V;            /* The control points        */
132 {
133     int     i, j, k, m, n, ub, lb;
134     int     row, column;        /* Table indices        */
135     Vector2     c[DEGREE+1];        /* V(i)'s - P            */
136     Vector2     d[DEGREE];        /* V(i+1) - V(i)        */
137     Point2     *w;            /* Ctl pts of 5th-degree curve  */
138     double     cdTable[3][4];        /* Dot product of c, d        */
139     static double z[3][4] = {    /* Precomputed "z" for cubics    */
140     {1.0, 0.6, 0.3, 0.1},
141     {0.4, 0.6, 0.6, 0.4},
142     {0.1, 0.3, 0.6, 1.0},
143     };
144 
145 
146     /*Determine the c's -- these are vectors created by subtracting*/
147     /* point P from each of the control points                */
148     for (i = 0; i <= DEGREE; i++) {
149         V2Sub(&V[i], &P, &c[i]);
150     }
151     /* Determine the d's -- these are vectors created by subtracting*/
152     /* each control point from the next                    */
153     for (i = 0; i <= DEGREE - 1; i++) {
154         d[i] = V2ScaleII(V2Sub(&V[i+1], &V[i], &d[i]), 3.0);
155     }
156 
157     /* Create the c,d table -- this is a table of dot products of the */
158     /* c's and d's                            */
159     for (row = 0; row <= DEGREE - 1; row++) {
160         for (column = 0; column <= DEGREE; column++) {
161             cdTable[row][column] = V2Dot(&d[row], &c[column]);
162         }
163     }
164 
165     /* Now, apply the z's to the dot products, on the skew diagonal*/
166     /* Also, set up the x-values, making these "points"        */
167     w = (Point2 *)malloc((unsigned)(W_DEGREE+1) * sizeof(Point2));
168     for (i = 0; i <= W_DEGREE; i++) {
169         w[i].y = 0.0;
170         w[i].x = (double)(i) / W_DEGREE;
171     }
172 
173     n = DEGREE;
174     m = DEGREE-1;
175     for (k = 0; k <= n + m; k++) {
176         lb = MAX(0, k - m);
177         ub = MIN(k, n);
178         for (i = lb; i <= ub; i++) {
179             j = k - i;
180             w[i+j].y += cdTable[j][i] * z[j][i];
181         }
182     }
183 
184     return (w);
185 }
186 
187 
188 /*
189  *  FindRoots :
190  *    Given a 5th-degree equation in Bernstein-Bezier form, find
191  *    all of the roots in the interval [0, 1].  Return the number
192  *    of roots found.
193  */
194 static int FindRoots(w, degree, t, depth)
195     Point2     *w;            /* The control points        */
196     int     degree;        /* The degree of the polynomial    */
197     double     *t;            /* RETURN candidate t-values    */
198     int     depth;        /* The depth of the recursion    */
199 {
200     int     i;
201     Point2     Left[W_DEGREE+1],    /* New left and right         */
202               Right[W_DEGREE+1];    /* control polygons        */
203     int     left_count,        /* Solution count from        */
204         right_count;        /* children            */
205     double     left_t[W_DEGREE+1],    /* Solutions from kids        */
206                right_t[W_DEGREE+1];
207 
208     switch (CrossingCount(w, degree)) {
209            case 0 : {    /* No solutions here    */
210          return 0;
211     }
212     case 1 : {    /* Unique solution    */
213         /* Stop recursion when the tree is deep enough    */
214         /* if deep enough, return 1 solution at midpoint     */
215         if (depth >= MAXDEPTH) {
216             t[0] = (w[0].x + w[W_DEGREE].x) / 2.0;
217             return 1;
218         }
219         if (ControlPolygonFlatEnough(w, degree)) {
220             t[0] = ComputeXIntercept(w, degree);
221             return 1;
222         }
223         break;
224     }
225 }
226 
227     /* Otherwise, solve recursively after    */
228     /* subdividing control polygon        */
229     Bezier(w, degree, 0.5, Left, Right);
230     left_count  = FindRoots(Left,  degree, left_t, depth+1);
231     right_count = FindRoots(Right, degree, right_t, depth+1);
232 
233 
234     /* Gather solutions together    */
235     for (i = 0; i < left_count; i++) {
236         t[i] = left_t[i];
237     }
238     for (i = 0; i < right_count; i++) {
239          t[i+left_count] = right_t[i];
240     }
241 
242     /* Send back total number of solutions    */
243     return (left_count+right_count);
244 }
245 
246 
247 /*
248  * CrossingCount :
249  *    Count the number of times a Bezier control polygon
250  *    crosses the 0-axis. This number is >= the number of roots.
251  *
252  */
253 static int CrossingCount(V, degree)
254     Point2    *V;            /*  Control pts of Bezier curve    */
255     int        degree;            /*  Degreee of Bezier curve     */
256 {
257     int     i;
258     int     n_crossings = 0;    /*  Number of zero-crossings    */
259     int        sign, old_sign;        /*  Sign of coefficients    */
260 
261     sign = old_sign = SGN(V[0].y);
262     for (i = 1; i <= degree; i++) {
263         sign = SGN(V[i].y);
264         if (sign != old_sign) n_crossings++;
265         old_sign = sign;
266     }
267     return n_crossings;
268 }
269 
270 
271 
272 /*
273  *  ControlPolygonFlatEnough :
274  *    Check if the control polygon of a Bezier curve is flat enough
275  *    for recursive subdivision to bottom out.
276  *
277  */
278 static int ControlPolygonFlatEnough(V, degree)
279     Point2    *V;        /* Control points    */
280     int     degree;        /* Degree of polynomial    */
281 {
282     int     i;            /* Index variable        */
283     double     *distance;        /* Distances from pts to line    */
284     double     max_distance_above;    /* maximum of these        */
285     double     max_distance_below;
286     double     error;            /* Precision of root        */
287     double     intercept_1,
288                intercept_2,
289                left_intercept,
290                right_intercept;
291     double     a, b, c;        /* Coefficients of implicit    */
292                         /* eqn for line from V[0]-V[deg]*/
293 
294     /* Find the  perpendicular distance        */
295     /* from each interior control point to     */
296     /* line connecting V[0] and V[degree]    */
297     distance = (double *)malloc((unsigned)(degree + 1) *                     sizeof(double));
298     {
299     double    abSquared;
300 
301     /* Derive the implicit equation for line connecting first *'
302     /*  and last control points */
303     a = V[0].y - V[degree].y;
304     b = V[degree].x - V[0].x;
305     c = V[0].x * V[degree].y - V[degree].x * V[0].y;
306 
307     abSquared = (a * a) + (b * b);
308 
309         for (i = 1; i < degree; i++) {
310         /* Compute distance from each of the points to that line    */
311             distance[i] = a * V[i].x + b * V[i].y + c;
312             if (distance[i] > 0.0) {
313                 distance[i] = (distance[i] * distance[i]) / abSquared;
314             }
315             if (distance[i] < 0.0) {
316                 distance[i] = -((distance[i] * distance[i]) /                         abSquared);
317             }
318         }
319     }
320 
321 
322     /* Find the largest distance    */
323     max_distance_above = 0.0;
324     max_distance_below = 0.0;
325     for (i = 1; i < degree; i++) {
326         if (distance[i] < 0.0) {
327             max_distance_below = MIN(max_distance_below, distance[i]);
328         };
329         if (distance[i] > 0.0) {
330             max_distance_above = MAX(max_distance_above, distance[i]);
331         }
332     }
333     free((char *)distance);
334 
335     {
336     double    det, dInv;
337     double    a1, b1, c1, a2, b2, c2;
338 
339     /*  Implicit equation for zero line */
340     a1 = 0.0;
341     b1 = 1.0;
342     c1 = 0.0;
343 
344     /*  Implicit equation for "above" line */
345     a2 = a;
346     b2 = b;
347     c2 = c + max_distance_above;
348 
349     det = a1 * b2 - a2 * b1;
350     dInv = 1.0/det;
351 
352     intercept_1 = (b1 * c2 - b2 * c1) * dInv;
353 
354     /*  Implicit equation for "below" line */
355     a2 = a;
356     b2 = b;
357     c2 = c + max_distance_below;
358 
359     det = a1 * b2 - a2 * b1;
360     dInv = 1.0/det;
361 
362     intercept_2 = (b1 * c2 - b2 * c1) * dInv;
363     }
364 
365     /* Compute intercepts of bounding box    */
366     left_intercept = MIN(intercept_1, intercept_2);
367     right_intercept = MAX(intercept_1, intercept_2);
368 
369     error = 0.5 * (right_intercept-left_intercept);
370     if (error < EPSILON) {
371         return 1;
372     }
373     else {
374         return 0;
375     }
376 }
377 
378 
379 
380 /*
381  *  ComputeXIntercept :
382  *    Compute intersection of chord from first control point to last
383  *      with 0-axis.
384  *
385  */
386 /* NOTE: "T" and "Y" do not have to be computed, and there are many useless
387  * operations in the following (e.g. "0.0 - 0.0").
388  */
389 static double ComputeXIntercept(V, degree)
390     Point2     *V;            /*  Control points    */
391     int        degree;         /*  Degree of curve    */
392 {
393     double    XLK, YLK, XNM, YNM, XMK, YMK;
394     double    det, detInv;
395     double    S, T;
396     double    X, Y;
397 
398     XLK = 1.0 - 0.0;
399     YLK = 0.0 - 0.0;
400     XNM = V[degree].x - V[0].x;
401     YNM = V[degree].y - V[0].y;
402     XMK = V[0].x - 0.0;
403     YMK = V[0].y - 0.0;
404 
405     det = XNM*YLK - YNM*XLK;
406     detInv = 1.0/det;
407 
408     S = (XNM*YMK - YNM*XMK) * detInv;
409 /*  T = (XLK*YMK - YLK*XMK) * detInv; */
410 
411     X = 0.0 + XLK * S;
412 /*  Y = 0.0 + YLK * S; */
413 
414     return X;
415 }
416 
417 
418 /*
419  *  Bezier :
420  *    Evaluate a Bezier curve at a particular parameter value
421  *      Fill in control points for resulting sub-curves if "Left" and
422  *    "Right" are non-null.
423  *
424  */
425 static Point2 Bezier(V, degree, t, Left, Right)
426     int     degree;        /* Degree of bezier curve    */
427     Point2     *V;            /* Control pts            */
428     double     t;            /* Parameter value        */
429     Point2     *Left;        /* RETURN left half ctl pts    */
430     Point2     *Right;        /* RETURN right half ctl pts    */
431 {
432     int     i, j;        /* Index variables    */
433     Point2     Vtemp[W_DEGREE+1][W_DEGREE+1];
434 
435 
436     /* Copy control points    */
437     for (j =0; j <= degree; j++) {
438         Vtemp[0][j] = V[j];
439     }
440 
441     /* Triangle computation    */
442     for (i = 1; i <= degree; i++) {
443         for (j =0 ; j <= degree - i; j++) {
444             Vtemp[i][j].x =
445                   (1.0 - t) * Vtemp[i-1][j].x + t * Vtemp[i-1][j+1].x;
446             Vtemp[i][j].y =
447                   (1.0 - t) * Vtemp[i-1][j].y + t * Vtemp[i-1][j+1].y;
448         }
449     }
450 
451     if (Left != NULL) {
452         for (j = 0; j <= degree; j++) {
453             Left[j]  = Vtemp[j][0];
454         }
455     }
456     if (Right != NULL) {
457         for (j = 0; j <= degree; j++) {
458             Right[j] = Vtemp[degree-j][j];
459         }
460     }
461 
462     return (Vtemp[degree][0]);
463 }
464 
465 static Vector2 V2ScaleII(v, s)
466     Vector2    *v;
467     double    s;
468 {
469     Vector2 result;
470 
471     result.x = v->x * s; result.y = v->y * s;
472     return (result);
473 }
474