/* 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 #include #include "gts.h" #define HEAP_INSERT_OBJECT(h, e) (GTS_OBJECT (e)->reserved =\ gts_eheap_insert (h, e)) #define HEAP_REMOVE_OBJECT(h, e) (gts_eheap_remove (h, GTS_OBJECT (e)->reserved),\ GTS_OBJECT (e)->reserved = NULL) static void psurface_destroy (GtsObject * object) { GtsPSurface * ps = GTS_PSURFACE (object); guint i; if (!GTS_PSURFACE_IS_CLOSED (ps)) gts_psurface_close (ps); for (i = 0; i < ps->split->len; i++) if (g_ptr_array_index (ps->split, i)) gts_object_destroy (GTS_OBJECT (g_ptr_array_index (ps->split, i))); g_ptr_array_free (ps->split, TRUE); (* GTS_OBJECT_CLASS (gts_psurface_class ())->parent_class->destroy) (object); } static void psurface_class_init (GtsObjectClass * klass) { klass->destroy = psurface_destroy; } static void psurface_init (GtsPSurface * psurface) { psurface->s = NULL; psurface->split = g_ptr_array_new (); psurface->split_class = gts_split_class (); psurface->pos = psurface->min = 0; psurface->vertices = psurface->faces = NULL; } /** * gts_psurface_class: * * Returns: the #GtsPSurfaceClass. */ GtsPSurfaceClass * gts_psurface_class (void) { static GtsPSurfaceClass * klass = NULL; if (klass == NULL) { GtsObjectClassInfo psurface_info = { "GtsPSurface", sizeof (GtsPSurface), sizeof (GtsPSurfaceClass), (GtsObjectClassInitFunc) psurface_class_init, (GtsObjectInitFunc) psurface_init, (GtsArgSetFunc) NULL, (GtsArgGetFunc) NULL }; klass = gts_object_class_new (gts_object_class (), &psurface_info); } return klass; } static GtsVertex * edge_collapse (GtsPSurface * ps, GtsEdge * e, GtsEHeap * heap, GtsCoarsenFunc coarsen_func, gpointer coarsen_data, gdouble maxcosine2) { GtsVertex * v1 = GTS_SEGMENT (e)->v1, * v2 = GTS_SEGMENT (e)->v2, * mid; GtsSplit * vs; GtsObject * o1, * o2; /* if the edge is degenerate (i.e. v1 == v2), destroy and return */ if (v1 == v2) { gts_object_destroy (GTS_OBJECT (e)); return NULL; } if (!gts_edge_collapse_is_valid (e) || /* check that a non-manifold edge is not a contact edge */ (g_slist_length (e->triangles) > 2 && gts_edge_is_contact (e) > 1)) { GTS_OBJECT (e)->reserved = gts_eheap_insert_with_key (heap, e, G_MAXDOUBLE); return NULL; } mid = (*coarsen_func) (e, ps->s->vertex_class, coarsen_data); if (gts_edge_collapse_creates_fold (e, mid, maxcosine2)) { GTS_OBJECT (e)->reserved = gts_eheap_insert_with_key (heap, e, G_MAXDOUBLE); gts_object_destroy (GTS_OBJECT (mid)); return NULL; } if (GTS_OBJECT (v1)->reserved) o1 = GTS_OBJECT (v1)->reserved; else o1 = GTS_OBJECT (v1); if (GTS_OBJECT (v2)->reserved) o2 = GTS_OBJECT (v2)->reserved; else o2 = GTS_OBJECT (v2); vs = gts_split_new (ps->split_class, mid, o1, o2); gts_split_collapse (vs, ps->s->edge_class, heap); GTS_OBJECT (vs->v)->reserved = vs; g_ptr_array_add (ps->split, vs); return mid; } static void update_2nd_closest_neighbors (GtsVertex * v, GtsEHeap * heap) { GSList * i = v->segments; GSList * list = NULL; while (i) { GtsSegment * s = i->data; if (GTS_IS_EDGE (s)) { GtsVertex * v1 = s->v1 == v ? s->v2 : s->v1; GSList * j = v1->segments; while (j) { GtsSegment * s1 = j->data; if (GTS_IS_EDGE (s1) && !g_slist_find (list, s1)) list = g_slist_prepend (list, s1); j = j->next; } } i = i->next; } i = list; while (i) { GtsEdge * e = i->data; if (GTS_OBJECT (e)->reserved) HEAP_REMOVE_OBJECT (heap, e); HEAP_INSERT_OBJECT (heap, e); i = i->next; } g_slist_free (list); } static gdouble edge_length2 (GtsEdge * e) { return gts_point_distance2 (GTS_POINT (GTS_SEGMENT (e)->v1), GTS_POINT (GTS_SEGMENT (e)->v2)); } static void create_heap_coarsen (GtsEdge * e, GtsEHeap * heap) { HEAP_INSERT_OBJECT (heap, e); } /* #define DEBUG_FOLD */ /* #define DEBUG_CONTACT_VERTEX */ #ifdef DEBUG_FOLD static void check_fold (GtsTriangle * t, gdouble * maxcosine2) { GtsEdge * e1 = t->e1, * e2 = t->e2, * e3 = t->e3; if (gts_triangles_are_folded (e1->triangles, GTS_SEGMENT (e1)->v1, GTS_SEGMENT (e1)->v2, *maxcosine2) || gts_triangles_are_folded (e2->triangles, GTS_SEGMENT (e2)->v1, GTS_SEGMENT (e2)->v2, *maxcosine2) || gts_triangles_are_folded (e3->triangles, GTS_SEGMENT (e3)->v1, GTS_SEGMENT (e3)->v2, *maxcosine2)) { fprintf (stderr, "triangle %p:(%p,%p,%p) is folded\n", t, e1, e2, e3); g_assert_not_reached (); } } #endif /** * gts_psurface_new: * @klass: a #GtsPSurfaceClass. * @surface: a #GtsSurface. * @split_class: a #GtsSplitClass to use for the new progressive surface. * @cost_func: cost function for the edge collapse algorithm. * @cost_data: data to pass to @cost_func. * @coarsen_func: the function returning the vertex replacement for the edge * collapse. * @coarsen_data: data to pass to @coarsen_func. * @stop_func: the function to call to decide whether to stop the coarsening * process. * @stop_data: data to pass to @stop_func. * @minangle: the minimum angle allowable between two neighboring triangles. * This is used to avoid introducing folds in the mesh during simplification. * * This function works in exactly the same way as the * gts_surface_coarsen() function, except that the history of edge * collapse is saved in an array of #GtsSplit objects. This allows for * dynamic continuous multiresolution control of the input @surface. * * Returns: a new progressive surface. */ GtsPSurface * gts_psurface_new (GtsPSurfaceClass * klass, GtsSurface * surface, GtsSplitClass * split_class, GtsKeyFunc cost_func, gpointer cost_data, GtsCoarsenFunc coarsen_func, gpointer coarsen_data, GtsStopFunc stop_func, gpointer stop_data, gdouble minangle) { GtsPSurface * psurface; GtsEHeap * heap; GtsEdge * e; gdouble top_cost, maxcosine2; guint i; g_return_val_if_fail (klass != NULL, NULL); g_return_val_if_fail (surface != NULL, NULL); g_return_val_if_fail (split_class != NULL, NULL); g_return_val_if_fail (stop_func != NULL, NULL); psurface = GTS_PSURFACE (gts_object_new (GTS_OBJECT_CLASS (klass))); psurface->s = surface; psurface->split_class = split_class; if (cost_func == NULL) cost_func = (GtsKeyFunc) edge_length2; if (coarsen_func == NULL) coarsen_func = (GtsCoarsenFunc) gts_segment_midvertex; heap = gts_eheap_new (cost_func, cost_data); maxcosine2 = cos (minangle); maxcosine2 *= maxcosine2; gts_eheap_freeze (heap); gts_surface_foreach_edge (surface, (GtsFunc) create_heap_coarsen, heap); gts_eheap_thaw (heap); /* we want to control edge destruction manually */ gts_allow_floating_edges = TRUE; while ((e = gts_eheap_remove_top (heap, &top_cost)) && (top_cost < G_MAXDOUBLE) && !(*stop_func) (top_cost, gts_eheap_size (heap) - gts_edge_face_number (e, surface), stop_data)) { GtsVertex * v = edge_collapse (psurface, e, heap, coarsen_func, coarsen_data, maxcosine2); if (v != NULL) { update_2nd_closest_neighbors (v, heap); #ifdef DEBUG_FOLD { GSList * triangles = gts_vertex_triangles (v, NULL), * i; fprintf (stderr, "\n---- Check for folds ----\n%p: ", v); i = triangles; while (i) { GtsTriangle * t = i->data; fprintf (stderr, "%p:(%p,%p,%p) ", t, t->e1, t->e2, t->e3); i = i->next; } fprintf (stderr, "\n"); g_slist_free (triangles); gts_surface_foreach_face (surface, (GtsFunc) check_fold, &maxcosine2); } #endif #ifdef DEBUG_CONTACT_VERTEX if (gts_vertex_is_contact (v, FALSE) != 1) { FILE * fptr = fopen ("after", "wt"); GSList * triangles = gts_vertex_triangles (v, NULL), * i; fprintf (stderr, "collapse of %p created a contact vertex\n", e); fprintf (fptr, "(geometry \"sphere\" { = SPHERE 0.1 0. 0. 0. })\n" "(normalization \"sphere\" none)\n"); i = triangles; while (i) { gts_write_triangle (i->data, GTS_POINT (v), fptr); i = i->next; } g_assert_not_reached (); } #endif } } gts_allow_floating_edges = FALSE; /* set reserved field of remaining edges back to NULL */ if (e) GTS_OBJECT (e)->reserved = NULL; gts_eheap_foreach (heap, (GFunc) gts_object_reset_reserved, NULL); gts_eheap_destroy (heap); psurface->pos = psurface->split->len; psurface->min = gts_surface_vertex_number (psurface->s); /* set reserved field of vertices (used to build the hierarchy) back to NULL */ for (i = 0; i < psurface->split->len; i++) { GtsSplit * vs = g_ptr_array_index (psurface->split, i); gts_object_reset_reserved (GTS_OBJECT (vs->v)); } return psurface; } /** * gts_psurface_add_vertex: * @ps: a #GtsPSurface. * * Adds a vertex to the progressive surface @ps by expanding the next * available #GtsSplit. * * Returns: the expanded #GtsSplit or %NULL if all the #GtsSplit have already * been expanded. */ GtsSplit * gts_psurface_add_vertex (GtsPSurface * ps) { GtsSplit * vs; g_return_val_if_fail (ps != NULL, NULL); g_return_val_if_fail (GTS_PSURFACE_IS_CLOSED (ps), NULL); if (ps->pos == 0) return NULL; vs = g_ptr_array_index (ps->split, --ps->pos); gts_split_expand (vs, ps->s, ps->s->edge_class); return vs; } /** * gts_psurface_remove_vertex: * @ps: a #GtsPSurface. * * Removes one vertex from the progressive surface @ps by collapsing the first * available #GtsSplit. * * Returns: the collapsed #GtsSplit or %NULL if all the #GtsSplit have already * been collapsed. */ GtsSplit * gts_psurface_remove_vertex (GtsPSurface * ps) { GtsSplit * vs; g_return_val_if_fail (ps != NULL, NULL); g_return_val_if_fail (GTS_PSURFACE_IS_CLOSED (ps), NULL); if (ps->pos == ps->split->len) return NULL; vs = g_ptr_array_index (ps->split, ps->pos++); gts_split_collapse (vs, ps->s->edge_class, NULL); return vs; } /** * gts_psurface_max_vertex_number: * @ps: a #GtsPSurface. * * Returns: the maximum number of vertices of @ps i.e. the number of vertices * if all the #GtsSplit were expanded. */ guint gts_psurface_max_vertex_number (GtsPSurface * ps) { g_return_val_if_fail (ps != NULL, 0); return ps->min + ps->split->len; } /** * gts_psurface_min_vertex_number: * @ps: a #GtsPSurface. * * Returns: the minimum number of vertices of @ps i.e. the number of vertices * if all the #GtsSplit were collapsed. */ guint gts_psurface_min_vertex_number (GtsPSurface * ps) { g_return_val_if_fail (ps != NULL, 0); return ps->min; } /** * gts_psurface_set_vertex_number: * @ps: a #GtsPSurface. * @n: a number of vertices. * * Performs the required number of collapses or expansions to set the number * of vertices of @ps to @n. */ void gts_psurface_set_vertex_number (GtsPSurface * ps, guint n) { g_return_if_fail (ps != NULL); g_return_if_fail (GTS_PSURFACE_IS_CLOSED (ps)); n = ps->min + ps->split->len - n; while (ps->pos > n && gts_psurface_add_vertex (ps)) ; while (ps->pos < n && gts_psurface_remove_vertex (ps)) ; } /** * gts_psurface_get_vertex_number: * @ps: a #GtsPSurface. * * Returns: the current number of vertices of @ps. */ guint gts_psurface_get_vertex_number (GtsPSurface * ps) { g_return_val_if_fail (ps != NULL, 0); if (!GTS_PSURFACE_IS_CLOSED (ps)) return ps->min + ps->pos; else return ps->min + ps->split->len - ps->pos; } /** * gts_psurface_foreach_vertex: * @ps: a #GtsPSurface. * @func: a function to call for each vertex of @ps. * @data: data to be passed to @func. * * Calls @func for each (potential) vertex of @ps, whether actually used * or not. The vertices are called in the order they were created during the * edge collapse operation. */ void gts_psurface_foreach_vertex (GtsPSurface * ps, GtsFunc func, gpointer data) { guint i; g_return_if_fail (ps != NULL); g_return_if_fail (func != NULL); g_return_if_fail (GTS_PSURFACE_IS_CLOSED (ps)); for (i = 0; i < ps->split->len; i++) { GtsSplit * vs = g_ptr_array_index (ps->split, i); (*func) (vs->v, data); } }