[build] Let "make distcheck" work again
[adg.git] / adg / adg-edges.c
blob9241d9d108a2dc61f8ec7781fe5fc5a24f3149c7
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 g_return_val_if_fail(source == NULL || ADG_IS_TRAIL(source), FALSE);
367 entity = (AdgEntity *) edges;
368 data = edges->data;
370 if (source == data->source)
371 return FALSE;
373 if (data->source != NULL)
374 g_object_weak_unref((GObject *) data->source,
375 (GWeakNotify) unset_source, edges);
377 data->source = source;
378 clear((AdgModel *) edges);
380 if (data->source != NULL)
381 g_object_weak_ref((GObject *) data->source,
382 (GWeakNotify) unset_source, edges);
384 return TRUE;
387 static gboolean
388 set_critical_angle(AdgEdges *edges, gdouble angle)
390 AdgEdgesPrivate *data;
391 gdouble threshold;
393 data = edges->data;
394 threshold = sin(angle);
395 threshold *= threshold * 2;
397 if (threshold == data->threshold)
398 return FALSE;
400 data->threshold = threshold;
402 return TRUE;
405 static void
406 unset_source(AdgEdges *edges)
408 AdgEdgesPrivate *data = edges->data;
410 if (data->source != NULL)
411 set_source(edges, NULL);
414 static void
415 clear_cpml_path(AdgEdges *edges)
417 AdgEdgesPrivate *data = edges->data;
419 if (data->cpml.array != NULL) {
420 g_array_free(data->cpml.array, TRUE);
421 data->cpml.array = NULL;
424 data->cpml.path.status = CAIRO_STATUS_INVALID_PATH_DATA;
425 data->cpml.path.data = NULL;
426 data->cpml.path.num_data = 0;
429 static GSList *
430 get_vertices(CpmlSegment *segment, gdouble threshold)
432 GSList *vertices;
433 CpmlPrimitive primitive;
434 CpmlVector old, new;
436 vertices = NULL;
437 cpml_primitive_from_segment(&primitive, segment);
438 old.x = old.y = 0;
440 do {
441 /* The first vector and the undefined ones
442 * must always be skipped */
443 if (old.x != 0 || old.y != 0) {
444 cpml_vector_set_length(&old, 1);
445 cpml_primitive_put_vector_at(&primitive, 0, &new);
446 cpml_vector_set_length(&new, 1);
448 /* Vertical vectors are always added, as they represent
449 * a vertical side and could be filleted, thus skipping
450 * the edge detection */
451 if (new.x == 0 ||
452 cpml_pair_squared_distance(&old, &new) > threshold) {
453 AdgPair pair;
455 cpml_primitive_put_pair_at(&primitive, 0, &pair);
456 vertices = g_slist_prepend(vertices, adg_pair_dup(&pair));
460 cpml_primitive_put_vector_at(&primitive, 1, &old);
461 } while (cpml_primitive_next(&primitive));
463 return g_slist_reverse(vertices);
466 /* Removes adjacent vertices lying on the same edge */
467 static GSList *
468 optimize_vertices(GSList *vertices)
470 GSList *vertex, *old_vertex;
471 AdgPair *pair, *old_pair;
473 /* Check for empty list */
474 if (vertices == NULL)
475 return vertices;
477 old_vertex = vertices;
479 while ((vertex = old_vertex->next) != NULL) {
480 pair = vertex->data;
481 old_pair = old_vertex->data;
483 if (pair->x != old_pair->x) {
484 old_vertex = vertex;
485 continue;
488 if (old_pair->y < pair->y) {
489 /* Preserve the old vertex and remove the current one */
490 g_free(pair);
491 vertices = g_slist_delete_link(vertices, vertex);
492 } else {
493 /* Preserve the current vertex and remove the old one */
494 g_free(old_pair);
495 vertices = g_slist_delete_link(vertices, old_vertex);
496 old_vertex = vertex;
500 return vertices;
503 static GArray *
504 build_array(const GSList *vertices)
506 cairo_path_data_t line[4];
507 GArray *array;
508 const GSList *vertex, *vertex2;
509 const AdgPair *pair, *pair2;
511 line[0].header.type = CPML_MOVE;
512 line[0].header.length = 2;
513 line[2].header.type = CPML_LINE;
514 line[2].header.length = 2;
516 array = g_array_new(FALSE, FALSE, sizeof(cairo_path_data_t));
517 vertex = vertices;
519 while (vertex != NULL) {
520 pair = vertex->data;
521 vertex = vertex->next;
522 vertex2 = vertex;
524 while (vertex2 != NULL) {
525 pair2 = vertex2->data;
527 if (pair->x == pair2->x) {
528 /* Opposite vertex found: append a line in the path
529 * and quit from this loop */
530 cpml_pair_to_cairo(pair, &line[1]);
531 cpml_pair_to_cairo(pair2, &line[3]);
532 array = g_array_append_vals(array, line, G_N_ELEMENTS(line));
533 break;
536 vertex2 = vertex2->next;
540 return array;