[CPML] Implemented put_extents() as a virtual method
[adg.git] / cpml / cpml-curve.c
blobc78e488b3e18c55de65c7cd3ada55bd2d42960b2
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.
31 * <important>
32 * <title>TODO</title>
33 * <itemizedlist>
34 * <listitem>the get_length() method must be implemented;</listitem>
35 * <listitem>actually the put_extents() method is implemented by computing
36 * the bounding box of the control polygon and this will likely
37 * include some empty space: there is room for improvements;</listitem>
38 * </itemizedlist>
39 * </important>
40 **/
43 #include "cpml-internal.h"
44 #include "cpml-extents.h"
45 #include "cpml-segment.h"
46 #include "cpml-primitive.h"
47 #include "cpml-primitive-private.h"
48 #include "cpml-curve.h"
51 static void put_extents (const CpmlPrimitive *curve,
52 CpmlExtents *extents);
55 const _CpmlPrimitiveClass *
56 _cpml_curve_get_class(void)
58 static _CpmlPrimitiveClass *p_class = NULL;
60 if (p_class == NULL) {
61 static _CpmlPrimitiveClass class_data = {
62 "curve", 4,
63 NULL,
64 put_extents,
65 NULL,
66 NULL,
67 NULL,
68 NULL,
69 NULL,
70 NULL
72 p_class = &class_data;
75 return p_class;
79 /**
80 * cpml_curve_put_pair_at_time:
81 * @curve: the #CpmlPrimitive curve data
82 * @t: the "time" value
83 * @pair: the destination pair
85 * Given the @curve Bézier cubic, finds the coordinates at time @t
86 * (where 0 is the start and 1 is the end) and stores the result
87 * in @pair. Keep in mind @t is not homogeneous, so 0.5 does not
88 * necessarily means the mid point.
90 * The relation 0 < @t < 1 must be satisfied, as interpolating on
91 * cubic curves is not allowed.
92 **/
93 void
94 cpml_curve_put_pair_at_time(const CpmlPrimitive *curve, double t,
95 CpmlPair *pair)
97 cairo_path_data_t *p1, *p2, *p3, *p4;
98 double t_2, t_3, t1, t1_2, t1_3;
100 p1 = cpml_primitive_get_point(curve, 0);
101 p2 = cpml_primitive_get_point(curve, 1);
102 p3 = cpml_primitive_get_point(curve, 2);
103 p4 = cpml_primitive_get_point(curve, 3);
105 t_2 = t * t;
106 t_3 = t_2 * t;
107 t1 = 1 - t;
108 t1_2 = t1 * t1;
109 t1_3 = t1_2 * t1;
111 pair->x = t1_3 * p1->point.x + 3 * t1_2 * t * p2->point.x
112 + 3 * t1 * t_2 * p3->point.x + t_3 * p4->point.x;
113 pair->y = t1_3 * p1->point.y + 3 * t1_2 * t * p2->point.y
114 + 3 * t1 * t_2 * p3->point.y + t_3 * p4->point.y;
118 * cpml_curve_put_pair_at:
119 * @curve: the #CpmlPrimitive curve data
120 * @pos: the position value
121 * @pair: the destination #CpmlPair
123 * Given the @curve Bézier cubic, finds the coordinates at position
124 * @pos (where 0 is the start and 1 is the end) and stores the result
125 * in @pair. It is similar to cpml_curve_put_pair_at_time() but the @pos
126 * value is evenly distribuited, that is 0.5 is exactly the mid point.
127 * If you do not need this feature, use cpml_curve_put_pair_at_time()
128 * as it is considerable faster.
130 * The relation 0 < @pos < 1 must be satisfied, as interpolating on
131 * cubic curves is not allowed.
133 * <important>
134 * <title>TODO</title>
135 * <itemizedlist>
136 * <listitem>To be implemented...</listitem>
137 * </itemizedlist>
138 * </important>
140 void
141 cpml_curve_put_pair_at(const CpmlPrimitive *curve, double pos, CpmlPair *pair)
146 * cpml_curve_put_vector_at_time:
147 * @curve: the #CpmlPrimitive curve data
148 * @t: the "time" value
149 * @vector: the destination vector
151 * Given the @curve Bézier cubic, finds the slope at time @t
152 * (where 0 is the start and 1 is the end) and stores the result
153 * in @vector. Keep in mind @t is not homogeneous, so 0.5
154 * does not necessarily means the mid point.
156 * @t must be inside the range 0 .. 1, as interpolating is not
157 * allowed.
159 void
160 cpml_curve_put_vector_at_time(const CpmlPrimitive *curve,
161 double t, CpmlVector *vector)
163 cairo_path_data_t *p1, *p2, *p3, *p4;
164 CpmlPair p21, p32, p43;
165 double t1, t1_2, t_2;
167 p1 = cpml_primitive_get_point(curve, 0);
168 p2 = cpml_primitive_get_point(curve, 1);
169 p3 = cpml_primitive_get_point(curve, 2);
170 p4 = cpml_primitive_get_point(curve, 3);
172 p21.x = p2->point.x - p1->point.x;
173 p21.y = p2->point.y - p1->point.y;
174 p32.x = p3->point.x - p2->point.x;
175 p32.y = p3->point.y - p2->point.y;
176 p43.x = p4->point.x - p3->point.x;
177 p43.y = p4->point.y - p3->point.y;
179 t1 = 1 - t;
180 t1_2 = t1 * t1;
181 t_2 = t * t;
183 vector->x = 3 * t1_2 * p21.x + 6 * t1 * t * p32.x + 3 * t_2 * p43.x;
184 vector->y = 3 * t1_2 * p21.y + 6 * t1 * t * p32.y + 3 * t_2 * p43.y;
188 * cpml_curve_put_vector_at:
189 * @curve: the #CpmlPrimitive curve data
190 * @pos: the position value
191 * @vector: the destination vector
193 * Given the @curve Bézier cubic, finds the slope at position @pos
194 * (where 0 is the start and 1 is the end) and stores the result
195 * in @vector. It is similar to cpml_curve_put_vector_at_time()
196 * but the @pos value is evenly distribuited, that is 0.5 is
197 * exactly the mid point. If you do not need this feature, use
198 * cpml_curve_put_vector_at_time() as it is considerable faster.
200 * @pos must be inside the range 0 .. 1, as interpolating is not
201 * allowed.
203 * <important>
204 * <title>TODO</title>
205 * <itemizedlist>
206 * <listitem>To be implemented...</listitem>
207 * </itemizedlist>
208 * </important>
210 void
211 cpml_curve_put_vector_at(const CpmlPrimitive *curve, double pos,
212 CpmlVector *vector)
217 * cpml_curve_get_closest_pos:
218 * @curve: the #CpmlPrimitive curve data
219 * @pair: the coordinates of the subject point
221 * Returns the pos value of the point on @curve nearest to @pair.
222 * The returned value is always between 0 and 1.
224 * <important>
225 * <title>TODO</title>
226 * <itemizedlist>
227 * <listitem>To be implemented...</listitem>
228 * </itemizedlist>
229 * </important>
231 * Returns: the pos value, always between 0 and 1
233 double
234 cpml_curve_get_closest_pos(const CpmlPrimitive *curve, const CpmlPair *pair)
236 /* TODO */
238 return 0;
242 * cpml_curve_put_intersections:
243 * @curve: the first curve
244 * @curve2: the second curve
245 * @max: maximum number of intersections to return
246 * (that is, the size of @dest)
247 * @dest: a vector of #CpmlPair
249 * Given two Bézier cubic curves (@curve and @curve2), gets their
250 * intersection points and store the result in @dest. Because two
251 * curves can have 4 intersections, @dest MUST be at least an array
252 * of 4 #CpmlPair.
254 * If @max is 0, the function returns 0 immediately without any
255 * further processing. If @curve and @curve2 are cohincident,
256 * their intersections are not considered.
258 * <important>
259 * <title>TODO</title>
260 * <itemizedlist>
261 * <listitem>To be implemented...</listitem>
262 * </itemizedlist>
263 * </important>
265 * Returns: the number of intersections found (max 4)
266 * or 0 if the primitives do not intersect
269 cpml_curve_put_intersections(const CpmlPrimitive *curve,
270 const CpmlPrimitive *curve2,
271 int max, CpmlPair *dest)
273 return 0;
277 * cpml_curve_put_intersections_with_arc:
278 * @curve: a curve
279 * @arc: an arc
280 * @max: maximum number of intersections to return
281 * (that is, the size of @dest)
282 * @dest: a vector of #CpmlPair
284 * Given a Bézier cubic @curve and an @arc, gets their intersection
285 * points and store the result in @dest. Because an arc and a cubic
286 * curve can have up to 4 intersections, @dest MUST be at least an
287 * array of 4 #CpmlPair.
289 * If @max is 0, the function returns 0 immediately without any
290 * further processing.
292 * <important>
293 * <title>TODO</title>
294 * <itemizedlist>
295 * <listitem>To be implemented...</listitem>
296 * </itemizedlist>
297 * </important>
299 * Returns: the number of intersections found (max 4)
300 * or 0 if the primitives do not intersect
303 cpml_curve_put_intersections_with_arc(const CpmlPrimitive *curve,
304 const CpmlPrimitive *arc,
305 int max, CpmlPair *dest)
307 return 0;
311 * cpml_curve_put_intersections_with_line:
312 * @curve: a curve
313 * @line: a line
314 * @max: maximum number of intersections to return
315 * (that is, the size of @dest)
316 * @dest: a vector of #CpmlPair
318 * Given a Bézier cubic @curve and a @line, gets their intersection
319 * points and store the result in @dest. Because a line and a cubic
320 * curve can have up to 4 intersections, @dest MUST be at least an
321 * array of 4 #CpmlPair.
323 * If @max is 0, the function returns 0 immediately without any
324 * further processing.
326 * <important>
327 * <title>TODO</title>
328 * <itemizedlist>
329 * <listitem>To be implemented...</listitem>
330 * </itemizedlist>
331 * </important>
333 * Returns: the number of intersections found (max 4)
334 * or 0 if the primitives do not intersect
337 cpml_curve_put_intersections_with_line(const CpmlPrimitive *curve,
338 const CpmlPrimitive *line,
339 int max, CpmlPair *dest)
341 return 0;
345 * cpml_curve_offset:
346 * @curve: the #CpmlPrimitive curve data
347 * @offset: distance for the computed parallel curve
349 * Given a cubic Bézier primitive in @curve, this function finds
350 * the approximated Bézier curve parallel to @curve at distance
351 * @offset (an offset curve). The four points needed to build the
352 * new curve are returned in the @curve struct.
354 * To solve the offset problem, a custom algorithm is used. First, the
355 * resulting curve MUST have the same slope at the start and end point.
356 * These constraints are not sufficient to resolve the system, so I let
357 * the curve pass thought a given point (pm, known and got from the
358 * original curve) at a given time (m, now hardcoded to 0.5).
360 * Firstly, I define some useful variables:
362 * v0 = unitvector(p[1]-p[0]) * offset;
363 * v3 = unitvector(p[3]-p[2]) * offset;
364 * p0 = p[0] + normal v0;
365 * p3 = p[3] + normal v3.
367 * Now I want the curve to have the specified slopes at the start
368 * and end point. Forcing the same slope at the start point means:
370 * p1 = p0 + k0 v0.
372 * where k0 is an arbitrary factor. Decomposing for x and y components:
374 * p1.x = p0.x + k0 v0.x;
375 * p1.y = p0.y + k0 v0.y.
377 * Doing the same for the end point gives:
379 * p2.x = p3.x + k3 v3.x;
380 * p2.y = p3.y + k3 v3.y.
382 * Now I interpolate the curve by forcing it to pass throught pm
383 * when "time" is m, where 0 < m < 1. The cubic Bézier function is:
385 * C(t) = (1-t)³p0 + 3t(1-t)²p1 + 3t²(1-t)p2 + t³p3.
387 * and forcing t=m and C(t) = pm:
389 * pm = (1-m)³p0 + 3m(1-m)²p1 + 3m²(1-m)p2 + m³p3.
391 * (1-m) p1 + m p2 = (pm - (1-m)³p0 - m³p3) / (3m (1-m)).
393 * So the final system is:
395 * p1.x = p0.x + k0 v0.x;
396 * p1.y = p0.y + k0 v0.y;
397 * p2.x = p3.x + k3 v3.x;
398 * p2.y = p3.y + k3 v3.y;
399 * (1-m) p1.x + m p2.x = (pm.x - (1-m)³p0.x - m³p3.x) / (3m (1-m));
400 * (1-m) p1.y + m p2.y = (pm.y - (1-m)³p0.y - m³p3.y) / (3m (1-m)).
402 * Substituting and resolving for k0 and k3:
404 * (1-m) k0 v0.x + m k3 v3.x =
405 * (pm.x - (1-m)³p0.x - m³p3.x) / (3m (1-m)) - (1-m) p0.x - m p3.x;
406 * (1-m) k0 v0.y + m k3 v3.y =
407 * (pm.y - (1-m)³p0.y - m³p3.y) / (3m (1-m)) - (1-m) p0.y - m p3.y.
409 * (1-m) k0 v0.x + m k3 v3.x =
410 * (pm.x - (1-m)²(1+2m) p0.x - m²(3-2m) p3.x) / (3m (1-m));
411 * (1-m) k0 v0.y + m k3 v3.y =
412 * (pm.y - (1-m)²(1+2m) p0.y - m²(3-2m) p3.y) / (3m (1-m)).
414 * Let:
416 * pk = (pm - (1-m)²(1+2m) p0 - m²(3-2m) p3) / (3m (1-m)).
418 * gives the following system:
420 * (1-m) k0 v0.x + m k3 v3.x = pk.x;
421 * (1-m) k0 v0.y + m k3 v3.y = pk.y.
423 * Now I should avoid division by 0 troubles. If either v0.x and v3.x
424 * are 0, the first equation will be inconsistent. More in general the
425 * v0.x*v3.y = v3.x*v3.y condition should be avoided. This is the first
426 * case to check, in which case an alternative approach is used. In the
427 * other cases the above system can be used.
429 * If v0.x != 0 I can resolve for k0 and then find k3:
431 * k0 = (pk.x - m k3 v3.x) / ((1-m) v0.x);
432 * (pk.x - m k3 v3.x) v0.y / v0.x + m k3 v3.y = pk.y.
434 * k0 = (pk.x - m k3 v3.x) / ((1-m) v0.x);
435 * k3 m (v3.y - v3.x v0.y / v0.x) = pk.y - pk.x v0.y / v0.x.
437 * k3 = (pk.y - pk.x v0.y / v0.x) / (m (v3.y - v3.x v0.y / v0.x));
438 * k0 = (pk.x - m k3 v3.x) / ((1-m) v0.x).
440 * If v3.x != 0 I can resolve for k3 and then find k0:
442 * k3 = (pk.x - (1-m) k0 v0.x) / (m v3.x);
443 * (1-m) k0 v0.y + (pk.x - (1-m) k0 v0.x) v3.y / v3.x = pk.y.
445 * k3 = (pk.x - (1-m) k0 v0.x) / (m v3.x);
446 * k0 (1-m) (v0.y - k0 v0.x v3.y / v3.x) = pk.y - pk.x v3.y / v3.x.
448 * k0 = (pk.y - pk.x v3.y / v3.x) / ((1-m) (v0.y - v0.x v3.y / v3.x));
449 * k3 = (pk.x - (1-m) k0 v0.x) / (m v3.x).
451 * <important>
452 * <title>TODO</title>
453 * <itemizedlist>
454 * <listitem>By default, interpolation of the new curve is made by offseting
455 * the mid point: use a better candidate.</listitem>
456 * <listitem>When the equations are inconsistent, the alternative approach
457 * performs very bad if <varname>v0</varname> and
458 * <varname>v3</varname> are opposite or staggered.</listitem>
459 * </itemizedlist>
460 * </important>
462 void
463 cpml_curve_offset(CpmlPrimitive *curve, double offset)
465 double m, mm;
466 CpmlVector v0, v3, vm, vtmp;
467 CpmlPair p0, p1, p2, p3, pm;
469 m = 0.5;
470 mm = 1-m;
472 /* Firstly, convert the curve points from cairo format to cpml format
473 * and store them (temporary) in p0..p3 */
474 cpml_pair_from_cairo(&p0, curve->org);
475 cpml_pair_from_cairo(&p1, &curve->data[1]);
476 cpml_pair_from_cairo(&p2, &curve->data[2]);
477 cpml_pair_from_cairo(&p3, &curve->data[3]);
479 /* v0 = p1-p0 */
480 cpml_pair_copy(&v0, &p1);
481 cpml_pair_sub(&v0, &p0);
483 /* v3 = p3-p2 */
484 cpml_pair_copy(&v3, &p3);
485 cpml_pair_sub(&v3, &p2);
487 /* pm = point in C(m) offseted the requested @offset distance */
488 cpml_curve_put_vector_at_time(curve, m, &vm);
489 cpml_vector_set_length(&vm, offset);
490 cpml_vector_normal(&vm);
491 cpml_curve_put_pair_at_time(curve, m, &pm);
492 cpml_pair_add(&pm, &vm);
494 /* p0 = p0 + normal of v0 of @offset magnitude (exact value) */
495 cpml_pair_copy(&vtmp, &v0);
496 cpml_vector_set_length(&vtmp, offset);
497 cpml_vector_normal(&vtmp);
498 cpml_pair_add(&p0, &vtmp);
500 /* p3 = p3 + normal of v3 of @offset magnitude, as done for p0 */
501 cpml_pair_copy(&vtmp, &v3);
502 cpml_vector_set_length(&vtmp, offset);
503 cpml_vector_normal(&vtmp);
504 cpml_pair_add(&p3, &vtmp);
506 if (v0.x*v3.y == v3.x*v0.y) {
507 /* Inconsistent equations: use the alternative approach */
508 p1.x = p0.x + v0.x + vm.x * 4/3;
509 p1.y = p0.y + v0.y + vm.y * 4/3;
510 p2.x = p3.x - v3.x + vm.x * 4/3;
511 p2.y = p3.y - v3.y + vm.y * 4/3;
512 } else {
513 CpmlPair pk;
514 double k0, k3;
516 pk.x = (pm.x - mm*mm*(1+m+m)*p0.x - m*m*(1+mm+mm)*p3.x) / (3*m*(1-m));
517 pk.y = (pm.y - mm*mm*(1+m+m)*p0.y - m*m*(1+mm+mm)*p3.y) / (3*m*(1-m));
519 if (v0.x != 0) {
520 k3 = (pk.y - pk.x*v0.y / v0.x) / (m*(v3.y - v3.x*v0.y / v0.x));
521 k0 = (pk.x - m*k3*v3.x) / (mm*v0.x);
522 } else {
523 k0 = (pk.y - pk.x*v3.y / v3.x) / (mm*(v0.y - v0.x*v3.y / v3.x));
524 k3 = (pk.x - mm*k0*v0.x) / (m*v3.x);
527 p1.x = p0.x + k0*v0.x;
528 p1.y = p0.y + k0*v0.y;
529 p2.x = p3.x + k3*v3.x;
530 p2.y = p3.y + k3*v3.y;
533 /* Return the new curve in the original array */
534 cpml_pair_to_cairo(&p0, curve->org);
535 cpml_pair_to_cairo(&p1, &curve->data[1]);
536 cpml_pair_to_cairo(&p2, &curve->data[2]);
537 cpml_pair_to_cairo(&p3, &curve->data[3]);
541 static void
542 put_extents(const CpmlPrimitive *curve, CpmlExtents *extents)
544 CpmlPair p1, p2, p3, p4;
546 extents->is_defined = 0;
548 cpml_pair_from_cairo(&p1, cpml_primitive_get_point(curve, 0));
549 cpml_pair_from_cairo(&p2, cpml_primitive_get_point(curve, 1));
550 cpml_pair_from_cairo(&p3, cpml_primitive_get_point(curve, 2));
551 cpml_pair_from_cairo(&p4, cpml_primitive_get_point(curve, 3));
553 cpml_extents_pair_add(extents, &p1);
554 cpml_extents_pair_add(extents, &p2);
555 cpml_extents_pair_add(extents, &p3);
556 cpml_extents_pair_add(extents, &p4);