[AdgTransformationMode] Renamed to AdgTransformMode
[adg.git] / cpml / cpml-arc.c
blob299fc3d736dbd6f95d32e566f27a83628a7d1eae
1 /* CPML - Cairo Path Manipulation Library
2 * Copyright (C) 2008, 2009 Nicola Fontana <ntd at entidi.it>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
21 /**
22 * SECTION:cpml-arc
23 * @Section_Id:CpmlArc
24 * @title: CpmlArc
25 * @short_description: Manipulation of circular arcs
27 * The following functions manipulate #CAIRO_PATH_ARC_TO #CpmlPrimitive.
28 * No validation is made on the input so use the following methods
29 * only when you are sure the <varname>primitive</varname> argument
30 * is effectively an arc-to.
32 * The arc primitive is defined by 3 points: the first one is the usual
33 * implicit point got from the previous primitive, the second point is
34 * an arbitrary intermediate point laying on the arc and the third point
35 * is the end of the arc. These points identify univocally an arc:
36 * furthermore, the intermediate point also gives the side of
37 * the arc.
39 * As a special case, when the first point is coincident with the end
40 * point the primitive is considered a circle with diameter defined by
41 * the segment between the first and the intermediate point.
43 * <important>
44 * <para>
45 * An arc is not a native cairo primitive and should be treated specially.
46 * </para>
47 * </important>
49 * Using these CPML APIs you are free to use #CAIRO_PATH_ARC_TO whenever
50 * you want but, if you are directly accessing the struct fields, you
51 * are responsible of converting arcs to curves before passing them
52 * to cairo. In other words, do not directly feed #CpmlPath struct to
53 * cairo (throught cairo_append_path() for example) or at least do not
54 * expect it will work.
56 * The conversion is provided by two APIs: cpml_arc_to_cairo() and
57 * cpml_arc_to_curves(). The former directly renders to a cairo context
58 * and is internally used by all the ..._to_cairo() functions when an
59 * arc is met. The latter provided a more powerful (and more complex)
60 * approach as it allows to specify the number of curves to use and do
61 * not need a cairo context.
62 **/
65 #include "cpml-arc.h"
66 #include "cpml-pair.h"
68 #include <stdlib.h>
69 #include <math.h>
72 /* Hardcoded max angle of the arc to be approximated by a Bézier curve:
73 * this influence the arc quality (the default value is got from cairo) */
74 #define ARC_MAX_ANGLE M_PI_2
77 static cairo_bool_t get_center (const CpmlPair *p,
78 CpmlPair *dest);
79 static void get_angles (const CpmlPair *p,
80 const CpmlPair *center,
81 double *start,
82 double *end);
83 static void arc_to_curve (CpmlPrimitive *curve,
84 const CpmlPair *center,
85 double r,
86 double start,
87 double end);
90 /**
91 * cpml_arc_type_get_npoints:
93 * Returns the number of point needed to properly specify an arc primitive.
95 * Return value: 3
96 **/
97 int
98 cpml_arc_type_get_npoints(void)
100 return 3;
104 * cpml_arc_info:
105 * @arc: the #CpmlPrimitive arc data
106 * @center: where to store the center coordinates (can be %NULL)
107 * @r: where to store the radius (can be %NULL)
108 * @start: where to store the starting angle (can be %NULL)
109 * @end: where to store the ending angle (can be %NULL)
111 * Given an @arc, this function calculates and returns its basic data.
112 * Any pointer can be %NULL, in which case the requested info is not
113 * returned. This function can fail (when the three points lay on a
114 * straight line, for example) in which case 0 is returned and no
115 * data can be considered valid.
117 * The radius @r can be 0 when the three points are coincidents: a
118 * circle with radius 0 is considered a valid path.
120 * When the start and end angle are returned, together with their
121 * values these angles implicitely gives another important information:
122 * the arc direction.
124 * If @start < @end the arc must be rendered with increasing angle
125 * value (clockwise direction using the ordinary cairo coordinate
126 * system) while if @start > @end the arc must be rendered in reverse
127 * order (that is counterclockwise in the cairo world). This is the
128 * reason the angle values are returned in the range
129 * { -M_PI < value < 3*M_PI } inclusive instead of the usual
130 * { -M_PI < value < M_PI } range.
132 * Return value: 1 if the function worked succesfully, 0 on errors
134 cairo_bool_t
135 cpml_arc_info(const CpmlPrimitive *arc, CpmlPair *center,
136 double *r, double *start, double *end)
138 CpmlPair p[3], l_center;
140 cpml_pair_from_cairo(&p[0], arc->org);
141 cpml_pair_from_cairo(&p[1], &arc->data[1]);
142 cpml_pair_from_cairo(&p[2], &arc->data[2]);
144 if (!get_center(p, &l_center))
145 return 0;
147 if (center)
148 *center = l_center;
150 if (r != NULL)
151 *r = cpml_pair_distance(&p[0], &l_center);
153 if (start != NULL || end != NULL) {
154 double l_start, l_end;
156 get_angles(p, &l_center, &l_start, &l_end);
158 if (start != NULL)
159 *start = l_start;
160 if (end != NULL)
161 *end = l_end;
164 return 1;
168 * cpml_arc_length:
169 * @arc: the #CpmlPrimitive arc data
171 * Given the @arc primitive, returns its length.
173 * Return value: the requested length or 0 on errors
175 double
176 cpml_arc_length(const CpmlPrimitive *arc)
178 double r, start, end, delta;
180 if (!cpml_arc_info(arc, NULL, &r, &start, &end) || start == end)
181 return 0.;
183 delta = end - start;
184 if (delta < 0)
185 delta += M_PI*2;
187 return r*delta;
191 * cpml_arc_extents:
192 * @arc: the #CpmlPrimitive arc data
193 * @extents: where to store the extents
195 * Given an @arc primitive, returns its boundary box in @extents.
197 void
198 cpml_arc_extents(const CpmlPrimitive *arc, CpmlExtents *extents)
200 double r, start, end;
201 CpmlPair pair;
203 if (!cpml_arc_info(arc, NULL, &r, &start, &end))
204 return;
206 /* Add the right quadrant point if needed */
207 if ((start < 0 && end > 0) ||
208 (end < M_PI * 2 && start > M_PI * 2)) {
209 pair.x = r;
210 pair.y = 0;
211 cpml_extents_pair_add(extents, &pair);
214 /* Add the bottom quadrant point if needed */
215 if ((start < M_PI_2 && end > M_PI_2) ||
216 (end < M_PI_2 * 5 && start > M_PI_2 * 5)) {
217 pair.x = 0;
218 pair.y = r;
219 cpml_extents_pair_add(extents, &pair);
222 /* Add the left quadrant point if needed */
223 if (start < M_PI && end > M_PI) {
224 pair.x = -r;
225 pair.y = 0;
226 cpml_extents_pair_add(extents, &pair);
229 /* Add the top quadrant point if needed */
230 if (start < M_PI_2 * 3 && end > M_PI) {
231 pair.x = 0;
232 pair.y = -r;
233 cpml_extents_pair_add(extents, &pair);
236 /* Add the start point */
237 cpml_pair_from_cairo(&pair, cpml_primitive_get_point(arc, 0));
238 cpml_extents_pair_add(extents, &pair);
240 /* Add the end point */
241 cpml_pair_from_cairo(&pair, cpml_primitive_get_point(arc, -1));
242 cpml_extents_pair_add(extents, &pair);
246 * cpml_arc_pair_at:
247 * @arc: the #CpmlPrimitive arc data
248 * @pair: the destination #CpmlPair
249 * @pos: the position value
251 * Given an @arc, finds the coordinates at position @pos (where 0 is
252 * the start and 1 is the end) and stores the result in @pair.
254 * @pos can also be outside the 0..1 limit, as interpolating on an
255 * arc is quite trivial.
257 void
258 cpml_arc_pair_at(const CpmlPrimitive *arc, CpmlPair *pair, double pos)
260 if (pos == 0.) {
261 cpml_pair_from_cairo(pair, arc->org);
262 } else if (pos == 1.) {
263 cpml_pair_from_cairo(pair, &arc->data[2]);
264 } else {
265 CpmlPair center;
266 double r, start, end, angle;
268 if (!cpml_arc_info(arc, &center, &r, &start, &end))
269 return;
271 angle = (end-start)*pos + start;
272 cpml_vector_from_angle(pair, angle);
273 cpml_vector_set_length(pair, r);
274 cpml_pair_add(pair, &center);
279 * cpml_arc_vector_at:
280 * @arc: the #CpmlPrimitive arc data
281 * @vector: the destination vector
282 * @pos: the position value
284 * Given an @arc, finds the slope at position @pos (where 0 is
285 * the start and 1 is the end) and stores the result in @vector.
287 * @pos can also be outside the 0..1 limit, as interpolating on an
288 * arc is quite trivial.
290 void
291 cpml_arc_vector_at(const CpmlPrimitive *arc, CpmlVector *vector, double pos)
293 double start, end, angle;
295 if (!cpml_arc_info(arc, NULL, NULL, &start, &end))
296 return;
298 angle = (end-start)*pos + start;
299 cpml_vector_from_angle(vector, angle);
300 cpml_vector_normal(vector);
302 if (start > end)
303 cpml_pair_negate(vector);
307 * cpml_arc_near_pos:
308 * @arc: the #CpmlPrimitive arc data
309 * @pair: the coordinates of the subject point
311 * Returns the pos value of the point on @arc nearest to @pair.
312 * The returned value is always between 0 and 1.
314 * <important>
315 * <title>TODO</title>
316 * <itemizedlist>
317 * <listitem>To be implemented...</listitem>
318 * </itemizedlist>
319 * </important>
321 * Return value: the pos value, always between 0 and 1
323 double
324 cpml_arc_near_pos(const CpmlPrimitive *arc, const CpmlPair *pair)
326 /* TODO */
328 return 0;
332 * cpml_arc_intersection:
333 * @arc: the first arc
334 * @arc2: the second arc
335 * @dest: a vector of #CpmlPair
336 * @max: maximum number of intersections to return
337 * (that is, the size of @dest)
339 * Given two arcs (@arc and @arc2), gets their intersection points
340 * and store the result in @dest. Keep in mind two arcs can have
341 * up to 2 intersections.
343 * If @max is 0, the function returns 0 immediately without any
344 * further processing. If @arc and @arc2 are cohincident (same
345 * center and same radius), their intersections are not considered.
347 * <important>
348 * <title>TODO</title>
349 * <itemizedlist>
350 * <listitem>To be implemented...</listitem>
351 * </itemizedlist>
352 * </important>
354 * Return value: the number of intersections found (max 2)
355 * or 0 if the primitives do not intersect
358 cpml_arc_intersection(const CpmlPrimitive *arc, const CpmlPrimitive *arc2,
359 CpmlPair *dest, int max)
361 return 0;
365 * cpml_arc_intersection_with_line:
366 * @arc: an arc
367 * @line: a line
368 * @dest: a vector of #CpmlPair
369 * @max: maximum number of intersections to return
370 * (that is, the size of @dest)
372 * Given an @arc and a @line, gets their intersection points
373 * and store the result in @dest. Keep in mind an arc and a
374 * line can have up to 2 intersections.
376 * If @max is 0, the function returns 0 immediately without any
377 * further processing.
379 * <important>
380 * <title>TODO</title>
381 * <itemizedlist>
382 * <listitem>To be implemented...</listitem>
383 * </itemizedlist>
384 * </important>
386 * Return value: the number of intersections found (max 2)
387 * or 0 if the primitives do not intersect
390 cpml_arc_intersection_with_line(const CpmlPrimitive *arc,
391 const CpmlPrimitive *line,
392 CpmlPair *dest, int max)
394 return 0;
398 * cpml_arc_offset:
399 * @arc: the #CpmlPrimitive arc data
400 * @offset: distance for the computed parallel arc
402 * Given an @arc, this function computes the parallel arc at
403 * distance @offset. The three points needed to build the
404 * new arc are returned in the @arc data (substituting the
405 * previous ones.
407 void
408 cpml_arc_offset(CpmlPrimitive *arc, double offset)
410 CpmlPair p[3], center;
411 double r;
413 cpml_pair_from_cairo(&p[0], arc->org);
414 cpml_pair_from_cairo(&p[1], &arc->data[1]);
415 cpml_pair_from_cairo(&p[2], &arc->data[2]);
417 if (!get_center(p, &center))
418 return;
420 r = cpml_pair_distance(&p[0], &center) + offset;
422 /* Offset the three points by calculating their vector from the center,
423 * setting the new radius as length and readding the center */
424 cpml_pair_sub(&p[0], &center);
425 cpml_pair_sub(&p[1], &center);
426 cpml_pair_sub(&p[2], &center);
428 cpml_vector_set_length(&p[0], r);
429 cpml_vector_set_length(&p[1], r);
430 cpml_vector_set_length(&p[2], r);
432 cpml_pair_add(&p[0], &center);
433 cpml_pair_add(&p[1], &center);
434 cpml_pair_add(&p[2], &center);
436 cpml_pair_to_cairo(&p[0], arc->org);
437 cpml_pair_to_cairo(&p[1], &arc->data[1]);
438 cpml_pair_to_cairo(&p[2], &arc->data[2]);
442 * cpml_arc_to_cairo:
443 * @arc: the #CpmlPrimitive arc data
444 * @cr: the destination cairo context
446 * Renders @arc to the @cr cairo context. As cairo does not support
447 * arcs natively, it is approximated using one or more Bézier curves.
449 * The number of curves used is dependent from the angle of the arc.
450 * Anyway, this function uses internally the hardcoded %M_PI_2 value
451 * as threshold value. This means the maximum arc approximated by a
452 * single curve will be a quarter of a circle and, consequently, a
453 * whole circle will be approximated by 4 Bézier curves.
455 void
456 cpml_arc_to_cairo(const CpmlPrimitive *arc, cairo_t *cr)
458 CpmlPair center;
459 double r, start, end;
460 int n_curves;
461 double step, angle;
462 CpmlPrimitive curve;
463 cairo_path_data_t data[4];
465 if (!cpml_arc_info(arc, &center, &r, &start, &end))
466 return;
468 n_curves = ceil(fabs(end-start) / ARC_MAX_ANGLE);
469 step = (end-start) / (double) n_curves;
470 curve.data = data;
472 for (angle = start; n_curves--; angle += step) {
473 arc_to_curve(&curve, &center, r, angle, angle+step);
474 cairo_curve_to(cr,
475 curve.data[1].point.x, curve.data[1].point.y,
476 curve.data[2].point.x, curve.data[2].point.y,
477 curve.data[3].point.x, curve.data[3].point.y);
482 * cpml_arc_to_curves:
483 * @arc: the #CpmlPrimitive arc data
484 * @segment: the destination #CpmlSegment
485 * @n_curves: number of Bézier to use
487 * Converts @arc to a serie of @n_curves Bézier curves and puts them
488 * inside @segment. Obviously, @segment must have enough space to
489 * contain at least @n_curves curves.
491 * This function works in a similar way as cpml_arc_to_cairo() but
492 * has two important differences: it does not need a cairo context
493 * and the number of curves to be generated is explicitely defined.
494 * The latter difference allows a more specific error control from
495 * the application: in the file src/cairo-arc.c, found in the cairo
496 * tarball (at least in cairo-1.9.1), there is a table showing the
497 * magnitude of error of this curve approximation algorithm.
499 void
500 cpml_arc_to_curves(const CpmlPrimitive *arc, CpmlSegment *segment,
501 int n_curves)
503 CpmlPair center;
504 double r, start, end;
505 double step, angle;
506 CpmlPrimitive curve;
508 if (!cpml_arc_info(arc, &center, &r, &start, &end))
509 return;
511 step = (end-start) / (double) n_curves;
512 segment->num_data = n_curves*4;
513 curve.segment = segment;
514 curve.data = segment->data;
516 for (angle = start; n_curves--; angle += step) {
517 arc_to_curve(&curve, &center, r, angle, angle+step);
518 curve.data += 4;
523 static cairo_bool_t
524 get_center(const CpmlPair *p, CpmlPair *dest)
526 CpmlPair b, c;
527 double d, b2, c2;
529 /* When p[0] == p[2], p[0]..p[1] is considered the diameter of a circle */
530 if (p[0].x == p[2].x && p[0].y == p[2].y) {
531 dest->x = (p[0].x + p[1].x) / 2;
532 dest->y = (p[0].y + p[1].y) / 2;
533 return 1;
536 /* Translate the 3 points of -p0, to simplify the formula */
537 cpml_pair_sub(cpml_pair_copy(&b, &p[1]), &p[0]);
538 cpml_pair_sub(cpml_pair_copy(&c, &p[2]), &p[0]);
540 /* Check for division by 0, that is the case where the 3 given points
541 * are laying on a straight line and there is no fitting circle */
542 d = (b.x*c.y - b.y*c.x) * 2;
543 if (d == 0.)
544 return 0;
546 b2 = b.x*b.x + b.y*b.y;
547 c2 = c.x*c.x + c.y*c.y;
549 dest->x = (c.y*b2 - b.y*c2) / d + p[0].x;
550 dest->y = (b.x*c2 - c.x*b2) / d + p[0].y;
552 return 1;
555 static void
556 get_angles(const CpmlPair *p, const CpmlPair *center,
557 double *start, double *end)
559 CpmlVector vector;
560 double mid;
562 /* Calculate the starting angle */
563 cpml_pair_sub(cpml_pair_copy(&vector, &p[0]), center);
564 *start = cpml_vector_angle(&vector);
566 if (p[0].x == p[2].x && p[0].y == p[2].y) {
567 /* When p[0] and p[2] are cohincidents, p[0]..p[1] is the diameter
568 * of a circle: return by convention start=start end=start+2PI */
569 *end = *start + M_PI*2;
570 } else {
571 /* Calculate the mid and end angle */
572 cpml_pair_sub(cpml_pair_copy(&vector, &p[1]), center);
573 mid = cpml_vector_angle(&vector);
574 cpml_pair_sub(cpml_pair_copy(&vector, &p[2]), center);
575 *end = cpml_vector_angle(&vector);
577 if (*end > *start) {
578 if (mid > *end || mid < *start)
579 *start += M_PI*2;
580 } else {
581 if (mid < *end || mid > *start)
582 *end += M_PI*2;
587 static void
588 arc_to_curve(CpmlPrimitive *curve, const CpmlPair *center,
589 double r, double start, double end)
591 double r_sin1, r_cos1;
592 double r_sin2, r_cos2;
593 double h;
595 r_sin1 = r*sin(start);
596 r_cos1 = r*cos(start);
597 r_sin2 = r*sin(end);
598 r_cos2 = r*cos(end);
600 h = 4./3. * tan((end-start) / 4.);
602 curve->data[0].header.type = CAIRO_PATH_CURVE_TO;
603 curve->data[0].header.length = 4;
604 curve->data[1].point.x = center->x + r_cos1 - h*r_sin1;
605 curve->data[1].point.y = center->y + r_sin1 + h*r_cos1;
606 curve->data[2].point.x = center->x + r_cos2 + h*r_sin2;
607 curve->data[2].point.y = center->y + r_sin2 - h*r_cos2;
608 curve->data[3].point.x = center->x + r_cos2;
609 curve->data[3].point.y = center->y + r_sin2;