/* GTS - Library for the manipulation of triangulated surfaces
* Copyright (C) 1999 Stéphane Popinet
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <math.h>
#include "gts.h"
gboolean gts_allow_floating_vertices = FALSE;
static void vertex_destroy (GtsObject * object)
{
GtsVertex * vertex = GTS_VERTEX (object);
GSList * i;
i = vertex->segments;
while (i) {
GTS_OBJECT_SET_FLAGS (i->data, GTS_DESTROYED);
i = i->next;
}
i = vertex->segments;
while (i) {
GSList * next = i->next;
gts_object_destroy (i->data);
i = next;
}
g_assert (vertex->segments == NULL);
(* GTS_OBJECT_CLASS (gts_vertex_class ())->parent_class->destroy) (object);
}
static void vertex_clone (GtsObject * clone, GtsObject * object)
{
(* GTS_OBJECT_CLASS (gts_vertex_class ())->parent_class->clone) (clone,
object);
GTS_VERTEX (clone)->segments = NULL;
}
static void vertex_class_init (GtsVertexClass * klass)
{
klass->intersection_attributes = NULL;
GTS_OBJECT_CLASS (klass)->clone = vertex_clone;
GTS_OBJECT_CLASS (klass)->destroy = vertex_destroy;
}
static void vertex_init (GtsVertex * vertex)
{
vertex->segments = NULL;
}
/**
* gts_vertex_class:
*
* Returns: the #GtsVertexClass.
*/
GtsVertexClass * gts_vertex_class (void)
{
static GtsVertexClass * klass = NULL;
if (klass == NULL) {
GtsObjectClassInfo vertex_info = {
"GtsVertex",
sizeof (GtsVertex),
sizeof (GtsVertexClass),
(GtsObjectClassInitFunc) vertex_class_init,
(GtsObjectInitFunc) vertex_init,
(GtsArgSetFunc) NULL,
(GtsArgGetFunc) NULL
};
klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_point_class ()),
&vertex_info);
}
return klass;
}
/**
* gts_vertex_new:
* @klass: a #GtsVertexClass.
* @x: the x-coordinate of the vertex to create.
* @y: the y-coordinate of the vertex to create.
* @z: the y-coordinate of the vertex to create.
*
* Returns: a new #GtsVertex with @x, @y and @z as coordinates.
*/
GtsVertex * gts_vertex_new (GtsVertexClass * klass,
gdouble x, gdouble y, gdouble z)
{
GtsVertex * v;
v = GTS_VERTEX (gts_object_new (GTS_OBJECT_CLASS (klass)));
gts_point_set (GTS_POINT (v), x, y, z);
return v;
}
/**
* gts_vertex_replace:
* @v: a #GtsVertex.
* @with: another #GtsVertex.
*
* Replaces vertex @v with vertex @with. @v and @with must be
* different. All the #GtsSegment which have @v has one of their
* vertices are updated. The segments list of vertex @v is freed and
* @v->segments is set to %NULL.
*/
void gts_vertex_replace (GtsVertex * v, GtsVertex * with)
{
GSList * i;
g_return_if_fail (v != NULL);
g_return_if_fail (with != NULL);
g_return_if_fail (v != with);
i = v->segments;
while (i) {
GtsSegment * s = i->data;
if (s->v1 != with && s->v2 != with)
with->segments = g_slist_prepend (with->segments, s);
if (s->v1 == v) s->v1 = with;
if (s->v2 == v) s->v2 = with;
i = i->next;
}
g_slist_free (v->segments);
v->segments = NULL;
}
/**
* gts_vertex_is_unattached:
* @v: a #GtsVertex.
*
* Returns: %TRUE if @v is not the endpoint of any #GtsSegment,
* %FALSE otherwise.
*/
gboolean gts_vertex_is_unattached (GtsVertex * v)
{
g_return_val_if_fail (v != NULL, FALSE);
if (v->segments == NULL)
return TRUE;
return FALSE;
}
/**
* gts_vertices_are_connected:
* @v1: a #GtsVertex.
* @v2: another #GtsVertex.
*
* Returns: if @v1 and @v2 are the vertices of the same #GtsSegment
* this segment else %NULL.
*/
GtsSegment * gts_vertices_are_connected (GtsVertex * v1, GtsVertex * v2)
{
GSList * i;
g_return_val_if_fail (v1 != NULL, FALSE);
g_return_val_if_fail (v2 != NULL, FALSE);
i = v1->segments;
while (i) {
GtsSegment * s = i->data;
if (s->v1 == v2 || s->v2 == v2)
return s;
i = i->next;
}
return NULL;
}
/**
* gts_vertices_from_segments:
* @segments: a list of #GtsSegment.
*
* Returns: a list of #GtsVertex, vertices of a #GtsSegment in @segments.
* Each element in the list is unique (no duplicates).
*/
GSList * gts_vertices_from_segments (GSList * segments)
{
GHashTable * hash;
GSList * vertices = NULL, * i;
hash = g_hash_table_new (NULL, NULL);
i = segments;
while (i) {
GtsSegment * s = i->data;
if (g_hash_table_lookup (hash, s->v1) == NULL) {
vertices = g_slist_prepend (vertices, s->v1);
g_hash_table_insert (hash, s->v1, s);
}
if (g_hash_table_lookup (hash, s->v2) == NULL) {
vertices = g_slist_prepend (vertices, s->v2);
g_hash_table_insert (hash, s->v2, s);
}
i = i->next;
}
g_hash_table_destroy (hash);
return vertices;
}
/**
* gts_vertex_triangles:
* @v: a #GtsVertex.
* @list: a list of #GtsTriangle.
*
* Adds all the #GtsTriangle which share @v as a vertex and do not
* already belong to @list.
*
* Returns: the new list of unique #GtsTriangle which share @v as a
* vertex.
*/
GSList * gts_vertex_triangles (GtsVertex * v,
GSList * list)
{
GSList * i;
g_return_val_if_fail (v != NULL, NULL);
i = v->segments;
while (i) {
GtsSegment * s = i->data;
if (GTS_IS_EDGE (s)) {
GSList * j = GTS_EDGE (s)->triangles;
while (j) {
if (!g_slist_find (list, j->data))
list = g_slist_prepend (list, j->data);
j = j->next;
}
}
i = i->next;
}
return list;
}
/**
* gts_vertex_faces:
* @v: a #GtsVertex.
* @surface: a #GtsSurface or %NULL.
* @list: a list of #GtsFace.
*
* Adds all the #GtsFace belonging to @surface (if not %NULL) which share
* @v as a vertex and do not already belong to @list.
*
* Returns: the new list of unique #GtsFace belonging to @surface
* which share @v as a vertex.
*/
GSList * gts_vertex_faces (GtsVertex * v,
GtsSurface * surface,
GSList * list)
{
GSList * i;
g_return_val_if_fail (v != NULL, NULL);
i = v->segments;
while (i) {
GtsSegment * s = i->data;
if (GTS_IS_EDGE (s)) {
GSList * j = GTS_EDGE (s)->triangles;
while (j) {
GtsTriangle * t = j->data;
if (GTS_IS_FACE (t)
&&
(!surface || gts_face_has_parent_surface (GTS_FACE (t), surface))
&&
!g_slist_find (list, t))
list = g_slist_prepend (list, t);
j = j->next;
}
}
i = i->next;
}
return list;
}
/**
* gts_vertex_neighbors:
* @v: a #GtsVertex.
* @list: a list of #GtsVertex.
* @surface: a #GtsSurface or %NULL.
*
* Adds to @list all the #GtsVertex connected to @v by a #GtsSegment and not
* already in @list. If @surface is not %NULL only the vertices connected to
* @v by an edge belonging to @surface are considered.
*
* Returns: the new list of unique #GtsVertex.
*/
GSList * gts_vertex_neighbors (GtsVertex * v,
GSList * list,
GtsSurface * surface)
{
GSList * i;
g_return_val_if_fail (v != NULL, NULL);
i = v->segments;
while (i) {
GtsSegment * s = i->data;
GtsVertex * v1 = s->v1 == v ? s->v2 : s->v1;
if (v1 != v &&
(!surface ||
(GTS_IS_EDGE (s) &&
gts_edge_has_parent_surface (GTS_EDGE (s), surface))) &&
!g_slist_find (list, v1))
list = g_slist_prepend (list, v1);
i = i->next;
}
return list;
}
/**
* gts_vertex_is_boundary:
* @v: a #GtsVertex.
* @surface: a #GtsSurface or %NULL.
*
* Returns: %TRUE if @v is used by a #GtsEdge boundary of @surface as
* determined by gts_edge_is_boundary(), %FALSE otherwise.
*/
gboolean gts_vertex_is_boundary (GtsVertex * v, GtsSurface * surface)
{
GSList * i;
g_return_val_if_fail (v != NULL, FALSE);
i = v->segments;
while (i) {
if (GTS_IS_EDGE (i->data) &&
gts_edge_is_boundary (i->data, surface))
return TRUE;
i = i->next;
}
return FALSE;
}
/**
* gts_vertices_merge:
* @vertices: a list of #GtsVertex.
* @epsilon: half the size of the bounding box to consider for each vertex.
* @check: function called for each pair of vertices about to be merged
* or %NULL.
*
* For each vertex v in @vertices look if there are any vertex of
* @vertices contained in a box centered on v of size 2*@epsilon. If
* there are and if @check is not %NULL and returns %TRUE, replace
* them with v (using gts_vertex_replace()), destroy them and remove
* them from list. This is done efficiently using Kd-Trees.
*
* Returns: the updated list of vertices.
*/
GList * gts_vertices_merge (GList * vertices,
gdouble epsilon,
gboolean (* check) (GtsVertex *, GtsVertex *))
{
GPtrArray * array;
GList * i;
GNode * kdtree;
g_return_val_if_fail (vertices != NULL, 0);
array = g_ptr_array_new ();
i = vertices;
while (i) {
g_ptr_array_add (array, i->data);
i = i->next;
}
kdtree = gts_kdtree_new (array, NULL);
g_ptr_array_free (array, TRUE);
i = vertices;
while (i) {
GtsVertex * v = i->data;
if (!GTS_OBJECT (v)->reserved) { /* Do something only if v is active */
GtsBBox * bbox;
GSList * selected, * j;
/* build bounding box */
bbox = gts_bbox_new (gts_bbox_class (),
v,
GTS_POINT (v)->x - epsilon,
GTS_POINT (v)->y - epsilon,
GTS_POINT (v)->z - epsilon,
GTS_POINT (v)->x + epsilon,
GTS_POINT (v)->y + epsilon,
GTS_POINT (v)->z + epsilon);
/* select vertices which are inside bbox using kdtree */
j = selected = gts_kdtree_range (kdtree, bbox, NULL);
while (j) {
GtsVertex * sv = j->data;
if (sv != v && !GTS_OBJECT (sv)->reserved && (!check || (*check) (sv, v))) {
/* sv is not v and is active */
gts_vertex_replace (sv, v);
GTS_OBJECT (sv)->reserved = sv; /* mark sv as inactive */
}
j = j->next;
}
g_slist_free (selected);
gts_object_destroy (GTS_OBJECT (bbox));
}
i = i->next;
}
gts_kdtree_destroy (kdtree);
/* destroy inactive vertices and removes them from list */
/* we want to control vertex destruction */
gts_allow_floating_vertices = TRUE;
i = vertices;
while (i) {
GtsVertex * v = i->data;
GList * next = i->next;
if (GTS_OBJECT (v)->reserved) { /* v is inactive */
gts_object_destroy (GTS_OBJECT (v));
vertices = g_list_remove_link (vertices, i);
g_list_free_1 (i);
}
i = next;
}
gts_allow_floating_vertices = FALSE;
return vertices;
}
/* returns the list of edges belonging to @surface turning around @v */
static GSList * edge_fan_list (GtsVertex * v,
GtsSurface * surface,
GtsFace * f,
GtsEdge * e,
GtsFace * first)
{
GSList * i = e->triangles;
GtsFace * neighbor = NULL;
GtsEdge * next = NULL, * enext = NULL;
while (i) {
GtsFace * f1 = i->data;
if (GTS_IS_FACE (f1) &&
f1 != f &&
gts_face_has_parent_surface (f1, surface)) {
g_return_val_if_fail (neighbor == NULL, NULL); /* non-manifold edge */
neighbor = f1;
}
i = i->next;
}
if (neighbor == NULL || neighbor == first) /* end of fan */
return NULL;
if (GTS_TRIANGLE (neighbor)->e1 == e) {
next = GTS_TRIANGLE (neighbor)->e2;
enext = GTS_TRIANGLE (neighbor)->e3;
}
else if (GTS_TRIANGLE (neighbor)->e2 == e) {
next = GTS_TRIANGLE (neighbor)->e3;
enext = GTS_TRIANGLE (neighbor)->e1;
}
else if (GTS_TRIANGLE (neighbor)->e3 == e) {
next = GTS_TRIANGLE (neighbor)->e1;
enext = GTS_TRIANGLE (neighbor)->e2;
}
else
g_assert_not_reached ();
/* checking for correct orientation */
g_return_val_if_fail (GTS_SEGMENT (enext)->v1 == v ||
GTS_SEGMENT (enext)->v2 == v, NULL);
return g_slist_prepend (edge_fan_list (v, surface, neighbor, enext, first),
next);
}
/**
* gts_vertex_fan_oriented:
* @v: a #GtsVertex.
* @surface: a #GtsSurface.
*
* Returns: a list of #GtsEdge describing in counterclockwise order the
* boundary of the fan of summit @v, the faces of the fan belonging to
* @surface.
*/
GSList * gts_vertex_fan_oriented (GtsVertex * v, GtsSurface * surface)
{
GtsFace * f = NULL;
guint d = 2;
GSList * i;
GtsVertex * v1, * v2, * v3;
GtsEdge * e1, * e2, * e3;
g_return_val_if_fail (v != NULL, NULL);
g_return_val_if_fail (surface != NULL, NULL);
i = v->segments;
while (i) {
GtsEdge * e = i->data;
if (GTS_IS_EDGE (e)) {
GSList * j = e->triangles;
GtsFace * f1 = NULL;
guint degree = 0;
while (j) {
if (GTS_IS_FACE (j->data) &&
gts_face_has_parent_surface (j->data, surface)) {
f1 = j->data;
degree++;
}
j = j->next;
}
if (f1 != NULL) {
g_return_val_if_fail (degree <= 2, NULL); /* non-manifold edge */
if (degree == 1) {
gts_triangle_vertices_edges (GTS_TRIANGLE (f1), NULL,
&v1, &v2, &v3, &e1, &e2, &e3);
if (v == v2) {
e2 = e3;
e3 = e1;
}
else if (v == v3) {
e3 = e2;
e2 = e1;
}
if (e3 != e) {
d = 1;
f = f1;
}
}
else if (degree <= d)
f = f1;
}
}
i = i->next;
}
if (f == NULL)
return NULL;
gts_triangle_vertices_edges (GTS_TRIANGLE (f), NULL,
&v1, &v2, &v3, &e1, &e2, &e3);
if (v == v2) {
e2 = e3;
e3 = e1;
}
else if (v == v3) {
e3 = e2;
e2 = e1;
}
return g_slist_prepend (edge_fan_list (v, surface, f, e3, f), e2);
}
#define edge_use_vertex(e, v) (GTS_SEGMENT(e)->v1 == v ||\
GTS_SEGMENT(e)->v2 == v)
static GtsEdge * replace_vertex (GtsTriangle * t,
GtsEdge * e1,
GtsVertex * v,
GtsVertex * with)
{
GtsEdge * e = NULL;
if (t->e1 != e1 && edge_use_vertex (t->e1, v))
e = t->e1;
else if (t->e2 != e1 && edge_use_vertex (t->e2, v))
e = t->e2;
else if (t->e3 != e1 && edge_use_vertex (t->e3, v))
e = t->e3;
else
return NULL;
if (with != v) {
GtsSegment * s = GTS_SEGMENT (e);
if (s->v1 == v) s->v1 = with;
if (s->v2 == v) s->v2 = with;
with->segments = g_slist_prepend (with->segments, s);
v->segments = g_slist_remove (v->segments, s);
}
return e;
}
static void triangle_next (GtsEdge * e, GtsVertex * v, GtsVertex * with)
{
GSList * i;
if (e == NULL)
return;
i = e->triangles;
while (i) {
GtsTriangle * t = i->data;
if (GTS_OBJECT (t)->reserved) {
GTS_OBJECT (t)->reserved = NULL;
triangle_next (replace_vertex (t, e, v, with), v, with);
}
i = i->next;
}
}
/**
* gts_vertex_is_contact:
* @v: a #GtsVertex.
* @sever: if %TRUE and if @v is a contact vertex between two or more
* sets of connected triangles replaces it with as many vertices,
* clones of @v.
*
* Returns: the number of sets of connected triangles sharing @v as a
* contact vertex.
*/
guint gts_vertex_is_contact (GtsVertex * v, gboolean sever)
{
GSList * triangles, * i;
GtsVertex * with = v;
guint ncomponent = 0;
g_return_val_if_fail (v != NULL, 0);
triangles = gts_vertex_triangles (v, NULL);
i = triangles;
while (i) {
GTS_OBJECT (i->data)->reserved = i;
i = i->next;
}
i = triangles;
while (i) {
GtsTriangle * t = i->data;
if (GTS_OBJECT (t)->reserved) {
GtsEdge * e;
if (ncomponent && sever)
with = GTS_VERTEX (gts_object_clone (GTS_OBJECT (v)));
GTS_OBJECT (t)->reserved = NULL;
e = replace_vertex (t, NULL, v, with);
triangle_next (e, v, with);
triangle_next (replace_vertex (t, e, v, with), v, with);
ncomponent++;
}
i = i->next;
}
g_slist_free (triangles);
return ncomponent;
}
/* GtsVertexNormal: Object */
static void vertex_normal_attributes (GtsVertex * v,
GtsObject * e,
GtsObject * t)
{
g_return_if_fail (GTS_IS_EDGE (e));
g_return_if_fail (GTS_IS_TRIANGLE (t));
if (GTS_IS_VERTEX_NORMAL (GTS_SEGMENT (e)->v1) &&
GTS_IS_VERTEX_NORMAL (GTS_SEGMENT (e)->v2)) {
GtsPoint * p1 = GTS_POINT (GTS_SEGMENT (e)->v1);
GtsPoint * p2 = GTS_POINT (GTS_SEGMENT (e)->v2);
GtsPoint * p = GTS_POINT (v);
gdouble a, b, lambda;
guint i;
a = p2->x - p1->x; b = p->x - p1->x;
if (fabs (p2->y - p1->y) > fabs (a)) {
a = p2->y - p1->y; b = p->y - p1->y;
}
if (fabs (p2->z - p1->z) > fabs (a)) {
a = p2->z - p1->z; b = p->z - p1->z;
}
lambda = a != 0. ? b/a : 0.;
for (i = 0; i < 3; i++)
GTS_VERTEX_NORMAL (v)->n[i] =
(1. - lambda)*GTS_VERTEX_NORMAL (GTS_SEGMENT (e)->v1)->n[i] +
lambda*GTS_VERTEX_NORMAL (GTS_SEGMENT (e)->v2)->n[i];
}
else {
GtsVertex * v1, * v2, * v3;
gts_triangle_vertices (GTS_TRIANGLE (t), &v1, &v2, &v3);
if (GTS_IS_VERTEX_NORMAL (v1) &&
GTS_IS_VERTEX_NORMAL (v2) &&
GTS_IS_VERTEX_NORMAL (v3)) {
GtsVector a1, a2, a3, det;
guint i, j = 0;
gdouble l1, l2;
gts_vector_init (a1, GTS_POINT (v1), GTS_POINT (v));
gts_vector_init (a2, GTS_POINT (v1), GTS_POINT (v2));
gts_vector_init (a3, GTS_POINT (v1), GTS_POINT (v3));
gts_vector_cross (det, a2, a3);
if (fabs (det[1]) > fabs (det[0])) j = 1;
if (fabs (det[2]) > fabs (det[j])) j = 2;
if (det[j] == 0.) {
g_warning ("vertex_normal_attributes: det[%d] == 0.", j);
return;
}
switch (j) {
case 0:
l1 = (a1[1]*a3[2] - a1[2]*a3[1])/det[0];
l2 = (a1[2]*a2[1] - a1[1]*a2[2])/det[0];
break;
case 1:
l1 = (a1[2]*a3[0] - a1[0]*a3[2])/det[1];
l2 = (a1[0]*a2[2] - a1[2]*a2[0])/det[1];
break;
case 2:
l1 = (a1[0]*a3[1] - a1[1]*a3[0])/det[2];
l2 = (a1[1]*a2[0] - a1[0]*a2[1])/det[2];
break;
default:
l1 = l2 = 0.;
}
for (i = 0; i < 3; i++)
GTS_VERTEX_NORMAL (v)->n[i] =
GTS_VERTEX_NORMAL (v1)->n[i]*(1. - l1 - l2) +
GTS_VERTEX_NORMAL (v2)->n[i]*l1 +
GTS_VERTEX_NORMAL (v3)->n[i]*l2;
}
}
}
static void gts_vertex_normal_class_init (GtsVertexClass * klass)
{
klass->intersection_attributes = vertex_normal_attributes;
}
GtsVertexClass * gts_vertex_normal_class (void)
{
static GtsVertexClass * klass = NULL;
if (klass == NULL) {
GtsObjectClassInfo gts_vertex_normal_info = {
"GtsVertexNormal",
sizeof (GtsVertexNormal),
sizeof (GtsVertexClass),
(GtsObjectClassInitFunc) gts_vertex_normal_class_init,
(GtsObjectInitFunc) NULL,
(GtsArgSetFunc) NULL,
(GtsArgGetFunc) NULL
};
klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_vertex_class ()),
>s_vertex_normal_info);
}
return klass;
}
/* GtsColorVertex: Object */
GtsVertexClass * gts_color_vertex_class (void)
{
static GtsVertexClass * klass = NULL;
if (klass == NULL) {
GtsObjectClassInfo gts_color_vertex_info = {
"GtsColorVertex",
sizeof (GtsColorVertex),
sizeof (GtsVertexClass),
(GtsObjectClassInitFunc) NULL,
(GtsObjectInitFunc) NULL,
(GtsArgSetFunc) NULL,
(GtsArgGetFunc) NULL
};
klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_vertex_class ()),
>s_color_vertex_info);
}
return klass;
}
syntax highlighted by Code2HTML, v. 0.9.1