Skip to content

Commit a3b3ebe

Browse files
committed
Implement endpoint-based elliptical arc drawing
Allow drawing elliptical arcs by specifying end points instead of center point, following some common vector graphic endpoint parameterization format. Ref: https://www.w3.org/TR/SVG/implnote.html
1 parent cf31070 commit a3b3ebe

File tree

4 files changed

+295
-0
lines changed

4 files changed

+295
-0
lines changed

apps/multi.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,52 @@ static void apps_jelly_start(twin_screen_t *screen, int x, int y, int w, int h)
220220
twin_window_show(window);
221221
}
222222

223+
static void draw_flower(twin_path_t *path, twin_fixed_t radius)
224+
{
225+
const twin_angle_t angle_72 = TWIN_ANGLE_360 / 5;
226+
const twin_angle_t angle_36 = TWIN_ANGLE_360 / 10;
227+
twin_fixed_t p_x = twin_fixed_mul(radius, twin_cos(-angle_36));
228+
twin_fixed_t p_y = twin_fixed_mul(radius, twin_sin(-angle_36));
229+
twin_path_move(path, p_x, p_y);
230+
231+
for (twin_angle_t a = angle_36; a <= TWIN_ANGLE_360; a += angle_72) {
232+
twin_fixed_t c_x = twin_fixed_mul(radius, twin_cos(a));
233+
twin_fixed_t c_y = twin_fixed_mul(radius, twin_sin(a));
234+
twin_fixed_t rx = radius;
235+
twin_fixed_t ry = radius * 3;
236+
twin_path_arc_ellipse(path, 1, 1, rx, ry, p_x, p_y, c_x, c_y,
237+
a - angle_36);
238+
p_x = c_x;
239+
p_y = c_y;
240+
}
241+
242+
twin_path_close(path);
243+
}
244+
245+
static void apps_flower_start(twin_screen_t *screen, int x, int y, int w, int h)
246+
{
247+
twin_window_t *window = twin_window_create(
248+
screen, TWIN_ARGB32, TwinWindowApplication, x, y, w, h);
249+
twin_pixmap_t *pixmap = window->pixmap;
250+
twin_path_t *stroke = twin_path_create();
251+
twin_path_t *path = twin_path_create();
252+
twin_path_translate(path, D(200), D(200));
253+
twin_path_scale(path, D(10), D(10));
254+
twin_path_translate(stroke, D(200), D(200));
255+
twin_fill(pixmap, 0xffffffff, TWIN_SOURCE, 0, 0, w, h);
256+
twin_window_set_name(window, "Flower");
257+
twin_path_move(stroke, D(-200), D(0));
258+
twin_path_draw(stroke, D(200), D(0));
259+
twin_path_move(stroke, D(0), D(200));
260+
twin_path_draw(stroke, D(0), D(-200));
261+
twin_path_set_cap_style(stroke, TwinCapProjecting);
262+
twin_paint_stroke(pixmap, 0xffcc9999, stroke, D(10));
263+
draw_flower(path, D(3));
264+
twin_paint_path(pixmap, 0xffe2d2d2, path);
265+
twin_path_destroy(stroke);
266+
twin_window_show(window);
267+
}
268+
223269
void apps_multi_start(twin_screen_t *screen,
224270
const char *name,
225271
int x,
@@ -233,4 +279,5 @@ void apps_multi_start(twin_screen_t *screen,
233279
apps_quickbrown_start(screen, x += 20, y += 20, w, h);
234280
apps_ascii_start(screen, x += 20, y += 20, w, h);
235281
apps_jelly_start(screen, x += 20, y += 20, w / 2, h);
282+
apps_flower_start(screen, x += 20, y += 20, w, h);
236283
}

include/twin.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,7 @@ void twin_path_ellipse(twin_path_t *path,
800800
twin_fixed_t y,
801801
twin_fixed_t x_radius,
802802
twin_fixed_t y_radius);
803+
803804
void twin_path_arc(twin_path_t *path,
804805
twin_fixed_t x,
805806
twin_fixed_t y,
@@ -808,6 +809,26 @@ void twin_path_arc(twin_path_t *path,
808809
twin_angle_t start,
809810
twin_angle_t extent);
810811

812+
void twin_path_arc_ellipse(twin_path_t *path,
813+
bool large_arc,
814+
bool sweep,
815+
twin_fixed_t radius_x,
816+
twin_fixed_t radius_y,
817+
twin_fixed_t cur_x,
818+
twin_fixed_t cur_y,
819+
twin_fixed_t target_x,
820+
twin_fixed_t target_y,
821+
twin_angle_t rotation);
822+
823+
void twin_path_arc_circle(twin_path_t *path,
824+
bool large_arc,
825+
bool sweep,
826+
twin_fixed_t radius,
827+
twin_fixed_t cur_x,
828+
twin_fixed_t cur_y,
829+
twin_fixed_t target_x,
830+
twin_fixed_t target_y);
831+
811832
void twin_path_rectangle(twin_path_t *path,
812833
twin_fixed_t x,
813834
twin_fixed_t y,
@@ -1104,6 +1125,10 @@ twin_fixed_t twin_tan(twin_angle_t a);
11041125

11051126
void twin_sincos(twin_angle_t a, twin_fixed_t *sin, twin_fixed_t *cos);
11061127

1128+
twin_angle_t twin_atan2(twin_fixed_t y, twin_fixed_t x);
1129+
1130+
twin_angle_t twin_acos(twin_fixed_t x);
1131+
11071132
/*
11081133
* widget.c
11091134
*/

src/path.c

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,146 @@ void twin_path_arc(twin_path_t *path,
236236
twin_path_set_matrix(path, save);
237237
}
238238

239+
static twin_angle_t vector_angle(twin_fixed_t ux,
240+
twin_fixed_t uy,
241+
twin_fixed_t vx,
242+
twin_fixed_t vy)
243+
{
244+
twin_fixed_t dot = twin_fixed_mul(ux, vx) + twin_fixed_mul(uy, vy);
245+
246+
twin_fixed_t ua =
247+
twin_fixed_sqrt(twin_fixed_mul(ux, ux) + twin_fixed_mul(uy, uy));
248+
twin_fixed_t va =
249+
twin_fixed_sqrt(twin_fixed_mul(vx, vx) + twin_fixed_mul(vy, vy));
250+
251+
/* cos(theta) = (u ⋅ v) / (|u| * |v|) */
252+
twin_fixed_t cos_theta = twin_fixed_div(dot, twin_fixed_mul(ua, va));
253+
twin_fixed_t cross = twin_fixed_mul(ux, vy) - twin_fixed_mul(uy, vx);
254+
twin_angle_t angle = twin_acos(cos_theta);
255+
return (cross < 0) ? -angle : angle;
256+
}
257+
258+
typedef struct {
259+
twin_fixed_t cx, cy;
260+
twin_angle_t start, extent;
261+
} twin_ellipse_param_t;
262+
263+
static twin_ellipse_param_t get_center_parameters(twin_fixed_t x1,
264+
twin_fixed_t y1,
265+
twin_fixed_t x2,
266+
twin_fixed_t y2,
267+
bool fa,
268+
bool fs,
269+
twin_fixed_t rx,
270+
twin_fixed_t ry,
271+
twin_fixed_t phi)
272+
{
273+
twin_fixed_t sin_phi = twin_sin(phi);
274+
twin_fixed_t cos_phi = twin_cos(phi);
275+
276+
/* Simplify through translation/rotation */
277+
twin_fixed_t x =
278+
twin_fixed_mul(cos_phi, twin_fixed_mul(x1 - x2, TWIN_FIXED_HALF)) +
279+
twin_fixed_mul(sin_phi, twin_fixed_mul(y1 - y2, TWIN_FIXED_HALF));
280+
281+
twin_fixed_t y =
282+
twin_fixed_mul(-sin_phi, twin_fixed_mul(x1 - x2, TWIN_FIXED_HALF)) +
283+
twin_fixed_mul(cos_phi, twin_fixed_mul(y1 - y2, TWIN_FIXED_HALF));
284+
285+
twin_fixed_t px = twin_fixed_mul(x, x);
286+
twin_fixed_t py = twin_fixed_mul(y, y);
287+
twin_fixed_t prx = twin_fixed_mul(rx, rx);
288+
twin_fixed_t pry = twin_fixed_mul(ry, ry);
289+
290+
/* Correct out-of-range radii */
291+
twin_fixed_t L = twin_fixed_div(px, prx) + twin_fixed_div(py, pry);
292+
293+
if (L > TWIN_FIXED_ONE) {
294+
twin_fixed_t sqrt_L = twin_fixed_sqrt(L);
295+
rx = twin_fixed_mul(sqrt_L, twin_fixed_abs(rx));
296+
ry = twin_fixed_mul(sqrt_L, twin_fixed_abs(ry));
297+
} else {
298+
rx = twin_fixed_abs(rx);
299+
ry = twin_fixed_abs(ry);
300+
}
301+
302+
/* Compute center */
303+
twin_fixed_t sign = (fa != fs) ? -1 : 1;
304+
twin_fixed_t numerator = twin_fixed_mul(prx, pry) -
305+
twin_fixed_mul(prx, py) - twin_fixed_mul(pry, px);
306+
307+
twin_fixed_t denominator =
308+
twin_fixed_mul(prx, py) + twin_fixed_mul(pry, px);
309+
twin_fixed_t M =
310+
sign * twin_fixed_sqrt(twin_fixed_div(numerator, denominator));
311+
twin_fixed_t _cx =
312+
twin_fixed_mul(M, twin_fixed_div(twin_fixed_mul(rx, y), ry));
313+
twin_fixed_t _cy =
314+
twin_fixed_mul(M, twin_fixed_div(twin_fixed_mul(-ry, x), rx));
315+
316+
twin_ellipse_param_t ret;
317+
ret.cx = twin_fixed_mul(cos_phi, _cx) - twin_fixed_mul(sin_phi, _cy) +
318+
twin_fixed_mul(x1 + x2, TWIN_FIXED_HALF);
319+
320+
ret.cy = twin_fixed_mul(sin_phi, _cx) + twin_fixed_mul(cos_phi, _cy) +
321+
twin_fixed_mul(y1 + y2, TWIN_FIXED_HALF);
322+
323+
/* Compute θ and dθ */
324+
ret.start = vector_angle(TWIN_FIXED_ONE, 0, twin_fixed_div(x - _cx, rx),
325+
twin_fixed_div(y - _cy, ry));
326+
twin_angle_t extent = vector_angle(
327+
twin_fixed_div(x - _cx, rx), twin_fixed_div(y - _cy, ry),
328+
twin_fixed_div(-x - _cx, rx), twin_fixed_div(-y - _cy, ry));
329+
330+
if (fs && extent > TWIN_ANGLE_0)
331+
extent -= TWIN_ANGLE_360;
332+
if (!fs && extent < TWIN_ANGLE_0)
333+
extent += TWIN_ANGLE_360;
334+
ret.start %= TWIN_ANGLE_360;
335+
extent %= TWIN_ANGLE_360;
336+
337+
ret.extent = extent;
338+
return ret;
339+
}
340+
341+
void twin_path_arc_ellipse(twin_path_t *path,
342+
bool large_arc,
343+
bool sweep,
344+
twin_fixed_t radius_x,
345+
twin_fixed_t radius_y,
346+
twin_fixed_t cur_x,
347+
twin_fixed_t cur_y,
348+
twin_fixed_t target_x,
349+
twin_fixed_t target_y,
350+
twin_angle_t rotation)
351+
{
352+
twin_ellipse_param_t param;
353+
param = get_center_parameters(cur_x, cur_y, target_x, target_y, large_arc,
354+
sweep, radius_x, radius_y, rotation);
355+
twin_matrix_t save = twin_path_current_matrix(path);
356+
357+
twin_path_translate(path, param.cx, param.cy);
358+
twin_path_rotate(path, rotation);
359+
twin_path_translate(path, -param.cx, -param.cy);
360+
twin_path_arc(path, param.cx, param.cy, radius_x, radius_y, param.start,
361+
param.extent);
362+
363+
twin_path_set_matrix(path, save);
364+
}
365+
366+
void twin_path_arc_circle(twin_path_t *path,
367+
bool large_arc,
368+
bool sweep,
369+
twin_fixed_t radius,
370+
twin_fixed_t cur_x,
371+
twin_fixed_t cur_y,
372+
twin_fixed_t target_x,
373+
twin_fixed_t target_y)
374+
{
375+
twin_path_arc_ellipse(path, large_arc, sweep, radius, radius, cur_x, cur_y,
376+
target_x, target_y, TWIN_ANGLE_0);
377+
}
378+
239379
void twin_path_rectangle(twin_path_t *path,
240380
twin_fixed_t x,
241381
twin_fixed_t y,

src/trig.c

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,86 @@ void twin_sincos(twin_angle_t a, twin_fixed_t *sin, twin_fixed_t *cos)
9797
*cos = cos_val;
9898
}
9999
}
100+
101+
static const twin_angle_t atan_table[] = {
102+
0x0200, /* arctan(2^0) = 45° -> 512 */
103+
0x0130, /* arctan(2^-1) = 26.565° -> 303 */
104+
0x009B, /* arctan(2^-2) = 14.036° -> 155 */
105+
0x004F, /* arctan(2^-3) = 7.125° -> 79 */
106+
0x0027, /* arctan(2^-4) = 3.576° -> 39 */
107+
0x0014, /* arctan(2^-5) = 1.790° -> 20 */
108+
0x000A, /* arctan(2^-6) = 0.895° -> 10 */
109+
0x0005, /* arctan(2^-7) = 0.448° -> 5 */
110+
0x0002, /* arctan(2^-8) = 0.224° -> 2 */
111+
0x0001, /* arctan(2^-9) = 0.112° -> 1 */
112+
0x0001, /* arctan(2^-10) = 0.056° -> 1 */
113+
0x0000, /* arctan(2^-11) = 0.028° -> 0 */
114+
};
115+
116+
static twin_angle_t twin_atan2_first_quadrant(twin_fixed_t y, twin_fixed_t x)
117+
{
118+
if (x == 0 && y == 0)
119+
return 0;
120+
if (x == 0)
121+
return TWIN_ANGLE_90;
122+
if (y == 0)
123+
return TWIN_ANGLE_0;
124+
twin_fixed_t current_x = x;
125+
twin_fixed_t current_y = y;
126+
twin_angle_t angle = 0;
127+
/* CORDIC iteration */
128+
for (int i = 0; i < 12; i++) {
129+
twin_fixed_t temp_x = current_x;
130+
if (current_y > 0) {
131+
current_x += (current_y >> i);
132+
current_y -= (temp_x >> i);
133+
angle += atan_table[i];
134+
} else {
135+
current_x -= (current_y >> i);
136+
current_y += (temp_x >> i);
137+
angle -= atan_table[i];
138+
}
139+
}
140+
return angle;
141+
}
142+
143+
twin_angle_t twin_atan2(twin_fixed_t y, twin_fixed_t x)
144+
{
145+
if (x == 0 && y == 0)
146+
return TWIN_ANGLE_0;
147+
if (x == 0)
148+
return (y > 0) ? TWIN_ANGLE_90 : TWIN_ANGLE_270;
149+
if (y == 0)
150+
return (x > 0) ? TWIN_ANGLE_0 : TWIN_ANGLE_180;
151+
twin_fixed_t x_sign_mask = x >> 31;
152+
twin_fixed_t abs_x = (x ^ x_sign_mask) - x_sign_mask;
153+
twin_fixed_t y_sign_mask = y >> 31;
154+
twin_fixed_t abs_y = (y ^ y_sign_mask) - y_sign_mask;
155+
twin_fixed_t m = ((~x_sign_mask & ~y_sign_mask) * 0) +
156+
((x_sign_mask & ~y_sign_mask) * 1) +
157+
((x_sign_mask & y_sign_mask) * 1) +
158+
((~x_sign_mask & y_sign_mask) * 2);
159+
twin_fixed_t sign = 1 - 2 * (x_sign_mask ^ y_sign_mask);
160+
twin_angle_t angle = twin_atan2_first_quadrant(abs_y, abs_x);
161+
/* First quadrant : angle
162+
Second quadrant : 180 - angle
163+
Third quadrant : 180 + angle
164+
Fourth quadrant : 360 - angle*/
165+
return TWIN_ANGLE_180 * m + sign * angle;
166+
}
167+
168+
twin_angle_t twin_acos(twin_fixed_t x)
169+
{
170+
if (x <= -TWIN_FIXED_ONE)
171+
return TWIN_ANGLE_180;
172+
if (x >= TWIN_FIXED_ONE)
173+
return TWIN_ANGLE_0;
174+
twin_fixed_t y = twin_fixed_sqrt(TWIN_FIXED_ONE - twin_fixed_mul(x, x));
175+
twin_angle_t angle;
176+
if (x >= 0) {
177+
angle = twin_atan2_first_quadrant(y, x);
178+
} else {
179+
angle = TWIN_ANGLE_180 - twin_atan2_first_quadrant(y, -x);
180+
}
181+
return angle;
182+
}

0 commit comments

Comments
 (0)