doc: update copyright line for 2021
[adg.git] / src / adg / adg-edges.c
blobf9a68fe5939a124e9f31e6d2423733bc926b3a67
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2021 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_WITH_PRIVATE(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 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, DEFAULT_CRITICAL_ANGLE,
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 = adg_edges_get_instance_private(edges);
145 data->source = NULL;
146 data->critical_angle = DEFAULT_CRITICAL_ANGLE;
147 data->axis_angle = 0;
148 data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA;
149 data->cairo.array = NULL;
152 static void
153 _adg_dispose(GObject *object)
155 AdgEdges *edges = (AdgEdges *) object;
157 adg_edges_set_source(edges, NULL);
159 if (_ADG_OLD_OBJECT_CLASS->dispose != NULL)
160 _ADG_OLD_OBJECT_CLASS->dispose(object);
163 static void
164 _adg_finalize(GObject *object)
166 _adg_clear_cairo_path((AdgEdges *) object);
168 if (_ADG_OLD_OBJECT_CLASS->finalize != NULL)
169 _ADG_OLD_OBJECT_CLASS->finalize(object);
172 static void
173 _adg_get_property(GObject *object, guint prop_id,
174 GValue *value, GParamSpec *pspec)
176 AdgEdges *edges = (AdgEdges *) object;
177 AdgEdgesPrivate *data = adg_edges_get_instance_private(edges);
179 switch (prop_id) {
180 case PROP_SOURCE:
181 g_value_set_object(value, data->source);
182 break;
183 case PROP_AXIS_ANGLE:
184 g_value_set_double(value, data->axis_angle);
185 break;
186 case PROP_CRITICAL_ANGLE:
187 g_value_set_double(value, data->critical_angle);
188 break;
189 default:
190 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
191 break;
195 static void
196 _adg_set_property(GObject *object, guint prop_id,
197 const GValue *value, GParamSpec *pspec)
199 AdgEdges *edges = (AdgEdges *) object;
200 AdgEdgesPrivate *data = adg_edges_get_instance_private(edges);
201 gpointer tmp_pointer;
202 gdouble tmp_double;
204 switch (prop_id) {
205 case PROP_SOURCE:
206 tmp_pointer = data->source;
207 data->source = g_value_get_object(value);
209 if (tmp_pointer != data->source) {
210 if (data->source)
211 g_object_weak_ref((GObject *) data->source,
212 (GWeakNotify) _adg_unset_source, object);
213 if (tmp_pointer)
214 g_object_weak_unref((GObject *) tmp_pointer,
215 (GWeakNotify) _adg_unset_source, object);
218 _adg_clear((AdgModel *) object);
219 break;
220 case PROP_AXIS_ANGLE:
221 tmp_double = g_value_get_double(value);
222 if (data->axis_angle != tmp_double) {
223 data->axis_angle = tmp_double;
224 _adg_clear_cairo_path(edges);
226 break;
227 case PROP_CRITICAL_ANGLE:
228 tmp_double = g_value_get_double(value);
229 if (data->critical_angle != tmp_double) {
230 data->critical_angle = tmp_double;
231 _adg_clear_cairo_path(edges);
233 break;
234 default:
235 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
236 break;
242 * adg_edges_new:
244 * Creates a new undefined model to keep track of the edges of
245 * another model. You should at least set the referred #AdgTrail
246 * with adg_edges_set_source().
248 * Returns: the newly created edges model
250 * Since: 1.0
252 AdgEdges *
253 adg_edges_new(void)
255 return g_object_new(ADG_TYPE_EDGES, NULL);
259 * adg_edges_new_with_source:
260 * @source: (transfer none): the new source #AdgTrail
262 * Creates a new edges model explicitely specifying the source trail.
263 * The returned object will own a weak reference on @source.
265 * Returns: the newly created edges model
267 * Since: 1.0
269 AdgEdges *
270 adg_edges_new_with_source(AdgTrail *source)
272 return g_object_new(ADG_TYPE_EDGES, "source", source, NULL);
276 * adg_edges_get_source:
277 * @edges: an #AdgEdges
279 * Gets the source #AdgTrail of this @edges model.
280 * The returned object is owned by @edges and should not be
281 * freed or modified.
283 * Returns: (transfer none): the requested #AdgTrail or <constant>NULL</constant> on errors.
285 * Since: 1.0
287 AdgTrail *
288 adg_edges_get_source(AdgEdges *edges)
290 AdgEdgesPrivate *data;
292 g_return_val_if_fail(ADG_IS_EDGES(edges), NULL);
294 data = adg_edges_get_instance_private(edges);
295 return data->source;
299 * adg_edges_set_source:
300 * @edges: an #AdgEdges
301 * @source: (transfer none): the new source #AdgTrail
303 * Sets @source as the source trail for @edges.
304 * After the call, @edges will own a weak reference on @source.
306 * Since: 1.0
308 void
309 adg_edges_set_source(AdgEdges *edges, AdgTrail *source)
311 g_return_if_fail(ADG_IS_EDGES(edges));
312 g_object_set(edges, "source", source, NULL);
316 * adg_edges_set_axis_angle:
317 * @edges: an #AdgEdges
318 * @angle: the new angle (in radians)
320 * Sets the axis angle of @edges to @angle, basically setting
321 * the #AdgEdges:axis-angle property. All the resulting edge
322 * lines will be normal to this axis.
324 * It is implied the axis will pass through the (0,0) point,
325 * so the underlying trail should be constructed accordingly.
327 * Since: 1.0
329 void
330 adg_edges_set_axis_angle(AdgEdges *edges, gdouble angle)
332 g_return_if_fail(ADG_IS_EDGES(edges));
333 g_object_set(edges, "axis-angle", angle, NULL);
337 * adg_edges_get_axis_angle:
338 * @edges: an #AdgEdges
340 * Gets the angle of the supposed axis of @edges. Refer to
341 * adg_edges_set_axis_angle() for details of what this parameter
342 * is used for.
344 * Returns: the value (in radians) of the axis angle
346 * Since: 1.0
348 gdouble
349 adg_edges_get_axis_angle(AdgEdges *edges)
351 AdgEdgesPrivate *data;
353 g_return_val_if_fail(ADG_IS_EDGES(edges), 0);
355 data = adg_edges_get_instance_private(edges);
356 return data->axis_angle;
360 * adg_edges_set_critical_angle:
361 * @edges: an #AdgEdges
362 * @angle: the new angle (in radians)
364 * Sets the critical angle of @edges to @angle, basically setting
365 * the #AdgEdges:critical-angle property.
367 * The critical angle defines what corner should generate an edge and
368 * what not. Typical values are close to 0, being 0 the lowest angle
369 * where every corner generates an edge.
371 * Since: 1.0
373 void
374 adg_edges_set_critical_angle(AdgEdges *edges, gdouble angle)
376 g_return_if_fail(ADG_IS_EDGES(edges));
377 g_object_set(edges, "critical-angle", angle, NULL);
381 * adg_edges_get_critical_angle:
382 * @edges: an #AdgEdges
384 * Gets the current critical angle of @edges. Refer to
385 * adg_edges_set_critical_angle() for details of what this parameter
386 * is used for.
388 * Returns: the value (in radians) of the critical angle
390 * Since: 1.0
392 gdouble
393 adg_edges_get_critical_angle(AdgEdges *edges)
395 AdgEdgesPrivate *data;
397 g_return_val_if_fail(ADG_IS_EDGES(edges), 0);
399 data = adg_edges_get_instance_private(edges);
400 return data->critical_angle;
404 static void
405 _adg_clear(AdgModel *model)
407 _adg_clear_cairo_path((AdgEdges *) model);
409 if (_ADG_OLD_MODEL_CLASS->clear != NULL)
410 _ADG_OLD_MODEL_CLASS->clear(model);
413 static cairo_path_t *
414 _adg_get_cairo_path(AdgTrail *trail)
416 AdgEdges *edges;
417 AdgEdgesPrivate *data;
418 gdouble threshold;
419 CpmlSegment segment;
420 GSList *vertices;
421 cairo_matrix_t map;
423 edges = (AdgEdges *) trail;
424 data = adg_edges_get_instance_private(edges);
426 /* Check for cached path */
427 if (data->cairo.path.status == CAIRO_STATUS_SUCCESS)
428 return &data->cairo.path;
430 _adg_clear_cairo_path((AdgEdges *) trail);
432 if (data->source != NULL) {
433 gint n;
435 /* The threshold is squared because the _adg_get_vertices()
436 * function uses cpml_pair_squared_distance() against the
437 * two vectors of every corner to avoid sqrt()ing everything */
438 threshold = sin(data->critical_angle);
439 threshold *= threshold * 2;
441 vertices = NULL;
442 for (n = 1; adg_trail_put_segment(data->source, n, &segment); ++ n) {
443 vertices = _adg_get_vertices(vertices, &segment, threshold);
446 /* Rotate all the vertices so the axis will always be on y=0:
447 * this is mainly needed to not complicate the _adg_path_build()
448 * code which assumes the y=0 axis is in effect */
449 cairo_matrix_init_rotate(&map, -data->axis_angle);
450 g_slist_foreach(vertices, (GFunc) cpml_pair_transform, &map);
452 vertices = _adg_optimize_vertices(vertices);
453 data->cairo.array = _adg_path_build(vertices);
455 g_slist_foreach(vertices, (GFunc) g_free, NULL);
456 g_slist_free(vertices);
458 /* Reapply the inverse of the previous transformation to
459 * move the vertices to their original positions */
460 cairo_matrix_invert(&map);
461 _adg_path_transform(data->cairo.array, &map);
463 data->cairo.path.status = CAIRO_STATUS_SUCCESS;
464 data->cairo.path.data = (cairo_path_data_t *) (data->cairo.array)->data;
465 data->cairo.path.num_data = (data->cairo.array)->len;
468 return &data->cairo.path;
471 static void
472 _adg_unset_source(AdgEdges *edges)
474 AdgEdgesPrivate *data = adg_edges_get_instance_private(edges);
475 data->source = NULL;
478 static void
479 _adg_clear_cairo_path(AdgEdges *edges)
481 AdgEdgesPrivate *data = adg_edges_get_instance_private(edges);
483 if (data->cairo.array != NULL) {
484 g_array_free(data->cairo.array, TRUE);
485 data->cairo.array = NULL;
488 data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA;
489 data->cairo.path.data = NULL;
490 data->cairo.path.num_data = 0;
494 * _adg_get_vertices:
495 * @vertices: a #GSList
496 * @segment: a #CpmlSegment
497 * @threshold: a theshold value
499 * Collects a list of #CpmlPair corners where the angle has a minimum
500 * threshold incidence of @threshold. The threshold is considered as
501 * the squared distance between the two unit vectors, the one before
502 * and the one after every corner.
504 * Returns: the original #GSList with new vertices appended.
506 * Since: 1.0
508 static GSList *
509 _adg_get_vertices(GSList *vertices, CpmlSegment *segment, gdouble threshold)
511 CpmlPrimitive primitive;
512 CpmlVector old, new;
513 CpmlPair pair;
515 cpml_primitive_from_segment(&primitive, segment);
516 /* The first vector starts undefined, so it will always be
517 * included (the squared distance between any vector and an
518 * undefined vector will always be greater than threshold) */
519 old.x = old.y = 0;
521 do {
522 cpml_vector_set_length(&old, 1);
523 cpml_primitive_put_vector_at(&primitive, 0, &new);
524 cpml_vector_set_length(&new, 1);
526 /* Vertical vectors are always added, as they represent
527 * a vertical side and could be filleted, thus skipping
528 * the edge detection */
529 if (new.x == 0 ||
530 cpml_pair_squared_distance(&old, &new) > threshold) {
531 cpml_primitive_put_pair_at(&primitive, 0, &pair);
532 vertices = g_slist_append(vertices, cpml_pair_dup(&pair));
535 cpml_primitive_put_vector_at(&primitive, 1, &old);
536 } while (cpml_primitive_next(&primitive));
538 return vertices;
541 /* Removes adjacent vertices lying on the same edge */
542 static GSList *
543 _adg_optimize_vertices(GSList *vertices)
545 GSList *vertex, *old_vertex;
546 CpmlPair *pair, *old_pair;
548 /* Check for empty list */
549 if (vertices == NULL)
550 return vertices;
552 old_vertex = vertices;
554 while ((vertex = old_vertex->next) != NULL) {
555 pair = vertex->data;
556 old_pair = old_vertex->data;
558 if (pair->x != old_pair->x) {
559 old_vertex = vertex;
560 continue;
563 if (old_pair->y < pair->y) {
564 /* Preserve the old vertex and remove the current one */
565 g_free(pair);
566 vertices = g_slist_delete_link(vertices, vertex);
567 } else {
568 /* Preserve the current vertex and remove the old one */
569 g_free(old_pair);
570 vertices = g_slist_delete_link(vertices, old_vertex);
571 old_vertex = vertex;
575 return vertices;
578 static GArray *
579 _adg_path_build(const GSList *vertices)
581 cairo_path_data_t line[4];
582 GArray *array;
583 const GSList *vertex, *vertex2;
584 const CpmlPair *pair, *pair2;
586 line[0].header.type = CPML_MOVE;
587 line[0].header.length = 2;
588 line[2].header.type = CPML_LINE;
589 line[2].header.length = 2;
591 array = g_array_new(FALSE, FALSE, sizeof(cairo_path_data_t));
592 vertex = vertices;
594 while (vertex != NULL) {
595 pair = vertex->data;
596 vertex = vertex->next;
597 vertex2 = vertex;
599 while (vertex2 != NULL) {
600 pair2 = vertex2->data;
602 if (pair->x == pair2->x) {
603 /* Opposite vertex found: append a line in the path
604 * and quit from this loop */
605 cpml_pair_to_cairo(pair, &line[1]);
606 cpml_pair_to_cairo(pair2, &line[3]);
607 array = g_array_append_vals(array, line, G_N_ELEMENTS(line));
608 break;
611 vertex2 = vertex2->next;
615 return array;
618 static void
619 _adg_path_transform(GArray *path_data, const cairo_matrix_t *map)
621 guint n;
622 cairo_path_data_t *data;
624 /* Only the odd items are transformed: the even ones are either
625 * header items, CPML_MOVE and CPML_LINE alternatively */
626 for (n = 1; n < path_data->len; n += 2) {
627 data = &g_array_index(path_data, cairo_path_data_t, n);
628 cairo_matrix_transform_point(map, &data->point.x, &data->point.y);