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.
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.
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>
45 * All fields are private and should not be used directly.
46 * Use its public methods instead.
50 #include "adg-internal.h"
51 #include "adg-edges.h"
52 #include "adg-edges-private.h"
55 #define PARENT_OBJECT_CLASS ((GObjectClass *) adg_edges_parent_class)
56 #define PARENT_MODEL_CLASS ((AdgModelClass *) adg_edges_parent_class)
66 static void dispose (GObject
*object
);
67 static void finalize (GObject
*object
);
68 static void get_property (GObject
*object
,
72 static void set_property (GObject
*object
,
76 static void clear (AdgModel
*model
);
77 static CpmlPath
* get_cpml_path (AdgTrail
*trail
);
78 static gboolean
set_source (AdgEdges
*edges
,
80 static gboolean
set_critical_angle (AdgEdges
*edges
,
82 static void unset_source (AdgEdges
*edges
);
83 static void clear_cpml_path (AdgEdges
*edges
);
84 static GSList
* get_vertices (CpmlSegment
*segment
,
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
);
94 adg_edges_class_init(AdgEdgesClass
*klass
)
96 GObjectClass
*gobject_class
;
97 AdgModelClass
*model_class
;
98 AdgTrailClass
*trail_class
;
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",
118 P_("The source where the edges should be computed from"),
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"),
128 g_object_class_install_property(gobject_class
, PROP_CRITICAL_ANGLE
, param
);
132 adg_edges_init(AdgEdges
*edges
)
134 AdgEdgesPrivate
*data
= G_TYPE_INSTANCE_GET_PRIVATE(edges
, ADG_TYPE_EDGES
,
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
;
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
);
159 finalize(GObject
*object
)
161 clear_cpml_path((AdgEdges
*) object
);
163 if (PARENT_OBJECT_CLASS
->finalize
)
164 PARENT_OBJECT_CLASS
->finalize(object
);
168 get_property(GObject
*object
, guint prop_id
, GValue
*value
, GParamSpec
*pspec
)
171 AdgEdgesPrivate
*data
;
173 edges
= (AdgEdges
*) object
;
178 g_value_set_object(value
, data
->source
);
180 case PROP_CRITICAL_ANGLE
:
181 g_value_set_double(value
, adg_edges_get_critical_angle(edges
));
184 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
190 set_property(GObject
*object
, guint prop_id
,
191 const GValue
*value
, GParamSpec
*pspec
)
193 AdgEdges
*edges
= (AdgEdges
*) object
;
197 set_source(edges
, g_value_get_object(value
));
199 case PROP_CRITICAL_ANGLE
:
200 set_critical_angle(edges
, g_value_get_double(value
));
203 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
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
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
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
246 adg_edges_get_source(AdgEdges
*edges
)
248 AdgEdgesPrivate
*data
;
250 g_return_val_if_fail(ADG_IS_EDGES(edges
), NULL
);
258 * adg_edges_set_source:
259 * @edges: an #AdgEdges
260 * @source: the new source #AdgTrail
262 * Sets @source as the source trail for @edges.
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
284 adg_edges_get_critical_angle(AdgEdges
*edges
)
286 AdgEdgesPrivate
*data
;
288 g_return_val_if_fail(ADG_IS_EDGES(edges
), 0);
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
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");
316 clear(AdgModel
*model
)
318 clear_cpml_path((AdgEdges
*) model
);
320 if (PARENT_MODEL_CLASS
->clear
)
321 PARENT_MODEL_CLASS
->clear(model
);
325 get_cpml_path(AdgTrail
*trail
)
328 AdgEdgesPrivate
*data
;
330 edges
= (AdgEdges
*) trail
;
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
) {
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
;
360 set_source(AdgEdges
*edges
, AdgTrail
*source
)
363 AdgEdgesPrivate
*data
;
365 g_return_val_if_fail(source
== NULL
|| ADG_IS_TRAIL(source
), FALSE
);
367 entity
= (AdgEntity
*) edges
;
370 if (source
== data
->source
)
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
);
388 set_critical_angle(AdgEdges
*edges
, gdouble angle
)
390 AdgEdgesPrivate
*data
;
394 threshold
= sin(angle
);
395 threshold
*= threshold
* 2;
397 if (threshold
== data
->threshold
)
400 data
->threshold
= threshold
;
406 unset_source(AdgEdges
*edges
)
408 AdgEdgesPrivate
*data
= edges
->data
;
410 if (data
->source
!= NULL
)
411 set_source(edges
, NULL
);
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;
430 get_vertices(CpmlSegment
*segment
, gdouble threshold
)
433 CpmlPrimitive primitive
;
437 cpml_primitive_from_segment(&primitive
, segment
);
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 */
452 cpml_pair_squared_distance(&old
, &new) > threshold
) {
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 */
468 optimize_vertices(GSList
*vertices
)
470 GSList
*vertex
, *old_vertex
;
471 AdgPair
*pair
, *old_pair
;
473 /* Check for empty list */
474 if (vertices
== NULL
)
477 old_vertex
= vertices
;
479 while ((vertex
= old_vertex
->next
) != NULL
) {
481 old_pair
= old_vertex
->data
;
483 if (pair
->x
!= old_pair
->x
) {
488 if (old_pair
->y
< pair
->y
) {
489 /* Preserve the old vertex and remove the current one */
491 vertices
= g_slist_delete_link(vertices
, vertex
);
493 /* Preserve the current vertex and remove the old one */
495 vertices
= g_slist_delete_link(vertices
, old_vertex
);
504 build_array(const GSList
*vertices
)
506 cairo_path_data_t line
[4];
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
));
519 while (vertex
!= NULL
) {
521 vertex
= vertex
->next
;
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
));
536 vertex2
= vertex2
->next
;