[CpmlArc] Implemented cpml_arc_pair_at()
[adg.git] / cpml / cpml-arc.c
blobce4b2ddd43ab2d87915b0ee99360c48326cf4fbe
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.
20 /**
21 * SECTION:arc
22 * @title: Circular arcs
23 * @short_description: Functions for manipulating circular arcs
25 * The following functions manipulate %CAIRO_PATH_ARC_TO #CpmlPrimitive.
26 * No check is made on the primitive struct, so be sure
27 * <structname>CpmlPrimitive</structname> is effectively an arc
28 * before calling these APIs.
30 * An arc is not a native cairo primitive and should be treated specially.
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 "direction" 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
41 * as the segment between the first point and the intermediate point.
42 **/
44 #include "cpml-arc.h"
45 #include "cpml-pair.h"
47 #include <stdlib.h>
48 #include <math.h>
51 /* Hardcoded max angle of the arc to be approximated by a Bézier curve:
52 * this influence the arc quality (the default value is got from cairo) */
53 #define ARC_MAX_ANGLE M_PI_2
56 static cairo_bool_t get_center (const CpmlPair *p,
57 CpmlPair *dest);
58 static void get_angles (const CpmlPair *p,
59 const CpmlPair *center,
60 CpmlPair *angles);
61 static void arc_to_curve (CpmlPrimitive *curve,
62 const CpmlPair *center,
63 double r,
64 double start,
65 double end);
68 /**
69 * cpml_arc_type_get_npoints:
71 * Returns the number of point needed to properly specify an arc primitive.
73 * Return value: 3
74 **/
75 int
76 cpml_arc_type_get_npoints(void)
78 return 3;
81 /**
82 * cpml_arc_info:
83 * @arc: the #CpmlPrimitive arc data
84 * @center: where to store the center coordinates (can be %NULL)
85 * @r: where to store the radius (can be %NULL)
86 * @start: where to store the starting angle (can be %NULL)
87 * @end: where to store the ending angle (can be %NULL)
89 * Given an @arc, this function calculates and returns its basic data.
90 * Any pointer can be %NULL, in which case the requested info is not
91 * returned. This function can fail (when the three points lay on a
92 * straight line, for example) in which case 0 is returned and no
93 * data can be considered valid.
95 * The radius @r can be 0 when the three points are coincidents: a
96 * circle with radius 0 is considered a valid path.
98 * When the start and end angle are returned, together with their
99 * values these angles implicitely gives another important information:
100 * the arc direction.
102 * If @start < @end the arc must be rendered with increasing angle
103 * value (clockwise direction using the ordinary cairo coordinate
104 * system) while if @start > @end the arc must be rendered in reverse
105 * order (that is counterclockwise in the cairo world). This is the
106 * reason the angle values are returned in the range
107 * { -M_PI < value < 3*M_PI } inclusive instead of the usual
108 * { -M_PI < value < M_PI } range.
110 * Return value: 1 if the function worked succesfully, 0 on errors
112 cairo_bool_t
113 cpml_arc_info(const CpmlPrimitive *arc, CpmlPair *center,
114 double *r, double *start, double *end)
116 CpmlPair p[3], l_center;
118 cpml_pair_from_cairo(&p[0], arc->org);
119 cpml_pair_from_cairo(&p[1], &arc->data[1]);
120 cpml_pair_from_cairo(&p[2], &arc->data[2]);
122 if (!get_center(p, &l_center))
123 return 0;
125 if (center)
126 *center = l_center;
128 if (r != NULL)
129 *r = cpml_pair_distance(&p[0], &l_center);
131 if (start != NULL || end != NULL) {
132 CpmlPair angles;
134 get_angles(p, &l_center, &angles);
136 if (start != NULL)
137 *start = angles.x;
138 if (end != NULL)
139 *end = angles.y;
142 return 1;
146 * cpml_arc_pair_at:
147 * @arc: the #CpmlPrimitive arc data
148 * @pair: the destination #CpmlPair
149 * @pos: the position value
151 * Given an @arc, finds the coordinates at position @pos (where 0 is
152 * the start and 1 is the end) and stores the result in @pair.
154 * @pos can also be outside the 0..1 limit, as interpolating on an
155 * arc is quite trivial.
157 void
158 cpml_arc_pair_at(const CpmlPrimitive *arc, CpmlPair *pair, double pos)
160 if (pos == 0.) {
161 cpml_pair_from_cairo(pair, arc->org);
162 } else if (pos == 1.) {
163 cpml_pair_from_cairo(pair, &arc->data[2]);
164 } else {
165 CpmlPair center;
166 double r, start, end;
167 double angle;
169 if (!cpml_arc_info(arc, &center, &r, &start, &end))
170 return;
172 angle = (end-start)*pos + start;
173 cpml_vector_from_angle(pair, angle, r);
174 cpml_pair_add(pair, &center);
179 * cpml_arc_vector_at:
180 * @arc: the #CpmlPrimitive arc data
181 * @vector: the destination vector
182 * @pos: the position value
184 * Given an @arc, finds the slope at position @pos (where 0 is
185 * the start and 1 is the end) and stores the result in @vector.
187 * <important>
188 * <title>TODO</title>
189 * <itemizedlist>
190 * <listitem>To be implemented...</listitem>
191 * </itemizedlist>
192 * </important>
194 void
195 cpml_arc_vector_at(const CpmlPrimitive *arc, CpmlVector *vector, double pos)
200 * cpml_arc_intersection:
201 * @arc: the first arc
202 * @arc2: the second arc
203 * @dest: a vector of at least 2 #CpmlPair
205 * Given two arcs (@arc and @arc2), gets their intersection points
206 * and store the result in @dest. Because two arcs can have
207 * 2 intersections, @dest MUST be at least an array of 2 #CpmlPair.
209 * <important>
210 * <title>TODO</title>
211 * <itemizedlist>
212 * <listitem>To be implemented...</listitem>
213 * </itemizedlist>
214 * </important>
216 * Return value: the number of intersections (max 2)
219 cpml_arc_intersection(const CpmlPrimitive *arc,
220 const CpmlPrimitive *arc2, CpmlPair *dest)
222 return 0;
226 * cpml_arc_intersection_with_line:
227 * @arc: an arc
228 * @line: a line
229 * @dest: a vector of at least 2 #CpmlPair
231 * Given an @arc and a @line, gets their intersection points
232 * and store the result in @dest. Because an arc and a line
233 * can have up to 2 intersections, @dest MUST be at least an
234 * array of 2 #CpmlPair.
236 * <important>
237 * <title>TODO</title>
238 * <itemizedlist>
239 * <listitem>To be implemented...</listitem>
240 * </itemizedlist>
241 * </important>
243 * Return value: the number of intersections (max 2)
246 cpml_arc_intersection_with_line(const CpmlPrimitive *arc,
247 const CpmlPrimitive *line, CpmlPair *dest)
249 return 0;
253 * cpml_arc_offset:
254 * @arc: the #CpmlPrimitive arc data
255 * @offset: distance for the computed parallel arc
257 * Given an @arc, this function computes the parallel arc at
258 * distance @offset. The three points needed to build the
259 * new arc are returned in the @arc data (substituting the
260 * previous ones.
262 void
263 cpml_arc_offset(CpmlPrimitive *arc, double offset)
265 CpmlPair p[3], center;
266 double r;
268 cpml_pair_from_cairo(&p[0], arc->org);
269 cpml_pair_from_cairo(&p[1], &arc->data[1]);
270 cpml_pair_from_cairo(&p[2], &arc->data[2]);
272 if (!get_center(p, &center))
273 return;
275 r = cpml_pair_distance(&p[0], &center) + offset;
277 /* Offset the three points by calculating their vector from the center,
278 * setting the new radius as length and readding the center */
279 cpml_pair_sub(&p[0], &center);
280 cpml_pair_sub(&p[1], &center);
281 cpml_pair_sub(&p[2], &center);
283 cpml_vector_set_length(&p[0], r);
284 cpml_vector_set_length(&p[1], r);
285 cpml_vector_set_length(&p[2], r);
287 cpml_pair_add(&p[0], &center);
288 cpml_pair_add(&p[1], &center);
289 cpml_pair_add(&p[2], &center);
291 cpml_pair_to_cairo(&p[0], arc->org);
292 cpml_pair_to_cairo(&p[1], &arc->data[1]);
293 cpml_pair_to_cairo(&p[2], &arc->data[2]);
297 * cpml_arc_to_cairo:
298 * @arc: the #CpmlPrimitive arc data
299 * @cr: the destination cairo context
301 * Renders @arc to the @cr cairo context. As cairo does not support
302 * arcs natively, it is approximated using one or more Bézier curves.
304 * The number of curves used is dependent from the angle of the arc.
305 * Anyway, this function uses internally the hardcoded %M_PI_2 value
306 * as threshold value. This means the maximum arc approximated by a
307 * single curve will be a quarter of a circle and, consequently, a
308 * whole circle will be approximated by 4 Bézier curves.
310 void
311 cpml_arc_to_cairo(const CpmlPrimitive *arc, cairo_t *cr)
313 CpmlPair center;
314 double r, start, end;
315 int segments;
316 double step, angle;
317 CpmlPrimitive curve;
318 cairo_path_data_t data[4];
320 if (!cpml_arc_info(arc, &center, &r, &start, &end))
321 return;
323 segments = ceil(fabs(end-start) / ARC_MAX_ANGLE);
324 step = (end-start) / (double) segments;
325 curve.data = data;
327 for (angle = start; segments--; angle += step) {
328 arc_to_curve(&curve, &center, r, angle, angle+step);
329 cairo_curve_to(cr,
330 curve.data[1].point.x, curve.data[1].point.y,
331 curve.data[2].point.x, curve.data[2].point.y,
332 curve.data[3].point.x, curve.data[3].point.y);
337 static cairo_bool_t
338 get_center(const CpmlPair *p, CpmlPair *dest)
340 CpmlPair div;
341 double p2[3];
343 /* When p[0] == p[2], p[0]..p[1] is considered the diameter of a circle */
344 if (p[0].x == p[2].x && p[0].y == p[2].y) {
345 dest->x = (p[0].x + p[1].x) / 2;
346 dest->y = (p[0].y + p[1].y) / 2;
347 return 1;
350 div.x = (p[0].x*(p[2].y-p[1].y) +
351 p[1].x*(p[0].y-p[2].y) +
352 p[1].x*(p[1].y-p[0].y)) * 2;
353 div.y = (p[0].y*(p[2].x-p[1].x) +
354 p[1].y*(p[0].x-p[2].x) +
355 p[2].y*(p[1].x-p[0].x)) * 2;
357 /* Check for division by 0, that is the case where the 3 given points
358 * are laying on a straight line) */
359 if (div.x == 0. || div.y == 0.)
360 return 0;
362 p2[0] = p[0].x*p[0].x + p[0].y*p[0].y;
363 p2[1] = p[1].x*p[1].x + p[1].y*p[1].y;
364 p2[2] = p[2].x*p[2].x + p[2].y*p[2].y;
366 /* Compute the center of the arc */
367 dest->x = (p2[0] * (p[2].y - p[1].y) +
368 p2[1] * (p[0].y - p[2].y) +
369 p2[2] * (p[1].y - p[0].y)) / div.x;
370 dest->y = (p2[0] * (p[2].x - p[1].x) +
371 p2[1] * (p[0].x - p[2].x) +
372 p2[2] * (p[1].x - p[0].x)) / div.y;
374 return 1;
377 static void
378 get_angles(const CpmlPair *p, const CpmlPair *center, CpmlPair *angles)
380 CpmlVector vector;
381 double start, mid, end;
383 /* Calculate the starting angle */
384 cpml_pair_sub(cpml_pair_copy(&vector, &p[0]), center);
385 start = cpml_vector_angle(&vector);
387 if (p[0].x == p[2].x && p[0].y == p[2].y) {
388 /* When p[0] and p[2] are cohincidents, p[0]..p[1] is the diameter
389 * of a circle: return by convention start=start end=start+2PI */
390 end = start + M_PI*2;
391 } else {
392 /* Calculate the mid and end angle */
393 cpml_pair_sub(cpml_pair_copy(&vector, &p[1]), center);
394 mid = cpml_vector_angle(&vector);
395 cpml_pair_sub(cpml_pair_copy(&vector, &p[2]), center);
396 end = cpml_vector_angle(&vector);
398 if (end > start) {
399 if (mid > end || mid < start)
400 start += M_PI*2;
401 } else {
402 if (mid < end || mid > start)
403 end += M_PI*2;
407 angles->x = start;
408 angles->y = end;
411 static void
412 arc_to_curve(CpmlPrimitive *curve, const CpmlPair *center,
413 double r, double start, double end)
415 double r_sin1, r_cos1;
416 double r_sin2, r_cos2;
417 double h;
419 r_sin1 = r*sin(start);
420 r_cos1 = r*cos(start);
421 r_sin2 = r*sin(end);
422 r_cos2 = r*cos(end);
424 h = 4./3. * tan((end-start) / 4.);
426 curve->data[0].header.type = CAIRO_PATH_CURVE_TO;
427 curve->data[0].header.length = 4;
428 curve->data[1].point.x = center->x + r_cos1 - h*r_sin1;
429 curve->data[1].point.y = center->y + r_sin1 + h*r_cos1;
430 curve->data[2].point.x = center->x + r_cos2 + h*r_sin2;
431 curve->data[2].point.y = center->y + r_sin2 - h*r_cos2;
432 curve->data[3].point.x = center->x + r_cos2;
433 curve->data[3].point.y = center->y + r_sin2;