[docs] Specifying when the returned value is const
[adg.git] / cpml / cpml-curve.c
blob45fc364d550202b8ab35de4c2906846d84c09386
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:cpml-curve
22 * @Section_Id:CpmlCurve
23 * @title: CpmlCurve
24 * @short_description: Bézier cubic curve primitive management
26 * The following functions manipulate %CAIRO_PATH_CURVE_TO #CpmlPrimitive.
27 * No validation is made on the input so use the following methods
28 * only when you are sure the <varname>primitive</varname> argument
29 * is effectively a cubic Bézier curve.
30 **/
33 #include "cpml-curve.h"
34 #include "cpml-pair.h"
37 /**
38 * cpml_curve_type_get_npoints:
40 * Returns the number of point needed to properly specify a curve primitive.
42 * Return value: 4
43 **/
44 int
45 cpml_curve_type_get_npoints(void)
47 return 4;
50 /**
51 * cpml_curve_length:
52 * @curve: the #CpmlPrimitive curve data
54 * Given the @curve primitive, returns the approximated length of
55 * the Bézier curve.
57 * <important>
58 * <title>TODO</title>
59 * <itemizedlist>
60 * <listitem>To be implemented...</listitem>
61 * </itemizedlist>
62 * </important>
64 * Return value: the requested length
65 **/
66 double
67 cpml_curve_length(const CpmlPrimitive *curve)
69 return 0.;
72 /**
73 * cpml_curve_extents:
74 * @curve: the #CpmlPrimitive curve data
75 * @extents: where to store the extents
77 * Given a @curve primitive, returns its boundary box in @extents.
78 **/
79 void
80 cpml_curve_extents(const CpmlPrimitive *curve, CpmlExtents *extents)
82 CpmlPair p1, p2, p3, p4;
84 extents->is_defined = 0;
86 cpml_pair_from_cairo(&p1, cpml_primitive_get_point(curve, 0));
87 cpml_pair_from_cairo(&p2, cpml_primitive_get_point(curve, 1));
88 cpml_pair_from_cairo(&p3, cpml_primitive_get_point(curve, 2));
89 cpml_pair_from_cairo(&p4, cpml_primitive_get_point(curve, 3));
91 cpml_extents_pair_add(extents, &p1);
92 cpml_extents_pair_add(extents, &p2);
93 cpml_extents_pair_add(extents, &p3);
94 cpml_extents_pair_add(extents, &p4);
97 /**
98 * cpml_curve_pair_at_time:
99 * @curve: the #CpmlPrimitive curve data
100 * @pair: the destination pair
101 * @t: the "time" value
103 * Given the @curve Bézier cubic, finds the coordinates at time @t
104 * (where 0 is the start and 1 is the end) and stores the result
105 * in @pair. Keep in mind @t is not homogeneous, so 0.5 does not
106 * necessarily means the mid point.
108 * The relation 0 < @t < 1 must be satisfied, as interpolating on
109 * cubic curves is not allowed.
111 void
112 cpml_curve_pair_at_time(const CpmlPrimitive *curve, CpmlPair *pair, double t)
114 cairo_path_data_t *p1, *p2, *p3, *p4;
115 double t_2, t_3, t1, t1_2, t1_3;
117 p1 = cpml_primitive_get_point(curve, 0);
118 p2 = cpml_primitive_get_point(curve, 1);
119 p3 = cpml_primitive_get_point(curve, 2);
120 p4 = cpml_primitive_get_point(curve, 3);
122 t_2 = t * t;
123 t_3 = t_2 * t;
124 t1 = 1 - t;
125 t1_2 = t1 * t1;
126 t1_3 = t1_2 * t1;
128 pair->x = t1_3 * p1->point.x + 3 * t1_2 * t * p2->point.x
129 + 3 * t1 * t_2 * p3->point.x + t_3 * p4->point.x;
130 pair->y = t1_3 * p1->point.y + 3 * t1_2 * t * p2->point.y
131 + 3 * t1 * t_2 * p3->point.y + t_3 * p4->point.y;
135 * cpml_curve_pair_at:
136 * @curve: the #CpmlPrimitive curve data
137 * @pair: the destination #CpmlPair
138 * @pos: the position value
140 * Given the @curve Bézier cubic, finds the coordinates at position
141 * @pos (where 0 is the start and 1 is the end) and stores the result
142 * in @pair. It is similar to cpml_curve_pair_at_time() but the @pos
143 * value is evenly distribuited, that is 0.5 is exactly the mid point.
144 * If you do not need this feature, use cpml_curve_pair_at_time()
145 * as it is considerable faster.
147 * The relation 0 < @pos < 1 must be satisfied, as interpolating on
148 * cubic curves is not allowed.
150 * <important>
151 * <title>TODO</title>
152 * <itemizedlist>
153 * <listitem>To be implemented...</listitem>
154 * </itemizedlist>
155 * </important>
157 void
158 cpml_curve_pair_at(const CpmlPrimitive *curve, CpmlPair *pair, double pos)
163 * cpml_curve_vector_at_time:
164 * @curve: the #CpmlPrimitive curve data
165 * @vector: the destination vector
166 * @t: the "time" value
168 * Given the @curve Bézier cubic, finds the slope at time @t
169 * (where 0 is the start and 1 is the end) and stores the result
170 * in @vector. Keep in mind @t is not homogeneous, so 0.5
171 * does not necessarily means the mid point.
173 * @t must be inside the range 0 .. 1, as interpolating is not
174 * allowed.
176 void
177 cpml_curve_vector_at_time(const CpmlPrimitive *curve,
178 CpmlVector *vector, double t)
180 cairo_path_data_t *p1, *p2, *p3, *p4;
181 CpmlPair p21, p32, p43;
182 double t1, t1_2, t_2;
184 p1 = cpml_primitive_get_point(curve, 0);
185 p2 = cpml_primitive_get_point(curve, 1);
186 p3 = cpml_primitive_get_point(curve, 2);
187 p4 = cpml_primitive_get_point(curve, 3);
189 p21.x = p2->point.x - p1->point.x;
190 p21.y = p2->point.y - p1->point.y;
191 p32.x = p3->point.x - p2->point.x;
192 p32.y = p3->point.y - p2->point.y;
193 p43.x = p4->point.x - p3->point.x;
194 p43.y = p4->point.y - p3->point.y;
196 t1 = 1 - t;
197 t1_2 = t1 * t1;
198 t_2 = t * t;
200 vector->x = 3 * t1_2 * p21.x + 6 * t1 * t * p32.x + 3 * t_2 * p43.x;
201 vector->y = 3 * t1_2 * p21.y + 6 * t1 * t * p32.y + 3 * t_2 * p43.y;
205 * cpml_curve_vector_at:
206 * @curve: the #CpmlPrimitive curve data
207 * @vector: the destination vector
208 * @pos: the position value
210 * Given the @curve Bézier cubic, finds the slope at position @pos
211 * (where 0 is the start and 1 is the end) and stores the result
212 * in @vector. It is similar to cpml_curve_vector_at_time() but the
213 * @pos value is evenly distribuited, that is 0.5 is exactly the
214 * mid point. If you do not need this feature, use
215 * cpml_curve_vector_at_time() as it is considerable faster.
217 * @pos must be inside the range 0 .. 1, as interpolating is not
218 * allowed.
220 * <important>
221 * <title>TODO</title>
222 * <itemizedlist>
223 * <listitem>To be implemented...</listitem>
224 * </itemizedlist>
225 * </important>
227 void
228 cpml_curve_vector_at(const CpmlPrimitive *curve,
229 CpmlVector *vector, double pos)
234 * cpml_curve_near_pos:
235 * @curve: the #CpmlPrimitive curve data
236 * @pair: the coordinates of the subject point
238 * Returns the pos value of the point on @curve nearest to @pair.
239 * The returned value is always between 0 and 1.
241 * <important>
242 * <title>TODO</title>
243 * <itemizedlist>
244 * <listitem>To be implemented...</listitem>
245 * </itemizedlist>
246 * </important>
248 * Return value: the pos value, always between 0 and 1
250 double
251 cpml_curve_near_pos(const CpmlPrimitive *curve, const CpmlPair *pair)
253 /* TODO */
255 return 0;
259 * cpml_curve_intersection:
260 * @curve: the first curve
261 * @curve2: the second curve
262 * @dest: a vector of #CpmlPair
263 * @max: maximum number of intersections to return
264 * (that is, the size of @dest)
266 * Given two Bézier cubic curves (@curve and @curve2), gets their
267 * intersection points and store the result in @dest. Because two
268 * curves can have 4 intersections, @dest MUST be at least an array
269 * of 4 #CpmlPair.
271 * If @max is 0, the function returns 0 immediately without any
272 * further processing. If @curve and @curve2 are cohincident,
273 * their intersections are not considered.
275 * <important>
276 * <title>TODO</title>
277 * <itemizedlist>
278 * <listitem>To be implemented...</listitem>
279 * </itemizedlist>
280 * </important>
282 * Return value: the number of intersections found (max 4)
283 * or 0 if the primitives do not intersect
286 cpml_curve_intersection(const CpmlPrimitive *curve,
287 const CpmlPrimitive *curve2,
288 CpmlPair *dest, int max)
290 return 0;
294 * cpml_curve_intersection_with_arc:
295 * @curve: a curve
296 * @arc: an arc
297 * @dest: a vector of #CpmlPair
298 * @max: maximum number of intersections to return
299 * (that is, the size of @dest)
301 * Given a Bézier cubic @curve and an @arc, gets their intersection
302 * points and store the result in @dest. Because an arc and a cubic
303 * curve can have up to 4 intersections, @dest MUST be at least an
304 * array of 4 #CpmlPair.
306 * If @max is 0, the function returns 0 immediately without any
307 * further processing.
309 * <important>
310 * <title>TODO</title>
311 * <itemizedlist>
312 * <listitem>To be implemented...</listitem>
313 * </itemizedlist>
314 * </important>
316 * Return value: the number of intersections found (max 4)
317 * or 0 if the primitives do not intersect
320 cpml_curve_intersection_with_arc(const CpmlPrimitive *curve,
321 const CpmlPrimitive *arc,
322 CpmlPair *dest, int max)
324 return 0;
328 * cpml_curve_intersection_with_line:
329 * @curve: a curve
330 * @line: a line
331 * @dest: a vector of #CpmlPair
332 * @max: maximum number of intersections to return
333 * (that is, the size of @dest)
335 * Given a Bézier cubic @curve and a @line, gets their intersection
336 * points and store the result in @dest. Because a line and a cubic
337 * curve can have up to 4 intersections, @dest MUST be at least an
338 * array of 4 #CpmlPair.
340 * If @max is 0, the function returns 0 immediately without any
341 * further processing.
343 * <important>
344 * <title>TODO</title>
345 * <itemizedlist>
346 * <listitem>To be implemented...</listitem>
347 * </itemizedlist>
348 * </important>
350 * Return value: the number of intersections found (max 4)
351 * or 0 if the primitives do not intersect
354 cpml_curve_intersection_with_line(const CpmlPrimitive *curve,
355 const CpmlPrimitive *line,
356 CpmlPair *dest, int max)
358 return 0;
362 * cpml_curve_offset:
363 * @curve: the #CpmlPrimitive curve data
364 * @offset: distance for the computed parallel curve
366 * Given a cubic Bézier primitive in @curve, this function finds
367 * the approximated Bézier curve parallel to @curve at distance
368 * @offset (an offset curve). The four points needed to build the
369 * new curve are returned in the @curve struct.
371 * To solve the offset problem, a custom algorithm is used. First, the
372 * resulting curve MUST have the same slope at the start and end point.
373 * These constraints are not sufficient to resolve the system, so I let
374 * the curve pass thought a given point (pm, known and got from the
375 * original curve) at a given time (m, now hardcoded to 0.5).
377 * Firstly, I define some useful variables:
379 * v0 = unitvector(p[1]-p[0]) * offset;
380 * v3 = unitvector(p[3]-p[2]) * offset;
381 * p0 = p[0] + normal v0;
382 * p3 = p[3] + normal v3.
384 * Now I want the curve to have the specified slopes at the start
385 * and end point. Forcing the same slope at the start point means:
387 * p1 = p0 + k0 v0.
389 * where k0 is an arbitrary factor. Decomposing for x and y components:
391 * p1.x = p0.x + k0 v0.x;
392 * p1.y = p0.y + k0 v0.y.
394 * Doing the same for the end point gives:
396 * p2.x = p3.x + k3 v3.x;
397 * p2.y = p3.y + k3 v3.y.
399 * Now I interpolate the curve by forcing it to pass throught pm
400 * when "time" is m, where 0 < m < 1. The cubic Bézier function is:
402 * C(t) = (1-t)³p0 + 3t(1-t)²p1 + 3t²(1-t)p2 + t³p3.
404 * and forcing t=m and C(t) = pm:
406 * pm = (1-m)³p0 + 3m(1-m)²p1 + 3m²(1-m)p2 + m³p3.
408 * (1-m) p1 + m p2 = (pm - (1-m)³p0 - m³p3) / (3m (1-m)).
410 * So the final system is:
412 * p1.x = p0.x + k0 v0.x;
413 * p1.y = p0.y + k0 v0.y;
414 * p2.x = p3.x + k3 v3.x;
415 * p2.y = p3.y + k3 v3.y;
416 * (1-m) p1.x + m p2.x = (pm.x - (1-m)³p0.x - m³p3.x) / (3m (1-m));
417 * (1-m) p1.y + m p2.y = (pm.y - (1-m)³p0.y - m³p3.y) / (3m (1-m)).
419 * Substituting and resolving for k0 and k3:
421 * (1-m) k0 v0.x + m k3 v3.x =
422 * (pm.x - (1-m)³p0.x - m³p3.x) / (3m (1-m)) - (1-m) p0.x - m p3.x;
423 * (1-m) k0 v0.y + m k3 v3.y =
424 * (pm.y - (1-m)³p0.y - m³p3.y) / (3m (1-m)) - (1-m) p0.y - m p3.y.
426 * (1-m) k0 v0.x + m k3 v3.x =
427 * (pm.x - (1-m)²(1+2m) p0.x - m²(3-2m) p3.x) / (3m (1-m));
428 * (1-m) k0 v0.y + m k3 v3.y =
429 * (pm.y - (1-m)²(1+2m) p0.y - m²(3-2m) p3.y) / (3m (1-m)).
431 * Let:
433 * pk = (pm - (1-m)²(1+2m) p0 - m²(3-2m) p3) / (3m (1-m)).
435 * gives the following system:
437 * (1-m) k0 v0.x + m k3 v3.x = pk.x;
438 * (1-m) k0 v0.y + m k3 v3.y = pk.y.
440 * Now I should avoid division by 0 troubles. If either v0.x and v3.x
441 * are 0, the first equation will be inconsistent. More in general the
442 * v0.x*v3.y = v3.x*v3.y condition should be avoided. This is the first
443 * case to check, in which case an alternative approach is used. In the
444 * other cases the above system can be used.
446 * If v0.x != 0 I can resolve for k0 and then find k3:
448 * k0 = (pk.x - m k3 v3.x) / ((1-m) v0.x);
449 * (pk.x - m k3 v3.x) v0.y / v0.x + m k3 v3.y = pk.y.
451 * k0 = (pk.x - m k3 v3.x) / ((1-m) v0.x);
452 * k3 m (v3.y - v3.x v0.y / v0.x) = pk.y - pk.x v0.y / v0.x.
454 * k3 = (pk.y - pk.x v0.y / v0.x) / (m (v3.y - v3.x v0.y / v0.x));
455 * k0 = (pk.x - m k3 v3.x) / ((1-m) v0.x).
457 * If v3.x != 0 I can resolve for k3 and then find k0:
459 * k3 = (pk.x - (1-m) k0 v0.x) / (m v3.x);
460 * (1-m) k0 v0.y + (pk.x - (1-m) k0 v0.x) v3.y / v3.x = pk.y.
462 * k3 = (pk.x - (1-m) k0 v0.x) / (m v3.x);
463 * k0 (1-m) (v0.y - k0 v0.x v3.y / v3.x) = pk.y - pk.x v3.y / v3.x.
465 * k0 = (pk.y - pk.x v3.y / v3.x) / ((1-m) (v0.y - v0.x v3.y / v3.x));
466 * k3 = (pk.x - (1-m) k0 v0.x) / (m v3.x).
468 * <important>
469 * <title>TODO</title>
470 * <itemizedlist>
471 * <listitem>By default, interpolation of the new curve is made by offseting
472 * the mid point: use a better candidate.</listitem>
473 * <listitem>When the equations are inconsistent, the alternative approach
474 * performs very bad if <varname>v0</varname> and
475 * <varname>v3</varname> are opposite or staggered.</listitem>
476 * </itemizedlist>
477 * </important>
479 void
480 cpml_curve_offset(CpmlPrimitive *curve, double offset)
482 double m, mm;
483 CpmlVector v0, v3, vm, vtmp;
484 CpmlPair p0, p1, p2, p3, pm;
486 m = 0.5;
487 mm = 1-m;
489 /* Firstly, convert the curve points from cairo format to cpml format
490 * and store them (temporary) in p0..p3 */
491 cpml_pair_from_cairo(&p0, curve->org);
492 cpml_pair_from_cairo(&p1, &curve->data[1]);
493 cpml_pair_from_cairo(&p2, &curve->data[2]);
494 cpml_pair_from_cairo(&p3, &curve->data[3]);
496 /* v0 = p1-p0 */
497 cpml_pair_sub(cpml_pair_copy(&v0, &p1), &p0);
499 /* v3 = p3-p2 */
500 cpml_pair_sub(cpml_pair_copy(&v3, &p3), &p2);
502 /* pm = point in C(m) offseted the requested @offset distance */
503 cpml_curve_vector_at_time(curve, &vm, m);
504 cpml_vector_set_length(&vm, offset);
505 cpml_vector_normal(&vm);
506 cpml_curve_pair_at_time(curve, &pm, m);
507 cpml_pair_add(&pm, &vm);
509 /* p0 = p0 + normal of v0 of @offset magnitude (exact value) */
510 cpml_vector_set_length(cpml_pair_copy(&vtmp, &v0), offset);
511 cpml_vector_normal(&vtmp);
512 cpml_pair_add(&p0, &vtmp);
514 /* p3 = p3 + normal of v3 of @offset magnitude, as done for p0 */
515 cpml_vector_set_length(cpml_pair_copy(&vtmp, &v3), offset);
516 cpml_vector_normal(&vtmp);
517 cpml_pair_add(&p3, &vtmp);
519 if (v0.x*v3.y == v3.x*v0.y) {
520 /* Inconsistent equations: use the alternative approach */
521 p1.x = p0.x + v0.x + vm.x * 4/3;
522 p1.y = p0.y + v0.y + vm.y * 4/3;
523 p2.x = p3.x - v3.x + vm.x * 4/3;
524 p2.y = p3.y - v3.y + vm.y * 4/3;
525 } else {
526 CpmlPair pk;
527 double k0, k3;
529 pk.x = (pm.x - mm*mm*(1+m+m)*p0.x - m*m*(1+mm+mm)*p3.x) / (3*m*(1-m));
530 pk.y = (pm.y - mm*mm*(1+m+m)*p0.y - m*m*(1+mm+mm)*p3.y) / (3*m*(1-m));
532 if (v0.x != 0) {
533 k3 = (pk.y - pk.x*v0.y / v0.x) / (m*(v3.y - v3.x*v0.y / v0.x));
534 k0 = (pk.x - m*k3*v3.x) / (mm*v0.x);
535 } else {
536 k0 = (pk.y - pk.x*v3.y / v3.x) / (mm*(v0.y - v0.x*v3.y / v3.x));
537 k3 = (pk.x - mm*k0*v0.x) / (m*v3.x);
540 p1.x = p0.x + k0*v0.x;
541 p1.y = p0.y + k0*v0.y;
542 p2.x = p3.x + k3*v3.x;
543 p2.y = p3.y + k3*v3.y;
546 /* Return the new curve in the original array */
547 cpml_pair_to_cairo(&p0, curve->org);
548 cpml_pair_to_cairo(&p1, &curve->data[1]);
549 cpml_pair_to_cairo(&p2, &curve->data[2]);
550 cpml_pair_to_cairo(&p3, &curve->data[3]);