[AdgEdges] Corrected bug in get_property()
[adg.git] / adg / adg-edges.c
blobc71e76039ac9f1a21e1a47f963dfe50a437b76c9
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 PARENT_OBJECT_CLASS ((GObjectClass *) adg_edges_parent_class)
56 #define PARENT_MODEL_CLASS ((AdgModelClass *) adg_edges_parent_class)
59 enum {
60 PROP_0,
61 PROP_SOURCE,
62 PROP_CRITICAL_ANGLE
66 static void dispose (GObject *object);
67 static void finalize (GObject *object);
68 static void get_property (GObject *object,
69 guint param_id,
70 GValue *value,
71 GParamSpec *pspec);
72 static void set_property (GObject *object,
73 guint param_id,
74 const GValue *value,
75 GParamSpec *pspec);
76 static void clear (AdgModel *model);
77 static CpmlPath * get_cpml_path (AdgTrail *trail);
78 static gboolean set_source (AdgEdges *edges,
79 AdgTrail *source);
80 static gboolean set_critical_angle (AdgEdges *edges,
81 gdouble angle);
82 static void unset_source (AdgEdges *edges);
83 static void clear_cpml_path (AdgEdges *edges);
84 static GSList * get_vertices (CpmlSegment *segment,
85 gdouble threshold);
86 static GSList * optimize_vertices (GSList *vertices);
87 static GArray * build_array (const GSList *vertices);
90 G_DEFINE_TYPE(AdgEdges, adg_edges, ADG_TYPE_TRAIL);
93 static void
94 adg_edges_class_init(AdgEdgesClass *klass)
96 GObjectClass *gobject_class;
97 AdgModelClass *model_class;
98 AdgTrailClass *trail_class;
99 GParamSpec *param;
101 gobject_class = (GObjectClass *) klass;
102 model_class = (AdgModelClass *) klass;
103 trail_class = (AdgTrailClass *) klass;
105 g_type_class_add_private(klass, sizeof(AdgEdgesPrivate));
107 gobject_class->dispose = dispose;
108 gobject_class->finalize = finalize;
109 gobject_class->get_property = get_property;
110 gobject_class->set_property = set_property;
112 model_class->clear = clear;
114 trail_class->get_cpml_path = get_cpml_path;
116 param = g_param_spec_object("source",
117 P_("Source"),
118 P_("The source where the edges should be computed from"),
119 ADG_TYPE_TRAIL,
120 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
121 g_object_class_install_property(gobject_class, PROP_SOURCE, param);
123 param = g_param_spec_double("critical-angle",
124 P_("Critical Angle"),
125 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"),
126 0, G_PI, G_PI / 45,
127 G_PARAM_READWRITE);
128 g_object_class_install_property(gobject_class, PROP_CRITICAL_ANGLE, param);
131 static void
132 adg_edges_init(AdgEdges *edges)
134 AdgEdgesPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(edges, ADG_TYPE_EDGES,
135 AdgEdgesPrivate);
137 data->source = NULL;
138 data->threshold = sin(G_PI / 45);
139 data->threshold *= data->threshold * 2;
141 data->cpml.path.status = CAIRO_STATUS_INVALID_PATH_DATA;
142 data->cpml.array = NULL;
144 edges->data = data;
147 static void
148 dispose(GObject *object)
150 AdgEdges *edges = (AdgEdges *) object;
152 adg_edges_set_source(edges, NULL);
154 if (PARENT_OBJECT_CLASS->dispose)
155 PARENT_OBJECT_CLASS->dispose(object);
158 static void
159 finalize(GObject *object)
161 clear_cpml_path((AdgEdges *) object);
163 if (PARENT_OBJECT_CLASS->finalize)
164 PARENT_OBJECT_CLASS->finalize(object);
167 static void
168 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
170 AdgEdges *edges;
171 AdgEdgesPrivate *data;
173 edges = (AdgEdges *) object;
174 data = edges->data;
176 switch (prop_id) {
177 case PROP_SOURCE:
178 g_value_set_object(value, data->source);
179 break;
180 case PROP_CRITICAL_ANGLE:
181 g_value_set_double(value, adg_edges_get_critical_angle(edges));
182 break;
183 default:
184 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
185 break;
189 static void
190 set_property(GObject *object, guint prop_id,
191 const GValue *value, GParamSpec *pspec)
193 AdgEdges *edges = (AdgEdges *) object;
195 switch (prop_id) {
196 case PROP_SOURCE:
197 set_source(edges, g_value_get_object(value));
198 break;
199 case PROP_CRITICAL_ANGLE:
200 set_critical_angle(edges, g_value_get_double(value));
201 break;
202 default:
203 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
204 break;
210 * adg_edges_new:
212 * Creates a new undefined model to keep track of the edges of
213 * another model. You should at least set the referred #AdgTrail
214 * with adg_edges_set_source().
216 * Returns: the newly created edges model
218 AdgEdges *
219 adg_edges_new(void)
221 return g_object_new(ADG_TYPE_EDGES, NULL);
225 * adg_edges_new_with_source:
227 * Creates a new edges model explicitely specifying the source trail.
229 * Returns: the newly created edges model
231 AdgEdges *
232 adg_edges_new_with_source(AdgTrail *source)
234 return g_object_new(ADG_TYPE_EDGES, "source", source, NULL);
238 * adg_edges_get_source:
239 * @edges: an #AdgEdges
241 * Gets the source #AdgTrail of this @edges model.
243 * Returns: the requested #AdgTrail or %NULL on errors
245 AdgTrail *
246 adg_edges_get_source(AdgEdges *edges)
248 AdgEdgesPrivate *data;
250 g_return_val_if_fail(ADG_IS_EDGES(edges), NULL);
252 data = edges->data;
254 return data->source;
258 * adg_edges_set_source:
259 * @edges: an #AdgEdges
260 * @source: the new source #AdgTrail
262 * Sets @source as the source trail for @edges.
264 void
265 adg_edges_set_source(AdgEdges *edges, AdgTrail *source)
267 g_return_if_fail(ADG_IS_EDGES(edges));
269 if (set_source(edges, source))
270 g_object_notify((GObject *) edges, "source");
274 * adg_edges_get_critical_angle:
275 * @edges: an #AdgEdges
277 * Gets the current critical angle of @edges. The angle is internally
278 * converted to a threshold value, so the returned angle could be not
279 * exactly what set throught adg_edges_set_critical_angle().
281 * Returns: the value (in radians) of the critical angle
283 gdouble
284 adg_edges_get_critical_angle(AdgEdges *edges)
286 AdgEdgesPrivate *data;
288 g_return_val_if_fail(ADG_IS_EDGES(edges), 0);
290 data = edges->data;
292 return asin(sqrt(data->threshold / 2));
296 * adg_edges_set_critical_angle:
297 * @edges: an #AdgEdges
298 * @angle: the new angle (in radians)
300 * Sets a new critical angle on @edges. The critical angle defines
301 * what corner should generate an edge and what not. Typical values
302 * are close to %0, being %0 the lowest angle where all the corners
303 * generate an edge.
305 void
306 adg_edges_set_critical_angle(AdgEdges *edges, gdouble angle)
308 g_return_if_fail(ADG_IS_EDGES(edges));
310 if (set_critical_angle(edges, angle))
311 g_object_notify((GObject *) edges, "critical-angle");
315 static void
316 clear(AdgModel *model)
318 clear_cpml_path((AdgEdges *) model);
320 if (PARENT_MODEL_CLASS->clear)
321 PARENT_MODEL_CLASS->clear(model);
324 static CpmlPath *
325 get_cpml_path(AdgTrail *trail)
327 AdgEdges *edges;
328 AdgEdgesPrivate *data;
330 edges = (AdgEdges *) trail;
331 data = edges->data;
333 /* Check for cached path */
334 if (data->cpml.path.status == CAIRO_STATUS_SUCCESS)
335 return &data->cpml.path;
337 clear_cpml_path((AdgEdges *) trail);
339 if (data->source != NULL) {
340 CpmlSegment segment;
341 GSList *vertices;
343 adg_trail_put_segment(data->source, 1, &segment);
344 vertices = get_vertices(&segment, 0.01);
345 vertices = optimize_vertices(vertices);
346 data->cpml.array = build_array(vertices);
348 g_slist_foreach(vertices, (GFunc) g_free, NULL);
349 g_slist_free(vertices);
351 data->cpml.path.status = CAIRO_STATUS_SUCCESS;
352 data->cpml.path.data = (cairo_path_data_t *) (data->cpml.array)->data;
353 data->cpml.path.num_data = (data->cpml.array)->len;
356 return &data->cpml.path;
359 static gboolean
360 set_source(AdgEdges *edges, AdgTrail *source)
362 AdgEntity *entity;
363 AdgEdgesPrivate *data;
365 entity = (AdgEntity *) edges;
366 data = edges->data;
368 if (source == data->source)
369 return FALSE;
371 if (data->source != NULL)
372 g_object_weak_unref((GObject *) data->source,
373 (GWeakNotify) unset_source, edges);
375 data->source = source;
376 clear((AdgModel *) edges);
378 if (data->source != NULL)
379 g_object_weak_ref((GObject *) data->source,
380 (GWeakNotify) unset_source, edges);
382 return TRUE;
385 static gboolean
386 set_critical_angle(AdgEdges *edges, gdouble angle)
388 AdgEdgesPrivate *data;
389 gdouble threshold;
391 data = edges->data;
392 threshold = sin(angle);
393 threshold *= threshold * 2;
395 if (threshold == data->threshold)
396 return FALSE;
398 data->threshold = threshold;
400 return TRUE;
403 static void
404 unset_source(AdgEdges *edges)
406 AdgEdgesPrivate *data = edges->data;
408 if (data->source != NULL)
409 set_source(edges, NULL);
412 static void
413 clear_cpml_path(AdgEdges *edges)
415 AdgEdgesPrivate *data = edges->data;
417 if (data->cpml.array != NULL) {
418 g_array_free(data->cpml.array, TRUE);
419 data->cpml.array = NULL;
422 data->cpml.path.status = CAIRO_STATUS_INVALID_PATH_DATA;
423 data->cpml.path.data = NULL;
424 data->cpml.path.num_data = 0;
427 static GSList *
428 get_vertices(CpmlSegment *segment, gdouble threshold)
430 GSList *vertices;
431 CpmlPrimitive primitive;
432 CpmlVector old, new;
434 vertices = NULL;
435 cpml_primitive_from_segment(&primitive, segment);
436 old.x = old.y = 0;
438 do {
439 /* The first vector and the undefined ones
440 * must always be skipped */
441 if (old.x != 0 || old.y != 0) {
442 cpml_vector_set_length(&old, 1);
443 cpml_primitive_put_vector_at(&primitive, 0, &new);
444 cpml_vector_set_length(&new, 1);
446 /* Vertical vectors are always added, as they represent
447 * a vertical side and could be filleted, thus skipping
448 * the edge detection */
449 if (new.x == 0 ||
450 cpml_pair_squared_distance(&old, &new) > threshold) {
451 AdgPair pair;
453 cpml_primitive_put_pair_at(&primitive, 0, &pair);
454 vertices = g_slist_prepend(vertices, adg_pair_dup(&pair));
458 cpml_primitive_put_vector_at(&primitive, 1, &old);
459 } while (cpml_primitive_next(&primitive));
461 return g_slist_reverse(vertices);
464 /* Removes adjacent vertices lying on the same edge */
465 static GSList *
466 optimize_vertices(GSList *vertices)
468 GSList *vertex, *old_vertex;
469 AdgPair *pair, *old_pair;
471 /* Check for empty list */
472 if (vertices == NULL)
473 return vertices;
475 old_vertex = vertices;
477 while ((vertex = old_vertex->next) != NULL) {
478 pair = vertex->data;
479 old_pair = old_vertex->data;
481 if (pair->x != old_pair->x) {
482 old_vertex = vertex;
483 continue;
486 if (old_pair->y < pair->y) {
487 /* Preserve the old vertex and remove the current one */
488 g_free(pair);
489 vertices = g_slist_delete_link(vertices, vertex);
490 } else {
491 /* Preserve the current vertex and remove the old one */
492 g_free(old_pair);
493 vertices = g_slist_delete_link(vertices, old_vertex);
494 old_vertex = vertex;
498 return vertices;
501 static GArray *
502 build_array(const GSList *vertices)
504 cairo_path_data_t line[4];
505 GArray *array;
506 const GSList *vertex, *vertex2;
507 const AdgPair *pair, *pair2;
509 line[0].header.type = CPML_MOVE;
510 line[0].header.length = 2;
511 line[2].header.type = CPML_LINE;
512 line[2].header.length = 2;
514 array = g_array_new(FALSE, FALSE, sizeof(cairo_path_data_t));
515 vertex = vertices;
517 while (vertex != NULL) {
518 pair = vertex->data;
519 vertex = vertex->next;
520 vertex2 = vertex;
522 while (vertex2 != NULL) {
523 pair2 = vertex2->data;
525 if (pair->x == pair2->x) {
526 /* Opposite vertex found: append a line in the path
527 * and quit from this loop */
528 cpml_pair_to_cairo(pair, &line[1]);
529 cpml_pair_to_cairo(pair2, &line[3]);
530 array = g_array_append_vals(array, line, G_N_ELEMENTS(line));
531 break;
534 vertex2 = vertex2->next;
538 return array;