|  | /* | 
|  | Solving the Nearest Point-on-Curve Problem | 
|  | and | 
|  | A Bezier Curve-Based Root-Finder | 
|  | by Philip J. Schneider | 
|  | from "Graphics Gems", Academic Press, 1990 | 
|  | */ | 
|  |  | 
|  | /*    point_on_curve.c    */ | 
|  |  | 
|  | #include <stdio.h> | 
|  | #include <malloc.h> | 
|  | #include <math.h> | 
|  | #include "GraphicsGems.h" | 
|  |  | 
|  | #define TESTMODE | 
|  |  | 
|  | /* | 
|  | *  Forward declarations | 
|  | */ | 
|  | Point2  NearestPointOnCurve(); | 
|  | static    int    FindRoots(); | 
|  | static    Point2    *ConvertToBezierForm(); | 
|  | static    double    ComputeXIntercept(); | 
|  | static    int    ControlPolygonFlatEnough(); | 
|  | static    int    CrossingCount(); | 
|  | static    Point2    Bezier(); | 
|  | static    Vector2    V2ScaleII(); | 
|  |  | 
|  | int        MAXDEPTH = 64;    /*  Maximum depth for recursion */ | 
|  |  | 
|  | #define    EPSILON    (ldexp(1.0,-MAXDEPTH-1)) /*Flatness control value */ | 
|  | #define    DEGREE    3            /*  Cubic Bezier curve        */ | 
|  | #define    W_DEGREE 5            /*  Degree of eqn to find roots of */ | 
|  |  | 
|  | #ifdef TESTMODE | 
|  | /* | 
|  | *  main : | 
|  | *    Given a cubic Bezier curve (i.e., its control points), and some | 
|  | *    arbitrary point in the plane, find the point on the curve | 
|  | *    closest to that arbitrary point. | 
|  | */ | 
|  | main() | 
|  | { | 
|  |  | 
|  | static Point2 bezCurve[4] = {    /*  A cubic Bezier curve    */ | 
|  | { 0.0, 0.0 }, | 
|  | { 1.0, 2.0 }, | 
|  | { 3.0, 3.0 }, | 
|  | { 4.0, 2.0 }, | 
|  | }; | 
|  | static Point2 arbPoint = { 3.5, 2.0 }; /*Some arbitrary point*/ | 
|  | Point2    pointOnCurve;         /*  Nearest point on the curve */ | 
|  |  | 
|  | /*  Find the closest point */ | 
|  | pointOnCurve = NearestPointOnCurve(arbPoint, bezCurve); | 
|  | printf("pointOnCurve : (%4.4f, %4.4f)\n", pointOnCurve.x, | 
|  | pointOnCurve.y); | 
|  | } | 
|  | #endif /* TESTMODE */ | 
|  |  | 
|  |  | 
|  | /* | 
|  | *  NearestPointOnCurve : | 
|  | *      Compute the parameter value of the point on a Bezier | 
|  | *        curve segment closest to some arbtitrary, user-input point. | 
|  | *        Return the point on the curve at that parameter value. | 
|  | * | 
|  | */ | 
|  | Point2 NearestPointOnCurve(P, V) | 
|  | Point2     P;            /* The user-supplied point      */ | 
|  | Point2     *V;            /* Control points of cubic Bezier */ | 
|  | { | 
|  | Point2    *w;            /* Ctl pts for 5th-degree eqn    */ | 
|  | double     t_candidate[W_DEGREE];    /* Possible roots        */ | 
|  | int     n_solutions;        /* Number of roots found    */ | 
|  | double    t;            /* Parameter value of closest pt*/ | 
|  |  | 
|  | /*  Convert problem to 5th-degree Bezier form    */ | 
|  | w = ConvertToBezierForm(P, V); | 
|  |  | 
|  | /* Find all possible roots of 5th-degree equation */ | 
|  | n_solutions = FindRoots(w, W_DEGREE, t_candidate, 0); | 
|  | free((char *)w); | 
|  |  | 
|  | /* Compare distances of P to all candidates, and to t=0, and t=1 */ | 
|  | { | 
|  | double     dist, new_dist; | 
|  | Point2     p; | 
|  | Vector2  v; | 
|  | int        i; | 
|  |  | 
|  |  | 
|  | /* Check distance to beginning of curve, where t = 0    */ | 
|  | dist = V2SquaredLength(V2Sub(&P, &V[0], &v)); | 
|  | t = 0.0; | 
|  |  | 
|  | /* Find distances for candidate points    */ | 
|  | for (i = 0; i < n_solutions; i++) { | 
|  | p = Bezier(V, DEGREE, t_candidate[i], | 
|  | (Point2 *)NULL, (Point2 *)NULL); | 
|  | new_dist = V2SquaredLength(V2Sub(&P, &p, &v)); | 
|  | if (new_dist < dist) { | 
|  | dist = new_dist; | 
|  | t = t_candidate[i]; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Finally, look at distance to end point, where t = 1.0 */ | 
|  | new_dist = V2SquaredLength(V2Sub(&P, &V[DEGREE], &v)); | 
|  | if (new_dist < dist) { | 
|  | dist = new_dist; | 
|  | t = 1.0; | 
|  | } | 
|  | } | 
|  |  | 
|  | /*  Return the point on the curve at parameter value t */ | 
|  | printf("t : %4.12f\n", t); | 
|  | return (Bezier(V, DEGREE, t, (Point2 *)NULL, (Point2 *)NULL)); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | *  ConvertToBezierForm : | 
|  | *        Given a point and a Bezier curve, generate a 5th-degree | 
|  | *        Bezier-format equation whose solution finds the point on the | 
|  | *      curve nearest the user-defined point. | 
|  | */ | 
|  | static Point2 *ConvertToBezierForm(P, V) | 
|  | Point2     P;            /* The point to find t for    */ | 
|  | Point2     *V;            /* The control points        */ | 
|  | { | 
|  | int     i, j, k, m, n, ub, lb; | 
|  | int     row, column;        /* Table indices        */ | 
|  | Vector2     c[DEGREE+1];        /* V(i)'s - P            */ | 
|  | Vector2     d[DEGREE];        /* V(i+1) - V(i)        */ | 
|  | Point2     *w;            /* Ctl pts of 5th-degree curve  */ | 
|  | double     cdTable[3][4];        /* Dot product of c, d        */ | 
|  | static double z[3][4] = {    /* Precomputed "z" for cubics    */ | 
|  | {1.0, 0.6, 0.3, 0.1}, | 
|  | {0.4, 0.6, 0.6, 0.4}, | 
|  | {0.1, 0.3, 0.6, 1.0}, | 
|  | }; | 
|  |  | 
|  |  | 
|  | /*Determine the c's -- these are vectors created by subtracting*/ | 
|  | /* point P from each of the control points                */ | 
|  | for (i = 0; i <= DEGREE; i++) { | 
|  | V2Sub(&V[i], &P, &c[i]); | 
|  | } | 
|  | /* Determine the d's -- these are vectors created by subtracting*/ | 
|  | /* each control point from the next                    */ | 
|  | for (i = 0; i <= DEGREE - 1; i++) { | 
|  | d[i] = V2ScaleII(V2Sub(&V[i+1], &V[i], &d[i]), 3.0); | 
|  | } | 
|  |  | 
|  | /* Create the c,d table -- this is a table of dot products of the */ | 
|  | /* c's and d's                            */ | 
|  | for (row = 0; row <= DEGREE - 1; row++) { | 
|  | for (column = 0; column <= DEGREE; column++) { | 
|  | cdTable[row][column] = V2Dot(&d[row], &c[column]); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Now, apply the z's to the dot products, on the skew diagonal*/ | 
|  | /* Also, set up the x-values, making these "points"        */ | 
|  | w = (Point2 *)malloc((unsigned)(W_DEGREE+1) * sizeof(Point2)); | 
|  | for (i = 0; i <= W_DEGREE; i++) { | 
|  | w[i].y = 0.0; | 
|  | w[i].x = (double)(i) / W_DEGREE; | 
|  | } | 
|  |  | 
|  | n = DEGREE; | 
|  | m = DEGREE-1; | 
|  | for (k = 0; k <= n + m; k++) { | 
|  | lb = MAX(0, k - m); | 
|  | ub = MIN(k, n); | 
|  | for (i = lb; i <= ub; i++) { | 
|  | j = k - i; | 
|  | w[i+j].y += cdTable[j][i] * z[j][i]; | 
|  | } | 
|  | } | 
|  |  | 
|  | return (w); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | *  FindRoots : | 
|  | *    Given a 5th-degree equation in Bernstein-Bezier form, find | 
|  | *    all of the roots in the interval [0, 1].  Return the number | 
|  | *    of roots found. | 
|  | */ | 
|  | static int FindRoots(w, degree, t, depth) | 
|  | Point2     *w;            /* The control points        */ | 
|  | int     degree;        /* The degree of the polynomial    */ | 
|  | double     *t;            /* RETURN candidate t-values    */ | 
|  | int     depth;        /* The depth of the recursion    */ | 
|  | { | 
|  | int     i; | 
|  | Point2     Left[W_DEGREE+1],    /* New left and right         */ | 
|  | Right[W_DEGREE+1];    /* control polygons        */ | 
|  | int     left_count,        /* Solution count from        */ | 
|  | right_count;        /* children            */ | 
|  | double     left_t[W_DEGREE+1],    /* Solutions from kids        */ | 
|  | right_t[W_DEGREE+1]; | 
|  |  | 
|  | switch (CrossingCount(w, degree)) { | 
|  | case 0 : {    /* No solutions here    */ | 
|  | return 0; | 
|  | } | 
|  | case 1 : {    /* Unique solution    */ | 
|  | /* Stop recursion when the tree is deep enough    */ | 
|  | /* if deep enough, return 1 solution at midpoint     */ | 
|  | if (depth >= MAXDEPTH) { | 
|  | t[0] = (w[0].x + w[W_DEGREE].x) / 2.0; | 
|  | return 1; | 
|  | } | 
|  | if (ControlPolygonFlatEnough(w, degree)) { | 
|  | t[0] = ComputeXIntercept(w, degree); | 
|  | return 1; | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Otherwise, solve recursively after    */ | 
|  | /* subdividing control polygon        */ | 
|  | Bezier(w, degree, 0.5, Left, Right); | 
|  | left_count  = FindRoots(Left,  degree, left_t, depth+1); | 
|  | right_count = FindRoots(Right, degree, right_t, depth+1); | 
|  |  | 
|  |  | 
|  | /* Gather solutions together    */ | 
|  | for (i = 0; i < left_count; i++) { | 
|  | t[i] = left_t[i]; | 
|  | } | 
|  | for (i = 0; i < right_count; i++) { | 
|  | t[i+left_count] = right_t[i]; | 
|  | } | 
|  |  | 
|  | /* Send back total number of solutions    */ | 
|  | return (left_count+right_count); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | * CrossingCount : | 
|  | *    Count the number of times a Bezier control polygon | 
|  | *    crosses the 0-axis. This number is >= the number of roots. | 
|  | * | 
|  | */ | 
|  | static int CrossingCount(V, degree) | 
|  | Point2    *V;            /*  Control pts of Bezier curve    */ | 
|  | int        degree;            /*  Degreee of Bezier curve     */ | 
|  | { | 
|  | int     i; | 
|  | int     n_crossings = 0;    /*  Number of zero-crossings    */ | 
|  | int        sign, old_sign;        /*  Sign of coefficients    */ | 
|  |  | 
|  | sign = old_sign = SGN(V[0].y); | 
|  | for (i = 1; i <= degree; i++) { | 
|  | sign = SGN(V[i].y); | 
|  | if (sign != old_sign) n_crossings++; | 
|  | old_sign = sign; | 
|  | } | 
|  | return n_crossings; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /* | 
|  | *  ControlPolygonFlatEnough : | 
|  | *    Check if the control polygon of a Bezier curve is flat enough | 
|  | *    for recursive subdivision to bottom out. | 
|  | * | 
|  | */ | 
|  | static int ControlPolygonFlatEnough(V, degree) | 
|  | Point2    *V;        /* Control points    */ | 
|  | int     degree;        /* Degree of polynomial    */ | 
|  | { | 
|  | int     i;            /* Index variable        */ | 
|  | double     *distance;        /* Distances from pts to line    */ | 
|  | double     max_distance_above;    /* maximum of these        */ | 
|  | double     max_distance_below; | 
|  | double     error;            /* Precision of root        */ | 
|  | double     intercept_1, | 
|  | intercept_2, | 
|  | left_intercept, | 
|  | right_intercept; | 
|  | double     a, b, c;        /* Coefficients of implicit    */ | 
|  | /* eqn for line from V[0]-V[deg]*/ | 
|  |  | 
|  | /* Find the  perpendicular distance        */ | 
|  | /* from each interior control point to     */ | 
|  | /* line connecting V[0] and V[degree]    */ | 
|  | distance = (double *)malloc((unsigned)(degree + 1) *                     sizeof(double)); | 
|  | { | 
|  | double    abSquared; | 
|  |  | 
|  | /* Derive the implicit equation for line connecting first *' | 
|  | /*  and last control points */ | 
|  | a = V[0].y - V[degree].y; | 
|  | b = V[degree].x - V[0].x; | 
|  | c = V[0].x * V[degree].y - V[degree].x * V[0].y; | 
|  |  | 
|  | abSquared = (a * a) + (b * b); | 
|  |  | 
|  | for (i = 1; i < degree; i++) { | 
|  | /* Compute distance from each of the points to that line    */ | 
|  | distance[i] = a * V[i].x + b * V[i].y + c; | 
|  | if (distance[i] > 0.0) { | 
|  | distance[i] = (distance[i] * distance[i]) / abSquared; | 
|  | } | 
|  | if (distance[i] < 0.0) { | 
|  | distance[i] = -((distance[i] * distance[i]) /                         abSquared); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Find the largest distance    */ | 
|  | max_distance_above = 0.0; | 
|  | max_distance_below = 0.0; | 
|  | for (i = 1; i < degree; i++) { | 
|  | if (distance[i] < 0.0) { | 
|  | max_distance_below = MIN(max_distance_below, distance[i]); | 
|  | }; | 
|  | if (distance[i] > 0.0) { | 
|  | max_distance_above = MAX(max_distance_above, distance[i]); | 
|  | } | 
|  | } | 
|  | free((char *)distance); | 
|  |  | 
|  | { | 
|  | double    det, dInv; | 
|  | double    a1, b1, c1, a2, b2, c2; | 
|  |  | 
|  | /*  Implicit equation for zero line */ | 
|  | a1 = 0.0; | 
|  | b1 = 1.0; | 
|  | c1 = 0.0; | 
|  |  | 
|  | /*  Implicit equation for "above" line */ | 
|  | a2 = a; | 
|  | b2 = b; | 
|  | c2 = c + max_distance_above; | 
|  |  | 
|  | det = a1 * b2 - a2 * b1; | 
|  | dInv = 1.0/det; | 
|  |  | 
|  | intercept_1 = (b1 * c2 - b2 * c1) * dInv; | 
|  |  | 
|  | /*  Implicit equation for "below" line */ | 
|  | a2 = a; | 
|  | b2 = b; | 
|  | c2 = c + max_distance_below; | 
|  |  | 
|  | det = a1 * b2 - a2 * b1; | 
|  | dInv = 1.0/det; | 
|  |  | 
|  | intercept_2 = (b1 * c2 - b2 * c1) * dInv; | 
|  | } | 
|  |  | 
|  | /* Compute intercepts of bounding box    */ | 
|  | left_intercept = MIN(intercept_1, intercept_2); | 
|  | right_intercept = MAX(intercept_1, intercept_2); | 
|  |  | 
|  | error = 0.5 * (right_intercept-left_intercept); | 
|  | if (error < EPSILON) { | 
|  | return 1; | 
|  | } | 
|  | else { | 
|  | return 0; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /* | 
|  | *  ComputeXIntercept : | 
|  | *    Compute intersection of chord from first control point to last | 
|  | *      with 0-axis. | 
|  | * | 
|  | */ | 
|  | /* NOTE: "T" and "Y" do not have to be computed, and there are many useless | 
|  | * operations in the following (e.g. "0.0 - 0.0"). | 
|  | */ | 
|  | static double ComputeXIntercept(V, degree) | 
|  | Point2     *V;            /*  Control points    */ | 
|  | int        degree;         /*  Degree of curve    */ | 
|  | { | 
|  | double    XLK, YLK, XNM, YNM, XMK, YMK; | 
|  | double    det, detInv; | 
|  | double    S, T; | 
|  | double    X, Y; | 
|  |  | 
|  | XLK = 1.0 - 0.0; | 
|  | YLK = 0.0 - 0.0; | 
|  | XNM = V[degree].x - V[0].x; | 
|  | YNM = V[degree].y - V[0].y; | 
|  | XMK = V[0].x - 0.0; | 
|  | YMK = V[0].y - 0.0; | 
|  |  | 
|  | det = XNM*YLK - YNM*XLK; | 
|  | detInv = 1.0/det; | 
|  |  | 
|  | S = (XNM*YMK - YNM*XMK) * detInv; | 
|  | /*  T = (XLK*YMK - YLK*XMK) * detInv; */ | 
|  |  | 
|  | X = 0.0 + XLK * S; | 
|  | /*  Y = 0.0 + YLK * S; */ | 
|  |  | 
|  | return X; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | *  Bezier : | 
|  | *    Evaluate a Bezier curve at a particular parameter value | 
|  | *      Fill in control points for resulting sub-curves if "Left" and | 
|  | *    "Right" are non-null. | 
|  | * | 
|  | */ | 
|  | static Point2 Bezier(V, degree, t, Left, Right) | 
|  | int     degree;        /* Degree of bezier curve    */ | 
|  | Point2     *V;            /* Control pts            */ | 
|  | double     t;            /* Parameter value        */ | 
|  | Point2     *Left;        /* RETURN left half ctl pts    */ | 
|  | Point2     *Right;        /* RETURN right half ctl pts    */ | 
|  | { | 
|  | int     i, j;        /* Index variables    */ | 
|  | Point2     Vtemp[W_DEGREE+1][W_DEGREE+1]; | 
|  |  | 
|  |  | 
|  | /* Copy control points    */ | 
|  | for (j =0; j <= degree; j++) { | 
|  | Vtemp[0][j] = V[j]; | 
|  | } | 
|  |  | 
|  | /* Triangle computation    */ | 
|  | for (i = 1; i <= degree; i++) { | 
|  | for (j =0 ; j <= degree - i; j++) { | 
|  | Vtemp[i][j].x = | 
|  | (1.0 - t) * Vtemp[i-1][j].x + t * Vtemp[i-1][j+1].x; | 
|  | Vtemp[i][j].y = | 
|  | (1.0 - t) * Vtemp[i-1][j].y + t * Vtemp[i-1][j+1].y; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (Left != NULL) { | 
|  | for (j = 0; j <= degree; j++) { | 
|  | Left[j]  = Vtemp[j][0]; | 
|  | } | 
|  | } | 
|  | if (Right != NULL) { | 
|  | for (j = 0; j <= degree; j++) { | 
|  | Right[j] = Vtemp[degree-j][j]; | 
|  | } | 
|  | } | 
|  |  | 
|  | return (Vtemp[degree][0]); | 
|  | } | 
|  |  | 
|  | static Vector2 V2ScaleII(v, s) | 
|  | Vector2    *v; | 
|  | double    s; | 
|  | { | 
|  | Vector2 result; | 
|  |  | 
|  | result.x = v->x * s; result.y = v->y * s; | 
|  | return (result); | 
|  | } |