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.
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.
44 * All fields are private and should not be used directly.
45 * Use its public methods instead.
51 #include "adg-internal.h"
52 #include "adg-model.h"
53 #include "adg-trail.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
)
75 static void _adg_dispose (GObject
*object
);
76 static void _adg_finalize (GObject
*object
);
77 static void _adg_get_property (GObject
*object
,
81 static void _adg_set_property (GObject
*object
,
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
,
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
);
99 adg_edges_class_init(AdgEdgesClass
*klass
)
101 GObjectClass
*gobject_class
;
102 AdgModelClass
*model_class
;
103 AdgTrailClass
*trail_class
;
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",
121 P_("The source from which the edges should be computed from"),
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",
128 P_("The angle of the axis of the source trail: it is implied this axis passes through (0,0)"),
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
,
138 g_object_class_install_property(gobject_class
, PROP_CRITICAL_ANGLE
, param
);
142 adg_edges_init(AdgEdges
*edges
)
144 AdgEdgesPrivate
*data
= adg_edges_get_instance_private(edges
);
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
;
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
);
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
);
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
);
181 g_value_set_object(value
, data
->source
);
183 case PROP_AXIS_ANGLE
:
184 g_value_set_double(value
, data
->axis_angle
);
186 case PROP_CRITICAL_ANGLE
:
187 g_value_set_double(value
, data
->critical_angle
);
190 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
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
;
206 tmp_pointer
= data
->source
;
207 data
->source
= g_value_get_object(value
);
209 if (tmp_pointer
!= data
->source
) {
211 g_object_weak_ref((GObject
*) data
->source
,
212 (GWeakNotify
) _adg_unset_source
, object
);
214 g_object_weak_unref((GObject
*) tmp_pointer
,
215 (GWeakNotify
) _adg_unset_source
, object
);
218 _adg_clear((AdgModel
*) object
);
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
);
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
);
235 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
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
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
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
283 * Returns: (transfer none): the requested #AdgTrail or <constant>NULL</constant> on errors.
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
);
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.
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.
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
344 * Returns: the value (in radians) of the axis angle
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.
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
388 * Returns: the value (in radians) of the critical angle
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
;
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
)
417 AdgEdgesPrivate
*data
;
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
) {
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;
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
;
472 _adg_unset_source(AdgEdges
*edges
)
474 AdgEdgesPrivate
*data
= adg_edges_get_instance_private(edges
);
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;
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.
509 _adg_get_vertices(GSList
*vertices
, CpmlSegment
*segment
, gdouble threshold
)
511 CpmlPrimitive primitive
;
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) */
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 */
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
));
541 /* Removes adjacent vertices lying on the same edge */
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
)
552 old_vertex
= vertices
;
554 while ((vertex
= old_vertex
->next
) != NULL
) {
556 old_pair
= old_vertex
->data
;
558 if (pair
->x
!= old_pair
->x
) {
563 if (old_pair
->y
< pair
->y
) {
564 /* Preserve the old vertex and remove the current one */
566 vertices
= g_slist_delete_link(vertices
, vertex
);
568 /* Preserve the current vertex and remove the old one */
570 vertices
= g_slist_delete_link(vertices
, old_vertex
);
579 _adg_path_build(const GSList
*vertices
)
581 cairo_path_data_t line
[4];
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
));
594 while (vertex
!= NULL
) {
596 vertex
= vertex
->next
;
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
));
611 vertex2
= vertex2
->next
;
619 _adg_path_transform(GArray
*path_data
, const cairo_matrix_t
*map
)
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
);