/* GTS - Library for the manipulation of triangulated surfaces
* Copyright (C) 1999-2003 Wagner Toledo Correa, 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 "gts.h"
#define PRINT_HEAP_ELEMENTS 0
typedef struct {
GtsTriangle * t;
gboolean used;
GSList * neighbors;
GtsEHeapPair *pos;
} tri_data_t;
typedef struct {
GHashTable * ht;
} map_t;
typedef struct {
map_t * map;
GtsEHeap * heap;
} heap_t;
static tri_data_t * tri_data_new (GtsTriangle * t);
static void tri_data_destroy (tri_data_t * td);
static guint tri_data_num_unused_neighbors2 (const tri_data_t * td,
const map_t * map);
static GHashTable * tri_data_unused_neighbors2 (const tri_data_t * td,
const map_t * map);
static map_t * map_new (GtsSurface * s);
static void map_destroy (map_t * map);
static tri_data_t * map_lookup (const map_t * map, GtsTriangle * t);
static heap_t * heap_new (GtsSurface * s);
static void heap_destroy (heap_t * heap);
static gboolean heap_is_empty (const heap_t * heap);
static GtsTriangle * heap_top (const heap_t * heap);
static void heap_remove (heap_t * heap, GtsTriangle * t);
/* helper functions */
static gboolean vertices_are_unique (GtsVertex * v1,
GtsVertex * v2,
GtsVertex * v3)
{
g_assert (v1 && v2 && v3);
return (v1 != v2 && v1 != v3 && v2 != v3);
}
static gboolean vertex_is_one_of (GtsVertex * v,
GtsVertex * v1,
GtsVertex * v2,
GtsVertex * v3)
{
g_assert (v && v1 && v2 && v3);
return v == v1 || v == v2 || v == v3;
}
static guint num_shared_vertices (GtsVertex * u1,
GtsVertex * u2,
GtsVertex * u3,
GtsVertex * v1,
GtsVertex * v2,
GtsVertex * v3)
{
guint n = 0;
g_assert (u1 && u2 && u3);
g_assert (v1 && v2 && v3);
g_assert (vertices_are_unique (u1, u2, u3));
g_assert (vertices_are_unique (v1, v2, v3));
if (vertex_is_one_of (v1, u1, u2, u3))
n++;
if (vertex_is_one_of (v2, u1, u2, u3))
n++;
if (vertex_is_one_of (v3, u1, u2, u3))
n++;
return n;
}
static gboolean vertices_match (GtsVertex * v1,
GtsVertex * v2,
GtsVertex * v3,
GtsVertex ** v4,
GtsVertex ** v5,
GtsVertex ** v6)
{
guint i;
g_assert (v4 && v5 && v6);
g_assert (*v4 && *v5 && *v6);
g_assert (vertices_are_unique (*v4, *v5, *v6));
for (i = 0; i < 2; i++) {
if ((!v1 || (v1 == *v4)) &&
(!v2 || (v2 == *v5)) &&
(!v3 || (v3 == *v6)))
return TRUE;
else {
GtsVertex * v7 = * v4;
*v4 = *v5;
*v5 = *v6;
*v6 = v7;
}
}
return ((!v1 || (v1 == *v4)) &&
(!v2 || (v2 == *v5)) &&
(!v3 || (v3 == *v6)));
}
static GtsVertex * non_shared_vertex1 (GtsVertex * u1,
GtsVertex * u2,
GtsVertex * u3,
GtsVertex * v1,
GtsVertex * v2,
GtsVertex * v3)
{
GtsVertex * u = NULL;
g_assert (u1 && u2 && u3);
g_assert (v1 && v2 && v3);
g_assert (vertices_are_unique (u1, u2, u3));
g_assert (vertices_are_unique (v1, v2, v3));
g_assert (num_shared_vertices (u1, u2, u3, v1, v2, v3) == 2);
if (!vertex_is_one_of (u1, v1, v2, v3)) {
g_assert (vertex_is_one_of (u2, v1, v2, v3));
g_assert (vertex_is_one_of (u3, v1, v2, v3));
u = u1;
} else if (!vertex_is_one_of (u2, v1, v2, v3)) {
g_assert (vertex_is_one_of (u1, v1, v2, v3));
g_assert (vertex_is_one_of (u3, v1, v2, v3));
u = u2;
} else if (!vertex_is_one_of (u3, v1, v2, v3)) {
g_assert (vertex_is_one_of (u1, v1, v2, v3));
g_assert (vertex_is_one_of (u2, v1, v2, v3));
u = u3;
} else
g_assert_not_reached ();
return u;
}
static void match_vertex (GtsVertex * v,
GtsVertex ** v1,
GtsVertex ** v2,
GtsVertex ** v3)
{
g_assert (v && v1 && v2 && v3);
g_assert (*v1 && *v2 && *v3);
g_assert (vertex_is_one_of (v, *v1, *v2, *v3));
while (*v1 != v) {
GtsVertex *v0 = *v1;
*v1 = *v2;
*v2 = *v3;
*v3 = v0;
}
}
/* tri_data_t functions */
static tri_data_t * tri_data_new (GtsTriangle * t)
{
tri_data_t * td;
td = g_malloc (sizeof (tri_data_t));
td->t = t;
td->used = FALSE;
td->neighbors = gts_triangle_neighbors (t);
td->pos = NULL;
return td;
}
static void tri_data_destroy (tri_data_t * td)
{
if (!td)
return;
g_slist_free (td->neighbors);
g_free (td);
}
static guint tri_data_num_unused_neighbors2 (const tri_data_t * td,
const map_t * map)
{
GHashTable *h;
guint n;
g_assert (td);
g_assert (map);
h = tri_data_unused_neighbors2 (td, map);
n = g_hash_table_size (h);
g_hash_table_destroy (h);
return n;
}
static void copy_key_to_array (gpointer key,
gpointer value,
gpointer user_data)
{
GtsTriangle * t = key;
GtsTriangle *** p = user_data;
(void) value;
g_assert (t);
g_assert (p && *p);
**p = t;
(*p)++;
}
static gboolean are_neighbors_unique (GHashTable *h)
{
GtsTriangle ** a;
GtsTriangle ** p;
gint i, j, n; /* guint won't work if n == 0 */
g_assert (h);
n = g_hash_table_size (h);
#ifdef DEBUG
if (n > 9)
g_warning ("triangle has %d 2-level neighbors", n);
#endif /* DEBUG */
a = g_malloc(n*sizeof (GtsTriangle *));
p = a;
g_hash_table_foreach (h, copy_key_to_array, &p);
for (i = 0; i < n - 1; i++) {
g_assert (a[i]);
for (j = i + 1; j < n; j++) {
g_assert (a[j]);
if (a[i] == a[j]) {
g_free (a);
return FALSE;
}
}
}
g_free (a);
return TRUE;
}
static GHashTable * tri_data_unused_neighbors2 (const tri_data_t * td,
const map_t * map)
{
GHashTable * h = g_hash_table_new (NULL, NULL);
GSList * li;
g_assert (td);
g_assert (map);
for (li = td->neighbors; li != NULL; li = li->next) {
GtsTriangle * t2 = li->data;
tri_data_t * td2 = map_lookup (map, t2);
GSList * lj;
g_assert (td2);
if (!td2->used) {
g_hash_table_insert (h, t2, td2);
for (lj = td2->neighbors; lj != NULL; lj = lj->next) {
GtsTriangle * t3 = lj->data;
tri_data_t * td3 = map_lookup (map, t3);
g_assert (td3);
if (td3 != td && !td3->used)
g_hash_table_insert (h, t3, td3);
}
}
}
g_assert (are_neighbors_unique (h));
return h;
}
#if PRINT_HEAP_ELEMENTS
static void tri_data_print (const tri_data_t * td, FILE * fp)
{
g_assert (td);
g_assert (fp);
fprintf(fp, "td=%p t=%p used=%d pos=%p key=%f\n",
td, td->t, td->used, td->pos,
td->pos ? td->pos->key : -1.0);
}
#endif /* PRINT_HEAP_ELEMENTS */
/* heap_t functions */
static gdouble triangle_priority (gpointer item, gpointer data)
{
GtsTriangle * t = item;
map_t * map = data;
tri_data_t * td;
gdouble k;
g_assert (t);
g_assert (map);
td = map_lookup (map, t);
g_assert (td);
k = tri_data_num_unused_neighbors2 (td, map);
return k;
}
#if PRINT_HEAP_ELEMENTS
static void print_heap_element (gpointer data, gpointer user_data)
{
GtsTriangle * t = data;
map_t * map = user_data;
tri_data_t * td;
g_assert (t);
g_assert (map);
td = map_lookup (map, t);
g_assert (td);
g_assert (!td->used);
g_assert (td->pos);
tri_data_print (td, stderr);
}
#endif /* PRINT_HEAP_ELEMENTS */
static void insert_entry_into_heap (gpointer key,
gpointer value,
gpointer user_data)
{
GtsTriangle * t = key;
tri_data_t * td = value;
GtsEHeap * heap = user_data;
g_assert (!td->pos);
td->pos = gts_eheap_insert (heap, t);
g_assert (td->pos);
}
static heap_t * heap_new (GtsSurface *s)
{
heap_t * heap;
g_assert (s);
heap = g_malloc (sizeof (heap_t));
heap->map = map_new (s);
heap->heap = gts_eheap_new (triangle_priority, heap->map);
g_hash_table_foreach (heap->map->ht,
insert_entry_into_heap,
heap->heap);
#if PRINT_HEAP_ELEMENTS
gts_eheap_foreach (heap->heap, print_heap_element, heap->map);
#endif /* PRINT_HEAP_ELEMENTS */
return heap;
}
static void heap_destroy (heap_t * heap)
{
if (!heap)
return;
map_destroy (heap->map);
gts_eheap_destroy (heap->heap);
g_free (heap);
}
static gboolean heap_is_empty (const heap_t * heap)
{
g_assert (heap);
g_assert (heap->heap);
return gts_eheap_size (heap->heap) == 0;
}
typedef struct {
const heap_t * heap;
double min_key;
} min_key_t;
static GtsTriangle * heap_top (const heap_t * heap)
{
GtsTriangle * t;
g_assert (heap);
g_assert (heap->heap);
t = gts_eheap_top (heap->heap, NULL);
return t;
}
static void decrease_key (gpointer key, gpointer value, gpointer user_data)
{
GtsTriangle * t = key;
tri_data_t * td = value;
heap_t *heap = user_data;
gdouble k;
(void) t;
g_assert (heap);
g_assert (heap->map);
g_assert (heap->heap);
g_assert (td);
g_assert (!td->used);
g_assert (td->pos);
k = tri_data_num_unused_neighbors2 (td, heap->map);
g_assert (k <= td->pos->key);
#ifdef DEBUG
if (k == td->pos->key)
g_warning ("same key: %f\n", k);
#endif /* DEBUG */
if (k != td->pos->key) {
g_assert (k < td->pos->key);
g_assert (k >= 0.0);
gts_eheap_decrease_key (heap->heap, td->pos, k);
}
}
static void heap_remove (heap_t * heap, GtsTriangle * t)
{
tri_data_t * td;
GHashTable * h;
g_assert (heap);
g_assert (t);
td = map_lookup (heap->map, t);
g_assert (td);
g_assert (!td->used);
g_assert (td->pos);
td->used = TRUE;
gts_eheap_remove (heap->heap, td->pos);
td->pos = NULL;
/* fprintf(stderr, "td: %p\n", td); */
h = tri_data_unused_neighbors2 (td, heap->map);
g_hash_table_foreach (h, decrease_key, heap);
g_hash_table_destroy (h);
}
/* map_t functions */
static gint create_map_entry (gpointer item, gpointer data)
{
GtsTriangle * t = item;
GHashTable * ht = data;
tri_data_t * td;
g_assert (t);
g_assert (ht);
td = tri_data_new (t);
g_hash_table_insert (ht, t, td);
return 0;
}
static void free_map_entry (gpointer key, gpointer value, gpointer user_data)
{
GtsTriangle * t = key;
tri_data_t * td = value;
(void) user_data;
g_assert (t);
g_assert (td);
g_assert (td->t == t);
tri_data_destroy (td);
}
static map_t * map_new (GtsSurface * s)
{
map_t * map;
map = g_malloc (sizeof (map_t));
map->ht = g_hash_table_new (NULL, NULL);
gts_surface_foreach_face (s, create_map_entry, map->ht);
return map;
}
static void map_destroy (map_t * map)
{
if (!map)
return;
g_hash_table_foreach (map->ht, free_map_entry, NULL);
g_hash_table_destroy (map->ht);
g_free (map);
}
static tri_data_t * map_lookup (const map_t * map, GtsTriangle * t)
{
tri_data_t * td;
g_assert (map);
g_assert (map->ht);
g_assert (t);
td = g_hash_table_lookup (map->ht, t);
g_assert (td);
g_assert (td->t == t);
return td;
}
/* other helper functions */
static GtsTriangle * find_min_neighbor (heap_t * heap, GtsTriangle * t)
{
GtsTriangle * min_neighbor = NULL;
gdouble min_key = G_MAXDOUBLE;
tri_data_t * td;
GSList * li;
g_assert (heap);
g_assert (t);
td = map_lookup (heap->map, t);
for (li = td->neighbors; li != NULL; li = li->next) {
GtsTriangle * t2 = li->data;
tri_data_t * td2 = map_lookup (heap->map, t2);
gdouble k;
g_assert (td2);
if (td2->used)
continue;
g_assert (td2->pos);
k = td2->pos->key;
if (k < min_key) {
min_key = k;
min_neighbor = t2;
}
}
return min_neighbor;
}
static GtsTriangle * find_neighbor_forward (heap_t * heap,
GtsTriangle * t,
GtsVertex ** v1,
GtsVertex ** v2,
GtsVertex ** v3,
gboolean left_turn)
{
GtsTriangle * neighbor = NULL;
tri_data_t * td;
GSList * li;
g_assert (heap);
g_assert (t);
g_assert (v1 && v2 && v3);
g_assert (vertices_are_unique (*v1, *v2, *v3));
td = map_lookup (heap->map, t);
g_assert (td);
for (li = td->neighbors; li && !neighbor; li = li->next) {
GtsTriangle * t2 = li->data;
tri_data_t * td2 = map_lookup (heap->map, t2);
GtsVertex * v4, * v5, * v6;
g_assert (td2);
if (t2 == t || td2->used)
continue;
gts_triangle_vertices (t2, &v4, &v5, &v6);
if (left_turn) {
if (!vertices_match (*v1, *v3, NULL, &v4, &v5, &v6))
continue;
} else {
if (!vertices_match (*v3, *v2, NULL, &v4, &v5, &v6))
continue;
}
neighbor = t2;
*v1 = v4;
*v2 = v5;
*v3 = v6;
}
return neighbor;
}
static GtsTriangle * find_neighbor_backward (heap_t * heap,
GtsTriangle * t,
GtsVertex ** v1,
GtsVertex ** v2,
GtsVertex ** v3,
gboolean left_turn)
{
GtsTriangle * neighbor = NULL;
tri_data_t * td;
GSList * li;
g_assert (heap);
g_assert (t);
g_assert (v1 && v2 && v3);
g_assert (vertices_are_unique (*v1, *v2, *v3));
td = map_lookup (heap->map, t);
g_assert (td);
for (li = td->neighbors; li && !neighbor; li = li->next) {
GtsTriangle * t2 = li->data;
tri_data_t * td2 = map_lookup (heap->map, t2);
GtsVertex * v4, * v5, * v6;
g_assert (td2);
if (t2 == t || td2->used)
continue;
gts_triangle_vertices (t2, &v4, &v5, &v6);
if (left_turn) {
if (!vertices_match (NULL, *v2, *v1, &v4, &v5, &v6))
continue;
} else if (!vertices_match(*v1, NULL, *v2, &v4, &v5, &v6))
continue;
neighbor = t2;
*v1 = v4;
*v2 = v5;
*v3 = v6;
}
return neighbor;
}
static GSList * grow_strip_forward (heap_t * heap,
GSList * strip,
GtsTriangle * t,
GtsVertex * v1,
GtsVertex * v2,
GtsVertex * v3)
{
gboolean left_turn;
g_assert (heap);
g_assert (g_slist_length(strip) == 2);
g_assert (t);
g_assert (v1 && v2 && v3);
g_assert (vertices_are_unique (v1, v2, v3));
left_turn = TRUE;
while ((t = find_neighbor_forward (heap, t, &v1, &v2, &v3,
left_turn)) != NULL) {
heap_remove (heap, t);
strip = g_slist_prepend (strip, t);
left_turn = !left_turn;
}
return strip;
}
static GSList * grow_strip_backward (heap_t * heap,
GSList * strip,
GtsTriangle * t,
GtsVertex * v1,
GtsVertex * v2,
GtsVertex * v3)
{
/* we have to make sure we add an even number of triangles */
GtsTriangle * t2;
g_assert (heap);
g_assert (g_slist_length(strip) >= 2);
g_assert (t);
g_assert (v1 && v2 && v3);
g_assert (vertices_are_unique (v1, v2, v3));
while ((t2 = find_neighbor_backward (heap, t, &v1, &v2, &v3,
FALSE)) != NULL
&& (t = find_neighbor_backward (heap, t2, &v1, &v2, &v3,
TRUE)) != NULL) {
heap_remove (heap, t2);
heap_remove (heap, t);
strip = g_slist_prepend (strip, t2);
strip = g_slist_prepend (strip, t);
}
return strip;
}
static gboolean find_right_turn (GtsVertex ** v1,
GtsVertex ** v2,
GtsVertex ** v3,
GtsVertex ** v4,
GtsVertex ** v5,
GtsVertex ** v6)
{
GtsVertex * v;
g_assert (v1 && v2 && v3);
g_assert (v4 && v5 && v6);
g_assert (vertices_are_unique (*v1, *v2, *v3));
g_assert (vertices_are_unique (*v4, *v5, *v6));
g_assert (num_shared_vertices (*v1, *v2, *v3, *v4, *v5, *v6) == 2);
v = non_shared_vertex1 (*v1, *v2, *v3, *v4, *v5, *v6);
match_vertex (v, v1, v2, v3);
match_vertex (*v3, v4, v5, v6);
g_assert (v1 && v2 && v3);
g_assert (v4 && v5 && v6);
g_assert (*v4 == *v3);
if (*v5 == *v2) {
g_assert (vertices_are_unique (*v1, *v2, *v3));
g_assert (vertices_are_unique (*v4, *v5, *v6));
g_assert (num_shared_vertices (*v1, *v2, *v3,
*v4, *v5, *v6) == 2);
return TRUE;
} else {
#ifdef DEBUG
g_warning ("couldn't find a right turn");
#endif /* DEBUG */
return FALSE;
}
}
/**
* gts_surface_strip:
* @s: a #GtsSurface.
*
* Decompose @s into triangle strips for fast-rendering.
*
* Returns: a list of triangle strips containing all the triangles of @s.
* A triangle strip is itself a list of successive triangles having one edge
* in common.
*/
GSList * gts_surface_strip (GtsSurface *s)
{
GSList * strips = NULL;
heap_t * heap;
g_return_val_if_fail (s != NULL, NULL);
heap = heap_new (s);
while (!heap_is_empty (heap)) {
GtsTriangle * t1, * t2;
GtsVertex * v1, * v2, * v3, * v4, * v5, * v6;
GSList * strip = NULL;
/* remove heap top */
t1 = heap_top (heap);
g_assert (t1);
heap_remove (heap, t1);
/* start a new strip */
strip = g_slist_prepend (strip, t1);
/* find second triangle */
t2 = find_min_neighbor (heap, t1);
if (t2) {
g_assert (t2 != t1);
/* find right turn */
gts_triangle_vertices (t1, &v1, &v2, &v3);
gts_triangle_vertices (t2, &v4, &v5, &v6);
if (find_right_turn (&v1, &v2, &v3, &v4, &v5, &v6)) {
heap_remove (heap, t2);
strip = g_slist_prepend (strip, t2);
/* grow strip forward */
strip = grow_strip_forward (heap, strip, t2, v4, v5, v6);
strip = g_slist_reverse (strip);
/* grow strip backward */
strip = grow_strip_backward (heap, strip, t1, v1, v2, v3);
}
}
strips = g_slist_prepend (strips, strip);
}
strips = g_slist_reverse (strips);
heap_destroy (heap);
return strips;
}
syntax highlighted by Code2HTML, v. 0.9.1