Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
nearestpoint.cpp
Go to the documentation of this file.
1/*
2** vim: ts=4 sw=4 et tw=0 wm=0
3**
4** RCS Information:
5** $Author: mjw $
6** $Revision: 1 $
7** $Date: 2006-03-28 15:59:38 +1100 (Tue, 28 Mar 2006) $
8**
9** Solving the Nearest Point-on-Curve Problem and
10** A Bezier Curve-Based Root-Finder
11** by Philip J. Schneider
12** from "Graphics Gems", Academic Press, 1990
13** modified by mwybrow, njh
14*/
15
16/* point_on_curve.c */
17
18static double SquaredLength(const Geom::Point a)
19{
20 return dot(a, a);
21}
22
23
24/*
25 * Forward declarations
26 */
27static int FindRoots(Geom::Point *w, int degree, double *t, int depth);
29static double ComputeXIntercept( Geom::Point *V, int degree);
31static int CrossingCount(Geom::Point *V, int degree);
32static Geom::Point Bez(Geom::Point *V, int degree, double t, Geom::Point *Left,
33 Geom::Point *Right);
34
35int MAXDEPTH = 64; /* Maximum depth for recursion */
36
37#define EPSILON (ldexp(1.0,-MAXDEPTH-1)) /*Flatness control value */
38#define DEGREE 3 /* Cubic Bezier curve */
39#define W_DEGREE 5 /* Degree of eqn to find roots of */
40
41
42/*
43 * NearestPointOnCurve :
44 * Compute the parameter value of the point on a Bezier
45 * curve segment closest to some arbtitrary, user-input point.
46 * Return the point on the curve at that parameter value.
47 *
48 Geom::Point P; The user-supplied point
49 Geom::Point *V; Control points of cubic Bezier
50*/
52{
53 double t_candidate[W_DEGREE]; /* Possible roots */
54
55 /* Convert problem to 5th-degree Bezier form */
57
58 /* Find all possible roots of 5th-degree equation */
59 int n_solutions = FindRoots(w, W_DEGREE, t_candidate, 0);
60 std::free((char *)w);
61
62 /* Check distance to end of the curve, where t = 1 */
63 double dist = SquaredLength(P - V[DEGREE]);
64 double t = 1.0;
65
66 /* Find distances for candidate points */
67 for (int i = 0; i < n_solutions; i++) {
68 Geom::Point p = Bez(V, DEGREE, t_candidate[i], NULL, NULL);
69 double new_dist = SquaredLength(P - p);
70 if (new_dist < dist) {
71 dist = new_dist;
72 t = t_candidate[i];
73 }
74 }
75
76 /* Return the parameter value t */
77 return t;
78}
79
80
81/*
82 * ConvertToBezierForm :
83 * Given a point and a Bezier curve, generate a 5th-degree
84 * Bezier-format equation whose solution finds the point on the
85 * curve nearest the user-defined point.
86 */
88 Geom::Point P, /* The point to find t for */
89 Geom::Point *V) /* The control points */
90{
91 Geom::Point c[DEGREE+1]; /* V(i)'s - P */
92 Geom::Point d[DEGREE]; /* V(i+1) - V(i) */
93 Geom::Point *w; /* Ctl pts of 5th-degree curve */
94 double cdTable[3][4]; /* Dot product of c, d */
95 static double z[3][4] = { /* Precomputed "z" for cubics */
96 {1.0, 0.6, 0.3, 0.1},
97 {0.4, 0.6, 0.6, 0.4},
98 {0.1, 0.3, 0.6, 1.0},
99 };
100
101
102 /*Determine the c's -- these are vectors created by subtracting*/
103 /* point P from each of the control points */
104 for (int i = 0; i <= DEGREE; i++) {
105 c[i] = V[i] - P;
106 }
107 /* Determine the d's -- these are vectors created by subtracting*/
108 /* each control point from the next */
109 for (int i = 0; i <= DEGREE - 1; i++) {
110 d[i] = 3.0*(V[i+1] - V[i]);
111 }
112
113 /* Create the c,d table -- this is a table of dot products of the */
114 /* c's and d's */
115 for (int row = 0; row <= DEGREE - 1; row++) {
116 for (int column = 0; column <= DEGREE; column++) {
117 cdTable[row][column] = dot(d[row], c[column]);
118 }
119 }
120
121 /* Now, apply the z's to the dot products, on the skew diagonal*/
122 /* Also, set up the x-values, making these "points" */
123 w = (Geom::Point *)malloc((unsigned)(W_DEGREE+1) * sizeof(Geom::Point));
124 for (int i = 0; i <= W_DEGREE; i++) {
125 w[i][Geom::Y] = 0.0;
126 w[i][Geom::X] = (double)(i) / W_DEGREE;
127 }
128
129 const int n = DEGREE;
130 const int m = DEGREE-1;
131 for (int k = 0; k <= n + m; k++) {
132 const int lb = std::max(0, k - m);
133 const int ub = std::min(k, n);
134 for (int i = lb; i <= ub; i++) {
135 int j = k - i;
136 w[i+j][Geom::Y] += cdTable[j][i] * z[j][i];
137 }
138 }
139
140 return w;
141}
142
143
144/*
145 * FindRoots :
146 * Given a 5th-degree equation in Bernstein-Bezier form, find
147 * all of the roots in the interval [0, 1]. Return the number
148 * of roots found.
149 */
150static int FindRoots(
151 Geom::Point *w, /* The control points */
152 int degree, /* The degree of the polynomial */
153 double *t, /* RETURN candidate t-values */
154 int depth) /* The depth of the recursion */
155{
156 int i;
157 Geom::Point Left[W_DEGREE+1], /* New left and right */
158 Right[W_DEGREE+1]; /* control polygons */
159 int left_count, /* Solution count from */
160 right_count; /* children */
161 double left_t[W_DEGREE+1], /* Solutions from kids */
162 right_t[W_DEGREE+1];
163
164 switch (CrossingCount(w, degree)) {
165 case 0 : { /* No solutions here */
166 return 0;
167 break;
168 }
169 case 1 : { /* Unique solution */
170 /* Stop recursion when the tree is deep enough */
171 /* if deep enough, return 1 solution at midpoint */
172 if (depth >= MAXDEPTH) {
173 t[0] = (w[0][Geom::X] + w[W_DEGREE][Geom::X]) / 2.0;
174 return 1;
175 }
177 t[0] = ComputeXIntercept(w, degree);
178 return 1;
179 }
180 break;
181 }
182 }
183
184 /* Otherwise, solve recursively after */
185 /* subdividing control polygon */
186 Bez(w, degree, 0.5, Left, Right);
187 left_count = FindRoots(Left, degree, left_t, depth+1);
188 right_count = FindRoots(Right, degree, right_t, depth+1);
189
190
191 /* Gather solutions together */
192 for (i = 0; i < left_count; i++) {
193 t[i] = left_t[i];
194 }
195 for (i = 0; i < right_count; i++) {
196 t[i+left_count] = right_t[i];
197 }
198
199 /* Send back total number of solutions */
200 return (left_count+right_count);
201}
202
203
204/*
205 * CrossingCount :
206 * Count the number of times a Bezier control polygon
207 * crosses the 0-axis. This number is >= the number of roots.
208 *
209 */
210static int CrossingCount(
211 Geom::Point *V, /* Control pts of Bezier curve */
212 int degree) /* Degree of Bezier curve */
213{
214 int n_crossings = 0; /* Number of zero-crossings */
215 int old_sign; /* Sign of coefficients */
216
217 old_sign = Geom::sgn(V[0][Geom::Y]);
218 for (int i = 1; i <= degree; i++) {
219 int sign = Geom::sgn(V[i][Geom::Y]);
220 if (sign != old_sign)
221 n_crossings++;
222 old_sign = sign;
223 }
224 return n_crossings;
225}
226
227
228
229/*
230 * ControlPolygonFlatEnough :
231 * Check if the control polygon of a Bezier curve is flat enough
232 * for recursive subdivision to bottom out.
233 *
234 */
236 Geom::Point *V, /* Control points */
237 int degree) /* Degree of polynomial */
238{
239 int i; /* Index variable */
240 double *distance; /* Distances from pts to line */
241 double max_distance_above; /* maximum of these */
242 double max_distance_below;
243 double error; /* Precision of root */
244 //Geom::Point t; /* Vector from V[0] to V[degree]*/
245 double intercept_1,
246 intercept_2,
247 left_intercept,
248 right_intercept;
249 double a, b, c; /* Coefficients of implicit */
250 /* eqn for line from V[0]-V[deg]*/
251
252 /* Find the perpendicular distance */
253 /* from each interior control point to */
254 /* line connecting V[0] and V[degree] */
255 distance = (double *)malloc((unsigned)(degree + 1) * sizeof(double));
256 {
257 double abSquared;
258
259 /* Derive the implicit equation for line connecting first */
260 /* and last control points */
261 a = V[0][Geom::Y] - V[degree][Geom::Y];
262 b = V[degree][Geom::X] - V[0][Geom::X];
263 c = V[0][Geom::X] * V[degree][Geom::Y] - V[degree][Geom::X] * V[0][Geom::Y];
264
265 abSquared = (a * a) + (b * b);
266
267 for (i = 1; i < degree; i++) {
268 /* Compute distance from each of the points to that line */
269 distance[i] = a * V[i][Geom::X] + b * V[i][Geom::Y] + c;
270 if (distance[i] > 0.0) {
271 distance[i] = (distance[i] * distance[i]) / abSquared;
272 }
273 if (distance[i] < 0.0) {
274 distance[i] = -((distance[i] * distance[i]) / abSquared);
275 }
276 }
277 }
278
279
280 /* Find the largest distance */
281 max_distance_above = 0.0;
282 max_distance_below = 0.0;
283 for (i = 1; i < degree; i++) {
284 if (distance[i] < 0.0) {
285 max_distance_below = std::min(max_distance_below, distance[i]);
286 };
287 if (distance[i] > 0.0) {
288 max_distance_above = std::max(max_distance_above, distance[i]);
289 }
290 }
291 free((char *)distance);
292
293 {
294 double det;
295 double a1, b1, c1, a2, b2, c2;
296
297 /* Implicit equation for zero line */
298 a1 = 0.0;
299 b1 = 1.0;
300 c1 = 0.0;
301
302 /* Implicit equation for "above" line */
303 a2 = a;
304 b2 = b;
305 c2 = c + max_distance_above;
306
307 det = a1 * b2 - a2 * b1;
308
309 intercept_1 = (b1 * c2 - b2 * c1) / det;
310
311 /* Implicit equation for "below" line */
312 a2 = a;
313 b2 = b;
314 c2 = c + max_distance_below;
315
316 det = a1 * b2 - a2 * b1;
317
318 intercept_2 = (b1 * c2 - b2 * c1) / det;
319 }
320
321 /* Compute intercepts of bounding box */
322 left_intercept = std::min(intercept_1, intercept_2);
323 right_intercept = std::max(intercept_1, intercept_2);
324
325 error = 0.5 * (right_intercept-left_intercept);
326 if (error < EPSILON) {
327 return 1;
328 }
329 else {
330 return 0;
331 }
332}
333
334
335
336/*
337 * ComputeXIntercept :
338 * Compute intersection of chord from first control point to last
339 * with 0-axis.
340 *
341 */
342static double ComputeXIntercept(
343 Geom::Point *V, /* Control points */
344 int degree) /* Degree of curve */
345{
346 const Geom::Point A = V[degree] - V[0];
347
348 return (A[Geom::X]*V[0][Geom::Y] - A[Geom::Y]*V[0][Geom::X]) / -A[Geom::Y];
349}
350
351
352/*
353 * Bez :
354 * Evaluate a Bezier curve at a particular parameter value
355 * Fill in control points for resulting sub-curves if "Left" and
356 * "Right" are non-null.
357 *
358 */
360 Geom::Point *V, /* Control pts */
361 int degree, /* Degree of bezier curve */
362 double t, /* Parameter value */
363 Geom::Point *Left, /* RETURN left half ctl pts */
364 Geom::Point *Right) /* RETURN right half ctl pts */
365{
366 Geom::Point Vtemp[W_DEGREE+1][W_DEGREE+1];
367
368
369 /* Copy control points */
370 for (int j =0; j <= degree; j++) {
371 Vtemp[0][j] = V[j];
372 }
373
374 /* Triangle computation */
375 for (int i = 1; i <= degree; i++) {
376 for (int j =0 ; j <= degree - i; j++) {
377 Vtemp[i][j] =
378 (1.0 - t) * Vtemp[i-1][j] + t * Vtemp[i-1][j+1];
379 }
380 }
381
382 if (Left != NULL) {
383 for (int j = 0; j <= degree; j++) {
384 Left[j] = Vtemp[j][0];
385 }
386 }
387 if (Right != NULL) {
388 for (int j = 0; j <= degree; j++) {
389 Right[j] = Vtemp[degree-j][j];
390 }
391 }
392
393 return (Vtemp[degree][0]);
394}
395
396/*
397 Local Variables:
398 mode:c++
399 c-file-style:"stroustrup"
400 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
401 indent-tabs-mode:nil
402 fill-column:99
403 End:
404*/
405// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
double distance(Shape const *s, Geom::Point const &p)
Definition Shape.cpp:2136
Two-dimensional point that doubles as a vector.
Definition point.h:66
const double w
Definition conic-4.cpp:19
static T det(T a, T b, T c, T d)
Definition conic-5.cpp:95
double c[8][4]
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
int sgn(const T &x)
Sign function - indicates the sign of a numeric type.
Definition math-utils.h:51
int MAXDEPTH
static double SquaredLength(const Geom::Point a)
double NearestPointOnCurve(Geom::Point P, Geom::Point *V)
static int ControlPolygonFlatEnough(Geom::Point *V, int degree)
static int FindRoots(Geom::Point *w, int degree, double *t, int depth)
static int CrossingCount(Geom::Point *V, int degree)
static double ComputeXIntercept(Geom::Point *V, int degree)
static Geom::Point Bez(Geom::Point *V, int degree, double t, Geom::Point *Left, Geom::Point *Right)
static Geom::Point * ConvertToBezierForm(Geom::Point P, Geom::Point *V)
static double sign(double const x)
Returns -1 or 1 according to the sign of x.
size_t degree
void dot(Cairo::RefPtr< Cairo::Context > &cr, double x, double y)