doc: update copyright line for 2019
[adg.git] / src / adg / adg-edges.c
blob8d7e787765e813eaf161d8c3e23f79522b0cbf51
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2019 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)
62 #define DEFAULT_CRITICAL_ANGLE (G_PI / 180)
65 G_DEFINE_TYPE(AdgEdges, adg_edges, ADG_TYPE_TRAIL)
67 enum {
68 PROP_0,
69 PROP_SOURCE,
70 PROP_CRITICAL_ANGLE,
71 PROP_AXIS_ANGLE
75 static void _adg_dispose (GObject *object);
76 static void _adg_finalize (GObject *object);
77 static void _adg_get_property (GObject *object,
78 guint param_id,
79 GValue *value,
80 GParamSpec *pspec);
81 static void _adg_set_property (GObject *object,
82 guint param_id,
83 const GValue *value,
84 GParamSpec *pspec);
85 static void _adg_clear (AdgModel *model);
86 static cairo_path_t * _adg_get_cairo_path (AdgTrail *trail);
87 static void _adg_unset_source (AdgEdges *edges);
88 static void _adg_clear_cairo_path (AdgEdges *edges);
89 static GSList * _adg_get_vertices (GSList *vertices,
90 CpmlSegment *segment,
91 gdouble threshold);
92 static GSList * _adg_optimize_vertices (GSList *vertices);
93 static GArray * _adg_path_build (const GSList *vertices);
94 static void _adg_path_transform (GArray *path_data,
95 const cairo_matrix_t*map);
98 static void
99 adg_edges_class_init(AdgEdgesClass *klass)
101 GObjectClass *gobject_class;
102 AdgModelClass *model_class;
103 AdgTrailClass *trail_class;
104 GParamSpec *param;
106 gobject_class = (GObjectClass *) klass;
107 model_class = (AdgModelClass *) klass;
108 trail_class = (AdgTrailClass *) klass;
110 g_type_class_add_private(klass, sizeof(AdgEdgesPrivate));
112 gobject_class->dispose = _adg_dispose;
113 gobject_class->finalize = _adg_finalize;
114 gobject_class->get_property = _adg_get_property;
115 gobject_class->set_property = _adg_set_property;
117 model_class->clear = _adg_clear;
119 trail_class->get_cairo_path = _adg_get_cairo_path;
121 param = g_param_spec_object("source",
122 P_("Source"),
123 P_("The source from which the edges should be computed from"),
124 ADG_TYPE_TRAIL,
125 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
126 g_object_class_install_property(gobject_class, PROP_SOURCE, param);
128 param = g_param_spec_double("axis-angle",
129 P_("Axis Angle"),
130 P_("The angle of the axis of the source trail: it is implied this axis passes through (0,0)"),
131 -G_PI, G_PI, 0,
132 G_PARAM_READWRITE);
133 g_object_class_install_property(gobject_class, PROP_AXIS_ANGLE, param);
135 param = g_param_spec_double("critical-angle",
136 P_("Critical Angle"),
137 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"),
138 0, G_PI, DEFAULT_CRITICAL_ANGLE,
139 G_PARAM_READWRITE);
140 g_object_class_install_property(gobject_class, PROP_CRITICAL_ANGLE, param);
143 static void
144 adg_edges_init(AdgEdges *edges)
146 AdgEdgesPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(edges, ADG_TYPE_EDGES,
147 AdgEdgesPrivate);
149 data->source = NULL;
150 data->critical_angle = DEFAULT_CRITICAL_ANGLE;
151 data->axis_angle = 0;
153 data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA;
154 data->cairo.array = NULL;
156 edges->data = data;
159 static void
160 _adg_dispose(GObject *object)
162 AdgEdges *edges = (AdgEdges *) object;
164 adg_edges_set_source(edges, NULL);
166 if (_ADG_OLD_OBJECT_CLASS->dispose != NULL)
167 _ADG_OLD_OBJECT_CLASS->dispose(object);
170 static void
171 _adg_finalize(GObject *object)
173 _adg_clear_cairo_path((AdgEdges *) object);
175 if (_ADG_OLD_OBJECT_CLASS->finalize != NULL)
176 _ADG_OLD_OBJECT_CLASS->finalize(object);
179 static void
180 _adg_get_property(GObject *object, guint prop_id,
181 GValue *value, GParamSpec *pspec)
183 AdgEdges *edges;
184 AdgEdgesPrivate *data;
186 edges = (AdgEdges *) object;
187 data = edges->data;
189 switch (prop_id) {
190 case PROP_SOURCE:
191 g_value_set_object(value, data->source);
192 break;
193 case PROP_AXIS_ANGLE:
194 g_value_set_double(value, data->axis_angle);
195 break;
196 case PROP_CRITICAL_ANGLE:
197 g_value_set_double(value, data->critical_angle);
198 break;
199 default:
200 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
201 break;
205 static void
206 _adg_set_property(GObject *object, guint prop_id,
207 const GValue *value, GParamSpec *pspec)
209 AdgEdges *edges;
210 AdgEdgesPrivate *data;
211 gpointer tmp_pointer;
212 gdouble tmp_double;
214 edges = (AdgEdges *) object;
215 data = edges->data;
217 switch (prop_id) {
218 case PROP_SOURCE:
219 tmp_pointer = data->source;
220 data->source = g_value_get_object(value);
222 if (tmp_pointer != data->source) {
223 if (data->source)
224 g_object_weak_ref((GObject *) data->source,
225 (GWeakNotify) _adg_unset_source, object);
226 if (tmp_pointer)
227 g_object_weak_unref((GObject *) tmp_pointer,
228 (GWeakNotify) _adg_unset_source, object);
231 _adg_clear((AdgModel *) object);
232 break;
233 case PROP_AXIS_ANGLE:
234 tmp_double = g_value_get_double(value);
235 if (data->axis_angle != tmp_double) {
236 data->axis_angle = tmp_double;
237 _adg_clear_cairo_path(edges);
239 break;
240 case PROP_CRITICAL_ANGLE:
241 tmp_double = g_value_get_double(value);
242 if (data->critical_angle != tmp_double) {
243 data->critical_angle = tmp_double;
244 _adg_clear_cairo_path(edges);
246 break;
247 default:
248 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
249 break;
255 * adg_edges_new:
257 * Creates a new undefined model to keep track of the edges of
258 * another model. You should at least set the referred #AdgTrail
259 * with adg_edges_set_source().
261 * Returns: the newly created edges model
263 * Since: 1.0
265 AdgEdges *
266 adg_edges_new(void)
268 return g_object_new(ADG_TYPE_EDGES, NULL);
272 * adg_edges_new_with_source:
273 * @source: (transfer none): the new source #AdgTrail
275 * Creates a new edges model explicitely specifying the source trail.
276 * The returned object will own a weak reference on @source.
278 * Returns: the newly created edges model
280 * Since: 1.0
282 AdgEdges *
283 adg_edges_new_with_source(AdgTrail *source)
285 return g_object_new(ADG_TYPE_EDGES, "source", source, NULL);
289 * adg_edges_get_source:
290 * @edges: an #AdgEdges
292 * Gets the source #AdgTrail of this @edges model.
293 * The returned object is owned by @edges and should not be
294 * freed or modified.
296 * Returns: (transfer none): the requested #AdgTrail or <constant>NULL</constant> on errors.
298 * Since: 1.0
300 AdgTrail *
301 adg_edges_get_source(AdgEdges *edges)
303 AdgEdgesPrivate *data;
305 g_return_val_if_fail(ADG_IS_EDGES(edges), NULL);
307 data = edges->data;
309 return data->source;
313 * adg_edges_set_source:
314 * @edges: an #AdgEdges
315 * @source: (transfer none): the new source #AdgTrail
317 * Sets @source as the source trail for @edges.
318 * After the call, @edges will own a weak reference on @source.
320 * Since: 1.0
322 void
323 adg_edges_set_source(AdgEdges *edges, AdgTrail *source)
325 g_return_if_fail(ADG_IS_EDGES(edges));
326 g_object_set(edges, "source", source, NULL);
330 * adg_edges_set_axis_angle:
331 * @edges: an #AdgEdges
332 * @angle: the new angle (in radians)
334 * Sets the axis angle of @edges to @angle, basically setting
335 * the #AdgEdges:axis-angle property. All the resulting edge
336 * lines will be normal to this axis.
338 * It is implied the axis will pass through the (0,0) point,
339 * so the underlying trail should be constructed accordingly.
341 * Since: 1.0
343 void
344 adg_edges_set_axis_angle(AdgEdges *edges, gdouble angle)
346 g_return_if_fail(ADG_IS_EDGES(edges));
347 g_object_set(edges, "axis-angle", angle, NULL);
351 * adg_edges_get_axis_angle:
352 * @edges: an #AdgEdges
354 * Gets the angle of the supposed axis of @edges. Refer to
355 * adg_edges_set_axis_angle() for details of what this parameter
356 * is used for.
358 * Returns: the value (in radians) of the axis angle
360 * Since: 1.0
362 gdouble
363 adg_edges_get_axis_angle(AdgEdges *edges)
365 AdgEdgesPrivate *data;
367 g_return_val_if_fail(ADG_IS_EDGES(edges), 0);
369 data = edges->data;
370 return data->axis_angle;
374 * adg_edges_set_critical_angle:
375 * @edges: an #AdgEdges
376 * @angle: the new angle (in radians)
378 * Sets the critical angle of @edges to @angle, basically setting
379 * the #AdgEdges:critical-angle property.
381 * The critical angle defines what corner should generate an edge and
382 * what not. Typical values are close to 0, being 0 the lowest angle
383 * where every corner generates an edge.
385 * Since: 1.0
387 void
388 adg_edges_set_critical_angle(AdgEdges *edges, gdouble angle)
390 g_return_if_fail(ADG_IS_EDGES(edges));
391 g_object_set(edges, "critical-angle", angle, NULL);
395 * adg_edges_get_critical_angle:
396 * @edges: an #AdgEdges
398 * Gets the current critical angle of @edges. Refer to
399 * adg_edges_set_critical_angle() for details of what this parameter
400 * is used for.
402 * Returns: the value (in radians) of the critical angle
404 * Since: 1.0
406 gdouble
407 adg_edges_get_critical_angle(AdgEdges *edges)
409 AdgEdgesPrivate *data;
411 g_return_val_if_fail(ADG_IS_EDGES(edges), 0);
413 data = edges->data;
414 return data->critical_angle;
418 static void
419 _adg_clear(AdgModel *model)
421 _adg_clear_cairo_path((AdgEdges *) model);
423 if (_ADG_OLD_MODEL_CLASS->clear != NULL)
424 _ADG_OLD_MODEL_CLASS->clear(model);
427 static cairo_path_t *
428 _adg_get_cairo_path(AdgTrail *trail)
430 AdgEdges *edges;
431 AdgEdgesPrivate *data;
432 gdouble threshold;
433 CpmlSegment segment;
434 GSList *vertices;
435 cairo_matrix_t map;
437 edges = (AdgEdges *) trail;
438 data = edges->data;
440 /* Check for cached path */
441 if (data->cairo.path.status == CAIRO_STATUS_SUCCESS)
442 return &data->cairo.path;
444 _adg_clear_cairo_path((AdgEdges *) trail);
446 if (data->source != NULL) {
447 gint n;
449 /* The threshold is squared because the _adg_get_vertices()
450 * function uses cpml_pair_squared_distance() against the
451 * two vectors of every corner to avoid sqrt()ing everything */
452 threshold = sin(data->critical_angle);
453 threshold *= threshold * 2;
455 vertices = NULL;
456 for (n = 1; adg_trail_put_segment(data->source, n, &segment); ++ n) {
457 vertices = _adg_get_vertices(vertices, &segment, threshold);
460 /* Rotate all the vertices so the axis will always be on y=0:
461 * this is mainly needed to not complicate the _adg_path_build()
462 * code which assumes the y=0 axis is in effect */
463 cairo_matrix_init_rotate(&map, -data->axis_angle);
464 g_slist_foreach(vertices, (GFunc) cpml_pair_transform, &map);
466 vertices = _adg_optimize_vertices(vertices);
467 data->cairo.array = _adg_path_build(vertices);
469 g_slist_foreach(vertices, (GFunc) g_free, NULL);
470 g_slist_free(vertices);
472 /* Reapply the inverse of the previous transformation to
473 * move the vertices to their original positions */
474 cairo_matrix_invert(&map);
475 _adg_path_transform(data->cairo.array, &map);
477 data->cairo.path.status = CAIRO_STATUS_SUCCESS;
478 data->cairo.path.data = (cairo_path_data_t *) (data->cairo.array)->data;
479 data->cairo.path.num_data = (data->cairo.array)->len;
482 return &data->cairo.path;
485 static void
486 _adg_unset_source(AdgEdges *edges)
488 AdgEdgesPrivate *data = edges->data;
489 data->source = NULL;
492 static void
493 _adg_clear_cairo_path(AdgEdges *edges)
495 AdgEdgesPrivate *data = edges->data;
497 if (data->cairo.array != NULL) {
498 g_array_free(data->cairo.array, TRUE);
499 data->cairo.array = NULL;
502 data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA;
503 data->cairo.path.data = NULL;
504 data->cairo.path.num_data = 0;
508 * _adg_get_vertices:
509 * @vertices: a #GSList
510 * @segment: a #CpmlSegment
511 * @threshold: a theshold value
513 * Collects a list of #CpmlPair corners where the angle has a minimum
514 * threshold incidence of @threshold. The threshold is considered as
515 * the squared distance between the two unit vectors, the one before
516 * and the one after every corner.
518 * Returns: the original #GSList with new vertices appended.
520 * Since: 1.0
522 static GSList *
523 _adg_get_vertices(GSList *vertices, CpmlSegment *segment, gdouble threshold)
525 CpmlPrimitive primitive;
526 CpmlVector old, new;
527 CpmlPair pair;
529 cpml_primitive_from_segment(&primitive, segment);
530 /* The first vector starts undefined, so it will always be
531 * included (the squared distance between any vector and an
532 * undefined vector will always be greater than threshold) */
533 old.x = old.y = 0;
535 do {
536 cpml_vector_set_length(&old, 1);
537 cpml_primitive_put_vector_at(&primitive, 0, &new);
538 cpml_vector_set_length(&new, 1);
540 /* Vertical vectors are always added, as they represent
541 * a vertical side and could be filleted, thus skipping
542 * the edge detection */
543 if (new.x == 0 ||
544 cpml_pair_squared_distance(&old, &new) > threshold) {
545 cpml_primitive_put_pair_at(&primitive, 0, &pair);
546 vertices = g_slist_append(vertices, cpml_pair_dup(&pair));
549 cpml_primitive_put_vector_at(&primitive, 1, &old);
550 } while (cpml_primitive_next(&primitive));
552 return vertices;
555 /* Removes adjacent vertices lying on the same edge */
556 static GSList *
557 _adg_optimize_vertices(GSList *vertices)
559 GSList *vertex, *old_vertex;
560 CpmlPair *pair, *old_pair;
562 /* Check for empty list */
563 if (vertices == NULL)
564 return vertices;
566 old_vertex = vertices;
568 while ((vertex = old_vertex->next) != NULL) {
569 pair = vertex->data;
570 old_pair = old_vertex->data;
572 if (pair->x != old_pair->x) {
573 old_vertex = vertex;
574 continue;
577 if (old_pair->y < pair->y) {
578 /* Preserve the old vertex and remove the current one */
579 g_free(pair);
580 vertices = g_slist_delete_link(vertices, vertex);
581 } else {
582 /* Preserve the current vertex and remove the old one */
583 g_free(old_pair);
584 vertices = g_slist_delete_link(vertices, old_vertex);
585 old_vertex = vertex;
589 return vertices;
592 static GArray *
593 _adg_path_build(const GSList *vertices)
595 cairo_path_data_t line[4];
596 GArray *array;
597 const GSList *vertex, *vertex2;
598 const CpmlPair *pair, *pair2;
600 line[0].header.type = CPML_MOVE;
601 line[0].header.length = 2;
602 line[2].header.type = CPML_LINE;
603 line[2].header.length = 2;
605 array = g_array_new(FALSE, FALSE, sizeof(cairo_path_data_t));
606 vertex = vertices;
608 while (vertex != NULL) {
609 pair = vertex->data;
610 vertex = vertex->next;
611 vertex2 = vertex;
613 while (vertex2 != NULL) {
614 pair2 = vertex2->data;
616 if (pair->x == pair2->x) {
617 /* Opposite vertex found: append a line in the path
618 * and quit from this loop */
619 cpml_pair_to_cairo(pair, &line[1]);
620 cpml_pair_to_cairo(pair2, &line[3]);
621 array = g_array_append_vals(array, line, G_N_ELEMENTS(line));
622 break;
625 vertex2 = vertex2->next;
629 return array;
632 static void
633 _adg_path_transform(GArray *path_data, const cairo_matrix_t *map)
635 guint n;
636 cairo_path_data_t *data;
638 /* Only the odd items are transformed: the even ones are either
639 * header items, CPML_MOVE and CPML_LINE alternatively */
640 for (n = 1; n < path_data->len; n += 2) {
641 data = &g_array_index(path_data, cairo_path_data_t, n);
642 cairo_matrix_transform_point(map, &data->point.x, &data->point.y);