[AdgEdges] Prefixed internal symbols
[adg.git] / src / adg / adg-edges.c
blob7bd21053f07b9b4e403d54b40928da858d45b834
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009,2010 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 * corners can be easily computed.
30 * <important>
31 * <title>TODO</title>
32 * <itemizedlist>
33 * <listitem>Actually the edges of the source trail are always computed
34 * taking the y=0 axis as the origin: anyway, it would be not
35 * too hard to apply an arbitrary transformation to aling the
36 * trail on the y=0 axis, compute the edges as usual and apply
37 * the inverse transformation to the result.</listitem>
38 * </itemizedlist>
39 * </important>
40 **/
42 /**
43 * AdgEdges:
45 * All fields are private and should not be used directly.
46 * Use its public methods instead.
47 **/
50 #include "adg-internal.h"
51 #include "adg-edges.h"
52 #include "adg-edges-private.h"
53 #include "adg-pair.h"
55 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_edges_parent_class)
56 #define _ADG_OLD_MODEL_CLASS ((AdgModelClass *) adg_edges_parent_class)
59 G_DEFINE_TYPE(AdgEdges, adg_edges, ADG_TYPE_TRAIL);
61 enum {
62 PROP_0,
63 PROP_SOURCE,
64 PROP_CRITICAL_ANGLE
68 static void _adg_dispose (GObject *object);
69 static void _adg_finalize (GObject *object);
70 static void _adg_get_property (GObject *object,
71 guint param_id,
72 GValue *value,
73 GParamSpec *pspec);
74 static void _adg_set_property (GObject *object,
75 guint param_id,
76 const GValue *value,
77 GParamSpec *pspec);
78 static void _adg_clear (AdgModel *model);
79 static CpmlPath * _adg_get_cpml_path (AdgTrail *trail);
80 static void _adg_unset_source (AdgEdges *edges);
81 static void _adg_clear_cpml_path (AdgEdges *edges);
82 static GSList * _adg_get_vertices (CpmlSegment *segment,
83 gdouble threshold);
84 static GSList * _adg_optimize_vertices (GSList *vertices);
85 static GArray * _adg_build_array (const GSList *vertices);
88 static void
89 adg_edges_class_init(AdgEdgesClass *klass)
91 GObjectClass *gobject_class;
92 AdgModelClass *model_class;
93 AdgTrailClass *trail_class;
94 GParamSpec *param;
96 gobject_class = (GObjectClass *) klass;
97 model_class = (AdgModelClass *) klass;
98 trail_class = (AdgTrailClass *) klass;
100 g_type_class_add_private(klass, sizeof(AdgEdgesPrivate));
102 gobject_class->dispose = _adg_dispose;
103 gobject_class->finalize = _adg_finalize;
104 gobject_class->get_property = _adg_get_property;
105 gobject_class->set_property = _adg_set_property;
107 model_class->clear = _adg_clear;
109 trail_class->get_cpml_path = _adg_get_cpml_path;
111 param = g_param_spec_object("source",
112 P_("Source"),
113 P_("The source where the edges should be computed from"),
114 ADG_TYPE_TRAIL,
115 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
116 g_object_class_install_property(gobject_class, PROP_SOURCE, param);
118 param = g_param_spec_double("critical-angle",
119 P_("Critical Angle"),
120 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"),
121 0, G_PI, G_PI / 45,
122 G_PARAM_READWRITE);
123 g_object_class_install_property(gobject_class, PROP_CRITICAL_ANGLE, param);
126 static void
127 adg_edges_init(AdgEdges *edges)
129 AdgEdgesPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(edges, ADG_TYPE_EDGES,
130 AdgEdgesPrivate);
132 data->source = NULL;
133 data->threshold = sin(G_PI / 45);
134 data->threshold *= data->threshold * 2;
136 data->cpml.path.status = CAIRO_STATUS_INVALID_PATH_DATA;
137 data->cpml.array = NULL;
139 edges->data = data;
142 static void
143 _adg_dispose(GObject *object)
145 AdgEdges *edges = (AdgEdges *) object;
147 adg_edges_set_source(edges, NULL);
149 if (_ADG_OLD_OBJECT_CLASS->dispose)
150 _ADG_OLD_OBJECT_CLASS->dispose(object);
153 static void
154 _adg_finalize(GObject *object)
156 _adg_clear_cpml_path((AdgEdges *) object);
158 if (_ADG_OLD_OBJECT_CLASS->finalize)
159 _ADG_OLD_OBJECT_CLASS->finalize(object);
162 static void
163 _adg_get_property(GObject *object, guint prop_id,
164 GValue *value, GParamSpec *pspec)
166 AdgEdges *edges;
167 AdgEdgesPrivate *data;
169 edges = (AdgEdges *) object;
170 data = edges->data;
172 switch (prop_id) {
173 case PROP_SOURCE:
174 g_value_set_object(value, data->source);
175 break;
176 case PROP_CRITICAL_ANGLE:
177 g_value_set_double(value, adg_edges_get_critical_angle(edges));
178 break;
179 default:
180 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
181 break;
185 static void
186 _adg_set_property(GObject *object, guint prop_id,
187 const GValue *value, GParamSpec *pspec)
189 AdgEdgesPrivate *data = ((AdgEdges *) object)->data;
191 switch (prop_id) {
192 case PROP_SOURCE:
193 if (data->source != NULL)
194 g_object_weak_unref((GObject *) data->source,
195 (GWeakNotify) _adg_unset_source, object);
197 data->source = g_value_get_object(value);
198 _adg_clear((AdgModel *) object);
200 if (data->source != NULL)
201 g_object_weak_ref((GObject *) data->source,
202 (GWeakNotify) _adg_unset_source, object);
203 break;
204 case PROP_CRITICAL_ANGLE:
205 data->threshold = sin(g_value_get_double(value));
206 data->threshold *= data->threshold * 2;
207 break;
208 default:
209 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
210 break;
216 * adg_edges_new:
218 * Creates a new undefined model to keep track of the edges of
219 * another model. You should at least set the referred #AdgTrail
220 * with adg_edges_set_source().
222 * Returns: the newly created edges model
224 AdgEdges *
225 adg_edges_new(void)
227 return g_object_new(ADG_TYPE_EDGES, NULL);
231 * adg_edges_new_with_source:
233 * Creates a new edges model explicitely specifying the source trail.
235 * Returns: the newly created edges model
237 AdgEdges *
238 adg_edges_new_with_source(AdgTrail *source)
240 return g_object_new(ADG_TYPE_EDGES, "source", source, NULL);
244 * adg_edges_get_source:
245 * @edges: an #AdgEdges
247 * Gets the source #AdgTrail of this @edges model.
249 * Returns: the requested #AdgTrail or %NULL on errors
251 AdgTrail *
252 adg_edges_get_source(AdgEdges *edges)
254 AdgEdgesPrivate *data;
256 g_return_val_if_fail(ADG_IS_EDGES(edges), NULL);
258 data = edges->data;
260 return data->source;
264 * adg_edges_set_source:
265 * @edges: an #AdgEdges
266 * @source: the new source #AdgTrail
268 * Sets @source as the source trail for @edges.
270 void
271 adg_edges_set_source(AdgEdges *edges, AdgTrail *source)
273 g_return_if_fail(ADG_IS_EDGES(edges));
274 g_object_set(edges, "source", source, NULL);
278 * adg_edges_get_critical_angle:
279 * @edges: an #AdgEdges
281 * Gets the current critical angle of @edges. The angle is internally
282 * converted to a threshold value, so the returned angle could be not
283 * exactly what set throught adg_edges_set_critical_angle().
285 * Returns: the value (in radians) of the critical angle
287 gdouble
288 adg_edges_get_critical_angle(AdgEdges *edges)
290 AdgEdgesPrivate *data;
292 g_return_val_if_fail(ADG_IS_EDGES(edges), 0);
294 data = edges->data;
296 return asin(sqrt(data->threshold / 2));
300 * adg_edges_set_critical_angle:
301 * @edges: an #AdgEdges
302 * @angle: the new angle (in radians)
304 * Sets a new critical angle on @edges. The critical angle defines
305 * what corner should generate an edge and what not. Typical values
306 * are close to %0, being %0 the lowest angle where all the corners
307 * generate an edge.
309 void
310 adg_edges_set_critical_angle(AdgEdges *edges, gdouble angle)
312 g_return_if_fail(ADG_IS_EDGES(edges));
313 g_object_set(edges, "critical-angle", angle, NULL);
317 static void
318 _adg_clear(AdgModel *model)
320 _adg_clear_cpml_path((AdgEdges *) model);
322 if (_ADG_OLD_MODEL_CLASS->clear)
323 _ADG_OLD_MODEL_CLASS->clear(model);
326 static CpmlPath *
327 _adg_get_cpml_path(AdgTrail *trail)
329 AdgEdges *edges;
330 AdgEdgesPrivate *data;
332 edges = (AdgEdges *) trail;
333 data = edges->data;
335 /* Check for cached path */
336 if (data->cpml.path.status == CAIRO_STATUS_SUCCESS)
337 return &data->cpml.path;
339 _adg_clear_cpml_path((AdgEdges *) trail);
341 if (data->source != NULL) {
342 CpmlSegment segment;
343 GSList *vertices;
345 adg_trail_put_segment(data->source, 1, &segment);
346 vertices = _adg_get_vertices(&segment, 0.01);
347 vertices = _adg_optimize_vertices(vertices);
348 data->cpml.array = _adg_build_array(vertices);
350 g_slist_foreach(vertices, (GFunc) g_free, NULL);
351 g_slist_free(vertices);
353 data->cpml.path.status = CAIRO_STATUS_SUCCESS;
354 data->cpml.path.data = (cairo_path_data_t *) (data->cpml.array)->data;
355 data->cpml.path.num_data = (data->cpml.array)->len;
358 return &data->cpml.path;
361 static void
362 _adg_unset_source(AdgEdges *edges)
364 g_object_set(edges, "source", NULL, NULL);
367 static void
368 _adg_clear_cpml_path(AdgEdges *edges)
370 AdgEdgesPrivate *data = edges->data;
372 if (data->cpml.array != NULL) {
373 g_array_free(data->cpml.array, TRUE);
374 data->cpml.array = NULL;
377 data->cpml.path.status = CAIRO_STATUS_INVALID_PATH_DATA;
378 data->cpml.path.data = NULL;
379 data->cpml.path.num_data = 0;
382 static GSList *
383 _adg_get_vertices(CpmlSegment *segment, gdouble threshold)
385 GSList *vertices;
386 CpmlPrimitive primitive;
387 CpmlVector old, new;
389 vertices = NULL;
390 cpml_primitive_from_segment(&primitive, segment);
391 old.x = old.y = 0;
393 do {
394 /* The first vector and the undefined ones
395 * must always be skipped */
396 if (old.x != 0 || old.y != 0) {
397 cpml_vector_set_length(&old, 1);
398 cpml_primitive_put_vector_at(&primitive, 0, &new);
399 cpml_vector_set_length(&new, 1);
401 /* Vertical vectors are always added, as they represent
402 * a vertical side and could be filleted, thus skipping
403 * the edge detection */
404 if (new.x == 0 ||
405 cpml_pair_squared_distance(&old, &new) > threshold) {
406 AdgPair pair;
408 cpml_primitive_put_pair_at(&primitive, 0, &pair);
409 vertices = g_slist_prepend(vertices, adg_pair_dup(&pair));
413 cpml_primitive_put_vector_at(&primitive, 1, &old);
414 } while (cpml_primitive_next(&primitive));
416 return g_slist_reverse(vertices);
419 /* Removes adjacent vertices lying on the same edge */
420 static GSList *
421 _adg_optimize_vertices(GSList *vertices)
423 GSList *vertex, *old_vertex;
424 AdgPair *pair, *old_pair;
426 /* Check for empty list */
427 if (vertices == NULL)
428 return vertices;
430 old_vertex = vertices;
432 while ((vertex = old_vertex->next) != NULL) {
433 pair = vertex->data;
434 old_pair = old_vertex->data;
436 if (pair->x != old_pair->x) {
437 old_vertex = vertex;
438 continue;
441 if (old_pair->y < pair->y) {
442 /* Preserve the old vertex and remove the current one */
443 g_free(pair);
444 vertices = g_slist_delete_link(vertices, vertex);
445 } else {
446 /* Preserve the current vertex and remove the old one */
447 g_free(old_pair);
448 vertices = g_slist_delete_link(vertices, old_vertex);
449 old_vertex = vertex;
453 return vertices;
456 static GArray *
457 _adg_build_array(const GSList *vertices)
459 cairo_path_data_t line[4];
460 GArray *array;
461 const GSList *vertex, *vertex2;
462 const AdgPair *pair, *pair2;
464 line[0].header.type = CPML_MOVE;
465 line[0].header.length = 2;
466 line[2].header.type = CPML_LINE;
467 line[2].header.length = 2;
469 array = g_array_new(FALSE, FALSE, sizeof(cairo_path_data_t));
470 vertex = vertices;
472 while (vertex != NULL) {
473 pair = vertex->data;
474 vertex = vertex->next;
475 vertex2 = vertex;
477 while (vertex2 != NULL) {
478 pair2 = vertex2->data;
480 if (pair->x == pair2->x) {
481 /* Opposite vertex found: append a line in the path
482 * and quit from this loop */
483 cpml_pair_to_cairo(pair, &line[1]);
484 cpml_pair_to_cairo(pair2, &line[3]);
485 array = g_array_append_vals(array, line, G_N_ELEMENTS(line));
486 break;
489 vertex2 = vertex2->next;
493 return array;