Skip to content

Commit d8aecb9

Browse files
committed
Decompose splines with t flexibly updated
This commit focuses on decomposing a spline into fewer segments by finding an optimal value of t that meets our target tolerance for flatness while ensuring it is at least as large as the maximum distance from the line segment connecting the starting and ending points of the original curve. The approach employs the bisection method to determine the value of t. The idea behind this method is based on the properties of curves. By choosing the point of maximum curvature as the junction between two line segments during curve fitting, we ensure that the curvature of all points on both segments is less than the maximum curvature of the original curve. This strategy guarantees that the two line segments fit the original curve as well as possible. Additionally, the properties of these two segments are such that the slope of the tangent line at all points in one segment is relatively positive compared to the slope of the line connecting the starting and ending points of the original curve, while the slope in the other line segment is relatively negative. Consequently, the point where the tangent line's slope is zero relative to the connecting line results in the maximum distance. This distance is recorded to ensure we maximize it as much as possible. Although this approach requires more computation to decompose the spline into fewer segments, the extra effort is justified for improved fit. I have modified the implementation of font-edit to use fixed-point arithmetic and used it as the evaluation testbed to do experiments as follows: original - Average number of _de_casteljau calls per point: 1.99 original - Average points per character: 18.89 flexibly update - Average number of _de_casteljau calls per point: 4.53 flexibly update - Average points per character: 16.30 See https://keithp.com/blogs/more-iterative-splines/ for details. Close #2
1 parent b75d5ca commit d8aecb9

File tree

1 file changed

+57
-26
lines changed

1 file changed

+57
-26
lines changed

src/spline.c

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,38 +11,38 @@ typedef struct _twin_spline {
1111
} twin_spline_t;
1212

1313
/*
14-
* Linearly interpolate between points 'a' and 'b' with a shift factor.
15-
* The shift factor determines the position between 'a' and 'b'.
14+
* Linearly interpolate between points 'a' and 'b' with a 't' factor.
15+
* The 't' factor determines the position between 'a' and 'b'.
1616
* The result is stored in 'result'.
1717
*/
1818
static void _lerp(const twin_spoint_t *a,
1919
const twin_spoint_t *b,
20-
int shift,
20+
twin_dfixed_t t,
2121
twin_spoint_t *result)
2222
{
23-
result->x = a->x + ((b->x - a->x) >> shift);
24-
result->y = a->y + ((b->y - a->y) >> shift);
23+
result->x = a->x + twin_dfixed_to_sfixed(twin_dfixed_mul(
24+
twin_sfixed_to_dfixed(b->x - a->x), t));
25+
result->y = a->y + twin_dfixed_to_sfixed(twin_dfixed_mul(
26+
twin_sfixed_to_dfixed(b->y - a->y), t));
2527
}
2628

2729
/*
28-
* Perform the de Casteljau algorithm to split a spline at a given shift
30+
* Perform the de Casteljau algorithm to split a spline at a given 't'
2931
* factor. The spline is split into two new splines 's1' and 's2'.
3032
*/
3133
static void _de_casteljau(twin_spline_t *spline,
32-
int shift,
34+
twin_dfixed_t t,
3335
twin_spline_t *s1,
3436
twin_spline_t *s2)
3537
{
36-
twin_spoint_t ab, bc, cd;
37-
twin_spoint_t abbc, bccd;
38-
twin_spoint_t final;
38+
twin_spoint_t ab, bc, cd, abbc, bccd, final;
3939

40-
_lerp(&spline->a, &spline->b, shift, &ab);
41-
_lerp(&spline->b, &spline->c, shift, &bc);
42-
_lerp(&spline->c, &spline->d, shift, &cd);
43-
_lerp(&ab, &bc, shift, &abbc);
44-
_lerp(&bc, &cd, shift, &bccd);
45-
_lerp(&abbc, &bccd, shift, &final);
40+
_lerp(&spline->a, &spline->b, t, &ab);
41+
_lerp(&spline->b, &spline->c, t, &bc);
42+
_lerp(&spline->c, &spline->d, t, &cd);
43+
_lerp(&ab, &bc, t, &abbc);
44+
_lerp(&bc, &cd, t, &bccd);
45+
_lerp(&abbc, &bccd, t, &final);
4646

4747
s1->a = spline->a;
4848
s1->b = ab;
@@ -56,10 +56,10 @@ static void _de_casteljau(twin_spline_t *spline,
5656
}
5757

5858
/*
59-
* Return an upper bound on the error (squared) that could result from
59+
* Return an upper bound on the distance (squared) that could result from
6060
* approximating a spline with a line segment connecting the two endpoints.
6161
*/
62-
static twin_dfixed_t _twin_spline_error_squared(twin_spline_t *spline)
62+
static twin_dfixed_t _twin_spline_distance_squared(twin_spline_t *spline)
6363
{
6464
twin_dfixed_t berr, cerr;
6565

@@ -72,12 +72,12 @@ static twin_dfixed_t _twin_spline_error_squared(twin_spline_t *spline)
7272
}
7373

7474
/*
75-
* Check if a spline is flat enough by comparing the error against the
75+
* Check if a spline is flat enough by comparing the distance against the
7676
* tolerance.
7777
*/
7878
static bool is_flat(twin_spline_t *spline, twin_dfixed_t tolerance_squared)
7979
{
80-
return _twin_spline_error_squared(spline) <= tolerance_squared;
80+
return _twin_spline_distance_squared(spline) <= tolerance_squared;
8181
}
8282

8383
/*
@@ -93,14 +93,45 @@ static void _twin_spline_decompose(twin_path_t *path,
9393
_twin_path_sdraw(path, spline->a.x, spline->a.y);
9494

9595
while (!is_flat(spline, tolerance_squared)) {
96-
int shift = 1;
96+
twin_dfixed_t hi = TWIN_SFIXED_ONE * TWIN_SFIXED_ONE, lo = 0,
97+
t_optimal = 0, t, max_distance = 0, distance;
9798
twin_spline_t left, right;
9899

99-
/* FIXME: Find the optimal shift value to decompose the spline */
100-
do {
101-
_de_casteljau(spline, shift, &left, &right);
102-
shift++;
103-
} while (!is_flat(&left, tolerance_squared));
100+
while (true) {
101+
t = (hi + lo) >> 1;
102+
if (t_optimal == t)
103+
break;
104+
105+
_de_casteljau(spline, t, &left, &right);
106+
107+
distance = _twin_spline_distance_squared(&left);
108+
if (distance < max_distance)
109+
break;
110+
111+
/* The left segment is close enough to fit the original spline. */
112+
if (distance <= tolerance_squared) {
113+
max_distance = distance;
114+
t_optimal = t;
115+
/*
116+
* Try to find a better point such that the line segment
117+
* connecting it to the previous point can fit the spline, while
118+
* maximizing the distance from the spline as much as possible.
119+
*/
120+
lo = t;
121+
} else {
122+
/*
123+
* The line segment connecting the previous point to the
124+
* currently tested point with value 't' cannot fit the original
125+
* spline. The point with value 't_optimal' is sufficient to fit
126+
* the original spline.
127+
*/
128+
if (t_optimal)
129+
break;
130+
hi = t;
131+
}
132+
}
133+
134+
_de_casteljau(spline, t_optimal, &left, &right);
104135

105136
/* Draw the left segment */
106137
_twin_path_sdraw(path, left.d.x, left.d.y);

0 commit comments

Comments
 (0)