Branch data Line data Source code
1 : : /* ______ ___ ___
2 : : * /\ _ \ /\_ \ /\_ \
3 : : * \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___
4 : : * \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\
5 : : * \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \
6 : : * \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
7 : : * \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
8 : : * /\____/
9 : : * \_/__/
10 : : *
11 : : * Polyline primitive.
12 : : *
13 : : *
14 : : * By Michał Cichoń.
15 : : *
16 : : * See readme.txt for copyright information.
17 : : */
18 : :
19 : : #include "allegro5/allegro.h"
20 : : #include "allegro5/allegro_primitives.h"
21 : : #include "allegro5/internal/aintern_list.h"
22 : : #include "allegro5/internal/aintern_prim.h"
23 : : #include <float.h>
24 : : #include <math.h>
25 : :
26 : :
27 : : /*
28 : : * Computes direction, normal direction and the length of the segment.
29 : : */
30 : 544 : static float compute_direction_and_normal(const float* begin, const float* end, float* dir, float* normal)
31 : : {
32 : : float length;
33 : :
34 : : /* Determine direction, normal direction and length of the line segment. */
35 : 544 : dir[0] = end[0] - begin[0];
36 : 544 : dir[1] = end[1] - begin[1];
37 : :
38 : 544 : length = _al_prim_normalize(dir);
39 : :
40 : 544 : normal[0] = -dir[1];
41 : 544 : normal[1] = dir[0];
42 : :
43 : 544 : return length;
44 : : }
45 : :
46 : : /*
47 : : * Compute end cross points.
48 : : */
49 : 36 : static void compute_end_cross_points(const float* v0, const float* v1, float radius, float* p0, float* p1)
50 : : {
51 : : float dir[2];
52 : : float normal[2];
53 : : /* XXX delete this parameter? */
54 : : (void)radius;
55 : :
56 : 36 : compute_direction_and_normal(v0, v1, dir, normal);
57 : :
58 : 36 : p0[0] = v1[0] + normal[0] * radius;
59 : 36 : p0[1] = v1[1] + normal[1] * radius;
60 : 36 : p1[0] = v1[0] - normal[0] * radius;
61 : 36 : p1[1] = v1[1] - normal[1] * radius;
62 : 36 : }
63 : :
64 : : /*
65 : : * Compute cross points.
66 : : */
67 : 248 : static void compute_cross_points(const float* v0, const float* v1, const float* v2, float radius,
68 : : float* l0, float* l1, float* r0, float* r1, float* out_middle, float* out_angle, float* out_miter_distance)
69 : : {
70 : : float normal_0[2], normal_1[2];
71 : : float dir_0[2], dir_1[2];
72 : : float middle[2];
73 : : float diff[2];
74 : : float miter_distance;
75 : : float angle;
76 : :
77 : : /* We accept a few call cases. Filter out unsupported. */
78 [ + - ][ - + ]: 248 : ASSERT((NULL != v0 || NULL != v2) && (NULL != v1));
[ # # ][ # # ]
[ # # ]
79 : :
80 : : /* Compute directions. */
81 : 248 : compute_direction_and_normal(v0, v1, dir_0, normal_0);
82 : 248 : compute_direction_and_normal(v1, v2, dir_1, normal_1);
83 : :
84 : : /* Compute angle of deflection between segments. */
85 : 248 : diff[0] = dir_0[0] * dir_1[0] + dir_0[1] * dir_1[1];
86 : 248 : diff[1] = -(dir_0[0] * dir_1[1] - dir_0[1] * dir_1[0]);
87 : :
88 [ - + ][ # # ]: 248 : angle = (diff[0] || diff[1]) ? atan2f(diff[1], diff[0]) : 0.0f;
89 : :
90 : : /* Calculate miter distance. */
91 [ + - ]: 248 : miter_distance = angle != 0.0f ? radius / cosf(fabsf(angle) * 0.5f) : radius;
92 : :
93 : 248 : middle[0] = normal_0[0] + normal_1[0];
94 : 248 : middle[1] = normal_0[1] + normal_1[1];
95 : :
96 : 248 : _al_prim_normalize(middle);
97 : :
98 : : /* Compute points. */
99 [ + + ]: 248 : if (angle > 0.0f)
100 : : {
101 : 130 : l0[0] = v1[0] + normal_0[0] * radius;
102 : 130 : l0[1] = v1[1] + normal_0[1] * radius;
103 : 130 : r0[0] = v1[0] + normal_1[0] * radius;
104 : 130 : r0[1] = v1[1] + normal_1[1] * radius;
105 : :
106 : 130 : l1[0] = r1[0] = v1[0] - middle[0] * miter_distance;
107 : 130 : l1[1] = r1[1] = v1[1] - middle[1] * miter_distance;
108 : : }
109 : : else
110 : : {
111 : 118 : middle[0] = -middle[0];
112 : 118 : middle[1] = -middle[1];
113 : :
114 : 118 : l1[0] = v1[0] - normal_0[0] * radius;
115 : 118 : l1[1] = v1[1] - normal_0[1] * radius;
116 : 118 : r1[0] = v1[0] - normal_1[0] * radius;
117 : 118 : r1[1] = v1[1] - normal_1[1] * radius;
118 : :
119 : 118 : l0[0] = r0[0] = v1[0] - middle[0] * miter_distance;
120 : 118 : l0[1] = r0[1] = v1[1] - middle[1] * miter_distance;
121 : : }
122 : :
123 [ + + ]: 248 : if (out_angle)
124 : 244 : *out_angle = angle;
125 : :
126 [ + + ]: 248 : if (out_miter_distance)
127 : 244 : *out_miter_distance = miter_distance;
128 : :
129 [ + + ]: 248 : if (out_middle)
130 : 244 : memcpy(out_middle, middle, sizeof(float) * 2);
131 : 248 : }
132 : :
133 : : /*
134 : : * Emits filled arc.
135 : : *
136 : : * Arc is defined by pivot point, radius, start and end angle.
137 : : * Starting and ending angle are wrapped to two pi range.
138 : : */
139 : 82 : static void emit_arc(ALLEGRO_PRIM_VERTEX_CACHE* cache, const float* pivot, float start, float end, float radius, int segments)
140 : : {
141 : : float step, angle, arc;
142 : : float v0[2];
143 : : float v1[2];
144 : : int i;
145 : :
146 : : /* This is very small arc, we will draw nothing. */
147 [ + - ]: 82 : if (fabsf(end - start) < 0.001f)
148 : 82 : return;
149 : :
150 : : /* Make sure start both start angle is located in the
151 : : * range [0, 2 * pi) and end angle is greater than
152 : : * start angle.
153 : : */
154 : 82 : start = fmodf(start, ALLEGRO_PI * 2.0f);
155 : 82 : end = fmodf(end, ALLEGRO_PI * 2.0f);
156 [ - + ]: 82 : if (end <= start)
157 : 0 : end += ALLEGRO_PI * 2.0f;
158 : :
159 : 82 : arc = end - start;
160 : :
161 : 82 : segments = (int)(segments * arc / ALLEGRO_PI * 2.0f);
162 [ - + ]: 82 : if (segments < 1)
163 : 0 : segments = 1;
164 : :
165 : 82 : step = arc / segments;
166 : :
167 : 82 : angle = start;
168 : :
169 : 82 : v0[0] = pivot[0] + cosf(angle) * radius;
170 : 82 : v0[1] = pivot[1] + sinf(angle) * radius;
171 [ + + ]: 1632 : for (i = 0; i < segments; ++i, angle += step)
172 : : {
173 : 1550 : v1[0] = pivot[0] + cosf(angle + step) * radius;
174 : 1550 : v1[1] = pivot[1] + sinf(angle + step) * radius;
175 : :
176 : 1550 : _al_prim_cache_push_triangle(cache, v0, pivot, v1);
177 : :
178 : 1550 : v0[0] = v1[0];
179 : 1550 : v0[1] = v1[1];
180 : : }
181 : : }
182 : :
183 : : /*
184 : : * Emits square cap.
185 : : *
186 : : * Square cap is an rectangle with edges lengths equal to radius
187 : : * and double radius, first along direction second along normal
188 : : * direction.
189 : : */
190 : 4 : static void emit_square_end_cap(ALLEGRO_PRIM_VERTEX_CACHE* cache, const float* pivot, const float* dir, const float* normal, float radius)
191 : : {
192 : : /* Prepare all four vertices of the rectangle. */
193 : 4 : float v0[2] = { pivot[0] + normal[0] * radius, pivot[1] + normal[1] * radius };
194 : 4 : float v1[2] = { pivot[0] - normal[0] * radius, pivot[1] - normal[1] * radius };
195 : 4 : float v2[2] = { v0[0] + dir[0] * radius, v0[1] + dir[1] * radius };
196 : 4 : float v3[2] = { v1[0] + dir[0] * radius, v1[1] + dir[1] * radius };
197 : :
198 : : /* Emit. */
199 : 4 : _al_prim_cache_push_triangle(cache, v0, v2, v3);
200 : 4 : _al_prim_cache_push_triangle(cache, v0, v3, v1);
201 : 4 : }
202 : :
203 : : /*
204 : : * Emits triangular cap.
205 : : */
206 : 4 : static void emit_triange_end_cap(ALLEGRO_PRIM_VERTEX_CACHE* cache, const float* pivot, const float* dir, const float* normal, float radius)
207 : : {
208 : : /* Prepare all four vertices of the rectangle. */
209 : 4 : float v0[2] = { pivot[0] + normal[0] * radius, pivot[1] + normal[1] * radius };
210 : 4 : float v1[2] = { pivot[0] - normal[0] * radius, pivot[1] - normal[1] * radius };
211 : 4 : float v2[2] = { pivot[0] + dir[0] * radius, pivot[1] + dir[1] * radius };
212 : :
213 : : /* Emit. */
214 : 4 : _al_prim_cache_push_triangle(cache, v0, v2, v1);
215 : 4 : }
216 : :
217 : : /*
218 : : * Emits rounded cap.
219 : : */
220 : 4 : static void emit_round_end_cap(ALLEGRO_PRIM_VERTEX_CACHE* cache, const float* pivot, const float* dir, const float* normal, float radius)
221 : : {
222 : 4 : float angle = atan2f(-normal[1], -normal[0]);
223 : : /* XXX delete these parameters? */
224 : : (void)dir;
225 : : (void)radius;
226 : :
227 : 4 : emit_arc(cache, pivot, angle, angle + ALLEGRO_PI, radius, 16);
228 : 4 : }
229 : :
230 : : /*
231 : : * Emits end cap.
232 : : *
233 : : * Direction of the end cap is defined by vector [v1 - v0]. p0 and p1 are starting
234 : : * and ending point of the cap. Both should be located on the circle with center
235 : : * in v1 and specified radius. p0 have to be located on the negative and p0 on the
236 : : * positive half plane defined by direction vector.
237 : : */
238 : 36 : static void emit_end_cap(ALLEGRO_PRIM_VERTEX_CACHE* cache, int cap_style, const float* v0, const float* v1, float radius)
239 : : {
240 : : float dir[2];
241 : : float normal[2];
242 : :
243 : : /* Do do not want you to call this function for closed cap.
244 : : * It is special and there is nothing we can do with it there.
245 : : */
246 [ - + ][ # # ]: 36 : ASSERT(cap_style != ALLEGRO_LINE_CAP_CLOSED);
[ # # ]
247 : :
248 : : /* There nothing we can do for this kind of ending cap. */
249 [ + + ]: 36 : if (cap_style == ALLEGRO_LINE_CAP_NONE)
250 : 36 : return;
251 : :
252 : : /* Compute normal and direction for our segment. */
253 : 12 : compute_direction_and_normal(v0, v1, dir, normal);
254 : :
255 : : /* Emit vertices for cap. */
256 [ + + ]: 12 : if (cap_style == ALLEGRO_LINE_CAP_SQUARE)
257 : 4 : emit_square_end_cap(cache, v1, dir, normal, radius);
258 [ + + ]: 8 : else if (cap_style == ALLEGRO_LINE_CAP_TRIANGLE)
259 : 4 : emit_triange_end_cap(cache, v1, dir, normal, radius);
260 [ + - ]: 4 : else if (cap_style == ALLEGRO_LINE_CAP_ROUND)
261 : 4 : emit_round_end_cap(cache, v1, dir, normal, radius);
262 : : else {
263 : :
264 [ # # ]: 36 : ASSERT("Unknown or unsupported style of ending cap." && false);
265 : : }
266 : : }
267 : :
268 : : /*
269 : : * Emits bevel join.
270 : : */
271 : : static void emit_bevel_join(ALLEGRO_PRIM_VERTEX_CACHE* cache, const float* pivot, const float* p0, const float* p1)
272 : : {
273 : 20 : _al_prim_cache_push_triangle(cache, pivot, p0, p1);
274 : : }
275 : :
276 : : /*
277 : : * Emits round join.
278 : : */
279 : 78 : static void emit_round_join(ALLEGRO_PRIM_VERTEX_CACHE* cache, const float* pivot, const float* p0, const float* p1, float radius)
280 : : {
281 : 78 : float start = atan2f(p1[1] - pivot[1], p1[0] - pivot[0]);
282 : 78 : float end = atan2f(p0[1] - pivot[1], p0[0] - pivot[0]);
283 : :
284 [ + + ]: 78 : if (end < start)
285 : 24 : end += ALLEGRO_PI * 2.0f;
286 : :
287 : 78 : emit_arc(cache, pivot, start, end, radius, 16);
288 : 78 : }
289 : :
290 : : /*
291 : : * Emits miter join.
292 : : */
293 : 40 : static void emit_miter_join(ALLEGRO_PRIM_VERTEX_CACHE* cache, const float* pivot, const float* p0, const float* p1,
294 : : float radius, const float* middle, float angle, float miter_distance, float max_miter_distance)
295 : : {
296 : : /* XXX delete this parameter? */
297 : : (void)radius;
298 : :
299 [ + + ]: 40 : if (miter_distance > max_miter_distance) {
300 : :
301 : 24 : float normal[2] = { -middle[1], middle[0] };
302 : :
303 : 24 : float offset = (miter_distance - max_miter_distance) * tanf((ALLEGRO_PI - fabsf(angle)) * 0.5f);
304 : :
305 : 48 : float v0[2] = {
306 : 24 : pivot[0] + middle[0] * max_miter_distance + normal[0] * offset,
307 : 24 : pivot[1] + middle[1] * max_miter_distance + normal[1] * offset
308 : : };
309 : :
310 : 48 : float v1[2] = {
311 : 24 : pivot[0] + middle[0] * max_miter_distance - normal[0] * offset,
312 : 24 : pivot[1] + middle[1] * max_miter_distance - normal[1] * offset
313 : : };
314 : :
315 : 24 : _al_prim_cache_push_triangle(cache, pivot, v0, v1);
316 : 24 : _al_prim_cache_push_triangle(cache, pivot, p0, v0);
317 : 24 : _al_prim_cache_push_triangle(cache, pivot, p1, v1);
318 : : }
319 : : else {
320 : :
321 : 32 : float miter[2] = {
322 : 16 : pivot[0] + middle[0] * miter_distance,
323 : 16 : pivot[1] + middle[1] * miter_distance,
324 : : };
325 : :
326 : 16 : _al_prim_cache_push_triangle(cache, pivot, p0, miter);
327 : 16 : _al_prim_cache_push_triangle(cache, pivot, miter, p1);
328 : : }
329 : 40 : }
330 : :
331 : :
332 : : /* Emit join between segments.
333 : : */
334 : 244 : static void emit_join(ALLEGRO_PRIM_VERTEX_CACHE* cache, int join_style, const float* pivot,
335 : : const float* p0, const float* p1, float radius, const float* middle,
336 : : float angle, float miter_distance, float miter_limit)
337 : : {
338 : : /* There is nothing to do for this type of join. */
339 [ + + ]: 244 : if (join_style == ALLEGRO_LINE_JOIN_NONE)
340 : 244 : return;
341 : :
342 [ + + ]: 138 : if (join_style == ALLEGRO_LINE_JOIN_BEVEL)
343 : : emit_bevel_join(cache, pivot, p0, p1);
344 [ + + ]: 118 : else if (join_style == ALLEGRO_LINE_JOIN_ROUND)
345 : 78 : emit_round_join(cache, pivot, p0, p1, radius);
346 [ + - ]: 40 : else if (join_style == ALLEGRO_LINE_JOIN_MITER)
347 : 40 : emit_miter_join(cache, pivot, p0, p1, radius, middle, angle, miter_distance, miter_limit * radius);
348 : : else {
349 : :
350 [ # # ]: 0 : ASSERT("Unknown or unsupported style of join." && false);
351 : : }
352 : : }
353 : :
354 : 22 : static void emit_polyline(ALLEGRO_PRIM_VERTEX_CACHE* cache, const float* vertices, int vertex_stride, int vertex_count, int join_style, int cap_style, float thickness, float miter_limit)
355 : : {
356 : : # define VERTEX(index) ((const float*)(((uint8_t*)vertices) + vertex_stride * ((vertex_count + (index)) % vertex_count)))
357 : :
358 : : float l0[2], l1[2];
359 : : float r0[2], r1[2];
360 : : float p0[2], p1[2];
361 : : float radius;
362 : : int steps;
363 : : int i;
364 : :
365 [ - + ][ # # ]: 22 : ASSERT(thickness > 0.0f);
[ # # ]
366 : :
367 : : /* Discard invalid lines. */
368 [ + - ]: 22 : if (vertex_count < 2)
369 : 22 : return;
370 : :
371 : 22 : radius = 0.5f * thickness;
372 : :
373 : : /* Single line cannot be closed. If user forgot to explicitly specify
374 : : * most desired alternative cap style, we just disable capping at all.
375 : : */
376 [ - + ]: 22 : if (vertex_count == 2 && cap_style == ALLEGRO_LINE_CAP_CLOSED)
377 : 0 : cap_style = ALLEGRO_LINE_CAP_NONE;
378 : :
379 : : /* Prepare initial set of vertices. */
380 [ + + ]: 22 : if (cap_style != ALLEGRO_LINE_CAP_CLOSED)
381 : : {
382 : : /* We can emit ending caps right now.
383 : : *
384 : : * VERTEX(-2) and similar are safe, because it at this place
385 : : * it is guaranteed that there are at least two vertices
386 : : * in the buffer.
387 : : */
388 : 18 : emit_end_cap(cache, cap_style, VERTEX(1), VERTEX(0), radius);
389 : 18 : emit_end_cap(cache, cap_style, VERTEX(-2), VERTEX(-1), radius);
390 : :
391 : : /* Compute points on the left side of the very first segment. */
392 : 18 : compute_end_cross_points(VERTEX(1), VERTEX(0), radius, p1, p0);
393 : :
394 : : /* For non-closed line we have N - 1 steps, but since we iterate
395 : : * from one, N is right value.
396 : : */
397 : 18 : steps = vertex_count;
398 : : }
399 : : else
400 : : {
401 : : /* Compute points on the left side of the very first segment. */
402 : 4 : compute_cross_points(VERTEX(-1), VERTEX(0), VERTEX(1), radius, l0, l1, p0, p1, NULL, NULL, NULL);
403 : :
404 : : /* Closed line use N steps, because last vertex have to be
405 : : * connected with first one.
406 : : */
407 : 4 : steps = vertex_count + 1;
408 : : }
409 : :
410 : : /* Process segments. */
411 [ + + ]: 284 : for (i = 1; i < steps; ++i)
412 : : {
413 : : /* Pick vertex and their neighbors. */
414 : 262 : const float* v0 = VERTEX(i - 1);
415 : 262 : const float* v1 = VERTEX(i);
416 : 262 : const float* v2 = VERTEX(i + 1);
417 : :
418 : : /* Choose correct cross points. */
419 [ + + ][ + + ]: 506 : if ((cap_style == ALLEGRO_LINE_CAP_CLOSED) || (i < steps - 1)) {
420 : :
421 : : float middle[2];
422 : : float miter_distance;
423 : : float angle;
424 : :
425 : : /* Compute cross points. */
426 : 244 : compute_cross_points(v0, v1, v2, radius, l0, l1, r0, r1, middle, &angle, &miter_distance);
427 : :
428 : : /* Emit join. */
429 [ + + ]: 244 : if (angle >= 0.0f)
430 : 126 : emit_join(cache, join_style, v1, l0, r0, radius, middle, angle, miter_distance, miter_limit);
431 : : else
432 : 118 : emit_join(cache, join_style, v1, r1, l1, radius, middle, angle, miter_distance, miter_limit);
433 : : }
434 : : else
435 : 18 : compute_end_cross_points(v0, v1, radius, l0, l1);
436 : :
437 : : /* Emit triangles. */
438 : 262 : _al_prim_cache_push_triangle(cache, v0, v1, l1);
439 : 262 : _al_prim_cache_push_triangle(cache, v0, l1, p1);
440 : 262 : _al_prim_cache_push_triangle(cache, v0, p0, l0);
441 : 262 : _al_prim_cache_push_triangle(cache, v0, l0, v1);
442 : :
443 : : /* Save current most right vertices. */
444 : 262 : memcpy(p0, r0, sizeof(float) * 2);
445 : 262 : memcpy(p1, r1, sizeof(float) * 2);
446 : : }
447 : :
448 : : # undef VERTEX
449 : : }
450 : :
451 : 24 : static void do_draw_polyline(ALLEGRO_PRIM_VERTEX_CACHE* cache, const float* vertices, int vertex_stride, int vertex_count, int join_style, int cap_style, ALLEGRO_COLOR color, float thickness, float miter_limit)
452 : : {
453 [ + + ]: 24 : if (thickness > 0.0f)
454 : : {
455 : 22 : _al_prim_cache_init(cache, ALLEGRO_PRIM_VERTEX_CACHE_TRIANGLE, color);
456 : 22 : emit_polyline(cache, vertices, vertex_stride, vertex_count, join_style, cap_style, thickness, miter_limit);
457 : 22 : _al_prim_cache_term(cache);
458 : : }
459 : : else
460 : : {
461 : : # define VERTEX(index) ((const float*)(((uint8_t*)vertices) + vertex_stride * ((vertex_count + (index)) % vertex_count)))
462 : :
463 : : int i;
464 : :
465 : 2 : _al_prim_cache_init(cache, ALLEGRO_PRIM_VERTEX_CACHE_LINE_STRIP, color);
466 : :
467 [ + + ]: 26 : for (i = 0; i < vertex_count; ++i) {
468 : :
469 [ - + ]: 24 : if (cache->size >= (ALLEGRO_VERTEX_CACHE_SIZE - 2))
470 : 0 : _al_prim_cache_flush(cache);
471 : :
472 : 24 : _al_prim_cache_push_point(cache, VERTEX(i));
473 : : }
474 : :
475 : 2 : _al_prim_cache_term(cache);
476 : :
477 : : # undef VERTEX
478 : : }
479 : 24 : }
480 : :
481 : : /* Function: al_draw_polyline
482 : : */
483 : 24 : void al_draw_polyline(const float* vertices, int vertex_count,
484 : : ALLEGRO_LINE_JOIN join_style, ALLEGRO_LINE_CAP cap_style,
485 : : ALLEGRO_COLOR color, float thickness, float miter_limit)
486 : : {
487 : : ALLEGRO_PRIM_VERTEX_CACHE cache;
488 : 24 : do_draw_polyline(&cache, vertices, sizeof(float) * 2, vertex_count, join_style, cap_style, color, thickness, miter_limit);
489 : 24 : }
490 : :
491 : : /* Function: al_draw_polyline_ex
492 : : */
493 : 0 : void al_draw_polyline_ex(const float* vertices, int vertex_stride,
494 : : int vertex_count, ALLEGRO_LINE_JOIN join_style, ALLEGRO_LINE_CAP cap_style,
495 : : ALLEGRO_COLOR color, float thickness, float miter_limit)
496 : : {
497 : : ALLEGRO_PRIM_VERTEX_CACHE cache;
498 : 0 : do_draw_polyline(&cache, vertices, vertex_stride, vertex_count, join_style, cap_style, color, thickness, miter_limit);
499 : 0 : }
500 : :
501 : : /* vim: set sts=3 sw=3 et: */
|