Blender V4.3
FitCurve.cpp
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2008-2022 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
11#include <cmath>
12#include <cstdio>
13#include <cstdlib> // for malloc and free
14
15#include "FitCurve.h"
16
17#include "BLI_sys_types.h"
18
19using namespace std;
20
21namespace Freestyle {
22
24
25/* Forward declarations */
26static double *Reparameterize(Vector2 *d, int first, int last, double *u, BezierCurve bezCurve);
27static double NewtonRaphsonRootFind(BezierCurve Q, Vector2 P, double u);
28static Vector2 BezierII(int degree, Vector2 *V, double t);
29static double B0(double u);
30static double B1(double u);
31static double B2(double u);
32static double B3(double u);
33static Vector2 ComputeLeftTangent(Vector2 *d, int end);
34static double ComputeMaxError(
35 Vector2 *d, int first, int last, BezierCurve bezCurve, double *u, int *splitPoint);
36static double *ChordLengthParameterize(Vector2 *d, int first, int last);
38 Vector2 *d, int first, int last, double *uPrime, Vector2 tHat1, Vector2 tHat2);
40static Vector2 V2ScaleIII(Vector2 v, double s);
42
43/* returns squared length of input vector */
44static double V2SquaredLength(Vector2 *a)
45{
46 return (((*a)[0] * (*a)[0]) + ((*a)[1] * (*a)[1]));
47}
48
49/* returns length of input vector */
50static double V2Length(Vector2 *a)
51{
52 return sqrt(V2SquaredLength(a));
53}
54
55static Vector2 *V2Scale(Vector2 *v, double newlen)
56{
57 double len = V2Length(v);
58 if (len != 0.0) {
59 (*v)[0] *= newlen / len;
60 (*v)[1] *= newlen / len;
61 }
62 return v;
63}
64
65/* return the dot product of vectors a and b */
66static double V2Dot(Vector2 *a, Vector2 *b)
67{
68 return (((*a)[0] * (*b)[0]) + ((*a)[1] * (*b)[1]));
69}
70
71/* return the distance between two points */
73{
74 double dx = (*a)[0] - (*b)[0];
75 double dy = (*a)[1] - (*b)[1];
76 return sqrt((dx * dx) + (dy * dy));
77}
78
79/* return vector sum c = a+b */
81{
82 (*c)[0] = (*a)[0] + (*b)[0];
83 (*c)[1] = (*a)[1] + (*b)[1];
84 return c;
85}
86
87/* normalizes the input vector and returns it */
89{
90 double len = V2Length(v);
91 if (len != 0.0) {
92 (*v)[0] /= len;
93 (*v)[1] /= len;
94 }
95 return v;
96}
97
98/* negates the input vector and returns it */
100{
101 (*v)[0] = -(*v)[0];
102 (*v)[1] = -(*v)[1];
103 return v;
104}
105
106/* GenerateBezier:
107 * Use least-squares method to find Bezier control points for region.
108 * Vector2 *d; Array of digitized points
109 * int first, last; Indices defining region
110 * double *uPrime; Parameter values for region
111 * Vector2 tHat1, tHat2; Unit tangents at endpoints
112 */
114 Vector2 *d, int first, int last, double *uPrime, Vector2 tHat1, Vector2 tHat2)
115{
116 int i;
117 Vector2 A[2]; /* rhs for eqn */
118 int nPts; /* Number of pts in sub-curve */
119 double C[2][2]; /* Matrix C */
120 double X[2]; /* Matrix X */
121 double det_C0_C1; /* Determinants of matrices */
122 double det_C0_X;
123 double det_X_C1;
124 double alpha_l; /* Alpha values, left and right */
125 double alpha_r;
126 Vector2 tmp; /* Utility variable */
127 BezierCurve bezCurve; /* RETURN bezier curve control points. */
128
129 bezCurve = (Vector2 *)malloc(4 * sizeof(Vector2));
130 nPts = last - first + 1;
131
132 /* Create the C and X matrices */
133 C[0][0] = 0.0;
134 C[0][1] = 0.0;
135 C[1][0] = 0.0;
136 C[1][1] = 0.0;
137 X[0] = 0.0;
138 X[1] = 0.0;
139 for (i = 0; i < nPts; i++) {
140 /* Compute the A's */
141 A[0] = tHat1;
142 A[1] = tHat2;
143 V2Scale(&A[0], B1(uPrime[i]));
144 V2Scale(&A[1], B2(uPrime[i]));
145
146 C[0][0] += V2Dot(&A[0], &A[0]);
147 C[0][1] += V2Dot(&A[0], &A[1]);
148 // C[1][0] += V2Dot(&A[0], &A[1]);
149 C[1][0] = C[0][1];
150 C[1][1] += V2Dot(&A[1], &A[1]);
151
152 tmp = V2SubII(d[first + i],
153 V2AddII(V2ScaleIII(d[first], B0(uPrime[i])),
154 V2AddII(V2ScaleIII(d[first], B1(uPrime[i])),
155 V2AddII(V2ScaleIII(d[last], B2(uPrime[i])),
156 V2ScaleIII(d[last], B3(uPrime[i]))))));
157
158 X[0] += V2Dot(&A[0], &tmp);
159 X[1] += V2Dot(&A[1], &tmp);
160 }
161
162 /* Compute the determinants of C and X */
163 det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
164 det_C0_X = C[0][0] * X[1] - C[0][1] * X[0];
165 det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
166
167 /* Finally, derive alpha values */
168 if (det_C0_C1 == 0.0) {
169 det_C0_C1 = (C[0][0] * C[1][1]) * 10.0e-12;
170 }
171 alpha_l = det_X_C1 / det_C0_C1;
172 alpha_r = det_C0_X / det_C0_C1;
173
174 /* If alpha negative, use the Wu/Barsky heuristic (see text) (if alpha is 0, you get coincident
175 * control points that lead to divide by zero in any subsequent NewtonRaphsonRootFind() call).
176 */
177 if (alpha_l < 1.0e-6 || alpha_r < 1.0e-6) {
178 double dist = V2DistanceBetween2Points(&d[last], &d[first]) / 3.0;
179
180 bezCurve[0] = d[first];
181 bezCurve[3] = d[last];
182 V2Add(&(bezCurve[0]), V2Scale(&(tHat1), dist), &(bezCurve[1]));
183 V2Add(&(bezCurve[3]), V2Scale(&(tHat2), dist), &(bezCurve[2]));
184 return bezCurve;
185 }
186
187 /* First and last control points of the Bezier curve are positioned exactly at the first and last
188 * data points Control points 1 and 2 are positioned an alpha distance out on the tangent
189 * vectors, left and right, respectively
190 */
191 bezCurve[0] = d[first];
192 bezCurve[3] = d[last];
193 V2Add(&bezCurve[0], V2Scale(&tHat1, alpha_l), &bezCurve[1]);
194 V2Add(&bezCurve[3], V2Scale(&tHat2, alpha_r), &bezCurve[2]);
195 return bezCurve;
196}
197
198/*
199 * Reparameterize:
200 * Given set of points and their parameterization, try to find a better parameterization.
201 * Vector2 *d; Array of digitized points
202 * int first, last; Indices defining region
203 * double *u; Current parameter values
204 * BezierCurve bezCurve; Current fitted curve
205 */
206static double *Reparameterize(Vector2 *d, int first, int last, double *u, BezierCurve bezCurve)
207{
208 int nPts = last - first + 1;
209 int i;
210 double *uPrime; /* New parameter values */
211
212 uPrime = (double *)malloc(nPts * sizeof(double));
213 for (i = first; i <= last; i++) {
214 uPrime[i - first] = NewtonRaphsonRootFind(bezCurve, d[i], u[i - first]);
215 }
216 return uPrime;
217}
218
219/*
220 * NewtonRaphsonRootFind:
221 * Use Newton-Raphson iteration to find better root.
222 * BezierCurve Q; Current fitted curve
223 * Vector2 P; Digitized point
224 * double u; Parameter value for "P"
225 */
226static double NewtonRaphsonRootFind(BezierCurve Q, Vector2 P, double u)
227{
228 double numerator, denominator;
229 Vector2 Q1[3], Q2[2]; /* Q' and Q'' */
230 Vector2 Q_u, Q1_u, Q2_u; /* u evaluated at Q, Q', & Q'' */
231 double uPrime; /* Improved u */
232 int i;
233
234 /* Compute Q(u) */
235 Q_u = BezierII(3, Q, u);
236
237 /* Generate control vertices for Q' */
238 for (i = 0; i <= 2; i++) {
239 Q1[i][0] = (Q[i + 1][0] - Q[i][0]) * 3.0;
240 Q1[i][1] = (Q[i + 1][1] - Q[i][1]) * 3.0;
241 }
242
243 /* Generate control vertices for Q'' */
244 for (i = 0; i <= 1; i++) {
245 Q2[i][0] = (Q1[i + 1][0] - Q1[i][0]) * 2.0;
246 Q2[i][1] = (Q1[i + 1][1] - Q1[i][1]) * 2.0;
247 }
248
249 /* Compute Q'(u) and Q''(u) */
250 Q1_u = BezierII(2, Q1, u);
251 Q2_u = BezierII(1, Q2, u);
252
253 /* Compute f(u)/f'(u) */
254 numerator = (Q_u[0] - P[0]) * (Q1_u[0]) + (Q_u[1] - P[1]) * (Q1_u[1]);
255 denominator = (Q1_u[0]) * (Q1_u[0]) + (Q1_u[1]) * (Q1_u[1]) + (Q_u[0] - P[0]) * (Q2_u[0]) +
256 (Q_u[1] - P[1]) * (Q2_u[1]);
257
258 /* u = u - f(u)/f'(u) */
259 if (denominator == 0) { // FIXME
260 return u;
261 }
262 uPrime = u - (numerator / denominator);
263 return uPrime;
264}
265
266/*
267 * Bezier:
268 * Evaluate a Bezier curve at a particular parameter value
269 * int degree; The degree of the bezier curve
270 * Vector2 *V; Array of control points
271 * double t; Parametric value to find point for
272 */
273static Vector2 BezierII(int degree, Vector2 *V, double t)
274{
275 int i, j;
276 Vector2 Q; /* Point on curve at parameter t */
277 Vector2 *Vtemp; /* Local copy of control points */
278
279 /* Copy array */
280 Vtemp = (Vector2 *)malloc(uint((degree + 1) * sizeof(Vector2)));
281 for (i = 0; i <= degree; i++) {
282 Vtemp[i] = V[i];
283 }
284
285 /* Triangle computation */
286 for (i = 1; i <= degree; i++) {
287 for (j = 0; j <= degree - i; j++) {
288 Vtemp[j][0] = (1.0 - t) * Vtemp[j][0] + t * Vtemp[j + 1][0];
289 Vtemp[j][1] = (1.0 - t) * Vtemp[j][1] + t * Vtemp[j + 1][1];
290 }
291 }
292
293 Q = Vtemp[0];
294 free((void *)Vtemp);
295 return Q;
296}
297
298/*
299 * B0, B1, B2, B3:
300 * Bezier multipliers
301 */
302static double B0(double u)
303{
304 double tmp = 1.0 - u;
305 return (tmp * tmp * tmp);
306}
307
308static double B1(double u)
309{
310 double tmp = 1.0 - u;
311 return (3 * u * (tmp * tmp));
312}
313
314static double B2(double u)
315{
316 double tmp = 1.0 - u;
317 return (3 * u * u * tmp);
318}
319
320static double B3(double u)
321{
322 return (u * u * u);
323}
324
325/*
326 * ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent:
327 * Approximate unit tangents at endpoints and "center" of digitized curve
328 */
329/* Vector2 *d; Digitized points
330 * int end; Index to "left" end of region
331 */
333{
334 Vector2 tHat1;
335 tHat1 = V2SubII(d[end + 1], d[end]);
336 tHat1 = *V2Normalize(&tHat1);
337 return tHat1;
338}
339
340/* Vector2 *d; Digitized points
341 * int end; Index to "right" end of region
342 */
344{
345 Vector2 tHat2;
346 tHat2 = V2SubII(d[end - 1], d[end]);
347 tHat2 = *V2Normalize(&tHat2);
348 return tHat2;
349}
350
351/* Vector2 *d; Digitized points
352 * int end; Index to point inside region
353 */
355{
356 Vector2 V1, V2, tHatCenter;
357
358 V1 = V2SubII(d[center - 1], d[center]);
359 V2 = V2SubII(d[center], d[center + 1]);
360 tHatCenter[0] = (V1[0] + V2[0]) / 2.0;
361 tHatCenter[1] = (V1[1] + V2[1]) / 2.0;
362 tHatCenter = *V2Normalize(&tHatCenter);
363
364 /* avoid numerical singularity in the special case when V1 == -V2 */
365 if (V2Length(&tHatCenter) < M_EPSILON) {
366 tHatCenter = *V2Normalize(&V1);
367 }
368
369 return tHatCenter;
370}
371
372/*
373 * ChordLengthParameterize:
374 * Assign parameter values to digitized points using relative distances between points.
375 * Vector2 *d; Array of digitized points
376 * int first, last; Indices defining region
377 */
378static double *ChordLengthParameterize(Vector2 *d, int first, int last)
379{
380 int i;
381 double *u; /* Parameterization */
382
383 u = (double *)malloc(uint(last - first + 1) * sizeof(double));
384
385 u[0] = 0.0;
386 for (i = first + 1; i <= last; i++) {
387 u[i - first] = u[i - first - 1] + V2DistanceBetween2Points(&d[i], &d[i - 1]);
388 }
389
390 for (i = first + 1; i <= last; i++) {
391 u[i - first] = u[i - first] / u[last - first];
392 }
393
394 return u;
395}
396
397/*
398 * ComputeMaxError :
399 * Find the maximum squared distance of digitized points to fitted curve.
400 * Vector2 *d; Array of digitized points
401 * int first, last; Indices defining region
402 * BezierCurve bezCurve; Fitted Bezier curve
403 * double *u; Parameterization of points
404 * int *splitPoint; Point of maximum error
405 */
406static double ComputeMaxError(
407 Vector2 *d, int first, int last, BezierCurve bezCurve, double *u, int *splitPoint)
408{
409 int i;
410 double maxDist; /* Maximum error */
411 double dist; /* Current error */
412 Vector2 P; /* Point on curve */
413 Vector2 v; /* Vector from point to curve */
414
415 *splitPoint = (last - first + 1) / 2;
416 maxDist = 0.0;
417 for (i = first + 1; i < last; i++) {
418 P = BezierII(3, bezCurve, u[i - first]);
419 v = V2SubII(P, d[i]);
420 dist = V2SquaredLength(&v);
421 if (dist >= maxDist) {
422 maxDist = dist;
423 *splitPoint = i;
424 }
425 }
426 return maxDist;
427}
428
430{
431 Vector2 c;
432 c[0] = a[0] + b[0];
433 c[1] = a[1] + b[1];
434 return c;
435}
436
437static Vector2 V2ScaleIII(Vector2 v, double s)
438{
440 result[0] = v[0] * s;
441 result[1] = v[1] * s;
442 return result;
443}
444
446{
447 Vector2 c;
448 c[0] = a[0] - b[0];
449 c[1] = a[1] - b[1];
450 return c;
451}
452
453//------------------------- WRAPPER -----------------------------//
454
456{
457 _vertices.clear();
458}
459
461{
462 for (int i = 0; i <= n; ++i) {
463 _vertices.push_back(curve[i]);
464 }
465}
466
467void FitCurveWrapper::FitCurve(vector<Vec2d> &data, vector<Vec2d> &oCurve, double error)
468{
469 int size = data.size();
470 Vector2 *d = new Vector2[size];
471 for (int i = 0; i < size; ++i) {
472 d[i][0] = data[i][0];
473 d[i][1] = data[i][1];
474 }
475
476 FitCurve(d, size, error);
477
478 delete[] d;
479
480 // copy results
481 for (vector<Vector2>::iterator v = _vertices.begin(), vend = _vertices.end(); v != vend; ++v) {
482 oCurve.emplace_back(v->x(), v->y());
483 }
484}
485
486void FitCurveWrapper::FitCurve(Vector2 *d, int nPts, double error)
487{
488 Vector2 tHat1, tHat2; /* Unit tangent vectors at endpoints */
489
490 tHat1 = ComputeLeftTangent(d, 0);
491 tHat2 = ComputeRightTangent(d, nPts - 1);
492 FitCubic(d, 0, nPts - 1, tHat1, tHat2, error);
493}
494
496 Vector2 *d, int first, int last, Vector2 tHat1, Vector2 tHat2, double error)
497{
498 BezierCurve bezCurve; /* Control points of fitted Bezier curve */
499 double *u; /* Parameter values for point */
500 double *uPrime; /* Improved parameter values */
501 double maxError; /* Maximum fitting error */
502 int splitPoint; /* Point to split point set at */
503 int nPts; /* Number of points in subset */
504 double iterationError; /* Error below which you try iterating */
505 int maxIterations = 4; /* Max times to try iterating */
506 Vector2 tHatCenter; /* Unit tangent vector at splitPoint */
507 int i;
508
509 iterationError = error * error;
510 nPts = last - first + 1;
511
512 /* Use heuristic if region only has two points in it */
513 if (nPts == 2) {
514 double dist = V2DistanceBetween2Points(&d[last], &d[first]) / 3.0;
515
516 bezCurve = (Vector2 *)malloc(4 * sizeof(Vector2));
517 bezCurve[0] = d[first];
518 bezCurve[3] = d[last];
519 V2Add(&bezCurve[0], V2Scale(&tHat1, dist), &bezCurve[1]);
520 V2Add(&bezCurve[3], V2Scale(&tHat2, dist), &bezCurve[2]);
521 DrawBezierCurve(3, bezCurve);
522 free((void *)bezCurve);
523 return;
524 }
525
526 /* Parameterize points, and attempt to fit curve */
527 u = ChordLengthParameterize(d, first, last);
528 bezCurve = GenerateBezier(d, first, last, u, tHat1, tHat2);
529
530 /* Find max deviation of points to fitted curve */
531 maxError = ComputeMaxError(d, first, last, bezCurve, u, &splitPoint);
532 if (maxError < error) {
533 DrawBezierCurve(3, bezCurve);
534 free((void *)u);
535 free((void *)bezCurve);
536 return;
537 }
538
539 /* If error not too large, try some reparameterization and iteration */
540 if (maxError < iterationError) {
541 for (i = 0; i < maxIterations; i++) {
542 uPrime = Reparameterize(d, first, last, u, bezCurve);
543
544 free((void *)u);
545 free((void *)bezCurve);
546 u = uPrime;
547
548 bezCurve = GenerateBezier(d, first, last, u, tHat1, tHat2);
549 maxError = ComputeMaxError(d, first, last, bezCurve, u, &splitPoint);
550
551 if (maxError < error) {
552 DrawBezierCurve(3, bezCurve);
553 free((void *)u);
554 free((void *)bezCurve);
555 return;
556 }
557 }
558 }
559
560 /* Fitting failed -- split at max error point and fit recursively */
561 free((void *)u);
562 free((void *)bezCurve);
563 tHatCenter = ComputeCenterTangent(d, splitPoint);
564 FitCubic(d, first, splitPoint, tHat1, tHatCenter, error);
565 V2Negate(&tHatCenter);
566 FitCubic(d, splitPoint, last, tHatCenter, tHat2, error);
567}
568
569} /* namespace Freestyle */
sqrt(x)+1/max(0
void BLI_kdtree_nd_ free(KDTree *tree)
unsigned int uint
typedef double(DMatrix)[4][4]
An Algorithm for Automatically Fitting Digitized Curves by Philip J. Schneider,.
#define X
ATTR_WARN_UNUSED_RESULT const BMVert * v
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
void DrawBezierCurve(int n, Vector2 *curve)
Definition FitCurve.cpp:460
void FitCubic(Vector2 *d, int first, int last, Vector2 tHat1, Vector2 tHat2, double error)
Definition FitCurve.cpp:495
void FitCurve(std::vector< Vec2d > &data, std::vector< Vec2d > &oCurve, double error)
Definition FitCurve.cpp:467
local_group_size(16, 16) .push_constant(Type b
int len
static void error(const char *str)
inherits from class Rep
Definition AppCanvas.cpp:20
static uint c
Definition RandGen.cpp:87
static Vector2 BezierII(int degree, Vector2 *V, double t)
Definition FitCurve.cpp:273
static double NewtonRaphsonRootFind(BezierCurve Q, Vector2 P, double u)
Definition FitCurve.cpp:226
static Vector2 * V2Normalize(Vector2 *v)
Definition FitCurve.cpp:88
static double * Reparameterize(Vector2 *d, int first, int last, double *u, BezierCurve bezCurve)
Definition FitCurve.cpp:206
static Vector2 * V2Negate(Vector2 *v)
Definition FitCurve.cpp:99
static Vector2 V2AddII(Vector2 a, Vector2 b)
Definition FitCurve.cpp:429
static double B1(double u)
Definition FitCurve.cpp:308
static const real M_EPSILON
Definition Precision.h:17
static Vector2 ComputeRightTangent(Vector2 *d, int end)
Definition FitCurve.cpp:343
static Vector2 * V2Add(Vector2 *a, Vector2 *b, Vector2 *c)
Definition FitCurve.cpp:80
static Vector2 V2ScaleIII(Vector2 v, double s)
Definition FitCurve.cpp:437
static double V2Dot(Vector2 *a, Vector2 *b)
Definition FitCurve.cpp:66
static double B0(double u)
Definition FitCurve.cpp:302
static BezierCurve GenerateBezier(Vector2 *d, int first, int last, double *uPrime, Vector2 tHat1, Vector2 tHat2)
Definition FitCurve.cpp:113
static Vector2 ComputeLeftTangent(Vector2 *d, int end)
Definition FitCurve.cpp:332
static Vector2 * V2Scale(Vector2 *v, double newlen)
Definition FitCurve.cpp:55
static double V2SquaredLength(Vector2 *a)
Definition FitCurve.cpp:44
static double * ChordLengthParameterize(Vector2 *d, int first, int last)
Definition FitCurve.cpp:378
static double B3(double u)
Definition FitCurve.cpp:320
static double B2(double u)
Definition FitCurve.cpp:314
static double V2DistanceBetween2Points(Vector2 *a, Vector2 *b)
Definition FitCurve.cpp:72
static double ComputeMaxError(Vector2 *d, int first, int last, BezierCurve bezCurve, double *u, int *splitPoint)
Definition FitCurve.cpp:406
static double V2Length(Vector2 *a)
Definition FitCurve.cpp:50
static Vector2 V2SubII(Vector2 a, Vector2 b)
Definition FitCurve.cpp:445
static Vector2 ComputeCenterTangent(Vector2 *d, int center)
Definition FitCurve.cpp:354
CCL_NAMESPACE_BEGIN struct Window V