doc: corrected NEWS.xml typo
[adg.git] / src / adg / adg-edges.c
blob164a1a09e62554b2856cfa9716ec3f58e4720f7f
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009,2010,2011,2012,2013 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:adg-edges
23 * @short_description: A model with the edges of another model
25 * The #AdgEdges can be used to render the edges of a yet existing
26 * #AdgTrail source. It is useful for any part made by revolution,
27 * where the shape is symmetric along a specific axis and thus the
28 * edge lines can be easily computed.
30 * The trail can be set by changing the #AdgEdges:source property
31 * or the relevant APIs. If the trail changes, a recomputation
32 * can be forced by calling the adg_model_clear() method.
34 * The angle of the axis is implied to pass through the (0,0) point
35 * and has an angle of #AdgEdges:axis-angle radiants. The default
36 * is a 0 radiant angle, meaning the y=0 axis is assumed.
38 * Since: 1.0
39 **/
41 /**
42 * AdgEdges:
44 * All fields are private and should not be used directly.
45 * Use its public methods instead.
47 * Since: 1.0
48 **/
51 #include "adg-internal.h"
52 #include "adg-model.h"
53 #include "adg-trail.h"
54 #include <math.h>
56 #include "adg-edges.h"
57 #include "adg-edges-private.h"
60 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_edges_parent_class)
61 #define _ADG_OLD_MODEL_CLASS ((AdgModelClass *) adg_edges_parent_class)
64 G_DEFINE_TYPE(AdgEdges, adg_edges, ADG_TYPE_TRAIL)
66 enum {
67 PROP_0,
68 PROP_SOURCE,
69 PROP_CRITICAL_ANGLE,
70 PROP_AXIS_ANGLE
74 static void _adg_dispose (GObject *object);
75 static void _adg_finalize (GObject *object);
76 static void _adg_get_property (GObject *object,
77 guint param_id,
78 GValue *value,
79 GParamSpec *pspec);
80 static void _adg_set_property (GObject *object,
81 guint param_id,
82 const GValue *value,
83 GParamSpec *pspec);
84 static void _adg_clear (AdgModel *model);
85 static cairo_path_t * _adg_get_cairo_path (AdgTrail *trail);
86 static void _adg_unset_source (AdgEdges *edges);
87 static void _adg_clear_cairo_path (AdgEdges *edges);
88 static GSList * _adg_get_vertices (CpmlSegment *segment,
89 gdouble threshold);
90 static GSList * _adg_optimize_vertices (GSList *vertices);
91 static GArray * _adg_path_build (const GSList *vertices);
92 static void _adg_path_transform (GArray *path_data,
93 const cairo_matrix_t*map);
96 static void
97 adg_edges_class_init(AdgEdgesClass *klass)
99 GObjectClass *gobject_class;
100 AdgModelClass *model_class;
101 AdgTrailClass *trail_class;
102 GParamSpec *param;
104 gobject_class = (GObjectClass *) klass;
105 model_class = (AdgModelClass *) klass;
106 trail_class = (AdgTrailClass *) klass;
108 g_type_class_add_private(klass, sizeof(AdgEdgesPrivate));
110 gobject_class->dispose = _adg_dispose;
111 gobject_class->finalize = _adg_finalize;
112 gobject_class->get_property = _adg_get_property;
113 gobject_class->set_property = _adg_set_property;
115 model_class->clear = _adg_clear;
117 trail_class->get_cairo_path = _adg_get_cairo_path;
119 param = g_param_spec_object("source",
120 P_("Source"),
121 P_("The source from which the edges should be computed from"),
122 ADG_TYPE_TRAIL,
123 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
124 g_object_class_install_property(gobject_class, PROP_SOURCE, param);
126 param = g_param_spec_double("axis-angle",
127 P_("Axis Angle"),
128 P_("The angle of the axis of the source trail: it is implied this axis passes through (0,0)"),
129 -G_PI, G_PI, 0,
130 G_PARAM_READWRITE);
131 g_object_class_install_property(gobject_class, PROP_AXIS_ANGLE, param);
133 param = g_param_spec_double("critical-angle",
134 P_("Critical Angle"),
135 P_("The angle that defines which corner generates an edge (if the corner angle is greater than this critical angle) and which edge is ignored"),
136 0, G_PI, G_PI / 45,
137 G_PARAM_READWRITE);
138 g_object_class_install_property(gobject_class, PROP_CRITICAL_ANGLE, param);
141 static void
142 adg_edges_init(AdgEdges *edges)
144 AdgEdgesPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(edges, ADG_TYPE_EDGES,
145 AdgEdgesPrivate);
147 data->source = NULL;
148 data->critical_angle = G_PI / 45;
149 data->axis_angle = 0;
151 data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA;
152 data->cairo.array = NULL;
154 edges->data = data;
157 static void
158 _adg_dispose(GObject *object)
160 AdgEdges *edges = (AdgEdges *) object;
162 adg_edges_set_source(edges, NULL);
164 if (_ADG_OLD_OBJECT_CLASS->dispose != NULL)
165 _ADG_OLD_OBJECT_CLASS->dispose(object);
168 static void
169 _adg_finalize(GObject *object)
171 _adg_clear_cairo_path((AdgEdges *) object);
173 if (_ADG_OLD_OBJECT_CLASS->finalize != NULL)
174 _ADG_OLD_OBJECT_CLASS->finalize(object);
177 static void
178 _adg_get_property(GObject *object, guint prop_id,
179 GValue *value, GParamSpec *pspec)
181 AdgEdges *edges;
182 AdgEdgesPrivate *data;
184 edges = (AdgEdges *) object;
185 data = edges->data;
187 switch (prop_id) {
188 case PROP_SOURCE:
189 g_value_set_object(value, data->source);
190 break;
191 case PROP_AXIS_ANGLE:
192 g_value_set_double(value, data->axis_angle);
193 break;
194 case PROP_CRITICAL_ANGLE:
195 g_value_set_double(value, data->critical_angle);
196 break;
197 default:
198 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
199 break;
203 static void
204 _adg_set_property(GObject *object, guint prop_id,
205 const GValue *value, GParamSpec *pspec)
207 AdgEdges *edges;
208 AdgEdgesPrivate *data;
209 gpointer tmp_pointer;
210 gdouble tmp_double;
212 edges = (AdgEdges *) object;
213 data = edges->data;
215 switch (prop_id) {
216 case PROP_SOURCE:
217 tmp_pointer = data->source;
218 data->source = g_value_get_object(value);
220 if (tmp_pointer != data->source) {
221 if (data->source)
222 g_object_weak_ref((GObject *) data->source,
223 (GWeakNotify) _adg_unset_source, object);
224 if (tmp_pointer)
225 g_object_weak_unref((GObject *) tmp_pointer,
226 (GWeakNotify) _adg_unset_source, object);
229 _adg_clear((AdgModel *) object);
230 break;
231 case PROP_AXIS_ANGLE:
232 tmp_double = g_value_get_double(value);
233 if (data->axis_angle != tmp_double) {
234 data->axis_angle = tmp_double;
235 _adg_clear_cairo_path(edges);
237 break;
238 case PROP_CRITICAL_ANGLE:
239 tmp_double = g_value_get_double(value);
240 if (data->critical_angle != tmp_double) {
241 data->critical_angle = tmp_double;
242 _adg_clear_cairo_path(edges);
244 break;
245 default:
246 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
247 break;
253 * adg_edges_new:
255 * Creates a new undefined model to keep track of the edges of
256 * another model. You should at least set the referred #AdgTrail
257 * with adg_edges_set_source().
259 * Returns: the newly created edges model
261 * Since: 1.0
263 AdgEdges *
264 adg_edges_new(void)
266 return g_object_new(ADG_TYPE_EDGES, NULL);
270 * adg_edges_new_with_source:
271 * @source: (transfer none): the new source #AdgTrail
273 * Creates a new edges model explicitely specifying the source trail.
274 * The returned object will own a weak reference on @source.
276 * Returns: the newly created edges model
278 * Since: 1.0
280 AdgEdges *
281 adg_edges_new_with_source(AdgTrail *source)
283 return g_object_new(ADG_TYPE_EDGES, "source", source, NULL);
287 * adg_edges_get_source:
288 * @edges: an #AdgEdges
290 * Gets the source #AdgTrail of this @edges model.
291 * The returned object is owned by @edges and should not be
292 * freed or modified.
294 * Returns: (transfer none): the requested #AdgTrail or %NULL on errors.
296 * Since: 1.0
298 AdgTrail *
299 adg_edges_get_source(AdgEdges *edges)
301 AdgEdgesPrivate *data;
303 g_return_val_if_fail(ADG_IS_EDGES(edges), NULL);
305 data = edges->data;
307 return data->source;
311 * adg_edges_set_source:
312 * @edges: an #AdgEdges
313 * @source: (transfer none): the new source #AdgTrail
315 * Sets @source as the source trail for @edges.
316 * After the call, @edges will own a weak reference on @source.
318 * Since: 1.0
320 void
321 adg_edges_set_source(AdgEdges *edges, AdgTrail *source)
323 g_return_if_fail(ADG_IS_EDGES(edges));
324 g_object_set(edges, "source", source, NULL);
328 * adg_edges_set_axis_angle:
329 * @edges: an #AdgEdges
330 * @angle: the new angle (in radians)
332 * Sets the axis angle of @edges to @angle, basically setting
333 * the #AdgEdges:axis-angle property. All the resulting edge
334 * lines will be normal to this axis.
336 * It is implied the axis will pass through the (0,0) point,
337 * so the underlying trail should be constructed accordingly.
339 * Since: 1.0
341 void
342 adg_edges_set_axis_angle(AdgEdges *edges, gdouble angle)
344 g_return_if_fail(ADG_IS_EDGES(edges));
345 g_object_set(edges, "axis-angle", angle, NULL);
349 * adg_edges_get_axis_angle:
350 * @edges: an #AdgEdges
352 * Gets the angle of the supposed axis of @edges. Refer to
353 * adg_edges_set_axis_angle() for details of what this parameter
354 * is used for.
356 * Returns: the value (in radians) of the axis angle
358 * Since: 1.0
360 gdouble
361 adg_edges_get_axis_angle(AdgEdges *edges)
363 AdgEdgesPrivate *data;
365 g_return_val_if_fail(ADG_IS_EDGES(edges), 0);
367 data = edges->data;
368 return data->axis_angle;
372 * adg_edges_set_critical_angle:
373 * @edges: an #AdgEdges
374 * @angle: the new angle (in radians)
376 * Sets the critical angle of @edges to @angle, basically setting
377 * the #AdgEdges:critical-angle property.
379 * The critical angle defines what corner should generate an edge and
380 * what not. Typical values are close to %0, being %0 the lowest angle
381 * where every corner generates an edge.
383 * Since: 1.0
385 void
386 adg_edges_set_critical_angle(AdgEdges *edges, gdouble angle)
388 g_return_if_fail(ADG_IS_EDGES(edges));
389 g_object_set(edges, "critical-angle", angle, NULL);
393 * adg_edges_get_critical_angle:
394 * @edges: an #AdgEdges
396 * Gets the current critical angle of @edges. Refer to
397 * adg_edges_set_critical_angle() for details of what this parameter
398 * is used for.
400 * Returns: the value (in radians) of the critical angle
402 * Since: 1.0
404 gdouble
405 adg_edges_get_critical_angle(AdgEdges *edges)
407 AdgEdgesPrivate *data;
409 g_return_val_if_fail(ADG_IS_EDGES(edges), 0);
411 data = edges->data;
412 return data->critical_angle;
416 static void
417 _adg_clear(AdgModel *model)
419 _adg_clear_cairo_path((AdgEdges *) model);
421 if (_ADG_OLD_MODEL_CLASS->clear != NULL)
422 _ADG_OLD_MODEL_CLASS->clear(model);
425 static cairo_path_t *
426 _adg_get_cairo_path(AdgTrail *trail)
428 AdgEdges *edges;
429 AdgEdgesPrivate *data;
430 gdouble threshold;
431 CpmlSegment segment;
432 GSList *vertices;
433 cairo_matrix_t map;
435 edges = (AdgEdges *) trail;
436 data = edges->data;
438 /* Check for cached path */
439 if (data->cairo.path.status == CAIRO_STATUS_SUCCESS)
440 return &data->cairo.path;
442 _adg_clear_cairo_path((AdgEdges *) trail);
444 if (data->source != NULL) {
445 adg_trail_put_segment(data->source, 1, &segment);
447 /* The threshold is squared because the _adg_get_vertices()
448 * function uses cpml_pair_squared_distance() against the
449 * two vectors of every corner to avoid sqrt()ing everything */
450 threshold = sin(data->critical_angle);
451 threshold *= threshold * 2;
453 vertices = _adg_get_vertices(&segment, threshold);
455 /* Rotate all the vertices so the axis will always be on y=0:
456 * this is mainly needed to not complicate the _adg_path_build()
457 * code which assumes the y=0 axis is in effect */
458 cairo_matrix_init_rotate(&map, -data->axis_angle);
459 g_slist_foreach(vertices, (GFunc) cpml_pair_transform, &map);
461 vertices = _adg_optimize_vertices(vertices);
462 data->cairo.array = _adg_path_build(vertices);
464 g_slist_foreach(vertices, (GFunc) g_free, NULL);
465 g_slist_free(vertices);
467 /* Reapply the inverse of the previous transformation to
468 * move the vertices to their original positions */
469 cairo_matrix_invert(&map);
470 _adg_path_transform(data->cairo.array, &map);
472 data->cairo.path.status = CAIRO_STATUS_SUCCESS;
473 data->cairo.path.data = (cairo_path_data_t *) (data->cairo.array)->data;
474 data->cairo.path.num_data = (data->cairo.array)->len;
477 return &data->cairo.path;
480 static void
481 _adg_unset_source(AdgEdges *edges)
483 g_object_set(edges, "source", NULL, NULL);
486 static void
487 _adg_clear_cairo_path(AdgEdges *edges)
489 AdgEdgesPrivate *data = edges->data;
491 if (data->cairo.array != NULL) {
492 g_array_free(data->cairo.array, TRUE);
493 data->cairo.array = NULL;
496 data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA;
497 data->cairo.path.data = NULL;
498 data->cairo.path.num_data = 0;
502 * _adg_get_vertices:
503 * @segment: a #CpmlSegment
504 * @threshold: a theshold value
506 * Collects a list of #CpmlPair corners where the angle has a minimum
507 * threshold incidence of @threshold. The threshold is considered as
508 * the squared distance between the two unit vectors, the one before
509 * and the one after every corner.
511 * Returns: a #GSList of #CpmlPair: the list should be freed with
512 * g_slist_free() and all the pairs freed with g_free()
513 * when no longer needed
515 * Since: 1.0
517 static GSList *
518 _adg_get_vertices(CpmlSegment *segment, gdouble threshold)
520 GSList *vertices;
521 CpmlPrimitive primitive;
522 CpmlVector old, new;
523 CpmlPair pair;
525 vertices = NULL;
526 cpml_primitive_from_segment(&primitive, segment);
527 old.x = old.y = 0;
529 do {
530 /* The first vector and the undefined ones
531 * must always be skipped */
532 if (old.x != 0 || old.y != 0) {
533 cpml_vector_set_length(&old, 1);
534 cpml_primitive_put_vector_at(&primitive, 0, &new);
535 cpml_vector_set_length(&new, 1);
537 /* Vertical vectors are always added, as they represent
538 * a vertical side and could be filleted, thus skipping
539 * the edge detection */
540 if (new.x == 0 ||
541 cpml_pair_squared_distance(&old, &new) > threshold) {
542 cpml_primitive_put_pair_at(&primitive, 0, &pair);
543 vertices = g_slist_prepend(vertices, cpml_pair_dup(&pair));
547 cpml_primitive_put_vector_at(&primitive, 1, &old);
548 } while (cpml_primitive_next(&primitive));
550 return g_slist_reverse(vertices);
553 /* Removes adjacent vertices lying on the same edge */
554 static GSList *
555 _adg_optimize_vertices(GSList *vertices)
557 GSList *vertex, *old_vertex;
558 CpmlPair *pair, *old_pair;
560 /* Check for empty list */
561 if (vertices == NULL)
562 return vertices;
564 old_vertex = vertices;
566 while ((vertex = old_vertex->next) != NULL) {
567 pair = vertex->data;
568 old_pair = old_vertex->data;
570 if (pair->x != old_pair->x) {
571 old_vertex = vertex;
572 continue;
575 if (old_pair->y < pair->y) {
576 /* Preserve the old vertex and remove the current one */
577 g_free(pair);
578 vertices = g_slist_delete_link(vertices, vertex);
579 } else {
580 /* Preserve the current vertex and remove the old one */
581 g_free(old_pair);
582 vertices = g_slist_delete_link(vertices, old_vertex);
583 old_vertex = vertex;
587 return vertices;
590 static GArray *
591 _adg_path_build(const GSList *vertices)
593 cairo_path_data_t line[4];
594 GArray *array;
595 const GSList *vertex, *vertex2;
596 const CpmlPair *pair, *pair2;
598 line[0].header.type = CPML_MOVE;
599 line[0].header.length = 2;
600 line[2].header.type = CPML_LINE;
601 line[2].header.length = 2;
603 array = g_array_new(FALSE, FALSE, sizeof(cairo_path_data_t));
604 vertex = vertices;
606 while (vertex != NULL) {
607 pair = vertex->data;
608 vertex = vertex->next;
609 vertex2 = vertex;
611 while (vertex2 != NULL) {
612 pair2 = vertex2->data;
614 if (pair->x == pair2->x) {
615 /* Opposite vertex found: append a line in the path
616 * and quit from this loop */
617 cpml_pair_to_cairo(pair, &line[1]);
618 cpml_pair_to_cairo(pair2, &line[3]);
619 array = g_array_append_vals(array, line, G_N_ELEMENTS(line));
620 break;
623 vertex2 = vertex2->next;
627 return array;
630 static void
631 _adg_path_transform(GArray *path_data, const cairo_matrix_t *map)
633 guint n;
634 cairo_path_data_t *data;
636 /* Only the odd items are transformed: the even ones are either
637 * header items, CPML_MOVE and CPML_LINE alternatively */
638 for (n = 1; n < path_data->len; n += 2) {
639 data = &g_array_index(path_data, cairo_path_data_t, n);
640 cairo_matrix_transform_point(map, &data->point.x, &data->point.y);