From 585ecacd586107f29933b30b844e01dabcd85e2b Mon Sep 17 00:00:00 2001 From: bert Date: Sat, 31 Dec 2016 18:49:32 +0100 Subject: [PATCH] Revert "Remove GTS sources in favour of the libgts package." This reverts commit 825caea4fe0dfe32b572017961a7f357fae97022. Signed-off-by: bert --- Makefile.am | 2 +- configure.ac | 18 +- doc/doxygen/pcb.dox | 1 + gts/.gitignore | 3 + gts/Makefile.am | 48 + gts/NOTES | 3 + gts/bbtree.c | 1289 ++++++++++++++++++++++++ gts/boolean.c | 2048 ++++++++++++++++++++++++++++++++++++++ gts/cdt.c | 1175 ++++++++++++++++++++++ gts/container.c | 493 +++++++++ gts/curvature.c | 621 ++++++++++++ gts/edge.c | 582 +++++++++++ gts/eheap.c | 461 +++++++++ gts/face.c | 297 ++++++ gts/fifo.c | 192 ++++ gts/graph.c | 1776 +++++++++++++++++++++++++++++++++ gts/gts-private.h | 37 + gts/gts.h | 2577 +++++++++++++++++++++++++++++++++++++++++++++++ gts/heap.c | 258 +++++ gts/hsurface.c | 405 ++++++++ gts/iso.c | 455 +++++++++ gts/isotetra.c | 840 ++++++++++++++++ gts/kdtree.c | 152 +++ gts/matrix.c | 728 ++++++++++++++ gts/misc.c | 692 +++++++++++++ gts/named.c | 188 ++++ gts/object.c | 345 +++++++ gts/oocs.c | 387 ++++++++ gts/partition.c | 1219 +++++++++++++++++++++++ gts/pgraph.c | 584 +++++++++++ gts/point.c | 986 ++++++++++++++++++ gts/predicates.c | 2742 ++++++++++++++++++++++++++++++++++++++++++++++++++ gts/predicates.h | 41 + gts/psurface.c | 471 +++++++++ gts/refine.c | 423 ++++++++ gts/rounding.h | 85 ++ gts/segment.c | 233 +++++ gts/split.c | 1843 ++++++++++++++++++++++++++++++++++ gts/stripe.c | 766 ++++++++++++++ gts/surface.c | 2743 +++++++++++++++++++++++++++++++++++++++++++++++++++ gts/triangle.c | 1094 ++++++++++++++++++++ gts/tribox3.c | 192 ++++ gts/vertex.c | 780 +++++++++++++++ gts/vopt.c | 522 ++++++++++ src/Makefile.am | 5 + src/toporouter.c | 2 + src/toporouter.h | 2 +- 47 files changed, 30789 insertions(+), 17 deletions(-) create mode 100644 gts/Makefile.am create mode 100644 gts/NOTES create mode 100644 gts/bbtree.c create mode 100644 gts/boolean.c create mode 100644 gts/cdt.c create mode 100644 gts/container.c create mode 100644 gts/curvature.c create mode 100644 gts/edge.c create mode 100644 gts/eheap.c create mode 100644 gts/face.c create mode 100644 gts/fifo.c create mode 100644 gts/graph.c create mode 100644 gts/gts-private.h create mode 100644 gts/gts.h create mode 100644 gts/heap.c create mode 100644 gts/hsurface.c create mode 100644 gts/iso.c create mode 100644 gts/isotetra.c create mode 100644 gts/kdtree.c create mode 100644 gts/matrix.c create mode 100644 gts/misc.c create mode 100644 gts/named.c create mode 100644 gts/object.c create mode 100644 gts/oocs.c create mode 100644 gts/partition.c create mode 100644 gts/pgraph.c create mode 100644 gts/point.c create mode 100644 gts/predicates.c create mode 100644 gts/predicates.h create mode 100644 gts/psurface.c create mode 100644 gts/refine.c create mode 100644 gts/rounding.h create mode 100644 gts/segment.c create mode 100644 gts/split.c create mode 100644 gts/stripe.c create mode 100644 gts/surface.c create mode 100644 gts/triangle.c create mode 100644 gts/tribox3.c create mode 100644 gts/vertex.c create mode 100644 gts/vopt.c diff --git a/Makefile.am b/Makefile.am index 0ce15e872a..6f9a94d543 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,7 +2,7 @@ ## ## Top level automake file for PCB -DIRS= w32 intl src data lib newlib example tools tutorial README_FILES po tests +DIRS= w32 intl gts src data lib newlib example tools tutorial README_FILES po tests SUBDIRS= ${DIRS} @DOC@ DIST_SUBDIRS= ${DIRS} doc diff --git a/configure.ac b/configure.ac index 8b32c460c2..a815f1bd1c 100644 --- a/configure.ac +++ b/configure.ac @@ -315,21 +315,6 @@ AS_CASE(["x$enable_toporouter"],[xyes | xno],, AC_MSG_RESULT([$enable_toporouter]) AM_CONDITIONAL([WITH_TOPOROUTER], test $enable_toporouter != no) -if test "x$enable_toporouter" = "xyes"; then - this_error_text=" - You have requested to build with the toporouter. For this to work you - must have the GTS library installed, typically found in packages named - 'libgts-dev' or similar. - - Alternatively you can disable the toporouter with --disable-toporouter." - # Testing for gts.h requires glib.h, which is tested for later. To not take - # apart toporouter related stuff and because testing for the library makes - # reasonably sure that GTS is available, we omit a test for gts.h. - #AC_CHECK_HEADER(gts.h, , AC_MSG_ERROR($this_error_text), glib.h) - AC_SEARCH_LIBS(gts_object_class_new, gts, , AC_MSG_ERROR($this_error_text)) - unset this_error_text -fi - AC_MSG_CHECKING([whether to enable toporouter output]) AC_ARG_ENABLE([toporouter-output], [AS_HELP_STRING([--enable-toporouter-output], [enable toporouter graphical output [default=no]]) ] @@ -1314,6 +1299,9 @@ fi dnl testsuite AC_CONFIG_FILES(tests/Makefile) +dnl GTS 0.7.6 - http://gts.sourceforge.net/ +AC_CONFIG_FILES(gts/Makefile) + dnl win32 build scripts AC_CONFIG_FILES(w32/Makefile) diff --git a/doc/doxygen/pcb.dox b/doc/doxygen/pcb.dox index 2e179587b2..11905e5a2d 100644 --- a/doc/doxygen/pcb.dox +++ b/doc/doxygen/pcb.dox @@ -601,6 +601,7 @@ WARN_LOGFILE = INPUT = ../../doc/doxygen/pcb-main.txt \ ../../doc/doxygen \ ../../src \ + ../../gts # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is diff --git a/gts/.gitignore b/gts/.gitignore index 8862c08d4e..1dfc2eaa87 100644 --- a/gts/.gitignore +++ b/gts/.gitignore @@ -1 +1,4 @@ *.[oa] +.dirstamp +predicates_init.h + diff --git a/gts/Makefile.am b/gts/Makefile.am new file mode 100644 index 0000000000..85af46458a --- /dev/null +++ b/gts/Makefile.am @@ -0,0 +1,48 @@ +## Process this file with automake to produce Makefile.in + +AM_CPPFLAGS = -I$(srcdir) -DG_LOG_DOMAIN=\"Gts\" + +noinst_LIBRARIES = libgts.a + +libgts_a_SOURCES = \ + object.c \ + point.c \ + vertex.c \ + segment.c \ + edge.c \ + triangle.c \ + face.c \ + kdtree.c \ + bbtree.c \ + misc.c \ + gts.h \ + gts-private.h \ + predicates.c \ + predicates.h \ + rounding.h \ + heap.c \ + eheap.c \ + fifo.c \ + matrix.c \ + surface.c \ + stripe.c \ + vopt.c \ + refine.c \ + iso.c \ + isotetra.c \ + split.c \ + psurface.c \ + hsurface.c \ + cdt.c \ + boolean.c \ + named.c \ + oocs.c \ + container.c \ + graph.c \ + pgraph.c \ + partition.c \ + curvature.c \ + tribox3.c + +noinst_HEADERS = \ + gts.h diff --git a/gts/NOTES b/gts/NOTES new file mode 100644 index 0000000000..c697949f79 --- /dev/null +++ b/gts/NOTES @@ -0,0 +1,3 @@ +- hsurface may create surfaces with duplicates edges and triangles (because of + collapses of "empty triangles"). +- psurface however will not. diff --git a/gts/bbtree.c b/gts/bbtree.c new file mode 100644 index 0000000000..cec93e4cf6 --- /dev/null +++ b/gts/bbtree.c @@ -0,0 +1,1289 @@ +/* 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 "gts.h" + +static void bbox_init (GtsBBox * bbox) +{ + bbox->bounded = NULL; +} + +/** + * gts_bbox_class: + * + * Returns: the #GtsBBoxClass. + */ +GtsBBoxClass * gts_bbox_class (void) +{ + static GtsBBoxClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo bbox_info = { + "GtsBBox", + sizeof (GtsBBox), + sizeof (GtsBBoxClass), + (GtsObjectClassInitFunc) NULL, + (GtsObjectInitFunc) bbox_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (gts_object_class (), &bbox_info); + } + + return klass; +} + +/** + * gts_bbox_set: + * @bbox: a #GtsBBox. + * @bounded: the object to be bounded. + * @x1: x-coordinate of the lower left corner. + * @y1: y-coordinate of the lower left corner. + * @z1: z-coordinate of the lower left corner. + * @x2: x-coordinate of the upper right corner. + * @y2: y-coordinate of the upper right corner. + * @z2: z-coordinate of the upper right corner. + * + * Sets fields of @bbox. + */ +void gts_bbox_set (GtsBBox * bbox, + gpointer bounded, + gdouble x1, gdouble y1, gdouble z1, + gdouble x2, gdouble y2, gdouble z2) +{ + g_return_if_fail (bbox != NULL); + g_return_if_fail (x2 >= x1 && y2 >= y1 && z2 >= z1); + + bbox->x1 = x1; bbox->y1 = y1; bbox->z1 = z1; + bbox->x2 = x2; bbox->y2 = y2; bbox->z2 = z2; + bbox->bounded = bounded; +} + +/** + * gts_bbox_new: + * @klass: a #GtsBBoxClass. + * @bounded: the object to be bounded. + * @x1: x-coordinate of the lower left corner. + * @y1: y-coordinate of the lower left corner. + * @z1: z-coordinate of the lower left corner. + * @x2: x-coordinate of the upper right corner. + * @y2: y-coordinate of the upper right corner. + * @z2: z-coordinate of the upper right corner. + * + * Returns: a new #GtsBBox. + */ +GtsBBox * gts_bbox_new (GtsBBoxClass * klass, + gpointer bounded, + gdouble x1, gdouble y1, gdouble z1, + gdouble x2, gdouble y2, gdouble z2) +{ + GtsBBox * bbox; + + g_return_val_if_fail (klass != NULL, NULL); + + bbox = GTS_BBOX (gts_object_new (GTS_OBJECT_CLASS (klass))); + gts_bbox_set (bbox, bounded, x1, y1, z1, x2, y2, z2); + return bbox; +} + +/** + * gts_bbox_triangle: + * @klass: a #GtsBBoxClass. + * @t: a #GtsTriangle. + * + * Returns: a new #GtsBBox bounding box of @t. + */ +GtsBBox * gts_bbox_triangle (GtsBBoxClass * klass, + GtsTriangle * t) +{ + GtsBBox * bbox; + GtsPoint * p; + + g_return_val_if_fail (t != NULL, NULL); + g_return_val_if_fail (klass != NULL, NULL); + + p = GTS_POINT (GTS_SEGMENT (t->e1)->v1); + bbox = gts_bbox_new (klass, t, p->x, p->y, p->z, p->x, p->y, p->z); + + p = GTS_POINT (GTS_SEGMENT (t->e1)->v2); + if (p->x > bbox->x2) bbox->x2 = p->x; + if (p->x < bbox->x1) bbox->x1 = p->x; + if (p->y > bbox->y2) bbox->y2 = p->y; + if (p->y < bbox->y1) bbox->y1 = p->y; + if (p->z > bbox->z2) bbox->z2 = p->z; + if (p->z < bbox->z1) bbox->z1 = p->z; + p = GTS_POINT (gts_triangle_vertex (t)); + if (p->x > bbox->x2) bbox->x2 = p->x; + if (p->x < bbox->x1) bbox->x1 = p->x; + if (p->y > bbox->y2) bbox->y2 = p->y; + if (p->y < bbox->y1) bbox->y1 = p->y; + if (p->z > bbox->z2) bbox->z2 = p->z; + if (p->z < bbox->z1) bbox->z1 = p->z; + + return bbox; +} + +/** + * gts_bbox_segment: + * @klass: a #GtsBBoxClass. + * @s: a #GtsSegment. + * + * Returns: a new #GtsBBox bounding box of @s. + */ +GtsBBox * gts_bbox_segment (GtsBBoxClass * klass, GtsSegment * s) +{ + GtsBBox * bbox; + GtsPoint * p1, * p2; + + g_return_val_if_fail (s != NULL, NULL); + g_return_val_if_fail (klass != NULL, NULL); + + bbox = gts_bbox_new (klass, s, 0., 0., 0., 0., 0., 0.); + + p1 = GTS_POINT (s->v1); + p2 = GTS_POINT (s->v2); + if (p1->x > p2->x) { + bbox->x2 = p1->x; bbox->x1 = p2->x; + } + else { + bbox->x1 = p1->x; bbox->x2 = p2->x; + } + if (p1->y > p2->y) { + bbox->y2 = p1->y; bbox->y1 = p2->y; + } + else { + bbox->y1 = p1->y; bbox->y2 = p2->y; + } + if (p1->z > p2->z) { + bbox->z2 = p1->z; bbox->z1 = p2->z; + } + else { + bbox->z1 = p1->z; bbox->z2 = p2->z; + } + + return bbox; +} + +static void bbox_foreach_vertex (GtsPoint * p, GtsBBox * bb) +{ + if (p->x < bb->x1) bb->x1 = p->x; + if (p->y < bb->y1) bb->y1 = p->y; + if (p->z < bb->z1) bb->z1 = p->z; + if (p->x > bb->x2) bb->x2 = p->x; + if (p->y > bb->y2) bb->y2 = p->y; + if (p->z > bb->z2) bb->z2 = p->z; +} + +/** + * gts_bbox_surface: + * @klass: a #GtsBBoxClass. + * @surface: a #GtsSurface. + * + * Returns: a new #GtsBBox bounding box of @surface. + */ +GtsBBox * gts_bbox_surface (GtsBBoxClass * klass, GtsSurface * surface) +{ + GtsBBox * bbox; + + g_return_val_if_fail (klass != NULL, NULL); + g_return_val_if_fail (surface != NULL, NULL); + + bbox = gts_bbox_new (klass, surface, 0., 0., 0., 0., 0., 0.); + bbox->x1 = bbox->y1 = bbox->z1 = G_MAXDOUBLE; + bbox->x2 = bbox->y2 = bbox->z2 = -G_MAXDOUBLE; + + gts_surface_foreach_vertex (surface, (GtsFunc) bbox_foreach_vertex, bbox); + + return bbox; +} + +/** + * gts_bbox_bboxes: + * @klass: a #GtsBBoxClass. + * @bboxes: a list of #GtsBBox. + * + * Returns: a new #GtsBBox bounding box of all the bounding boxes in + * @bboxes. + */ +GtsBBox * gts_bbox_bboxes (GtsBBoxClass * klass, GSList * bboxes) +{ + GtsBBox * bbox; + GtsBBox * bb; + + g_return_val_if_fail (bboxes != NULL, NULL); + g_return_val_if_fail (klass != NULL, NULL); + + bb = bboxes->data; + bbox = gts_bbox_new (klass, bboxes, + bb->x1, bb->y1, bb->z1, bb->x2, bb->y2, bb->z2); + bboxes = bboxes->next; + while (bboxes) { + bb = bboxes->data; + if (bb->x1 < bbox->x1) bbox->x1 = bb->x1; + if (bb->y1 < bbox->y1) bbox->y1 = bb->y1; + if (bb->z1 < bbox->z1) bbox->z1 = bb->z1; + if (bb->x2 > bbox->x2) bbox->x2 = bb->x2; + if (bb->y2 > bbox->y2) bbox->y2 = bb->y2; + if (bb->z2 > bbox->z2) bbox->z2 = bb->z2; + bboxes = bboxes->next; + } + + return bbox; +} + +/** + * gts_bbox_points: + * @klass: a #GtsBBoxClass. + * @points: a list of #GtsPoint. + * + * Returns: a new #GtsBBox bounding box of @points. + */ +GtsBBox * gts_bbox_points (GtsBBoxClass * klass, GSList * points) +{ + GtsPoint * p; + GtsBBox * bbox; + GSList * i; + + if (points == NULL) + return NULL; + + p = points->data; + bbox = gts_bbox_new (klass, points, p->x, p->y, p->z, p->x, p->y, p->z); + + i = points->next; + while (i) { + p = i->data; + if (p->x > bbox->x2) + bbox->x2 = p->x; + else if (p->x < bbox->x1) + bbox->x1 = p->x; + if (p->y > bbox->y2) + bbox->y2 = p->y; + else if (p->y < bbox->y1) + bbox->y1 = p->y; + if (p->z > bbox->z2) + bbox->z2 = p->z; + else if (p->z < bbox->z1) + bbox->z1 = p->z; + i = i->next; + } + + return bbox; +} + +/** + * gts_bboxes_are_overlapping: + * @bb1: a #GtsBBox. + * @bb2: a #GtsBBox. + * + * Returns: %TRUE if the bounding boxes @bb1 and @bb2 are overlapping + * (including just touching), %FALSE otherwise. + */ +gboolean gts_bboxes_are_overlapping (GtsBBox * bb1, GtsBBox * bb2) +{ + if (bb1 == bb2) + return TRUE; + if (bb1->x1 > bb2->x2) + return FALSE; + if (bb2->x1 > bb1->x2) + return FALSE; + if (bb1->y1 > bb2->y2) + return FALSE; + if (bb2->y1 > bb1->y2) + return FALSE; + if (bb1->z1 > bb2->z2) + return FALSE; + if (bb2->z1 > bb1->z2) + return FALSE; + return TRUE; +} + +#define bbox_volume(bb) (((bb)->x2 -\ + (bb)->x1)*\ + ((bb)->y2 -\ + (bb)->y1)*\ + ((bb)->z2 -\ + (bb)->z1)) + +/** + * gts_bbox_diagonal2: + * @bb: a #GtsBBox. + * + * Returns: the squared length of the diagonal of @bb. + */ +gdouble gts_bbox_diagonal2 (GtsBBox * bb) +{ + gdouble x, y, z; + + g_return_val_if_fail (bb != NULL, 0.); + + x = bb->x2 - bb->x1; + y = bb->y2 - bb->y1; + z = bb->z2 - bb->z1; + + return x*x + y*y + z*z; +} + +/** + * gts_bbox_draw: + * @bb: a #GtsBBox. + * @fptr: a file pointer. + * + * Writes in file @fptr an OOGL (Geomview) description of @bb. + */ +void gts_bbox_draw (GtsBBox * bb, FILE * fptr) +{ + g_return_if_fail (bb != NULL); + + fprintf (fptr, "OFF 8 6 12\n"); + fprintf (fptr, "%g %g %g\n", + bb->x1, bb->y1, bb->z1); + fprintf (fptr, "%g %g %g\n", + bb->x2, bb->y1, bb->z1); + fprintf (fptr, "%g %g %g\n", + bb->x2, bb->y2, bb->z1); + fprintf (fptr, "%g %g %g\n", + bb->x1, bb->y2, bb->z1); + fprintf (fptr, "%g %g %g\n", + bb->x1, bb->y1, bb->z2); + fprintf (fptr, "%g %g %g\n", + bb->x2, bb->y1, bb->z2); + fprintf (fptr, "%g %g %g\n", + bb->x2, bb->y2, bb->z2); + fprintf (fptr, "%g %g %g\n", + bb->x1, bb->y2, bb->z2); + fputs ("4 3 2 1 0\n" + "4 4 5 6 7\n" + "4 2 3 7 6\n" + "4 0 1 5 4\n" + "4 0 4 7 3\n" + "4 1 2 6 5\n", + fptr); +} + +#define MINMAX(x1, x2, xmin, xmax) { if (x1 < x2) { xmin = x1; xmax = x2; }\ + else { xmin = x2; xmax = x1; } } + +/** + * gts_bbox_point_distance2: + * @bb: a #GtsBBox. + * @p: a #GtsPoint. + * @min: a pointer on a gdouble. + * @max: a pointer on a gdouble. + * + * Sets @min and @max to lower and upper bounds for the square of the + * Euclidean distance between the object contained in @bb and @p. For these + * bounds to make any sense the bounding box must be "tight" i.e. each of the + * 6 faces of the box must at least be touched by one point of the bounded + * object. + */ +void gts_bbox_point_distance2 (GtsBBox * bb, GtsPoint * p, + gdouble * min, gdouble * max) +{ + gdouble x1, y1, z1, x2, y2, z2, x, y, z; + gdouble dmin, dmax, xd1, xd2, yd1, yd2, zd1, zd2; + gdouble mx, Mx, my, My, mz, Mz; + + g_return_if_fail (bb != NULL); + g_return_if_fail (p != NULL); + g_return_if_fail (min != NULL); + g_return_if_fail (max != NULL); + + x1 = bb->x1; y1 = bb->y1; z1 = bb->z1; + x2 = bb->x2; y2 = bb->y2; z2 = bb->z2; + x = p->x; y = p->y; z = p->z; + + xd1 = (x1 - x)*(x1 - x); + xd2 = (x - x2)*(x - x2); + yd1 = (y1 - y)*(y1 - y); + yd2 = (y - y2)*(y - y2); + zd1 = (z1 - z)*(z1 - z); + zd2 = (z - z2)*(z - z2); + + dmin = x < x1 ? xd1 : x > x2 ? xd2 : 0.0; + dmin += y < y1 ? yd1 : y > y2 ? yd2 : 0.0; + dmin += z < z1 ? zd1 : z > z2 ? zd2 : 0.0; + + MINMAX (xd1, xd2, mx, Mx); + MINMAX (yd1, yd2, my, My); + MINMAX (zd1, zd2, mz, Mz); + + dmax = mx + My + Mz; + dmax = MIN (dmax, Mx + my + Mz); + dmax = MIN (dmax, Mx + My + mz); + + *min = dmin; + *max = dmax; +} + +/** + * gts_bbox_is_stabbed: + * @bb: a #GtsBBox. + * @p: a #GtsPoint. + * + * Returns: %TRUE if the ray starting at @p and ending at (+infty, + * @p->y, @p->z) intersects with @bb, %FALSE otherwise. + */ +gboolean gts_bbox_is_stabbed (GtsBBox * bb, GtsPoint * p) +{ + g_return_val_if_fail (bb != NULL, FALSE); + g_return_val_if_fail (p != NULL, FALSE); + + if (p->x > bb->x2 || + p->y < bb->y1 || p->y > bb->y2 || + p->z < bb->z1 || p->z > bb->z2) + return FALSE; + return TRUE; +} + +extern int triBoxOverlap (double boxcenter[3], + double boxhalfsize[3], + double triverts[3][3]); + +/** + * gts_bbox_overlaps_triangle: + * @bb: a #GtsBBox. + * @t: a #GtsTriangle. + * + * This is a wrapper around the fast overlap test of Tomas + * Akenine-Moller (http://www.cs.lth.se/home/Tomas_Akenine_Moller/). + * + * Returns: %TRUE if @bb overlaps with @t, %FALSE otherwise. + */ +gboolean gts_bbox_overlaps_triangle (GtsBBox * bb, GtsTriangle * t) +{ + double bc[3], bh[3], tv[3][3]; + GtsPoint * p1, * p2, * p3; + + g_return_val_if_fail (bb != NULL, FALSE); + g_return_val_if_fail (t != NULL, FALSE); + + bc[0] = (bb->x2 + bb->x1)/2.; + bh[0] = (bb->x2 - bb->x1)/2.; + bc[1] = (bb->y2 + bb->y1)/2.; + bh[1] = (bb->y2 - bb->y1)/2.; + bc[2] = (bb->z2 + bb->z1)/2.; + bh[2] = (bb->z2 - bb->z1)/2.; + p1 = GTS_POINT (GTS_SEGMENT (t->e1)->v1); + p2 = GTS_POINT (GTS_SEGMENT (t->e1)->v2); + p3 = GTS_POINT (gts_triangle_vertex (t)); + tv[0][0] = p1->x; tv[0][1] = p1->y; tv[0][2] = p1->z; + tv[1][0] = p2->x; tv[1][1] = p2->y; tv[1][2] = p2->z; + tv[2][0] = p3->x; tv[2][1] = p3->y; tv[2][2] = p3->z; + + return triBoxOverlap (bc, bh, tv); +} + +/** + * gts_bbox_overlaps_segment: + * @bb: a #GtsBBox. + * @s: a #GtsSegment. + * + * This functions uses gts_bbox_overlaps_triangle() with a degenerate + * triangle. + * + * Returns: %TRUE if @bb overlaps with @s, %FALSE otherwise. + */ +gboolean gts_bbox_overlaps_segment (GtsBBox * bb, GtsSegment * s) +{ + double bc[3], bh[3], tv[3][3]; + GtsPoint * p1, * p2, * p3; + + g_return_val_if_fail (bb != NULL, FALSE); + g_return_val_if_fail (s != NULL, FALSE); + + bc[0] = (bb->x2 + bb->x1)/2.; + bh[0] = (bb->x2 - bb->x1)/2.; + bc[1] = (bb->y2 + bb->y1)/2.; + bh[1] = (bb->y2 - bb->y1)/2.; + bc[2] = (bb->z2 + bb->z1)/2.; + bh[2] = (bb->z2 - bb->z1)/2.; + p1 = GTS_POINT (s->v1); + p2 = GTS_POINT (s->v2); + p3 = p1; + tv[0][0] = p1->x; tv[0][1] = p1->y; tv[0][2] = p1->z; + tv[1][0] = p2->x; tv[1][1] = p2->y; tv[1][2] = p2->z; + tv[2][0] = p3->x; tv[2][1] = p3->y; tv[2][2] = p3->z; + + return triBoxOverlap (bc, bh, tv); +} + +/** + * gts_bb_tree_new: + * @bboxes: a list of #GtsBBox. + * + * Builds a new hierarchy of bounding boxes for @bboxes. At each + * level, the GNode->data field contains a #GtsBBox bounding box of + * all the children. The tree is binary and is built by repeatedly + * cutting in two approximately equal halves the bounding boxes at + * each level until a leaf node (i.e. a bounding box given in @bboxes) + * is reached. In order to minimize the depth of the tree, the cutting + * direction is always chosen as perpendicular to the longest + * dimension of the bounding box. + * + * Returns: a new hierarchy of bounding boxes. + */ +GNode * gts_bb_tree_new (GSList * bboxes) +{ + GSList * i, * positive = NULL, * negative = NULL; + GNode * node; + GtsBBox * bbox; + guint dir, np = 0, nn = 0; + gdouble * p1, * p2; + gdouble cut; + + g_return_val_if_fail (bboxes != NULL, NULL); + + if (bboxes->next == NULL) /* leaf node */ + return g_node_new (bboxes->data); + + bbox = gts_bbox_bboxes (gts_bbox_class (), bboxes); + node = g_node_new (bbox); + + if (bbox->x2 - bbox->x1 > bbox->y2 - bbox->y1) { + if (bbox->z2 - bbox->z1 > bbox->x2 - bbox->x1) + dir = 2; + else + dir = 0; + } + else if (bbox->z2 - bbox->z1 > bbox->y2 - bbox->y1) + dir = 2; + else + dir = 1; + + p1 = (gdouble *) &bbox->x1; + p2 = (gdouble *) &bbox->x2; + cut = (p1[dir] + p2[dir])/2.; + i = bboxes; + while (i) { + bbox = i->data; + p1 = (gdouble *) &bbox->x1; + p2 = (gdouble *) &bbox->x2; + if ((p1[dir] + p2[dir])/2. > cut) { + positive = g_slist_prepend (positive, bbox); + np++; + } + else { + negative = g_slist_prepend (negative, bbox); + nn++; + } + i = i->next; + } + if (!positive) { + GSList * last = g_slist_nth (negative, (nn - 1)/2); + positive = last->next; + last->next = NULL; + } + else if (!negative) { + GSList * last = g_slist_nth (positive, (np - 1)/2); + negative = last->next; + last->next = NULL; + } + g_node_prepend (node, gts_bb_tree_new (positive)); + g_slist_free (positive); + g_node_prepend (node, gts_bb_tree_new (negative)); + g_slist_free (negative); + + return node; +} + +static void prepend_triangle_bbox (GtsTriangle * t, GSList ** bboxes) +{ + *bboxes = g_slist_prepend (*bboxes, + gts_bbox_triangle (gts_bbox_class (), t)); +} + +/** + * gts_bb_tree_surface: + * @s: a #GtsSurface. + * + * Returns: a new hierarchy of bounding boxes bounding the faces of @s. + */ +GNode * gts_bb_tree_surface (GtsSurface * s) +{ + GSList * bboxes = NULL; + GNode * tree; + + g_return_val_if_fail (s != NULL, NULL); + + gts_surface_foreach_face (s, (GtsFunc) prepend_triangle_bbox, &bboxes); + tree = gts_bb_tree_new (bboxes); + g_slist_free (bboxes); + + return tree; +} + +/** + * gts_bb_tree_stabbed: + * @tree: a bounding box tree. + * @p: a #GtsPoint. + * + * Returns: a list of bounding boxes, leaves of @tree which are + * stabbed by the ray defined by @p (see gts_bbox_is_stabbed()). + */ +GSList * gts_bb_tree_stabbed (GNode * tree, GtsPoint * p) +{ + GSList * list = NULL; + GtsBBox * bb; + GNode * i; + + g_return_val_if_fail (tree != NULL, NULL); + g_return_val_if_fail (p != NULL, NULL); + + bb = tree->data; + if (!gts_bbox_is_stabbed (bb, p)) + return NULL; + if (tree->children == NULL) /* leaf node */ + return g_slist_prepend (NULL, bb); + i = tree->children; + while (i) { + list = g_slist_concat (list, gts_bb_tree_stabbed (i, p)); + i = i->next; + } + return list; +} + +/** + * gts_bb_tree_overlap: + * @tree: a bounding box tree. + * @bbox: a #GtsBBox. + * + * Returns: a list of bounding boxes, leaves of @tree which overlap @bbox. + */ +GSList * gts_bb_tree_overlap (GNode * tree, GtsBBox * bbox) +{ + GSList * list = NULL; + GtsBBox * bb; + GNode * i; + + g_return_val_if_fail (tree != NULL, NULL); + g_return_val_if_fail (bbox != NULL, NULL); + + bb = tree->data; + if (!gts_bboxes_are_overlapping (bbox, bb)) + return NULL; + if (tree->children == NULL) /* leaf node */ + return g_slist_prepend (NULL, bb); + i = tree->children; + while (i) { + list = g_slist_concat (list, gts_bb_tree_overlap (i, bbox)); + i = i->next; + } + return list; +} + +/** + * gts_bb_tree_is_overlapping: + * @tree: a bounding box tree. + * @bbox: a #GtsBBox. + * + * Returns: %TRUE if any leaf of @tree overlaps @bbox, %FALSE otherwise. + */ +gboolean gts_bb_tree_is_overlapping (GNode * tree, GtsBBox * bbox) +{ + GtsBBox * bb; + GNode * i; + + g_return_val_if_fail (tree != NULL, FALSE); + g_return_val_if_fail (bbox != NULL, FALSE); + + bb = tree->data; + if (!gts_bboxes_are_overlapping (bbox, bb)) + return FALSE; + if (tree->children == NULL) /* leaf node */ + return TRUE; + i = tree->children; + while (i) { + if (gts_bb_tree_is_overlapping (i, bbox)) + return TRUE; + i = i->next; + } + return FALSE; +} + +/** + * gts_bb_tree_traverse_overlapping: + * @tree1: a bounding box tree. + * @tree2: a bounding box tree. + * @func: a #GtsBBTreeTraverseFunc. + * @data: user data to be passed to @func. + * + * Calls @func for each overlapping pair of leaves of @tree1 and @tree2. + */ +void gts_bb_tree_traverse_overlapping (GNode * tree1, GNode * tree2, + GtsBBTreeTraverseFunc func, + gpointer data) +{ + GtsBBox * bb1, * bb2; + + g_return_if_fail (tree1 != NULL && tree2 != NULL); + + bb1 = tree1->data; bb2 = tree2->data; + if (!gts_bboxes_are_overlapping (bb1, bb2)) + return; + + if (tree1->children == NULL && tree2->children == NULL) + (*func) (tree1->data, tree2->data, data); + else if (tree2->children == NULL || + (tree1->children != NULL && + bbox_volume (bb1) > bbox_volume (bb2))) { + GNode * i = tree1->children; + while (i) { + gts_bb_tree_traverse_overlapping (i, tree2, func, data); + i = i->next; + } + } + else { + GNode * i = tree2->children; + while (i) { + gts_bb_tree_traverse_overlapping (tree1, i, func, data); + i = i->next; + } + } +} + +/** + * gts_bb_tree_draw: + * @tree: a bounding box tree. + * @depth: a specified depth. + * @fptr: a file pointer. + * + * Write in @fptr an OOGL (Geomview) description of @tree for the + * depth specified by @depth. + */ +void gts_bb_tree_draw (GNode * tree, guint depth, FILE * fptr) +{ + guint d; + + g_return_if_fail (tree != NULL); + g_return_if_fail (fptr != NULL); + + d = g_node_depth (tree); + + if (d == 1) + fprintf (fptr, "{ LIST"); + + if (d == depth) + gts_bbox_draw (tree->data, fptr); + else if (d < depth) { + GNode * i = tree->children; + while (i) { + gts_bb_tree_draw (i, depth, fptr); + i = i->next; + } + } + + if (d == 1) + fprintf (fptr, "}\n"); +} + +static void bb_tree_free (GNode * tree, gboolean free_leaves) +{ + GNode * i; + + g_return_if_fail (tree != NULL); + + if (!free_leaves && tree->children == NULL) /* leaf node */ + return; + + gts_object_destroy (tree->data); + + i = tree->children; + while (i) { + bb_tree_free (i, free_leaves); + i = i->next; + } +} + +/** + * gts_bb_tree_destroy: + * @tree: a bounding box tree. + * @free_leaves: if %TRUE the bounding boxes given by the user are freed. + * + * Destroys all the bounding boxes created by @tree and destroys the + * tree itself. If @free_leaves is set to %TRUE, destroys boxes given + * by the user when creating the tree (i.e. leaves of the tree). + */ +void gts_bb_tree_destroy (GNode * tree, gboolean free_leaves) +{ + g_return_if_fail (tree != NULL); + + bb_tree_free (tree, free_leaves); + g_node_destroy (tree); +} + +static gdouble bb_tree_min_max (GNode * tree, + GtsPoint * p, + gdouble min_max, + GSList ** list) +{ + GNode * tree1, * tree2; + gdouble min1, max1, min2, max2; + + if (tree->children == NULL) { + *list = g_slist_prepend (*list, tree->data); + return min_max; + } + tree1 = tree->children; + gts_bbox_point_distance2 (tree1->data, p, &min1, &max1); + if (max1 < min_max) + min_max = max1; + + tree2 = tree1->next; + gts_bbox_point_distance2 (tree2->data, p, &min2, &max2); + if (max2 < min_max) + min_max = max2; + + if (min1 < min2) { + if (min1 <= min_max) { + min_max = bb_tree_min_max (tree1, p, min_max, list); + if (min2 <= min_max) + min_max = bb_tree_min_max (tree2, p, min_max, list); + } + } + else { + if (min2 <= min_max) { + min_max = bb_tree_min_max (tree2, p, min_max, list); + if (min1 <= min_max) + min_max = bb_tree_min_max (tree1, p, min_max, list); + } + } + + return min_max; +} + +/** + * gts_bb_tree_point_closest_bboxes: + * @tree: a bounding box tree. + * @p: a #GtsPoint. + * + * Returns: a list of #GtsBBox. One of the bounding boxes is assured to contain + * the object of @tree closest to @p. + */ +GSList * gts_bb_tree_point_closest_bboxes (GNode * tree, + GtsPoint * p) +{ + gdouble min, min_max; + GSList * list = NULL, * i, * prev = NULL; + + g_return_val_if_fail (tree != NULL, NULL); + g_return_val_if_fail (p != NULL, NULL); + + gts_bbox_point_distance2 (tree->data, p, &min, &min_max); + min_max = bb_tree_min_max (tree, p, min_max, &list); + + i = list; + while (i) { + GSList * next = i->next; + gdouble min, max; + + gts_bbox_point_distance2 (i->data, p, &min, &max); + + if (min > min_max) { + if (prev == NULL) + list = next; + else + prev->next = next; + g_slist_free_1 (i); + } + else + prev = i; + i = next; + } + + return list; +} + +/** + * gts_bb_tree_point_distance: + * @tree: a bounding box tree. + * @p: a #GtsPoint. + * @distance: a #GtsBBoxDistFunc. + * @bbox: if not %NULL is set to the bounding box containing the closest + * object. + * + * Returns: the distance as evaluated by @distance between @p and the closest + * object in @tree. + */ +gdouble gts_bb_tree_point_distance (GNode * tree, + GtsPoint * p, + GtsBBoxDistFunc distance, + GtsBBox ** bbox) +{ + GSList * list, * i; + gdouble dmin = G_MAXDOUBLE; + + g_return_val_if_fail (tree != NULL, dmin); + g_return_val_if_fail (p != NULL, dmin); + g_return_val_if_fail (distance != NULL, dmin); + + i = list = gts_bb_tree_point_closest_bboxes (tree, p); + while (i) { + gdouble d = (*distance) (p, GTS_BBOX (i->data)->bounded); + + if (fabs (d) < fabs (dmin)) { + dmin = d; + if (bbox) + *bbox = i->data; + } + i = i->next; + } + g_slist_free (list); + + return dmin; +} + +/** + * gts_bb_tree_point_closest: + * @tree: a bounding box tree. + * @p: a #GtsPoint. + * @closest: a #GtsBBoxClosestFunc. + * @distance: if not %NULL is set to the distance between @p and the + * new #GtsPoint. + * + * Returns: a new #GtsPoint, closest point to @p and belonging to an object of + * @tree. + */ +GtsPoint * gts_bb_tree_point_closest (GNode * tree, + GtsPoint * p, + GtsBBoxClosestFunc closest, + gdouble * distance) +{ + GSList * list, * i; + gdouble dmin = G_MAXDOUBLE; + GtsPoint * np = NULL; + + g_return_val_if_fail (tree != NULL, NULL); + g_return_val_if_fail (p != NULL, NULL); + g_return_val_if_fail (closest != NULL, NULL); + + i = list = gts_bb_tree_point_closest_bboxes (tree, p); + while (i) { + GtsPoint * tp = (*closest) (p, GTS_BBOX (i->data)->bounded); + gdouble d = gts_point_distance2 (tp, p); + + if (d < dmin) { + if (np) + gts_object_destroy (GTS_OBJECT (np)); + np = tp; + dmin = d; + } + else + gts_object_destroy (GTS_OBJECT (tp)); + i = i->next; + } + g_slist_free (list); + + if (distance) + *distance = dmin; + + return np; +} + +/** + * gts_bb_tree_triangle_distance: + * @tree: a bounding box tree. + * @t: a #GtsTriangle. + * @distance: a #GtsBBoxDistFunc. + * @delta: spatial scale of the sampling to be used. + * @range: a #GtsRange to be filled with the results. + * + * Given a triangle @t, points are sampled regularly on its surface + * using @delta as increment. The distance from each of these points + * to the closest object of @tree is computed using @distance and the + * gts_bb_tree_point_distance() function. The fields of @range are + * filled with the number of points sampled, the minimum, average and + * maximum value and the standard deviation. + */ +void gts_bb_tree_triangle_distance (GNode * tree, + GtsTriangle * t, + GtsBBoxDistFunc distance, + gdouble delta, + GtsRange * range) +{ + GtsPoint * p1, * p2, * p3, * p; + GtsVector p1p2, p1p3; + gdouble l1, t1, dt1; + guint i, n1; + + g_return_if_fail (tree != NULL); + g_return_if_fail (t != NULL); + g_return_if_fail (distance != NULL); + g_return_if_fail (delta > 0.); + g_return_if_fail (range != NULL); + + gts_triangle_vertices (t, + (GtsVertex **) &p1, + (GtsVertex **) &p2, + (GtsVertex **) &p3); + + gts_vector_init (p1p2, p1, p2); + gts_vector_init (p1p3, p1, p3); + gts_range_init (range); + p = GTS_POINT (gts_object_new (GTS_OBJECT_CLASS (gts_point_class ()))); + + l1 = sqrt (gts_vector_scalar (p1p2, p1p2)); + n1 = l1/delta + 1; + dt1 = 1.0/(gdouble) n1; + t1 = 0.0; + for (i = 0; i <= n1; i++, t1 += dt1) { + gdouble t2 = 1. - t1; + gdouble x = t2*p1p3[0]; + gdouble y = t2*p1p3[1]; + gdouble z = t2*p1p3[2]; + gdouble l2 = sqrt (x*x + y*y + z*z); + guint j, n2 = (guint) (l2/delta + 1); + gdouble dt2 = t2/(gdouble) n2; + + x = t2*p1->x + t1*p2->x; + y = t2*p1->y + t1*p2->y; + z = t2*p1->z + t1*p2->z; + + t2 = 0.0; + for (j = 0; j <= n2; j++, t2 += dt2) { + p->x = x + t2*p1p3[0]; + p->y = y + t2*p1p3[1]; + p->z = z + t2*p1p3[2]; + + gts_range_add_value (range, + gts_bb_tree_point_distance (tree, p, distance, NULL)); + } + } + + gts_object_destroy (GTS_OBJECT (p)); + gts_range_update (range); +} + +/** + * gts_bb_tree_segment_distance: + * @tree: a bounding box tree. + * @s: a #GtsSegment. + * @distance: a #GtsBBoxDistFunc. + * @delta: spatial scale of the sampling to be used. + * @range: a #GtsRange to be filled with the results. + * + * Given a segment @s, points are sampled regularly on its length + * using @delta as increment. The distance from each of these points + * to the closest object of @tree is computed using @distance and the + * gts_bb_tree_point_distance() function. The fields of @range are + * filled with the number of points sampled, the minimum, average and + * maximum value and the standard deviation. + */ +void gts_bb_tree_segment_distance (GNode * tree, + GtsSegment * s, + gdouble (*distance) (GtsPoint *, + gpointer), + gdouble delta, + GtsRange * range) +{ + GtsPoint * p1, * p2, * p; + GtsVector p1p2; + gdouble l, t, dt; + guint i, n; + + g_return_if_fail (tree != NULL); + g_return_if_fail (s != NULL); + g_return_if_fail (distance != NULL); + g_return_if_fail (delta > 0.); + g_return_if_fail (range != NULL); + + p1 = GTS_POINT (s->v1); + p2 = GTS_POINT (s->v2); + + gts_vector_init (p1p2, p1, p2); + gts_range_init (range); + p = GTS_POINT (gts_object_new (GTS_OBJECT_CLASS (gts_point_class()))); + + l = sqrt (gts_vector_scalar (p1p2, p1p2)); + n = (guint) (l/delta + 1); + dt = 1.0/(gdouble) n; + t = 0.0; + for (i = 0; i <= n; i++, t += dt) { + p->x = p1->x + t*p1p2[0]; + p->y = p1->y + t*p1p2[1]; + p->z = p1->z + t*p1p2[2]; + + gts_range_add_value (range, + gts_bb_tree_point_distance (tree, p, distance, NULL)); + } + + gts_object_destroy (GTS_OBJECT (p)); + gts_range_update (range); +} + +static void surface_distance_foreach_triangle (GtsTriangle * t, + gpointer * data) +{ + gdouble * delta = data[1]; + GtsRange * range = data[2]; + gdouble * total_area = data[3], area; + GtsRange range_triangle; + + gts_bb_tree_triangle_distance (data[0], t, data[4], *delta, &range_triangle); + + if (range_triangle.min < range->min) + range->min = range_triangle.min; + if (range_triangle.max > range->max) + range->max = range_triangle.max; + range->n += range_triangle.n; + + area = gts_triangle_area (t); + *total_area += area; + range->sum += area*range_triangle.mean; + range->sum2 += area*range_triangle.mean*range_triangle.mean; +} + +/** + * gts_bb_tree_surface_distance: + * @tree: a bounding box tree. + * @s: a #GtsSurface. + * @distance: a #GtsBBoxDistFunc. + * @delta: a sampling increment defined as the percentage of the diagonal + * of the root bounding box of @tree. + * @range: a #GtsRange to be filled with the results. + * + * Calls gts_bb_tree_triangle_distance() for each face of @s. The + * fields of @range are filled with the minimum, maximum and average + * distance. The average distance is defined as the sum of the average + * distances for each triangle weighthed by their area and divided by + * the total area of the surface. The standard deviation is defined + * accordingly. The @n field of @range is filled with the number of + * sampled points used. + */ +void gts_bb_tree_surface_distance (GNode * tree, + GtsSurface * s, + GtsBBoxDistFunc distance, + gdouble delta, + GtsRange * range) +{ + gpointer data[5]; + gdouble total_area = 0.; + + g_return_if_fail (tree != NULL); + g_return_if_fail (s != NULL); + g_return_if_fail (delta > 0. && delta < 1.); + g_return_if_fail (range != NULL); + + gts_range_init (range); + delta *= sqrt (gts_bbox_diagonal2 (tree->data)); + data[0] = tree; + data[1] = δ + data[2] = range; + data[3] = &total_area; + data[4] = distance; + + gts_surface_foreach_face (s, + (GtsFunc) surface_distance_foreach_triangle, + data); + + if (total_area > 0.) { + if (range->sum2 - range->sum*range->sum/total_area >= 0.) + range->stddev = sqrt ((range->sum2 - range->sum*range->sum/total_area) + /total_area); + else + range->stddev = 0.; + range->mean = range->sum/total_area; + } + else + range->min = range->max = range->mean = range->stddev = 0.; +} + +static void surface_distance_foreach_boundary (GtsEdge * e, + gpointer * data) +{ + gdouble * delta = data[1]; + GtsRange * range = data[2]; + gdouble * total_length = data[3], length; + GtsRange range_edge; + + if (gts_edge_is_boundary (e, NULL)) { + GtsSegment * s = GTS_SEGMENT (e); + + gts_bb_tree_segment_distance (data[0], s, data[4], *delta, &range_edge); + + if (range_edge.min < range->min) + range->min = range_edge.min; + if (range_edge.max > range->max) + range->max = range_edge.max; + range->n += range_edge.n; + + length = gts_point_distance (GTS_POINT (s->v1), GTS_POINT (s->v2)); + *total_length += length; + range->sum += length*range_edge.mean; + range->sum2 += length*range_edge.mean*range_edge.mean; + } +} + +/** + * gts_bb_tree_surface_boundary_distance: + * @tree: a bounding box tree. + * @s: a #GtsSurface. + * @distance: a #GtsBBoxDistFunc. + * @delta: a sampling increment defined as the percentage of the diagonal + * of the root bounding box of @tree. + * @range: a #GtsRange to be filled with the results. + * + * Calls gts_bb_tree_segment_distance() for each edge boundary of @s. + * The fields of @range are filled with the minimum, maximum and + * average distance. The average distance is defined as the sum of the + * average distances for each boundary edge weighthed by their length + * and divided by the total length of the boundaries. The standard + * deviation is defined accordingly. The @n field of @range is filled + * with the number of sampled points used. + */ +void gts_bb_tree_surface_boundary_distance (GNode * tree, + GtsSurface * s, + gdouble (*distance) (GtsPoint *, + gpointer), + gdouble delta, + GtsRange * range) +{ + gpointer data[5]; + gdouble total_length = 0.; + + g_return_if_fail (tree != NULL); + g_return_if_fail (s != NULL); + g_return_if_fail (delta > 0. && delta < 1.); + g_return_if_fail (range != NULL); + + gts_range_init (range); + delta *= sqrt (gts_bbox_diagonal2 (tree->data)); + data[0] = tree; + data[1] = δ + data[2] = range; + data[3] = &total_length; + data[4] = distance; + + gts_surface_foreach_edge (s, + (GtsFunc) surface_distance_foreach_boundary, + data); + + if (total_length > 0.) { + if (range->sum2 - range->sum*range->sum/total_length >= 0.) + range->stddev = sqrt ((range->sum2 - + range->sum*range->sum/total_length) + /total_length); + else + range->stddev = 0.; + range->mean = range->sum/total_length; + } + else + range->min = range->max = range->mean = range->stddev = 0.; +} diff --git a/gts/boolean.c b/gts/boolean.c new file mode 100644 index 0000000000..79f3e0c8b2 --- /dev/null +++ b/gts/boolean.c @@ -0,0 +1,2048 @@ +/* GTS - Library for the manipulation of triangulated surfaces + * Copyright (C) 1999--2002 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 "gts.h" + +/*#define DEBUG*/ +/*#define DEBUG_BOOLEAN*/ +/*#define CHECK_ORIENTED*/ + +#ifdef DEBUG +# include "gts-private.h" +#endif /* DEBUG */ + +static void surface_inter_destroy (GtsObject * object) +{ + GtsSurfaceInter * si = GTS_SURFACE_INTER (object); + + gts_object_destroy (GTS_OBJECT (si->s1)); + gts_object_destroy (GTS_OBJECT (si->s2)); + g_slist_free (si->edges); + + (* GTS_OBJECT_CLASS (gts_surface_inter_class ())->parent_class->destroy) + (object); +} + +static void surface_inter_class_init (GtsObjectClass * klass) +{ + klass->destroy = surface_inter_destroy; +} + +static void surface_inter_init (GtsSurfaceInter * si) +{ + si->s1 = si->s2 = NULL; + si->edges = NULL; +} + +/** + * gts_surface_inter_class: + * + * Returns: the #GtsSurfaceInterClass. + */ +GtsSurfaceInterClass * gts_surface_inter_class (void) +{ + static GtsSurfaceInterClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo surface_inter_info = { + "GtsSurfaceInter", + sizeof (GtsSurfaceInter), + sizeof (GtsSurfaceInterClass), + (GtsObjectClassInitFunc) surface_inter_class_init, + (GtsObjectInitFunc) surface_inter_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (gts_object_class (), &surface_inter_info); + } + + return klass; +} + +/* EdgeInter: Header */ + +typedef struct _EdgeInter EdgeInter; + +struct _EdgeInter { + GtsEdge parent; + + GtsTriangle * t1, * t2; +}; + +#define EDGE_INTER(obj) GTS_OBJECT_CAST (obj,\ + EdgeInter,\ + edge_inter_class ()) +#define IS_EDGE_INTER(obj) (gts_object_is_from_class (obj,\ + edge_inter_class ())) + +static GtsEdgeClass * edge_inter_class (void); +static EdgeInter * edge_inter_new (GtsVertex * v1, GtsVertex * v2, + GtsTriangle * t1, GtsTriangle * t2); + +/* EdgeInter: Object */ + +static GtsEdgeClass * edge_inter_class (void) +{ + static GtsEdgeClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo edge_inter_info = { + "EdgeInter", + sizeof (EdgeInter), + sizeof (GtsEdgeClass), + (GtsObjectClassInitFunc) NULL, + (GtsObjectInitFunc) NULL, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_constraint_class ()), + &edge_inter_info); + } + + return klass; +} + +static EdgeInter * edge_inter_new (GtsVertex * v1, GtsVertex * v2, + GtsTriangle * t1, GtsTriangle * t2) +{ + EdgeInter * object; + + object = EDGE_INTER (gts_edge_new (GTS_EDGE_CLASS (edge_inter_class ()), + v1, v2)); + object->t1 = t1; + object->t2 = t2; + + return object; +} + +#ifdef DEBUG +static void write_surface_graph (GtsSurface * s, FILE * fp) +{ + GSList * l = NULL; + GtsGraph * g; + static void add_to_list (gpointer data, GSList ** l) { + *l = g_slist_prepend (*l, data); + } + + gts_surface_foreach_vertex (s, (GtsFunc) gts_object_reset_reserved, NULL); + gts_surface_foreach_edge (s, (GtsFunc) gts_object_reset_reserved, NULL); + gts_surface_foreach_edge (s, (GtsFunc) add_to_list, &l); + g = gts_segments_graph_new (gts_graph_class (), l); + gts_graph_write_dot (g, fp); + gts_object_destroy (GTS_OBJECT (g)); + g_slist_free (l); +} +#endif /* DEBUG */ + +static GtsPoint * segment_triangle_intersection (GtsSegment * s, + GtsTriangle * t, + GtsPointClass * klass) +{ + GtsPoint * A, * B, * C, * D, * E; + gint ABCE, ABCD, ADCE, ABDE, BCDE; + GtsEdge * AB, * BC, * CA; + gdouble a, b, c; + + g_return_val_if_fail (s != NULL, NULL); + g_return_val_if_fail (t != NULL, NULL); + g_return_val_if_fail (klass != NULL, NULL); + + gts_triangle_vertices_edges (t, NULL, + (GtsVertex **) &A, + (GtsVertex **) &B, + (GtsVertex **) &C, + &AB, &BC, &CA); + D = GTS_POINT (s->v1); + E = GTS_POINT (s->v2); + + ABCE = gts_point_orientation_3d_sos (A, B, C, E); + ABCD = gts_point_orientation_3d_sos (A, B, C, D); + if (ABCE < 0 || ABCD > 0) { + GtsPoint * tmpp; + gint tmp; + + tmpp = E; E = D; D = tmpp; + tmp = ABCE; ABCE = ABCD; ABCD = tmp; + } + if (ABCE < 0 || ABCD > 0) + return NULL; + ADCE = gts_point_orientation_3d_sos (A, D, C, E); + if (ADCE < 0) + return NULL; + ABDE = gts_point_orientation_3d_sos (A, B, D, E); + if (ABDE < 0) + return NULL; + BCDE = gts_point_orientation_3d_sos (B, C, D, E); + if (BCDE < 0) + return NULL; + a = gts_point_orientation_3d (A, B, C, E); + b = gts_point_orientation_3d (A, B, C, D); + if (a != b) { + c = a/(a - b); + return gts_point_new (klass, + E->x + c*(D->x - E->x), + E->y + c*(D->y - E->y), + E->z + c*(D->z - E->z)); + } + /* D and E are contained within ABC */ +#ifdef DEBUG + fprintf (stderr, + "segment: %p:%s triangle: %p:%s intersection\n" + "D and E contained in ABC\n", + s, GTS_NEDGE (s)->name, t, GTS_NFACE (t)->name); +#endif /* DEBUG */ + g_assert (a == 0.); + return gts_point_new (klass, + (E->x + D->x)/2., + (E->y + D->y)/2., + (E->z + D->z)/2.); +} + +static gint triangle_triangle_orientation (GtsPoint * p1, + GtsPoint * p2, GtsPoint * p3, + GtsPoint * p4, GtsPoint * p5, + GtsPoint * p6) +{ + gint o4 = 0, o5 = 0, o6 = 0; + + if (p4 != p1 && p4 != p2 && p4 != p3) + o4 = gts_point_orientation_3d_sos (p1, p2, p3, p4); + if (p5 != p1 && p5 != p2 && p5 != p3) + o5 = gts_point_orientation_3d_sos (p1, p2, p3, p5); + if (o4*o5 < 0) + return 0; + if (p6 != p1 && p6 != p2 && p6 != p3) + o6 = gts_point_orientation_3d_sos (p1, p2, p3, p6); + if (o4*o6 < 0 || o5*o6 < 0) + return 0; + if (o4) return o4; + if (o5) return o5; + g_assert (o6); + return o6; +} + +static gint triangle_point_orientation (GtsTriangle * t1, + GtsTriangle * t2, + gint o1, + GtsPoint * p) +{ + GtsPoint * p1 = GTS_POINT (GTS_SEGMENT (t1->e1)->v1); + GtsPoint * p2 = GTS_POINT (GTS_SEGMENT (t1->e1)->v2); + GtsPoint * p3 = GTS_POINT (gts_triangle_vertex (t1)); + GtsPoint * p4 = GTS_POINT (GTS_SEGMENT (t2->e1)->v1); + GtsPoint * p5 = GTS_POINT (GTS_SEGMENT (t2->e1)->v2); + GtsPoint * p6 = GTS_POINT (gts_triangle_vertex (t2)); + gint o = triangle_triangle_orientation (p1, p2, p3, p4, p5, p6); + + if (o != 0) + return o; + o = triangle_triangle_orientation (p4, p5, p6, p1, p2, p3); + if (o != 0) { + gint o2 = gts_point_orientation_3d_sos (p4, p5, p6, p); + + return - o*o1*o2; + } + return 0; +} + +static void add_edge_inter (GtsEdge * e, + GtsTriangle * t, + GtsVertex * v) +{ + GtsVertex * ev1 = GTS_SEGMENT (e)->v1, * ev2 = GTS_SEGMENT (e)->v2; + GList * i = GTS_OBJECT (e)->reserved; + + GTS_OBJECT (v)->reserved = t; + if (i == NULL) { + GTS_OBJECT (e)->reserved = g_list_prepend (NULL, v); +#ifdef DEBUG + fprintf (stderr, "add_edge_inter: inserting %p (%p,%p)\n", v, e, t); +#endif /* DEBUG */ + } + else { + GtsPoint * p1 = GTS_POINT (GTS_SEGMENT (t->e1)->v1); + GtsPoint * p2 = GTS_POINT (GTS_SEGMENT (t->e1)->v2); + GtsPoint * p3 = GTS_POINT (gts_triangle_vertex (t)); + gint o1, oref = gts_point_orientation_3d_sos (p1, p2, p3, GTS_POINT (ev1)); + + o1 = oref; + while (i) { + gint o2 = triangle_point_orientation (t, GTS_OBJECT (i->data)->reserved, + oref, GTS_POINT (ev1)); + + if (o2 == 0) { +#ifdef DEBUG + g_warning ("add_edge_inter: safe sign evaluation failed\n"); +#endif /* DEBUG */ + o2 = gts_point_orientation_3d_sos (p1, p2, p3, i->data); + } + + if (o1*o2 < 0) + break; + ev1 = i->data; + o1 = o2; + i = i->next; + } + if (i != NULL) { + GList * n = g_list_prepend (NULL, v); + + ev2 = i->data; + n->next = i; + n->prev = i->prev; + i->prev = n; + if (n->prev == NULL) + GTS_OBJECT (e)->reserved = n; + else + n->prev->next = n; + } + else { + g_assert (o1*gts_point_orientation_3d_sos (p1, p2, p3, GTS_POINT (ev2)) + < 0); + GTS_OBJECT (e)->reserved = g_list_append (GTS_OBJECT (e)->reserved, v); + } +#ifdef DEBUG + fprintf (stderr, + "add_edge_inter: inserting %p (%p,%p) between %p and %p\n", + v, e, t, ev1, ev2); + i = GTS_OBJECT (e)->reserved; + while (i) { + fprintf (stderr, " %p", i->data); + i = i->next; + } + fprintf (stderr, "\n"); +#endif /* DEBUG */ + } +} + +static GtsVertex * intersects (GtsEdge * e, + GtsTriangle * t, + GtsSurface * s) +{ + GList * i = GTS_OBJECT (e)->reserved; + GtsVertex * v; + + while (i) { + if (GTS_OBJECT (i->data)->reserved == t) + return i->data; + i = i->next; + } + + v = GTS_VERTEX (segment_triangle_intersection (GTS_SEGMENT (e), t, + GTS_POINT_CLASS (s->vertex_class))); + if (v != NULL) { +#ifdef DEBUG + if (GTS_IS_NVERTEX (v) && GTS_IS_NEDGE (e) && GTS_IS_NFACE (t) && + GTS_NVERTEX (v)->name[0] == '\0') + g_snprintf (GTS_NVERTEX (v)->name, GTS_NAME_LENGTH, "%s|%s", + GTS_NEDGE (e)->name, GTS_NFACE (t)->name); +#endif /* DEBUG */ + if (s->vertex_class->intersection_attributes) + (*s->vertex_class->intersection_attributes) + (v, GTS_OBJECT (e), GTS_OBJECT (t)); + add_edge_inter (e, t, v); + } + return v; +} + +/* see figure misc/orientation.fig */ +static gint intersection_orientation (GtsTriangle * t1, + GtsEdge * e, + GtsTriangle * t2) +{ + GtsVertex * v1, * v2, * v3; + GtsEdge * e2, * e3; + GtsVertex * v4, * v5, * v6; + + gts_triangle_vertices_edges (t1, e, &v1, &v2, &v3, &e, &e2, &e3); + gts_triangle_vertices (t2, &v4, &v5, &v6); + + return gts_point_orientation_3d_sos (GTS_POINT (v4), + GTS_POINT (v5), + GTS_POINT (v6), + GTS_POINT (v2)); +} + +#define UPDATE_ORIENTATION if (o > 0) { vi2 = v; /* e2 = e; */ } else { vi2 = vi1;\ + /* e2 = e1; */\ + vi1 = v;\ + /* e1 = e; */ } + +static void intersect_edges (GtsBBox * bb1, GtsBBox * bb2, + GtsSurfaceInter * si) +{ + GtsSurface * s1 = GTS_OBJECT (si->s1)->reserved; + GtsTriangle * t1 = GTS_TRIANGLE (bb1->bounded); + GtsTriangle * t2 = GTS_TRIANGLE (bb2->bounded); + GtsVertex * v, * vi1 = NULL, * vi2 = NULL; + //GtsEdge * e1 = NULL, * e2 = NULL, * e; + + vi1 = intersects (t2->e1, t1, s1); + //e1 = t2->e1; + v = intersects (t2->e2, t1, s1); + //e = t2->e2; + if (!vi1) { + vi1 = v; + //e1 = e; + } + else if (v) { + gint o = intersection_orientation (t2, t2->e2, t1); + UPDATE_ORIENTATION; + } + if (!vi2) { + v = intersects (t2->e3, t1, s1); + //e = t2->e3; + if (!vi1) { + vi1 = v; + //e1 = e; + } + else if (v) { + gint o = intersection_orientation (t2, t2->e3, t1); + UPDATE_ORIENTATION; + } + } + if (!vi2) { + v = intersects (t1->e1, t2, s1); + //e = t1->e1; + if (!vi1) { + vi1 = v; + //e1 = e; + } + else if (v) { + gint o = - intersection_orientation (t1, t1->e1, t2); + UPDATE_ORIENTATION; + } + } + if (!vi2) { + v = intersects (t1->e2, t2, s1); + //e = t1->e2; + if (!vi1) { + vi1 = v; + //e1 = e; + } + else if (v) { + gint o = - intersection_orientation (t1, t1->e2, t2); + UPDATE_ORIENTATION; + } + } + if (!vi2) { + v = intersects (t1->e3, t2, s1); + //e = t1->e3; + if (!vi1) { + vi1 = v; + //e1 = e; + } + else if (v) { + gint o = - intersection_orientation (t1, t1->e3, t2); + UPDATE_ORIENTATION; + } + } + + g_assert ((!vi1 && !vi2) || (vi1 && vi2)); + if (vi1) { + GtsEdge * e = GTS_EDGE (edge_inter_new (vi1, vi2, t1, t2)); + +#ifdef DEBUG + fprintf (stderr, "creating constraint %p: %p->%p: %p/%p\n", + e, vi1, vi2, t1, t2); +#endif /* DEBUG */ + gts_surface_add_face (si->s1, GTS_FACE (t1)); + gts_surface_add_face (si->s2, GTS_FACE (t2)); + si->edges = g_slist_prepend (si->edges, e); + GTS_OBJECT (t1)->reserved = g_slist_prepend (GTS_OBJECT (t1)->reserved, e); + GTS_OBJECT (t2)->reserved = g_slist_prepend (GTS_OBJECT (t2)->reserved, e); + } +} + +static GtsSurfaceInter * surface_inter_new (GtsSurfaceInterClass * klass, + GtsSurface * s1, + GtsSurface * s2, + GNode * faces_tree1, + GNode * faces_tree2) +{ + GtsSurfaceInter * si; + + si = GTS_SURFACE_INTER (gts_object_new (GTS_OBJECT_CLASS (klass))); + si->s1 = gts_surface_new (gts_surface_class (), + s1->face_class, + s1->edge_class, + s1->vertex_class); + GTS_OBJECT (si->s1)->reserved = s1; + si->s2 = gts_surface_new (gts_surface_class (), + s2->face_class, + s2->edge_class, + s2->vertex_class); + GTS_OBJECT (si->s2)->reserved = s2; + gts_bb_tree_traverse_overlapping (faces_tree1, faces_tree2, + (GtsBBTreeTraverseFunc) intersect_edges, + si); + + return si; +} + +static void free_slist (GtsObject * o) +{ + g_slist_free (o->reserved); + o->reserved = NULL; +} + +static void free_glist (GtsObject * o) +{ + g_list_foreach (o->reserved, (GFunc) gts_object_reset_reserved, NULL); + g_list_free (o->reserved); + o->reserved = NULL; +} + +/** + * gts_surface_intersection: + * @s1: a #GtsSurface. + * @s2: a #GtsSurface. + * @faces_tree1: a bounding box tree (see gts_bb_tree_new()) for + * the faces of @s1. + * @faces_tree2: a bounding box tree for the faces of @s2. + * + * Returns: a list of #GtsEdge defining the curve intersection of the + * two surfaces. + */ +GSList * gts_surface_intersection (GtsSurface * s1, + GtsSurface * s2, + GNode * faces_tree1, + GNode * faces_tree2) +{ + GtsSurfaceInter * si; + GSList * inter; + + g_return_val_if_fail (s1 != NULL, NULL); + g_return_val_if_fail (s2 != NULL, NULL); + g_return_val_if_fail (faces_tree1 != NULL, NULL); + g_return_val_if_fail (faces_tree2 != NULL, NULL); + + si = surface_inter_new (gts_surface_inter_class (), + s1, s2, faces_tree1, faces_tree2); + + gts_surface_foreach_face (si->s1, (GtsFunc) free_slist, NULL); + gts_surface_foreach_face (si->s2, (GtsFunc) free_slist, NULL); + gts_surface_foreach_edge (si->s1, (GtsFunc) free_glist, NULL); + gts_surface_foreach_edge (si->s2, (GtsFunc) free_glist, NULL); + inter = si->edges; + si->edges = NULL; + gts_object_destroy (GTS_OBJECT (si)); + + return inter; +} + +typedef enum { + INTERIOR = 1 << (GTS_USER_FLAG), + RELEVANT = 1 << (GTS_USER_FLAG + 1) +} CurveFlag; + +#define IS_SET(s, f) ((GTS_OBJECT_FLAGS (s) & (f)) != 0) +#define SET(s, f) (GTS_OBJECT_FLAGS (s) |= (f)) +#define UNSET(s, f) (GTS_OBJECT_FLAGS (s) &= ~(f)) +#define NEXT(s) (GTS_OBJECT (s)->reserved) + +#ifdef DEBUG +static void print_segment (GtsSegment * s) +{ + fprintf (stderr, "%p: %s->%s ", s, + GTS_NVERTEX (s->v1)->name, + GTS_NVERTEX (s->v2)->name); + if (NEXT (s)) { + GtsSegment * next = NEXT (s); + + fprintf (stderr, "next %p: %s->%s\n", next, + GTS_NVERTEX (next->v1)->name, + GTS_NVERTEX (next->v2)->name); + } + else + fprintf (stderr, "next NULL\n"); +} + +static void write_nodes (GSList * i, GHashTable * hash, guint * nn, + FILE * fp) +{ + while (i) { + GtsSegment * s = i->data; + + if (!g_hash_table_lookup (hash, s->v1)) { + fprintf (fp, " %u [ label = \"%p\" ];\n", *nn, s->v1); + g_hash_table_insert (hash, s->v1, GUINT_TO_POINTER ((*nn)++)); + } + if (!g_hash_table_lookup (hash, s->v2)) { + fprintf (fp, " %u [ label = \"%p\" ];\n", *nn, s->v2); + g_hash_table_insert (hash, s->v2, GUINT_TO_POINTER ((*nn)++)); + } + i = i->next; + } +} + +static void write_edges (GSList * i, GHashTable * hash, + GtsSurface * surface, + FILE * fp) +{ + while (i) { + GtsSegment * s = i->data; + + fprintf (fp, " %u -> %u [ label = \"%p:%d\" ];\n", + GPOINTER_TO_UINT (g_hash_table_lookup (hash, s->v1)), + GPOINTER_TO_UINT (g_hash_table_lookup (hash, s->v2)), + s, + gts_edge_face_number (GTS_EDGE (s), surface)); + i = i->next; + } +} + +static void write_graph (GSList * boundary, GSList * interior, + GtsSurface * surface, + FILE * fp) +{ + GHashTable * hash = g_hash_table_new (NULL, NULL); + guint nn = 1; + + fprintf (fp, "digraph oriented_curve {\n"); + write_nodes (boundary, hash, &nn, fp); + write_nodes (interior, hash, &nn, fp); + write_edges (boundary, hash, surface, fp); + fprintf (fp, " edge [ color = red ];\n"); + write_edges (interior, hash, surface, fp); + fprintf (fp, "}\n"); + g_hash_table_destroy (hash); +} + +static void write_graph1 (GtsSegment * start, GSList * i, + GtsSurface * surface, + FILE * fp) +{ + GSList * boundary = NULL, * interior = NULL; + GtsSegment * s = start; + + do { + boundary = g_slist_prepend (boundary, s); + s = NEXT (s); + } while (s != start); + while (i) { + if (IS_SET (i->data, INTERIOR)) + interior = g_slist_prepend (interior, i->data); + i = i->next; + } + write_graph (boundary, interior, surface, fp); + g_slist_free (boundary); + g_slist_free (interior); +} + +static void print_loop (GtsSegment * start, FILE * fp) +{ + GtsSegment * s = start; + + do { + fprintf (fp, " %p: %p:%s -> %p:%s\n", + s, + s->v1, GTS_NVERTEX (s->v1)->name, + s->v2, GTS_NVERTEX (s->v2)->name); + s = NEXT (s); + } while (s != start && s != NULL); +} + +static void draw_vector (GtsPoint * p1, GtsPoint * p2, FILE * fp) +{ + gdouble x = p2->x - p1->x; + gdouble y = p2->y - p1->y; + gdouble z = p2->z - p1->z; + + fprintf (fp, "VECT 1 3 0 3 0 %g %g %g %g %g %g %g %g %g\n", + p1->x + x - (x - y/2.)/5., + p1->y + y - (x/2. + y)/5., + p1->z + z - (x/2. + z)/5., + p1->x + x, + p1->y + y, + p1->z + z, + p1->x + x - (x + y/2.)/5., + p1->y + y + (x/2. - y)/5., + p1->z + z + (x/2. - z)/5.); + fprintf (fp, "VECT 1 2 0 2 0 %g %g %g %g %g %g\n", + p1->x, p1->y, p1->z, + p1->x + x, + p1->y + y, + p1->z + z); +} + +static void draw_vector1 (GtsPoint * p1, GtsPoint * p2, GtsPoint * o, + FILE * fp) +{ + gdouble x1 = o->x + 0.9*(p1->x - o->x); + gdouble y1 = o->y + 0.9*(p1->y - o->y); + gdouble z1 = o->z + 0.9*(p1->z - o->z); + gdouble x2 = o->x + 0.9*(p2->x - o->x); + gdouble y2 = o->y + 0.9*(p2->y - o->y); + gdouble z2 = o->z + 0.9*(p2->z - o->z); + gdouble x = x2 - x1; + gdouble y = y2 - y1; + gdouble z = z2 - z1; + + fprintf (fp, "VECT 1 3 0 3 0 %g %g %g %g %g %g %g %g %g\n", + x1 + x - (x - y/2.)/5., + y1 + y - (x/2. + y)/5., + z1 + z - (x/2. + z)/5., + x1 + x, + y1 + y, + z1 + z, + x1 + x - (x + y/2.)/5., + y1 + y + (x/2. - y)/5., + z1 + z + (x/2. - z)/5.); + fprintf (fp, "VECT 1 2 0 2 0 %g %g %g %g %g %g\n", + x1, y1, z1, + x1 + x, + y1 + y, + z1 + z); +} + +static void write_segments (GSList * boundary, GSList * interior, + FILE * fp) +{ + GSList * i = boundary; + + fprintf (fp, "LIST {\n"); + while (i) { + GSList * inext = i->next; + GtsSegment * s = i->data; + GtsSegment * next = inext ? inext->data : boundary->data; + GtsVertex * v1, * v2; + + if (s->v1 != next->v1 && s->v1 != next->v2) { + v1 = s->v1; + v2 = s->v2; + } + else { + v1 = s->v2; + v2 = s->v1; + } + draw_vector (GTS_POINT (v1), GTS_POINT (v2), fp); + i = inext; + } + i = interior; + while (i) { + GtsSegment * s = i->data; + + draw_vector (GTS_POINT (s->v1), GTS_POINT (s->v2), fp); + i = i->next; + } + fprintf (fp, "}\n"); +} + +static void write_loops (GSList * i, FILE * fp) +{ + guint nl = 0; + + while (i) { + GtsSegment * start = i->data, * s; + GtsPoint os; + guint n = 0; + + fprintf (fp, "(geometry \"loop%d\" = LIST {\n", nl++); + + os.x = os.y = os.z = 0.; + s = start; + do { + GtsSegment * next = NEXT (s); + GtsPoint * p; + + if (s->v1 != next->v1 && s->v1 != next->v2) + p = GTS_POINT (s->v1); + else + p = GTS_POINT (s->v2); + os.x += p->x; os.y += p->y; os.z += p->z; n++; + s = next; + } while (s != start); + os.x /= n; os.y /= n; os.z /= n; + + s = start; + do { + GtsSegment * next = NEXT (s); + + if (s->v1 != next->v1 && s->v1 != next->v2) + draw_vector1 (GTS_POINT (s->v1), GTS_POINT (s->v2), &os, fp); + else + draw_vector1 (GTS_POINT (s->v2), GTS_POINT (s->v1), &os, fp); + s = next; + } while (s != start); + + fprintf (fp, "})\n"); + + i = i->next; + } +} + +#define NAME(v) (GTS_IS_NVERTEX (v) ? GTS_NVERTEX (v)->name : "") +#endif /* DEBUG */ + +static GtsSegment * prev_flag (GtsSegment * s, CurveFlag flag) +{ + GSList * i = s->v1->segments; + + while (i) { + if (i->data != s && IS_SET (i->data, flag)) + return i->data; + i = i->next; + } + return NULL; +} + +static GtsSegment * next_flag (GtsSegment * s, CurveFlag flag) +{ + GSList * i = s->v2->segments; + + while (i) { + if (i->data != s && IS_SET (i->data, flag)) + return i->data; + i = i->next; + } + return NULL; +} + +static GtsSegment * next_interior (GtsVertex * v) +{ + GSList * i = v->segments; + + while (i) { + GtsSegment * s = i->data; + + if (s->v1 == v && IS_SET (s, INTERIOR)) + return s; + i = i->next; + } + return NULL; +} + +static GtsSegment * prev_interior (GtsVertex * v) +{ + GSList * i = v->segments; + + while (i) { + GtsSegment * s = i->data; + + if (s->v2 == v && IS_SET (s, INTERIOR)) + return s; + i = i->next; + } + return NULL; +} + +static GtsSegment * reverse (GtsSegment * start, + gboolean interior, + gboolean * isloop) +{ + GtsSegment * s = start, * prev = NULL, * rprev = NULL; + GtsSegment * rstart = NULL, * rstart1 = NULL; + + do { + GtsSegment * rs; + + g_assert (IS_EDGE_INTER (s)); + rs = GTS_SEGMENT (edge_inter_new (s->v2, s->v1, + EDGE_INTER (s)->t1, EDGE_INTER (s)->t2)); + + if (rstart == NULL) + rstart = rs; + else if (rstart1 == NULL) + rstart1 = rs; + if (interior) + SET (rs, INTERIOR); + NEXT (rs) = rprev; + rprev = rs; + prev = s; + s = NEXT (s); + } while (s != NULL && s != start); + if (s == start) { + NEXT (rstart) = rprev; + *isloop = TRUE; + } + else { + NEXT (rstart) = start; + NEXT (prev) = rprev; + *isloop = FALSE; + } + return rstart1; +} + +static GSList * interior_loops (GSList * interior) +{ + GSList * i = interior; + GSList * loops = NULL; + + i = interior; + while (i) { + GtsSegment * s = i->data; + + if (IS_SET (s, RELEVANT)) { + GtsSegment * start = s, * end; + + do { + GtsSegment * next = next_flag (s, INTERIOR); + + UNSET (s, RELEVANT); + end = s; + s = NEXT (s) = next; + } while (s != NULL && s != start); + + if (s == start) + loops = g_slist_prepend (loops, start); + else { + GtsSegment * next, * prev; + gboolean isloop; + + s = prev_flag (start, INTERIOR); + while (s) { + UNSET (s, RELEVANT); + NEXT (s) = start; + start = s; + s = prev_flag (s, INTERIOR); + } + next = next_flag (end, RELEVANT); + prev = prev_flag (start, RELEVANT); + if (prev != NULL) + SET (start->v1, INTERIOR); + if (next != NULL) + SET (end->v2, INTERIOR); + if (next == NULL && prev == NULL) + loops = g_slist_prepend (loops, start); + else + reverse (start, TRUE, &isloop); + } + } + i = i->next; + } + return loops; +} + +#define ORIENTATION(p1,p2,p3,o) (gts_point_orientation_3d (p1, p2, o, p3)) +#define ORIENTATION_SOS(p1,p2,p3,o) (gts_point_orientation_3d_sos (p1, p2, o, p3)) + +#define ORIENTED_VERTICES(s,next,w1,w2) {\ + if ((s)->v1 == (next)->v1 || (s)->v1 == (next)->v2) {\ + w1 = (s)->v2;\ + w2 = (s)->v1;\ + }\ + else {\ + w1 = (s)->v1;\ + w2 = (s)->v2;\ + }\ +} + +#if 0 +static GtsSegment * segment_intersects (GtsPoint * p1, GtsPoint * p2, + GSList * i, + GtsPoint * o) +{ + while (i) { + GtsSegment * s = i->data; + GtsPoint * p3 = GTS_POINT (s->v1); + GtsPoint * p4 = GTS_POINT (s->v2); + + if (p3 != p1 && p3 != p2 && p4 != p1 && p4 != p2) { + gdouble o1 = ORIENTATION (p3, p4, p1, o); + gdouble o2 = ORIENTATION (p3, p4, p2, o); + + if ((o1 < 0. && o2 > 0.) || (o1 > 0. && o2 < 0.)) { + o1 = ORIENTATION (p1, p2, p3, o); + o2 = ORIENTATION (p1, p2, p4, o); + + if ((o1 <= 0. && o2 >= 0.) || (o1 >= 0. && o2 <= 0.)) + return s; + } + } + i = i->next; + } + return NULL; +} +#else +static GtsSegment * segment_intersects (GtsPoint * p1, GtsPoint * p2, + GSList * i, + GtsPoint * o) +{ + while (i) { + GtsSegment * s = i->data; + GtsPoint * p3 = GTS_POINT (s->v1); + GtsPoint * p4 = GTS_POINT (s->v2); + + if (p3 != p1 && p3 != p2 && p4 != p1 && p4 != p2) { + gint o1 = ORIENTATION_SOS (p3, p4, p1, o); + gint o2 = ORIENTATION_SOS (p3, p4, p2, o); + + if (o1*o2 < 0) { + o1 = ORIENTATION_SOS (p1, p2, p3, o); + o2 = ORIENTATION_SOS (p1, p2, p4, o); + + if (o1*o2 < 0) + return s; + } + } + i = i->next; + } + return NULL; +} +#endif + +static gboolean is_inside_wedge (GtsSegment * s1, GtsSegment * s2, + GtsPoint * p, GtsPoint * o) +{ + GtsVertex * v1, * v2, * v3; + + ORIENTED_VERTICES (s1, s2, v1, v2); + v3 = s2->v1 != v2 ? s2->v1 : s2->v2; + + if (ORIENTATION (GTS_POINT (v1), GTS_POINT (v2), + GTS_POINT (v3), o) >= 0.) { + if (ORIENTATION (GTS_POINT (v1), GTS_POINT (v2), p, o) <= 0. || + ORIENTATION (GTS_POINT (v2), GTS_POINT (v3), p, o) <= 0.) + return FALSE; + } + else if (ORIENTATION (GTS_POINT (v1), GTS_POINT (v2), p, o) <= 0. && + ORIENTATION (GTS_POINT (v2), GTS_POINT (v3), p, o) <= 0.) + return FALSE; + return TRUE; +} + +static GtsSegment * connection (GtsPoint * p, + GSList * interior, + GSList * bloops, + GtsPoint * o) +{ + while (bloops) { + GtsSegment * start = bloops->data, * s = start; + + do { + GtsSegment * next = NEXT (s); + GtsVertex * v2 = s->v1 == next->v1 || s->v1 == next->v2 ? s->v1 : s->v2; + + if (is_inside_wedge (s, next, p, o) && + !segment_intersects (p, GTS_POINT (v2), interior, o)) + return s; + s = next; + } while (s != start); + bloops = bloops->next; + } + return NULL; +} + +static gdouble loop_orientation (GtsSegment * start, + GtsPoint * p, GtsPoint * o) +{ + GtsSegment * s = start; + gdouble or = 0.; + + do { + GtsSegment * next = NEXT (s); + GtsVertex * v1, * v2; + + ORIENTED_VERTICES (s, next, v1, v2); + or += ORIENTATION (p, GTS_POINT (v1), GTS_POINT (v2), o); + s = next; + } while (s != start); + +#ifdef DEBUG + fprintf (stderr, "loop orientation: %g\n", or); +#endif /* DEBUG */ + + return or; +} + +static void connect_interior_loop (GtsSegment * start, + GSList ** interior, + GSList ** bloops, + GtsSurface * surface, + GtsPoint * o) +{ + GtsSegment * s = start, * c = NULL, * next, * s1, * rs1, * rs; + GtsVertex * v, * cv; + gboolean isloop; + + do { + if (!(c = connection (GTS_POINT (s->v2), *interior, *bloops, o))) + s = NEXT (s); + } while (s != start && !c); + g_assert (c); + next = NEXT (c); + v = c->v1 == next->v1 || c->v1 == next->v2 ? c->v1 : c->v2; + cv = s->v2; +#ifdef DEBUG + fprintf (stderr, "connecting %p:%s with %p:%s\n", + cv, NAME (cv), v, NAME (v)); + fprintf (stderr, " c: %p: %p:%s %p:%s\n", c, + c->v1, NAME (c->v1), + c->v2, NAME (c->v2)); + fprintf (stderr, " next: %p: %p:%s %p:%s\n", next, + next->v1, NAME (next->v1), + next->v2, NAME (next->v2)); +#endif /* DEBUG */ + rs = reverse (s, FALSE, &isloop); + if (isloop) { + if (loop_orientation (rs, GTS_POINT (v), o) < 0.) { + GtsSegment * tmp = s; + s = rs; + rs = tmp; + } + *bloops = g_slist_prepend (*bloops, rs); + } + s1 = GTS_SEGMENT (gts_edge_new (surface->edge_class, v, cv)); + rs1 = GTS_SEGMENT (gts_edge_new (surface->edge_class, cv, v)); + NEXT (c) = s1; + NEXT (rs1) = next; + *interior = g_slist_prepend (*interior, s1); + NEXT (s1) = NEXT (s); + NEXT (s) = rs1; +} + +static GSList * boundary_loops (GSList * boundary) +{ + GSList * i = boundary; + GtsSegment * start = i->data; + GSList * loops = NULL; + + while (i) { + GtsSegment * s = i->data; + GSList * inext = i->next; + GtsSegment * next = inext ? inext->data : start; + GtsVertex * v = s->v1 == next->v1 || s->v1 == next->v2 ? s->v1 : s->v2; + + if (IS_SET (v, INTERIOR)) { + GtsSegment * intprev = prev_interior (v); + + NEXT (intprev) = next; + NEXT (s) = next_interior (v); + UNSET (v, INTERIOR); + } + else + NEXT (s) = next; + i = inext; + } + + i = boundary; + while (i) { + start = i->data; + + if (IS_SET (start, RELEVANT)) { + GtsSegment * s = start; + + do { + UNSET (s, RELEVANT); + UNSET (s, INTERIOR); + s = NEXT (s); + } while (s != start); + loops = g_slist_prepend (loops, start); + } + i = i->next; + } + + return loops; +} + +typedef struct _Ear Ear; + +struct _Ear { + GtsVertex * v1, * v2, * v3; + GtsSegment * s1, * s2, * s3; +}; + +static gboolean point_in_wedge (GtsPoint * p1, GtsPoint * p2, GtsPoint * p3, + GtsPoint * p, gboolean closed, GtsPoint * o) +{ + gdouble o1; + + if (p == p2 || p == p3) + return FALSE; + o1 = ORIENTATION (p1, p2, p, o); + if ((closed && o1 < 0.) || (!closed && o1 <= 0.)) return FALSE; + o1 = ORIENTATION (p3, p1, p, o); + if ((closed && o1 < 0.) || (!closed && o1 <= 0.)) return FALSE; + return TRUE; +} + +#if 0 +static gboolean segment_intersects1 (GtsPoint * p1, GtsPoint * p2, + GtsPoint * p3, GtsPoint * p4, + gboolean closed, GtsPoint * o) +{ + gdouble o1 = ORIENTATION (p3, p4, p1, o); + gdouble o2 = ORIENTATION (p3, p4, p2, o); + gdouble o3, o4; + + if ((closed && ((o1 > 0. && o2 > 0.) || (o1 < 0. && o2 < 0.))) || + (!closed && ((o1 >= 0. && o2 >= 0.) || (o1 <= 0. && o2 <= 0.)))) + return FALSE; + o3 = ORIENTATION (p1, p2, p3, o); + o4 = ORIENTATION (p1, p2, p4, o); + if ((o3 > 0. && o4 > 0.) || (o3 < 0. && o4 < 0.)) + return FALSE; + if (closed) return TRUE; + if ((o3 == 0. && o4 > 0.) || (o4 == 0. && o3 > 0.)) + return TRUE; + return FALSE; +} +#else +static gboolean segment_intersects1 (GtsPoint * p1, GtsPoint * p2, + GtsPoint * p3, GtsPoint * p4, + gboolean closed, GtsPoint * o) +{ + gint o1, o2; + + o1 = ORIENTATION_SOS (p3, p4, p1, o); + o2 = ORIENTATION_SOS (p3, p4, p2, o); + if (o1*o2 > 0) + return FALSE; + o1 = ORIENTATION_SOS (p1, p2, p3, o); + o2 = ORIENTATION_SOS (p1, p2, p4, o); + if (o1*o2 > 0) + return FALSE; + return TRUE; +} +#endif + +static GtsSegment * triangle_intersects_segments (GtsPoint * p1, + GtsPoint * p2, + GtsPoint * p3, + gboolean closed, + GtsSegment * start, + GtsPoint * o) +{ + GtsSegment * s = start; + + do { + GtsPoint * p4 = GTS_POINT (s->v1); + GtsPoint * p5 = GTS_POINT (s->v2); + + if (p4 == p1) { + if (point_in_wedge (p1, p2, p3, p5, closed, o)) + return s; + } + else if (p4 == p2) { + if (point_in_wedge (p2, p3, p1, p5, closed, o)) + return s; + } + else if (p4 == p3) { + if (point_in_wedge (p3, p1, p2, p5, closed, o)) + return s; + } + else if (p5 == p1) { + if (point_in_wedge (p1, p2, p3, p4, closed, o)) + return s; + } + else if (p5 == p2) { + if (point_in_wedge (p2, p3, p1, p4, closed, o)) + return s; + } + else if (p5 == p3) { + if (point_in_wedge (p3, p1, p2, p4, closed, o)) + return s; + } + else if (segment_intersects1 (p1, p2, p4, p5, closed, o) || + segment_intersects1 (p2, p3, p4, p5, closed, o) || + segment_intersects1 (p3, p1, p4, p5, closed, o)) + return s; + s = NEXT (s); + } while (s != start); + return NULL; +} + +static gboolean new_ear (GtsSegment * s, + Ear * e, + GtsSegment * start, + guint sloppy, + GtsPoint * o) +{ + gdouble or; + + e->s1 = s; + e->s2 = NEXT (s); + + g_return_val_if_fail (e->s2, FALSE); + g_return_val_if_fail (e->s2 != e->s1, FALSE); + + ORIENTED_VERTICES (e->s1, e->s2, e->v1, e->v2); + e->v3 = e->s2->v1 != e->v2 ? e->s2->v1 : e->s2->v2; + if (e->v3 == e->v1) + return FALSE; + e->s3 = NEXT (e->s2); + if (gts_segment_connect (e->s3, e->v1, e->v3)) { + if (NEXT (e->s3) != e->s1) + return FALSE; + } + else if (gts_vertices_are_connected (e->v1, e->v3)) + return FALSE; + else + e->s3 = NULL; + or = ORIENTATION (GTS_POINT (e->v1), GTS_POINT (e->v2), GTS_POINT (e->v3),o); + switch (sloppy) { + case 0: + if (or <= 0. || + triangle_intersects_segments (GTS_POINT (e->v1), GTS_POINT (e->v2), + GTS_POINT (e->v3), TRUE, start, o)) + return FALSE; + break; + case 1: + if (or < 0. || + (or > 0. && + triangle_intersects_segments (GTS_POINT (e->v1), GTS_POINT (e->v2), + GTS_POINT (e->v3), FALSE, start, o))) + return FALSE; + break; + case 2: + if ((or > 0. && + triangle_intersects_segments (GTS_POINT (e->v1), GTS_POINT (e->v2), + GTS_POINT (e->v3), FALSE, start, o)) || + (or < 0. && + triangle_intersects_segments (GTS_POINT (e->v2), GTS_POINT (e->v1), + GTS_POINT (e->v3), FALSE, start, o))) + return FALSE; + break; + case 3: + if (or < 0.) + return FALSE; + break; + } +#ifdef DEBUG + if (or <= 0.) + fprintf (stderr, "or: %g\n", or); +#endif /* DEBUG */ + g_assert (or > -1e-6); + return TRUE; +} + +static void triangulate_loop (GtsSegment * start, + GtsSurface * surface, + GtsPoint * o) +{ + GtsSegment * prev = start, * s; + guint sloppy = 0; +#ifdef DEBUG + guint nt = 0; +#endif /* DEBUG */ + + s = NEXT (start); + while (NEXT (s) != s) { + GtsSegment * next = NEXT (s); + Ear e; + +#ifdef DEBUG + fprintf (stderr, "prev: %p s: %p next: %p\n", prev, s, next); +#endif /* DEBUG */ + + if (!new_ear (s, &e, start, sloppy, o)) { + if (s == start) { + sloppy++; +#ifdef DEBUG + fprintf (stderr, "sloppy: %u\n", sloppy); +#endif /* DEBUG */ + } + prev = s; + s = next; + } + else { + GtsFace * f; + + if (!GTS_IS_EDGE (e.s3)) + e.s3 = GTS_SEGMENT (gts_edge_new (surface->edge_class, e.v1, e.v3)); + f = gts_face_new (surface->face_class, + GTS_EDGE (e.s1), GTS_EDGE (e.s2), GTS_EDGE (e.s3)); + gts_surface_add_face (surface, f); + UNSET (e.s1, RELEVANT); + UNSET (e.s1, INTERIOR); + UNSET (e.s2, RELEVANT); + UNSET (e.s2, INTERIOR); + NEXT (prev) = e.s3; + NEXT (e.s3) = NEXT (e.s2); + NEXT (e.s1) = NEXT (e.s2) = NULL; + start = prev; + s = NEXT (prev); + sloppy = 0; +#ifdef DEBUG + { + gchar name[80]; + FILE * fp; + + fprintf (stderr, " t.%u: (%p:%s,%p:%s,%p:%s)\n", + nt, + e.v1, NAME (e.v1), + e.v2, NAME (e.v2), + e.v3, NAME (e.v3)); + sprintf (name, "/tmp/t.%u", nt++); + fp = fopen (name, "wt"); + // gts_surface_write (surface, fp); + gts_write_triangle (GTS_TRIANGLE (f), NULL, fp); + // write_graph1 (start, interior, surface, fp); + fclose (fp); + print_loop (start, stderr); + } +#endif /* DEBUG */ + } + } + UNSET (s, RELEVANT); + UNSET (s, INTERIOR); + NEXT (s) = NULL; +} + +#ifdef CHECK_ORIENTED +static void check_object (GtsObject * o) +{ + g_assert (o->reserved == NULL); + g_assert (o->flags == 0); +} + +static void check_boundary (GtsEdge * e, GtsSurface * s) +{ + check_object (GTS_OBJECT (e)); + check_object (GTS_OBJECT (GTS_SEGMENT (e)->v1)); + check_object (GTS_OBJECT (GTS_SEGMENT (e)->v2)); + g_assert (gts_edge_face_number (e, s) == 1); +} + +static void check_interior (GtsEdge * e, GtsSurface * s) +{ + guint n; + check_object (GTS_OBJECT (e)); + check_object (GTS_OBJECT (GTS_SEGMENT (e)->v1)); + check_object (GTS_OBJECT (GTS_SEGMENT (e)->v2)); + + n = gts_edge_face_number (e, s); +#ifdef DEBUG + if (n != 2) + gts_surface_print_stats (s, stderr); +#endif /* DEBUG */ + g_assert (n == 2); +} + +static void check_boundary_interior_triangulation (GSList * boundary, + GSList * interior, + GtsSurface * surface) +{ + g_slist_foreach (boundary, (GFunc) check_boundary, surface); + g_slist_foreach (interior, (GFunc) check_interior, surface); +} +#endif /*ifdef CHECK_ORIENTED */ + +static void merge_duplicate (GtsEdge * e) +{ + GtsEdge * dup = gts_edge_is_duplicate (e); + + g_assert (dup); + gts_edge_replace (dup, e); + gts_object_destroy (GTS_OBJECT (dup)); +} + +static void triangulate_boundary_interior (GSList * boundary, + GSList * interior, + GtsSurface * s, + GtsPoint * o) +{ + GSList * iloops, * bloops, * i; + + i = boundary; + while (i) { + SET (i->data, RELEVANT); + i = i->next; + } + i = interior; + while (i) { + SET (i->data, RELEVANT); + SET (i->data, INTERIOR); + i = i->next; + } + + iloops = interior_loops (interior); + bloops = boundary_loops (boundary); + + i = iloops; + while (i) { +#ifdef DEBUG + fprintf (stderr, "--- interior loop ---\n"); + print_loop (i->data, stderr); +#endif /* DEBUG */ + connect_interior_loop (i->data, &interior, &bloops, s, o); + i = i->next; + } + +#ifdef DEBUG + { + FILE * fp = fopen ("/tmp/bloops", "w"); + write_loops (bloops, fp); + fclose (fp); + } +#endif /* DEBUG */ + + i = bloops; + while (i) { +#ifdef DEBUG + fprintf (stderr, "--- boundary loop ---\n"); + print_loop (i->data, stderr); +#endif /* DEBUG */ + triangulate_loop (i->data, s, o); + i = i->next; + } + + g_slist_foreach (interior, (GFunc) merge_duplicate, NULL); + g_slist_free (iloops); + g_slist_free (bloops); + +#ifdef CHECK_ORIENTED + check_boundary_interior_triangulation (boundary, interior, s); +#endif /* CHECK_ORIENTED */ +} + +static void create_edges (GtsSegment * s, GtsSurface * surface) +{ + if (GTS_OBJECT (s)->reserved) { + GList * i = GTS_OBJECT (s)->reserved; + GtsVertex * v1 = i->data; + + GTS_OBJECT (s)->reserved = g_list_prepend (i, + gts_edge_new (surface->edge_class, s->v1, v1)); + while (i) { + GList * next = i->next; + GtsVertex * v2 = next ? next->data : s->v2; + + GTS_OBJECT (i->data)->reserved = NULL; + i->data = gts_edge_new (surface->edge_class, v1, v2); + v1 = v2; + i = next; + } + } +} + +static void add_boundary (GtsSegment * s, GtsSegment * next, + GSList ** boundary) +{ + if (GTS_OBJECT (s)->reserved == NULL) + *boundary = g_slist_prepend (*boundary, s); + else { + if (s->v2 == next->v2 || s->v2 == next->v1) { + GList * i = g_list_last (GTS_OBJECT (s)->reserved); + + while (i) { + *boundary = g_slist_prepend (*boundary, i->data); + i = i->prev; + } + } + else { + GList * i = GTS_OBJECT (s)->reserved; + + while (i) { + *boundary = g_slist_prepend (*boundary, i->data); + i = i->next; + } + } + } +} + +static void triangulate_face (GtsTriangle * t, GtsSurface * surface) +{ + GSList * interior = GTS_OBJECT (t)->reserved; + GSList * boundary = NULL; + GtsSurface * s = gts_surface_new (gts_surface_class (), + surface->face_class, + surface->edge_class, + surface->vertex_class); + gdouble x, y, z; + GtsPoint * p = GTS_POINT (GTS_SEGMENT (t->e1)->v1); + GtsPoint * o; + + GTS_OBJECT (t)->reserved = NULL; + gts_triangle_normal (t, &x, &y, &z); + g_assert (x != 0. || y != 0. || z != 0.); + o = gts_point_new (gts_point_class (), p->x + x, p->y + y, p->z + z); + add_boundary (GTS_SEGMENT (t->e3), GTS_SEGMENT (t->e1), &boundary); + add_boundary (GTS_SEGMENT (t->e2), GTS_SEGMENT (t->e3), &boundary); + add_boundary (GTS_SEGMENT (t->e1), GTS_SEGMENT (t->e2), &boundary); +#ifdef DEBUG + { + static guint nt = 0; + char name[80]; + FILE * fp; + + fprintf (stderr, "%u: triangulating %p\n", nt, t); +if (nt == 28) + fprintf (stderr, "tintin!!!!\n"); + sprintf (name, "/tmp/oc.%u", nt++); + fp = fopen (name, "wt"); + // write_graph (boundary, interior, s, fp); + write_segments (boundary, interior, fp); + fclose (fp); + } +#endif /* DEBUG */ + triangulate_boundary_interior (boundary, interior, s, o); + g_slist_free (interior); + g_slist_free (boundary); + if (GTS_OBJECT (t)->klass->attributes) + gts_surface_foreach_face (s, (GtsFunc) gts_object_attributes, t); + gts_surface_merge (surface, s); + gts_object_destroy (GTS_OBJECT (s)); + gts_object_destroy (GTS_OBJECT (o)); +} + +static void free_edge_list (GtsObject * o) +{ + g_list_free (o->reserved); + o->reserved = NULL; +} + +/** + * gts_surface_inter_new: + * @klass: a #GtsSurfaceInterClass. + * @s1: a #GtsSurface. + * @s2: a #GtsSurface. + * @faces_tree1: a bounding box tree (see gts_bb_tree_new()) for + * the faces of @s1. + * @faces_tree2: a bounding box tree for the faces of @s2. + * @is_open1: whether @s1 is an "open" surface. + * @is_open2: whether @s2 is an "open" surface. + * + * When triangulating the cut faces, the new faces inherit the + * attributes of these original faces through their attributes() + * method. + * + * Returns: a new #GtsSurfaceInter describing the intersection of @s1 + * and @s2. + */ +GtsSurfaceInter * gts_surface_inter_new (GtsSurfaceInterClass * klass, + GtsSurface * s1, + GtsSurface * s2, + GNode * faces_tree1, + GNode * faces_tree2, + gboolean is_open1, + gboolean is_open2) +{ + GtsSurfaceInter * si; + GtsSurface * s; + + g_return_val_if_fail (klass != NULL, NULL); + g_return_val_if_fail (s1 != NULL, NULL); + g_return_val_if_fail (s2 != NULL, NULL); + g_return_val_if_fail (faces_tree1 != NULL, NULL); + g_return_val_if_fail (faces_tree2 != NULL, NULL); + + si = surface_inter_new (klass, s1, s2, faces_tree1, faces_tree2); + + gts_surface_foreach_edge (si->s1, (GtsFunc) create_edges, si->s1); + gts_surface_foreach_edge (si->s2, (GtsFunc) create_edges, si->s2); + +#ifdef DEBUG + fprintf (stderr, "====== triangulating s1 ======\n"); +#endif /* DEBUG */ + s = gts_surface_new (gts_surface_class (), + s1->face_class, + s1->edge_class, + s1->vertex_class); + gts_surface_foreach_face (si->s1, (GtsFunc) triangulate_face, s); + gts_surface_foreach_edge (si->s1, (GtsFunc) free_edge_list, NULL); + gts_object_destroy (GTS_OBJECT (si->s1)); + si->s1 = s; + GTS_OBJECT (si->s1)->reserved = s1; + +#ifdef DEBUG + fprintf (stderr, "====== triangulating s2 ======\n"); +#endif /* DEBUG */ + s = gts_surface_new (gts_surface_class (), + s2->face_class, + s2->edge_class, + s2->vertex_class); + gts_surface_foreach_face (si->s2, (GtsFunc) triangulate_face, s); + gts_surface_foreach_edge (si->s2, (GtsFunc) free_edge_list, NULL); + gts_object_destroy (GTS_OBJECT (si->s2)); + si->s2 = s; + GTS_OBJECT (si->s2)->reserved = s2; + + return si; +} + +static void check_surface_edge (GtsEdge * e, gpointer * data) +{ + gboolean * ok = data[0]; + GtsSurface * s = data[1]; + GtsSurface * bs = GTS_OBJECT (s)->reserved; + guint nf = gts_edge_face_number (e, s); + + if (nf < 1 || nf > 2) { + *ok = FALSE; + g_return_if_fail (nf >= 1 && nf <= 2); + } + if (nf == 1 && gts_edge_face_number (e, bs) == 0) { + *ok = FALSE; + g_return_if_fail (gts_edge_face_number (e, bs) > 0); + } +} + +static void mark_edge (GtsObject * o, gpointer data) +{ + o->reserved = data; +} + +static gint triangle_orientation (GtsTriangle * t, GtsEdge * e) +{ + GtsSegment * s = GTS_SEGMENT (t->e1 == e ? t->e2 + : + t->e2 == e ? t->e3 + : + t->e1); + GtsVertex * v2 = GTS_SEGMENT (e)->v2; + + if (s->v1 == v2 || s->v2 == v2) + return 1; + return -1; +} + +static gboolean check_orientation (GtsEdge * e, GtsSurface * s) +{ + GtsTriangle * t1 = NULL, * t2 = NULL; + GSList * i = e->triangles; + gint o1 = 0, o2 = 0; + + while (i) { + if (GTS_IS_FACE (i->data) && + gts_face_has_parent_surface (i->data, s)) { + if (t1 == NULL) { + t1 = i->data; + o1 = triangle_orientation (t1, e); + } + else if (t2 == NULL) { + t2 = i->data; + o2 = triangle_orientation (t2, e); + g_return_val_if_fail (o1*o2 < 0, FALSE); + } + else + g_assert_not_reached (); + } + i = i->next; + } + g_return_val_if_fail (t1 && t2, FALSE); + return TRUE; +} + +static void check_edge (GtsSegment * s, gpointer * data) +{ + gboolean * ok = data[0]; + GtsSurfaceInter * si = data[1]; + gboolean * closed = data[2]; + GSList * j; + guint nn = 0; + + j = s->v1->segments; + while (j && *ok) { + GtsSegment * s1 = j->data; + + if (s1 != s && GTS_OBJECT (s1)->reserved == si) { + if (s1->v2 != s->v1) + *ok = FALSE; + nn++; + } + j = j->next; + } + j = s->v2->segments; + while (j && *ok) { + GtsSegment * s1 = j->data; + + if (s1 != s && GTS_OBJECT (s1)->reserved == si) { + if (s1->v1 != s->v2) + *ok = FALSE; + nn++; + } + j = j->next; + } + if (nn != 2) + *closed = FALSE; + + if (!check_orientation (GTS_EDGE (s), si->s1)) + *ok = FALSE; + if (!check_orientation (GTS_EDGE (s), si->s2)) + *ok = FALSE; +} + +/** + * gts_surface_inter_check: + * @si: a #GtsSurfaceInter. + * @closed: is set to %TRUE if @si->edges is a closed curve, %FALSE + * otherwise. + * + * Returns: %TRUE if the curve described by @si is an orientable + * manifold, %FALSE otherwise. + */ +gboolean gts_surface_inter_check (GtsSurfaceInter * si, + gboolean * closed) +{ + gboolean ok = TRUE; + gpointer data[3]; + + g_return_val_if_fail (si != NULL, FALSE); + g_return_val_if_fail (closed != NULL, FALSE); + + *closed = si->edges ? TRUE : FALSE; + + /* mark edges as used by si */ + g_slist_foreach (si->edges, (GFunc) mark_edge, si); + + data[0] = &ok; + data[1] = si; + data[2] = closed; + g_slist_foreach (si->edges, (GFunc) check_edge, data); + g_slist_foreach (si->edges, (GFunc) gts_object_reset_reserved, NULL); + + /* check connectivity of the faces of @si */ + if (*closed) { + gpointer data[2]; + + data[0] = &ok; + data[1] = si->s1; + gts_surface_foreach_edge (si->s1, (GtsFunc) check_surface_edge, data); + data[1] = si->s2; + gts_surface_foreach_edge (si->s2, (GtsFunc) check_surface_edge, data); + } + + return ok; +} + +/* Given @e and @f returns a #GtsFace compatible with @f and belonging to + @s1 or @s2 */ +static GtsFace * next_compatible_face (GtsEdge * e, + GtsFace * f, + GtsSurface * s1, + GtsSurface * s2) +{ + GSList * i = e->triangles; + GtsFace * f2 = NULL, * f3 = NULL; + + while (i) { + GtsFace * f1 = i->data; + + if (f1 != f && GTS_IS_FACE (f1)) { + if (gts_face_has_parent_surface (f1, s1)) + return f1; + if (gts_face_has_parent_surface (f1, s2)) { + if (f2 == NULL) f2 = f1; + else if (f3 == NULL) f3 = f1; + else g_assert_not_reached (); /* s2 is a non-manifold surface */ + } + } + i = i->next; + } + if (f3 == NULL) { + if (gts_edge_is_boundary (e, s2)) + return NULL; + return f2; + } + g_assert (gts_face_has_parent_surface (f, s1)); + if (gts_triangles_are_compatible (GTS_TRIANGLE (f), GTS_TRIANGLE (f2), e)) + return f2; + return f3; +} + +static void walk_faces (GtsEdge * e, GtsFace * f, + GtsSurface * s1, + GtsSurface * s2, + GtsSurface * s) +{ + GtsFifo * faces = gts_fifo_new (); + GtsFifo * edges = gts_fifo_new (); + + gts_fifo_push (faces, f); + gts_fifo_push (edges, e); + while ((f = gts_fifo_pop (faces)) && (e = gts_fifo_pop (edges))) { + if (!GTS_OBJECT (f)->reserved) { + GtsTriangle * t = GTS_TRIANGLE (f); + GtsFace * f1; + + gts_surface_add_face (s, f); + GTS_OBJECT (f)->reserved = s; + if (t->e1 != e && !GTS_OBJECT (t->e1)->reserved && + (f1 = next_compatible_face (t->e1, f, s1, s2))) { + gts_fifo_push (faces, f1); + gts_fifo_push (edges, t->e1); + } + if (t->e2 != e && !GTS_OBJECT (t->e2)->reserved && + (f1 = next_compatible_face (t->e2, f, s1, s2))) { + gts_fifo_push (faces, f1); + gts_fifo_push (edges, t->e2); + } + if (t->e3 != e && !GTS_OBJECT (t->e3)->reserved && + (f1 = next_compatible_face (t->e3, f, s1, s2))) { + gts_fifo_push (faces, f1); + gts_fifo_push (edges, t->e3); + } + } + } + gts_fifo_destroy (faces); + gts_fifo_destroy (edges); +} + +/** + * gts_surface_inter_boolean: + * @si: a #GtsSurfaceInter. + * @surface: a #GtsSurface. + * @op: a #GtsBooleanOperation. + * + * Adds to @surface the part of the surface described by @si and @op. + */ +void gts_surface_inter_boolean (GtsSurfaceInter * si, + GtsSurface * surface, + GtsBooleanOperation op) +{ + GtsSurface * s = NULL; + gint orient = 1; + GSList * i; + + g_return_if_fail (si != NULL); + g_return_if_fail (surface != NULL); + + switch (op) { + case GTS_1_OUT_2: s = si->s1; orient = 1; break; + case GTS_1_IN_2: s = si->s1; orient = -1; break; + case GTS_2_OUT_1: s = si->s2; orient = -1; break; + case GTS_2_IN_1: s = si->s2; orient = 1; break; + default: g_assert_not_reached (); + } + + /* mark edges as belonging to intersection */ + g_slist_foreach (si->edges, (GFunc) mark_edge, si); + + i = si->edges; + while (i) { + GtsEdge * e = i->data; + GSList * j = e->triangles; + + while (j) { + if (gts_face_has_parent_surface (j->data, s) && + orient*triangle_orientation (j->data, e) > 0) { +#ifdef DEBUG_BOOLEAN + GtsFace * boundary = gts_edge_is_boundary (e, surface); + + g_assert (!boundary || boundary == j->data); +#endif /* DEBUG_BOOLEAN */ + walk_faces (e, j->data, s, GTS_OBJECT (s)->reserved, surface); + break; + } + j = j->next; + } + i = i->next; + } + g_slist_foreach (si->edges, (GFunc) gts_object_reset_reserved, NULL); + gts_surface_foreach_face (surface, + (GtsFunc) gts_object_reset_reserved, NULL); +} + +static void self_intersecting (GtsBBox * bb1, GtsBBox * bb2, + gpointer * d) +{ + GtsTriangle * t1 = bb1->bounded; + GtsTriangle * t2 = bb2->bounded; + + if (t1 != t2) { + GtsSegment * s1 = GTS_SEGMENT (t1->e1); + GtsSegment * s2 = GTS_SEGMENT (t1->e2); + GtsSegment * s3 = GTS_SEGMENT (t1->e3); + GtsSegment * s4 = GTS_SEGMENT (t2->e1); + GtsSegment * s5 = GTS_SEGMENT (t2->e2); + GtsSegment * s6 = GTS_SEGMENT (t2->e3); + GtsPoint * pi; + + if ((!gts_segments_touch (s4, s1) && + !gts_segments_touch (s4, s2) && + !gts_segments_touch (s4, s3) && + (pi = segment_triangle_intersection (s4, t1, gts_point_class ())) + != NULL) || + (!gts_segments_touch (s5, s1) && + !gts_segments_touch (s5, s2) && + !gts_segments_touch (s5, s3) && + (pi = segment_triangle_intersection (s5, t1, gts_point_class ())) + != NULL) || + (!gts_segments_touch (s6, s1) && + !gts_segments_touch (s6, s2) && + !gts_segments_touch (s6, s3) && + (pi = segment_triangle_intersection (s6, t1, gts_point_class ())) + != NULL)) { + GtsBBTreeTraverseFunc func = d[0]; + gpointer data = d[1]; + gboolean * self_inter = d[2]; + + gts_object_destroy (GTS_OBJECT (pi)); + *self_inter = TRUE; + (* func) (bb1, bb2, data); + } + } +} + +/** + * gts_surface_foreach_intersecting_face: + * @s: a #GtsSurface. + * @func: a #GtsBBTreeTraverseFunc. + * @data: user data to pass to @func. + * + * Calls @func for each intersecting pair of faces of @s. + * + * Returns: %TRUE if @func was called at least once, %FALSE otherwise. + */ +gboolean gts_surface_foreach_intersecting_face (GtsSurface * s, + GtsBBTreeTraverseFunc func, + gpointer data) +{ + GNode * tree; + gpointer d[3]; + gboolean self_inter = FALSE; + + g_return_val_if_fail (s != NULL, FALSE); + g_return_val_if_fail (func != NULL, FALSE); + + tree = gts_bb_tree_surface (s); + d[0] = func; + d[1] = data; + d[2] = &self_inter; + gts_bb_tree_traverse_overlapping (tree, tree, + (GtsBBTreeTraverseFunc) self_intersecting, + d); + gts_bb_tree_destroy (tree, TRUE); + + return self_inter; +} + +static void add_intersecting (GtsBBox * bb1, GtsBBox * bb2, + GtsSurface * intersected) +{ + gts_surface_add_face (intersected, bb1->bounded); + gts_surface_add_face (intersected, bb2->bounded); +} + +/** + * gts_surface_is_self_intersecting: + * @s: a #GtsSurface. + * + * Returns: a new #GtsSurface containing the faces of @s which are + * self-intersecting or %NULL if no faces of @s are self-intersecting. + */ +GtsSurface * gts_surface_is_self_intersecting (GtsSurface * s) +{ + GtsSurface * intersected; + + g_return_val_if_fail (s != NULL, NULL); + + intersected = gts_surface_new (GTS_SURFACE_CLASS (GTS_OBJECT (s)->klass), + s->face_class, + s->edge_class, + s->vertex_class); + if (!gts_surface_foreach_intersecting_face (s, + (GtsBBTreeTraverseFunc) add_intersecting, intersected)) { + gts_object_destroy (GTS_OBJECT (intersected)); + intersected = NULL; + } + return intersected; +} diff --git a/gts/cdt.c b/gts/cdt.c new file mode 100644 index 0000000000..61c4eb56b6 --- /dev/null +++ b/gts/cdt.c @@ -0,0 +1,1175 @@ +/* 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. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + + +#include +#include "gts.h" + +#ifdef USE_SURFACE_BTREE + +static gint find_closest (GtsTriangle * t, gpointer value, gpointer * data) +{ + guint * ns = data[2]; + guint * n = data[3]; + + if (*n >= *ns) + return TRUE; + else { + gdouble * dmin = data[0]; + gpointer * closest = data[1]; + GtsPoint * p = data[4]; + + if (gts_triangle_orientation (t) > 0.) { + GtsPoint * p1 = GTS_POINT (GTS_SEGMENT (t->e1)->v1); + gdouble d = (p->x - p1->x)*(p->x - p1->x) + (p->y - p1->y)*(p->y - p1->y); + + if (d < *dmin) { + *dmin = d; + *closest = t; + } + (*n)++; + } + } + return FALSE; +} + +/* select the face closest to @p among n^1/3 randomly picked faces + * of @surface */ +static GtsFace * closest_face (GtsSurface * s, GtsPoint * p) +{ + guint n = 0, nt, ns; + gdouble dmin = G_MAXDOUBLE; + GtsFace * closest = NULL; + gpointer data[5]; + + nt = gts_surface_face_number (s); + if (!nt) + return NULL; + ns = exp (log ((gdouble) nt)/3.); + + data[0] = &dmin; + data[1] = &closest; + data[2] = &ns; + data[3] = &n; + data[4] = p; + g_tree_traverse (s->faces, (GTraverseFunc) find_closest, G_IN_ORDER, data); + + return closest; +} + +#else /* not USE_SURFACE_BTREE */ + +typedef struct _SFindClosest SFindClosest; + +struct _SFindClosest { + gdouble dmin; + GtsFace *closest; + GtsPoint * p; + gint stop; +}; + +# if GLIB_CHECK_VERSION(2,4,0) +/* finally, with g_hash_table_find we are able to stop iteration over the hash + table in the middle */ + +static gboolean find_closest (gpointer key, gpointer value, gpointer user_data) +{ + SFindClosest * data = (SFindClosest *) user_data; + GtsFace * f = GTS_FACE (value); + + if (gts_triangle_orientation (GTS_TRIANGLE (f)) > 0.) { + GtsPoint * p1 = GTS_POINT (GTS_SEGMENT (GTS_TRIANGLE (f)->e1)->v1); + gdouble d = ((data->p->x - p1->x)*(data->p->x - p1->x) + + (data->p->y - p1->y)*(data->p->y - p1->y)); + + if (d < data->dmin) { + data->dmin = d; + data->closest = f; + } + } + data->stop--; + return !(data->stop > 0); +} + +static GtsFace * closest_face (GtsSurface * s, GtsPoint * p) +{ + SFindClosest fc; + + fc.dmin = G_MAXDOUBLE; + fc.closest = NULL; + fc.p = p; + fc.stop = (gint) exp (log ((gdouble) g_hash_table_size (s->faces))/3.); + g_hash_table_find (s->faces, find_closest, &fc); + + return fc.closest; +} + +# else /* VERSION < 2.4.0 */ + +static void +find_closest (gpointer key, gpointer value, gpointer user_data) +{ + SFindClosest * data = (SFindClosest *) user_data; + GtsFace * f = GTS_FACE (value); + + if (gts_triangle_orientation (GTS_TRIANGLE (f)) > 0.) { + GtsPoint * p1 = GTS_POINT (GTS_SEGMENT (GTS_TRIANGLE (f)->e1)->v1); + gdouble d = ((data->p->x - p1->x)*(data->p->x - p1->x) + + (data->p->y - p1->y)*(data->p->y - p1->y)); + + if (d < data->dmin) { + data->dmin = d; + data->closest = f; + } + } + data->stop--; +} + +/* select the face closest to @p among n^1/3 randomly picked faces + * of @surface */ +static GtsFace * closest_face (GtsSurface * s, GtsPoint * p) +{ + SFindClosest fc; + + if (!g_hash_table_size (s->faces)) + return NULL; + + fc.dmin = G_MAXDOUBLE; + fc.closest = NULL; + fc.p = p; + fc.stop = (gint) exp (log ((gdouble) g_hash_table_size (s->faces))/3.); + g_hash_table_foreach (s->faces, find_closest, &fc); + return fc.closest; +} +# endif /* VERSION < 2.4.0 */ +#endif /* not USE_SURFACE_BTREE */ + +/* returns the face belonging to @surface and neighbor of @f via @e */ +static GtsFace * neighbor (GtsFace * f, + GtsEdge * e, + GtsSurface * surface) +{ + GSList * i = e->triangles; + GtsTriangle * t = GTS_TRIANGLE (f); + + while (i) { + GtsTriangle * t1 = i->data; + if (t1 != t && + GTS_IS_FACE (t1) && + gts_face_has_parent_surface (GTS_FACE (t1), surface)) + return GTS_FACE (t1); + i = i->next; + } + return NULL; +} + +/* given a triangle @t and a segment s (@o -> @p). + @o must be in @t. Returns the + edge of @t which is intersected by s or %NULL if @p is also + contained in @t (on_summit is set to %FALSE) or if s intersects @t + exactly on one of its summit (on_summit is set to %TRUE). */ +static GtsEdge * triangle_next_edge (GtsTriangle * t, + GtsPoint * o, GtsPoint * p, + gboolean * on_summit) +{ + GtsVertex * v1, * v2, * v3; + GtsEdge * e1, * e2, * e3; + gdouble orient = 0.0; + + gts_triangle_vertices_edges (t, NULL, + &v1, &v2, &v3, + &e1, &e2, &e3); + + *on_summit = FALSE; + orient = gts_point_orientation (o, GTS_POINT (v1), p); + if (orient > 0.0) { + orient = gts_point_orientation (o, GTS_POINT (v2), p); + if (orient > 0.0) { + if (gts_point_orientation (GTS_POINT (v2), GTS_POINT (v3), p) >= 0.0) + return NULL; + return e2; + } + if (orient < 0.0) { + if (gts_point_orientation (GTS_POINT (v1), GTS_POINT (v2), p) >= 0.0) + return NULL; + return e1; + } + if (gts_point_orientation (GTS_POINT (v1), GTS_POINT (v2), p) < 0.0) + *on_summit = TRUE; + return NULL; + } + + if (orient < 0.0) { + orient = gts_point_orientation (o, GTS_POINT (v3), p); + if (orient > 0.0) { + if (gts_point_orientation (GTS_POINT (v3), GTS_POINT (v1), p) >= 0.0) + return NULL; + return e3; + } + if (orient < 0.0) { + if (gts_point_orientation (GTS_POINT (v2), GTS_POINT (v3), p) >= 0.0) + return NULL; + return e2; + } + if (gts_point_orientation (GTS_POINT (v3), GTS_POINT (v1), p) < 0.0) + *on_summit = TRUE; + return NULL; + } + + if (gts_point_orientation (GTS_POINT (v2), GTS_POINT (v3), p) < 0.0) + return e2; + if (gts_point_orientation (GTS_POINT (v1), GTS_POINT (v2), p) < 0.0) + *on_summit = TRUE; + return NULL; +} + +static void triangle_barycenter (GtsTriangle * t, GtsPoint * b) +{ + GtsPoint * p = GTS_POINT (gts_triangle_vertex (t)); + b->x = (p->x + + GTS_POINT (GTS_SEGMENT(t->e1)->v1)->x + + GTS_POINT (GTS_SEGMENT(t->e1)->v2)->x)/3.; + b->y = (p->y + + GTS_POINT (GTS_SEGMENT(t->e1)->v1)->y + + GTS_POINT (GTS_SEGMENT(t->e1)->v2)->y)/3.; +} + +static GtsFace * point_locate (GtsPoint * o, + GtsPoint * p, + GtsFace * f, + GtsSurface * surface) +{ + GtsEdge * prev; + gboolean on_summit; + GtsVertex * v1, * v2, * v3; + GtsEdge * e2, * e3; + + prev = triangle_next_edge (GTS_TRIANGLE (f), o, p, &on_summit); + + if (!prev) { + GtsFace * f1; + + if (!on_summit) + return f; /* p is inside f */ + + /* s intersects f exactly on a summit: restarts from a neighbor of f */ + if ((f1 = neighbor (f, GTS_TRIANGLE (f)->e1, surface)) || + (f1 = neighbor (f, GTS_TRIANGLE (f)->e2, surface)) || + (f1 = neighbor (f, GTS_TRIANGLE (f)->e3, surface))) { + triangle_barycenter (GTS_TRIANGLE (f1), o); + return point_locate (o, p, f1, surface); + } + return NULL; + } + + f = neighbor (f, prev, surface); + if (f) + gts_triangle_vertices_edges (GTS_TRIANGLE (f), prev, + &v1, &v2, &v3, &prev, &e2, &e3); + while (f) { + gdouble orient = gts_point_orientation (o, GTS_POINT (v3), p); + + if (orient < 0.0) { + if (gts_point_orientation (GTS_POINT (v2), GTS_POINT (v3), p) >= 0.0) + return f; /* p is inside f */ + f = neighbor (f, e2, surface); + prev = e2; + v1 = v3; + } + else if (orient > 0.0) { + if (gts_point_orientation (GTS_POINT (v3), GTS_POINT (v1), p) >= 0.0) + return f; /* p is inside f */ + f = neighbor (f, e3, surface); + prev = e3; + v2 = v3; + } + else { + GtsFace * f1; + + if (gts_point_orientation (GTS_POINT (v2), GTS_POINT (v3), p) >= 0.0) + return f; /* p is inside f */ + + /* s intersects f exactly on v3: restarts from a neighbor of f */ + if ((f1 = neighbor (f, e2, surface)) || + (f1 = neighbor (f, e3, surface))) { + triangle_barycenter (GTS_TRIANGLE (f1), o); + return point_locate (o, p, f1, surface); + } + return NULL; + } + /* update e2, e3, v3 for the new triangle */ + if (f) { + if (prev == GTS_TRIANGLE (f)->e1) { + e2 = GTS_TRIANGLE (f)->e2; e3 = GTS_TRIANGLE (f)->e3; + } + else if (prev == GTS_TRIANGLE (f)->e2) { + e2 = GTS_TRIANGLE (f)->e3; e3 = GTS_TRIANGLE (f)->e1; + } + else { + e2 = GTS_TRIANGLE (f)->e1; e3 = GTS_TRIANGLE (f)->e2; + } + if (GTS_SEGMENT (e2)->v1 == v1 || GTS_SEGMENT (e2)->v1 == v2) + v3 = GTS_SEGMENT (e2)->v2; + else + v3 = GTS_SEGMENT (e2)->v1; + } + } + return NULL; +} + +/** + * gts_point_locate: + * @p: a #GtsPoint. + * @surface: a #GtsSurface. + * @guess: %NULL or a face of @surface close to @p. + * + * Locates the face of the planar projection of @surface containing + * @p. The planar projection of @surface must define a connected set + * of triangles without holes and bounded by a convex boundary. The + * algorithm is randomized and performs in O(n^1/3) expected time + * where n is the number of triangles of @surface. + * + * If a good @guess is given the point location can be significantly faster. + * + * Returns: a #GtsFace of @surface containing @p or %NULL if @p is not + * contained within the boundary of @surface. + */ +GtsFace * gts_point_locate (GtsPoint * p, + GtsSurface * surface, + GtsFace * guess) +{ + GtsFace * fr; + GtsPoint * o; + + g_return_val_if_fail (p != NULL, NULL); + g_return_val_if_fail (surface != NULL, NULL); + g_return_val_if_fail (guess == NULL || + gts_face_has_parent_surface (guess, surface), NULL); + + if (guess == NULL) + guess = closest_face (surface, p); + else + g_return_val_if_fail (gts_triangle_orientation (GTS_TRIANGLE (guess)) > 0., NULL); + + if (guess == NULL) + return NULL; + + o = GTS_POINT (gts_object_new (GTS_OBJECT_CLASS (gts_point_class ()))); + triangle_barycenter (GTS_TRIANGLE (guess), o); + fr = point_locate (o, p, guess, surface); + gts_object_destroy (GTS_OBJECT (o)); + + return fr; +} + + +/** + * gts_constraint_class: + * + * Returns: the #GtsConstraintClass. + */ +GtsConstraintClass * gts_constraint_class (void) +{ + static GtsConstraintClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo constraint_info = { + "GtsConstraint", + sizeof (GtsConstraint), + sizeof (GtsConstraintClass), + (GtsObjectClassInitFunc) NULL, + (GtsObjectInitFunc) NULL, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_edge_class ()), + &constraint_info); + } + + return klass; +} + +static void split_list (GtsListFace * f, GtsListFace * f1, GtsListFace * f2, + GtsPoint * p1, GtsPoint * p2, + GSList ** last1, GSList ** last2) +{ + GSList * i = f->points, * l1 = *last1, * l2 = *last2; + + while (i) { + GtsPoint * p = i->data; + + if (gts_point_orientation (p1, p2, p) >= 0.) { + if (l1) l1->next = i; else f1->points = i; + l1 = i; + } + else { + if (l2) l2->next = i; else f2->points = i; + l2 = i; + } + i = i->next; + } + f->points = NULL; + *last1 = l1; + *last2 = l2; +} + +/* cf. figure misc/swap.fig */ +static void swap_if_in_circle (GtsFace * f1, + GtsVertex * v1, + GtsVertex * v2, + GtsVertex * v3, + GtsEdge * e1, + GtsEdge * e2, + GtsEdge * e3, + GtsSurface * surface) +{ + GtsFace * f2; + GtsEdge * e4, *e5; + GtsVertex * v4; + + if (GTS_IS_CONSTRAINT (e1)) /* @e1 is a constraint can not swap */ + return; + + f2 = neighbor (f1, e1, surface); + if (f2 == NULL) /* @e1 is a boundary of @surface */ + return; + + if (GTS_TRIANGLE (f2)->e1 == e1) { + e4 = GTS_TRIANGLE (f2)->e2; e5 = GTS_TRIANGLE (f2)->e3; + } + else if (GTS_TRIANGLE (f2)->e2 == e1) { + e4 = GTS_TRIANGLE (f2)->e3; e5 = GTS_TRIANGLE (f2)->e1; + } + else { + e4 = GTS_TRIANGLE (f2)->e1; e5 = GTS_TRIANGLE (f2)->e2; + } + if (GTS_SEGMENT (e4)->v1 == GTS_SEGMENT (e1)->v1 || + GTS_SEGMENT (e4)->v1 == GTS_SEGMENT (e1)->v2) + v4 = GTS_SEGMENT (e4)->v2; + else + v4 = GTS_SEGMENT (e4)->v1; + + if (gts_point_in_circle (GTS_POINT (v4), GTS_POINT (v1), + GTS_POINT (v2), GTS_POINT (v3)) > 0.0) { + GtsEdge * en; + GtsSegment * sn = gts_vertices_are_connected (v3, v4); + GtsFace * f3, * f4; + + if (!GTS_IS_EDGE (sn)) + en = gts_edge_new (surface->edge_class, v3, v4); + else + en = GTS_EDGE (sn); + + f3 = gts_face_new (surface->face_class, en, e5, e2); + gts_object_attributes (GTS_OBJECT (f3), GTS_OBJECT (f1)); + f4 = gts_face_new (surface->face_class, en, e3, e4); + gts_object_attributes (GTS_OBJECT (f4), GTS_OBJECT (f2)); + + if (GTS_IS_LIST_FACE (f3)) { + GSList * last3 = NULL, * last4 = NULL; + + if (GTS_IS_LIST_FACE (f1)) + split_list (GTS_LIST_FACE (f1), GTS_LIST_FACE (f3), GTS_LIST_FACE (f4), + GTS_POINT (v3), GTS_POINT (v4), &last3, &last4); + if (GTS_IS_LIST_FACE (f2)) + split_list (GTS_LIST_FACE (f2), GTS_LIST_FACE (f3), GTS_LIST_FACE (f4), + GTS_POINT (v3), GTS_POINT (v4), &last3, &last4); + if (last3) last3->next = NULL; + if (last4) last4->next = NULL; + } + + gts_surface_remove_face (surface, f1); + gts_surface_remove_face (surface, f2); + gts_surface_add_face (surface, f3); + gts_surface_add_face (surface, f4); + + swap_if_in_circle (f3, v4, v2, v3, e5, e2, en, surface); + swap_if_in_circle (f4, v1, v4, v3, e4, en, e3, surface); + } +} + +/** + * gts_delaunay_add_vertex_to_face: + * @surface: a #GtsSurface. + * @v: a #GtsVertex. + * @f: a #GtsFace belonging to @surface. + * + * Adds vertex @v to the face @f of the Delaunay triangulation defined + * by @surface. + * + * Returns: %NULL is @v has been successfully added to @surface or was + * already contained in @surface or a #GtsVertex having the same x and + * y coordinates as @v. + */ +GtsVertex * gts_delaunay_add_vertex_to_face (GtsSurface * surface, + GtsVertex * v, + GtsFace * f) +{ + GtsEdge * e1, * e2, * e3; + GtsSegment * s4, * s5, * s6; + GtsEdge * e4, * e5, * e6; + GtsVertex * v1, * v2, * v3; + GtsFace * nf[3]; + + g_return_val_if_fail (surface != NULL, v); + g_return_val_if_fail (v != NULL, v); + g_return_val_if_fail (f != NULL, v); + + gts_triangle_vertices_edges (GTS_TRIANGLE (f), NULL, + &v1, &v2, &v3, &e1, &e2, &e3); + if (v == v1 || v == v2 || v == v3) /* v already in @surface */ + return NULL; + if (GTS_POINT (v)->x == GTS_POINT (v1)->x && + GTS_POINT (v)->y == GTS_POINT (v1)->y) + return v1; + if (GTS_POINT (v)->x == GTS_POINT (v2)->x && + GTS_POINT (v)->y == GTS_POINT (v2)->y) + return v2; + if (GTS_POINT (v)->x == GTS_POINT (v3)->x && + GTS_POINT (v)->y == GTS_POINT (v3)->y) + return v3; + + s4 = gts_vertices_are_connected (v, v1); + if (!GTS_IS_EDGE (s4)) + e4 = gts_edge_new (surface->edge_class, v, v1); + else + e4 = GTS_EDGE (s4); + s5 = gts_vertices_are_connected (v, v2); + if (!GTS_IS_EDGE (s5)) + e5 = gts_edge_new (surface->edge_class, v, v2); + else + e5 = GTS_EDGE (s5); + s6 = gts_vertices_are_connected (v, v3); + if (!GTS_IS_EDGE (s6)) + e6 = gts_edge_new (surface->edge_class, v, v3); + else + e6 = GTS_EDGE (s6); + + /* cf. figure misc/swap.fig */ + nf[0] = gts_face_new (surface->face_class, e4, e1, e5); + gts_object_attributes (GTS_OBJECT (nf[0]), GTS_OBJECT (f)); + nf[1] = gts_face_new (surface->face_class, e5, e2, e6); + gts_object_attributes (GTS_OBJECT (nf[1]), GTS_OBJECT (f)); + nf[2] = gts_face_new (surface->face_class, e6, e3, e4); + gts_object_attributes (GTS_OBJECT (nf[2]), GTS_OBJECT (f)); + + if (GTS_IS_LIST_FACE (f) && GTS_IS_LIST_FACE (nf[0])) { + GSList * i = GTS_LIST_FACE (f)->points, * last[3] = { NULL, NULL, NULL }; + + while (i) { + GtsPoint * p = i->data; + GSList * next = i->next; + guint j; + + if (p != GTS_POINT (v)) { + if (gts_point_orientation (GTS_POINT (v), GTS_POINT (v1), p) >= 0.) { + gdouble o = gts_point_orientation (GTS_POINT (v), GTS_POINT (v2), p); + + if (o != 0.) + j = o > 0. ? 1 : 0; + else + j = gts_point_orientation (GTS_POINT (v), GTS_POINT (v3), p) + > 0. ? 0 : 1; + } + else if (gts_point_orientation (GTS_POINT (v), GTS_POINT (v3), p) > 0.) + j = 2; + else + j = 1; + if (last[j]) + last[j]->next = i; + else + GTS_LIST_FACE (nf[j])->points = i; + last[j] = i; + } + else + g_slist_free_1 (i); + i = next; + } + GTS_LIST_FACE (f)->points = NULL; + if (last[0]) last[0]->next = NULL; + if (last[1]) last[1]->next = NULL; + if (last[2]) last[2]->next = NULL; + } + + gts_surface_remove_face (surface, f); + gts_surface_add_face (surface, nf[0]); + gts_surface_add_face (surface, nf[1]); + gts_surface_add_face (surface, nf[2]); + + swap_if_in_circle (nf[0], v1, v2, v, e1, e5, e4, surface); + swap_if_in_circle (nf[1], v2, v3, v, e2, e6, e5, surface); + swap_if_in_circle (nf[2], v3, v1, v, e3, e4, e6, surface); + + return NULL; +} + +/** + * gts_delaunay_add_vertex: + * @surface: a #GtsSurface. + * @v: a #GtsVertex. + * @guess: %NULL or a #GtsFace belonging to @surface to be used as an initial + * guess for point location. + * + * Adds vertex @v to the Delaunay triangulation defined by + * @surface. If @v is not contained in the convex hull bounding + * @surface, @v is not added to the triangulation. + * + * Returns: %NULL is @v has been successfully added to @surface or was + * already contained in @surface, @v if @v is not contained in the + * convex hull bounding surface or a #GtsVertex having the same x and + * y coordinates as @v. + */ +GtsVertex * gts_delaunay_add_vertex (GtsSurface * surface, + GtsVertex * v, + GtsFace * guess) +{ + GtsFace * f; + + g_return_val_if_fail (surface != NULL, v); + g_return_val_if_fail (v != NULL, v); + + if (!(f = gts_point_locate (GTS_POINT (v), surface, guess))) + return v; + return gts_delaunay_add_vertex_to_face (surface, v, f); +} + +static gboolean polygon_in_circle (GSList * poly, + GtsPoint * p1, + GtsPoint * p2, + GtsPoint * p3) +{ + GtsVertex * v1 = NULL, * v2 = NULL; + + while (poly) { + GtsSegment * s = poly->data; + GtsVertex * v; + v = s->v1; + if (v != v1 && v != v2 && + v != GTS_VERTEX (p1) && + v != GTS_VERTEX (p2) && + v != GTS_VERTEX (p3) && + gts_point_in_circle (GTS_POINT (v), p1, p2, p3) > 0.) + return TRUE; + v = s->v2; + if (v != v1 && v != v2 && + v != GTS_VERTEX (p1) && + v != GTS_VERTEX (p2) && + v != GTS_VERTEX (p3) && + gts_point_in_circle (GTS_POINT (v), p1, p2, p3) > 0.) + return TRUE; + v1 = s->v1; + v2 = s->v2; + poly = poly->next; + } + return FALSE; +} + +static void triangulate_polygon (GSList * poly, + GtsSurface * surface, + GtsFace * ref) +{ + GSList * i, * poly1, * poly2; + GtsVertex * v1, * v2, * v3 = NULL; + gboolean found = FALSE; + GtsSegment * s, * s1, * s2; + GtsEdge * e1, * e2; + GtsFace * f; + + if (poly == NULL || poly->next == NULL) { + g_slist_free (poly); + return; + } + + s = poly->data; + s1 = poly->next->data; + if (s->v1 == s1->v1 || s->v1 == s1->v2) { + v1 = s->v2; + v2 = s->v1; + } + else { + g_assert (s->v2 == s1->v1 || s->v2 == s1->v2); + v1 = s->v1; + v2 = s->v2; + } + + i = poly->next; + v3 = v2; + while (i && !found) { + s1 = i->data; + if (s1->v1 == v3) + v3 = s1->v2; + else { + g_assert (s1->v2 == v3); + v3 = s1->v1; + } + if (v3 != v1 && + gts_point_orientation (GTS_POINT (v1), + GTS_POINT (v2), + GTS_POINT (v3)) >= 0. && + !polygon_in_circle (poly, + GTS_POINT (v1), + GTS_POINT (v2), + GTS_POINT (v3))) + found = TRUE; + else + i = i->next; + } + + if (!found) { + g_slist_free (poly); + return; + } + + s1 = gts_vertices_are_connected (v2, v3); + if (!GTS_IS_EDGE (s1)) + e1 = gts_edge_new (surface->edge_class, v2, v3); + else + e1 = GTS_EDGE (s1); + s2 = gts_vertices_are_connected (v3, v1); + if (!GTS_IS_EDGE (s2)) + e2 = gts_edge_new (surface->edge_class, v3, v1); + else + e2 = GTS_EDGE (s2); + f = gts_face_new (surface->face_class, GTS_EDGE (s), e1, e2); + gts_object_attributes (GTS_OBJECT (f), GTS_OBJECT (ref)); + gts_surface_add_face (surface, f); + + poly1 = poly->next; + g_slist_free_1 (poly); + if (i->next && e2 != i->next->data) + poly2 = g_slist_prepend (i->next, e2); + else + poly2 = i->next; + if (e1 != i->data) + i->next = g_slist_prepend (NULL, e1); + else + i->next = NULL; + + triangulate_polygon (poly1, surface, ref); + triangulate_polygon (poly2, surface, ref); +} + +/** + * gts_delaunay_remove_vertex: + * @surface: a #GtsSurface. + * @v: a #GtsVertex. + * + * Removes @v from the Delaunay triangulation defined by @surface and + * restores the Delaunay property. Vertex @v must not be used by any + * constrained edge otherwise the triangulation is not guaranteed to + * be Delaunay. + */ +void gts_delaunay_remove_vertex (GtsSurface * surface, GtsVertex * v) +{ + GSList * triangles, * i; + GtsFace * ref = NULL; + + g_return_if_fail (surface != NULL); + g_return_if_fail (v != NULL); + + i = triangles = gts_vertex_triangles (v, NULL); + while (i && !ref) { + if (GTS_IS_FACE (i->data) && + gts_face_has_parent_surface (i->data, surface)) + ref = i->data; + i = i->next; + } + if (!ref) { + g_slist_free (triangles); + g_return_if_fail (ref); + } + triangulate_polygon (gts_vertex_fan_oriented (v, surface), surface, ref); + i = triangles; + while (i) { + if (GTS_IS_FACE (i->data) && + gts_face_has_parent_surface (i->data, surface)) + gts_surface_remove_face (surface, i->data); + i = i->next; + } + g_slist_free (triangles); +} + +#define NEXT_CUT(edge, edge1, list) { next = neighbor (f, edge, surface);\ + remove_triangles (e, surface);\ + if (!constraint && !e->triangles)\ + gts_object_destroy (GTS_OBJECT (e));\ + g_assert (next);\ + *list = g_slist_prepend (*list, edge1);\ + return g_slist_concat (constraint,\ + remove_intersected_edge (s, edge,\ + next, surface, left, right));\ + } + +static void remove_triangles (GtsEdge * e, GtsSurface * s) +{ + GSList * i = e->triangles; + + while (i) { + GSList * next = i->next; + + if (GTS_IS_FACE (i->data) && gts_face_has_parent_surface (i->data, s)) + gts_surface_remove_face (s, i->data); + i = next; + } +} + +static GSList * +remove_intersected_edge (GtsSegment * s, + GtsEdge * e, + GtsFace * f, + GtsSurface * surface, + GSList ** left, GSList ** right) +{ + GtsVertex * v1, * v2, * v3; + GtsEdge * e1, * e2; + gdouble o1, o2; + GtsFace * next; + GSList * constraint = NULL; + + if (GTS_IS_CONSTRAINT (e)) + constraint = g_slist_prepend (NULL, e); + + gts_triangle_vertices_edges (GTS_TRIANGLE (f), e, + &v1, &v2, &v3, &e, &e1, &e2); + + o1 = gts_point_orientation (GTS_POINT (v2), GTS_POINT (v3), + GTS_POINT (s->v2)); + o2 = gts_point_orientation (GTS_POINT (v3), GTS_POINT (v1), + GTS_POINT (s->v2)); + + if (o1 == 0. && o2 == 0.) { +/* if(o2 != 0.) { + fprintf(stderr, "o1 = %f o2 = %f\n", o1, o2); + fprintf(stderr, "v1 = %f, %f\n", GTS_POINT(v1)->x, GTS_POINT(v1)->y); + fprintf(stderr, "v2 = %f, %f\n", GTS_POINT(v2)->x, GTS_POINT(v2)->y); + fprintf(stderr, "v3 = %f, %f\n", GTS_POINT(v3)->x, GTS_POINT(v3)->y); + fprintf(stderr, "s->v2 = %f, %f\n", GTS_POINT(s->v2)->x, GTS_POINT(s->v2)->y); + + g_assert (o2 == 0.); + }*/ + // if(o2 == 0.) { + remove_triangles (e, surface); + if (!constraint && !e->triangles) + gts_object_destroy (GTS_OBJECT (e)); + *left = g_slist_prepend (*left, e2); + *right = g_slist_prepend (*right, e1); +// } + } + else if (o1 > 0.) { + g_assert (o2 <= 0.); + NEXT_CUT (e2, e1, right) + } + else if (o2 >= 0.) + NEXT_CUT (e1, e2, left) + else { + gdouble o3 = gts_point_orientation (GTS_POINT (s->v1), GTS_POINT (s->v2), + GTS_POINT (v3)); + if (o3 > 0.) + NEXT_CUT (e1, e2, left) + else + NEXT_CUT (e2, e1, right) + } + return constraint; +} + +static GSList * +remove_intersected_vertex (GtsSegment * s, + GtsVertex * v, + GtsSurface * surface, + GSList ** left, + GSList ** right, + GtsFace ** ref) +{ + GSList * triangles = gts_vertex_triangles (v, NULL); + GSList * i; + + i = triangles; + while (i) { + GtsTriangle * t = i->data; + if (GTS_IS_FACE (t) && + gts_face_has_parent_surface (GTS_FACE (t), surface)) { + GtsVertex * v1, * v2, * v3; + gdouble o1, o2; + + gts_triangle_vertices (t, &v1, &v2, &v3); + if (v == v2) { + v2 = v3; + v3 = v1; + } + else if (v == v3) { + v3 = v2; + v2 = v1; + } + else + g_assert (v == v1); + + if ((o1 = gts_point_orientation (GTS_POINT (v), GTS_POINT (v2), + GTS_POINT (s->v2))) >= 0. && + (o2 = gts_point_orientation (GTS_POINT (v3), GTS_POINT (v), + GTS_POINT (s->v2))) >= 0.) { + gdouble o3 = gts_point_orientation (GTS_POINT (v2), GTS_POINT (v3), + GTS_POINT (s->v2)); + GtsEdge * e = gts_triangle_edge_opposite (t, v); + GtsEdge * e1, * e2; + GtsFace * next = neighbor (GTS_FACE (t), e, surface); + + *ref = GTS_FACE (t); + gts_triangle_vertices_edges (t, e, &v2, &v3, &v, &e, &e2, &e1); + + g_slist_free (triangles); + + if (o3 >= 0.) /* @s->v2 is inside (or on the edge) of t */ + return NULL; + + gts_allow_floating_faces = TRUE; + gts_surface_remove_face (surface, GTS_FACE (t)); + gts_allow_floating_faces = FALSE; + + *left = g_slist_prepend (*left, e2); + *right = g_slist_prepend (*right, e1); + + g_assert (next); + return remove_intersected_edge (s, e, next, surface, left, right); + } + } + i = i->next; + } + + g_assert_not_reached (); + return NULL; +} + +/** + * gts_delaunay_add_constraint: + * @surface: a #GtsSurface. + * @c: a #GtsConstraint. + * + * Add constraint @c to the constrained Delaunay triangulation defined by + * @surface. + * + * Returns: a list of #GtsConstraint conflicting (i.e. intersecting) with @c + * which were removed from @surface (%NULL if there was none). + */ +GSList * gts_delaunay_add_constraint (GtsSurface * surface, + GtsConstraint * c) +{ + GSList * constraints; + GtsVertex * v1; //, * v2; + GSList * left = NULL, * right = NULL; + GtsFace * ref = NULL; + + g_return_val_if_fail (surface != NULL, NULL); + g_return_val_if_fail (c != NULL, NULL); + g_return_val_if_fail (GTS_IS_CONSTRAINT (c), NULL); + + v1 = GTS_SEGMENT (c)->v1; + //v2 = GTS_SEGMENT (c)->v2; + + gts_allow_floating_edges = TRUE; + constraints = remove_intersected_vertex (GTS_SEGMENT (c), v1, surface, + &left, &right, &ref); + gts_allow_floating_edges = FALSE; +#if 1 + triangulate_polygon (g_slist_prepend (g_slist_reverse (right), c), + surface, ref); + triangulate_polygon (g_slist_prepend (left, c), + surface, ref); +#else + right = g_slist_prepend (g_slist_reverse (right), c); + left = g_slist_prepend (left, c); + { + FILE * fp0 = fopen ("hole", "wt"); + FILE * fp1 = fopen ("right", "wt"); + FILE * fp2 = fopen ("left", "wt"); + GSList * i = left; + + gts_surface_write (surface, fp0); + fclose (fp0); + + fprintf (fp2, "LIST {\n"); + while (i) { + GtsSegment * s = i->data; + fprintf (fp2, + "# %p: %p->%p\n" + "VECT 1 2 0 2 0 %g %g 0 %g %g 0\n", + s, s->v1, s->v2, + GTS_POINT (s->v1)->x, GTS_POINT (s->v1)->y, + GTS_POINT (s->v2)->x, GTS_POINT (s->v2)->y); + i = i->next; + } + fprintf (fp2, "}\n"); + fprintf (fp1, "LIST {\n"); + i = right; + while (i) { + GtsSegment * s = i->data; + fprintf (fp1, + "# %p: %p->%p\n" + "VECT 1 2 0 2 0 %g %g 0 %g %g 0\n", + s, s->v1, s->v2, + GTS_POINT (s->v1)->x, GTS_POINT (s->v1)->y, + GTS_POINT (s->v2)->x, GTS_POINT (s->v2)->y); + i = i->next; + } + fprintf (fp1, "}\n"); + fclose (fp1); + fclose (fp2); + } + triangulate_polygon (right, surface); + triangulate_polygon (left, surface); +#endif + if (ref && !ref->surfaces) { + gts_allow_floating_edges = TRUE; + gts_object_destroy (GTS_OBJECT (ref)); + gts_allow_floating_edges = FALSE; + } + return constraints; +} + +static void delaunay_check (GtsTriangle * t, gpointer * data) +{ + GtsSurface * surface = data[0]; + GtsFace ** face = data[1]; + + if (*face == NULL) { + GSList * i, * list; + GtsVertex * v1, * v2, * v3; + + gts_triangle_vertices (t, &v1, &v2, &v3); + list = gts_vertex_neighbors (v1, NULL, surface); + list = gts_vertex_neighbors (v2, list, surface); + list = gts_vertex_neighbors (v3, list, surface); + i = list; + while (i && *face == NULL) { + GtsVertex * v = i->data; + if (v != v1 && v != v2 && v != v3 && + gts_point_in_circle (GTS_POINT (v), + GTS_POINT (v1), + GTS_POINT (v2), + GTS_POINT (v3)) > 0.) + *face = GTS_FACE (t); + i = i->next; + } + g_slist_free (list); + } +} + +/** + * gts_delaunay_check: + * @surface: a #GtsSurface. + * + * Returns: %NULL if the planar projection of @surface is a Delaunay + * triangulation (unconstrained), a #GtsFace violating the Delaunay + * property otherwise. + */ +GtsFace * gts_delaunay_check (GtsSurface * surface) +{ + GtsFace * face = NULL; + gpointer data[2]; + + g_return_val_if_fail (surface != NULL, FALSE); + + data[0] = surface; + data[1] = &face; + gts_surface_foreach_face (surface, (GtsFunc) delaunay_check, data); + + return face; +} + +/** + * gts_delaunay_remove_hull: + * @surface: a #GtsSurface. + * + * Removes all the edges of the boundary of @surface which are not + * constraints. + */ +void gts_delaunay_remove_hull (GtsSurface * surface) +{ + GSList * boundary; + + g_return_if_fail (surface != NULL); + + boundary = gts_surface_boundary (surface); + gts_allow_floating_edges = TRUE; + while (boundary) { + GSList * i = boundary; + GtsEdge * e = i->data; + + boundary = i->next; + g_slist_free_1 (i); + if (!GTS_IS_CONSTRAINT (e)) { + GtsTriangle * t = GTS_TRIANGLE (gts_edge_is_boundary (e, surface)); + + if (t != NULL) { + if (t->e1 != e && !GTS_IS_CONSTRAINT (t->e1) && + !gts_edge_is_boundary (t->e1, surface)) + boundary = g_slist_prepend (boundary, t->e1); + if (t->e2 != e && !GTS_IS_CONSTRAINT (t->e2) && + !gts_edge_is_boundary (t->e2, surface)) + boundary = g_slist_prepend (boundary, t->e2); + if (t->e3 != e && !GTS_IS_CONSTRAINT (t->e3) && + !gts_edge_is_boundary (t->e3, surface)) + boundary = g_slist_prepend (boundary, t->e3); + gts_surface_remove_face (surface, GTS_FACE (t)); + } + if (!e->triangles) + gts_object_destroy (GTS_OBJECT (e)); + } + } + gts_allow_floating_edges = FALSE; +} + +/* GtsListFace: Object */ + +static void gts_list_face_destroy (GtsObject * object) +{ + g_slist_free (GTS_LIST_FACE (object)->points); + + (* GTS_OBJECT_CLASS (gts_list_face_class ())->parent_class->destroy) + (object); +} + +static void gts_list_face_class_init (GtsFaceClass * klass) +{ + GTS_OBJECT_CLASS (klass)->destroy = gts_list_face_destroy; +} + +GtsFaceClass * gts_list_face_class (void) +{ + static GtsFaceClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo gts_list_face_info = { + "GtsListFace", + sizeof (GtsListFace), + sizeof (GtsFaceClass), + (GtsObjectClassInitFunc) gts_list_face_class_init, + (GtsObjectInitFunc) NULL, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_face_class ()), + >s_list_face_info); + } + + return klass; +} diff --git a/gts/container.c b/gts/container.c new file mode 100644 index 0000000000..e1dc0fa73d --- /dev/null +++ b/gts/container.c @@ -0,0 +1,493 @@ +/* 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 "gts.h" + +/* GtsContainee */ + +static void containee_class_init (GtsContaineeClass * klass) +{ + klass->remove_container = NULL; + klass->add_container = NULL; + klass->foreach = NULL; + klass->is_contained = NULL; + klass->replace = NULL; +} + +GtsContaineeClass * gts_containee_class (void) +{ + static GtsContaineeClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo containee_info = { + "GtsContainee", + sizeof (GtsContainee), + sizeof (GtsContaineeClass), + (GtsObjectClassInitFunc) containee_class_init, + (GtsObjectInitFunc) NULL, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (gts_object_class (), + &containee_info); + } + + return klass; +} + +GtsContainee * gts_containee_new (GtsContaineeClass * klass) +{ + GtsContainee * object; + + object = GTS_CONTAINEE (gts_object_new (GTS_OBJECT_CLASS (klass))); + + return object; +} + +gboolean gts_containee_is_contained (GtsContainee * item, + GtsContainer * c) +{ + g_return_val_if_fail (item != NULL, FALSE); + g_return_val_if_fail (c != NULL, FALSE); + + if (GTS_CONTAINEE_CLASS (GTS_OBJECT (item)->klass)->is_contained) + return + (* GTS_CONTAINEE_CLASS (GTS_OBJECT (item)->klass)->is_contained) + (item, c); + return FALSE; +} + +void gts_containee_replace (GtsContainee * item, + GtsContainee * with) +{ + if (GTS_CONTAINEE_CLASS (GTS_OBJECT (item)->klass)->replace) + (* GTS_CONTAINEE_CLASS (GTS_OBJECT (item)->klass)->replace) (item, with); + if (GTS_CONTAINEE_CLASS (GTS_OBJECT (item)->klass)->foreach) { + (* GTS_CONTAINEE_CLASS (GTS_OBJECT (item)->klass)->foreach) + (item, (GtsFunc) gts_container_add, with); + (* GTS_CONTAINEE_CLASS (GTS_OBJECT (item)->klass)->foreach) + (item, (GtsFunc) gts_container_remove, item); + } +} + +/* GtsSListContainee */ + +static void slist_containee_destroy (GtsObject * object) +{ + GtsSListContainee * item = GTS_SLIST_CONTAINEE (object); + GSList * i; + + i = item->containers; + while (i) { + GSList * next = i->next; + + gts_container_remove (i->data, GTS_CONTAINEE (item)); + i = next; + } + g_assert (item->containers == NULL); + + (* GTS_OBJECT_CLASS (gts_slist_containee_class ())->parent_class->destroy) + (object); +} + +static void slist_containee_remove_container (GtsContainee * i, + GtsContainer * c) +{ + GtsSListContainee * item = GTS_SLIST_CONTAINEE (i); + item->containers = g_slist_remove (item->containers, c); +} + +static void slist_containee_add_container (GtsContainee * i, + GtsContainer * c) +{ + GtsSListContainee * item = GTS_SLIST_CONTAINEE (i); + if (!g_slist_find (item->containers, c)) + item->containers = g_slist_prepend (item->containers, c); +} + +static void slist_containee_foreach (GtsContainee * c, + GtsFunc func, + gpointer data) +{ + GSList * i = GTS_SLIST_CONTAINEE (c)->containers; + + while (i) { + GSList * next = i->next; + + (* func) (i->data, data); + i = next; + } +} + +static gboolean slist_containee_is_contained (GtsContainee * i, + GtsContainer * c) +{ + return g_slist_find (GTS_SLIST_CONTAINEE (i)->containers, c) ? TRUE : FALSE; +} + +static void slist_containee_class_init (GtsSListContaineeClass * klass) +{ + GTS_CONTAINEE_CLASS (klass)->remove_container = + slist_containee_remove_container; + GTS_CONTAINEE_CLASS (klass)->add_container = + slist_containee_add_container; + GTS_CONTAINEE_CLASS (klass)->foreach = + slist_containee_foreach; + GTS_CONTAINEE_CLASS (klass)->is_contained = + slist_containee_is_contained; + + GTS_OBJECT_CLASS (klass)->destroy = slist_containee_destroy; +} + +static void slist_containee_init (GtsSListContainee * object) +{ + object->containers = NULL; +} + +GtsSListContaineeClass * gts_slist_containee_class (void) +{ + static GtsSListContaineeClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo slist_containee_info = { + "GtsSListContainee", + sizeof (GtsSListContainee), + sizeof (GtsSListContaineeClass), + (GtsObjectClassInitFunc) slist_containee_class_init, + (GtsObjectInitFunc) slist_containee_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_containee_class ()), + &slist_containee_info); + } + + return klass; +} + +/* GtsContainer */ + +static void remove_container (GtsContainee * item, GtsContainer * c) +{ + if (GTS_CONTAINEE_CLASS (GTS_OBJECT (item)->klass)->remove_container) + (* GTS_CONTAINEE_CLASS (GTS_OBJECT (item)->klass)->remove_container) + (item, c); +} + +static void container_destroy (GtsObject * object) +{ + GtsContainer * c = GTS_CONTAINER (object); + + gts_container_foreach (c, (GtsFunc) remove_container, c); + + (* GTS_OBJECT_CLASS (gts_container_class ())->parent_class->destroy) + (object); +} + +static void container_add (GtsContainer * c, GtsContainee * item) +{ + if (GTS_CONTAINEE_CLASS (GTS_OBJECT (item)->klass)->add_container) + (* GTS_CONTAINEE_CLASS (GTS_OBJECT (item)->klass)->add_container) + (item, c); +} + +static void container_remove (GtsContainer * c, GtsContainee * item) +{ + if (GTS_CONTAINEE_CLASS (GTS_OBJECT (item)->klass)->remove_container) + (* GTS_CONTAINEE_CLASS (GTS_OBJECT (item)->klass)->remove_container) + (item, c); +} + +static void container_clone_add (GtsContainee * item, GtsContainer * clone) +{ + gts_container_add (clone, item); +} + +static void container_clone (GtsObject * clone, GtsObject * object) +{ + gts_object_init (clone, object->klass); + gts_container_foreach (GTS_CONTAINER (object), + (GtsFunc) container_clone_add, clone); +} + +static void container_class_init (GtsContainerClass * klass) +{ + klass->add = container_add; + klass->remove = container_remove; + klass->foreach = NULL; + klass->size = NULL; + + GTS_OBJECT_CLASS (klass)->destroy = container_destroy; + GTS_OBJECT_CLASS (klass)->clone = container_clone; +} + +GtsContainerClass * gts_container_class (void) +{ + static GtsContainerClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo container_info = { + "GtsContainer", + sizeof (GtsContainer), + sizeof (GtsContainerClass), + (GtsObjectClassInitFunc) container_class_init, + (GtsObjectInitFunc) NULL, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = + gts_object_class_new (GTS_OBJECT_CLASS (gts_slist_containee_class ()), + &container_info); + } + + return klass; +} + +GtsContainer * gts_container_new (GtsContainerClass * klass) +{ + GtsContainer * object; + + object = GTS_CONTAINER (gts_object_new (GTS_OBJECT_CLASS (klass))); + + return object; +} + +void gts_container_add (GtsContainer * c, + GtsContainee * item) +{ + g_return_if_fail (c != NULL); + g_return_if_fail (item != NULL); + + g_assert (GTS_CONTAINER_CLASS (GTS_OBJECT (c)->klass)->add); + (* GTS_CONTAINER_CLASS (GTS_OBJECT (c)->klass)->add) (c, item); +} + +void gts_container_remove (GtsContainer * c, + GtsContainee * item) +{ + g_return_if_fail (c != NULL); + g_return_if_fail (item != NULL); + + g_assert (GTS_CONTAINER_CLASS (GTS_OBJECT (c)->klass)->remove); + (* GTS_CONTAINER_CLASS (GTS_OBJECT (c)->klass)->remove) (c, item); +} + +void gts_container_foreach (GtsContainer * c, + GtsFunc func, + gpointer data) +{ + g_return_if_fail (c != NULL); + g_return_if_fail (func != NULL); + + if (GTS_CONTAINER_CLASS (GTS_OBJECT (c)->klass)->foreach) + (* GTS_CONTAINER_CLASS (GTS_OBJECT (c)->klass)->foreach) (c, func, data); +} + +guint gts_container_size (GtsContainer * c) +{ + g_return_val_if_fail (c != NULL, 0); + + if (GTS_CONTAINER_CLASS (GTS_OBJECT (c)->klass)->size) + return (* GTS_CONTAINER_CLASS (GTS_OBJECT (c)->klass)->size) (c); + return 0; +} + +/* GtsHashContainer */ + +static void hash_container_destroy (GtsObject * object) +{ + GHashTable * items = GTS_HASH_CONTAINER (object)->items; + + (* GTS_OBJECT_CLASS (gts_hash_container_class ())->parent_class->destroy) + (object); + + g_hash_table_destroy (items); +} + +static void hash_container_add (GtsContainer * c, GtsContainee * item) +{ + g_return_if_fail (GTS_HASH_CONTAINER (c)->frozen == FALSE); + + g_hash_table_insert (GTS_HASH_CONTAINER (c)->items, item, NULL); + + (* GTS_CONTAINER_CLASS (GTS_OBJECT_CLASS (gts_hash_container_class ())->parent_class)->add) (c, item); +} + +static void hash_container_remove (GtsContainer * c, GtsContainee * item) +{ + g_return_if_fail (GTS_HASH_CONTAINER (c)->frozen == FALSE); + + g_hash_table_remove (GTS_HASH_CONTAINER (c)->items, item); + + (* GTS_CONTAINER_CLASS (GTS_OBJECT_CLASS (gts_hash_container_class ())->parent_class)->remove) (c, item); +} + +static void hash_foreach (GtsContainee * item, + gpointer item_data, + gpointer * info) +{ + (* ((GtsFunc) info[0])) (item, info[1]); +} + +static void hash_container_foreach (GtsContainer * c, + GtsFunc func, + gpointer data) +{ + gpointer info[2]; + + info[0] = func; + info[1] = data; + /* prevent removing or adding items */ + GTS_HASH_CONTAINER (c)->frozen = TRUE; + g_hash_table_foreach (GTS_HASH_CONTAINER (c)->items, + (GHFunc) hash_foreach, info); + GTS_HASH_CONTAINER (c)->frozen = FALSE; +} + +static guint hash_container_size (GtsContainer * c) +{ + return g_hash_table_size (GTS_HASH_CONTAINER (c)->items); +} + +static void hash_container_class_init (GtsHashContainerClass * klass) +{ + GTS_CONTAINER_CLASS (klass)->add = hash_container_add; + GTS_CONTAINER_CLASS (klass)->remove = hash_container_remove; + GTS_CONTAINER_CLASS (klass)->foreach = hash_container_foreach; + GTS_CONTAINER_CLASS (klass)->size = hash_container_size; + + GTS_OBJECT_CLASS (klass)->destroy = hash_container_destroy; +} + +static void hash_container_init (GtsHashContainer * object) +{ + object->items = g_hash_table_new (NULL, NULL); + object->frozen = FALSE; +} + +GtsHashContainerClass * gts_hash_container_class (void) +{ + static GtsHashContainerClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo hash_container_info = { + "GtsHashContainer", + sizeof (GtsHashContainer), + sizeof (GtsHashContainerClass), + (GtsObjectClassInitFunc) hash_container_class_init, + (GtsObjectInitFunc) hash_container_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_container_class ()), + &hash_container_info); + } + + return klass; +} + +/* GtsSListContainer */ + +static void slist_container_destroy (GtsObject * object) +{ + GSList * items = GTS_SLIST_CONTAINER (object)->items; + + (* GTS_OBJECT_CLASS (gts_slist_container_class ())->parent_class->destroy) + (object); + + g_slist_free (items); +} + +static void slist_container_add (GtsContainer * c, GtsContainee * item) +{ + g_return_if_fail (GTS_SLIST_CONTAINER (c)->frozen == FALSE); + + if (!g_slist_find (GTS_SLIST_CONTAINER (c)->items, item)) + GTS_SLIST_CONTAINER (c)->items = + g_slist_prepend (GTS_SLIST_CONTAINER (c)->items, item); + + (* GTS_CONTAINER_CLASS (GTS_OBJECT_CLASS (gts_slist_container_class ())->parent_class)->add) (c, item); +} + +static void slist_container_remove (GtsContainer * c, GtsContainee * item) +{ + g_return_if_fail (GTS_SLIST_CONTAINER (c)->frozen == FALSE); + + GTS_SLIST_CONTAINER (c)->items = + g_slist_remove (GTS_SLIST_CONTAINER (c)->items, item); + + (* GTS_CONTAINER_CLASS (GTS_OBJECT_CLASS (gts_slist_container_class ())->parent_class)->remove) (c, item); +} + +static void slist_container_foreach (GtsContainer * c, + GtsFunc func, + gpointer data) +{ + GSList * i; + + i = GTS_SLIST_CONTAINER (c)->items; + while (i) { + GSList * next = i->next; + + (* func) (i->data, data); + i = next; + } +} + +static guint slist_container_size (GtsContainer * c) +{ + return g_slist_length (GTS_SLIST_CONTAINER (c)->items); +} + +static void slist_container_class_init (GtsSListContainerClass * klass) +{ + GTS_CONTAINER_CLASS (klass)->add = slist_container_add; + GTS_CONTAINER_CLASS (klass)->remove = slist_container_remove; + GTS_CONTAINER_CLASS (klass)->foreach = slist_container_foreach; + GTS_CONTAINER_CLASS (klass)->size = slist_container_size; + + GTS_OBJECT_CLASS (klass)->destroy = slist_container_destroy; +} + +static void slist_container_init (GtsSListContainer * object) +{ + object->items = NULL; + object->frozen = FALSE; +} + +GtsSListContainerClass * gts_slist_container_class (void) +{ + static GtsSListContainerClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo slist_container_info = { + "GtsSListContainer", + sizeof (GtsSListContainer), + sizeof (GtsSListContainerClass), + (GtsObjectClassInitFunc) slist_container_class_init, + (GtsObjectInitFunc) slist_container_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_container_class ()), + &slist_container_info); + } + + return klass; +} diff --git a/gts/curvature.c b/gts/curvature.c new file mode 100644 index 0000000000..70f6af272a --- /dev/null +++ b/gts/curvature.c @@ -0,0 +1,621 @@ +/* GTS - Library for the manipulation of triangulated surfaces + * Copyright (C) 1999-2002 Ray Jones, 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 "gts.h" + +static gboolean angle_obtuse (GtsVertex * v, GtsFace * f) +{ + GtsEdge * e = gts_triangle_edge_opposite (GTS_TRIANGLE (f), v); + GtsVector vec1, vec2; + + gts_vector_init (vec1, GTS_POINT (v), GTS_POINT (GTS_SEGMENT (e)->v1)); + gts_vector_init (vec2, GTS_POINT (v), GTS_POINT (GTS_SEGMENT (e)->v2)); + + return (gts_vector_scalar (vec1, vec2) < 0.0); +} + +static gboolean triangle_obtuse (GtsVertex * v, GtsFace * f) +{ + GtsEdge * e = gts_triangle_edge_opposite (GTS_TRIANGLE (f), v); + + return (angle_obtuse (v, f) || + angle_obtuse (GTS_SEGMENT (e)->v1, f) || + angle_obtuse (GTS_SEGMENT (e)->v2, f)); +} + +static gdouble cotan (GtsVertex * vo, GtsVertex * v1, GtsVertex * v2) +{ + /* cf. Appendix B of [Meyer et al 2002] */ + GtsVector u, v; + gdouble udotv, denom; + + gts_vector_init (u, GTS_POINT (vo), GTS_POINT (v1)); + gts_vector_init (v, GTS_POINT (vo), GTS_POINT (v2)); + + udotv = gts_vector_scalar (u, v); + denom = sqrt (gts_vector_scalar (u,u)*gts_vector_scalar (v,v) - + udotv*udotv); + + + /* denom can be zero if u==v. Returning 0 is acceptable, based on + * the callers of this function below. */ + if (denom == 0.0) return (0.0); + + return (udotv/denom); +} + +static gdouble angle_from_cotan (GtsVertex * vo, + GtsVertex * v1, GtsVertex * v2) +{ + /* cf. Appendix B and the caption of Table 1 from [Meyer et al 2002] */ + GtsVector u, v; + gdouble udotv, denom; + + gts_vector_init (u, GTS_POINT (vo), GTS_POINT (v1)); + gts_vector_init (v, GTS_POINT (vo), GTS_POINT (v2)); + + udotv = gts_vector_scalar (u, v); + denom = sqrt (gts_vector_scalar (u,u)*gts_vector_scalar (v,v) + - udotv*udotv); + + /* Note: I assume this is what they mean by using atan2 (). -Ray Jones */ + + /* tan = denom/udotv = y/x (see man page for atan2) */ + return (fabs (atan2 (denom, udotv))); +} + +static gdouble region_area (GtsVertex * v, GtsFace * f) +{ + /* cf. Section 3.3 of [Meyer et al 2002] */ + + if (gts_triangle_area (GTS_TRIANGLE (f)) == 0.0) return (0.0); + + if (triangle_obtuse (v, f)) { + if (angle_obtuse (v, f)) + return (gts_triangle_area (GTS_TRIANGLE (f))/2.0); + else + return (gts_triangle_area (GTS_TRIANGLE (f))/4.0); + } else { + GtsEdge * e = gts_triangle_edge_opposite (GTS_TRIANGLE (f), v); + + return ((cotan (GTS_SEGMENT (e)->v1, v, GTS_SEGMENT (e)->v2)* + gts_point_distance2 (GTS_POINT (v), + GTS_POINT (GTS_SEGMENT (e)->v2)) + + cotan (GTS_SEGMENT (e)->v2, v, GTS_SEGMENT (e)->v1)* + gts_point_distance2 (GTS_POINT (v), + GTS_POINT (GTS_SEGMENT (e)->v1))) + /8.0); + } +} + +/** + * gts_vertex_mean_curvature_normal: + * @v: a #GtsVertex. + * @s: a #GtsSurface. + * @Kh: the Mean Curvature Normal at @v. + * + * Computes the Discrete Mean Curvature Normal approximation at @v. + * The mean curvature at @v is half the magnitude of the vector @Kh. + * + * Note: the normal computed is not unit length, and may point either + * into or out of the surface, depending on the curvature at @v. It + * is the responsibility of the caller of the function to use the mean + * curvature normal appropriately. + * + * This approximation is from the paper: + * Discrete Differential-Geometry Operators for Triangulated 2-Manifolds + * Mark Meyer, Mathieu Desbrun, Peter Schroder, Alan H. Barr + * VisMath '02, Berlin (Germany) + * http://www-grail.usc.edu/pubs.html + * + * Returns: %TRUE if the operator could be evaluated, %FALSE if the + * evaluation failed for some reason (@v is boundary or is the + * endpoint of a non-manifold edge.) + */ +gboolean gts_vertex_mean_curvature_normal (GtsVertex * v, GtsSurface * s, + GtsVector Kh) +{ + GSList * faces, * edges, * i; + gdouble area = 0.0; + + g_return_val_if_fail (v != NULL, FALSE); + g_return_val_if_fail (s != NULL, FALSE); + + /* this operator is not defined for boundary edges */ + if (gts_vertex_is_boundary (v, s)) return (FALSE); + + faces = gts_vertex_faces (v, s, NULL); + g_return_val_if_fail (faces != NULL, FALSE); + + edges = gts_vertex_fan_oriented (v, s); + if (edges == NULL) { + g_slist_free (faces); + return (FALSE); + } + + i = faces; + while (i) { + GtsFace * f = i->data; + + area += region_area (v, f); + i = i->next; + } + g_slist_free (faces); + + Kh[0] = Kh[1] = Kh[2] = 0.0; + + i = edges; + while (i) { + GtsEdge * e = i->data; + GtsVertex * v1 = GTS_SEGMENT (e)->v1; + GtsVertex * v2 = GTS_SEGMENT (e)->v2; + gdouble temp; + + temp = cotan (v1, v, v2); + Kh[0] += temp*(GTS_POINT (v2)->x - GTS_POINT (v)->x); + Kh[1] += temp*(GTS_POINT (v2)->y - GTS_POINT (v)->y); + Kh[2] += temp*(GTS_POINT (v2)->z - GTS_POINT (v)->z); + + temp = cotan (v2, v, v1); + Kh[0] += temp*(GTS_POINT (v1)->x - GTS_POINT (v)->x); + Kh[1] += temp*(GTS_POINT (v1)->y - GTS_POINT (v)->y); + Kh[2] += temp*(GTS_POINT (v1)->z - GTS_POINT (v)->z); + + i = i->next; + } + g_slist_free (edges); + + if (area > 0.0) { + Kh[0] /= 2*area; + Kh[1] /= 2*area; + Kh[2] /= 2*area; + } else { + return (FALSE); + } + + return TRUE; +} + +/** + * gts_vertex_gaussian_curvature: + * @v: a #GtsVertex. + * @s: a #GtsSurface. + * @Kg: the Discrete Gaussian Curvature approximation at @v. + * + * Computes the Discrete Gaussian Curvature approximation at @v. + * + * This approximation is from the paper: + * Discrete Differential-Geometry Operators for Triangulated 2-Manifolds + * Mark Meyer, Mathieu Desbrun, Peter Schroder, Alan H. Barr + * VisMath '02, Berlin (Germany) + * http://www-grail.usc.edu/pubs.html + * + * Returns: %TRUE if the operator could be evaluated, %FALSE if the + * evaluation failed for some reason (@v is boundary or is the + * endpoint of a non-manifold edge.) + */ +gboolean gts_vertex_gaussian_curvature (GtsVertex * v, GtsSurface * s, + gdouble * Kg) +{ + GSList * faces, * edges, * i; + gdouble area = 0.0; + gdouble angle_sum = 0.0; + + g_return_val_if_fail (v != NULL, FALSE); + g_return_val_if_fail (s != NULL, FALSE); + g_return_val_if_fail (Kg != NULL, FALSE); + + /* this operator is not defined for boundary edges */ + if (gts_vertex_is_boundary (v, s)) return (FALSE); + + faces = gts_vertex_faces (v, s, NULL); + g_return_val_if_fail (faces != NULL, FALSE); + + edges = gts_vertex_fan_oriented (v, s); + if (edges == NULL) { + g_slist_free (faces); + return (FALSE); + } + + i = faces; + while (i) { + GtsFace * f = i->data; + + area += region_area (v, f); + i = i->next; + } + g_slist_free (faces); + + i = edges; + while (i) { + GtsEdge * e = i->data; + GtsVertex * v1 = GTS_SEGMENT (e)->v1; + GtsVertex * v2 = GTS_SEGMENT (e)->v2; + + angle_sum += angle_from_cotan (v, v1, v2); + i = i->next; + } + g_slist_free (edges); + + *Kg = (2.0*M_PI - angle_sum)/area; + + return TRUE; +} + +/** + * gts_vertex_principal_curvatures: + * @Kh: mean curvature. + * @Kg: Gaussian curvature. + * @K1: first principal curvature. + * @K2: second principal curvature. + * + * Computes the principal curvatures at a point given the mean and + * Gaussian curvatures at that point. + * + * The mean curvature can be computed as one-half the magnitude of the + * vector computed by gts_vertex_mean_curvature_normal(). + * + * The Gaussian curvature can be computed with + * gts_vertex_gaussian_curvature(). + */ +void gts_vertex_principal_curvatures (gdouble Kh, gdouble Kg, + gdouble * K1, gdouble * K2) +{ + gdouble temp = Kh*Kh - Kg; + + g_return_if_fail (K1 != NULL); + g_return_if_fail (K2 != NULL); + + if (temp < 0.0) temp = 0.0; + temp = sqrt (temp); + *K1 = Kh + temp; + *K2 = Kh - temp; +} + +/* from Maple */ +static void linsolve (gdouble m11, gdouble m12, gdouble b1, + gdouble m21, gdouble m22, gdouble b2, + gdouble * x1, gdouble * x2) +{ + gdouble temp; + + temp = 1.0 / (m21*m12 - m11*m22); + *x1 = (m12*b2 - m22*b1)*temp; + *x2 = (m11*b2 - m21*b1)*temp; +} + +/* from Maple - largest eigenvector of [a b; b c] */ +static void eigenvector (gdouble a, gdouble b, gdouble c, + GtsVector e) +{ + if (b == 0.0) { + e[0] = 0.0; + } else { + e[0] = -(c - a - sqrt (c*c - 2*a*c + a*a + 4*b*b))/(2*b); + } + e[1] = 1.0; + e[2] = 0.0; +} + +/** + * gts_vertex_principal_directions: + * @v: a #GtsVertex. + * @s: a #GtsSurface. + * @Kh: mean curvature normal (a #GtsVector). + * @Kg: Gaussian curvature (a gdouble). + * @e1: first principal curvature direction (direction of largest curvature). + * @e2: second principal curvature direction. + * + * Computes the principal curvature directions at a point given @Kh + * and @Kg, the mean curvature normal and Gaussian curvatures at that + * point, computed with gts_vertex_mean_curvature_normal() and + * gts_vertex_gaussian_curvature(), respectively. + * + * Note that this computation is very approximate and tends to be + * unstable. Smoothing of the surface or the principal directions may + * be necessary to achieve reasonable results. + */ +void gts_vertex_principal_directions (GtsVertex * v, GtsSurface * s, + GtsVector Kh, gdouble Kg, + GtsVector e1, GtsVector e2) +{ + GtsVector N; + gdouble normKh; + GSList * i, * j; + GtsVector basis1, basis2, d, eig; + gdouble ve2, vdotN; + gdouble aterm_da, bterm_da, cterm_da, const_da; + gdouble aterm_db, bterm_db, cterm_db, const_db; + gdouble a, b, c; + gdouble K1, K2; + gdouble *weights, *kappas, *d1s, *d2s; + gint edge_count; + gdouble err_e1, err_e2; + int e; + + /* compute unit normal */ + normKh = sqrt (gts_vector_scalar (Kh, Kh)); + + if (normKh > 0.0) { + N[0] = Kh[0] / normKh; + N[1] = Kh[1] / normKh; + N[2] = Kh[2] / normKh; + } else { + /* This vertex is a point of zero mean curvature (flat or saddle + * point). Compute a normal by averaging the adjacent triangles + */ + N[0] = N[1] = N[2] = 0.0; + i = gts_vertex_faces (v, s, NULL); + while (i) { + gdouble x, y, z; + gts_triangle_normal (GTS_TRIANGLE ((GtsFace *) i->data), + &x, &y, &z); + N[0] += x; + N[1] += y; + N[2] += z; + + i = i->next; + } + g_return_if_fail (gts_vector_norm (N) > 0.0); + gts_vector_normalize (N); + } + + + /* construct a basis from N: */ + /* set basis1 to any component not the largest of N */ + basis1[0] = basis1[1] = basis1[2] = 0.0; + if (fabs (N[0]) > fabs (N[1])) + basis1[1] = 1.0; + else + basis1[0] = 1.0; + + /* make basis2 orthogonal to N */ + gts_vector_cross (basis2, N, basis1); + gts_vector_normalize (basis2); + + /* make basis1 orthogonal to N and basis2 */ + gts_vector_cross (basis1, N, basis2); + gts_vector_normalize (basis1); + + aterm_da = bterm_da = cterm_da = const_da = 0.0; + aterm_db = bterm_db = cterm_db = const_db = 0.0; + + weights = g_malloc (sizeof (gdouble)*g_slist_length (v->segments)); + kappas = g_malloc (sizeof (gdouble)*g_slist_length (v->segments)); + d1s = g_malloc (sizeof (gdouble)*g_slist_length (v->segments)); + d2s = g_malloc (sizeof (gdouble)*g_slist_length (v->segments)); + edge_count = 0; + + i = v->segments; + while (i) { + GtsEdge * e; + GtsFace * f1, * f2; + gdouble weight, kappa, d1, d2; + GtsVector vec_edge; + + if (! GTS_IS_EDGE (i->data)) { + i = i->next; + continue; + } + + e = i->data; + + /* since this vertex passed the tests in + * gts_vertex_mean_curvature_normal(), this should be true. */ + g_assert (gts_edge_face_number (e, s) == 2); + + /* identify the two triangles bordering e in s */ + f1 = f2 = NULL; + j = e->triangles; + while (j) { + if ((! GTS_IS_FACE (j->data)) || + (! gts_face_has_parent_surface (GTS_FACE (j->data), s))) { + j = j->next; + continue; + } + if (f1 == NULL) + f1 = GTS_FACE (j->data); + else { + f2 = GTS_FACE (j->data); + break; + } + j = j->next; + } + g_assert (f2 != NULL); + + /* We are solving for the values of the curvature tensor + * B = [ a b ; b c ]. + * The computations here are from section 5 of [Meyer et al 2002]. + * + * The first step is to calculate the linear equations governing + * the values of (a,b,c). These can be computed by setting the + * derivatives of the error E to zero (section 5.3). + * + * Since a + c = norm(Kh), we only compute the linear equations + * for dE/da and dE/db. (NB: [Meyer et al 2002] has the + * equation a + b = norm(Kh), but I'm almost positive this is + * incorrect.) + * + * Note that the w_ij (defined in section 5.2) are all scaled by + * (1/8*A_mixed). We drop this uniform scale factor because the + * solution of the linear equations doesn't rely on it. + * + * The terms of the linear equations are xterm_dy with x in + * {a,b,c} and y in {a,b}. There are also const_dy terms that are + * the constant factors in the equations. + */ + + /* find the vector from v along edge e */ + gts_vector_init (vec_edge, GTS_POINT (v), + GTS_POINT ((GTS_SEGMENT (e)->v1 == v) ? + GTS_SEGMENT (e)->v2 : GTS_SEGMENT (e)->v1)); + ve2 = gts_vector_scalar (vec_edge, vec_edge); + vdotN = gts_vector_scalar (vec_edge, N); + + /* section 5.2 - There is a typo in the computation of kappa. The + * edges should be x_j-x_i. + */ + kappa = 2.0 * vdotN / ve2; + + /* section 5.2 */ + + /* I don't like performing a minimization where some of the + * weights can be negative (as can be the case if f1 or f2 are + * obtuse). To ensure all-positive weights, we check for + * obtuseness and use values similar to those in region_area(). */ + weight = 0.0; + if (! triangle_obtuse(v, f1)) { + weight += ve2 * + cotan (gts_triangle_vertex_opposite (GTS_TRIANGLE (f1), e), + GTS_SEGMENT (e)->v1, GTS_SEGMENT (e)->v2) / 8.0; + } else { + if (angle_obtuse (v, f1)) { + weight += ve2 * gts_triangle_area (GTS_TRIANGLE (f1)) / 4.0; + } else { + weight += ve2 * gts_triangle_area (GTS_TRIANGLE (f1)) / 8.0; + } + } + + if (! triangle_obtuse(v, f2)) { + weight += ve2 * + cotan (gts_triangle_vertex_opposite (GTS_TRIANGLE (f2), e), + GTS_SEGMENT (e)->v1, GTS_SEGMENT (e)->v2) / 8.0; + } else { + if (angle_obtuse (v, f2)) { + weight += ve2 * gts_triangle_area (GTS_TRIANGLE (f2)) / 4.0; + } else { + weight += ve2 * gts_triangle_area (GTS_TRIANGLE (f2)) / 8.0; + } + } + + /* projection of edge perpendicular to N (section 5.3) */ + d[0] = vec_edge[0] - vdotN * N[0]; + d[1] = vec_edge[1] - vdotN * N[1]; + d[2] = vec_edge[2] - vdotN * N[2]; + gts_vector_normalize (d); + + /* not explicit in the paper, but necessary. Move d to 2D basis. */ + d1 = gts_vector_scalar (d, basis1); + d2 = gts_vector_scalar (d, basis2); + + /* store off the curvature, direction of edge, and weights for later use */ + weights[edge_count] = weight; + kappas[edge_count] = kappa; + d1s[edge_count] = d1; + d2s[edge_count] = d2; + edge_count++; + + /* Finally, update the linear equations */ + aterm_da += weight * d1 * d1 * d1 * d1; + bterm_da += weight * d1 * d1 * 2 * d1 * d2; + cterm_da += weight * d1 * d1 * d2 * d2; + const_da += weight * d1 * d1 * (- kappa); + + aterm_db += weight * d1 * d2 * d1 * d1; + bterm_db += weight * d1 * d2 * 2 * d1 * d2; + cterm_db += weight * d1 * d2 * d2 * d2; + const_db += weight * d1 * d2 * (- kappa); + + i = i->next; + } + + /* now use the identity (Section 5.3) a + c = |Kh| = 2 * kappa_h */ + aterm_da -= cterm_da; + const_da += cterm_da * normKh; + + aterm_db -= cterm_db; + const_db += cterm_db * normKh; + + /* check for solvability of the linear system */ + if (((aterm_da * bterm_db - aterm_db * bterm_da) != 0.0) && + ((const_da != 0.0) || (const_db != 0.0))) { + linsolve (aterm_da, bterm_da, -const_da, + aterm_db, bterm_db, -const_db, + &a, &b); + + c = normKh - a; + + eigenvector (a, b, c, eig); + } else { + /* region of v is planar */ + eig[0] = 1.0; + eig[1] = 0.0; + } + + /* Although the eigenvectors of B are good estimates of the + * principal directions, it seems that which one is attached to + * which curvature direction is a bit arbitrary. This may be a bug + * in my implementation, or just a side-effect of the inaccuracy of + * B due to the discrete nature of the sampling. + * + * To overcome this behavior, we'll evaluate which assignment best + * matches the given eigenvectors by comparing the curvature + * estimates computed above and the curvatures calculated from the + * discrete differential operators. */ + + gts_vertex_principal_curvatures (0.5 * normKh, Kg, &K1, &K2); + + err_e1 = err_e2 = 0.0; + /* loop through the values previously saved */ + for (e = 0; e < edge_count; e++) { + gdouble weight, kappa, d1, d2; + gdouble temp1, temp2; + gdouble delta; + + weight = weights[e]; + kappa = kappas[e]; + d1 = d1s[e]; + d2 = d2s[e]; + + temp1 = fabs (eig[0] * d1 + eig[1] * d2); + temp1 = temp1 * temp1; + temp2 = fabs (eig[1] * d1 - eig[0] * d2); + temp2 = temp2 * temp2; + + /* err_e1 is for K1 associated with e1 */ + delta = K1 * temp1 + K2 * temp2 - kappa; + err_e1 += weight * delta * delta; + + /* err_e2 is for K1 associated with e2 */ + delta = K2 * temp1 + K1 * temp2 - kappa; + err_e2 += weight * delta * delta; + } + g_free (weights); + g_free (kappas); + g_free (d1s); + g_free (d2s); + + /* rotate eig by a right angle if that would decrease the error */ + if (err_e2 < err_e1) { + gdouble temp = eig[0]; + + eig[0] = eig[1]; + eig[1] = -temp; + } + + e1[0] = eig[0] * basis1[0] + eig[1] * basis2[0]; + e1[1] = eig[0] * basis1[1] + eig[1] * basis2[1]; + e1[2] = eig[0] * basis1[2] + eig[1] * basis2[2]; + gts_vector_normalize (e1); + + /* make N,e1,e2 a right handed coordinate sytem */ + gts_vector_cross (e2, N, e1); + gts_vector_normalize (e2); +} diff --git a/gts/edge.c b/gts/edge.c new file mode 100644 index 0000000000..708c06c969 --- /dev/null +++ b/gts/edge.c @@ -0,0 +1,582 @@ +/* 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 "gts.h" + +gboolean gts_allow_floating_edges = FALSE; + +static void edge_destroy (GtsObject * object) +{ + GtsEdge * edge = GTS_EDGE (object); + GSList * i; + + i = edge->triangles; + while (i) { + GSList * next = i->next; + gts_object_destroy (i->data); + i = next; + } + g_assert (edge->triangles == NULL); + + (* GTS_OBJECT_CLASS (gts_edge_class ())->parent_class->destroy) (object); +} + +static void edge_clone (GtsObject * clone, GtsObject * object) +{ + (* GTS_OBJECT_CLASS (gts_edge_class ())->parent_class->clone) (clone, + object); + GTS_SEGMENT (clone)->v1 = GTS_SEGMENT (clone)->v2 = NULL; + GTS_EDGE (clone)->triangles = NULL; +} + +static void edge_class_init (GtsObjectClass * klass) +{ + klass->clone = edge_clone; + klass->destroy = edge_destroy; +} + +static void edge_init (GtsEdge * edge) +{ + edge->triangles = NULL; +} + +/** + * gts_edge_class: + * + * Returns: the #GtsEdgeClass. + */ +GtsEdgeClass * gts_edge_class (void) +{ + static GtsEdgeClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo edge_info = { + "GtsEdge", + sizeof (GtsEdge), + sizeof (GtsEdgeClass), + (GtsObjectClassInitFunc) edge_class_init, + (GtsObjectInitFunc) edge_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_segment_class ()), + &edge_info); + } + + return klass; +} + +/** + * gts_edge_new: + * @klass: a #GtsEdgeClass. + * @v1: a #GtsVertex. + * @v2: a #GtsVertex. + * + * Returns: a new #GtsEdge linking @v1 and @v2. + */ +GtsEdge * gts_edge_new (GtsEdgeClass * klass, + GtsVertex * v1, GtsVertex * v2) +{ + return GTS_EDGE (gts_segment_new (GTS_SEGMENT_CLASS (klass), v1, v2)); +} + +void gts_edge_remove(GtsEdge *edge) +{ + edge->segment.v1->segments = g_slist_remove(edge->segment.v1->segments, &edge->segment); + edge->segment.v2->segments = g_slist_remove(edge->segment.v2->segments, &edge->segment); + edge_destroy(GTS_OBJECT (edge)); +} + +/** + * gts_edge_replace: + * @e: a #GtsEdge. + * @with: a #GtsEdge. + * + * Replaces @e with @with. For each triangle which uses @e as an + * edge, @e is replaced with @with. The @with->triangles list is + * updated appropriately and the @e->triangles list is freed and set + * to %NULL. + */ +void gts_edge_replace (GtsEdge * e, GtsEdge * with) +{ + GSList * i; + + g_return_if_fail (e != NULL && with != NULL && e != with); + + i = e->triangles; + while (i) { + GtsTriangle * t = i->data; + if (t->e1 == e) t->e1 = with; + if (t->e2 == e) t->e2 = with; + if (t->e3 == e) t->e3 = with; + if (!g_slist_find (with->triangles, t)) + with->triangles = g_slist_prepend (with->triangles, t); + i = i->next; + } + g_slist_free (e->triangles); + e->triangles = NULL; +} + +/** + * gts_edge_has_parent_surface: + * @e: a #GtsEdge. + * @surface: a #GtsSurface. + * + * Returns: a #GtsFace of @surface having @e as an edge, %NULL otherwise. + */ +GtsFace * gts_edge_has_parent_surface (GtsEdge * e, GtsSurface * surface) +{ + GSList * i; + + g_return_val_if_fail (e != NULL, NULL); + + i = e->triangles; + while (i) { + if (GTS_IS_FACE (i->data) && + gts_face_has_parent_surface (i->data, surface)) + return i->data; + i = i->next; + } + return NULL; +} + +/** + * gts_edge_has_any_parent_surface: + * @e: a #GtsEdge. + * + * Returns: %NULL if @e is not an edge of any triangle or if all the + * faces having @e has an edge do not belong to any surface, + * a #GtsFace belonging to a surface and having @e as an edge. + */ +GtsFace * gts_edge_has_any_parent_surface (GtsEdge * e) +{ + GSList * i; + + g_return_val_if_fail (e != NULL, NULL); + + i = e->triangles; + while (i) { + GtsTriangle * t = i->data; + if (GTS_IS_FACE (t) && GTS_FACE (t)->surfaces != NULL) + return GTS_FACE (t); + i = i->next; + } + return NULL; +} + +/** + * gts_edge_is_boundary: + * @e: a #GtsEdge. + * @surface: a #GtsSurface or %NULL. + * + * Returns: the unique #GtsFace (which belongs to @surface) and which + * has @e as an edge (i.e. @e is a boundary edge (of @surface)) or %NULL + * if there is more than one or no faces (belonging to @surface) and + * with @e as an edge. + */ +GtsFace * gts_edge_is_boundary (GtsEdge * e, GtsSurface * surface) +{ + GSList * i; + GtsFace * f = NULL; + + g_return_val_if_fail (e != NULL, NULL); + + i = e->triangles; + while (i) { + if (GTS_IS_FACE (i->data)) { + if (!surface || gts_face_has_parent_surface (i->data, surface)) { + if (f != NULL) + return NULL; + f = i->data; + } + } + i = i->next; + } + return f; +} + +/** + * gts_edges_from_vertices: + * @vertices: a list of #GtsVertex. + * @parent: a #GtsSurface. + * + * Returns: a list of unique #GtsEdge which have one of their vertices in + * @vertices and are used by a face of @parent. + */ +GSList * gts_edges_from_vertices (GSList * vertices, GtsSurface * parent) +{ + GHashTable * hash; + GSList * edges = NULL, * i; + + g_return_val_if_fail (parent != NULL, NULL); + + hash = g_hash_table_new (NULL, NULL); + i = vertices; + while (i) { + GSList * j = GTS_VERTEX (i->data)->segments; + while (j) { + GtsSegment * s = j->data; + if (GTS_IS_EDGE (s) && + gts_edge_has_parent_surface (GTS_EDGE (s), parent) && + g_hash_table_lookup (hash, s) == NULL) { + edges = g_slist_prepend (edges, s); + g_hash_table_insert (hash, s, i); + } + j = j->next; + } + i = i->next; + } + g_hash_table_destroy (hash); + return edges; +} + +/** + * gts_edge_face_number: + * @e: a #GtsEdge. + * @s: a #GtsSurface. + * + * Returns: the number of faces using @e and belonging to @s. + */ +guint gts_edge_face_number (GtsEdge * e, GtsSurface * s) +{ + GSList * i; + guint nt = 0; + + g_return_val_if_fail (e != NULL, 0); + g_return_val_if_fail (s != NULL, 0); + + i = e->triangles; + while (i) { + if (GTS_IS_FACE (i->data) && + gts_face_has_parent_surface (GTS_FACE (i->data), s)) + nt++; + i = i->next; + } + return nt; +} + +/** + * gts_edge_is_duplicate: + * @e: a #GtsEdge. + * + * Returns: the first #GtsEdge different from @e which shares the + * same endpoints or %NULL if there is none. + */ +GtsEdge * gts_edge_is_duplicate (GtsEdge * e) +{ + GSList * i; + GtsVertex * v2; + + g_return_val_if_fail (e != NULL, NULL); + + v2 = GTS_SEGMENT (e)->v2; + i = GTS_SEGMENT (e)->v1->segments; + if (GTS_SEGMENT (e)->v1 == v2) /* e is degenerate: special treatment */ + while (i) { + GtsSegment * s = i->data; + if (s != GTS_SEGMENT (e) && + GTS_IS_EDGE (s) && + s->v1 == v2 && s->v2 == v2) + return GTS_EDGE (s); + i = i->next; + } + else /* e is not degenerate */ + while (i) { + GtsSegment * s = i->data; + if (s != GTS_SEGMENT (e) && + GTS_IS_EDGE (s) && + (s->v1 == v2 || s->v2 == v2)) + return GTS_EDGE (s); + i = i->next; + } + return NULL; +} + +/** + * gts_edges_merge: + * @edges: a list of #GtsEdge. + * + * For each edge in @edges check if it is duplicated (as + * returned by gts_edge_is_duplicate()). If it is replace it by its + * duplicate, destroy it and remove it from the list. + * + * Returns: the updated @edges list. + */ +GList * gts_edges_merge (GList * edges) +{ + GList * i = edges; + + /* we want to control edge destruction */ + gts_allow_floating_edges = TRUE; + while (i) { + GtsEdge * e = i->data; + GtsEdge * de = gts_edge_is_duplicate (e); + if (de) { + GList * next = i->next; + edges = g_list_remove_link (edges, i); + g_list_free_1 (i); + i = next; + gts_edge_replace (e, de); + gts_object_destroy (GTS_OBJECT (e)); + } + else + i = i->next; + } + gts_allow_floating_edges = FALSE;; + + return edges; +} + +static void triangle_vertices_edges (GtsTriangle * t, + GtsEdge * e, + GtsVertex ** v, + GtsEdge ** ee1, + GtsEdge ** ee2) +{ + GtsEdge * e1 = t->e1, * e2 = t->e2, * e3 = t->e3; + GtsVertex * v1 = GTS_SEGMENT (e)->v1; + + if (e1 == e) e1 = e3; + else if (e2 == e) e2 = e3; + else g_assert (e3 == e); + + if (GTS_SEGMENT (e2)->v1 == v1 || GTS_SEGMENT (e2)->v2 == v1) { + e3 = e1; e1 = e2; e2 = e3; + } + if (GTS_SEGMENT (e1)->v1 == v1) + *v = GTS_SEGMENT (e1)->v2; + else + *v = GTS_SEGMENT (e1)->v1; + *ee1 = e1; + *ee2 = e2; +} + +/** + * gts_edge_belongs_to_tetrahedron: + * @e: a #GtsEdge. + * + * Returns: %TRUE if @e is used by faces forming a tetrahedron, %FALSE + * otherwise. + */ +gboolean gts_edge_belongs_to_tetrahedron (GtsEdge * e) +{ + GSList * i; + + g_return_val_if_fail (e != NULL, FALSE); + + i = e->triangles; + while (i) { + GtsEdge * e1, * e2; + GtsVertex * vt1; + GSList * j = i->next; + triangle_vertices_edges (i->data, e, &vt1, &e1, &e2); + while (j) { + GtsSegment * s5; + GtsEdge * e3, * e4; + GtsVertex * vt2; + + triangle_vertices_edges (j->data, e, &vt2, &e3, &e4); + s5 = gts_vertices_are_connected (vt1, vt2); + if (GTS_IS_EDGE (s5) && + gts_triangle_use_edges (e1, e3, GTS_EDGE (s5)) && + gts_triangle_use_edges (e2, e4, GTS_EDGE (s5))) + return TRUE; + j = j->next; + } + i = i->next; + } + + return FALSE; +} + +#define edge_use_vertex(e, v) (GTS_SEGMENT(e)->v1 == v ||\ + GTS_SEGMENT(e)->v2 == v) + +static GtsEdge * next_edge (GtsTriangle * t, + GtsEdge * e1, + GtsEdge * e) +{ + GtsVertex * v1 = GTS_SEGMENT (e)->v1; + GtsVertex * v2 = GTS_SEGMENT (e)->v2; + + if (t->e1 != e1 && t->e1 != e && + (edge_use_vertex (t->e1, v1) || edge_use_vertex (t->e1, v2))) + return t->e1; + else if (t->e2 != e1 && t->e2 != e && + (edge_use_vertex (t->e2, v1) || edge_use_vertex (t->e2, v2))) + return t->e2; + else if (t->e3 != e1 && t->e3 != e && + (edge_use_vertex (t->e3, v1) || edge_use_vertex (t->e3, v2))) + return t->e3; + g_assert_not_reached (); + return NULL; +} + +static void triangle_next (GtsEdge * e1, GtsEdge * e) +{ + GSList * i; + + i = e1->triangles; + while (i) { + GtsTriangle * t = i->data; + if (GTS_OBJECT (t)->reserved) { + GTS_OBJECT (t)->reserved = NULL; + triangle_next (next_edge (t, e1, e), e); + } + i = i->next; + } +} + +/** + * gts_edge_is_contact: + * @e: a #GtsEdge. + * + * Returns: the number of sets of connected triangles sharing @e as a + * contact edge. + */ +guint gts_edge_is_contact (GtsEdge * e) +{ + GSList * i, * triangles; + guint ncomponent = 0; + + g_return_val_if_fail (e != NULL, 0); + + triangles = gts_vertex_triangles (GTS_SEGMENT (e)->v1, NULL); + i = triangles = gts_vertex_triangles (GTS_SEGMENT (e)->v2, triangles); + while (i) { + GTS_OBJECT (i->data)->reserved = i; + i = i->next; + } + + i = e->triangles; + while (i) { + GtsTriangle * t = i->data; + if (GTS_OBJECT (t)->reserved) { + GtsEdge * e1; + GTS_OBJECT (t)->reserved = NULL; + e1 = next_edge (t, NULL, e); + triangle_next (e1, e); + triangle_next (next_edge (t, e1, e), e); + ncomponent++; + } + i = i->next; + } + + g_slist_foreach (triangles, (GFunc) gts_object_reset_reserved, NULL); + g_slist_free (triangles); + + return ncomponent; +} + +/** + * gts_edge_swap: + * @e: a #GtsEdge. + * @s: a #GtsSurface. + * + * Performs an "edge swap" on the two triangles sharing @e and + * belonging to @s. + */ +void gts_edge_swap (GtsEdge * e, GtsSurface * s) +{ + GtsTriangle * t1 = NULL, * t2 = NULL, * t; + GtsFace * f; + GSList * i; + GtsVertex * v1, * v2, * v3, * v4, * v5, * v6; + GtsEdge * e1, * e2, * e3, * e4; + GtsSegment * v3v6; + + g_return_if_fail (e != NULL); + g_return_if_fail (s != NULL); + + i = e->triangles; + while (i) { + if (GTS_IS_FACE (i->data) && gts_face_has_parent_surface (i->data, s)) { + if (!t1) + t1 = i->data; + else if (!t2) + t2 = i->data; + else + g_return_if_fail (gts_edge_face_number (e, s) == 2); + } + i = i->next; + } + g_assert (t1 && t2); + + gts_triangle_vertices_edges (t1, e, &v1, &v2, &v3, &e, &e1, &e2); + gts_triangle_vertices_edges (t2, e, &v4, &v5, &v6, &e, &e3, &e4); + g_assert (v2 == v4 && v1 == v5); + + v3v6 = gts_vertices_are_connected (v3, v6); + if (!GTS_IS_EDGE (v3v6)) + v3v6 = GTS_SEGMENT (gts_edge_new (s->edge_class, v3, v6)); + f = gts_face_new (s->face_class, e1, GTS_EDGE (v3v6), e4); + if ((t = gts_triangle_is_duplicate (GTS_TRIANGLE (f))) && + GTS_IS_FACE (t)) { + gts_object_destroy (GTS_OBJECT (f)); + f = GTS_FACE (t); + } + gts_surface_add_face (s, f); + + f = gts_face_new (s->face_class, GTS_EDGE (v3v6), e2, e3); + if ((t = gts_triangle_is_duplicate (GTS_TRIANGLE (f))) && + GTS_IS_FACE (t)) { + gts_object_destroy (GTS_OBJECT (f)); + f = GTS_FACE (t); + } + gts_surface_add_face (s, f); + + gts_surface_remove_face (s, GTS_FACE (t1)); + gts_surface_remove_face (s, GTS_FACE (t2)); +} + +/** + * gts_edge_manifold_faces: + * @e: a #GtsEdge. + * @s: a #GtsSurface. + * @f1: pointer for first face. + * @f2: pointer for second face. + * + * If @e is a manifold edge of surface @s, fills @f1 and @f2 with the + * faces belonging to @s and sharing @e. + * + * Returns: %TRUE if @e is a manifold edge, %FALSE otherwise. + */ +gboolean gts_edge_manifold_faces (GtsEdge * e, GtsSurface * s, + GtsFace ** f1, GtsFace ** f2) +{ + GSList * i; + + g_return_val_if_fail (e != NULL, FALSE); + g_return_val_if_fail (s != NULL, FALSE); + g_return_val_if_fail (f1 != NULL, FALSE); + g_return_val_if_fail (f2 != NULL, FALSE); + + *f1 = *f2 = NULL; + i = e->triangles; + while (i) { + if (GTS_IS_FACE (i->data) && gts_face_has_parent_surface (i->data, s)) { + if (!(*f1)) *f1 = i->data; + else if (!(*f2)) *f2 = i->data; + else return FALSE; + } + i = i->next; + } + + return (*f1 && *f2); +} diff --git a/gts/eheap.c b/gts/eheap.c new file mode 100644 index 0000000000..29f462dc3d --- /dev/null +++ b/gts/eheap.c @@ -0,0 +1,461 @@ +/* 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 "gts.h" + +#define PARENT(i) ((i) >= 2 ? (i)/2 : 0) +#define LEFT_CHILD(i) (2*(i)) +#define RIGHT_CHILD(i) (2*(i) + 1) + + +/** + * gts_eheap_new: + * @key_func: a #GtsKeyFunc or %NULL. + * @data: user data to be passed to @key_func. + * + * Returns: a new #GtsEHeap using @key_func as key. + */ +GtsEHeap * gts_eheap_new (GtsKeyFunc key_func, + gpointer data) +{ + GtsEHeap * heap; + + heap = g_malloc (sizeof(GtsEHeap)); + heap->elts = g_ptr_array_new (); + heap->func = key_func; + heap->data = data; + heap->frozen = FALSE; + heap->randomized = FALSE; + return heap; +} + +static void sift_up (GtsEHeap * heap, guint i) +{ + GtsEHeapPair * parent, * child; + guint p; + gpointer * pdata = heap->elts->pdata; + gdouble key; + + child = pdata[i - 1]; + key = child->key; + while ((p = PARENT (i))) { + parent = pdata[p - 1]; + if (parent->key > key || + (heap->randomized && parent->key == key && rand () < RAND_MAX/2)) { + pdata[p - 1] = child; + pdata[i - 1] = parent; + child->pos = p; + parent->pos = i; + i = p; + } + else + i = 0; + } +} + +/** + * gts_eheap_insert: + * @heap: a #GtsEHeap. + * @p: a pointer to add to the heap. + * + * Inserts a new element @p in the heap. + * + * Returns: a #GtsEHeapPair describing the position of the element in the heap. + * This pointer is necessary for gts_eheap_remove() and + * gts_eheap_decrease_key(). + */ +GtsEHeapPair * gts_eheap_insert (GtsEHeap * heap, gpointer p) +{ + GtsEHeapPair * pair; + GPtrArray * elts; + + g_return_val_if_fail (heap != NULL, NULL); + g_return_val_if_fail (heap->func != NULL, NULL); + + elts = heap->elts; + pair = g_malloc (sizeof (GtsEHeapPair)); + g_ptr_array_add (elts, pair); + pair->data = p; + pair->pos = elts->len; + pair->key = (*heap->func) (p, heap->data); + if (!heap->frozen) + sift_up (heap, elts->len); + return pair; +} + +/** + * gts_eheap_insert_with_key: + * @heap: a #GtsEHeap. + * @p: a pointer to add to the heap. + * @key: the value of the key associated to @p. + * + * Inserts a new element @p in the heap. + * + * Returns: a #GtsEHeapPair describing the position of the element in the heap. + * This pointer is necessary for gts_eheap_remove() and + * gts_eheap_decrease_key(). + */ +GtsEHeapPair * gts_eheap_insert_with_key (GtsEHeap * heap, + gpointer p, + gdouble key) +{ + GtsEHeapPair * pair; + GPtrArray * elts; + + g_return_val_if_fail (heap != NULL, NULL); + + elts = heap->elts; + pair = g_malloc (sizeof (GtsEHeapPair)); + g_ptr_array_add (elts, pair); + pair->data = p; + pair->pos = elts->len; + pair->key = key; + if (!heap->frozen) + sift_up (heap, elts->len); + return pair; +} + +static void sift_down (GtsEHeap * heap, guint i) +{ + GtsEHeapPair * left_child, * right_child, * child, * parent; + guint lc, rc, c; + gpointer * pdata = heap->elts->pdata; + guint len = heap->elts->len; + gdouble key; + + lc = LEFT_CHILD (i); + rc = RIGHT_CHILD (i); + left_child = lc <= len ? pdata[lc - 1] : NULL; + right_child = rc <= len ? pdata[rc - 1] : NULL; + + parent = pdata[i - 1]; + key = parent->key; + while (left_child != NULL) { + if (right_child == NULL || left_child->key < right_child->key) { + child = left_child; + c = lc; + } + else { + child = right_child; + c = rc; + } + if (key > child->key) { + pdata[i - 1] = child; + child->pos = i; + pdata[c - 1] = parent; + parent->pos = c; + i = c; + lc = LEFT_CHILD (i); + rc = RIGHT_CHILD (i); + left_child = lc <= len ? pdata[lc - 1] : NULL; + right_child = rc <= len ? pdata[rc - 1] : NULL; + } + else + left_child = NULL; + } +} + +/** + * gts_eheap_remove_top: + * @heap: a #GtsEHeap. + * @key: a pointer on a gdouble or %NULL. + * + * Removes the element at the top of the heap and optionally (if @key is not + * %NULL) returns the value of its key. + * + * Returns: the element at the top of the heap. + */ +gpointer gts_eheap_remove_top (GtsEHeap * heap, gdouble * key) +{ + gpointer root; + GPtrArray * elts; + guint len; + GtsEHeapPair * pair; + + g_return_val_if_fail (heap != NULL, NULL); + + elts = heap->elts; + len = elts->len; + + if (len == 0) + return NULL; + if (len == 1) { + pair = g_ptr_array_remove_index (elts, 0); + root = pair->data; + if (key) + *key = pair->key; + g_free (pair); + return root; + } + + pair = elts->pdata[0]; + root = pair->data; + if (key) + *key = pair->key; + g_free (pair); + pair = g_ptr_array_remove_index (elts, len - 1); + elts->pdata[0] = pair; + pair->pos = 1; + sift_down (heap, 1); + return root; +} + +/** + * gts_eheap_top: + * @heap: a #GtsEHeap. + * @key: a pointer on a gdouble or %NULL. + * + * Returns: the element at the top of the heap and optionally (if @key is not + * %NULL) its key. + */ +gpointer gts_eheap_top (GtsEHeap * heap, gdouble * key) +{ + GtsEHeapPair * pair; + GPtrArray * elts; + + g_return_val_if_fail (heap != NULL, NULL); + + elts = heap->elts; + + if (elts->len == 0) + return NULL; + + pair = elts->pdata[0]; + if (key) + *key = pair->key; + return pair->data; +} + +/** + * gts_eheap_destroy: + * @heap: a #GtsEHeap. + * + * Free all the memory allocated for @heap. + */ +void gts_eheap_destroy (GtsEHeap * heap) +{ + guint i; + + g_return_if_fail (heap != NULL); + + for (i = 0; i < heap->elts->len; i++) + g_free (heap->elts->pdata[i]); + g_ptr_array_free (heap->elts, TRUE); + g_free (heap); +} + +/** + * gts_eheap_thaw: + * @heap: a #GtsEHeap. + * + * If @heap has been frozen previously using gts_eheap_freeze(), reorder it + * in O(n) time and unfreeze it. + */ +void gts_eheap_thaw (GtsEHeap * heap) +{ + guint i; + + g_return_if_fail (heap != NULL); + + if (!heap->frozen) + return; + + for (i = heap->elts->len/2; i > 0; i--) + sift_down (heap, i); + + heap->frozen = FALSE; +} + +/** + * gts_eheap_foreach: + * @heap: a #GtsEHeap. + * @func: the function to call for each element in the heap. + * @data: to pass to @func. + */ +void gts_eheap_foreach (GtsEHeap * heap, + GFunc func, + gpointer data) +{ + guint i; + GPtrArray * elts; + + g_return_if_fail (heap != NULL); + g_return_if_fail (func != NULL); + + elts = heap->elts; + for (i = 0; i < elts->len; i++) + (*func) (((GtsEHeapPair *) elts->pdata[i])->data, data); +} + +/** + * gts_eheap_remove: + * @heap: a #GtsEHeap. + * @p: a #GtsEHeapPair. + * + * Removes element corresponding to @p from @heap in O(log n). + * + * Returns: the element just removed from @heap. + */ +gpointer gts_eheap_remove (GtsEHeap * heap, GtsEHeapPair * p) +{ + GtsEHeapPair ** pdata; + GtsEHeapPair * parent; + guint i, par; + gpointer data; + + g_return_val_if_fail (heap != NULL, NULL); + g_return_val_if_fail (p != NULL, NULL); + + pdata = (GtsEHeapPair **)heap->elts->pdata; + i = p->pos; + data = p->data; + + g_return_val_if_fail (i > 0 && i <= heap->elts->len, NULL); + g_return_val_if_fail (p == pdata[i - 1], NULL); + + /* move element to the top */ + while ((par = PARENT (i))) { + parent = pdata[par - 1]; + pdata[par - 1] = p; + pdata[i - 1] = parent; + p->pos = par; + parent->pos = i; + i = par; + } + + gts_eheap_remove_top (heap, NULL); + + return data; +} + +/** + * gts_eheap_decrease_key: + * @heap: a #GtsEHeap. + * @p: a #GtsEHeapPair. + * @new_key: the new value of the key for this element. Must be smaller than + * the current key. + * + * Decreases the value of the key of the element at position @p. + */ +void gts_eheap_decrease_key (GtsEHeap * heap, + GtsEHeapPair * p, + gdouble new_key) +{ + guint i; + + g_return_if_fail (heap != NULL); + g_return_if_fail (p != NULL); + + i = p->pos; + g_return_if_fail (i > 0 && i <= heap->elts->len); + g_return_if_fail (p == heap->elts->pdata[i - 1]); + + g_return_if_fail (new_key <= p->key); + + p->key = new_key; + if (!heap->frozen) + sift_up (heap, i); +} + +/** + * gts_eheap_freeze: + * @heap: a #GtsEHeap. + * + * Freezes the heap. Any subsequent operation will not preserve the heap + * property. Used in conjunction with gts_eheap_insert() and gts_eheap_thaw() + * to create a heap in O(n) time. + */ +void gts_eheap_freeze (GtsEHeap * heap) +{ + g_return_if_fail (heap != NULL); + + heap->frozen = TRUE; +} + +/** + * gts_eheap_size: + * @heap: a #GtsEHeap. + * + * Returns: the number of items in @heap. + */ +guint gts_eheap_size (GtsEHeap * heap) +{ + g_return_val_if_fail (heap != NULL, 0); + + return heap->elts->len; +} + +/** + * gts_eheap_update: + * @heap: a #GtsEHeap. + * + * Updates the key of each element of @heap and reorders it. + */ +void gts_eheap_update (GtsEHeap * heap) +{ + guint i, len; + GtsEHeapPair ** pairs; + gpointer data; + GtsKeyFunc func; + + g_return_if_fail (heap != NULL); + g_return_if_fail (heap->func != NULL); + + heap->frozen = TRUE; + + len = heap->elts->len; + pairs = (GtsEHeapPair **) heap->elts->pdata; + data = heap->data; + func = heap->func; + + for (i = 0; i < len; i++) { + GtsEHeapPair * pair = pairs[i]; + pair->key = (*func) (pair->data, data); + } + + gts_eheap_thaw (heap); +} + +/** + * gts_eheap_key: + * @heap: a #GtsEHeap. + * @p: a pointer to be tested; + * + * Returns: the value of the key for pointer @p. + */ +gdouble gts_eheap_key (GtsEHeap * heap, gpointer p) +{ + g_return_val_if_fail (heap != NULL, 0.); + g_return_val_if_fail (heap->func != NULL, 0.); + + return (* heap->func) (p, heap->data); +} + +/** + * gts_eheap_randomized: + * @heap: a #GtsEHeap. + * @randomized: whether @heap should be randomized. + */ +void gts_eheap_randomized (GtsEHeap * heap, gboolean randomized) +{ + g_return_if_fail (heap != NULL); + + heap->randomized = randomized; +} diff --git a/gts/face.c b/gts/face.c new file mode 100644 index 0000000000..f6009f1a17 --- /dev/null +++ b/gts/face.c @@ -0,0 +1,297 @@ +/* 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 "gts.h" + +gboolean gts_allow_floating_faces = FALSE; + +static void face_destroy (GtsObject * object) +{ + GtsFace * face = GTS_FACE (object); + GSList * i; + + i = face->surfaces; + while (i) { + GSList * next = i->next; + gts_surface_remove_face (i->data, face); + i = next; + } + g_assert (face->surfaces == NULL); + + (* GTS_OBJECT_CLASS (gts_face_class ())->parent_class->destroy) (object); +} + +static void face_clone (GtsObject * clone, GtsObject * object) +{ + (* GTS_OBJECT_CLASS (gts_face_class ())->parent_class->clone) (clone, + object); + GTS_FACE (clone)->surfaces = NULL; +} + +static void face_class_init (GtsFaceClass * klass) +{ + GTS_OBJECT_CLASS (klass)->clone = face_clone; + GTS_OBJECT_CLASS (klass)->destroy = face_destroy; +} + +static void face_init (GtsFace * face) +{ + face->surfaces = NULL; +} + +/** + * gts_face_class: + * + * Returns: the #GtsFaceClass. + */ +GtsFaceClass * gts_face_class (void) +{ + static GtsFaceClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo face_info = { + "GtsFace", + sizeof (GtsFace), + sizeof (GtsFaceClass), + (GtsObjectClassInitFunc) face_class_init, + (GtsObjectInitFunc) face_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_triangle_class ()), + &face_info); + } + + return klass; +} + +/** + * gts_face_new: + * @klass: a #GtsFaceClass. + * @e1: a #GtsEdge. + * @e2: a #GtsEdge. + * @e3: a #GtsEdge. + * + * Returns: a new #GtsFace using @e1, @e2 and @e3 as edges. + */ +GtsFace * gts_face_new (GtsFaceClass * klass, + GtsEdge * e1, GtsEdge * e2, GtsEdge * e3) +{ + GtsFace * f; + + f = GTS_FACE (gts_object_new (GTS_OBJECT_CLASS (klass))); + gts_triangle_set (GTS_TRIANGLE (f), e1, e2, e3); + + return f; +} + +/** + * gts_face_has_parent_surface: + * @f: a #GtsFace. + * @s: a #GtsSurface. + * + * Returns: %TRUE if @f belongs to @s, %FALSE otherwise. + */ +gboolean gts_face_has_parent_surface (GtsFace * f, GtsSurface * s) +{ + GSList * i; + + g_return_val_if_fail (f != NULL, FALSE); + + i = f->surfaces; + while (i) { + if (i->data == s) + return TRUE; + i = i->next; + } + return FALSE; +} + +/** + * gts_faces_from_edges: + * @edges: a list of #GtsEdge. + * @s: a #GtsSurface or %NULL. + * + * Builds a list of unique faces which belong to @s and have + * one of their edges in @edges. + * + * Returns: the list of faces. + */ +GSList * gts_faces_from_edges (GSList * edges, GtsSurface * s) +{ + GHashTable * hash; + GSList * faces = NULL, * i; + + hash = g_hash_table_new (NULL, NULL); + i = edges; + while (i) { + GSList * j = GTS_EDGE (i->data)->triangles; + while (j) { + GtsTriangle * t = j->data; + if (GTS_IS_FACE (t) && + (!s || gts_face_has_parent_surface (GTS_FACE (t), s)) && + g_hash_table_lookup (hash, t) == NULL) { + faces = g_slist_prepend (faces, t); + g_hash_table_insert (hash, t, i); + } + j = j->next; + } + i = i->next; + } + g_hash_table_destroy (hash); + + return faces; +} + +/** + * gts_face_neighbor_number: + * @f: a #GtsFace. + * @s: a #GtsSurface or %NULL. + * + * Returns: the number of faces neighbors of @f and belonging to @s. + */ +guint gts_face_neighbor_number (GtsFace * f, GtsSurface * s) +{ + GSList * i; + guint nn = 0; + GtsEdge * e[4], ** e1 = e; + + g_return_val_if_fail (f != NULL, 0); + + e[0] = GTS_TRIANGLE (f)->e1; + e[1] = GTS_TRIANGLE (f)->e2; + e[2] = GTS_TRIANGLE (f)->e3; + e[3] = NULL; + while (*e1) { + i = (*e1++)->triangles; + while (i) { + GtsTriangle * t = i->data; + if (GTS_FACE (t) != f && + GTS_IS_FACE (t) && + (!s || gts_face_has_parent_surface (GTS_FACE (t), s))) + nn++; + i = i->next; + } + } + + return nn; +} + +/** + * gts_face_neighbors: + * @f: a #GtsFace. + * @s: a #GtsSurface or %NULL. + * + * Returns: a list of unique #GtsFace neighbors of @f and belonging to @s. + */ +GSList * gts_face_neighbors (GtsFace * f, GtsSurface * s) +{ + GSList * i, * list = NULL; + GtsEdge * e[4], ** e1 = e; + + g_return_val_if_fail (f != NULL, NULL); + + e[0] = GTS_TRIANGLE (f)->e1; + e[1] = GTS_TRIANGLE (f)->e2; + e[2] = GTS_TRIANGLE (f)->e3; + e[3] = NULL; + while (*e1) { + i = (*e1++)->triangles; + while (i) { + GtsTriangle * t = i->data; + if (GTS_FACE (t) != f && + GTS_IS_FACE (t) && + (!s || gts_face_has_parent_surface (GTS_FACE (t), s))) + list = g_slist_prepend (list, t); + i = i->next; + } + } + + return list; +} + +/** + * gts_face_foreach_neighbor: + * @f: a #GtsFace. + * @s: a #GtsSurface or %NULL. + * @func: a #GtsFunc. + * @data: user data to pass to @func. + * + * Calls @func for each neighbor of @f belonging to @s (if not %NULL). + */ +void gts_face_foreach_neighbor (GtsFace * f, + GtsSurface * s, + GtsFunc func, + gpointer data) +{ + GSList * i; + GtsEdge * e[4], ** e1 = e; + + g_return_if_fail (f != NULL); + g_return_if_fail (func != NULL); + + e[0] = GTS_TRIANGLE (f)->e1; + e[1] = GTS_TRIANGLE (f)->e2; + e[2] = GTS_TRIANGLE (f)->e3; + e[3] = NULL; + while (*e1) { + i = (*e1++)->triangles; + while (i) { + GtsTriangle * t = i->data; + if (GTS_FACE (t) != f && + GTS_IS_FACE (t) && + (!s || gts_face_has_parent_surface (GTS_FACE (t), s))) + (* func) (t, data); + i = i->next; + } + } +} + +static gboolean triangle_is_incompatible (GtsTriangle * t, GtsEdge * e, GtsSurface * s) +{ + GSList * i = e->triangles; + + while (i) { + if (i->data != t && + GTS_IS_FACE (i->data) && + gts_face_has_parent_surface (i->data, s) && + !gts_triangles_are_compatible (t, i->data, e)) + return TRUE; + i = i->next; + } + return FALSE; +} + +/** + * gts_face_is_compatible: + * @f: a #GtsFace. + * @s: a #GtsSurface. + * + * Returns: %TRUE if @f is compatible with all its neighbors belonging + * to @s, %FALSE otherwise. + */ +gboolean gts_face_is_compatible (GtsFace * f, GtsSurface * s) +{ + g_return_val_if_fail (f != NULL, FALSE); + g_return_val_if_fail (s != NULL, FALSE); + + return !(triangle_is_incompatible (GTS_TRIANGLE (f), GTS_TRIANGLE (f)->e1, s) || + triangle_is_incompatible (GTS_TRIANGLE (f), GTS_TRIANGLE (f)->e2, s) || + triangle_is_incompatible (GTS_TRIANGLE (f), GTS_TRIANGLE (f)->e3, s)); +} diff --git a/gts/fifo.c b/gts/fifo.c new file mode 100644 index 0000000000..8b3d2b6686 --- /dev/null +++ b/gts/fifo.c @@ -0,0 +1,192 @@ +/* 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 "gts.h" + +struct _GtsFifo { + GList * head; + GList * tail; +}; + +/** + * gts_fifo_new: + * + * Returns: a new #GtsFifo. + */ +GtsFifo * gts_fifo_new () +{ + GtsFifo * fifo = g_malloc (sizeof (GtsFifo)); + + fifo->head = fifo->tail = NULL; + return fifo; +} + +/** + * gts_fifo_write: + * @fifo: a #GtsFifo. + * @fp: a file pointer. + * + * Writes the content of @fifo in @fp. + */ +void gts_fifo_write (GtsFifo * fifo, FILE * fp) +{ + GList * i; + + g_return_if_fail (fifo != NULL); + g_return_if_fail (fp != NULL); + + fprintf (fp, "["); + i = fifo->head; + while (i) { + fprintf (fp, "%p ", i->data); + i = i->next; + } + fprintf (fp, "]"); +} + +/** + * gts_fifo_push: + * @fifo: a #GtsFifo. + * @data: data to add to @fifo. + * + * Push @data into @fifo. + */ +void gts_fifo_push (GtsFifo * fifo, gpointer data) +{ + g_return_if_fail (fifo != NULL); + + fifo->head = g_list_prepend (fifo->head, data); + if (fifo->tail == NULL) + fifo->tail = fifo->head; +} + +/** + * gts_fifo_pop: + * @fifo: a #GtsFifo. + * + * Removes the first element from @fifo. + * + * Returns: the first element in @fifo or %NULL if @fifo is empty. + */ +gpointer gts_fifo_pop (GtsFifo * fifo) +{ + gpointer data; + GList * tail; + + g_return_val_if_fail (fifo != NULL, NULL); + + if (fifo->tail == NULL) + return NULL; + tail = fifo->tail->prev; + data = fifo->tail->data; + fifo->head = g_list_remove_link (fifo->head, fifo->tail); + g_list_free_1 (fifo->tail); + fifo->tail = tail; + return data; +} + +/** + * gts_fifo_top: + * @fifo: a #GtsFifo. + * + * Returns: the first element in @fifo or %NULL if @fifo is empty. + */ +gpointer gts_fifo_top (GtsFifo * fifo) +{ + g_return_val_if_fail (fifo != NULL, NULL); + + if (fifo->tail == NULL) + return NULL; + return fifo->tail->data; +} + +/** + * gts_fifo_size: + * @fifo: a #GtsFifo. + * + * Returns: the number of elements in @fifo. + */ +guint gts_fifo_size (GtsFifo * fifo) +{ + g_return_val_if_fail (fifo != NULL, 0); + + return g_list_length (fifo->head); +} + +/** + * gts_fifo_destroy: + * @fifo: a #GtsFifo. + * + * Frees all the memory allocated for @fifo. + */ +void gts_fifo_destroy (GtsFifo * fifo) +{ + g_return_if_fail (fifo != NULL); + g_list_free (fifo->head); + g_free (fifo); +} + +/** + * gts_fifo_is_empty: + * @fifo: a #GtsFifo. + * + * Returns: %TRUE if @fifo is empty, %FALSE otherwise. + */ +gboolean gts_fifo_is_empty (GtsFifo * fifo) +{ + g_return_val_if_fail (fifo != NULL, TRUE); + + return (fifo->head == NULL); +} + +/** + * gts_fifo_foreach: + * @fifo: a #GtsFifo. + * @func: a #GtsFunc. + * @data: user data to be passed to @func. + * + * Calls @func in order for each item in @fifo, passing @data. + */ +void gts_fifo_foreach (GtsFifo * fifo, GtsFunc func, gpointer data) +{ + GList * i; + + g_return_if_fail (fifo != NULL); + g_return_if_fail (func != NULL); + + i = fifo->tail; + while (i) { + (* func) (i->data, data); + i = i->prev; + } +} + +/** + * gts_fifo_reverse: + * @fifo: a #GtsFifo. + * + * Reverses the order of elements in @fifo. + */ +void gts_fifo_reverse (GtsFifo * fifo) +{ + g_return_if_fail (fifo != NULL); + + fifo->tail = fifo->head; + fifo->head = g_list_reverse (fifo->head); +} diff --git a/gts/graph.c b/gts/graph.c new file mode 100644 index 0000000000..1566c9571d --- /dev/null +++ b/gts/graph.c @@ -0,0 +1,1776 @@ +/* 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" + +/* GtsGNode */ + +gboolean gts_allow_floating_gnodes = FALSE; + +static void gnode_remove_container (GtsContainee * i, GtsContainer * c) +{ + (* GTS_CONTAINEE_CLASS (GTS_OBJECT_CLASS (gts_gnode_class ())->parent_class)->remove_container) (i, c); + if (GTS_SLIST_CONTAINEE (i)->containers == NULL && + !gts_allow_floating_gnodes && + !GTS_OBJECT_DESTROYED(GTS_OBJECT (i))) + gts_object_destroy (GTS_OBJECT (i)); +} + +static void gnode_class_init (GtsGNodeClass * klass) +{ + klass->weight = NULL; + + GTS_CONTAINEE_CLASS (klass)->remove_container = gnode_remove_container; +} + +static void gnode_init (GtsGNode * n) +{ + n->level = 0; +} + +/** + * gts_gnode_class: + * + * Returns: the #GtsGNodeClass. + */ +GtsGNodeClass * gts_gnode_class (void) +{ + static GtsGNodeClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo gnode_info = { + "GtsGNode", + sizeof (GtsGNode), + sizeof (GtsGNodeClass), + (GtsObjectClassInitFunc) gnode_class_init, + (GtsObjectInitFunc) gnode_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = + gts_object_class_new (GTS_OBJECT_CLASS (gts_slist_container_class ()), + &gnode_info); + } + + return klass; +} + +/** + * gts_gnode_new: + * @klass: a #GtsGNodeClass. + * + * Returns: a new #GtsGNode. + */ +GtsGNode * gts_gnode_new (GtsGNodeClass * klass) +{ + GtsGNode * object; + + object = GTS_GNODE (gts_object_new (GTS_OBJECT_CLASS (klass))); + + return object; +} + +/** + * gts_gnode_foreach_neighbor: + * @n: a #GtsGNode. + * @g: a #GtsGraph or %NULL. + * @func: a #GtsFunc. + * @data: user data to be passed to @func. + * + * Calls @func for each neighbor #GtsGNode of @n (belonging to @g if + * @g is not %NULL. + */ +void gts_gnode_foreach_neighbor (GtsGNode * n, + GtsGraph * g, + GtsFunc func, + gpointer data) +{ + GSList * i; + + g_return_if_fail (n != NULL); + g_return_if_fail (func != NULL); + + i = GTS_SLIST_CONTAINER (n)->items; + while (i) { + GtsGNode * n1 = GTS_GNODE_NEIGHBOR (n, i->data); + if (g == NULL || gts_containee_is_contained (GTS_CONTAINEE (n1), + GTS_CONTAINER (g))) + (* func) (n1, data); + i = i->next; + } +} + +/** + * gts_gnode_foreach_edge: + * @n: a #GtsGNode. + * @g: a #GtsGraph or %NULL. + * @func: a #GtsFunc. + * @data: user data to be passed to @func. + * + * Calls @func for each #GtsGEdge connecting @n to another #GtsGNode + * (belonging to @g if @g is not %NULL. + */ +void gts_gnode_foreach_edge (GtsGNode * n, + GtsGraph * g, + GtsFunc func, + gpointer data) +{ + GSList * i; + + g_return_if_fail (n != NULL); + g_return_if_fail (func != NULL); + + i = GTS_SLIST_CONTAINER (n)->items; + while (i) { + GtsGNode * n1 = GTS_GNODE_NEIGHBOR (n, i->data); + if (g == NULL || gts_containee_is_contained (GTS_CONTAINEE (n1), + GTS_CONTAINER (g))) + (* func) (i->data, data); + i = i->next; + } +} + +/** + * gts_gnode_degree: + * @n: a #GtsGNode. + * @g: a #GtsGraph or %NULL. + * + * Returns: the number of neighbors of @n (belonging to @g if @g is not %NULL). + */ +guint gts_gnode_degree (GtsGNode * n, + GtsGraph * g) +{ + GSList * i; + guint nn = 0; + + g_return_val_if_fail (n != NULL, 0); + + i = GTS_SLIST_CONTAINER (n)->items; + while (i) { + GtsGNode * n1 = GTS_GNODE_NEIGHBOR (n, i->data); + if (g == NULL || gts_containee_is_contained (GTS_CONTAINEE (n1), + GTS_CONTAINER (g))) + nn++; + i = i->next; + } + + return nn; +} + +/** + * gts_gnode_move_cost: + * @n: a #GtsGNode. + * @src: a #GtsGraph containing @n. + * @dst: another #GtsGraph. + * + * Returns: the cost (increase in the sum of the weights of the edges cut) of + * moving @n from @src to @dst. + */ +gfloat gts_gnode_move_cost (GtsGNode * n, + GtsGraph * src, + GtsGraph * dst) +{ + GSList * i; + gfloat cost = 0.; + + g_return_val_if_fail (n != NULL, G_MAXFLOAT); + g_return_val_if_fail (src != NULL, G_MAXFLOAT); + g_return_val_if_fail (dst != NULL, G_MAXFLOAT); + g_return_val_if_fail (gts_containee_is_contained (GTS_CONTAINEE (n), + GTS_CONTAINER (src)), + G_MAXFLOAT); + + i = GTS_SLIST_CONTAINER (n)->items; + while (i) { + GtsGEdge * ge = i->data; + GtsGNode * neighbor = GTS_GNODE_NEIGHBOR (n, ge); + + if (gts_containee_is_contained (GTS_CONTAINEE (neighbor), + GTS_CONTAINER (src))) + cost += gts_gedge_weight (ge); + else if (gts_containee_is_contained (GTS_CONTAINEE (neighbor), + GTS_CONTAINER (dst))) + cost -= gts_gedge_weight (ge); + i = i->next; + } + + return cost; +} + +/** + * gts_gnode_weight: + * @n: a #GtsGNode. + * + * Returns: the weight of @n as defined by the weight() method of the + * #GtsGNodeClass. + */ +gfloat gts_gnode_weight (GtsGNode * n) +{ + g_return_val_if_fail (n != NULL, 0.); + + if (GTS_GNODE_CLASS (GTS_OBJECT (n)->klass)->weight) + return (* GTS_GNODE_CLASS (GTS_OBJECT (n)->klass)->weight) (n); + return 1.; +} + +/* GtsNGNode */ + +static void ngnode_init (GtsNGNode * n) +{ + n->id = 0; +} + +/** + * gts_ngnode_class: + * + * Returns: the #GtsNGNodeClass. + */ +GtsNGNodeClass * gts_ngnode_class (void) +{ + static GtsNGNodeClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo ngnode_info = { + "GtsNGNode", + sizeof (GtsNGNode), + sizeof (GtsNGNodeClass), + (GtsObjectClassInitFunc) NULL, + (GtsObjectInitFunc) ngnode_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_gnode_class ()), + &ngnode_info); + } + + return klass; +} + +/** + * gts_ngnode_new: + * @klass: a #GtsNGNodeClass. + * + * Returns: a new #GtsNGNode with identity @id. + */ +GtsNGNode * gts_ngnode_new (GtsNGNodeClass * klass, + guint id) +{ + GtsNGNode * n; + + n = GTS_NGNODE (gts_gnode_new (GTS_GNODE_CLASS (klass))); + n->id = id; + + return n; +} + +/* GtsWGNode */ + +static gfloat wgnode_weight (GtsGNode * n) +{ + return GTS_WGNODE (n)->weight; +} + +static void wgnode_class_init (GtsWGNodeClass * klass) +{ + GTS_GNODE_CLASS (klass)->weight = wgnode_weight; +} + +static void wgnode_init (GtsWGNode * n) +{ + n->weight = 1.; +} + +/** + * gts_wgnode_class: + * + * Returns: the #GtsWGNodeClass. + */ +GtsWGNodeClass * gts_wgnode_class (void) +{ + static GtsWGNodeClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo wgnode_info = { + "GtsWGNode", + sizeof (GtsWGNode), + sizeof (GtsWGNodeClass), + (GtsObjectClassInitFunc) wgnode_class_init, + (GtsObjectInitFunc) wgnode_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_gnode_class ()), + &wgnode_info); + } + + return klass; +} + +/** + * gts_wgnode_new: + * @klass: a #GtsWGNodeClass. + * @weight: the weight of the #GtsWGNode to create. + * + * Returns: a new #GtsWGNode of weight @weight. + */ +GtsWGNode * gts_wgnode_new (GtsWGNodeClass * klass, + gfloat weight) +{ + GtsWGNode * n; + + n = GTS_WGNODE (gts_gnode_new (GTS_GNODE_CLASS (klass))); + n->weight = weight; + + return n; +} + +/* GtsPNode */ + +static void pnode_write (GtsGNode * n, FILE * fp) +{ + if (GTS_IS_NVERTEX (GTS_PNODE (n)->data)) + fprintf (fp, "label=\"%p:%s\",", + GTS_PNODE (n)->data, + GTS_NVERTEX (GTS_PNODE (n)->data)->name); + else + fprintf (fp, "label=\"%p\",", GTS_PNODE (n)->data); +} + +static void pnode_class_init (GtsPNodeClass * klass) +{ + GTS_GNODE_CLASS (klass)->write = pnode_write; +} + +static void pnode_init (GtsPNode * pn) +{ + pn->data = NULL; +} + +/** + * gts_pnode_class: + * + * Returns: the #GtsPNodeClass. + */ +GtsPNodeClass * gts_pnode_class (void) +{ + static GtsPNodeClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo pnode_info = { + "GtsPNode", + sizeof (GtsPNode), + sizeof (GtsPNodeClass), + (GtsObjectClassInitFunc) pnode_class_init, + (GtsObjectInitFunc) pnode_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_gnode_class ()), + &pnode_info); + } + + return klass; +} + +/** + * gts_pnode_new: + * @klass: a #GtsPNodeClass. + * @data: user data. + * + * Returns: a new #GtsPNode associated with @data. + */ +GtsPNode * gts_pnode_new (GtsPNodeClass * klass, gpointer data) +{ + GtsPNode * pn; + + pn = GTS_PNODE (gts_object_new (GTS_OBJECT_CLASS (klass))); + pn->data = data; + + return pn; +} + +/* GtsFNode */ + +static void fnode_write (GtsGNode * n, FILE * fp) +{ + fprintf (fp, "label=\"%p\",", GTS_FNODE (n)->f); +} + +static void fnode_class_init (GtsGNodeClass * klass) +{ + klass->write = fnode_write; +} + +static void fnode_init (GtsFNode * fn) +{ + fn->f = NULL; +} + +/** + * gts_fnode_class: + * + * Returns: the #GtsFNodeClass. + */ +GtsFNodeClass * gts_fnode_class (void) +{ + static GtsFNodeClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo fnode_info = { + "GtsFNode", + sizeof (GtsFNode), + sizeof (GtsFNodeClass), + (GtsObjectClassInitFunc) fnode_class_init, + (GtsObjectInitFunc) fnode_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_gnode_class ()), + &fnode_info); + } + + return klass; +} + +/** + * gts_fnode_new: + * @klass: a #GtsFNodeClass. + * @f: a #GtsFace. + * + * Returns: a new #GtsFNode associated with face @f. + */ +GtsFNode * gts_fnode_new (GtsFNodeClass * klass, GtsFace * f) +{ + GtsFNode * fn; + + g_return_val_if_fail (f != NULL, NULL); + + fn = GTS_FNODE (gts_object_new (GTS_OBJECT_CLASS (klass))); + fn->f = f; + + return fn; +} + +/* GtsGEdge */ + +static void gedge_destroy (GtsObject * object) +{ + GtsGEdge * ge = GTS_GEDGE (object); + + if (ge->n1) + gts_container_remove (GTS_CONTAINER (ge->n1), GTS_CONTAINEE (ge)); + if (ge->n2) + gts_container_remove (GTS_CONTAINER (ge->n2), GTS_CONTAINEE (ge)); + + (* GTS_OBJECT_CLASS (gts_gedge_class ())->parent_class->destroy) (object); +} + +static void gedge_remove_container (GtsContainee * i, GtsContainer * c) +{ + GtsGEdge * ge = GTS_GEDGE (i); + GtsGNode * n1 = ge->n1; + GtsGNode * n2 = ge->n2; + + ge->n1 = ge->n2 = NULL; + if (n1 != NULL && n2 != NULL) { + if (GTS_CONTAINER (n1) == c) { + if (n2 && n2 != n1) gts_container_remove (GTS_CONTAINER (n2), i); + } + else if (GTS_CONTAINER (n2) == c) { + if (n1 && n1 != n2) gts_container_remove (GTS_CONTAINER (n1), i); + } + else + g_assert_not_reached (); + (* GTS_OBJECT_CLASS (gts_gedge_class ())->parent_class->destroy) + (GTS_OBJECT (i)); + } +} + +static gboolean gedge_is_contained (GtsContainee * i, GtsContainer * c) +{ + GtsGEdge * ge = GTS_GEDGE (i); + + if (GTS_CONTAINER (ge->n1) == c || GTS_CONTAINER (ge->n2) == c) + return TRUE; + return FALSE; +} + +static void gedge_class_init (GtsGEdgeClass * klass) +{ + klass->link = NULL; + klass->weight = NULL; + + GTS_CONTAINEE_CLASS (klass)->remove_container = gedge_remove_container; + GTS_CONTAINEE_CLASS (klass)->is_contained = gedge_is_contained; + + GTS_OBJECT_CLASS (klass)->destroy = gedge_destroy; +} + +static void gedge_init (GtsGEdge * object) +{ + object->n1 = object->n2 = NULL; +} + +/** + * gts_gedge_class: + * + * Returns: the #GtsGEdgeClass. + */ +GtsGEdgeClass * gts_gedge_class (void) +{ + static GtsGEdgeClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo gedge_info = { + "GtsGEdge", + sizeof (GtsGEdge), + sizeof (GtsGEdgeClass), + (GtsObjectClassInitFunc) gedge_class_init, + (GtsObjectInitFunc) gedge_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_containee_class ()), + &gedge_info); + } + + return klass; +} + +/** + * gts_gedge_new: + * @klass: a #GtsGEdgeClass. + * @n1: a #GtsGNode. + * @n2: another #GtsGNode. + * + * Returns: a new #GtsGEdge linking @n1 and @n2. + */ +GtsGEdge * gts_gedge_new (GtsGEdgeClass * klass, GtsGNode * n1, GtsGNode * n2) +{ + GtsGEdge * object; + + g_return_val_if_fail (n1 != NULL, NULL); + g_return_val_if_fail (n2 != NULL, NULL); + + object = GTS_GEDGE (gts_object_new (GTS_OBJECT_CLASS (klass))); + object->n1 = n1; + gts_container_add (GTS_CONTAINER (n1), GTS_CONTAINEE (object)); + object->n2 = n2; + if (n1 != n2) + gts_container_add (GTS_CONTAINER (n2), GTS_CONTAINEE (object)); + + if (klass->link) + object = (* klass->link) (object, n1, n2); + + return object; +} + +/** + * gts_gedge_weight: + * @e: a #GtsGEdge. + * + * Returns: the weight of edge @e as defined by the weight() method of + * #GtsGEdgeClass. + */ +gfloat gts_gedge_weight (GtsGEdge * e) +{ + g_return_val_if_fail (e != NULL, 0.); + + if (GTS_GEDGE_CLASS (GTS_OBJECT (e)->klass)->weight) + return (* GTS_GEDGE_CLASS (GTS_OBJECT (e)->klass)->weight) (e); + return 1.; +} + +/* GtsPGEdge */ + +static void pgedge_write (GtsGEdge * ge, FILE * fp) +{ + if (GTS_IS_EDGE (GTS_PGEDGE (ge)->data)) { + GtsEdge * e = GTS_PGEDGE (ge)->data; + guint n = g_slist_length (e->triangles); + + fprintf (fp, "label=\"%p:%s:%d\",color=%s", e, + GTS_IS_NEDGE (e) ? GTS_NEDGE (e)->name : "", + n, + n == 0 ? "black" : + n == 1 ? "blue" : + n == 2 ? "green" : + n == 3 ? "violet" : + n == 4 ? "red" : + "pink"); + } + else + fprintf (fp, "label=\"%p\",", GTS_PGEDGE (ge)->data); +} + +static void pgedge_class_init (GtsPGEdgeClass * klass) +{ + GTS_GEDGE_CLASS (klass)->write = pgedge_write; +} + +static void pgedge_init (GtsPGEdge * e) +{ + e->data = NULL; +} + +/** + * gts_pgedge_class: + * + * Returns: the #GtsPGEdgeClass. + */ +GtsPGEdgeClass * gts_pgedge_class (void) +{ + static GtsPGEdgeClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo pgedge_info = { + "GtsPGEdge", + sizeof (GtsPGEdge), + sizeof (GtsPGEdgeClass), + (GtsObjectClassInitFunc) pgedge_class_init, + (GtsObjectInitFunc) pgedge_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_gedge_class ()), + &pgedge_info); + } + + return klass; +} + +/** + * gts_pgedge_new: + * @klass: a #GtsPGEdgeClass. + * @n1: a #GtsGNode. + * @n2: another #GtsGNode. + * @data: user data. + * + * Returns: a new #GtsPGEdge associated with @data linking @n1 and @n2. + */ +GtsPGEdge * gts_pgedge_new (GtsPGEdgeClass * klass, + GtsGNode * g1, + GtsGNode * g2, + gpointer data) +{ + GtsPGEdge * we; + + we = GTS_PGEDGE (gts_gedge_new (GTS_GEDGE_CLASS (klass), g1, g2)); + we->data = data; + + return we; +} + +/* GtsWGEdge */ + +static gfloat wgedge_weight (GtsGEdge * e) +{ + return GTS_WGEDGE (e)->weight; +} + +static void wgedge_class_init (GtsWGEdgeClass * klass) +{ + GTS_GEDGE_CLASS (klass)->weight = wgedge_weight; +} + +static void wgedge_init (GtsWGEdge * e) +{ + e->weight = 1.; +} + +/** + * gts_wgedge_class: + * + * Returns: the #GtsWGEdgeClass. + */ +GtsWGEdgeClass * gts_wgedge_class (void) +{ + static GtsWGEdgeClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo wgedge_info = { + "GtsWGEdge", + sizeof (GtsWGEdge), + sizeof (GtsWGEdgeClass), + (GtsObjectClassInitFunc) wgedge_class_init, + (GtsObjectInitFunc) wgedge_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_gedge_class ()), + &wgedge_info); + } + + return klass; +} + +/** + * gts_wgedge_new: + * @klass: a #GtsWGEdgeClass. + * @n1: a #GtsGNode. + * @n2: another #GtsGNode. + * @weight: the weight of the new edge. + * + * Returns: a new #GtsWGEdge of weight @weight linking @n1 and @n2. + */ +GtsWGEdge * gts_wgedge_new (GtsWGEdgeClass * klass, + GtsGNode * g1, + GtsGNode * g2, + gfloat weight) +{ + GtsWGEdge * we; + + we = GTS_WGEDGE (gts_gedge_new (GTS_GEDGE_CLASS (klass), g1, g2)); + we->weight = weight; + + return we; +} + +/* GtsGraph */ + +static void graph_init (GtsGraph * g) +{ + g->graph_class = gts_graph_class (); + g->node_class = gts_gnode_class (); + g->edge_class = gts_gedge_class (); +} + +static void graph_write (GtsObject * object, FILE * fp) +{ + GtsGraph * graph = GTS_GRAPH (object); + + fprintf (fp, " %s %s %s", + object->klass->info.name, + GTS_OBJECT_CLASS (graph->node_class)->info.name, + GTS_OBJECT_CLASS (graph->edge_class)->info.name); +} + +static void graph_read (GtsObject ** object, GtsFile * f) +{ + GtsObjectClass * klass; + + if (f->type != GTS_STRING) { + gts_file_error (f, "expecting a string (GtsGNodeClass)"); + return; + } + klass = gts_object_class_from_name (f->token->str); + if (klass == NULL) { + gts_file_error (f, "unknown class `%s'", f->token->str); + return; + } + if (!gts_object_class_is_from_class (klass, gts_gnode_class ())) { + gts_file_error (f, "class `%s' is not a GtsGNodeClass", f->token->str); + return; + } + GTS_GRAPH (*object)->node_class = GTS_GNODE_CLASS (klass); + gts_file_next_token (f); + + if (f->type != GTS_STRING) { + gts_file_error (f, "expecting a string (GtsGEdgeClass)"); + return; + } + klass = gts_object_class_from_name (f->token->str); + if (klass == NULL) { + gts_file_error (f, "unknown class `%s'", f->token->str); + return; + } + if (!gts_object_class_is_from_class (klass, gts_gedge_class ())) { + gts_file_error (f, "class `%s' is not a GtsGEdgeClass", f->token->str); + return; + } + GTS_GRAPH (*object)->edge_class = GTS_GEDGE_CLASS (klass); + gts_file_next_token (f); +} + +static void graph_class_init (GtsGraphClass * klass) +{ + klass->weight = NULL; + + GTS_OBJECT_CLASS (klass)->write = graph_write; + GTS_OBJECT_CLASS (klass)->read = graph_read; +} + +/** + * gts_graph_class: + * + * Returns: the #GtsGraphClass. + */ +GtsGraphClass * gts_graph_class (void) +{ + static GtsGraphClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo graph_info = { + "GtsGraph", + sizeof (GtsGraph), + sizeof (GtsGraphClass), + (GtsObjectClassInitFunc) graph_class_init, + (GtsObjectInitFunc) graph_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_hash_container_class ()), + &graph_info); + } + + return klass; +} + +/** + * gts_graph_new: + * @klass: a #GtsGraphClass. + * @node_class: a #GtsGNodeClass. + * @edge_class: a #GtsGEdgeClass. + * + * Returns: a new #GtsGraph using @node_class and @edge_class as node types. + */ +GtsGraph * gts_graph_new (GtsGraphClass * klass, + GtsGNodeClass * node_class, + GtsGEdgeClass * edge_class) +{ + GtsGraph * g; + + g_return_val_if_fail (klass != NULL, NULL); + g_return_val_if_fail (node_class != NULL, NULL); + g_return_val_if_fail (edge_class != NULL, NULL); + + g = GTS_GRAPH (gts_object_new (GTS_OBJECT_CLASS (klass))); + g->node_class = node_class; + g->edge_class = edge_class; + + return g; +} + +static void compute_degree (GtsGNode * n, gpointer * data) +{ + GtsGraph * g = data[0]; + GtsRange * degree = data[1]; + + gts_range_add_value (degree, gts_gnode_degree (n, g)); +} + +/** + * gts_graph_print_stats: + * @g: a #GtsGraph. + * @fp: a file pointer. + * + * Writes to @fp a summary of the properties of @g. + */ +void gts_graph_print_stats (GtsGraph * g, FILE * fp) +{ + GtsRange degree; + gpointer data[2]; + + g_return_if_fail (g != NULL); + g_return_if_fail (fp != NULL); + + fprintf (fp, "# nodes: %d weight: %g\n", + gts_container_size (GTS_CONTAINER (g)), + gts_graph_weight (g)); + fprintf (fp, "# degree: "); + gts_range_init (°ree); + data[0] = g; + data[1] = °ree; + gts_container_foreach (GTS_CONTAINER (g), (GtsFunc) compute_degree, data); + gts_range_update (°ree); + gts_range_print (°ree, fp); + fprintf (fp, "\n"); + fprintf (fp, "# edges cut: %d edges cut weight: %g\n", + gts_graph_edges_cut (g), + gts_graph_edges_cut_weight (g)); +} + +struct _GtsGraphTraverse { + GtsFifo * q; + GtsGraph * g; +}; + +static void reset_level (GtsGNode * n) +{ + n->level = 0; +} + +/** + * gts_graph_traverse_new: + * @g: a #GtsGraph. + * @n: a #GtsGNode belonging to @g. + * @type: the type of traversal. + * @reinit: if %TRUE, the traversal is reinitialized. + * + * Returns: a new #GtsGraphTraverse initialized for the traversal of + * @g of type @type, starting from @n. + */ +GtsGraphTraverse * gts_graph_traverse_new (GtsGraph * g, + GtsGNode * n, + GtsTraverseType type, + gboolean reinit) +{ + GtsGraphTraverse * t; + + g_return_val_if_fail (g != NULL, NULL); + g_return_val_if_fail (n != NULL, NULL); + g_return_val_if_fail (gts_containee_is_contained (GTS_CONTAINEE (n), + GTS_CONTAINER (g)), + NULL); + + if (reinit) + gts_container_foreach (GTS_CONTAINER (g), (GtsFunc) reset_level, NULL); + + t = g_malloc (sizeof (GtsGraphTraverse)); + t->q = gts_fifo_new (); + t->g = g; + n->level = 1; + gts_fifo_push (t->q, n); + + return t; +} + +static void push_neighbor (GtsGNode * n, gpointer * data) +{ + GtsFifo * q = data[0]; + GtsGNode * u = data[1]; + + if (n->level == 0) { + n->level = u->level + 1; + gts_fifo_push (q, n); + } +} + +/** + * gts_graph_traverse_next: + * @t: a #GtsGraphTraverse. + * + * Returns: the next #GtsGNode of the traversal defined by @t or %NULL + * if the traversal is complete. + */ +GtsGNode * gts_graph_traverse_next (GtsGraphTraverse * t) +{ + GtsGNode * u; + + g_return_val_if_fail (t != NULL, NULL); + + u = gts_fifo_pop (t->q); + if (u) { + gpointer data[2]; + + data[0] = t->q; + data[1] = u; + gts_gnode_foreach_neighbor (u, t->g, (GtsFunc) push_neighbor, data); + } + + return u; +} + +/** + * gts_graph_traverse_what_next: + * @t: a #GtsGraphTraverse. + * + * Returns: the next #GtsGNode of the traversal defined by @t or %NULL + * if the traversal is complete but without advancing the traversal. + */ +GtsGNode * gts_graph_traverse_what_next (GtsGraphTraverse * t) +{ + g_return_val_if_fail (t != NULL, NULL); + + return gts_fifo_top (t->q); +} + +/** + * gts_graph_traverse_destroy: + * @t: a #GtsGraphTraverse. + * + * Frees all the memory allocated for @t. + */ +void gts_graph_traverse_destroy (GtsGraphTraverse * t) +{ + g_return_if_fail (t != NULL); + + gts_fifo_destroy (t->q); + g_free (t); +} + +static void edge_foreach_node (GtsGNode * n, gpointer * info) +{ + GtsFunc func = (GtsFunc) info[0]; + gpointer data = info[1]; + GHashTable * hash = info[2]; + GSList * i = GTS_SLIST_CONTAINER (n)->items; + + while (i) { + GtsGEdge * e = i->data; + if (!g_hash_table_lookup (hash, e)) { + (* func) (e, data); + g_hash_table_insert (hash, e, e); + } + i = i->next; + } +} + +/** + * gts_graph_foreach_edge: + * @g: a #GtsGraph. + * @func: a #GtsFunc. + * @data: user data to be passed to @func. + * + * Calls @func for each #GtsEdge of @g. + */ +void gts_graph_foreach_edge (GtsGraph * g, GtsFunc func, gpointer data) +{ + gpointer info[3]; + GHashTable * hash; + + g_return_if_fail (g != NULL); + g_return_if_fail (func != NULL); + + info[0] = func; + info[1] = data; + info[2] = hash = g_hash_table_new (NULL, NULL); + gts_container_foreach (GTS_CONTAINER (g), (GtsFunc) edge_foreach_node, info); + g_hash_table_destroy (hash); +} + +/** + * gts_graph_weight: + * @g: a #GtsGraph. + * + * Returns: the weight of graph @g as defined by the weight() method + * of #GtsGraphClass. + */ +gfloat gts_graph_weight (GtsGraph * g) +{ + g_return_val_if_fail (g != NULL, 0.); + + if (GTS_GRAPH_CLASS (GTS_OBJECT (g)->klass)->weight) + return (* GTS_GRAPH_CLASS (GTS_OBJECT (g)->klass)->weight) (g); + return (gfloat) gts_container_size (GTS_CONTAINER (g)); +} + +/** + * gts_graph_distance_sum: + * @g: a #GtsGraph. + * @center: a #GtsGNode of @g. + * + * Returns: the sum of the distances between all the other #GtsGNode + * of @g and @center. + */ +guint gts_graph_distance_sum (GtsGraph * g, GtsGNode * center) +{ + GtsGraphTraverse * t; + GtsGNode * n; + guint sum = 0; + + g_return_val_if_fail (g != NULL, 0); + g_return_val_if_fail (center != NULL, 0); + + t = gts_graph_traverse_new (g, center, GTS_BREADTH_FIRST, TRUE); + while ((n = gts_graph_traverse_next (t))) + sum += n->level - 1; + gts_graph_traverse_destroy (t); + + return sum; +} + +/** + * gts_graph_farthest: + * @g: a #GtsGraph. + * @gnodes: a list of #GtsGNode belonging to @g. + * + * Returns: the #GtsGNode belonging to @g and farthest from all the nodes in + * @gnodes (hmmm, definition of "farthest"?). + */ +GtsGNode * gts_graph_farthest (GtsGraph * g, GSList * gnodes) +{ + GtsGNode * farthest = NULL; + GSList * i; + gboolean reinit = TRUE, changed = TRUE; + guint level = 1; + + g_return_val_if_fail (g != NULL, NULL); + + /* initialize traversals */ + i = gnodes; + while (i) { + GTS_OBJECT (i->data)->reserved = + gts_graph_traverse_new (g, i->data, GTS_BREADTH_FIRST, reinit); + reinit = FALSE; + i = i->next; + } + + while (changed) { + changed = FALSE; + i = gnodes; + while (i) { + GtsGraphTraverse * t = GTS_OBJECT (i->data)->reserved; + GtsGNode * n; + while ((n = gts_graph_traverse_what_next (t)) && n->level == level) { + changed = TRUE; + farthest = n; + gts_graph_traverse_next (t); + } + i = i->next; + } + level++; + } + + /* destroy traversals */ + i = gnodes; + while (i) { + gts_graph_traverse_destroy (GTS_OBJECT (i->data)->reserved); + GTS_OBJECT (i->data)->reserved = NULL; + i = i->next; + } + return farthest; +} + +static void neighbor_count (GtsGNode * n, gpointer * data) +{ + guint * cuts = data[0]; + GtsGraph * g = data[1]; + + if (!gts_containee_is_contained (GTS_CONTAINEE (n), GTS_CONTAINER (g))) + (*cuts)++; +} + +static void count_edge_cuts (GtsGNode * n, gpointer * data) +{ + gts_gnode_foreach_neighbor (n, NULL, (GtsFunc) neighbor_count, data); +} + +/** + * gts_graph_edges_cut: + * @g: a #GtsGraph. + * + * Returns: the number of edges of @g connecting nodes belonging to @g + * to nodes not belonging to @g. + */ +guint gts_graph_edges_cut (GtsGraph * g) +{ + guint cuts = 0; + gpointer data[2]; + + g_return_val_if_fail (g != NULL, 0); + + data[0] = &cuts; + data[1] = g; + gts_container_foreach (GTS_CONTAINER (g), (GtsFunc) count_edge_cuts, data); + + return cuts; +} + +static void sum_edge_cuts_weight (GtsGNode * n, gpointer * data) +{ + gfloat * weight = data[0]; + GtsGraph * g = data[1]; + GSList * i = GTS_SLIST_CONTAINER (n)->items; + + while (i) { + GtsGNode * n1 = GTS_GNODE_NEIGHBOR (n, i->data); + if (!gts_containee_is_contained (GTS_CONTAINEE (n1), GTS_CONTAINER (g))) + *weight += gts_gedge_weight (i->data); + i = i->next; + } +} + +/** + * gts_graph_edges_cut_weight: + * @g: a #GtsGraph. + * + * Returns: the sum of the weights of the edges of @g connecting nodes + * belonging to @g to nodes not belonging to @g. + */ +gfloat gts_graph_edges_cut_weight (GtsGraph * g) +{ + gfloat weight = 0.; + gpointer data[2]; + + g_return_val_if_fail (g != NULL, 0); + + data[0] = &weight; + data[1] = g; + gts_container_foreach (GTS_CONTAINER (g), (GtsFunc) sum_edge_cuts_weight, + data); + + return weight; +} + +/** + * gts_graph_read_jostle: + * @g: a #GtsGraph. + * @fp: a #GtsFile. + * + * Adds to @g the nodes and edges defined in the file pointed to by + * @fp. This file must use the Jostle "graph" ASCII format. + * The nodes created are of type #GtsNGNode and their identities are the + * line number at which they appear in @fp. + * + * Returns: 0 if the lecture was successful, the line number at which + * an error occured otherwise (in which case the @error field of @fp + * is set). + */ +guint gts_graph_read_jostle (GtsGraph * g, GtsFile * fp) +{ + guint nn, ne, n; + GtsGNode ** nodes; + + g_return_val_if_fail (g != NULL, 1); + g_return_val_if_fail (fp != NULL, 1); + + if (fp->type != GTS_INT) { + gts_file_error (fp, "expecting an integer (number of nodes)"); + return fp->line; + } + nn = atoi (fp->token->str); + gts_file_next_token (fp); + + if (fp->type != GTS_INT) { + gts_file_error (fp, "expecting an integer (number of edges)"); + return fp->line; + } + ne = atoi (fp->token->str); + + gts_file_first_token_after (fp, '\n'); + nodes = g_malloc (sizeof (GtsGNode *)*(nn + 1)); + + n = 0; + while (n < nn && fp->type != GTS_ERROR) { + GtsNGNode * node = gts_ngnode_new (gts_ngnode_class (), fp->line); + + nodes[n++] = GTS_GNODE (node); + gts_container_add (GTS_CONTAINER (g), GTS_CONTAINEE (node)); + do { + if (fp->type != GTS_INT) + gts_file_error (fp, "expecting an integer (node index)"); + else { + guint in = atoi (fp->token->str); + + if (in == 0 || in > nn) + gts_file_error (fp, "node index `%d' is out of range `[1,%d]'", + in, nn); + else if (in == n) + gts_file_error (fp, "node index `%d' references itself", in); + else if (in < n) { + gts_gedge_new (g->edge_class, GTS_GNODE (node), nodes[in - 1]); + ne--; + gts_file_next_token (fp); + } + } + } while (fp->type != GTS_ERROR && fp->type != '\n'); + } + g_free (nodes); + + if (fp->type != GTS_ERROR) { + if (n != nn) + gts_file_error (fp, "only `%d' nodes read out of `%d'", + n, nn); + else if (ne > 0) + gts_file_error (fp, "`%d' unallocated edges remaining", + ne); + } + + if (fp->type == GTS_ERROR) + return fp->line; + return 0; +} + +static void count_edges (GtsGEdge * e, guint * nedge) +{ + (*nedge)++; +} + +static void write_node (GtsObject * node, gpointer * data) +{ + FILE * fp = data[0]; + guint * nnode = data[1]; + + node->reserved = GUINT_TO_POINTER ((*nnode)++); + if (node->klass->write) + (* node->klass->write) (node, fp); + fputc ('\n', fp); +} + +static void write_edge (GtsGEdge * edge, FILE * fp) +{ + fprintf (fp, "%u %u", + GPOINTER_TO_UINT (GTS_OBJECT (edge->n1)->reserved), + GPOINTER_TO_UINT (GTS_OBJECT (edge->n2)->reserved)); + if (GTS_OBJECT (edge)->klass->write) + (* GTS_OBJECT (edge)->klass->write) (GTS_OBJECT (edge), fp); + fputc ('\n', fp); +} + +/** + * gts_graph_write: + * @g: a #GtsGraph. + * @fp: a file pointer. + * + * Writes in the file @fp an ASCII representation of @g. The file + * format is as follows. + * + * All the lines beginning with #GTS_COMMENTS are ignored. The first line + * contains two unsigned integers separated by spaces. The first + * integer is the number of nodes, nn, the second is the number of + * edges, ne. + * + * Follows nn lines containing node description. + * Follows ne lines containing the two indices (starting + * from one) of the nodes of each edge. + * + * The format described above is the least common denominator to all + * GTS files. Consistent with an object-oriented approach, the GTS + * file format is extensible. Each of the lines of the file can be + * extended with user-specific attributes accessible through the + * read() and write() virtual methods of each of the objects written + * (graph, nodes or edges). When read with different object classes, + * these extra attributes are just ignored. + */ +void gts_graph_write (GtsGraph * g, FILE * fp) +{ + guint nnode = 1, nedge = 0; + gpointer data[2]; + + g_return_if_fail (g != NULL); + g_return_if_fail (fp != NULL); + + gts_graph_foreach_edge (g, (GtsFunc) count_edges, &nedge); + fprintf (fp, "%u %u", gts_container_size (GTS_CONTAINER (g)), nedge); + if (GTS_OBJECT (g)->klass->write) + (* GTS_OBJECT (g)->klass->write) (GTS_OBJECT (g), fp); + fputc ('\n', fp); + data[0] = fp; + data[1] = &nnode; + gts_container_foreach (GTS_CONTAINER (g), (GtsFunc) write_node, data); + gts_graph_foreach_edge (g, (GtsFunc) write_edge, fp); + gts_container_foreach (GTS_CONTAINER (g), + (GtsFunc) gts_object_reset_reserved, NULL); +} + +/** + * gts_graph_read: + * @fp: a #GtsFile. + * + * Reads a graph from a file. + * + * Returns: the new #GtsGraph or %NULL if an error occured (in which + * case the @error field of @fp is set). + */ +GtsGraph * gts_graph_read (GtsFile * fp) +{ + GtsGraph * g; + GtsGNode ** nodes; + guint nn, ne, n; + + g_return_val_if_fail (fp != NULL, NULL); + + if (fp->type != GTS_INT) { + gts_file_error (fp, "expecting an integer (number of nodes)"); + return NULL; + } + nn = atoi (fp->token->str); + gts_file_next_token (fp); + + if (fp->type != GTS_INT) { + gts_file_error (fp, "expecting an integer (number of edges)"); + return NULL; + } + ne = atoi (fp->token->str); + + gts_file_next_token (fp); + if (fp->type != '\n') { + GtsObjectClass * klass; + + gts_graph_class (); + gts_gnode_class (); + gts_gedge_class (); + + if (fp->type != GTS_STRING) { + gts_file_error (fp, "expecting a string (GtsGraphClass)"); + return NULL; + } + klass = gts_object_class_from_name (fp->token->str); + if (klass == NULL) { + gts_file_error (fp, "unknown class `%s'", fp->token->str); + return NULL; + } + if (!gts_object_class_is_from_class (klass, gts_graph_class ())) { + gts_file_error (fp, "class `%s' is not a GtsGraphClass", fp->token->str); + return NULL; + } + g = GTS_GRAPH (gts_object_new (klass)); + g->graph_class = GTS_GRAPH_CLASS (klass); + gts_file_next_token (fp); + (* klass->read) ((GtsObject **) &g, fp); + if (fp->type == GTS_ERROR) { + gts_object_destroy (GTS_OBJECT (g)); + return NULL; + } + } + else + g = GTS_GRAPH (gts_object_new (GTS_OBJECT_CLASS (gts_graph_class ()))); + gts_file_first_token_after (fp, '\n'); + if (nn <= 0) + return g; + + nodes = g_malloc ((nn + 1)*sizeof (GtsGNode *)); + + n = 0; + while (n < nn && fp->type != GTS_ERROR) { + GtsObject * new_node = + gts_object_new (GTS_OBJECT_CLASS (g->node_class)); + + gts_container_add (GTS_CONTAINER (g), GTS_CONTAINEE (new_node)); + if (GTS_OBJECT_CLASS (g->node_class)->read) + (*GTS_OBJECT_CLASS (g->node_class)->read) (&new_node, fp); + gts_file_first_token_after (fp, '\n'); + nodes[n++] = GTS_GNODE (new_node); + } + if (fp->type == GTS_ERROR) + nn = n; + + n = 0; + while (n < ne && fp->type != GTS_ERROR) { + guint n1, n2; + + if (fp->type != GTS_INT) + gts_file_error (fp, "expecting an integer (first node index)"); + else { + n1 = atoi (fp->token->str); + if (n1 == 0 || n1 > nn) + gts_file_error (fp, "node index `%d' is out of range `[1,%d]'", + n1, nn); + else { + gts_file_next_token (fp); + if (fp->type != GTS_INT) + gts_file_error (fp, "expecting an integer (second node index)"); + else { + n2 = atoi (fp->token->str); + if (n2 == 0 || n2 > nn) + gts_file_error (fp, "node index `%d' is out of range `[1,%d]'", + n2, nn); + else { + GtsGEdge * new_edge = + gts_gedge_new (g->edge_class, nodes[n1 - 1], nodes [n2 - 1]); + + gts_file_next_token (fp); + if (fp->type != '\n') + if (GTS_OBJECT_CLASS (g->edge_class)->read) + (*GTS_OBJECT_CLASS (g->edge_class)->read) + ((GtsObject **) &new_edge, fp); + gts_file_first_token_after (fp, '\n'); + n++; + } + } + } + } + } + + if (fp->type == GTS_ERROR) { + gts_allow_floating_gnodes = TRUE; + while (nn) + gts_object_destroy (GTS_OBJECT (nodes[nn-- - 1])); + gts_allow_floating_gnodes = FALSE; + } + g_free (nodes); + + if (fp->type == GTS_ERROR) { + gts_object_destroy (GTS_OBJECT (g)); + return NULL; + } + return g; +} + +static void write_dot_node (GtsGNode * node, gpointer * data) +{ + FILE * fp = data[0]; + guint * nnode = data[1]; + + fprintf (fp, " n%u", *nnode); + if (GTS_GNODE_CLASS (GTS_OBJECT (node)->klass)->write) { + fputs (" [", fp); + (* GTS_GNODE_CLASS (GTS_OBJECT (node)->klass)->write) (node, fp); + fputc (']', fp); + } + fputs (";\n", fp); + GTS_OBJECT (node)->reserved = GUINT_TO_POINTER ((*nnode)++); +} + +static void write_dot_edge (GtsGEdge * edge, FILE * fp) +{ + fprintf (fp, " n%u -> n%u", + GPOINTER_TO_UINT (GTS_OBJECT (edge->n1)->reserved), + GPOINTER_TO_UINT (GTS_OBJECT (edge->n2)->reserved)); + if (GTS_GEDGE_CLASS (GTS_OBJECT (edge)->klass)->write) { + fputs (" [", fp); + (* GTS_GEDGE_CLASS (GTS_OBJECT (edge)->klass)->write) (edge, fp); + fputc (']', fp); + } + fputs (";\n", fp); +} + +/** + * gts_graph_write_dot: + * @g: a #GtsGraph. + * @fp: a file pointer. + * + * Writes in the file @fp an ASCII representation of @g in the dot format of + * AT&T Bell Labs. + */ +void gts_graph_write_dot (GtsGraph * g, FILE * fp) +{ + guint nnode = 1; + gpointer data[2]; + + g_return_if_fail (g != NULL); + g_return_if_fail (fp != NULL); + + fprintf (fp, "digraph \"%p\" {\n", g); + data[0] = fp; + data[1] = &nnode; + gts_container_foreach (GTS_CONTAINER (g), (GtsFunc) write_dot_node, data); + gts_graph_foreach_edge (g, (GtsFunc) write_dot_edge, fp); + fputs ("}\n", fp); + + gts_container_foreach (GTS_CONTAINER (g), + (GtsFunc) gts_object_reset_reserved, NULL); +} + +/* GtsWGraph */ + +static gfloat wgraph_weight (GtsGraph * g) +{ + return GTS_WGRAPH (g)->weight; +} + +static void wgraph_add (GtsContainer * g, GtsContainee * n) +{ + GtsWGraph * wg = GTS_WGRAPH (g); + gfloat w = gts_gnode_weight (GTS_GNODE (n)); + + wg->weight += w; + + (* GTS_CONTAINER_CLASS (GTS_OBJECT_CLASS (gts_wgraph_class ())->parent_class)->add) (g, n); +} + +static void wgraph_remove (GtsContainer * g, GtsContainee * n) +{ + GTS_WGRAPH (g)->weight -= gts_gnode_weight (GTS_GNODE (n)); + + (* GTS_CONTAINER_CLASS (GTS_OBJECT_CLASS (gts_wgraph_class ())->parent_class)->remove) (g, n); +} + +static void wgraph_class_init (GtsWGraphClass * klass) +{ + GTS_GRAPH_CLASS (klass)->weight = wgraph_weight; + + GTS_CONTAINER_CLASS (klass)->add = wgraph_add; + GTS_CONTAINER_CLASS (klass)->remove = wgraph_remove; +} + +static void wgraph_init (GtsWGraph * g) +{ + g->weight = 0.; +} + +/** + * gts_wgraph_class: + * + * Returns: the #GtsWGraphClass. + */ +GtsWGraphClass * gts_wgraph_class (void) +{ + static GtsWGraphClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo wgraph_info = { + "GtsWGraph", + sizeof (GtsWGraph), + sizeof (GtsWGraphClass), + (GtsObjectClassInitFunc) wgraph_class_init, + (GtsObjectInitFunc) wgraph_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_graph_class ()), + &wgraph_info); + } + + return klass; +} + +static void weight_max (GtsGNode * n, gfloat * wmax) +{ + gfloat w = gts_gnode_weight (n); + + if (w > *wmax) + *wmax = w; +} + +/** + * gts_wgraph_weight_max: + * @wg: a #GtsWGraph. + * + * Returns: the maximum weight of any vertices belonging to @g. + */ +gfloat gts_wgraph_weight_max (GtsWGraph * wg) +{ + gfloat wmax = - G_MAXFLOAT; + + g_return_val_if_fail (wg != NULL, 0.); + + gts_container_foreach (GTS_CONTAINER (wg), (GtsFunc) weight_max, &wmax); + + return wmax; +} + +/* Surface graph */ + +static void create_node (GtsFace * f, GtsGraph * graph) +{ + GtsFNode * fn = gts_fnode_new (gts_fnode_class (), f); + + gts_container_add (GTS_CONTAINER (graph), GTS_CONTAINEE (fn)); + GTS_OBJECT (f)->reserved = fn; +} + +static void create_edge (GtsEdge * e, GtsSurface * s) +{ + GSList * i = e->triangles; + + while (i) { + GtsFace * f = i->data; + if (GTS_IS_FACE (f) && gts_face_has_parent_surface (f, s)) { + GSList * j = i->next; + while (j) { + GtsFace * f1 = j->data; + if (GTS_IS_FACE (f1) && gts_face_has_parent_surface (f1, s)) + gts_pgedge_new (gts_pgedge_class (), + GTS_OBJECT (f)->reserved, + GTS_OBJECT (f1)->reserved, + e); + j = j->next; + } + } + i = i->next; + } +} + +/** + * gts_surface_graph_new: + * @klass: a #GtsGraphClass. + * @s: a #GtsSurface. + * + * Returns: a new #GtsGraph representing the connectivity of the faces + * of @s. This graph uses #GtsFGNode as nodes which allows to store + * the dependencies between nodes and faces of @s. + */ +GtsGraph * gts_surface_graph_new (GtsGraphClass * klass, + GtsSurface * s) +{ + GtsGraph * graph; + + g_return_val_if_fail (klass != NULL, NULL); + g_return_val_if_fail (s != NULL, NULL); + + graph = GTS_GRAPH (gts_object_new (GTS_OBJECT_CLASS (klass))); + gts_surface_foreach_face (s, (GtsFunc) create_node, graph); + gts_surface_foreach_edge (s, (GtsFunc) create_edge, s); + gts_surface_foreach_face (s, (GtsFunc) gts_object_reset_reserved, NULL); + + return graph; +} + +static void create_segment_edge (GtsSegment * s, GtsGraph * graph) +{ + GtsGNode * n1 = GTS_OBJECT (s->v1)->reserved, * n2; + + if (n1 == NULL) { + n1 = GTS_GNODE (gts_pnode_new (gts_pnode_class (), s->v1)); + gts_container_add (GTS_CONTAINER (graph), GTS_CONTAINEE (n1)); + GTS_OBJECT (s->v1)->reserved = n1; + } + + n2 = GTS_OBJECT (s->v2)->reserved; + if (n2 == NULL) { + n2 = GTS_GNODE (gts_pnode_new (gts_pnode_class (), s->v2)); + gts_container_add (GTS_CONTAINER (graph), GTS_CONTAINEE (n2)); + GTS_OBJECT (s->v2)->reserved = n2; + } + + gts_pgedge_new (gts_pgedge_class (), n1, n2, s); +} + +static void reset_reserved (GtsSegment * s) +{ + GTS_OBJECT (s->v1)->reserved = GTS_OBJECT (s->v2)->reserved = NULL; +} + +/** + * gts_segments_graph_new: + * @klass: a #GtsGraphClass. + * @segments: a list of #GtsSegment. + * + * Returns: a new #GtsGraph representing the connectivity of the segments + * in @segments. + */ +GtsGraph * gts_segments_graph_new (GtsGraphClass * klass, + GSList * segments) +{ + GtsGraph * graph; + + g_return_val_if_fail (klass != NULL, NULL); + + graph = GTS_GRAPH (gts_object_new (GTS_OBJECT_CLASS (klass))); + g_slist_foreach (segments, (GFunc) create_segment_edge, graph); + g_slist_foreach (segments, (GFunc) reset_reserved, NULL); + + return graph; +} + +static void add_to_surface (GtsGNode * n, GtsSurface * s) +{ + if (GTS_IS_FNODE (n)) + gts_surface_add_face (s, GTS_FNODE (n)->f); +} + +/** + * gts_surface_graph_surface: + * @surface_graph: a #GtsGraph using #GtsFGNode as nodes. + * @s: a #GtsSurface. + * + * Returns: a new #GtsSurface using the same classes as @s and + * composed of the faces defined by @surface_graph. + */ +GtsSurface * gts_surface_graph_surface (GtsGraph * surface_graph, + GtsSurface * s) +{ + GtsSurface * s1; + + g_return_val_if_fail (surface_graph != NULL, NULL); + g_return_val_if_fail (s != NULL, NULL); + + s1 = gts_surface_new (GTS_SURFACE_CLASS (GTS_OBJECT (s)->klass), + s->face_class, + s->edge_class, + s->vertex_class); + gts_container_foreach (GTS_CONTAINER (surface_graph), + (GtsFunc) add_to_surface, s1); + return s1; +} + diff --git a/gts/gts-private.h b/gts/gts-private.h new file mode 100644 index 0000000000..59246d10e4 --- /dev/null +++ b/gts/gts-private.h @@ -0,0 +1,37 @@ +/* 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. + */ + +#ifndef __GTS_PRIVATE_H__ +#define __GTS_PRIVATE_H__ + +/* Debugging flags */ + +/* #define DEBUG_FUNCTIONS */ + +#ifdef DEBUG_FUNCTIONS +/* #define DEBUG_LEAKS */ +#define DEBUG_IDENTITY +guint id (gpointer p); +void id_insert (gpointer p); +void id_remove (gpointer p); +void gts_write_triangle (GtsTriangle * t, GtsPoint * o, FILE * fptr); +void gts_write_segment (GtsSegment * s, GtsPoint * o, FILE * fptr); +#endif /* DEBUG_FUNCTIONS */ + +#endif /* __GTS_PRIVATE_H__ */ diff --git a/gts/gts.h b/gts/gts.h new file mode 100644 index 0000000000..9397230bff --- /dev/null +++ b/gts/gts.h @@ -0,0 +1,2577 @@ +/* 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. + */ + +#ifndef __GTS_H__ +#define __GTS_H__ + +#include +#include + +#define GTS_MAJOR_VERSION 0 +#define GTS_MINOR_VERSION 7 +#define GTS_MICRO_VERSION 6 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Added based on glib.h by M J Loehr 01/01/01 */ +/* GTS version. + * we prefix variable declarations so they can + * properly get exported in windows dlls. + */ +#ifdef NATIVE_WIN32 +# ifdef GTS_COMPILATION +# define GTS_C_VAR __declspec(dllexport) +# else /* not GTS_COMPILATION */ +# define GTS_C_VAR extern __declspec(dllimport) +# endif /* not GTS_COMPILATION */ +#else /* not NATIVE_WIN32 */ +# define GTS_C_VAR extern +#endif /* not NATIVE_WIN32 */ + +GTS_C_VAR const guint gts_major_version; +GTS_C_VAR const guint gts_minor_version; +GTS_C_VAR const guint gts_micro_version; +GTS_C_VAR const guint gts_interface_age; +GTS_C_VAR const guint gts_binary_age; + +#define GTS_CHECK_VERSION(major,minor,micro) \ + (gts_major_version > (major) || \ + (gts_major_version == (major) && gts_minor_version > (minor)) || \ + (gts_major_version == (major) && gts_minor_version == (minor) && \ + gts_micro_version >= (micro))) + +#define GTS_COMMENTS "#!" +#define GTS_MAINTAINER "popinet@users.sourceforge.net" + + + + +void gts_predicates_init(); + + +/* Class declarations for base types */ + +typedef struct _GtsObjectClassInfo GtsObjectClassInfo; +typedef struct _GtsObject GtsObject; +typedef struct _GtsObjectClass GtsObjectClass; +typedef struct _GtsPoint GtsPoint; +typedef struct _GtsPointClass GtsPointClass; +typedef struct _GtsVertex GtsVertex; +typedef struct _GtsVertexClass GtsVertexClass; +typedef struct _GtsSegment GtsSegment; +typedef struct _GtsSegmentClass GtsSegmentClass; +typedef struct _GtsEdge GtsEdge; +typedef struct _GtsEdgeClass GtsEdgeClass; +typedef struct _GtsTriangle GtsTriangle; +typedef struct _GtsTriangleClass GtsTriangleClass; +typedef struct _GtsFace GtsFace; +typedef struct _GtsFaceClass GtsFaceClass; +typedef struct _GtsBBox GtsBBox; +typedef struct _GtsBBoxClass GtsBBoxClass; +typedef struct _GtsSurface GtsSurface; +typedef struct _GtsSurfaceClass GtsSurfaceClass; + +typedef void (*GtsObjectClassInitFunc) (GtsObjectClass * objclass); +typedef void (*GtsObjectInitFunc) (GtsObject * obj); +typedef void (*GtsArgSetFunc) (GtsObject * obj); +typedef void (*GtsArgGetFunc) (GtsObject * obj); + +typedef gdouble GtsVector[3]; +typedef gdouble GtsVector4[4]; +typedef GtsVector4 GtsMatrix; +/** + * GtsKeyFunc: + * @item: A pointer to an item to be stored in the heap. + * @data: User data passed to gts_eheap_new(). + * + * Returns: the value of the key for the given item. + */ +typedef gdouble (*GtsKeyFunc) (gpointer item, + gpointer data); +typedef enum +{ + GTS_OUT = -1, + GTS_ON = 0, + GTS_IN = 1 +} GtsIntersect; + +typedef struct _GtsColor GtsColor; + +struct _GtsColor { + gfloat r, g, b; +}; + +typedef gint (*GtsFunc) (gpointer item, + gpointer data); + +/* misc.c */ + +typedef struct _GtsFile GtsFile; + +typedef enum { + GTS_NONE = 1 << 8, + GTS_INT = 1 << 9, + GTS_UINT = 1 << 10, + GTS_FLOAT = 1 << 11, + GTS_DOUBLE = 1 << 12, + GTS_STRING = 1 << 13, + GTS_FILE = 1 << 14, + GTS_ERROR = 1 << 15 +} GtsTokenType; + +struct _GtsFile { + FILE * fp; + gchar * s, * s1; + guint line, pos; + GString * token; + GtsTokenType type; + gchar * error; + + guint curline, curpos; + guint scope, scope_max; + gint next_token; + gchar * delimiters; + gchar * comments; + gchar * tokens; +}; + +typedef struct _GtsFileVariable GtsFileVariable; + +struct _GtsFileVariable { + GtsTokenType type; + gchar name[30]; + gboolean unique; + gpointer data; + gboolean set; + guint line, pos; +}; + + +GtsFile * gts_file_new (FILE * fp); +GtsFile * gts_file_new_from_string (const gchar * s); +void gts_file_verror (GtsFile * f, + const gchar * format, + va_list args); +void gts_file_error (GtsFile * f, + const gchar * format, + ...); +gint gts_file_getc (GtsFile * f); +guint gts_file_read (GtsFile * f, + gpointer ptr, + guint size, + guint nmemb); +gint gts_file_getc_scope (GtsFile * f); +void gts_file_next_token (GtsFile * f); +void gts_file_first_token_after (GtsFile * f, + GtsTokenType type); +void gts_file_assign_start (GtsFile * f, + GtsFileVariable * vars); +GtsFileVariable * gts_file_assign_next (GtsFile * f, + GtsFileVariable * vars); +void gts_file_assign_variables (GtsFile * f, + GtsFileVariable * vars); +void gts_file_variable_error (GtsFile * f, + GtsFileVariable * vars, + const gchar * name, + const gchar * format, + ...); +void gts_file_destroy (GtsFile * f); + +/* Objects: object.c */ + +#ifdef GTS_CHECK_CASTS +# define GTS_OBJECT_CAST(obj, type, klass) ((type *) gts_object_check_cast (obj, klass)) +# define GTS_OBJECT_CLASS_CAST(objklass, type, klass) ((type *) gts_object_class_check_cast (objklass, klass)) +#else /* not GTS_CHECK_CASTS */ +# define GTS_OBJECT_CAST(obj, type, klass) ((type *) (obj)) +# define GTS_OBJECT_CLASS_CAST(objklass, type, klass) ((type *) (objklass)) +#endif /* not GTS_CHECK_CASTS */ + +#define GTS_CLASS_NAME_LENGTH 40 +#define GTS_OBJECT(obj) GTS_OBJECT_CAST (obj,\ + GtsObject,\ + gts_object_class ()) +#define GTS_OBJECT_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsObjectClass,\ + gts_object_class()) +#define GTS_IS_OBJECT(obj) (gts_object_is_from_class (obj,\ + gts_object_class ())) + +typedef enum +{ + GTS_DESTROYED = 1 << 0, + GTS_USER_FLAG = 1 /* user flags start from here */ +} GtsObjectFlags; + +#define GTS_OBJECT_FLAGS(obj) (GTS_OBJECT (obj)->flags) +#define GTS_OBJECT_DESTROYED(obj) ((GTS_OBJECT_FLAGS (obj) & GTS_DESTROYED) != 0) +#define GTS_OBJECT_SET_FLAGS(obj,flag) G_STMT_START{ (GTS_OBJECT_FLAGS (obj) |= (flag)); }G_STMT_END +#define GTS_OBJECT_UNSET_FLAGS(obj,flag) G_STMT_START{ (GTS_OBJECT_FLAGS (obj) &= ~(flag)); }G_STMT_END + +struct _GtsObjectClassInfo { + gchar name[GTS_CLASS_NAME_LENGTH]; + guint object_size; + guint class_size; + GtsObjectClassInitFunc class_init_func; + GtsObjectInitFunc object_init_func; + GtsArgSetFunc arg_set_func; + GtsArgGetFunc arg_get_func; +}; + +struct _GtsObject { + GtsObjectClass * klass; + + gpointer reserved; + guint32 flags; +}; + +struct _GtsObjectClass { + GtsObjectClassInfo info; + GtsObjectClass * parent_class; + + void (* clone) (GtsObject *, GtsObject *); + void (* destroy) (GtsObject *); + void (* read) (GtsObject **, GtsFile *); + void (* write) (GtsObject *, FILE *); + GtsColor (* color) (GtsObject *); + void (* attributes) (GtsObject *, GtsObject *); +}; + +gpointer gts_object_class_new (GtsObjectClass * parent_class, + GtsObjectClassInfo * info); +GtsObjectClass * gts_object_class (void); +gpointer gts_object_check_cast (gpointer object, + gpointer klass); +gpointer gts_object_class_check_cast (gpointer klass, + gpointer from); + +static inline +gpointer gts_object_is_from_class (gpointer object, + gpointer klass) +{ + GtsObjectClass * c; + + g_return_val_if_fail (klass != NULL, NULL); + + if (object == NULL) + return NULL; + + c = ((GtsObject *) object)->klass; + + g_return_val_if_fail (c != NULL, NULL); + + while (c) { + if (c == klass) + return object; + c = c->parent_class; + } + + return NULL; +} + +static inline +gpointer gts_object_class_is_from_class (gpointer klass, + gpointer from) +{ + GtsObjectClass * c; + + g_return_val_if_fail (klass != NULL, NULL); + g_return_val_if_fail (from != NULL, NULL); + + c = (GtsObjectClass *) klass; + while (c) { + if (c == from) + return klass; + c = c->parent_class; + } + + return NULL; +} + +GtsObjectClass * gts_object_class_from_name (const gchar * name); + +GtsObject * gts_object_new (GtsObjectClass * klass); +GtsObject * gts_object_clone (GtsObject * object); +void gts_object_attributes (GtsObject * object, + GtsObject * from); +void gts_object_init (GtsObject * object, + GtsObjectClass * klass); +void gts_object_reset_reserved (GtsObject * object); +void gts_object_destroy (GtsObject * object); +void gts_finalize (void); + +/* Ranges: surface.c */ +typedef struct _GtsRange GtsRange; + +struct _GtsRange { + gdouble min, max, sum, sum2, mean, stddev; + guint n; +}; + +void gts_range_init (GtsRange * r); +void gts_range_reset (GtsRange * r); +void gts_range_add_value (GtsRange * r, + gdouble val); +void gts_range_update (GtsRange * r); +void gts_range_print (GtsRange * r, + FILE * fptr); + +/* Points: point.c */ + +#define GTS_IS_POINT(obj) (gts_object_is_from_class (obj,\ + gts_point_class ())) +#define GTS_POINT(obj) GTS_OBJECT_CAST (obj,\ + GtsPoint,\ + gts_point_class ()) +#define GTS_POINT_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsPointClass,\ + gts_point_class ()) + +struct _GtsPoint { + GtsObject object; + + gdouble x, y, z; /* must be contiguous (cast to robust functions) */ +}; + +struct _GtsPointClass { + GtsObjectClass parent_class; + gboolean binary; +}; + +GtsPointClass * gts_point_class (void); +GtsPoint * gts_point_new (GtsPointClass * klass, + gdouble x, + gdouble y, + gdouble z); +void gts_point_set (GtsPoint * p, + gdouble x, + gdouble y, + gdouble z); +#define gts_point_is_in_rectangle(p, p1, p2) ((p)->x >= (p1)->x &&\ + (p)->x <= (p2)->x &&\ + (p)->y >= (p1)->y &&\ + (p)->y <= (p2)->y &&\ + (p)->z >= (p1)->z &&\ + (p)->z <= (p2)->z) +GtsPoint * gts_segment_triangle_intersection (GtsSegment * s, + GtsTriangle * t, + gboolean boundary, + GtsPointClass * klass); +void gts_point_transform (GtsPoint * p, + GtsMatrix * m); +gdouble gts_point_distance (GtsPoint * p1, + GtsPoint * p2); +gdouble gts_point_distance2 (GtsPoint * p1, + GtsPoint * p2); +gdouble gts_point_orientation_3d (GtsPoint * p1, + GtsPoint * p2, + GtsPoint * p3, + GtsPoint * p4); +gint gts_point_orientation_3d_sos (GtsPoint * p1, + GtsPoint * p2, + GtsPoint * p3, + GtsPoint * p4); +GtsIntersect gts_point_is_in_triangle (GtsPoint * p, + GtsTriangle * t); +gdouble gts_point_in_circle (GtsPoint * p, + GtsPoint * p1, + GtsPoint * p2, + GtsPoint * p3); +gdouble gts_point_in_sphere (GtsPoint * p, + GtsPoint * p1, + GtsPoint * p2, + GtsPoint * p3, + GtsPoint * p4); +gdouble gts_point_in_triangle_circle (GtsPoint * p, + GtsTriangle * t); +gdouble gts_point_orientation (GtsPoint * p1, + GtsPoint * p2, + GtsPoint * p3); +gint gts_point_orientation_sos (GtsPoint * p1, + GtsPoint * p2, + GtsPoint * p3); +gdouble gts_point_segment_distance2 (GtsPoint * p, + GtsSegment * s); +gdouble gts_point_segment_distance (GtsPoint * p, + GtsSegment * s); +void gts_point_segment_closest (GtsPoint * p, + GtsSegment * s, + GtsPoint * closest); +gdouble gts_point_triangle_distance2 (GtsPoint * p, + GtsTriangle * t); +gdouble gts_point_triangle_distance (GtsPoint * p, + GtsTriangle * t); +void gts_point_triangle_closest (GtsPoint * p, + GtsTriangle * t, + GtsPoint * closest); +gboolean gts_point_is_inside_surface (GtsPoint * p, + GNode * tree, + gboolean is_open); + +/* Vertices: vertex.c */ + +#define GTS_IS_VERTEX(obj) (gts_object_is_from_class (obj,\ + gts_vertex_class ())) +#define GTS_VERTEX(obj) GTS_OBJECT_CAST (obj,\ + GtsVertex,\ + gts_vertex_class ()) +#define GTS_VERTEX_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsVertexClass,\ + gts_vertex_class ()) +struct _GtsVertex { + GtsPoint p; + + GSList * segments; +}; + +struct _GtsVertexClass { + GtsPointClass parent_class; + + void (* intersection_attributes) (GtsVertex *, + GtsObject *, + GtsObject *); +}; + +GTS_C_VAR +gboolean gts_allow_floating_vertices; + +GtsVertexClass * gts_vertex_class (void); +GtsVertex * gts_vertex_new (GtsVertexClass * klass, + gdouble x, + gdouble y, + gdouble z); +void gts_vertex_replace (GtsVertex * v, + GtsVertex * with); +gboolean gts_vertex_is_unattached (GtsVertex * v); +GtsSegment * gts_vertices_are_connected (GtsVertex * v1, + GtsVertex * v2); +GSList * gts_vertex_triangles (GtsVertex * v, + GSList * list); +GSList * gts_vertex_faces (GtsVertex * v, + GtsSurface * surface, + GSList * list); +GSList * gts_vertex_neighbors (GtsVertex * v, + GSList * list, + GtsSurface * surface); +GSList * gts_vertices_from_segments (GSList * segments); +gboolean gts_vertex_is_boundary (GtsVertex * v, + GtsSurface * surface); +GList * gts_vertices_merge (GList * vertices, + gdouble epsilon, + gboolean (* check) (GtsVertex *, GtsVertex *)); +GSList * gts_vertex_fan_oriented (GtsVertex * v, + GtsSurface * surface); +guint gts_vertex_is_contact (GtsVertex * v, gboolean sever); + +/* GtsVertexNormal: Header */ + +typedef struct _GtsVertexNormal GtsVertexNormal; + +struct _GtsVertexNormal { + /*< private >*/ + GtsVertex parent; + + /*< public >*/ + GtsVector n; +}; + +#define GTS_VERTEX_NORMAL(obj) GTS_OBJECT_CAST (obj,\ + GtsVertexNormal,\ + gts_vertex_normal_class ()) +#define GTS_IS_VERTEX_NORMAL(obj) (gts_object_is_from_class (obj,\ + gts_vertex_normal_class ())) + +GtsVertexClass * gts_vertex_normal_class (void); + +/* GtsColorVertex: Header */ + +typedef struct _GtsColorVertex GtsColorVertex; + +struct _GtsColorVertex { + /*< private >*/ + GtsVertex parent; + + /*< public >*/ + GtsColor c; +}; + +#define GTS_COLOR_VERTEX(obj) GTS_OBJECT_CAST (obj,\ + GtsColorVertex,\ + gts_color_vertex_class ()) +#define GTS_IS_COLOR_VERTEX(obj) (gts_object_is_from_class (obj,\ + gts_color_vertex_class ())) + +GtsVertexClass * gts_color_vertex_class (void); + +/* Segments: segment.c */ + +#define GTS_IS_SEGMENT(obj) (gts_object_is_from_class (obj,\ + gts_segment_class ())) +#define GTS_SEGMENT(obj) GTS_OBJECT_CAST (obj,\ + GtsSegment,\ + gts_segment_class ()) +#define GTS_SEGMENT_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsSegmentClass,\ + gts_segment_class ()) + +struct _GtsSegment { + GtsObject object; + + GtsVertex * v1; + GtsVertex * v2; +}; + +struct _GtsSegmentClass { + GtsObjectClass parent_class; +}; + +GtsSegmentClass * gts_segment_class (void); +GtsSegment * gts_segment_new (GtsSegmentClass * klass, + GtsVertex * v1, + GtsVertex * v2); +#define gts_segment_connect(s, e1, e2) (((s)->v1 == e1 &&\ + (s)->v2 == e2) || \ + ((s)->v1 == e2 &&\ + (s)->v2 == e1)) +#define gts_segments_are_identical(s1, s2) (((s1)->v1 == (s2)->v1 &&\ + (s1)->v2 == (s2)->v2)\ + ||\ + ((s1)->v1 == (s2)->v2 &&\ + (s1)->v2 == (s2)->v1)) +#define gts_segments_touch(s1, s2) ((s1)->v1 == (s2)->v1 ||\ + (s1)->v1 == (s2)->v2 ||\ + (s1)->v2 == (s2)->v1 ||\ + (s1)->v2 == (s2)->v2) +GtsIntersect gts_segments_are_intersecting (GtsSegment * s1, + GtsSegment * s2); +GtsSegment * gts_segment_is_duplicate (GtsSegment * s); +GtsVertex * gts_segment_midvertex (GtsSegment * s, + GtsVertexClass * klass); +GSList * gts_segments_from_vertices (GSList * vertices); +gboolean gts_segment_is_ok (GtsSegment * s); + +/* Edges: edge.c */ + +#define GTS_IS_EDGE(obj) (gts_object_is_from_class (obj,\ + gts_edge_class ())) +#define GTS_EDGE(obj) GTS_OBJECT_CAST (obj,\ + GtsEdge,\ + gts_edge_class ()) +#define GTS_EDGE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsEdgeClass,\ + gts_edge_class ()) + +struct _GtsEdge { + GtsSegment segment; + + GSList * triangles; +}; + +struct _GtsEdgeClass { + GtsSegmentClass parent_class; +}; + +GTS_C_VAR +gboolean gts_allow_floating_edges; + +GtsEdgeClass * gts_edge_class (void); +GtsEdge * gts_edge_new (GtsEdgeClass * klass, + GtsVertex * v1, + GtsVertex * v2); +/** + * gts_edge_is_unattached: + * @s: a #GtsEdge. + * + * Evaluates to %TRUE if no triangles uses @s as an edge, %FALSE otherwise. + */ +#define gts_edge_is_unattached(s) ((s)->triangles == NULL ? TRUE : FALSE) +GtsFace * gts_edge_has_parent_surface (GtsEdge * e, + GtsSurface * surface); +GtsFace * gts_edge_has_any_parent_surface (GtsEdge * e); +GtsFace * gts_edge_is_boundary (GtsEdge * e, + GtsSurface * surface); +void gts_edge_replace (GtsEdge * e, + GtsEdge * with); +GSList * gts_edges_from_vertices (GSList * vertices, + GtsSurface * parent); +guint gts_edge_face_number (GtsEdge * e, + GtsSurface * s); +gboolean gts_edge_collapse_is_valid (GtsEdge * e); +gboolean gts_edge_collapse_creates_fold (GtsEdge * e, + GtsVertex * v, + gdouble max); +GtsEdge * gts_edge_is_duplicate (GtsEdge * e); +GList * gts_edges_merge (GList * edges); +gboolean gts_edge_belongs_to_tetrahedron (GtsEdge * e); +guint gts_edge_is_contact (GtsEdge * e); +void gts_edge_swap (GtsEdge * e, + GtsSurface * s); +gboolean gts_edge_manifold_faces (GtsEdge * e, + GtsSurface * s, + GtsFace ** f1, + GtsFace ** f2); + +/* Triangles: triangle.c */ + +#define GTS_IS_TRIANGLE(obj) (gts_object_is_from_class (obj,\ + gts_triangle_class ())) +#define GTS_TRIANGLE(obj) GTS_OBJECT_CAST (obj,\ + GtsTriangle,\ + gts_triangle_class ()) +#define GTS_TRIANGLE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsTriangleClass,\ + gts_triangle_class ()) + +struct _GtsTriangle { + GtsObject object; + + GtsEdge * e1; + GtsEdge * e2; + GtsEdge * e3; +}; + +struct _GtsTriangleClass { + GtsObjectClass parent_class; +}; + +GtsTriangleClass * gts_triangle_class (void); +void gts_triangle_set (GtsTriangle * triangle, + GtsEdge * e1, + GtsEdge * e2, + GtsEdge * e3); +GtsTriangle * gts_triangle_new (GtsTriangleClass * klass, + GtsEdge * e1, + GtsEdge * e2, + GtsEdge * e3); +#define gts_triangle_vertex(t) (GTS_SEGMENT (GTS_TRIANGLE (t)->e1)->v1 ==\ + GTS_SEGMENT (GTS_TRIANGLE (t)->e2)->v1 || \ + GTS_SEGMENT (GTS_TRIANGLE (t)->e1)->v2 ==\ + GTS_SEGMENT (GTS_TRIANGLE (t)->e2)->v1 ? \ + GTS_SEGMENT (GTS_TRIANGLE (t)->e2)->v2 :\ + GTS_SEGMENT (GTS_TRIANGLE (t)->e2)->v1) +GtsVertex * gts_triangle_vertex_opposite (GtsTriangle * t, + GtsEdge * e); +GtsEdge * gts_triangle_edge_opposite (GtsTriangle * t, + GtsVertex * v); +gdouble gts_triangles_angle (GtsTriangle * t1, + GtsTriangle * t2); +gboolean gts_triangles_are_compatible (GtsTriangle * t1, + GtsTriangle * t2, + GtsEdge * e); +gdouble gts_triangle_area (GtsTriangle * t); +gdouble gts_triangle_perimeter (GtsTriangle * t); +gdouble gts_triangle_quality (GtsTriangle * t); +void gts_triangle_normal (GtsTriangle * t, + gdouble * x, + gdouble * y, + gdouble * z); +gdouble gts_triangle_orientation (GtsTriangle * t); +void gts_triangle_revert (GtsTriangle * t); +GSList * gts_triangles_from_edges (GSList * edges); +void gts_triangle_vertices_edges (GtsTriangle * t, + GtsEdge * e, + GtsVertex ** v1, + GtsVertex ** v2, + GtsVertex ** v3, + GtsEdge ** e1, + GtsEdge ** e2, + GtsEdge ** e3); +GtsTriangle * gts_triangle_enclosing (GtsTriangleClass * klass, + GSList * points, + gdouble scale); +guint gts_triangle_neighbor_number (GtsTriangle * t); +GSList * gts_triangle_neighbors (GtsTriangle * t); +GtsEdge * gts_triangles_common_edge (GtsTriangle * t1, + GtsTriangle * t2); +GtsTriangle * gts_triangle_is_duplicate (GtsTriangle * t); +GtsTriangle * gts_triangle_use_edges (GtsEdge * e1, + GtsEdge * e2, + GtsEdge * e3); +gboolean gts_triangle_is_ok (GtsTriangle * t); +void gts_triangle_vertices (GtsTriangle * t, + GtsVertex ** v1, + GtsVertex ** v2, + GtsVertex ** v3); +GtsPoint * gts_triangle_circumcircle_center (GtsTriangle * t, + GtsPointClass * point_class); +gboolean gts_triangles_are_folded (GSList * triangles, + GtsVertex * A, GtsVertex * B, + gdouble max); +GtsObject * gts_triangle_is_stabbed (GtsTriangle * t, + GtsPoint * p, + gdouble * orientation); +void gts_triangle_interpolate_height (GtsTriangle * t, + GtsPoint * p); + +/* Faces: face.c */ + +#define GTS_IS_FACE(obj) (gts_object_is_from_class (obj,\ + gts_face_class ())) +#define GTS_FACE(obj) GTS_OBJECT_CAST (obj,\ + GtsFace,\ + gts_face_class ()) +#define GTS_FACE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsFaceClass,\ + gts_face_class ()) + +struct _GtsFace { + GtsTriangle triangle; + + GSList * surfaces; +}; + +struct _GtsFaceClass { + GtsTriangleClass parent_class; +}; + +GTS_C_VAR +gboolean gts_allow_floating_faces; + +GtsFaceClass * gts_face_class (void); +GtsFace * gts_face_new (GtsFaceClass * klass, + GtsEdge * e1, + GtsEdge * e2, + GtsEdge * e3); +gboolean gts_face_has_parent_surface (GtsFace * f, + GtsSurface * s); +GSList * gts_faces_from_edges (GSList * edges, + GtsSurface * s); +guint gts_face_neighbor_number (GtsFace * f, + GtsSurface * s); +GSList * gts_face_neighbors (GtsFace * f, + GtsSurface * s); +void gts_face_foreach_neighbor (GtsFace * f, + GtsSurface * s, + GtsFunc func, + gpointer data); +gboolean gts_face_is_compatible (GtsFace * f, + GtsSurface * s); + +/* Matrices: matrix.c */ + +#define gts_vector_cross(C,A,B) ((C)[0] = (A)[1]*(B)[2] - (A)[2]*(B)[1],\ + (C)[1] = (A)[2]*(B)[0] - (A)[0]*(B)[2],\ + (C)[2] = (A)[0]*(B)[1] - (A)[1]*(B)[0]) + +#define gts_vector_init(v, p1, p2) ((v)[0] = (p2)->x - (p1)->x,\ + (v)[1] = (p2)->y - (p1)->y,\ + (v)[2] = (p2)->z - (p1)->z) +#define gts_vector_scalar(v1, v2) ((v1)[0]*(v2)[0] +\ + (v1)[1]*(v2)[1] +\ + (v1)[2]*(v2)[2]) +#define gts_vector_norm(v) (sqrt ((v)[0]*(v)[0] +\ + (v)[1]*(v)[1] +\ + (v)[2]*(v)[2])) +#define gts_vector_normalize(v) {\ + gdouble __gts_n = gts_vector_norm (v);\ + if (__gts_n > 0.) {\ + (v)[0] /= __gts_n;\ + (v)[1] /= __gts_n;\ + (v)[2] /= __gts_n;\ + }\ +} +GtsMatrix * gts_matrix_new (gdouble a00, gdouble a01, gdouble a02, gdouble a03, + gdouble a10, gdouble a11, gdouble a12, gdouble a13, + gdouble a20, gdouble a21, gdouble a22, gdouble a23, + gdouble a30, gdouble a31, gdouble a32, gdouble a33); +void gts_matrix_assign (GtsMatrix * m, + gdouble a00, gdouble a01, gdouble a02, gdouble a03, + gdouble a10, gdouble a11, gdouble a12, gdouble a13, + gdouble a20, gdouble a21, gdouble a22, gdouble a23, + gdouble a30, gdouble a31, gdouble a32, gdouble a33); +GtsMatrix * gts_matrix_projection (GtsTriangle * t); +GtsMatrix * gts_matrix_transpose (GtsMatrix * m); +gdouble gts_matrix_determinant (GtsMatrix * m); +GtsMatrix * gts_matrix_inverse (GtsMatrix * m); +GtsMatrix * gts_matrix3_inverse (GtsMatrix * m); +void gts_matrix_print (GtsMatrix * m, + FILE * fptr); +guint gts_matrix_compatible_row (GtsMatrix * A, + GtsVector b, + guint n, + GtsVector A1, + gdouble b1); +guint gts_matrix_quadratic_optimization (GtsMatrix * A, + GtsVector b, + guint n, + GtsMatrix * H, + GtsVector c); +GtsMatrix * gts_matrix_product (GtsMatrix * m1, + GtsMatrix * m2); +GtsMatrix * gts_matrix_zero (GtsMatrix * m); +GtsMatrix * gts_matrix_identity (GtsMatrix * m); +GtsMatrix * gts_matrix_scale (GtsMatrix * m, + GtsVector s); +GtsMatrix * gts_matrix_translate (GtsMatrix * m, + GtsVector t); +GtsMatrix * gts_matrix_rotate (GtsMatrix * m, + GtsVector r, + gdouble angle); +void gts_matrix_destroy (GtsMatrix * m); +void gts_vector_print (GtsVector v, + FILE * fptr); +void gts_vector4_print (GtsVector4 v, + FILE * fptr); + +/* Kdtrees: kdtree.c */ + +#define gts_kdtree_destroy(tree) g_node_destroy(tree) + +GNode * gts_kdtree_new (GPtrArray * points, + int (*compare) + (const void *, + const void *)); +GSList * gts_kdtree_range (GNode * tree, + GtsBBox * bbox, + int (*compare) + (const void *, + const void *)); + +/* Bboxtrees: bbtree.c */ + +/** + * GtsBBTreeTraverseFunc: + * @bb1: a #GtsBBox. + * @bb2: another #GtsBBox. + * @data: user data passed to the function. + * + * User function called for each pair of overlapping bounding + * boxes. See gts_bb_tree_traverse_overlapping(). + */ +typedef void (*GtsBBTreeTraverseFunc) (GtsBBox * bb1, + GtsBBox * bb2, + gpointer data); +/** + * GtsBBoxDistFunc: + * @p: a #GtsPoint. + * @bounded: an object bounded by a #GtsBBox. + * + * User function returning the (minimum) distance between the object + * defined by @bounded and point @p. + * + * Returns: the distance between @p and @bounded. + */ +typedef gdouble (*GtsBBoxDistFunc) (GtsPoint * p, + gpointer bounded); +/** + * GtsBBoxClosestFunc: + * @p: a #GtsPoint. + * @bounded: an object bounded by a #GtsBBox. + * + * User function returning a #GtsPoint belonging to the object defined + * by @bounded and closest to @p. + * + * Returns: a #GtsPoint. + */ +typedef GtsPoint * (*GtsBBoxClosestFunc) (GtsPoint * p, + gpointer bounded); + +/** + * GTS_IS_BBOX: + * @obj: a #GtsObject. + * + * Evaluates to %TRUE if @obj is a #GtsBBox, %FALSE otherwise. + */ +#define GTS_IS_BBOX(obj) (gts_object_is_from_class (obj,\ + gts_bbox_class ())) +/** + * GTS_BBOX: + * @obj: a #GtsObject. + * + * Casts @obj to #GtsBBox. + */ +#define GTS_BBOX(obj) GTS_OBJECT_CAST (obj,\ + GtsBBox,\ + gts_bbox_class ()) +/** + * GTS_BBOX_CLASS: + * @klass: a descendant of #GtsBBoxClass. + * + * Casts @klass to #GtsBBoxClass. + */ +#define GTS_BBOX_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsBBoxClass,\ + gts_bbox_class ()) + +struct _GtsBBox { + GtsObject object; + gpointer bounded; + gdouble x1, y1, z1; + gdouble x2, y2, z2; +}; + +struct _GtsBBoxClass { + GtsObjectClass parent_class; +}; + +GtsBBoxClass * gts_bbox_class (void); +GtsBBox * gts_bbox_new (GtsBBoxClass * klass, + gpointer bounded, + gdouble x1, + gdouble y1, + gdouble z1, + gdouble x2, + gdouble y2, + gdouble z2); +void gts_bbox_set (GtsBBox * bbox, + gpointer bounded, + gdouble x1, + gdouble y1, + gdouble z1, + gdouble x2, + gdouble y2, + gdouble z2); +GtsBBox * gts_bbox_segment (GtsBBoxClass * klass, + GtsSegment * s); +GtsBBox * gts_bbox_triangle (GtsBBoxClass * klass, + GtsTriangle * t); +GtsBBox * gts_bbox_surface (GtsBBoxClass * klass, + GtsSurface * surface); +GtsBBox * gts_bbox_bboxes (GtsBBoxClass * klass, + GSList * bboxes); +GtsBBox * gts_bbox_points (GtsBBoxClass * klass, + GSList * points); +/** + * gts_bbox_point_is_inside: + * @bbox: a #GtsBBox. + * @p: a #GtsPoint. + * + * Evaluates to %TRUE if @p is inside (or on the boundary) of @bbox, %FALSE otherwise. + */ +#define gts_bbox_point_is_inside(bbox, p) ((p)->x >= (bbox)->x1 &&\ + (p)->y >= (bbox)->y1 &&\ + (p)->z >= (bbox)->z1 &&\ + (p)->x <= (bbox)->x2 &&\ + (p)->y <= (bbox)->y2 &&\ + (p)->z <= (bbox)->z2) +gboolean gts_bboxes_are_overlapping (GtsBBox * bb1, + GtsBBox * bb2); +void gts_bbox_draw (GtsBBox * bb, + FILE * fptr); +gdouble gts_bbox_diagonal2 (GtsBBox * bb); +void gts_bbox_point_distance2 (GtsBBox * bb, + GtsPoint * p, + gdouble * min, + gdouble * max); +gboolean gts_bbox_is_stabbed (GtsBBox * bb, + GtsPoint * p); +gboolean gts_bbox_overlaps_triangle (GtsBBox * bb, + GtsTriangle * t); +gboolean gts_bbox_overlaps_segment (GtsBBox * bb, + GtsSegment * s); + +GNode * gts_bb_tree_new (GSList * bboxes); +GNode * gts_bb_tree_surface (GtsSurface * s); +GSList * gts_bb_tree_stabbed (GNode * tree, + GtsPoint * p); +GSList * gts_bb_tree_overlap (GNode * tree, + GtsBBox * bbox); +gboolean gts_bb_tree_is_overlapping (GNode * tree, + GtsBBox * bbox); +void gts_bb_tree_traverse_overlapping (GNode * tree1, + GNode * tree2, + GtsBBTreeTraverseFunc func, + gpointer data); +void gts_bb_tree_draw (GNode * tree, + guint depth, + FILE * fptr); +GSList * gts_bb_tree_point_closest_bboxes (GNode * tree, + GtsPoint * p); +gdouble gts_bb_tree_point_distance (GNode * tree, + GtsPoint * p, + GtsBBoxDistFunc distance, + GtsBBox ** bbox); +GtsPoint * gts_bb_tree_point_closest (GNode * tree, + GtsPoint * p, + GtsBBoxClosestFunc closest, + gdouble * distance); +void gts_bb_tree_segment_distance (GNode * tree, + GtsSegment * s, + GtsBBoxDistFunc distance, + gdouble delta, + GtsRange * range); +void gts_bb_tree_triangle_distance (GNode * tree, + GtsTriangle * t, + GtsBBoxDistFunc distance, + gdouble delta, + GtsRange * range); +void gts_bb_tree_surface_distance (GNode * tree, + GtsSurface * s, + GtsBBoxDistFunc distance, + gdouble delta, + GtsRange * range); +void gts_bb_tree_surface_boundary_distance + (GNode * tree, + GtsSurface * s, + GtsBBoxDistFunc distance, + gdouble delta, + GtsRange * range); +void gts_bb_tree_destroy (GNode * tree, + gboolean free_leaves); + +/* Surfaces: surface.c */ + +typedef struct _GtsSurfaceStats GtsSurfaceStats; +typedef struct _GtsSurfaceQualityStats GtsSurfaceQualityStats; +typedef GtsVertex * (*GtsRefineFunc) (GtsEdge * e, + GtsVertexClass * klass, + gpointer data); +typedef GtsVertex * (*GtsCoarsenFunc) (GtsEdge * e, + GtsVertexClass * klass, + gpointer data); +typedef gboolean (*GtsStopFunc) (gdouble cost, + guint nedge, + gpointer data); + +struct _GtsSurfaceStats { + guint n_faces; + guint n_incompatible_faces; + guint n_duplicate_faces; + guint n_duplicate_edges; + guint n_boundary_edges; + guint n_non_manifold_edges; + GtsRange edges_per_vertex, faces_per_edge; + GtsSurface * parent; +}; + +struct _GtsSurfaceQualityStats { + GtsRange face_quality; + GtsRange face_area; + GtsRange edge_length; + GtsRange edge_angle; + GtsSurface * parent; +}; + +struct _GtsSurface { + GtsObject object; + +#ifdef USE_SURFACE_BTREE + GTree * faces; +#else /* not USE_SURFACE_BTREE */ + GHashTable * faces; +#endif /* not USE_SURFACE_BTREE */ + GtsFaceClass * face_class; + GtsEdgeClass * edge_class; + GtsVertexClass * vertex_class; + gboolean keep_faces; +}; + +struct _GtsSurfaceClass { + GtsObjectClass parent_class; + + void (* add_face) (GtsSurface *, GtsFace *); + void (* remove_face) (GtsSurface *, GtsFace *); +}; + +#define GTS_IS_SURFACE(obj) (gts_object_is_from_class (obj,\ + gts_surface_class ())) +#define GTS_SURFACE(obj) GTS_OBJECT_CAST (obj,\ + GtsSurface,\ + gts_surface_class ()) +#define GTS_SURFACE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsSurfaceClass,\ + gts_surface_class ()) + +GtsSurfaceClass * gts_surface_class (void); +GtsSurface * gts_surface_new (GtsSurfaceClass * klass, + GtsFaceClass * face_class, + GtsEdgeClass * edge_class, + GtsVertexClass * vertex_class); +void gts_surface_add_face (GtsSurface * s, + GtsFace * f); +void gts_surface_remove_face (GtsSurface * s, + GtsFace * f); +guint gts_surface_read (GtsSurface * surface, + GtsFile * f); +gdouble gts_surface_area (GtsSurface * s); +void gts_surface_stats (GtsSurface * s, + GtsSurfaceStats * stats); +void gts_surface_quality_stats (GtsSurface * s, + GtsSurfaceQualityStats * stats); +void gts_surface_print_stats (GtsSurface * s, + FILE * fptr); +void gts_surface_write (GtsSurface * s, + FILE * fptr); +void gts_surface_write_oogl (GtsSurface * s, + FILE * fptr); +void gts_surface_write_vtk (GtsSurface * s, + FILE * fptr); +void gts_surface_write_oogl_boundary (GtsSurface * s, + FILE * fptr); +void gts_surface_foreach_vertex (GtsSurface * s, + GtsFunc func, + gpointer data); +void gts_surface_foreach_edge (GtsSurface * s, + GtsFunc func, + gpointer data); +void gts_surface_foreach_face (GtsSurface * s, + GtsFunc func, + gpointer data); +guint gts_surface_foreach_face_remove (GtsSurface * s, + GtsFunc func, + gpointer data); +typedef struct _GtsSurfaceTraverse GtsSurfaceTraverse; +GtsSurfaceTraverse * gts_surface_traverse_new (GtsSurface * s, + GtsFace * f); +GtsFace * gts_surface_traverse_next (GtsSurfaceTraverse * t, + guint * level); +void gts_surface_traverse_destroy (GtsSurfaceTraverse * t); +void gts_surface_refine (GtsSurface * surface, + GtsKeyFunc cost_func, + gpointer cost_data, + GtsRefineFunc refine_func, + gpointer refine_data, + GtsStopFunc stop_func, + gpointer stop_data); +gboolean gts_edge_collapse_is_valid (GtsEdge * e); +void gts_surface_coarsen (GtsSurface * surface, + GtsKeyFunc cost_func, + gpointer cost_data, + GtsCoarsenFunc coarsen_func, + gpointer coarsen_data, + GtsStopFunc stop_func, + gpointer stop_data, + gdouble minangle); +gboolean gts_coarsen_stop_number (gdouble cost, + guint nedge, + guint * min_number); +gboolean gts_coarsen_stop_cost (gdouble cost, + guint nedge, + gdouble * max_cost); +void gts_surface_tessellate (GtsSurface * s, + GtsRefineFunc refine_func, + gpointer refine_data); +GtsSurface * gts_surface_generate_sphere (GtsSurface * s, + guint geodesation_order); +GtsSurface * gts_surface_copy (GtsSurface * s1, + GtsSurface * s2); +void gts_surface_merge (GtsSurface * s, + GtsSurface * with); +gboolean gts_surface_is_manifold (GtsSurface * s); +gboolean gts_surface_is_closed (GtsSurface * s); +gboolean gts_surface_is_orientable (GtsSurface * s); +gdouble gts_surface_volume (GtsSurface * s); +gdouble gts_surface_center_of_mass (GtsSurface * s, + GtsVector cm); +gdouble gts_surface_center_of_area (GtsSurface * s, + GtsVector cm); +guint gts_surface_vertex_number (GtsSurface * s); +guint gts_surface_edge_number (GtsSurface * s); +guint gts_surface_face_number (GtsSurface * s); +void gts_surface_distance (GtsSurface * s1, + GtsSurface * s2, + gdouble delta, + GtsRange * face_range, + GtsRange * boundary_range); +GSList * gts_surface_boundary (GtsSurface * surface); +GSList * gts_surface_split (GtsSurface * s); + +/* Discrete differential operators: curvature.c */ + +gboolean gts_vertex_mean_curvature_normal (GtsVertex * v, + GtsSurface * s, + GtsVector Kh); +gboolean gts_vertex_gaussian_curvature (GtsVertex * v, + GtsSurface * s, + gdouble * Kg); +void gts_vertex_principal_curvatures (gdouble Kh, + gdouble Kg, + gdouble * K1, + gdouble * K2); +void gts_vertex_principal_directions (GtsVertex * v, + GtsSurface * s, + GtsVector Kh, + gdouble Kg, + GtsVector e1, + GtsVector e2); + +/* Volume optimization: vopt.c */ +typedef struct _GtsVolumeOptimizedParams GtsVolumeOptimizedParams; + +struct _GtsVolumeOptimizedParams { + gdouble volume_weight; + gdouble boundary_weight; + gdouble shape_weight; +}; + +GtsVertex * gts_volume_optimized_vertex (GtsEdge * edge, + GtsVertexClass * klass, + GtsVolumeOptimizedParams * params); +gdouble gts_volume_optimized_cost (GtsEdge * e, + GtsVolumeOptimizedParams * params); + +/* bool operations: boolean.c */ + +GSList * gts_surface_intersection (GtsSurface * s1, + GtsSurface * s2, + GNode * faces_tree1, + GNode * faces_tree2); + +typedef struct _GtsSurfaceInter GtsSurfaceInter; +typedef struct _GtsSurfaceInterClass GtsSurfaceInterClass; +/** + * GtsBooleanOperation: + * @GTS_1_OUT_2: identifies the part of the first surface which lies + * outside the second surface. + * @GTS_1_IN_2: identifies the part of the first surface which lies + * inside the second surface. + * @GTS_2_OUT_1: identifies the part of the second surface which lies + * outside the first surface. + * @GTS_2_IN_1: identifies the part of the second surface which lies + * inside the first surface. + */ +typedef enum { GTS_1_OUT_2, + GTS_1_IN_2, + GTS_2_OUT_1, + GTS_2_IN_1 } GtsBooleanOperation; + +/** + * GTS_IS_SURFACE_INTER: + * @obj: a #GtsObject. + * + * Evaluates to %TRUE if @obj is a #GtsSurfaceInter, %FALSE otherwise. + */ +#define GTS_IS_SURFACE_INTER(obj) (gts_object_is_from_class (obj,\ + gts_surface_inter_class ())) +/** + * GTS_SURFACE_INTER: + * @obj: a descendant of #GtsSurfaceInter. + * + * Casts @obj to #GtsSurfaceInter. + */ +#define GTS_SURFACE_INTER(obj) GTS_OBJECT_CAST (obj,\ + GtsSurfaceInter,\ + gts_surface_inter_class ()) +/** + * GTS_SURFACE_INTER_CLASS: + * @klass: a descendant of #GtsSurfaceInterClass. + * + * Casts @klass to #GtsSurfaceInterClass. + */ +#define GTS_SURFACE_INTER_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsSurfaceInterClass,\ + gts_surface_inter_class ()) + +struct _GtsSurfaceInter { + GtsObject object; + + GtsSurface * s1; + GtsSurface * s2; + GSList * edges; +}; + +struct _GtsSurfaceInterClass { + GtsObjectClass parent_class; +}; + +GtsSurfaceInterClass * +gts_surface_inter_class (void); +GtsSurfaceInter * +gts_surface_inter_new (GtsSurfaceInterClass * klass, + GtsSurface * s1, + GtsSurface * s2, + GNode * faces_tree1, + GNode * faces_tree2, + gboolean is_open1, + gboolean is_open2); +gboolean +gts_surface_inter_check (GtsSurfaceInter * si, + gboolean * closed); +void +gts_surface_inter_boolean (GtsSurfaceInter * si, + GtsSurface * surface, + GtsBooleanOperation op); +gboolean gts_surface_foreach_intersecting_face (GtsSurface * s, + GtsBBTreeTraverseFunc func, + gpointer data); +GtsSurface * +gts_surface_is_self_intersecting (GtsSurface * s); + +/* Binary Heap: heap.c */ + +typedef struct _GtsHeap GtsHeap; + +GtsHeap * gts_heap_new (GCompareFunc compare_func); +void gts_heap_insert (GtsHeap * heap, gpointer p); +gpointer gts_heap_remove_top (GtsHeap * heap); +gpointer gts_heap_top (GtsHeap * heap); +void gts_heap_thaw (GtsHeap * heap); +void gts_heap_foreach (GtsHeap * heap, + GFunc func, + gpointer user_data); +void gts_heap_freeze (GtsHeap * heap); +guint gts_heap_size (GtsHeap * heap); +void gts_heap_destroy (GtsHeap * heap); + +/* Extended Binary Heap: eheap.c */ + +typedef struct _GtsEHeap GtsEHeap; +typedef struct _GtsEHeapPair GtsEHeapPair; + +struct _GtsEHeap { + GPtrArray * elts; + GtsKeyFunc func; + gpointer data; + gboolean frozen, randomized; +}; + +/** + * _GtsEHeapPair: + * @data: Points to the item stored in the heap. + * @key: Value of the key for this item. + * @pos: Private field. + */ +struct _GtsEHeapPair { + gpointer data; + gdouble key; + guint pos; +}; + +GtsEHeap * gts_eheap_new (GtsKeyFunc key_func, + gpointer data); +GtsEHeapPair * gts_eheap_insert (GtsEHeap * heap, + gpointer p); +GtsEHeapPair * gts_eheap_insert_with_key (GtsEHeap * heap, + gpointer p, + gdouble key); +gpointer gts_eheap_remove_top (GtsEHeap * heap, + gdouble * key); +gpointer gts_eheap_top (GtsEHeap * heap, + gdouble * key); +void gts_eheap_thaw (GtsEHeap * heap); +void gts_eheap_foreach (GtsEHeap * heap, + GFunc func, + gpointer data); +gpointer gts_eheap_remove (GtsEHeap * heap, + GtsEHeapPair * p); +void gts_eheap_decrease_key (GtsEHeap * heap, + GtsEHeapPair * p, + gdouble new_key); +void gts_eheap_freeze (GtsEHeap * heap); +guint gts_eheap_size (GtsEHeap * heap); +void gts_eheap_update (GtsEHeap * heap); +gdouble gts_eheap_key (GtsEHeap * heap, + gpointer p); +void gts_eheap_randomized (GtsEHeap * heap, + gboolean randomized); +void gts_eheap_destroy (GtsEHeap * heap); + +/* FIFO queues: fifo.c */ + +typedef struct _GtsFifo GtsFifo; + +GtsFifo * gts_fifo_new (void); +void gts_fifo_write (GtsFifo * fifo, + FILE * fp); +void gts_fifo_push (GtsFifo * fifo, + gpointer data); +gpointer gts_fifo_pop (GtsFifo * fifo); +gpointer gts_fifo_top (GtsFifo * fifo); +guint gts_fifo_size (GtsFifo * fifo); +gboolean gts_fifo_is_empty (GtsFifo * fifo); +void gts_fifo_foreach (GtsFifo * fifo, + GtsFunc func, + gpointer data); +void gts_fifo_reverse (GtsFifo * fifo); +void gts_fifo_destroy (GtsFifo * fifo); + +/* Progressive surfaces */ + +/* split.c */ + +typedef struct _GtsSplit GtsSplit; +typedef struct _GtsSplitClass GtsSplitClass; +typedef struct _GtsSplitCFace GtsSplitCFace; + +struct _GtsSplit { + GtsObject object; + + GtsVertex * v; + GtsObject * v1; + GtsObject * v2; + GtsSplitCFace * cfaces; + guint ncf; +}; + +struct _GtsSplitClass { + GtsObjectClass parent_class; +}; + +#define GTS_IS_SPLIT(obj) (gts_object_is_from_class (obj,\ + gts_split_class ())) +#define GTS_SPLIT(obj) GTS_OBJECT_CAST (obj,\ + GtsSplit,\ + gts_split_class ()) +#define GTS_SPLIT_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsSplitClass,\ + gts_split_class ()) +#define GTS_SPLIT_V1(vs) (GTS_IS_SPLIT ((vs)->v1) ?\ + GTS_SPLIT ((vs)->v1)->v :\ + GTS_VERTEX ((vs)->v1)) +#define GTS_SPLIT_V2(vs) (GTS_IS_SPLIT ((vs)->v2) ?\ + GTS_SPLIT ((vs)->v2)->v :\ + GTS_VERTEX ((vs)->v2)) + +GtsSplitClass * gts_split_class (void); +GtsSplit * gts_split_new (GtsSplitClass * klass, + GtsVertex * v, + GtsObject * o1, + GtsObject * o2); +void gts_split_collapse (GtsSplit * vs, + GtsEdgeClass * klass, + GtsEHeap * heap); +void gts_split_expand (GtsSplit * vs, + GtsSurface * s, + GtsEdgeClass * klass); +typedef gboolean (*GtsSplitTraverseFunc) (GtsSplit * vs, + gpointer data); +void gts_split_traverse (GtsSplit * root, + GTraverseType order, + gint depth, + GtsSplitTraverseFunc func, + gpointer data); +guint gts_split_height (GtsSplit * root); + +/* psurface.c */ + +typedef struct _GtsPSurface GtsPSurface; +typedef struct _GtsPSurfaceClass GtsPSurfaceClass; + +struct _GtsPSurface { + GtsObject object; + + GtsSurface * s; + GPtrArray * split; + GtsSplitClass * split_class; + guint pos, min; + + GPtrArray * vertices; + GPtrArray * faces; +}; + +struct _GtsPSurfaceClass { + GtsObjectClass parent_class; +}; + +#define GTS_IS_PSURFACE(obj) (gts_object_is_from_class (obj,\ + gts_psurface_class ())) +#define GTS_PSURFACE(obj) GTS_OBJECT_CAST (obj,\ + GtsPSurface,\ + gts_psurface_class ()) +#define GTS_PSURFACE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsPSurfaceClass,\ + gts_psurface_class ()) +#define GTS_PSURFACE_IS_CLOSED(ps) (!(ps)->vertices) + +GtsPSurfaceClass * gts_psurface_class (void); +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); +GtsSplit * gts_psurface_add_vertex (GtsPSurface * ps); +GtsSplit * gts_psurface_remove_vertex (GtsPSurface * ps); +guint gts_psurface_max_vertex_number (GtsPSurface * ps); +guint gts_psurface_min_vertex_number (GtsPSurface * ps); +void gts_psurface_set_vertex_number (GtsPSurface * ps, + guint n); +guint gts_psurface_get_vertex_number (GtsPSurface * ps); +void gts_psurface_write (GtsPSurface * ps, + FILE * fptr); +GtsPSurface * gts_psurface_open (GtsPSurfaceClass * klass, + GtsSurface * s, + GtsSplitClass * split_class, + GtsFile * f); +GtsSplit * gts_psurface_read_vertex (GtsPSurface * ps, + GtsFile * fp); +void gts_psurface_close (GtsPSurface * ps); +void gts_psurface_foreach_vertex (GtsPSurface * ps, + GtsFunc func, + gpointer data); + +/* hsurface.c */ + +typedef struct _GtsHSplit GtsHSplit; +typedef struct _GtsHSplitClass GtsHSplitClass; +typedef struct _GtsHSurface GtsHSurface; +typedef struct _GtsHSurfaceClass GtsHSurfaceClass; + +struct _GtsHSplit { + GtsSplit split; + + GtsEHeapPair * index; + GtsHSplit * parent; + guint nchild; +}; + +struct _GtsHSplitClass { + GtsSplitClass parent_class; +}; + +#define GTS_IS_HSPLIT(obj) (gts_object_is_from_class (obj,\ + gts_hsplit_class ())) +#define GTS_HSPLIT(obj) GTS_OBJECT_CAST (obj,\ + GtsHSplit,\ + gts_hsplit_class ()) +#define GTS_HSPLIT_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsHSplitClass,\ + gts_hsplit_class ()) + +GtsHSplitClass * gts_hsplit_class (void); +GtsHSplit * gts_hsplit_new (GtsHSplitClass * klass, + GtsSplit * vs); +void gts_hsplit_collapse (GtsHSplit * hs, + GtsHSurface * hsurface); +void gts_hsplit_expand (GtsHSplit * hs, + GtsHSurface * hsurface); +void gts_hsplit_force_expand (GtsHSplit * hs, + GtsHSurface * hsurface); + +struct _GtsHSurface { + GtsObject object; + + GtsSurface * s; + GSList * roots; + GtsEHeap * expandable; + GtsEHeap * collapsable; + GPtrArray * split; + guint nvertex; +}; + +struct _GtsHSurfaceClass { + GtsObjectClass parent_class; +}; + +#define GTS_IS_HSURFACE(obj) (gts_object_is_from_class (obj,\ + gts_hsurface_class ())) +#define GTS_HSURFACE(obj) GTS_OBJECT_CAST (obj,\ + GtsHSurface,\ + gts_hsurface_class ()) +#define GTS_HSURFACE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsHSurfaceClass,\ + gts_hsurface_class ()) + +GtsHSurfaceClass * gts_hsurface_class (void); +GtsHSurface * gts_hsurface_new (GtsHSurfaceClass * klass, + GtsHSplitClass * hsplit_class, + GtsPSurface * psurface, + GtsKeyFunc expand_key, + gpointer expand_data, + GtsKeyFunc collapse_key, + gpointer collapse_data); +void gts_hsurface_traverse (GtsHSurface * hsurface, + GTraverseType order, + gint depth, + GtsSplitTraverseFunc func, + gpointer data); +void gts_hsurface_foreach (GtsHSurface * hsurface, + GTraverseType order, + GtsFunc func, + gpointer data); +guint gts_hsurface_height (GtsHSurface * hsurface); + +/* Constrained Delaunay triangulation: cdt.c */ + +/** + * GTS_IS_CONSTRAINT: + * @obj: a #GtsObject. + * + * Evaluates to %TRUE if @obj is a #GtsConstraint, %FALSE otherwise. + */ +#define GTS_IS_CONSTRAINT(obj) (gts_object_is_from_class (obj,\ + gts_constraint_class ())) +/** + * GTS_CONSTRAINT: + * @obj: a descendant of #GtsConstraint. + * + * Casts @obj to #GtsConstraint. + */ +#define GTS_CONSTRAINT(obj) GTS_OBJECT_CAST (obj,\ + GtsConstraint,\ + gts_constraint_class ()) +/** + * GTS_CONSTRAINT_CLASS: + * @klass: a desscendant of #GtsConstraintClass. + * + * Casts @klass to #GtsConstraintClass. + */ +#define GTS_CONSTRAINT_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsConstraintClass,\ + gts_constraint_class ()) + +struct _GtsConstraint { + GtsEdge edge; +}; + +struct _GtsConstraintClass { + GtsEdgeClass parent_class; +}; + +typedef struct _GtsConstraint GtsConstraint; +typedef struct _GtsConstraintClass GtsConstraintClass; + +GtsConstraintClass * gts_constraint_class (void); + +GtsFace * gts_point_locate (GtsPoint * p, + GtsSurface * surface, + GtsFace * guess); +GtsVertex * gts_delaunay_add_vertex_to_face (GtsSurface * surface, + GtsVertex * v, + GtsFace * f); +GtsVertex * gts_delaunay_add_vertex (GtsSurface * surface, + GtsVertex * v, + GtsFace * guess); +void gts_delaunay_remove_vertex (GtsSurface * surface, + GtsVertex * v); +GtsFace * gts_delaunay_check (GtsSurface * surface); +GSList * gts_delaunay_add_constraint (GtsSurface * surface, + GtsConstraint * c); +void gts_delaunay_remove_hull (GtsSurface * surface); + +/* GtsListFace: Header */ + +typedef struct _GtsListFace GtsListFace; + +struct _GtsListFace { + /*< private >*/ + GtsFace parent; + + /*< public >*/ + GSList * points; +}; + +#define GTS_LIST_FACE(obj) GTS_OBJECT_CAST (obj,\ + GtsListFace,\ + gts_list_face_class ()) +#define GTS_IS_LIST_FACE(obj) (gts_object_is_from_class (obj,\ + gts_list_face_class ())) + +GtsFaceClass * gts_list_face_class (void); + +/* Constrained Delaunay refinement: refine.c */ + +typedef gboolean (* GtsEncroachFunc) (GtsVertex * v, + GtsEdge * e, + GtsSurface * s, + gpointer data); + +gboolean gts_vertex_encroaches_edge (GtsVertex * v, + GtsEdge * e); +GtsVertex * gts_edge_is_encroached (GtsEdge * e, + GtsSurface * s, + GtsEncroachFunc encroaches, + gpointer data); +guint gts_delaunay_conform (GtsSurface * surface, + gint steiner_max, + GtsEncroachFunc encroaches, + gpointer data); +guint gts_delaunay_refine (GtsSurface * surface, + gint steiner_max, + GtsEncroachFunc encroaches, + gpointer encroach_data, + GtsKeyFunc cost, + gpointer cost_data); + +/* Isosurfaces (marching cubes): iso.c */ + +typedef struct _GtsGridPlane GtsGridPlane; +typedef struct _GtsIsoSlice GtsIsoSlice; +typedef struct _GtsCartesianGrid GtsCartesianGrid; + +struct _GtsGridPlane { + GtsPoint ** p; + guint nx, ny; +}; + +struct _GtsCartesianGrid { + guint nx, ny, nz; + gdouble x, dx, y, dy, z, dz; +}; + +typedef void (*GtsIsoCartesianFunc) (gdouble ** a, + GtsCartesianGrid g, + guint i, + gpointer data); + +GtsGridPlane * gts_grid_plane_new (guint nx, + guint ny); +void gts_grid_plane_destroy (GtsGridPlane * g); +GtsIsoSlice * gts_iso_slice_new (guint nx, guint ny); +void gts_iso_slice_fill (GtsIsoSlice * slice, + GtsGridPlane * plane1, + GtsGridPlane * plane2, + gdouble ** f1, + gdouble ** f2, + gdouble iso, + GtsVertexClass * klass); +void gts_iso_slice_fill_cartesian (GtsIsoSlice * slice, + GtsCartesianGrid g, + gdouble ** f1, + gdouble ** f2, + gdouble iso, + GtsVertexClass * klass); +void gts_iso_slice_destroy (GtsIsoSlice * slice); +void gts_isosurface_slice (GtsIsoSlice * slice1, + GtsIsoSlice * slice2, + GtsSurface * surface); +void gts_isosurface_cartesian (GtsSurface * surface, + GtsCartesianGrid g, + GtsIsoCartesianFunc f, + gpointer data, + gdouble iso); + +/* Isosurfaces (marching tetrahedra): isotetra.c */ + +void gts_isosurface_tetra (GtsSurface * surface, + GtsCartesianGrid g, + GtsIsoCartesianFunc f, + gpointer data, + gdouble iso); +void gts_isosurface_tetra_bcl (GtsSurface * surface, + GtsCartesianGrid g, + GtsIsoCartesianFunc f, + gpointer data, + gdouble iso); +void gts_isosurface_tetra_bounded (GtsSurface * surface, + GtsCartesianGrid g, + GtsIsoCartesianFunc f, + gpointer data, + gdouble iso); + +/* Named vertices, edges and triangles: named.c */ + +#define GTS_NAME_LENGTH 40 + +#define GTS_NVERTEX(obj) GTS_OBJECT_CAST (obj,\ + GtsNVertex,\ + gts_nvertex_class ()) +#define GTS_NVERTEX_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsNVertexClass,\ + gts_nvertex_class()) +#define GTS_IS_NVERTEX(obj) (gts_object_is_from_class (obj,\ + gts_nvertex_class ())) + +typedef struct _GtsNVertex GtsNVertex; +typedef struct _GtsNVertexClass GtsNVertexClass; + +struct _GtsNVertex { + GtsVertex parent; + char name[GTS_NAME_LENGTH]; +}; + +struct _GtsNVertexClass { + GtsVertexClass parent_class; +}; + +GtsNVertexClass * gts_nvertex_class (void); + +#define GTS_NEDGE(obj) GTS_OBJECT_CAST (obj,\ + GtsNEdge,\ + gts_nedge_class ()) +#define GTS_NEDGE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsNEdgeClass,\ + gts_nedge_class()) +#define GTS_IS_NEDGE(obj) (gts_object_is_from_class (obj,\ + gts_nedge_class ())) + +typedef struct _GtsNEdge GtsNEdge; +typedef struct _GtsNEdgeClass GtsNEdgeClass; + +struct _GtsNEdge { + GtsEdge parent; + char name[GTS_NAME_LENGTH]; +}; + +struct _GtsNEdgeClass { + GtsEdgeClass parent_class; +}; + +GtsNEdgeClass * gts_nedge_class (void); + +#define GTS_NFACE(obj) GTS_OBJECT_CAST (obj,\ + GtsNFace,\ + gts_nface_class ()) +#define GTS_NFACE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsNFaceClass,\ + gts_nface_class()) +#define GTS_IS_NFACE(obj) (gts_object_is_from_class (obj,\ + gts_nface_class ())) + +typedef struct _GtsNFace GtsNFace; +typedef struct _GtsNFaceClass GtsNFaceClass; + +struct _GtsNFace { + GtsFace parent; + char name[GTS_NAME_LENGTH]; +}; + +struct _GtsNFaceClass { + GtsFaceClass parent_class; +}; + +GtsNFaceClass * gts_nface_class (void); + +/* Cluster object for out-of-core simplification: oocs.c */ + +#define GTS_CLUSTER(obj) GTS_OBJECT_CAST (obj,\ + GtsCluster,\ + gts_cluster_class ()) +#define GTS_CLUSTER_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsClusterClass,\ + gts_cluster_class()) +#define GTS_IS_CLUSTER(obj) (gts_object_is_from_class (obj,\ + gts_cluster_class ())) + +typedef struct _GtsCluster GtsCluster; +typedef struct _GtsClusterClass GtsClusterClass; +typedef struct _GtsClusterId GtsClusterId; + +struct _GtsClusterId { + guint x, y, z; +}; + +struct _GtsCluster { + GtsObject parent; + + GtsClusterId id; + GtsVertex * v; + guint n; +}; + +struct _GtsClusterClass { + GtsObjectClass parent_class; + + void (* add) (GtsCluster * c, GtsPoint * p, gpointer data); + void (* update) (GtsCluster * c); +}; + +GtsClusterClass * gts_cluster_class (void); +GtsCluster * gts_cluster_new (GtsClusterClass * klass, + GtsClusterId id, + GtsVertexClass * vklass); +void gts_cluster_add (GtsCluster * c, + GtsPoint * p, + gpointer data); +void gts_cluster_update (GtsCluster * c); + +/* Cluster group object for out-of-core simplification: oocs.c */ + +#define GTS_CLUSTER_GRID(obj) GTS_OBJECT_CAST (obj,\ + GtsClusterGrid,\ + gts_cluster_grid_class ()) +#define GTS_CLUSTER_GRID_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsClusterGridClass,\ + gts_cluster_grid_class()) +#define GTS_IS_CLUSTER_GRID(obj) (gts_object_is_from_class (obj,\ + gts_cluster_grid_class ())) + +typedef struct _GtsClusterGrid GtsClusterGrid; +typedef struct _GtsClusterGridClass GtsClusterGridClass; + +struct _GtsClusterGrid { + GtsObject parent; + + GtsSurface * surface; + GtsBBox * bbox; + GtsVector size; + + GtsClusterClass * cluster_class; + GHashTable * clusters; +}; + +struct _GtsClusterGridClass { + GtsObjectClass parent_class; +}; + +GtsClusterGridClass * gts_cluster_grid_class (void); +GtsClusterGrid * gts_cluster_grid_new (GtsClusterGridClass * klass, + GtsClusterClass * cluster_class, + GtsSurface * s, + GtsBBox * bbox, + gdouble delta); +void gts_cluster_grid_add_triangle (GtsClusterGrid * cluster_grid, + GtsPoint * p1, + GtsPoint * p2, + GtsPoint * p3, + gpointer data); +GtsRange gts_cluster_grid_update (GtsClusterGrid * cluster_grid); + +/* Triangle strip generation: stripe.c */ +GSList * gts_surface_strip (GtsSurface * s); + +/* GtsContainee: container.c */ + +typedef struct _GtsContainee GtsContainee; +typedef struct _GtsContaineeClass GtsContaineeClass; +typedef struct _GtsContainer GtsContainer; +typedef struct _GtsContainerClass GtsContainerClass; + +struct _GtsContainee { + GtsObject object; +}; + +struct _GtsContaineeClass { + GtsObjectClass parent_class; + + void (* add_container) (GtsContainee *, GtsContainer *); + void (* remove_container) (GtsContainee *, GtsContainer *); + void (* foreach) (GtsContainee *, GtsFunc, gpointer); + gboolean (* is_contained) (GtsContainee *, GtsContainer *); + void (* replace) (GtsContainee *, GtsContainee *); +}; + +#define GTS_CONTAINEE(obj) GTS_OBJECT_CAST (obj,\ + GtsContainee,\ + gts_containee_class ()) +#define GTS_CONTAINEE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsContaineeClass,\ + gts_containee_class()) +#define GTS_IS_CONTAINEE(obj) (gts_object_is_from_class (obj,\ + gts_containee_class ())) + +GtsContaineeClass * gts_containee_class (void); +GtsContainee * gts_containee_new (GtsContaineeClass * klass); +gboolean gts_containee_is_contained (GtsContainee * item, + GtsContainer * c); +void gts_containee_replace (GtsContainee * item, + GtsContainee * with); + +/* GtsSListContainee: container.c */ + +typedef struct _GtsSListContainee GtsSListContainee; +typedef struct _GtsSListContaineeClass GtsSListContaineeClass; + +struct _GtsSListContainee { + GtsContainee containee; + + GSList * containers; +}; + +struct _GtsSListContaineeClass { + GtsContaineeClass parent_class; +}; + +#define GTS_SLIST_CONTAINEE(obj) GTS_OBJECT_CAST (obj,\ + GtsSListContainee,\ + gts_slist_containee_class ()) +#define GTS_SLIST_CONTAINEE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsSListContaineeClass,\ + gts_slist_containee_class()) +#define GTS_IS_SLIST_CONTAINEE(obj) (gts_object_is_from_class (obj,\ + gts_slist_containee_class ())) + +GtsSListContaineeClass * gts_slist_containee_class (void); + +/* GtsContainer: container.c */ + +struct _GtsContainer { + GtsSListContainee object; +}; + +struct _GtsContainerClass { + GtsSListContaineeClass parent_class; + + void (* add) (GtsContainer *, GtsContainee *); + void (* remove) (GtsContainer *, GtsContainee *); + void (* foreach) (GtsContainer *, GtsFunc, gpointer); + guint (* size) (GtsContainer *); +}; + +#define GTS_CONTAINER(obj) GTS_OBJECT_CAST (obj,\ + GtsContainer,\ + gts_container_class ()) +#define GTS_CONTAINER_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsContainerClass,\ + gts_container_class()) +#define GTS_IS_CONTAINER(obj) (gts_object_is_from_class (obj,\ + gts_container_class ())) + +GtsContainerClass * gts_container_class (void); +GtsContainer * gts_container_new (GtsContainerClass * klass); +void gts_container_add (GtsContainer * c, + GtsContainee * item); +void gts_container_remove (GtsContainer * c, + GtsContainee * item); +void gts_container_foreach (GtsContainer * c, + GtsFunc func, + gpointer data); +guint gts_container_size (GtsContainer * c); + +/* GtsHashContainer: container.c */ + +typedef struct _GtsHashContainer GtsHashContainer; +typedef struct _GtsHashContainerClass GtsHashContainerClass; + +struct _GtsHashContainer { + GtsContainer c; + + GHashTable * items; + gboolean frozen; +}; + +struct _GtsHashContainerClass { + GtsContainerClass parent_class; +}; + +#define GTS_HASH_CONTAINER(obj) GTS_OBJECT_CAST (obj,\ + GtsHashContainer,\ + gts_hash_container_class ()) +#define GTS_HASH_CONTAINER_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsHashContainerClass,\ + gts_hash_container_class()) +#define GTS_IS_HASH_CONTAINER(obj) (gts_object_is_from_class (obj,\ + gts_hash_container_class ())) + +GtsHashContainerClass * gts_hash_container_class (void); + +/* GtsSListContainer: container.c */ + +typedef struct _GtsSListContainer GtsSListContainer; +typedef struct _GtsSListContainerClass GtsSListContainerClass; + +struct _GtsSListContainer { + GtsContainer c; + + GSList * items; + gboolean frozen; +}; + +struct _GtsSListContainerClass { + GtsContainerClass parent_class; +}; + +#define GTS_SLIST_CONTAINER(obj) GTS_OBJECT_CAST (obj,\ + GtsSListContainer,\ + gts_slist_container_class ()) +#define GTS_SLIST_CONTAINER_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsSListContainerClass,\ + gts_slist_container_class()) +#define GTS_IS_SLIST_CONTAINER(obj) (gts_object_is_from_class (obj,\ + gts_slist_container_class ())) + +GtsSListContainerClass * gts_slist_container_class (void); + +/* GtsGNode: graph.c */ + +typedef struct _GtsGNode GtsGNode; +typedef struct _GtsGNodeClass GtsGNodeClass; +typedef struct _GtsGraph GtsGraph; +typedef struct _GtsGraphClass GtsGraphClass; + +struct _GtsGNode { + GtsSListContainer container; + + guint level; +}; + +struct _GtsGNodeClass { + GtsSListContainerClass parent_class; + + gfloat (* weight) (GtsGNode *); + void (* write) (GtsGNode *, FILE *); +}; + +#define GTS_GNODE(obj) GTS_OBJECT_CAST (obj,\ + GtsGNode,\ + gts_gnode_class ()) +#define GTS_GNODE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsGNodeClass,\ + gts_gnode_class()) +#define GTS_IS_GNODE(obj) (gts_object_is_from_class (obj,\ + gts_gnode_class ())) +#define GTS_GNODE_NEIGHBOR(n,e) (GTS_GEDGE (e)->n1 == n ? GTS_GEDGE (e)->n2 : GTS_GEDGE (e)->n2 == n ? GTS_GEDGE (e)->n1 : NULL) + +GtsGNodeClass * gts_gnode_class (void); +GtsGNode * gts_gnode_new (GtsGNodeClass * klass); +void gts_gnode_foreach_neighbor (GtsGNode * n, + GtsGraph * g, + GtsFunc func, + gpointer data); +void gts_gnode_foreach_edge (GtsGNode * n, + GtsGraph * g, + GtsFunc func, + gpointer data); +guint gts_gnode_degree (GtsGNode * n, + GtsGraph * g); +gfloat gts_gnode_move_cost (GtsGNode * n, + GtsGraph * src, + GtsGraph * dst); +gfloat gts_gnode_weight (GtsGNode * n); + +GTS_C_VAR +gboolean gts_allow_floating_gnodes; + +/* GtsNGNode: graph.c */ + +typedef struct _GtsNGNode GtsNGNode; +typedef struct _GtsNGNodeClass GtsNGNodeClass; + +struct _GtsNGNode { + GtsGNode node; + + guint id; +}; + +struct _GtsNGNodeClass { + GtsGNodeClass parent_class; +}; + +#define GTS_NGNODE(obj) GTS_OBJECT_CAST (obj,\ + GtsNGNode,\ + gts_ngnode_class ()) +#define GTS_NGNODE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsNGNodeClass,\ + gts_ngnode_class()) +#define GTS_IS_NGNODE(obj) (gts_object_is_from_class (obj,\ + gts_ngnode_class ())) + +GtsNGNodeClass * gts_ngnode_class (void); +GtsNGNode * gts_ngnode_new (GtsNGNodeClass * klass, + guint id); + +/* GtsWGNode: graph.c */ + +typedef struct _GtsWGNode GtsWGNode; +typedef struct _GtsWGNodeClass GtsWGNodeClass; + +struct _GtsWGNode { + GtsGNode node; + + gfloat weight; +}; + +struct _GtsWGNodeClass { + GtsGNodeClass parent_class; +}; + +#define GTS_WGNODE(obj) GTS_OBJECT_CAST (obj,\ + GtsWGNode,\ + gts_wgnode_class ()) +#define GTS_WGNODE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsWGNodeClass,\ + gts_wgnode_class()) +#define GTS_IS_WGNODE(obj) (gts_object_is_from_class (obj,\ + gts_wgnode_class ())) + +GtsWGNodeClass * gts_wgnode_class (void); +GtsWGNode * gts_wgnode_new (GtsWGNodeClass * klass, + gfloat weight); + +/* GtsPNode */ + +typedef struct _GtsPNode GtsPNode; +typedef struct _GtsPNodeClass GtsPNodeClass; + +struct _GtsPNode { + GtsGNode node; + + gpointer data; +}; + +struct _GtsPNodeClass { + GtsGNodeClass parent_class; +}; + +#define GTS_PNODE(obj) GTS_OBJECT_CAST (obj,\ + GtsPNode,\ + gts_pnode_class ()) +#define GTS_PNODE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsPNodeClass,\ + gts_pnode_class()) +#define GTS_IS_PNODE(obj) (gts_object_is_from_class (obj,\ + gts_pnode_class ())) + +GtsPNodeClass * gts_pnode_class (void); +GtsPNode * gts_pnode_new (GtsPNodeClass * klass, + gpointer data); + +/* GtsFNode */ + +typedef struct _GtsFNode GtsFNode; +typedef struct _GtsFNodeClass GtsFNodeClass; + +struct _GtsFNode { + GtsGNode node; + + GtsFace * f; +}; + +struct _GtsFNodeClass { + GtsGNodeClass parent_class; +}; + +#define GTS_FNODE(obj) GTS_OBJECT_CAST (obj,\ + GtsFNode,\ + gts_fnode_class ()) +#define GTS_FNODE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsFNodeClass,\ + gts_fnode_class()) +#define GTS_IS_FNODE(obj) (gts_object_is_from_class (obj,\ + gts_fnode_class ())) + +GtsFNodeClass * gts_fnode_class (void); +GtsFNode * gts_fnode_new (GtsFNodeClass * klass, + GtsFace * f); + +/* GtsGEdge: graph.c */ + +typedef struct _GtsGEdge GtsGEdge; +typedef struct _GtsGEdgeClass GtsGEdgeClass; + +struct _GtsGEdge { + GtsContainee containee; + + GtsGNode * n1; + GtsGNode * n2; +}; + +struct _GtsGEdgeClass { + GtsContaineeClass parent_class; + + GtsGEdge * (* link) (GtsGEdge * e, GtsGNode * n1, GtsGNode * n2); + gfloat (* weight) (GtsGEdge * e); + void (* write) (GtsGEdge * e, FILE * fp); +}; + +#define GTS_GEDGE(obj) GTS_OBJECT_CAST (obj,\ + GtsGEdge,\ + gts_gedge_class ()) +#define GTS_GEDGE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsGEdgeClass,\ + gts_gedge_class()) +#define GTS_IS_GEDGE(obj) (gts_object_is_from_class (obj,\ + gts_gedge_class ())) + +GtsGEdgeClass * gts_gedge_class (void); +GtsGEdge * gts_gedge_new (GtsGEdgeClass * klass, + GtsGNode * n1, + GtsGNode * n2); +gfloat gts_gedge_weight (GtsGEdge * e); +#define gts_gedge_connects(e, a1, a2)\ + (((e)->n1 == a1 && (e)->n2 == a2) || ((e)->n1 == a2 && (e)->n2 == a1)) + +/* GtsPGEdge: graph.c */ + +typedef struct _GtsPGEdge GtsPGEdge; +typedef struct _GtsPGEdgeClass GtsPGEdgeClass; + +struct _GtsPGEdge { + GtsGEdge gedge; + + gpointer data; +}; + +struct _GtsPGEdgeClass { + GtsGEdgeClass parent_class; +}; + +#define GTS_PGEDGE(obj) GTS_OBJECT_CAST (obj,\ + GtsPGEdge,\ + gts_pgedge_class ()) +#define GTS_PGEDGE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsPGEdgeClass,\ + gts_pgedge_class()) +#define GTS_IS_PGEDGE(obj) (gts_object_is_from_class (obj,\ + gts_pgedge_class ())) + +GtsPGEdgeClass * gts_pgedge_class (void); +GtsPGEdge * gts_pgedge_new (GtsPGEdgeClass * klass, + GtsGNode * n1, + GtsGNode * n2, + gpointer data); + +/* GtsWGEdge: graph.c */ + +typedef struct _GtsWGEdge GtsWGEdge; +typedef struct _GtsWGEdgeClass GtsWGEdgeClass; + +struct _GtsWGEdge { + GtsGEdge gedge; + + gfloat weight; +}; + +struct _GtsWGEdgeClass { + GtsGEdgeClass parent_class; +}; + +#define GTS_WGEDGE(obj) GTS_OBJECT_CAST (obj,\ + GtsWGEdge,\ + gts_wgedge_class ()) +#define GTS_WGEDGE_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsWGEdgeClass,\ + gts_wgedge_class()) +#define GTS_IS_WGEDGE(obj) (gts_object_is_from_class (obj,\ + gts_wgedge_class ())) + +GtsWGEdgeClass * gts_wgedge_class (void); +GtsWGEdge * gts_wgedge_new (GtsWGEdgeClass * klass, + GtsGNode * n1, + GtsGNode * n2, + gfloat weight); + +/* GtsGraph: graph.c */ + +struct _GtsGraph { + GtsHashContainer object; + + GtsGraphClass * graph_class; + GtsGNodeClass * node_class; + GtsGEdgeClass * edge_class; +}; + +struct _GtsGraphClass { + GtsHashContainerClass parent_class; + + gfloat (* weight) (GtsGraph *); +}; + +#define GTS_GRAPH(obj) GTS_OBJECT_CAST (obj,\ + GtsGraph,\ + gts_graph_class ()) +#define GTS_GRAPH_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsGraphClass,\ + gts_graph_class()) +#define GTS_IS_GRAPH(obj) (gts_object_is_from_class (obj,\ + gts_graph_class ())) + +GtsGraphClass * gts_graph_class (void); +GtsGraph * gts_graph_new (GtsGraphClass * klass, + GtsGNodeClass * node_class, + GtsGEdgeClass * edge_class); +void gts_graph_print_stats (GtsGraph * g, + FILE * fp); +typedef struct _GtsGraphTraverse GtsGraphTraverse; +typedef enum { GTS_BREADTH_FIRST + } GtsTraverseType; +GtsGraphTraverse * gts_graph_traverse_new (GtsGraph * g, + GtsGNode * n, + GtsTraverseType type, + gboolean reinit); +GtsGNode * gts_graph_traverse_next (GtsGraphTraverse * t); +GtsGNode * gts_graph_traverse_what_next (GtsGraphTraverse * t); +void gts_graph_traverse_destroy (GtsGraphTraverse * t); +void gts_graph_foreach_edge (GtsGraph * g, + GtsFunc func, + gpointer data); +gfloat gts_graph_weight (GtsGraph * g); +guint gts_graph_distance_sum (GtsGraph * g, + GtsGNode * center); +GtsGNode * gts_graph_farthest (GtsGraph * g, + GSList * gnodes); +guint gts_graph_edges_cut (GtsGraph * g); +gfloat gts_graph_edges_cut_weight (GtsGraph * g); +void gts_graph_write (GtsGraph * g, + FILE * fp); +void gts_graph_write_dot (GtsGraph * g, + FILE * fp); +GtsGraph * gts_graph_read (GtsFile * fp); +guint gts_graph_read_jostle (GtsGraph * g, + GtsFile * fp); + +/* GtsWGraph: graph.c */ + +typedef struct _GtsWGraph GtsWGraph; +typedef struct _GtsWGraphClass GtsWGraphClass; + +struct _GtsWGraph { + GtsGraph graph; + + gfloat weight; +}; + +struct _GtsWGraphClass { + GtsGraphClass parent_class; +}; + +#define GTS_WGRAPH(obj) GTS_OBJECT_CAST (obj,\ + GtsWGraph,\ + gts_wgraph_class ()) +#define GTS_WGRAPH_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsWGraphClass,\ + gts_wgraph_class()) +#define GTS_IS_WGRAPH(obj) (gts_object_is_from_class (obj,\ + gts_wgraph_class ())) + +GtsWGraphClass * gts_wgraph_class (void); +gfloat gts_wgraph_weight_max (GtsWGraph * wg); + +/* Surface graph: graph.c */ + +GtsGraph * gts_surface_graph_new (GtsGraphClass * klass, + GtsSurface * s); +GtsSurface * gts_surface_graph_surface (GtsGraph * surface_graph, + GtsSurface * s); + +/* Segments graph: graph.c */ + +GtsGraph * gts_segments_graph_new (GtsGraphClass * klass, + GSList * segments); + +/* GtsGNodeSplit: pgraph.c */ + +typedef struct _GtsGNodeSplit GtsGNodeSplit; +typedef struct _GtsGNodeSplitClass GtsGNodeSplitClass; + +struct _GtsGNodeSplit { + GtsObject object; + + GtsGNode * n; + GtsObject * n1; + GtsObject * n2; +}; + +struct _GtsGNodeSplitClass { + GtsObjectClass parent_class; +}; + +#define GTS_GNODE_SPLIT(obj) GTS_OBJECT_CAST (obj,\ + GtsGNodeSplit,\ + gts_gnode_split_class ()) +#define GTS_GNODE_SPLIT_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsGNodeSplitClass,\ + gts_gnode_split_class()) +#define GTS_IS_GNODE_SPLIT(obj) (gts_object_is_from_class (obj,\ + gts_gnode_split_class ())) +#define GTS_GNODE_SPLIT_N1(ns) (GTS_IS_GNODE_SPLIT ((ns)->n1) ? GTS_GNODE_SPLIT ((ns)->n1)->n : GTS_GNODE ((ns)->n1)) +#define GTS_GNODE_SPLIT_N2(ns) (GTS_IS_GNODE_SPLIT ((ns)->n2) ? GTS_GNODE_SPLIT ((ns)->n2)->n : GTS_GNODE ((ns)->n2)) + +GtsGNodeSplitClass * gts_gnode_split_class (void); +GtsGNodeSplit * gts_gnode_split_new (GtsGNodeSplitClass * klass, + GtsGNode * n, + GtsObject * n1, + GtsObject * n2); +void gts_gnode_split_collapse (GtsGNodeSplit * ns, + GtsGraph * g, + GtsWGEdgeClass * klass); +void gts_gnode_split_expand (GtsGNodeSplit * ns, + GtsGraph * g); + +/* GtsPGraph: pgraph.c */ + +typedef struct _GtsPGraph GtsPGraph; +typedef struct _GtsPGraphClass GtsPGraphClass; + +struct _GtsPGraph { + GtsObject object; + + GtsGraph * g; + GPtrArray * split; + GArray * levels; + GtsGNodeSplitClass * split_class; + GtsWGEdgeClass * edge_class; + guint pos, min, level; +}; + +struct _GtsPGraphClass { + GtsObjectClass parent_class; +}; + +#define GTS_PGRAPH(obj) GTS_OBJECT_CAST (obj,\ + GtsPGraph,\ + gts_pgraph_class ()) +#define GTS_PGRAPH_CLASS(klass) GTS_OBJECT_CLASS_CAST (klass,\ + GtsPGraphClass,\ + gts_pgraph_class()) +#define GTS_IS_PGRAPH(obj) (gts_object_is_from_class (obj,\ + gts_pgraph_class ())) + +GtsPGraphClass * gts_pgraph_class (void); +GtsPGraph * gts_pgraph_new (GtsPGraphClass * klass, + GtsGraph * g, + GtsGNodeSplitClass * split_class, + GtsWGNodeClass * node_class, + GtsWGEdgeClass * edge_class, + guint min); +GtsGNodeSplit * gts_pgraph_add_node (GtsPGraph * pg); +GtsGNodeSplit * gts_pgraph_remove_node (GtsPGraph * pg); +void gts_pgraph_set_node_number (GtsPGraph *pg, + guint n); +guint gts_pgraph_get_node_number (GtsPGraph *pg); +guint gts_pgraph_min_node_number (GtsPGraph *pg); +guint gts_pgraph_max_node_number (GtsPGraph *pg); +void gts_pgraph_foreach_node (GtsPGraph *pg, + GtsFunc func, + gpointer data); +gboolean gts_pgraph_down (GtsPGraph * pg, + GtsFunc func, + gpointer data); +/* Graph partition: partition.c */ + +GSList * gts_graph_bubble_partition (GtsGraph * g, + guint np, + guint niter, + GtsFunc step_info, + gpointer data); +guint gts_graph_partition_edges_cut (GSList * partition); +gfloat gts_graph_partition_edges_cut_weight (GSList * partition); +void gts_graph_partition_print_stats (GSList * partition, + FILE * fp); +gfloat gts_graph_partition_balance (GSList * partition); +GSList * gts_graph_partition_clone (GSList * partition); +GSList * gts_graph_recursive_bisection (GtsWGraph * wg, + guint n, + guint ntry, + guint mmax, + guint nmin, + gfloat imbalance); +void gts_graph_partition_destroy (GSList * partition); + +/* Graph bisection: partition.c */ + +typedef struct _GtsGraphBisection GtsGraphBisection; + +struct _GtsGraphBisection { + GtsGraph * g; + GtsGraph * g1; + GtsGraph * g2; + GHashTable * bg1; + GHashTable * bg2; +}; + +gboolean gts_graph_bisection_check (GtsGraphBisection * bg); +GtsGraphBisection * gts_graph_ggg_bisection (GtsGraph * g, + guint ntry); +GtsGraphBisection * gts_graph_bfgg_bisection (GtsGraph * g, + guint ntry); +gdouble gts_graph_bisection_kl_refine (GtsGraphBisection * bg, + guint mmax); +gdouble gts_graph_bisection_bkl_refine (GtsGraphBisection * bg, + guint mmax, + gfloat imbalance); +GtsGraphBisection * gts_graph_bisection_new (GtsWGraph * wg, + guint ntry, + guint mmax, + guint nmin, + gfloat imbalance); +void gts_graph_bisection_destroy (GtsGraphBisection * bg, + gboolean destroy_graphs); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __GTS_H__ */ diff --git a/gts/heap.c b/gts/heap.c new file mode 100644 index 0000000000..4a37e58645 --- /dev/null +++ b/gts/heap.c @@ -0,0 +1,258 @@ +/* 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 "gts.h" + +#define PARENT(i) ((i) >= 2 ? (i)/2 : 0) +#define LEFT_CHILD(i) (2*(i)) +#define RIGHT_CHILD(i) (2*(i) + 1) + +struct _GtsHeap { + GPtrArray * elts; + GCompareFunc func; + gboolean frozen; +}; + +/** + * gts_heap_new: + * @compare_func: a GCompareFunc. + * + * Returns: a new #GtsHeap using @compare_func as a sorting function. + */ +GtsHeap * gts_heap_new (GCompareFunc compare_func) +{ + GtsHeap * heap; + + g_return_val_if_fail (compare_func != NULL, NULL); + + heap = g_malloc (sizeof(GtsHeap)); + heap->elts = g_ptr_array_new (); + heap->func = compare_func; + heap->frozen = FALSE; + return heap; +} + +static void sift_up (GtsHeap * heap, guint i) +{ + gpointer parent, child; + guint p; + gpointer * pdata = heap->elts->pdata; + GCompareFunc func = heap->func; + + child = pdata[i - 1]; + while ((p = PARENT (i))) { + parent = pdata[p - 1]; + if ((*func) (parent, child) > 0) { + pdata[p - 1] = child; + pdata[i - 1] = parent; + i = p; + } + else + i = 0; + } +} + +/** + * gts_heap_insert: + * @heap: a #GtsHeap. + * @p: a pointer to add to the heap. + * + * Inserts a new element @p in the heap. + */ +void gts_heap_insert (GtsHeap * heap, gpointer p) +{ + g_return_if_fail (heap != NULL); + + g_ptr_array_add (heap->elts, p); + if (!heap->frozen) + sift_up (heap, heap->elts->len); +} + +static void sift_down (GtsHeap * heap, guint i) +{ + gpointer left_child, right_child, child, parent; + guint lc, rc, c; + gpointer * pdata = heap->elts->pdata; + guint len = heap->elts->len; + GCompareFunc func = heap->func; + + lc = LEFT_CHILD (i); + rc = RIGHT_CHILD (i); + left_child = lc <= len ? pdata[lc - 1] : NULL; + right_child = rc <= len ? pdata[rc - 1] : NULL; + + parent = pdata[i - 1]; + while (left_child != NULL) { + if (right_child == NULL || + (*func) (left_child, right_child) < 0) { + child = left_child; + c = lc; + } + else { + child = right_child; + c = rc; + } + if ((*func) (parent, child) > 0) { + pdata[i - 1] = child; + pdata[c - 1] = parent; + i = c; + lc = LEFT_CHILD (i); + rc = RIGHT_CHILD (i); + left_child = lc <= len ? pdata[lc - 1] : NULL; + right_child = rc <= len ? pdata[rc - 1] : NULL; + } + else + left_child = NULL; + } +} + +/** + * gts_heap_remove_top: + * @heap: a #GtsHeap. + * + * Removes the element at the top of the heap. + * + * Returns: the element at the top of the heap. + */ +gpointer gts_heap_remove_top (GtsHeap * heap) +{ + gpointer root; + GPtrArray * elts; + guint len; + + g_return_val_if_fail (heap != NULL, NULL); + + elts = heap->elts; len = elts->len; + + if (len == 0) + return NULL; + if (len == 1) + return g_ptr_array_remove_index (elts, 0); + + root = elts->pdata[0]; + elts->pdata[0] = g_ptr_array_remove_index (elts, len - 1); + sift_down (heap, 1); + return root; +} + +/** + * gts_heap_top: + * @heap: a #GtsHeap. + * + * Returns: the element at the top of the heap. + */ +gpointer gts_heap_top (GtsHeap * heap) +{ + GPtrArray * elts; + guint len; + + g_return_val_if_fail (heap != NULL, NULL); + + elts = heap->elts; + len = elts->len; + if (len == 0) + return NULL; + return elts->pdata[0]; +} + +/** + * gts_heap_destroy: + * @heap: a #GtsHeap. + * + * Free all the memory allocated for @heap. + */ +void gts_heap_destroy (GtsHeap * heap) +{ + g_return_if_fail (heap != NULL); + + g_ptr_array_free (heap->elts, TRUE); + g_free (heap); +} + +/** + * gts_heap_thaw: + * @heap: a #GtsHeap. + * + * If @heap has been frozen previously using gts_heap_freeze(), reorder it + * in O(n) time and unfreeze it. + */ +void gts_heap_thaw (GtsHeap * heap) +{ + guint i; + + g_return_if_fail (heap != NULL); + + if (!heap->frozen) + return; + + for (i = heap->elts->len/2; i > 0; i--) + sift_down (heap, i); + + heap->frozen = FALSE; +} + +/** + * gts_heap_foreach: + * @heap: a #GtsHeap. + * @func: the function to call for each element in the heap. + * @user_data: to pass to @func. + */ +void gts_heap_foreach (GtsHeap * heap, + GFunc func, + gpointer user_data) +{ + guint i; + GPtrArray * elts; + + g_return_if_fail (heap != NULL); + g_return_if_fail (func != NULL); + + elts = heap->elts; + for (i = 0; i < elts->len; i++) + (*func) (elts->pdata[i], user_data); +} + +/** + * gts_heap_freeze: + * @heap: a #GtsHeap. + * + * Freezes the heap. Any subsequent operation will not preserve the heap + * property. Used in conjunction with gts_heap_insert() and gts_heap_thaw() + * to create a heap in O(n) time. + */ +void gts_heap_freeze (GtsHeap * heap) +{ + g_return_if_fail (heap != NULL); + + heap->frozen = TRUE; +} + +/** + * gts_heap_size: + * @heap: a #GtsHeap. + * + * Returns: the number of items in @heap. + */ +guint gts_heap_size (GtsHeap * heap) +{ + g_return_val_if_fail (heap != NULL, 0); + + return heap->elts->len; +} diff --git a/gts/hsurface.c b/gts/hsurface.c new file mode 100644 index 0000000000..80ac66a06a --- /dev/null +++ b/gts/hsurface.c @@ -0,0 +1,405 @@ +/* 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_HSPLIT(h, e) ((e)->index = gts_eheap_insert (h, e)) +#define HEAP_REMOVE_HSPLIT(h, e) (gts_eheap_remove (h, (e)->index),\ + (e)->index = NULL) + +static void hsplit_init (GtsHSplit * hsplit) +{ + hsplit->index = NULL; + hsplit->parent = NULL; + hsplit->nchild = 0; +} + +/** + * gts_hsplit_class: + * + * Returns: the #GtsHSplitClass. + */ +GtsHSplitClass * gts_hsplit_class (void) +{ + static GtsHSplitClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo hsplit_info = { + "GtsHSplit", + sizeof (GtsHSplit), + sizeof (GtsHSplitClass), + (GtsObjectClassInitFunc) NULL, + (GtsObjectInitFunc) hsplit_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_split_class ()), + &hsplit_info); + } + + return klass; +} + +/** + * gts_hsplit_new: + * @klass: a #GtsHSplitClass. + * @vs: a #GtsSplit. + * + * Returns: a new #GtsHSplit, hierarchical extension of @vs. + */ +GtsHSplit * gts_hsplit_new (GtsHSplitClass * klass, GtsSplit * vs) +{ + GtsHSplit * hs; + + g_return_val_if_fail (vs != NULL, NULL); + + hs = GTS_HSPLIT (gts_object_new (GTS_OBJECT_CLASS (klass))); + memcpy (hs, vs, sizeof (GtsSplit)); + GTS_OBJECT (hs)->reserved = NULL; + + return hs; +} + +/** + * gts_hsplit_collapse: + * @hs: a #GtsHSplit. + * @hsurface: a #GtsHSurface. + * + * Collapses the #GtsSplit defined by @hs, updates the expandable and + * collapsable priority heaps of @hsurface. + */ +void gts_hsplit_collapse (GtsHSplit * hs, + GtsHSurface * hsurface) +{ + GtsHSplit * parent; + GtsSplit * vs; + + g_return_if_fail (hs != NULL); + g_return_if_fail (hs->nchild == 2); + g_return_if_fail (hsurface != NULL); + + gts_split_collapse (GTS_SPLIT (hs), hsurface->s->edge_class, NULL); + + hsurface->nvertex--; + hs->nchild = 0; + HEAP_REMOVE_HSPLIT (hsurface->collapsable, hs); + HEAP_INSERT_HSPLIT (hsurface->expandable, hs); + + vs = GTS_SPLIT (hs); + if (GTS_IS_HSPLIT (vs->v1)) + HEAP_REMOVE_HSPLIT (hsurface->expandable, GTS_HSPLIT (vs->v1)); + if (GTS_IS_HSPLIT (vs->v2)) + HEAP_REMOVE_HSPLIT (hsurface->expandable, GTS_HSPLIT (vs->v2)); + + parent = hs->parent; + if (parent && ++parent->nchild == 2) + HEAP_INSERT_HSPLIT (hsurface->collapsable, parent); +} + +/** + * gts_hsplit_expand: + * @hs: a #GtsHSplit. + * @hsurface: a #GtsHSurface. + * + * Expands the #GtsSplit defined by @hs (which must be expandable) + * and updates the priority heaps of @hsurface. + */ +void gts_hsplit_expand (GtsHSplit * hs, + GtsHSurface * hsurface) +{ + GtsHSplit * parent; + GtsSplit * vs; + + g_return_if_fail (hs != NULL); + g_return_if_fail (hsurface != NULL); + g_return_if_fail (hs->nchild == 0); + + gts_split_expand (GTS_SPLIT (hs), hsurface->s, hsurface->s->edge_class); + hsurface->nvertex++; + hs->nchild = 2; + HEAP_REMOVE_HSPLIT (hsurface->expandable, hs); + HEAP_INSERT_HSPLIT (hsurface->collapsable, hs); + + vs = GTS_SPLIT (hs); + if (GTS_IS_HSPLIT (vs->v1)) + HEAP_INSERT_HSPLIT (hsurface->expandable, GTS_HSPLIT (vs->v1)); + if (GTS_IS_HSPLIT (vs->v2)) + HEAP_INSERT_HSPLIT (hsurface->expandable, GTS_HSPLIT (vs->v2)); + + parent = hs->parent; + if (parent && parent->nchild-- == 2) + HEAP_REMOVE_HSPLIT (hsurface->collapsable, parent); +} + +static void hsurface_destroy (GtsObject * object) +{ + GtsHSurface * hs = GTS_HSURFACE (object); + + gts_hsurface_traverse (hs, G_POST_ORDER, -1, + (GtsSplitTraverseFunc) gts_object_destroy, + NULL); + g_slist_free (hs->roots); + if (hs->expandable) + gts_eheap_destroy (hs->expandable); + if (hs->collapsable) + gts_eheap_destroy (hs->collapsable); + g_ptr_array_free (hs->split, TRUE); + + (* GTS_OBJECT_CLASS (gts_hsurface_class ())->parent_class->destroy) (object); +} + +static void hsurface_class_init (GtsObjectClass * klass) +{ + klass->destroy = hsurface_destroy; +} + +static void hsurface_init (GtsHSurface * hsurface) +{ + hsurface->s = NULL; + hsurface->roots = NULL; + hsurface->expandable = hsurface->collapsable = NULL; + hsurface->split = g_ptr_array_new (); + hsurface->nvertex = 0; +} + +/** + * gts_hsurface_class: + * + * Returns: the #GtsHSurfaceClass. + */ +GtsHSurfaceClass * gts_hsurface_class (void) +{ + static GtsHSurfaceClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo hsurface_info = { + "GtsHSurface", + sizeof (GtsHSurface), + sizeof (GtsHSurfaceClass), + (GtsObjectClassInitFunc) hsurface_class_init, + (GtsObjectInitFunc) hsurface_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (gts_object_class (), + &hsurface_info); + } + + return klass; +} + +/** + * gts_hsurface_new: + * @klass: a #GtsHSurfaceClass. + * @hsplit_class: a #GtsHSplitClass. + * @psurface: a #GtsPSurface. + * @expand_key: a #GtsKeyFunc used to order the priority heap of expandable + * #GtsHSplit. + * @expand_data: data to be passed to @expand_key. + * @collapse_key: a #GtsKeyFunc used to order the priority heap of collapsable + * #GtsHSplit. + * @collapse_data: data to be passed to @collapsed_key. + * + * Returns: a new #GtsHSurface, hierarchical extension of @psurface + * and using #GtsHSplit of class @hsplit_class. Note that @psurface is + * destroyed in the process. + */ +GtsHSurface * gts_hsurface_new (GtsHSurfaceClass * klass, + GtsHSplitClass * hsplit_class, + GtsPSurface * psurface, + GtsKeyFunc expand_key, + gpointer expand_data, + GtsKeyFunc collapse_key, + gpointer collapse_data) +{ + GtsHSurface * hsurface; + + g_return_val_if_fail (klass != NULL, NULL); + g_return_val_if_fail (hsplit_class != NULL, NULL); + g_return_val_if_fail (psurface != NULL, NULL); + g_return_val_if_fail (expand_key != NULL, NULL); + g_return_val_if_fail (collapse_key != NULL, NULL); + + hsurface = GTS_HSURFACE (gts_object_new (GTS_OBJECT_CLASS (klass))); + hsurface->s = psurface->s; + hsurface->expandable = gts_eheap_new (expand_key, expand_data); + hsurface->collapsable = gts_eheap_new (collapse_key, collapse_data); + g_ptr_array_set_size (hsurface->split, psurface->split->len); + + while (gts_psurface_remove_vertex (psurface)) + ; + while (psurface->pos) { + GtsSplit * vs = g_ptr_array_index (psurface->split, psurface->pos - 1); + GtsHSplit * hs = gts_hsplit_new (hsplit_class, vs); + + g_ptr_array_index (hsurface->split, psurface->pos - 1) = hs; + psurface->pos--; + + hs->parent = GTS_OBJECT (vs)->reserved; + if (hs->parent) { + GtsSplit * vsp = GTS_SPLIT (hs->parent); + + if (vsp->v1 == GTS_OBJECT (vs)) { + g_assert (vsp->v2 != GTS_OBJECT (vs)); + vsp->v1 = GTS_OBJECT (hs); + } + else { + g_assert (vsp->v2 == GTS_OBJECT (vs)); + vsp->v2 = GTS_OBJECT (hs); + } + } + else + hsurface->roots = g_slist_prepend (hsurface->roots, hs); + + hs->nchild = 0; + if (GTS_IS_SPLIT (vs->v1)) + GTS_OBJECT (vs->v1)->reserved = hs; + else + hs->nchild++; + if (GTS_IS_SPLIT (vs->v2)) + GTS_OBJECT (vs->v2)->reserved = hs; + else + hs->nchild++; + + gts_split_expand (vs, psurface->s, psurface->s->edge_class); + + if (hs->nchild == 2) + HEAP_INSERT_HSPLIT (hsurface->collapsable, hs); + } + + hsurface->nvertex = gts_surface_vertex_number (hsurface->s); + gts_object_destroy (GTS_OBJECT (psurface)); + + return hsurface; +} + +/** + * gts_hsurface_traverse: + * @hsurface: a #GtsHSurface. + * @order: the order in which nodes are visited - G_PRE_ORDER or G_POST_ORDER. + * @depth: the maximum depth of the traversal. Nodes below this depth + * will not be visited. If max_depth is -1 all nodes in the tree are + * visited. If depth is 1, only the root is visited. If depth is 2, + * the root and its children are visited. And so on. + * @func: the function to call for each visited #GtsHSplit. + * @data: user data to pass to the function. + * + * Traverses a hierarchical surface starting from its roots. It calls + * the given function for each #GtsHSplit visited. + * See also gts_split_traverse(). + */ +void gts_hsurface_traverse (GtsHSurface * hsurface, + GTraverseType order, + gint depth, + GtsSplitTraverseFunc func, + gpointer data) +{ + GSList * i; + + g_return_if_fail (hsurface != NULL); + g_return_if_fail (func != NULL); + g_return_if_fail (order < G_LEVEL_ORDER); + g_return_if_fail (depth == -1 || depth > 0); + + i = hsurface->roots; + while (i) { + gts_split_traverse (i->data, order, depth, func, data); + i = i->next; + } +} + +/** + * gts_hsurface_foreach: + * @hsurface: a #GtsHSurface. + * @order: the order in which #GtsHSplit are visited - G_PRE_ORDER or + * G_POST_ORDER. + * @func: the function to call for each visited #GtsHSplit. + * @data: user data to pass to the function. + * + * Starts by expanding all the #GtsHSplit of @hsurface. If @order is + * G_PRE_ORDER, calls @func for each #GtsHSplit and collapses it. If + * order is G_POST_ORDER, collapses each #GtsHSplit first and then + * calls @func. The traversal can be halted at any point by returning + * TRUE from func. + */ +void gts_hsurface_foreach (GtsHSurface * hsurface, + GTraverseType order, + GtsFunc func, + gpointer data) +{ + GtsHSplit * hs; + guint i = 0, len; + gboolean stop = FALSE; + + g_return_if_fail (hsurface != NULL); + g_return_if_fail (func != NULL); + g_return_if_fail (order == G_PRE_ORDER || order == G_POST_ORDER); + + while ((hs = gts_eheap_top (hsurface->expandable, NULL))) + gts_hsplit_expand (hs, hsurface); + + len = hsurface->split->len; + switch (order) { + case G_PRE_ORDER: + while (i < len && !stop) { + GtsHSplit * hs = g_ptr_array_index (hsurface->split, i); + stop = (*func) (hs, data); + if (!stop) + gts_hsplit_collapse (hs, hsurface); + i++; + } + break; + case G_POST_ORDER: + while (i < len && !stop) { + GtsHSplit * hs = g_ptr_array_index (hsurface->split, i); + gts_hsplit_collapse (hs, hsurface); + stop = (*func) (hs, data); + i++; + } + break; + default: + g_assert_not_reached (); + } +} + +/** + * gts_hsurface_height: + * @hsurface: a #GtsHSurface. + * + * Returns: the maximum height of the tree described by @hsurface. + */ +guint gts_hsurface_height (GtsHSurface * hsurface) +{ + GSList * i; + guint height = 0; + + g_return_val_if_fail (hsurface != NULL, 0); + + i = hsurface->roots; + while (i) { + guint tmp_height = gts_split_height (i->data); + if (tmp_height > height) + height = tmp_height; + i = i->next; + } + + return height; +} diff --git a/gts/iso.c b/gts/iso.c new file mode 100644 index 0000000000..5995a199cc --- /dev/null +++ b/gts/iso.c @@ -0,0 +1,455 @@ +/* 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 "gts.h" + +typedef enum { LEFT = 0, RIGHT = 1 } Orientation; + +typedef struct { + GtsVertex * v; + Orientation orientation; +} OrientedVertex; + +struct _GtsIsoSlice { + OrientedVertex *** vertices; + guint nx, ny; +}; + +/* coordinates of the edges of the cube (see doc/isocube.fig) */ +static guint c[12][4] = { + {0, 0, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 1}, {0, 0, 1, 0}, + {1, 0, 0, 0}, {1, 0, 0, 1}, {1, 1, 0, 1}, {1, 1, 0, 0}, + {2, 0, 0, 0}, {2, 1, 0, 0}, {2, 1, 1, 0}, {2, 0, 1, 0}}; + +/* first index is the edge number, second index is the edge orientation + (RIGHT or LEFT), third index are the edges which this edge may connect to + in order */ +static guint edge[12][2][3] = { + {{9, 1, 8}, {4, 3, 7}}, /* 0 */ + {{6, 2, 5}, {8, 0, 9}}, /* 1 */ + {{10, 3, 11}, {5, 1, 6}}, /* 2 */ + {{7, 0, 4}, {11, 2, 10}}, /* 3 */ + {{3, 7, 0}, {8, 5, 11}}, /* 4 */ + {{11, 4, 8}, {1, 6, 2}}, /* 5 */ + {{2, 5, 1}, {9, 7, 10}}, /* 6 */ + {{10, 6, 9}, {0, 4, 3}}, /* 7 */ + {{5, 11, 4}, {0, 9, 1}}, /* 8 */ + {{1, 8, 0}, {7, 10, 6}}, /* 9 */ + {{6, 9, 7}, {3, 11, 2}}, /* 10 */ + {{2, 10, 3}, {4, 8, 5}} /* 11 */ +}; + +static void ** malloc2D (guint nx, guint ny, gulong size) +{ + void ** m = g_malloc (nx*sizeof (void *)); + guint i; + + for (i = 0; i < nx; i++) + m[i] = g_malloc0 (ny*size); + + return m; +} + +static void free2D (void ** m, guint nx) +{ + guint i; + + g_return_if_fail (m != NULL); + + for (i = 0; i < nx; i++) + g_free (m[i]); + g_free (m); +} + +/** + * gts_grid_plane_new: + * @nx: + * @ny: + * + * Returns: + */ +GtsGridPlane * gts_grid_plane_new (guint nx, guint ny) +{ + GtsGridPlane * g = g_malloc (sizeof (GtsGridPlane)); + + g->p = (GtsPoint **) malloc2D (nx, ny, sizeof (GtsPoint)); + g->nx = nx; + g->ny = ny; + + return g; +} + +/** + * gts_grid_plane_destroy: + * @g: + * + */ +void gts_grid_plane_destroy (GtsGridPlane * g) +{ + g_return_if_fail (g != NULL); + + free2D ((void **) g->p, g->nx); + g_free (g); +} + +/** + * gts_iso_slice_new: + * @nx: number of vertices in the x direction. + * @ny: number of vertices in the y direction. + * + * Returns: a new #GtsIsoSlice. + */ +GtsIsoSlice * gts_iso_slice_new (guint nx, guint ny) +{ + GtsIsoSlice * slice; + + g_return_val_if_fail (nx > 1, NULL); + g_return_val_if_fail (ny > 1, NULL); + + slice = g_malloc (sizeof (GtsIsoSlice)); + + slice->vertices = g_malloc (3*sizeof (OrientedVertex **)); + slice->vertices[0] = + (OrientedVertex **) malloc2D (nx, ny, sizeof (OrientedVertex)); + slice->vertices[1] = + (OrientedVertex **) malloc2D (nx - 1, ny, sizeof (OrientedVertex)); + slice->vertices[2] = + (OrientedVertex **) malloc2D (nx, ny - 1, sizeof (OrientedVertex)); + slice->nx = nx; + slice->ny = ny; + + return slice; +} + +/** + * gts_iso_slice_fill: + * @slice: a #GtsIsoSlice. + * @plane1: a #GtsGridPlane. + * @plane2: another #GtsGridPlane. + * @f1: values of the function corresponding to @plane1. + * @f2: values of the function corresponding to @plane2. + * @iso: isosurface value. + * @klass: a #GtsVertexClass or one of its descendant to be used for the + * new vertices. + * + * Fill @slice with the coordinates of the vertices defined by + * f1 (x,y,z) = @iso and f2 (x, y, z) = @iso. + */ +void gts_iso_slice_fill (GtsIsoSlice * slice, + GtsGridPlane * plane1, + GtsGridPlane * plane2, + gdouble ** f1, + gdouble ** f2, + gdouble iso, + GtsVertexClass * klass) +{ + OrientedVertex *** vertices; + GtsPoint ** p1, ** p2 = NULL; + guint i, j, nx, ny; + + g_return_if_fail (slice != NULL); + g_return_if_fail (plane1 != NULL); + g_return_if_fail (f1 != NULL); + g_return_if_fail (f2 == NULL || plane2 != NULL); + + p1 = plane1->p; + if (plane2) + p2 = plane2->p; + vertices = slice->vertices; + nx = slice->nx; + ny = slice->ny; + + if (f2) + for (i = 0; i < nx; i++) + for (j = 0; j < ny; j++) { + gdouble v1 = f1[i][j] - iso; + gdouble v2 = f2[i][j] - iso; + if ((v1 >= 0. && v2 < 0.) || (v1 < 0. && v2 >= 0.)) { + gdouble c2 = v1/(v1 - v2), c1 = 1. - c2; + vertices[0][i][j].v = + gts_vertex_new (klass, + c1*p1[i][j].x + c2*p2[i][j].x, + c1*p1[i][j].y + c2*p2[i][j].y, + c1*p1[i][j].z + c2*p2[i][j].z); + vertices[0][i][j].orientation = v2 >= 0. ? RIGHT : LEFT; + } + else + vertices[0][i][j].v = NULL; + } + for (i = 0; i < nx - 1; i++) + for (j = 0; j < ny; j++) { + gdouble v1 = f1[i][j] - iso; + gdouble v2 = f1[i+1][j] - iso; + if ((v1 >= 0. && v2 < 0.) || (v1 < 0. && v2 >= 0.)) { + gdouble c2 = v1/(v1 - v2), c1 = 1. - c2; + vertices[1][i][j].v = + gts_vertex_new (klass, + c1*p1[i][j].x + c2*p1[i+1][j].x, + c1*p1[i][j].y + c2*p1[i+1][j].y, + c1*p1[i][j].z + c2*p1[i+1][j].z); + vertices[1][i][j].orientation = v2 >= 0. ? RIGHT : LEFT; + } + else + vertices[1][i][j].v = NULL; + } + for (i = 0; i < nx; i++) + for (j = 0; j < ny - 1; j++) { + gdouble v1 = f1[i][j] - iso; + gdouble v2 = f1[i][j+1] - iso; + if ((v1 >= 0. && v2 < 0.) || (v1 < 0. && v2 >= 0.)) { + gdouble c2 = v1/(v1 - v2), c1 = 1. - c2; + vertices[2][i][j].v = + gts_vertex_new (klass, + c1*p1[i][j].x + c2*p1[i][j+1].x, + c1*p1[i][j].y + c2*p1[i][j+1].y, + c1*p1[i][j].z + c2*p1[i][j+1].z); + vertices[2][i][j].orientation = v2 >= 0. ? RIGHT : LEFT; + } + else + vertices[2][i][j].v = NULL; + } +} + +/** + * gts_iso_slice_fill_cartesian: + * @slice: a #GtsIsoSlice. + * @g: a #GtsCartesianGrid. + * @f1: values of the function for plane z = @g.z. + * @f2: values of the function for plane z = @g.z + @g.dz. + * @iso: isosurface value. + * @klass: a #GtsVertexClass. + * + * Fill @slice with the coordinates of the vertices defined by + * f1 (x,y,z) = @iso and f2 (x, y, z) = @iso. + */ +void gts_iso_slice_fill_cartesian (GtsIsoSlice * slice, + GtsCartesianGrid g, + gdouble ** f1, + gdouble ** f2, + gdouble iso, + GtsVertexClass * klass) +{ + OrientedVertex *** vertices; + guint i, j; + gdouble x, y; + + g_return_if_fail (slice != NULL); + g_return_if_fail (f1 != NULL); + + vertices = slice->vertices; + + if (f2) + for (i = 0, x = g.x; i < g.nx; i++, x += g.dx) + for (j = 0, y = g.y; j < g.ny; j++, y += g.dy) { + gdouble v1 = f1[i][j] - iso; + gdouble v2 = f2[i][j] - iso; + if ((v1 >= 0. && v2 < 0.) || (v1 < 0. && v2 >= 0.)) { + vertices[0][i][j].v = + gts_vertex_new (klass, + x, y, g.z + g.dz*v1/(v1 - v2)); + vertices[0][i][j].orientation = v2 >= 0. ? RIGHT : LEFT; + } + else + vertices[0][i][j].v = NULL; + } + for (i = 0, x = g.x; i < g.nx - 1; i++, x += g.dx) + for (j = 0, y = g.y; j < g.ny; j++, y += g.dy) { + gdouble v1 = f1[i][j] - iso; + gdouble v2 = f1[i+1][j] - iso; + if ((v1 >= 0. && v2 < 0.) || (v1 < 0. && v2 >= 0.)) { + vertices[1][i][j].v = + gts_vertex_new (klass, x + g.dx*v1/(v1 - v2), y, g.z); + vertices[1][i][j].orientation = v2 >= 0. ? RIGHT : LEFT; + } + else + vertices[1][i][j].v = NULL; + } + for (i = 0, x = g.x; i < g.nx; i++, x += g.dx) + for (j = 0, y = g.y; j < g.ny - 1; j++, y += g.dy) { + gdouble v1 = f1[i][j] - iso; + gdouble v2 = f1[i][j+1] - iso; + if ((v1 >= 0. && v2 < 0.) || (v1 < 0. && v2 >= 0.)) { + vertices[2][i][j].v = + gts_vertex_new (klass, x, y + g.dy*v1/(v1 - v2), g.z); + vertices[2][i][j].orientation = v2 >= 0. ? RIGHT : LEFT; + } + else + vertices[2][i][j].v = NULL; + } +} + +/** + * gts_iso_slice_destroy: + * @slice: a #GtsIsoSlice. + * + * Free all memory allocated for @slice. + */ +void gts_iso_slice_destroy (GtsIsoSlice * slice) +{ + g_return_if_fail (slice != NULL); + + free2D ((void **) slice->vertices[0], slice->nx); + free2D ((void **) slice->vertices[1], slice->nx - 1); + free2D ((void **) slice->vertices[2], slice->nx); + g_free (slice->vertices); + g_free (slice); +} + +/** + * gts_isosurface_slice: + * @slice1: a #GtsIsoSlice. + * @slice2: another #GtsIsoSlice. + * @surface: a #GtsSurface. + * + * Given two successive slices @slice1 and @slice2 link their vertices with + * segments and triangles which are added to @surface. + */ +void gts_isosurface_slice (GtsIsoSlice * slice1, + GtsIsoSlice * slice2, + GtsSurface * surface) +{ + guint j, k, l, nx, ny; + OrientedVertex *** vertices[2]; + GtsVertex * va[12]; + + g_return_if_fail (slice1 != NULL); + g_return_if_fail (slice2 != NULL); + g_return_if_fail (surface != NULL); + g_return_if_fail (slice1->nx == slice2->nx && slice1->ny == slice2->ny); + + vertices[0] = slice1->vertices; + vertices[1] = slice2->vertices; + nx = slice1->nx; + ny = slice1->ny; + + /* link vertices with segments and triangles */ + for (j = 0; j < nx - 1; j++) + for (k = 0; k < ny - 1; k++) { + gboolean cube_is_cut = FALSE; + for (l = 0; l < 12; l++) { + guint nv = 0, e = l; + OrientedVertex ov = + vertices[c[e][1]][c[e][0]][j + c[e][2]][k + c[e][3]]; + while (ov.v && !GTS_OBJECT (ov.v)->reserved) { + guint m = 0, * ne = edge[e][ov.orientation]; + va[nv++] = ov.v; + GTS_OBJECT (ov.v)->reserved = surface; + ov.v = NULL; + while (m < 3 && !ov.v) { + e = ne[m++]; + ov = vertices[c[e][1]][c[e][0]][j + c[e][2]][k + c[e][3]]; + } + } + /* create edges and faces */ + if (nv > 2) { + GtsEdge * e1, * e2, * e3; + guint m; + if (!(e1 = GTS_EDGE (gts_vertices_are_connected (va[0], va[1])))) + e1 = gts_edge_new (surface->edge_class, va[0], va[1]); + for (m = 1; m < nv - 1; m++) { + if (!(e2 = GTS_EDGE (gts_vertices_are_connected (va[m], va[m+1])))) + e2 = gts_edge_new (surface->edge_class, va[m], va[m+1]); + if (!(e3 = GTS_EDGE (gts_vertices_are_connected (va[m+1], va[0])))) + e3 = gts_edge_new (surface->edge_class, va[m+1], va[0]); + gts_surface_add_face (surface, + gts_face_new (surface->face_class, + e1, e2, e3)); + e1 = e3; + } + } + if (nv > 0) + cube_is_cut = TRUE; + } + if (cube_is_cut) + for (l = 0; l < 12; l++) { + GtsVertex * v = + vertices[c[l][1]][c[l][0]][j + c[l][2]][k + c[l][3]].v; + if (v) + GTS_OBJECT (v)->reserved = NULL; + } + } +} + +#define SWAP(s1, s2, tmp) (tmp = s1, s1 = s2, s2 = tmp) + +/** + * gts_isosurface_cartesian: + * @surface: a #GtsSurface. + * @g: a #GtsCartesianGrid. + * @f: a #GtsIsoCartesianFunc. + * @data: user data to be passed to @f. + * @iso: isosurface value. + * + * Adds to @surface new faces defining the isosurface f(x,y,z) = @iso. By + * convention, the normals to the surface are pointing toward the positive + * values of f(x,y,z) - @iso. + * + * The user function @f is called successively for each value of the z + * coordinate defined by @g. It must fill the corresponding (x,y) plane with + * the values of the function for which the isosurface is to be computed. + */ +void gts_isosurface_cartesian (GtsSurface * surface, + GtsCartesianGrid g, + GtsIsoCartesianFunc f, + gpointer data, + gdouble iso) +{ + void * tmp; + gdouble ** f1, ** f2; + GtsIsoSlice * slice1, * slice2; + guint i; + + g_return_if_fail (surface != NULL); + g_return_if_fail (f != NULL); + g_return_if_fail (g.nx > 1); + g_return_if_fail (g.ny > 1); + g_return_if_fail (g.nz > 1); + + slice1 = gts_iso_slice_new (g.nx, g.ny); + slice2 = gts_iso_slice_new (g.nx, g.ny); + f1 = (gdouble **) malloc2D (g.nx, g.ny, sizeof (gdouble)); + f2 = (gdouble **) malloc2D (g.nx, g.ny, sizeof (gdouble)); + + (*f) (f1, g, 0, data); + g.z += g.dz; + (*f) (f2, g, 1, data); + g.z -= g.dz; + gts_iso_slice_fill_cartesian (slice1, g, f1, f2, iso, + surface->vertex_class); + g.z += g.dz; + for (i = 2; i < g.nz; i++) { + g.z += g.dz; + (*f) (f1, g, i, data); + SWAP (f1, f2, tmp); + g.z -= g.dz; + gts_iso_slice_fill_cartesian (slice2, g, f1, f2, iso, + surface->vertex_class); + g.z += g.dz; + gts_isosurface_slice (slice1, slice2, surface); + SWAP (slice1, slice2, tmp); + } + gts_iso_slice_fill_cartesian (slice2, g, f2, NULL, iso, + surface->vertex_class); + gts_isosurface_slice (slice1, slice2, surface); + + gts_iso_slice_destroy (slice1); + gts_iso_slice_destroy (slice2); + free2D ((void **) f1, g.nx); + free2D ((void **) f2, g.nx); +} diff --git a/gts/isotetra.c b/gts/isotetra.c new file mode 100644 index 0000000000..35fe2bae50 --- /dev/null +++ b/gts/isotetra.c @@ -0,0 +1,840 @@ +/* GTS-Library conform marching tetrahedra algorithm + * Copyright (C) 2002 Gert Wollny + * + * 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 +#ifdef NATIVE_WIN32 +# include +# define M_SQRT2 1.41421356237309504880 +#endif /* NATIVE_WIN32 */ + +typedef struct { + gint nx, ny; + gdouble ** data; +} slice_t; + +typedef struct { + gint x, y, z; + gboolean mid; + gdouble d; +} tetra_vertex_t; + +/* this helper is a lookup table for vertices */ +typedef struct { + gint nx, ny; + GtsVertex ** vtop, ** vmid, **vbot; +} helper_t ; + +typedef struct { + GHashTable * vbot, * vtop; +} helper_bcl ; + + +static helper_t * init_helper (int nx, int ny) +{ + gint nxy = 4*nx*ny; + helper_t *retval = g_malloc0 (sizeof (helper_t)); + + retval->nx = nx; + retval->ny = ny; + retval->vtop = g_malloc0 (sizeof (GtsVertex *)*nxy); + retval->vmid = g_malloc0 (sizeof (GtsVertex *)*nxy); + retval->vbot = g_malloc0 (sizeof (GtsVertex *)*nxy); + return retval; +} + +static helper_bcl * init_helper_bcl (void) +{ + helper_bcl *retval = g_malloc0 (sizeof (helper_bcl)); + + retval->vtop = g_hash_table_new (g_str_hash, g_str_equal); + retval->vbot = g_hash_table_new (g_str_hash, g_str_equal); + return retval; +} + +static void free_helper (helper_t * h) +{ + g_free (h->vtop); + g_free (h->vmid); + g_free (h->vbot); + g_free (h); +} + +static void free_helper_bcl (helper_bcl * h) +{ + g_hash_table_destroy (h->vtop); + g_hash_table_destroy (h->vbot); + g_free (h); +} + +/* move the vertices in the bottom slice to the top, and clear the + other slices in the lookup tables */ +static void helper_advance (helper_t * h) +{ + GtsVertex ** help = h->vbot; + h->vbot = h->vtop; + h->vtop = help; + + memset (h->vmid, 0, 4*sizeof(GtsVertex *) * h->nx * h->ny); + memset (h->vbot, 0, 4*sizeof(GtsVertex *) * h->nx * h->ny); +} + +static void helper_advance_bcl (helper_bcl * h) +{ + GHashTable * help = g_hash_table_new (g_str_hash, g_str_equal); + + g_hash_table_destroy (h->vbot); + h->vbot = h->vtop; + h->vtop = help; +} + +/* find the zero-crossing of line through v1 and v2 and return the + corresponding GtsVertex */ +static GtsVertex * get_vertex (gint mz, + const tetra_vertex_t * v1, + const tetra_vertex_t * v2, + helper_t * help, + GtsCartesianGrid * g, + GtsVertexClass * klass) +{ + GtsVertex ** vertex; + gint x, y, index, idx2, z; + gdouble dx, dy, dz, d; + + g_assert (v1->d - v2->d != 0.); + + dx = dy = dz = 0.0; + d = v1->d/(v1->d - v2->d); + + index = 0; + + if (v1->x != v2->x) { + index |= 1; + dx = d; + } + + if (v1->y != v2->y) { + index |= 2; + dy = d; + } + + if (v1->z != v2->z) { + dz = d; + } + + x = v1->x; + if (v1->x > v2->x) { x = v2->x; dx = 1.0 - dx; } + + y = v1->y; + if (v1->y > v2->y) { y = v2->y; dy = 1.0 - dy;} + + z = v1->z; + if (v1->z > v2->z) { z = v2->z; dz = 1.0 - dz;} + + idx2 = 4 * ( x + y * help->nx ) + index; + + if (v1->z == v2->z) + vertex = (mz == z) ? &help->vtop[idx2] : &help->vbot[idx2]; + else + vertex = &help->vmid[idx2]; + + if (mz != z && dz != 0.0) { + fprintf(stderr, "%f \n", dz); + } + + /* if vertex is not yet created, do it now */ + if (!*vertex) + *vertex = gts_vertex_new (klass, + g->dx * ( x + dx) + g->x, + g->dy * ( y + dy) + g->y, + g->dz * ( z + dz) + g->z); + + return *vertex; +} + +static GtsVertex * get_vertex_bcl (gint mz, + const tetra_vertex_t * v1, + const tetra_vertex_t * v2, + helper_bcl * help, + GtsCartesianGrid * g, + GtsVertexClass * klass) +{ + GtsVertex * v; + GHashTable * table; + gchar * s1, * s2, * hash; + gdouble x1, x2, y1, y2, z1, z2, d; + + g_assert (v1->d - v2->d != 0.); + + /* first find correct hash table */ + if ((v1->z > mz) && (v2->z > mz)) + table = help->vtop; + else + table = help->vbot; + + d = v1->d / (v1->d - v2->d); + + /* sort vertices */ + s1 = g_strdup_printf ("%d %d %d %d", v1->x, v1->y, v1->z, v1->mid); + s2 = g_strdup_printf ("%d %d %d %d", v2->x, v2->y, v2->z, v2->mid); + + hash = (d == 0.0) ? g_strdup (s1) : + (d == 1.0) ? g_strdup (s2) : + (strcmp (s1, s2) < 0) ? g_strjoin (" ", s1, s2, NULL) : + g_strjoin (" ", s2, s1, NULL); + + /* return existing vertex or make a new one */ + v = g_hash_table_lookup (table, hash); + if (!v){ + + x1 = g->dx * (v1->x + (v1->mid / 2.0)) + g->x; + x2 = g->dx * (v2->x + (v2->mid / 2.0)) + g->x; + y1 = g->dy * (v1->y + (v1->mid / 2.0)) + g->y; + y2 = g->dy * (v2->y + (v2->mid / 2.0)) + g->y; + z1 = g->dz * (v1->z + (v1->mid / 2.0)) + g->z; + z2 = g->dz * (v2->z + (v2->mid / 2.0)) + g->z; + + v = gts_vertex_new (klass, x1 * (1.0 - d) + d * x2, + y1 * (1.0 - d) + d * y2, + z1 * (1.0 - d) + d * z2); + + g_hash_table_insert (table, g_strdup(hash), v); + } + g_free (s1); + g_free (s2); + g_free (hash); + + return v; +} + +/* create an edge connecting the zero crossings of lines through a + pair of vertices, or return an existing one */ +static GtsEdge * get_edge (GtsVertex * v1, GtsVertex * v2, + GtsEdgeClass * klass) +{ + GtsSegment *s; + GtsEdge *edge; + + g_assert (v1); + g_assert (v2); + + s = gts_vertices_are_connected (v1, v2); + + if (GTS_IS_EDGE (s)) + edge = GTS_EDGE(s); + else + edge = gts_edge_new (klass, v1, v2); + return edge; +} + +static void add_face (GtsSurface * surface, + const tetra_vertex_t * a1, const tetra_vertex_t * a2, + const tetra_vertex_t * b1, const tetra_vertex_t * b2, + const tetra_vertex_t * c1, const tetra_vertex_t * c2, + gint rev, helper_t * help, + gint z, GtsCartesianGrid * g) +{ + GtsFace * t; + GtsEdge * e1, * e2, * e3; + GtsVertex * v1 = get_vertex (z, a1, a2, help, g, surface->vertex_class); + GtsVertex * v2 = get_vertex (z, b1, b2, help, g, surface->vertex_class); + GtsVertex * v3 = get_vertex (z, c1, c2, help, g, surface->vertex_class); + + g_assert (v1 != v2); + g_assert (v2 != v3); + g_assert (v1 != v3); + + if (!rev) { + e1 = get_edge (v1, v2, surface->edge_class); + e2 = get_edge (v2, v3, surface->edge_class); + e3 = get_edge (v1, v3, surface->edge_class); + } else { + e1 = get_edge (v1, v3, surface->edge_class); + e2 = get_edge (v2, v3, surface->edge_class); + e3 = get_edge (v1, v2, surface->edge_class); + } + + t = gts_face_new (surface->face_class, e1, e2, e3); + gts_surface_add_face (surface, t); +} + +static void add_face_bcl (GtsSurface * surface, + const tetra_vertex_t * a1, + const tetra_vertex_t * a2, + const tetra_vertex_t * b1, + const tetra_vertex_t * b2, + const tetra_vertex_t * c1, + const tetra_vertex_t * c2, + gint rev, helper_bcl * help, + gint z, GtsCartesianGrid * g) +{ + GtsFace * t; + GtsEdge * e1, * e2, * e3; + GtsVertex * v1 = get_vertex_bcl (z, a1, a2, help, g, surface->vertex_class); + GtsVertex * v2 = get_vertex_bcl (z, b1, b2, help, g, surface->vertex_class); + GtsVertex * v3 = get_vertex_bcl (z, c1, c2, help, g, surface->vertex_class); + + if (v1 == v2 || v2 == v3 || v1 == v3) + return; + + if (!rev) { + e1 = get_edge (v1, v2, surface->edge_class); + e2 = get_edge (v2, v3, surface->edge_class); + e3 = get_edge (v1, v3, surface->edge_class); + } else { + e1 = get_edge (v1, v3, surface->edge_class); + e2 = get_edge (v2, v3, surface->edge_class); + e3 = get_edge (v1, v2, surface->edge_class); + } + + t = gts_face_new (surface->face_class, e1, e2, e3); + gts_surface_add_face (surface, t); +} + +/* create a new slice of site nx \times ny */ +static slice_t * new_slice (gint nx, gint ny) +{ + gint x; + slice_t * retval = g_malloc (sizeof (slice_t)); + + retval->data = g_malloc (nx*sizeof(gdouble *)); + retval->nx = nx; + retval->ny = ny; + for (x = 0; x < nx; x++) + retval->data[x] = g_malloc (ny*sizeof (gdouble)); + return retval; +} + +/* initialize a slice with inival */ +static void slice_init (slice_t * slice, gdouble inival) +{ + gint x, y; + + g_assert (slice); + + for (x = 0; x < slice->nx; x++) + for (y = 0; y < slice->ny; y++) + slice->data[x][y] = inival; +} + +/* free the memory of a slice */ +static void free_slice (slice_t * slice) +{ + gint x; + + g_return_if_fail (slice != NULL); + + for (x = 0; x < slice->nx; x++) + g_free (slice->data[x]); + g_free (slice->data); + g_free (slice); +} + +static void analyze_tetrahedra (const tetra_vertex_t * a, + const tetra_vertex_t * b, + const tetra_vertex_t * c, + const tetra_vertex_t * d, + gint parity, GtsSurface * surface, + helper_t * help, + gint z, GtsCartesianGrid * g) +{ + gint rev = parity; + gint code = 0; + + if (a->d >= 0.) code |= 1; + if (b->d >= 0.) code |= 2; + if (c->d >= 0.) code |= 4; + if (d->d >= 0.) code |= 8; + + switch (code) { + case 15: + case 0: return; /* all inside or outside */ + + case 14:rev = !parity; + case 1:add_face (surface, a, b, a, d, a, c, rev, help, z, g); + break; + case 13:rev = ! parity; + case 2:add_face (surface, a, b, b, c, b, d, rev, help, z, g); + break; + case 12:rev = !parity; + case 3:add_face (surface, a, d, a, c, b, c, rev, help, z, g); + add_face (surface, a, d, b, c, b, d, rev, help, z, g); + break; + case 11:rev = !parity; + case 4:add_face (surface, a, c, c, d, b, c, rev, help, z, g); + break; + case 10:rev = !parity; + case 5: add_face (surface, a, b, a, d, c, d, rev, help, z, g); + add_face (surface, a, b, c, d, b, c, rev, help, z, g); + break; + case 9:rev = !parity; + case 6:add_face (surface, a, b, a, c, c, d, rev, help, z, g); + add_face (surface, a, b, c, d, b, d, rev, help, z, g); + break; + case 7:rev = !parity; + case 8:add_face (surface, a, d, b, d, c, d, rev, help, z, g); + break; + } +} + +static void analyze_tetrahedra_bcl (const tetra_vertex_t * a, + const tetra_vertex_t * b, + const tetra_vertex_t * c, + const tetra_vertex_t * d, + GtsSurface * surface, + helper_bcl * help, + gint z, GtsCartesianGrid * g) +{ + gint rev = 0; + gint code = 0; + + if (a->d >= 0.) code |= 1; + if (b->d >= 0.) code |= 2; + if (c->d >= 0.) code |= 4; + if (d->d >= 0.) code |= 8; + + switch (code) { + case 15: + case 0: return; /* all inside or outside */ + + case 14:rev = !rev; + case 1:add_face_bcl (surface, a, b, a, d, a, c, rev, help, z, g); + break; + case 13:rev = !rev; + case 2:add_face_bcl (surface, a, b, b, c, b, d, rev, help, z, g); + break; + case 12:rev = !rev; + case 3:add_face_bcl (surface, a, d, a, c, b, c, rev, help, z, g); + add_face_bcl (surface, a, d, b, c, b, d, rev, help, z, g); + break; + case 11:rev = !rev; + case 4:add_face_bcl (surface, a, c, c, d, b, c, rev, help, z, g); + break; + case 10:rev = !rev; + case 5: add_face_bcl (surface, a, b, a, d, c, d, rev, help, z, g); + add_face_bcl (surface, a, b, c, d, b, c, rev, help, z, g); + break; + case 9:rev = !rev; + case 6:add_face_bcl (surface, a, b, a, c, c, d, rev, help, z, g); + add_face_bcl (surface, a, b, c, d, b, d, rev, help, z, g); + break; + case 7:rev = !rev; + case 8:add_face_bcl (surface, a, d, b, d, c, d, rev, help, z, g); + break; + } +} + +static void iso_slice_evaluate (slice_t * s1, slice_t * s2, + GtsCartesianGrid g, + gint z, GtsSurface * surface, helper_t * help) +{ + gint x,y; + tetra_vertex_t v0, v1, v2, v3, v4, v5, v6, v7; + gdouble ** s1p = s1->data; + gdouble ** s2p = s2->data; + + for (y = 0; y < g.ny-1; y++) + for (x = 0; x < g.nx-1; x++) { + gint parity = (((x ^ y) ^ z) & 1); + + v0.x = x ; v0.y = y ; v0.z = z ; v0.mid = FALSE; v0.d = s1p[x ][y ]; + v1.x = x ; v1.y = y+1; v1.z = z ; v1.mid = FALSE; v1.d = s1p[x ][y+1]; + v2.x = x+1; v2.y = y ; v2.z = z ; v2.mid = FALSE; v2.d = s1p[x+1][y ]; + v3.x = x+1; v3.y = y+1; v3.z = z ; v3.mid = FALSE; v3.d = s1p[x+1][y+1]; + v4.x = x ; v4.y = y ; v4.z = z+1; v4.mid = FALSE; v4.d = s2p[x ][y ]; + v5.x = x ; v5.y = y+1; v5.z = z+1; v5.mid = FALSE; v5.d = s2p[x ][y+1]; + v6.x = x+1; v6.y = y ; v6.z = z+1; v6.mid = FALSE; v6.d = s2p[x+1][y ]; + v7.x = x+1; v7.y = y+1; v7.z = z+1; v7.mid = FALSE; v7.d = s2p[x+1][y+1]; + + if (parity == 0) { + analyze_tetrahedra (&v0, &v1, &v2, &v4, parity, surface, help, z, &g); + analyze_tetrahedra (&v7, &v1, &v4, &v2, parity, surface, help, z, &g); + analyze_tetrahedra (&v1, &v7, &v3, &v2, parity, surface, help, z, &g); + analyze_tetrahedra (&v1, &v7, &v4, &v5, parity, surface, help, z, &g); + analyze_tetrahedra (&v2, &v6, &v4, &v7, parity, surface, help, z, &g); + }else{ + analyze_tetrahedra (&v4, &v5, &v6, &v0, parity, surface, help, z, &g); + analyze_tetrahedra (&v3, &v5, &v0, &v6, parity, surface, help, z, &g); + analyze_tetrahedra (&v5, &v3, &v7, &v6, parity, surface, help, z, &g); + analyze_tetrahedra (&v5, &v3, &v0, &v1, parity, surface, help, z, &g); + analyze_tetrahedra (&v6, &v2, &v0, &v3, parity, surface, help, z, &g); + } + } +} + +static void iso_slice_evaluate_bcl (slice_t * s1, slice_t * s2, slice_t * s3, + GtsCartesianGrid g, + gint z, GtsSurface * surface, + helper_bcl * help) +{ + gint x,y; + tetra_vertex_t v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, w0; + gdouble ** s1p = s1->data; + gdouble ** s2p = s2->data; + gdouble ** s3p = s3->data; + + for (y = 0; y < g.ny-2; y++) + for (x = 0; x < g.nx-2; x++) { + v0.x = x ; v0.y = y ; v0.z = z ; v0.mid = TRUE; + v0.d = (s1p[x ][y ] + s2p[x ][y ] + + s1p[x ][y+1] + s2p[x ][y+1] + + s1p[x+1][y ] + s2p[x+1][y ] + + s1p[x+1][y+1] + s2p[x+1][y+1])/8.0; + + v1.x = x+1; v1.y = y ; v1.z = z ; v1.mid = TRUE; + v1.d = (s1p[x+1][y ] + s2p[x+1][y ] + + s1p[x+1][y+1] + s2p[x+1][y+1] + + s1p[x+2][y ] + s2p[x+2][y ] + + s1p[x+2][y+1] + s2p[x+2][y+1])/8.0; + + v2.x = x ; v2.y = y+1; v2.z = z ; v2.mid = TRUE; + v2.d = (s1p[x ][y+1] + s2p[x ][y+1] + + s1p[x ][y+2] + s2p[x ][y+2] + + s1p[x+1][y+1] + s2p[x+1][y+1] + + s1p[x+1][y+2] + s2p[x+1][y+2])/8.0; + + v3.x = x ; v3.y = y ; v3.z = z+1; v3.mid = TRUE; + v3.d = (s2p[x ][y ] + s3p[x ][y ] + + s2p[x ][y+1] + s3p[x ][y+1] + + s2p[x+1][y ] + s3p[x+1][y ] + + s2p[x+1][y+1] + s3p[x+1][y+1])/8.0; + + v4.x = x+1; v4.y = y ; v4.z = z ; v4.mid = FALSE; v4.d = s1p[x+1][y ]; + v5.x = x ; v5.y = y+1; v5.z = z ; v5.mid = FALSE; v5.d = s1p[x ][y+1]; + v6.x = x+1; v6.y = y+1; v6.z = z ; v6.mid = FALSE; v6.d = s1p[x+1][y+1]; + v7.x = x+1; v7.y = y ; v7.z = z+1; v7.mid = FALSE; v7.d = s2p[x+1][y ]; + v8.x = x ; v8.y = y+1; v8.z = z+1; v8.mid = FALSE; v8.d = s2p[x ][y+1]; + v9.x = x+1; v9.y = y+1; v9.z = z+1; v9.mid = FALSE; v9.d = s2p[x+1][y+1]; + w0.x = x ; w0.y = y ; w0.z = z+1; w0.mid = FALSE; w0.d = s2p[x ][y ]; + + analyze_tetrahedra_bcl (&v0, &v9, &v6, &v1, surface, help, z, &g); + analyze_tetrahedra_bcl (&v0, &v6, &v4, &v1, surface, help, z, &g); + analyze_tetrahedra_bcl (&v0, &v4, &v7, &v1, surface, help, z, &g); + analyze_tetrahedra_bcl (&v0, &v7, &v9, &v1, surface, help, z, &g); + analyze_tetrahedra_bcl (&v0, &v5, &v6, &v2, surface, help, z, &g); + analyze_tetrahedra_bcl (&v0, &v6, &v9, &v2, surface, help, z, &g); + analyze_tetrahedra_bcl (&v0, &v9, &v8, &v2, surface, help, z, &g); + analyze_tetrahedra_bcl (&v0, &v8, &v5, &v2, surface, help, z, &g); + analyze_tetrahedra_bcl (&v0, &v8, &v9, &v3, surface, help, z, &g); + analyze_tetrahedra_bcl (&v0, &v9, &v7, &v3, surface, help, z, &g); + analyze_tetrahedra_bcl (&v0, &v7, &w0, &v3, surface, help, z, &g); + analyze_tetrahedra_bcl (&v0, &w0, &v8, &v3, surface, help, z, &g); + } +} + +/* copy src into dest by stripping off the iso value and leave out + the boundary (which should be G_MINDOUBLE) */ +static void copy_to_bounded (slice_t * dest, slice_t * src, + gdouble iso, gdouble fill) +{ + gint x,y; + gdouble * src_ptr; + gdouble * dest_ptr = dest->data[0]; + + g_assert(dest->ny == src->ny + 2); + g_assert(dest->nx == src->nx + 2); + + for (y = 0; y < dest->ny; ++y, ++dest_ptr) + *dest_ptr = fill; + + for (x = 1; x < src->nx - 1; ++x) { + dest_ptr = dest->data[x]; + src_ptr = src->data[x-1]; + *dest_ptr++ = fill; + for (y = 0; y < src->ny; ++y, ++dest_ptr, ++src_ptr) + *dest_ptr = *src_ptr - iso; + *dest_ptr++ = fill; + } + + dest_ptr = dest->data[y]; + + for (y = 0; y < dest->ny; ++y, ++dest_ptr) + *dest_ptr = fill; +} + +static void iso_sub (slice_t * s, gdouble iso) +{ + gint x,y; + + for (x = 0; x < s->nx; ++x) { + gdouble *ptr = s->data[x]; + + for (y = 0; y < s->ny; ++y, ++ptr) + *ptr -= iso; + } +} + + +/** + * gts_isosurface_tetra_bounded: + * @surface: a #GtsSurface. + * @g: a #GtsCartesianGrid. + * @f: a #GtsIsoCartesianFunc. + * @data: user data to be passed to @f. + * @iso: isosurface value. + * + * Adds to @surface new faces defining the isosurface f(x,y,z) = + * @iso. By convention, the normals to the surface are pointing toward + * the positive values of f(x,y,z) - @iso. To ensure a closed object, + * a boundary of G_MINDOUBLE is added around the domain + * + * The user function @f is called successively for each value of the z + * coordinate defined by @g. It must fill the corresponding (x,y) + * plane with the values of the function for which the isosurface is + * to be computed. + */ +void gts_isosurface_tetra_bounded (GtsSurface * surface, + GtsCartesianGrid g, + GtsIsoCartesianFunc f, + gpointer data, + gdouble iso) +{ + slice_t *slice1, *slice2, *transfer_slice; + GtsCartesianGrid g_intern = g; + helper_t *helper; + gint z; + + g_return_if_fail (surface != NULL); + g_return_if_fail (f != NULL); + g_return_if_fail (g.nx > 1); + g_return_if_fail (g.ny > 1); + g_return_if_fail (g.nz > 1); + + /* create the helper slices */ + slice1 = new_slice (g.nx + 2, g.ny + 2); + slice2 = new_slice (g.nx + 2, g.ny + 2); + + /* initialize the first slice as OUTSIDE */ + slice_init (slice1, -1.0); + + /* create a slice of the original image size */ + transfer_slice = new_slice (g.nx, g.ny); + + /* adapt the parameters to our enlarged image */ + g_intern.x -= g.dx; + g_intern.y -= g.dy; + g_intern.z -= g.dz; + g_intern.nx = g.nx + 2; + g_intern.ny = g.ny + 2; + g_intern.nz = g.nz; + + /* create the helper for vertex-lookup */ + helper = init_helper (g_intern.nx, g_intern.ny); + + /* go slicewise through the data */ + z = 0; + while (z < g.nz) { + slice_t * hs; + + /* request slice */ + f (transfer_slice->data, g, z, data); + g.z += g.dz; + + /* copy slice in enlarged image and mark the border as OUTSIDE */ + copy_to_bounded (slice2, transfer_slice, iso, -1.); + + /* triangulate */ + iso_slice_evaluate (slice1, slice2, g_intern, z, surface, helper); + + /* switch the input slices */ + hs = slice1; slice1 = slice2; slice2 = hs; + + /* switch the vertex lookup tables */ + helper_advance(helper); + ++z; + } + + /* initialize the last slice as OUTSIDE */ + slice_init (slice2, - 1.0); + + /* close the object */ + iso_slice_evaluate(slice1, slice2, g_intern, z, surface, helper); + + free_helper (helper); + free_slice (slice1); + free_slice (slice2); + free_slice (transfer_slice); +} + +/** + * gts_isosurface_tetra: + * @surface: a #GtsSurface. + * @g: a #GtsCartesianGrid. + * @f: a #GtsIsoCartesianFunc. + * @data: user data to be passed to @f. + * @iso: isosurface value. + * + * Adds to @surface new faces defining the isosurface f(x,y,z) = + * @iso. By convention, the normals to the surface are pointing toward + * the positive values of f(x,y,z) - @iso. + * + * The user function @f is called successively for each value of the z + * coordinate defined by @g. It must fill the corresponding (x,y) + * plane with the values of the function for which the isosurface is + * to be computed. + */ +void gts_isosurface_tetra (GtsSurface * surface, + GtsCartesianGrid g, + GtsIsoCartesianFunc f, + gpointer data, + gdouble iso) +{ + slice_t *slice1, *slice2; + helper_t *helper; + gint z; + GtsCartesianGrid g_internal; + + g_return_if_fail (surface != NULL); + g_return_if_fail (f != NULL); + g_return_if_fail (g.nx > 1); + g_return_if_fail (g.ny > 1); + g_return_if_fail (g.nz > 1); + + memcpy (&g_internal, &g, sizeof (GtsCartesianGrid)); + + /* create the helper slices */ + slice1 = new_slice (g.nx, g.ny); + slice2 = new_slice (g.nx, g.ny); + + /* create the helper for vertex-lookup */ + helper = init_helper (g.nx, g.ny); + + z = 0; + f (slice1->data, g, z, data); + iso_sub (slice1, iso); + + z++; + g.z += g.dz; + + /* go slicewise through the data */ + while (z < g.nz) { + slice_t * hs; + + /* request slice */ + f (slice2->data, g, z, data); + iso_sub (slice2, iso); + + g.z += g.dz; + + /* triangulate */ + iso_slice_evaluate (slice1, slice2, g_internal, z-1, surface, helper); + + /* switch the input slices */ + hs = slice1; slice1 = slice2; slice2 = hs; + + /* switch the vertex lookup tables */ + helper_advance (helper); + + ++z; + } + + free_helper(helper); + free_slice(slice1); + free_slice(slice2); +} + +/** + * gts_isosurface_tetra_bcl: + * @surface: a #GtsSurface. + * @g: a #GtsCartesianGrid. + * @f: a #GtsIsoCartesianFunc. + * @data: user data to be passed to @f. + * @iso: isosurface value. + * + * Adds to @surface new faces defining the isosurface f(x,y,z) = + * @iso. By convention, the normals to the surface are pointing toward + * the positive values of f(x,y,z) - @iso. + * + * The user function @f is called successively for each value of the z + * coordinate defined by @g. It must fill the corresponding (x,y) + * plane with the values of the function for which the isosurface is + * to be computed. + * + * This version produces the dual "body-centered" faces relative to + * the faces produced by gts_isosurface_tetra(). + */ +void gts_isosurface_tetra_bcl (GtsSurface * surface, + GtsCartesianGrid g, + GtsIsoCartesianFunc f, + gpointer data, + gdouble iso) +{ + slice_t *slice1, *slice2, *slice3; + helper_bcl *helper; + gint z; + GtsCartesianGrid g_internal; + + g_return_if_fail (surface != NULL); + g_return_if_fail (f != NULL); + g_return_if_fail (g.nx > 1); + g_return_if_fail (g.ny > 1); + g_return_if_fail (g.nz > 1); + + memcpy (&g_internal, &g, sizeof (GtsCartesianGrid)); + + /* create the helper slices */ + slice1 = new_slice (g.nx, g.ny); + slice2 = new_slice (g.nx, g.ny); + slice3 = new_slice (g.nx, g.ny); + + /* create the helper for vertex-lookup */ + helper = init_helper_bcl (); + + z = 0; + f (slice1->data, g, z, data); + iso_sub (slice1, iso); + + z++; + g.z += g.dz; + + f (slice2->data, g, z, data); + iso_sub (slice1, iso); + + z++; + g.z += g.dz; + + /* go slicewise through the data */ + while (z < g.nz) { + slice_t * hs; + + /* request slice */ + f (slice3->data, g, z, data); + iso_sub (slice3, iso); + + g.z += g.dz; + + /* triangulate */ + iso_slice_evaluate_bcl (slice1, slice2, slice3, g_internal, z-2, + surface, helper); + + /* switch the input slices */ + hs = slice1; slice1 = slice2; slice2 = slice3; slice3 = hs; + + /* switch the vertex lookup tables */ + helper_advance_bcl (helper); + + ++z; + } + + free_helper_bcl(helper); + free_slice(slice1); + free_slice(slice2); + free_slice(slice3); +} diff --git a/gts/kdtree.c b/gts/kdtree.c new file mode 100644 index 0000000000..ec5d422fdf --- /dev/null +++ b/gts/kdtree.c @@ -0,0 +1,152 @@ +/* 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 "gts.h" + + +static int compare_x (const void * p1, const void * p2) { + GtsPoint + * pp1 = *((gpointer *) p1), + * pp2 = *((gpointer *) p2); + if (pp1->x > pp2->x) + return 1; + return -1; +} + +static int compare_y (const void * p1, const void * p2) { + GtsPoint + * pp1 = *((gpointer *) p1), + * pp2 = *((gpointer *) p2); + if (pp1->y > pp2->y) + return 1; + return -1; +} + +static int compare_z (const void * p1, const void * p2) { + GtsPoint + * pp1 = *((gpointer *) p1), + * pp2 = *((gpointer *) p2); + if (pp1->z > pp2->z) + return 1; + return -1; +} + +/** + * gts_kdtree_new: + * @points: an array of #GtsPoint. + * @compare: always %NULL. + * + * Note that the order of the points in array @points is modified by this + * function. + * + * Returns: a new 3D tree for @points. + */ +GNode * gts_kdtree_new (GPtrArray * points, + int (*compare) (const void *, const void *)) +{ + guint middle; + GPtrArray array; + GNode * node; + GtsPoint * point; + + g_return_val_if_fail (points != NULL, NULL); + g_return_val_if_fail (points->len > 0, NULL); + + /* sort the points */ + if (compare == compare_x) compare = compare_y; + else if (compare == compare_y) compare = compare_z; + else compare = compare_x; + qsort (points->pdata, points->len, sizeof (gpointer), compare); + + middle = (points->len - 1)/2; + point = points->pdata[middle]; + node = g_node_new (point); + + if (points->len > 1) { + array.len = middle; + if (array.len > 0) { + array.pdata = points->pdata; + g_node_prepend (node, gts_kdtree_new (&array, compare)); + } + else + g_node_prepend (node, g_node_new (NULL)); + + array.len = points->len - middle - 1; + if (array.len > 0) { + array.pdata = &(points->pdata[middle + 1]); + g_node_prepend (node, gts_kdtree_new (&array, compare)); + } + else + g_node_prepend (node, g_node_new (NULL)); + } + + return node; +} + +/** + * gts_kdtree_range: + * @tree: a 3D tree. + * @bbox: a #GtsBBox. + * @compare: always %NULL. + * + * Returns: a list of #GtsPoint belonging to @tree which are inside @bbox. + */ +GSList * gts_kdtree_range (GNode * tree_3d, + GtsBBox * bbox, + int (*compare) (const void *, const void *)) +{ + GSList * list = NULL; + GtsPoint * p; + gdouble left, right, v; + GNode * node; + + g_return_val_if_fail (tree_3d != NULL, NULL); + g_return_val_if_fail (bbox != NULL, NULL); + + p = tree_3d->data; + if (p == NULL) + return NULL; + + if (gts_bbox_point_is_inside (bbox, p)) + list = g_slist_prepend (list, p); + + if (compare == compare_x) { + left = bbox->y1; right = bbox->y2; v = p->y; + compare = compare_y; + } + else if (compare == compare_y) { + left = bbox->z1; right = bbox->z2; v = p->z; + compare = compare_z; + } + else { + left = bbox->x1; right = bbox->x2; v = p->x; + compare = compare_x; + } + + if ((node = tree_3d->children)) { + if (right >= v) + list = g_slist_concat (list, gts_kdtree_range (node, bbox, compare)); + node = node->next; + if (left <= v) + list = g_slist_concat (list, gts_kdtree_range (node, bbox, compare)); + } + return list; +} + diff --git a/gts/matrix.c b/gts/matrix.c new file mode 100644 index 0000000000..eb0b1f8b3a --- /dev/null +++ b/gts/matrix.c @@ -0,0 +1,728 @@ +/* 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 "gts.h" + +/** + * gts_matrix_new: + * @a00: element [0][0]. + * @a01: element [0][1]. + * @a02: element [0][2]. + * @a03: element [0][3]. + * @a10: element [1][0]. + * @a11: element [1][1]. + * @a12: element [1][2]. + * @a13: element [1][3]. + * @a20: element [2][0]. + * @a21: element [2][1]. + * @a22: element [2][2]. + * @a23: element [2][3]. + * @a30: element [3][0]. + * @a31: element [3][1]. + * @a32: element [3][2]. + * @a33: element [3][3]. + * + * Allocates memory and initializes a new #GtsMatrix. + * + * Returns: a pointer to the newly created #GtsMatrix. + */ +GtsMatrix * gts_matrix_new (gdouble a00, gdouble a01, gdouble a02, gdouble a03, + gdouble a10, gdouble a11, gdouble a12, gdouble a13, + gdouble a20, gdouble a21, gdouble a22, gdouble a23, + gdouble a30, gdouble a31, gdouble a32, gdouble a33) +{ + GtsMatrix * m; + + m = g_malloc (4*sizeof (GtsVector4)); + + m[0][0] = a00; m[1][0] = a10; m[2][0] = a20; m[3][0] = a30; + m[0][1] = a01; m[1][1] = a11; m[2][1] = a21; m[3][1] = a31; + m[0][2] = a02; m[1][2] = a12; m[2][2] = a22; m[3][2] = a32; + m[0][3] = a03; m[1][3] = a13; m[2][3] = a23; m[3][3] = a33; + + return m; +} + +/** + * gts_matrix_assign: + * @m: a #GtsMatrix. + * @a00: element [0][0]. + * @a01: element [0][1]. + * @a02: element [0][2]. + * @a03: element [0][3]. + * @a10: element [1][0]. + * @a11: element [1][1]. + * @a12: element [1][2]. + * @a13: element [1][3]. + * @a20: element [2][0]. + * @a21: element [2][1]. + * @a22: element [2][2]. + * @a23: element [2][3]. + * @a30: element [3][0]. + * @a31: element [3][1]. + * @a32: element [3][2]. + * @a33: element [3][3]. + * + * Set values of matrix elements. + */ +void gts_matrix_assign (GtsMatrix * m, + gdouble a00, gdouble a01, gdouble a02, gdouble a03, + gdouble a10, gdouble a11, gdouble a12, gdouble a13, + gdouble a20, gdouble a21, gdouble a22, gdouble a23, + gdouble a30, gdouble a31, gdouble a32, gdouble a33) +{ + g_return_if_fail (m != NULL); + + m[0][0] = a00; m[1][0] = a10; m[2][0] = a20; m[3][0] = a30; + m[0][1] = a01; m[1][1] = a11; m[2][1] = a21; m[3][1] = a31; + m[0][2] = a02; m[1][2] = a12; m[2][2] = a22; m[3][2] = a32; + m[0][3] = a03; m[1][3] = a13; m[2][3] = a23; m[3][3] = a33; +} + +/** + * gts_matrix_projection: + * @t: a #GtsTriangle. + * + * Creates a new #GtsMatrix representing the projection onto a plane of normal + * given by @t. + * + * Returns: a pointer to the newly created #GtsMatrix. + */ +GtsMatrix * gts_matrix_projection (GtsTriangle * t) +{ + GtsVertex * v1, * v2, * v3; + GtsEdge * e1, * e2, * e3; + GtsMatrix * m; + gdouble x1, y1, z1, x2, y2, z2, x3, y3, z3, l; + + g_return_val_if_fail (t != NULL, NULL); + + m = g_malloc (4*sizeof (GtsVector4)); + gts_triangle_vertices_edges (t, NULL, &v1, &v2, &v3, &e1, &e2, &e3); + + x1 = GTS_POINT (v2)->x - GTS_POINT (v1)->x; + y1 = GTS_POINT (v2)->y - GTS_POINT (v1)->y; + z1 = GTS_POINT (v2)->z - GTS_POINT (v1)->z; + x2 = GTS_POINT (v3)->x - GTS_POINT (v1)->x; + y2 = GTS_POINT (v3)->y - GTS_POINT (v1)->y; + z2 = GTS_POINT (v3)->z - GTS_POINT (v1)->z; + x3 = y1*z2 - z1*y2; y3 = z1*x2 - x1*z2; z3 = x1*y2 - y1*x2; + x2 = y3*z1 - z3*y1; y2 = z3*x1 - x3*z1; z2 = x3*y1 - y3*x1; + + l = sqrt (x1*x1 + y1*y1 + z1*z1); + g_assert (l > 0.0); + m[0][0] = x1/l; m[1][0] = y1/l; m[2][0] = z1/l; m[3][0] = 0.; + l = sqrt (x2*x2 + y2*y2 + z2*z2); + g_assert (l > 0.0); + m[0][1] = x2/l; m[1][1] = y2/l; m[2][1] = z2/l; m[3][1] = 0.; + l = sqrt (x3*x3 + y3*y3 + z3*z3); + g_assert (l > 0.0); + m[0][2] = x3/l; m[1][2] = y3/l; m[2][2] = z3/l; m[3][2] = 0.; + m[0][3] = 0; m[1][3] = 0.; m[2][3] = 0.; m[3][3] = 1.; + + return m; +} + +/** + * gts_matrix_transpose: + * @m: a #GtsMatrix. + * + * Returns: a pointer to a newly created #GtsMatrix transposed of @m. + */ +GtsMatrix * gts_matrix_transpose (GtsMatrix * m) +{ + GtsMatrix * mi; + + g_return_val_if_fail (m != NULL, NULL); + + mi = g_malloc (4*sizeof (GtsVector4)); + + mi[0][0] = m[0][0]; mi[1][0] = m[0][1]; + mi[2][0] = m[0][2]; mi[3][0] = m[0][3]; + mi[0][1] = m[1][0]; mi[1][1] = m[1][1]; + mi[2][1] = m[1][2]; mi[3][1] = m[1][3]; + mi[0][2] = m[2][0]; mi[1][2] = m[2][1]; + mi[2][2] = m[2][2]; mi[3][2] = m[2][3]; + mi[0][3] = m[3][0]; mi[1][3] = m[3][1]; + mi[2][3] = m[3][2]; mi[3][3] = m[3][3]; + + return mi; +} + +/* + * calculate the determinant of a 2x2 matrix. + * + * Adapted from: + * Matrix Inversion + * by Richard Carling + * from "Graphics Gems", Academic Press, 1990 + */ +static gdouble det2x2 (gdouble a, gdouble b, gdouble c, gdouble d) +{ + gdouble ans2; + + ans2 = a*d - b*c; + return ans2; +} + +/* + * calculate the determinant of a 3x3 matrix + * in the form + * + * | a1, b1, c1 | + * | a2, b2, c2 | + * | a3, b3, c3 | + * + * Adapted from: + * Matrix Inversion + * by Richard Carling + * from "Graphics Gems", Academic Press, 1990 + */ +static gdouble det3x3 (gdouble a1, gdouble a2, gdouble a3, + gdouble b1, gdouble b2, gdouble b3, + gdouble c1, gdouble c2, gdouble c3) +{ + gdouble ans3; + + ans3 = a1 * det2x2( b2, b3, c2, c3 ) + - b1 * det2x2( a2, a3, c2, c3 ) + + c1 * det2x2( a2, a3, b2, b3 ); + return ans3; +} + +/** + * gts_matrix_determinant: + * @m: a #GtsMatrix. + * + * Returns: the value of det(@m). + */ +gdouble gts_matrix_determinant (GtsMatrix * m) +{ + gdouble ans4; + gdouble a1, a2, a3, a4, b1, b2, b3, b4, c1, c2, c3, c4, d1, d2, d3, d4; + + g_return_val_if_fail (m != NULL, 0.0); + + a1 = m[0][0]; b1 = m[0][1]; + c1 = m[0][2]; d1 = m[0][3]; + + a2 = m[1][0]; b2 = m[1][1]; + c2 = m[1][2]; d2 = m[1][3]; + + a3 = m[2][0]; b3 = m[2][1]; + c3 = m[2][2]; d3 = m[2][3]; + + a4 = m[3][0]; b4 = m[3][1]; + c4 = m[3][2]; d4 = m[3][3]; + + ans4 = a1 * det3x3 (b2, b3, b4, c2, c3, c4, d2, d3, d4) + - b1 * det3x3 (a2, a3, a4, c2, c3, c4, d2, d3, d4) + + c1 * det3x3 (a2, a3, a4, b2, b3, b4, d2, d3, d4) + - d1 * det3x3 (a2, a3, a4, b2, b3, b4, c2, c3, c4); + + return ans4; +} + +/* + * adjoint( original_matrix, inverse_matrix ) + * + * calculate the adjoint of a 4x4 matrix + * + * Let a denote the minor determinant of matrix A obtained by + * ij + * + * deleting the ith row and jth column from A. + * + * i+j + * Let b = (-1) a + * ij ji + * + * The matrix B = (b ) is the adjoint of A + * ij + */ +static GtsMatrix * adjoint (GtsMatrix * m) +{ + gdouble a1, a2, a3, a4, b1, b2, b3, b4; + gdouble c1, c2, c3, c4, d1, d2, d3, d4; + GtsMatrix * ma; + + a1 = m[0][0]; b1 = m[0][1]; + c1 = m[0][2]; d1 = m[0][3]; + + a2 = m[1][0]; b2 = m[1][1]; + c2 = m[1][2]; d2 = m[1][3]; + + a3 = m[2][0]; b3 = m[2][1]; + c3 = m[2][2]; d3 = m[2][3]; + + a4 = m[3][0]; b4 = m[3][1]; + c4 = m[3][2]; d4 = m[3][3]; + + ma = g_malloc (4*sizeof (GtsVector4)); + + /* row column labeling reversed since we transpose rows & columns */ + + ma[0][0] = det3x3 (b2, b3, b4, c2, c3, c4, d2, d3, d4); + ma[1][0] = - det3x3 (a2, a3, a4, c2, c3, c4, d2, d3, d4); + ma[2][0] = det3x3 (a2, a3, a4, b2, b3, b4, d2, d3, d4); + ma[3][0] = - det3x3 (a2, a3, a4, b2, b3, b4, c2, c3, c4); + + ma[0][1] = - det3x3 (b1, b3, b4, c1, c3, c4, d1, d3, d4); + ma[1][1] = det3x3 (a1, a3, a4, c1, c3, c4, d1, d3, d4); + ma[2][1] = - det3x3 (a1, a3, a4, b1, b3, b4, d1, d3, d4); + ma[3][1] = det3x3 (a1, a3, a4, b1, b3, b4, c1, c3, c4); + + ma[0][2] = det3x3 (b1, b2, b4, c1, c2, c4, d1, d2, d4); + ma[1][2] = - det3x3 (a1, a2, a4, c1, c2, c4, d1, d2, d4); + ma[2][2] = det3x3 (a1, a2, a4, b1, b2, b4, d1, d2, d4); + ma[3][2] = - det3x3 (a1, a2, a4, b1, b2, b4, c1, c2, c4); + + ma[0][3] = - det3x3 (b1, b2, b3, c1, c2, c3, d1, d2, d3); + ma[1][3] = det3x3 (a1, a2, a3, c1, c2, c3, d1, d2, d3); + ma[2][3] = - det3x3 (a1, a2, a3, b1, b2, b3, d1, d2, d3); + ma[3][3] = det3x3 (a1, a2, a3, b1, b2, b3, c1, c2, c3); + + return ma; +} + + +/** + * gts_matrix_inverse: + * @m: a #GtsMatrix. + * + * Returns: a pointer to a newly created #GtsMatrix inverse of @m or %NULL + * if @m is not invertible. + */ +GtsMatrix * gts_matrix_inverse (GtsMatrix * m) +{ + GtsMatrix * madj; + gdouble det; + gint i, j; + + g_return_val_if_fail (m != NULL, NULL); + + det = gts_matrix_determinant (m); + if (det == 0.) + return NULL; + + madj = adjoint (m); + for (i = 0; i < 4; i++) + for(j = 0; j < 4; j++) + madj[i][j] /= det; + + return madj; +} + +/** + * gts_matrix3_inverse: + * @m: a 3x3 #GtsMatrix. + * + * Returns: a pointer to a newly created 3x3 #GtsMatrix inverse of @m or %NULL + * if @m is not invertible. + */ +GtsMatrix * gts_matrix3_inverse (GtsMatrix * m) +{ + GtsMatrix * mi; + gdouble det; + + g_return_val_if_fail (m != NULL, NULL); + + det = (m[0][0]*(m[1][1]*m[2][2] - m[2][1]*m[1][2]) - + m[0][1]*(m[1][0]*m[2][2] - m[2][0]*m[1][2]) + + m[0][2]*(m[1][0]*m[2][1] - m[2][0]*m[1][1])); + if (det == 0.0) + return NULL; + + mi = g_malloc0 (4*sizeof (GtsVector)); + + mi[0][0] = (m[1][1]*m[2][2] - m[1][2]*m[2][1])/det; + mi[0][1] = (m[2][1]*m[0][2] - m[0][1]*m[2][2])/det; + mi[0][2] = (m[0][1]*m[1][2] - m[1][1]*m[0][2])/det; + mi[1][0] = (m[1][2]*m[2][0] - m[1][0]*m[2][2])/det; + mi[1][1] = (m[0][0]*m[2][2] - m[2][0]*m[0][2])/det; + mi[1][2] = (m[1][0]*m[0][2] - m[0][0]*m[1][2])/det; + mi[2][0] = (m[1][0]*m[2][1] - m[2][0]*m[1][1])/det; + mi[2][1] = (m[2][0]*m[0][1] - m[0][0]*m[2][1])/det; + mi[2][2] = (m[0][0]*m[1][1] - m[0][1]*m[1][0])/det; + + return mi; +} + +/** + * gts_matrix_print: + * @m: a #GtsMatrix. + * @fptr: a file descriptor. + * + * Print @m to file @fptr. + */ +void gts_matrix_print (GtsMatrix * m, FILE * fptr) +{ + g_return_if_fail (m != NULL); + g_return_if_fail (fptr != NULL); + + fprintf (fptr, + "[[%15.7g %15.7g %15.7g %15.7g]\n" + " [%15.7g %15.7g %15.7g %15.7g]\n" + " [%15.7g %15.7g %15.7g %15.7g]\n" + " [%15.7g %15.7g %15.7g %15.7g]]\n", + m[0][0], m[0][1], m[0][2], m[0][3], + m[1][0], m[1][1], m[1][2], m[1][3], + m[2][0], m[2][1], m[2][2], m[2][3], + m[3][0], m[3][1], m[3][2], m[3][3]); +} + +/** + * gts_vector_print: + * @v: a #GtsVector. + * @fptr: a file descriptor. + * + * Print @s to file @fptr. + */ +void gts_vector_print (GtsVector v, FILE * fptr) +{ + g_return_if_fail (fptr != NULL); + + fprintf (fptr, + "[%15.7g %15.7g %15.7g ]\n", + v[0], v[1], v[2]); +} + +/** + * gts_vector4_print: + * @v: a #GtsVector4. + * @fptr: a file descriptor. + * + * Print @v to file @fptr. + */ +void gts_vector4_print (GtsVector4 v, FILE * fptr) +{ + g_return_if_fail (fptr != NULL); + + fprintf (fptr, + "[%15.7g %15.7g %15.7g %15.7g]\n", + v[0], v[1], v[2], v[3]); +} + +/* [cos(alpha)]^2 */ +#define COSALPHA2 0.999695413509 /* alpha = 1 degree */ +/* [sin(alpha)]^2 */ +#define SINALPHA2 3.04586490453e-4 /* alpha = 1 degree */ + +/** + * gts_matrix_compatible_row: + * @A: a #GtsMatrix. + * @b: a #GtsVector. + * @n: the number of previous constraints of @A.x=@b. + * @A1: a #GtsMatrix. + * @b1: a #GtsVector. + * + * Given a system of @n constraints @A.x=@b adds to it the compatible + * constraints defined by @A1.x=@b1. The compatibility is determined + * by insuring that the resulting system is well-conditioned (see + * Lindstrom and Turk (1998, 1999)). + * + * Returns: the number of constraints of the resulting system. + */ +guint gts_matrix_compatible_row (GtsMatrix * A, + GtsVector b, + guint n, + GtsVector A1, + gdouble b1) +{ + gdouble na1; + + g_return_val_if_fail (A != NULL, 0); + + na1 = gts_vector_scalar (A1, A1); + if (na1 == 0.0) + return n; + + /* normalize row */ + na1 = sqrt (na1); + A1[0] /= na1; A1[1] /= na1; A1[2] /= na1; b1 /= na1; + + if (n == 1) { + gdouble a0a1 = gts_vector_scalar (A[0], A1); + if (a0a1*a0a1 >= COSALPHA2) + return 1; + } + else if (n == 2) { + GtsVector V; + gdouble s; + + gts_vector_cross (V, A[0], A[1]); + s = gts_vector_scalar (V, A1); + if (s*s <= gts_vector_scalar (V, V)*SINALPHA2) + return 2; + } + + A[n][0] = A1[0]; A[n][1] = A1[1]; A[n][2] = A1[2]; b[n] = b1; + return n + 1; +} + +/** + * gts_matrix_quadratic_optimization: + * @A: a #GtsMatrix. + * @b: a #GtsVector. + * @n: the number of constraints (must be smaller than 3). + * @H: a symmetric positive definite Hessian. + * @c: a #GtsVector. + * + * Solve a quadratic optimization problem: Given a quadratic objective function + * f which can be written as: f(x) = x^t.@H.x + @c^t.x + k, where @H is the + * symmetric positive definite Hessian of f and k is a constant, find the + * minimum of f subject to the set of @n prior linear constraints, defined by + * the first @n rows of @A and @b (@A.x = @b). The new constraints given by + * the minimization are added to @A and @b only if they are linearly + * independent as determined by gts_matrix_compatible_row(). + * + * Returns: the new number of constraints defined by @A and @b. + */ +guint gts_matrix_quadratic_optimization (GtsMatrix * A, + GtsVector b, + guint n, + GtsMatrix * H, + GtsVector c) +{ + g_return_val_if_fail (A != NULL, 0); + g_return_val_if_fail (b != NULL, 0); + g_return_val_if_fail (n < 3, 0); + g_return_val_if_fail (H != NULL, 0); + + switch (n) { + case 0: { + n = gts_matrix_compatible_row (A, b, n, H[0], - c[0]); + n = gts_matrix_compatible_row (A, b, n, H[1], - c[1]); + n = gts_matrix_compatible_row (A, b, n, H[2], - c[2]); + return n; + } + case 1: { + GtsVector Q0 = {0., 0., 0.}; + GtsVector Q1 = {0., 0., 0.}; + GtsVector A1; + gdouble max = A[0][0]*A[0][0]; + guint d = 0; + + /* build a vector orthogonal to the constraint */ + if (A[0][1]*A[0][1] > max) { max = A[0][1]*A[0][1]; d = 1; } + if (A[0][2]*A[0][2] > max) { max = A[0][2]*A[0][2]; d = 2; } + switch (d) { + case 0: Q0[0] = - A[0][2]/A[0][0]; Q0[2] = 1.0; break; + case 1: Q0[1] = - A[0][2]/A[0][1]; Q0[2] = 1.0; break; + case 2: Q0[2] = - A[0][0]/A[0][2]; Q0[0] = 1.0; break; + } + + /* build a second vector orthogonal to the first and to the constraint */ + gts_vector_cross (Q1, A[0], Q0); + + A1[0] = gts_vector_scalar (Q0, H[0]); + A1[1] = gts_vector_scalar (Q0, H[1]); + A1[2] = gts_vector_scalar (Q0, H[2]); + + n = gts_matrix_compatible_row (A, b, n, A1, - gts_vector_scalar (Q0, c)); + + A1[0] = gts_vector_scalar (Q1, H[0]); + A1[1] = gts_vector_scalar (Q1, H[1]); + A1[2] = gts_vector_scalar (Q1, H[2]); + + n = gts_matrix_compatible_row (A, b, n, A1, - gts_vector_scalar (Q1, c)); + + return n; + } + case 2: { + /* build a vector orthogonal to the two constraints */ + GtsVector A1, Q; + + gts_vector_cross (Q, A[0], A[1]); + A1[0] = gts_vector_scalar (Q, H[0]); + A1[1] = gts_vector_scalar (Q, H[1]); + A1[2] = gts_vector_scalar (Q, H[2]); + + n = gts_matrix_compatible_row (A, b, n, A1, - gts_vector_scalar (Q, c)); + + return n; + } + default: + g_assert_not_reached (); + } + return 0; +} + +/** + * gts_matrix_destroy: + * @m: a #GtsMatrix. + * + * Free all the memory allocated for @m. + */ +void gts_matrix_destroy (GtsMatrix * m) +{ + g_free (m); +} + +/** + * gts_matrix_product: + * @m1: a #GtsMatrix. + * @m2: another #GtsMatrix. + * + * Returns: a new #GtsMatrix, product of @m1 and @m2. + */ +GtsMatrix * gts_matrix_product (GtsMatrix * m1, GtsMatrix * m2) +{ + guint i, j; + GtsMatrix * m; + + g_return_val_if_fail (m1 != NULL, NULL); + g_return_val_if_fail (m2 != NULL, NULL); + g_return_val_if_fail (m1 != m2, NULL); + + m = g_malloc (4*sizeof (GtsVector4)); + + for (i = 0; i < 4; i++) + for (j = 0; j < 4; j++) + m[i][j] = m1[i][0]*m2[0][j] + m1[i][1]*m2[1][j] + + m1[i][2]*m2[2][j] + m1[i][3]*m2[3][j]; + return m; +} + +/** + * gts_matrix_zero: + * @m: a #GtsMatrix or $NULL. + * + * Initializes @m to zeros. Allocates a matrix if @m is %NULL. + * + * Returns: the zero'ed matrix. + */ +GtsMatrix * gts_matrix_zero (GtsMatrix * m) +{ + if (m == NULL) + m = g_malloc0 (4*sizeof (GtsVector4)); + else { + m[0][0] = m[1][0] = m[2][0] = m[3][0] = 0.; + m[0][1] = m[1][1] = m[2][1] = m[3][1] = 0.; + m[0][2] = m[1][2] = m[2][2] = m[3][2] = 0.; + m[0][3] = m[1][3] = m[2][3] = m[3][3] = 0.; + } + return m; +} + +/** + * gts_matrix_identity: + * @m: a #GtsMatrix or %NULL. + * + * Initializes @m to an identity matrix. Allocates a matrix if @m is %NULL. + * + * Returns: the identity matrix. + */ +GtsMatrix * gts_matrix_identity (GtsMatrix * m) +{ + m = gts_matrix_zero (m); + m[0][0] = m[1][1] = m[2][2] = m[3][3] = 1.; + return m; +} + +/** + * gts_matrix_scale: + * @m: a #GtsMatrix or %NULL. + * @s: the scaling vector. + * + * Initializes @m to a scaling matrix for @s. Allocates a matrix if @m + * is %NULL. + * + * Returns: the scaling matrix. + */ +GtsMatrix * gts_matrix_scale (GtsMatrix * m, GtsVector s) +{ + m = gts_matrix_zero (m); + m[0][0] = s[0]; + m[1][1] = s[1]; + m[2][2] = s[2]; + m[3][3] = 1.; + return m; +} + +/** + * gts_matrix_translate: + * @m: a #GtsMatrix or %NULL. + * @t: the translation vector. + * + * Initializes @m to a translation matrix for @t. Allocates a new + * matrix if @m is %NULL. + * + * Returns: the translation matix. + */ +GtsMatrix * gts_matrix_translate (GtsMatrix * m, GtsVector t) +{ + m = gts_matrix_zero (m); + m[0][3] = t[0]; + m[1][3] = t[1]; + m[2][3] = t[2]; + m[3][3] = 1.; + m[0][0] = m[1][1] = m[2][2] = 1.; + return m; +} + +/** + * gts_matrix_rotate: + * @m: a #GtsMatrix or %NULL. + * @r: the rotation axis. + * @angle: the angle (in radians) to rotate by. + * + * Initializes @m to a rotation matrix around @r by @angle. + * Allocates a new matrix if @m is %NULL. + * + * Returns: the rotation matrix. + */ +GtsMatrix * gts_matrix_rotate (GtsMatrix * m, + GtsVector r, + gdouble angle) +{ + gdouble c, c1, s; + + gts_vector_normalize (r); + + c = cos (angle); + c1 = 1. - c; + s = sin (angle); + + if (m == NULL) + m = g_malloc (4*sizeof (GtsVector4)); + + m[0][0] = r[0]*r[0]*c1 + c; + m[0][1] = r[0]*r[1]*c1 - r[2]*s; + m[0][2] = r[0]*r[2]*c1 + r[1]*s; + m[0][3] = 0.; + + m[1][0] = r[1]*r[0]*c1 + r[2]*s; + m[1][1] = r[1]*r[1]*c1 + c; + m[1][2] = r[1]*r[2]*c1 - r[0]*s; + m[1][3] = 0.; + + m[2][0] = r[2]*r[0]*c1 - r[1]*s; + m[2][1] = r[2]*r[1]*c1 + r[0]*s; + m[2][2] = r[2]*r[2]*c1 + c; + m[2][3] = 0.; + + m[3][0] = 0.; + m[3][1] = 0.; + m[3][2] = 0.; + m[3][3] = 1.; + + return m; +} diff --git a/gts/misc.c b/gts/misc.c new file mode 100644 index 0000000000..393ba06bbe --- /dev/null +++ b/gts/misc.c @@ -0,0 +1,692 @@ +/* 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" +#include "gts-private.h" +#include "config.h" + +const guint gts_major_version = GTS_MAJOR_VERSION; +const guint gts_minor_version = GTS_MINOR_VERSION; +const guint gts_micro_version = GTS_MICRO_VERSION; +const guint gts_interface_age = 1; +const guint gts_binary_age = 1; + +static gboolean char_in_string (char c, const char * s) +{ + while (*s != '\0') + if (*(s++) == c) + return TRUE; + return FALSE; +} + +static GtsFile * file_new (void) +{ + GtsFile * f; + + f = g_malloc (sizeof (GtsFile)); + f->fp = NULL; + f->s = f->s1 = NULL; + f->curline = 1; + f->curpos = 1; + f->token = g_string_new (""); + f->type = '\0'; + f->error = NULL; + f->next_token = '\0'; + + f->scope = f->scope_max = 0; + f->delimiters = g_strdup (" \t"); + f->comments = g_strdup (GTS_COMMENTS); + f->tokens = g_strdup ("\n{}()="); + + return f; +} + +/** + * gts_file_new: + * @fp: a file pointer. + * + * Returns: a new #GtsFile. + */ +GtsFile * gts_file_new (FILE * fp) +{ + GtsFile * f; + + g_return_val_if_fail (fp != NULL, NULL); + + f = file_new (); + f->fp = fp; + gts_file_next_token (f); + + return f; +} + +/** + * gts_file_new_from_string: + * @s: a string. + * + * Returns: a new #GtsFile. + */ +GtsFile * gts_file_new_from_string (const gchar * s) +{ + GtsFile * f; + + g_return_val_if_fail (s != NULL, NULL); + + f = file_new (); + f->s1 = f->s = g_strdup (s); + gts_file_next_token (f); + + return f; +} + +/** + * gts_file_destroy: + * @f: a #GtsFile. + * + * Frees all the memory allocated for @f. + */ +void gts_file_destroy (GtsFile * f) +{ + g_return_if_fail (f != NULL); + + g_free (f->delimiters); + g_free (f->comments); + g_free (f->tokens); + if (f->error) + g_free (f->error); + if (f->s1) + g_free (f->s1); + g_string_free (f->token, TRUE); + g_free (f); +} + +/** + * gts_file_verror: + * @f: a @GtsFile. + * @format: the standard sprintf() format string. + * @args: the list of parameters to insert into the format string. + * + * Sets the @error field of @f using g_strdup_vprintf(). + * + * This function can be called only once and disables any other + * operation on @f (gts_file_close() excepted). + */ +void gts_file_verror (GtsFile * f, + const gchar * format, + va_list args) +{ + g_return_if_fail (f != NULL); + g_return_if_fail (format != NULL); + + g_assert (f->type != GTS_ERROR); + f->error = g_strdup_vprintf (format, args); + f->type = GTS_ERROR; +} + +/** + * gts_file_error: + * @f: a @GtsFile. + * @format: the standard sprintf() format string. + * @...: the parameters to insert into the format string. + * + * Sets the @error field of @f using gts_file_verror(). + * + * This function can be called only once and disables any other + * operation on @f (gts_file_close() excepted). + */ +void gts_file_error (GtsFile * f, + const gchar * format, + ...) +{ + va_list args; + + g_return_if_fail (f != NULL); + g_return_if_fail (format != NULL); + + va_start (args, format); + gts_file_verror (f, format, args); + va_end (args); +} + +static gint next_char (GtsFile * f) +{ + if (f->fp) + return fgetc (f->fp); + else if (*f->s == '\0') + return EOF; + return *(f->s++); +} + +/** + * gts_file_getc : + * @f: a #GtsFile. + * + * Returns: the next character in @f or EOF if the end of the file is + * reached or if an error occured. + */ +gint gts_file_getc (GtsFile * f) +{ + gint c; + + g_return_val_if_fail (f != NULL, EOF); + + if (f->type == GTS_ERROR) + return EOF; + + c = next_char (f); + f->curpos++; + while (char_in_string (c, f->comments)) { + while (c != EOF && c != '\n') + c = next_char (f); + if (c == '\n') { + f->curline++; + f->curpos = 1; + c = next_char (f); + } + } + switch (c) { + case '\n': + f->curline++; + f->curpos = 1; + break; + case '{': + f->scope++; + break; + case '}': + if (f->scope == 0) { + f->line = f->curline; + f->pos = f->curpos - 1; + gts_file_error (f, "no matching opening brace"); + c = EOF; + } + else + f->scope--; + } + return c; +} + +/** + * gts_file_read: + * @f: a #GtsFile. + * @ptr: a pointer. + * @size: size of an element. + * @nmemb: number of elements. + * + * Reads @nmemb elements of data, each @size bytes long, from @f, + * storing them at the location given by @ptr. + * + * Returns: the number of elements read. + */ +guint gts_file_read (GtsFile * f, gpointer ptr, guint size, guint nmemb) +{ + guint i, n; + gchar * p; + + g_return_val_if_fail (f != NULL, 0); + g_return_val_if_fail (ptr != NULL, 0); + g_return_val_if_fail (f->fp != NULL, 0); + + if (f->type == GTS_ERROR) + return 0; + + n = fread (ptr, size, nmemb, f->fp); + for (i = 0, p = ptr; i < n*size; i++, p++) { + f->curpos++; + if (*p == '\n') { + f->curline++; + f->curpos = 1; + } + } + return n; +} + +/** + * gts_file_getc_scope : + * @f: a #GtsFile. + * + * Returns: the next character in @f in the scope defined by + * @f->scope_max or EOF if the end of the file is reached or if an + * error occured. + */ +gint gts_file_getc_scope (GtsFile * f) +{ + gint c; + + g_return_val_if_fail (f != NULL, EOF); + + if (f->type == GTS_ERROR) + return EOF; + + if (f->scope <= f->scope_max) + c = gts_file_getc (f); + else { + c = gts_file_getc (f); + while (c != EOF && f->scope > f->scope_max) + c = gts_file_getc (f); + } + return c; +} + +/** + * gts_file_next_token: + * @f: a #GtsFile. + * + * Reads next token from @f and updates its @token and @delim fields. + */ +void gts_file_next_token (GtsFile * f) +{ + gint c; + gboolean in_string = FALSE; + + g_return_if_fail (f != NULL); + + if (f->type == GTS_ERROR) + return; + f->token->str[0] = '\0'; + f->token->len = 0; + if (f->next_token != '\0') { + if (char_in_string (f->next_token, f->tokens)) { + f->line = f->curline; + f->pos = f->curpos - 1; + g_string_append_c (f->token, f->next_token); + f->type = f->next_token; + f->next_token = '\0'; + return; + } + else { + c = f->next_token; + f->next_token = '\0'; + } + } + else + c = gts_file_getc_scope (f); + f->type = GTS_NONE; + while (c != EOF && (!in_string || !char_in_string (c, f->delimiters))) { + if (in_string) { + if (char_in_string (c, f->tokens)) { + f->next_token = c; + break; + } + g_string_append_c (f->token, c); + } + else if (!char_in_string (c, f->delimiters)) { + in_string = TRUE; + f->line = f->curline; + f->pos = f->curpos - 1; + g_string_append_c (f->token, c); + if (char_in_string (c, f->tokens)) { + f->type = c; + break; + } + } + c = gts_file_getc_scope (f); + } + if (f->type == GTS_NONE && f->token->len > 0) { + gchar * a; + + a = f->token->str; + while (*a != '\0' && char_in_string (*a, "+-")) a++; + if (*a == '\0') { + f->type = GTS_STRING; + return; + } + a = f->token->str; + while (*a != '\0' && char_in_string (*a, "+-0123456789")) a++; + if (*a == '\0') { + f->type = GTS_INT; + return; + } + a = f->token->str; + while (*a != '\0' && char_in_string (*a, "+-eE.")) a++; + if (*a == '\0') { + f->type = GTS_STRING; + return; + } + a = f->token->str; + while (*a != '\0' && char_in_string (*a, "+-0123456789eE.")) a++; + if (*a == '\0') { + f->type = GTS_FLOAT; + return; + } + a = f->token->str; + if (!strncmp (a, "0x", 2) || + !strncmp (a, "-0x", 3) || + !strncmp (a, "+0x", 3)) { + while (*a != '\0' && char_in_string (*a, "+-0123456789abcdefx")) a++; + if (*a == '\0') { + f->type = GTS_INT; + return; + } + a = f->token->str; + while (*a != '\0' && char_in_string (*a, "+-0123456789abcdefx.p")) a++; + if (*a == '\0') { + f->type = GTS_FLOAT; + return; + } + } + f->type = GTS_STRING; + } +} + +/** + * gts_file_first_token_after: + * @f: a #GtsFile. + * @type: a #GtsTokenType. + * + * Finds and sets the first token of a type different from @type + * occuring after a token of type @type. + */ +void gts_file_first_token_after (GtsFile * f, GtsTokenType type) +{ + g_return_if_fail (f != NULL); + + while (f->type != GTS_ERROR && + f->type != GTS_NONE && + f->type != type) + gts_file_next_token (f); + while (f->type == type) + gts_file_next_token (f); +} + +/** + * gts_file_assign_start: + * @f: a #GtsFile. + * @vars: a %GTS_NONE terminated array of #GtsFileVariable. + * + * Opens a block delimited by braces to read a list of optional + * arguments specified by @vars. + * + * If an error is encountered the @error field of @f is set. + */ +void gts_file_assign_start (GtsFile * f, GtsFileVariable * vars) +{ + GtsFileVariable * var; + + g_return_if_fail (f != NULL); + g_return_if_fail (vars != NULL); + + var = vars; + while (var->type != GTS_NONE) + (var++)->set = FALSE; + + if (f->type != '{') { + gts_file_error (f, "expecting an opening brace"); + return; + } + + f->scope_max++; + gts_file_next_token (f); +} + +/** + * gts_file_assign_next: + * @f: a #GtsFile. + * @vars: a %GTS_NONE terminated array of #GtsFileVariable. + * + * Assigns the next optional argument of @vars read from @f. + * + * Returns: the variable of @vars which has been assigned or %NULL if + * no variable has been assigned (if an error has been encountered the + * @error field of @f is set). + */ +GtsFileVariable * gts_file_assign_next (GtsFile * f, GtsFileVariable * vars) +{ + GtsFileVariable * var; + gboolean found = FALSE; + + g_return_val_if_fail (f != NULL, NULL); + g_return_val_if_fail (vars != NULL, NULL); + + while (f->type == '\n') + gts_file_next_token (f); + if (f->type == '}') { + f->scope_max--; + gts_file_next_token (f); + return NULL; + } + if (f->type == GTS_ERROR) + return NULL; + + var = vars; + while (f->type != GTS_ERROR && var->type != GTS_NONE && !found) { + if (!strcmp (var->name, f->token->str)) { + found = TRUE; + if (var->unique && var->set) + gts_file_error (f, "variable `%s' was already set at line %d:%d", + var->name, var->line, var->pos); + else { + var->line = f->line; + var->pos = f->pos; + gts_file_next_token (f); + if (f->type != '=') + gts_file_error (f, "expecting `='"); + else { + var->set = TRUE; + switch (var->type) { + case GTS_FILE: + break; + case GTS_INT: + gts_file_next_token (f); + if (f->type != GTS_INT) { + gts_file_error (f, "expecting an integer"); + var->set = FALSE; + } + else if (var->data) + *((gint *) var->data) = atoi (f->token->str); + break; + case GTS_UINT: + gts_file_next_token (f); + if (f->type != GTS_INT) { + gts_file_error (f, "expecting an integer"); + var->set = FALSE; + } + else if (var->data) + *((guint *) var->data) = atoi (f->token->str); + break; + case GTS_FLOAT: + gts_file_next_token (f); + if (f->type != GTS_INT && f->type != GTS_FLOAT) { + gts_file_error (f, "expecting a number"); + var->set = FALSE; + } + else if (var->data) + *((gfloat *) var->data) = atof (f->token->str); + break; + case GTS_DOUBLE: + gts_file_next_token (f); + if (f->type != GTS_INT && f->type != GTS_FLOAT) { + gts_file_error (f, "expecting a number"); + var->set = FALSE; + } + else if (var->data) + *((gdouble *) var->data) = atof (f->token->str); + break; + case GTS_STRING: + gts_file_next_token (f); + if (f->type != GTS_INT && + f->type != GTS_FLOAT && + f->type != GTS_STRING) { + gts_file_error (f, "expecting a string"); + var->set = FALSE; + } + else if (var->data) + *((gchar **) var->data) = g_strdup (f->token->str); + break; + default: + g_assert_not_reached (); + } + } + } + } + else + var++; + } + if (!found) + gts_file_error (f, "unknown identifier `%s'", f->token->str); + else if (f->type != GTS_ERROR) { + g_assert (var->set); + gts_file_next_token (f); + return var; + } + return NULL; +} + +/** + * gts_file_assign_variables: + * @f: a #GtsFile. + * @vars: an array of #GtsFileVariable. + * + * Assigns all the variables belonging to @vars found in @f. + * + * If an error is encountered the @error field of @f is set. + */ +void gts_file_assign_variables (GtsFile * f, GtsFileVariable * vars) +{ + g_return_if_fail (f != NULL); + g_return_if_fail (vars != NULL); + + gts_file_assign_start (f, vars); + while (gts_file_assign_next (f, vars)) + ; +} + +/** + * gts_file_variable_error: + * @f: a #GtsFile. + * @vars: an array of #GtsFileVariable. + * @name: the name of a variable in @vars. + * @format: the standard sprintf() format string. + * @...: the parameters to insert into the format string. + * + * Sets the @error field of @f using gts_file_verror(). + * + * String @name must match one of the variable names in @vars. + * + * If variable @name has been assigned (using gts_file_assign_variables()) + * sets the @line and @pos fields of @f to the line and position where + * it has been assigned. + */ +void gts_file_variable_error (GtsFile * f, + GtsFileVariable * vars, + const gchar * name, + const gchar * format, + ...) +{ + va_list args; + GtsFileVariable * var; + + g_return_if_fail (f != NULL); + g_return_if_fail (vars != NULL); + g_return_if_fail (name != NULL); + g_return_if_fail (format != NULL); + + var = vars; + while (var->type != GTS_NONE && strcmp (var->name, name)) + var++; + + g_return_if_fail (var->type != GTS_NONE); /* @name not found in @vars */ + + if (var->set) { + f->line = var->line; + f->pos = var->pos; + } + + va_start (args, format); + gts_file_verror (f, format, args); + va_end (args); +} + +#ifdef DEBUG_FUNCTIONS +static GHashTable * ids = NULL; +static guint next_id = 1; + +guint id (gpointer p) +{ + g_return_val_if_fail (p != NULL, 0); + g_return_val_if_fail (ids != NULL, 0); + g_assert (g_hash_table_lookup (ids, p)); + return GPOINTER_TO_UINT (g_hash_table_lookup (ids, p)); +} + +void id_insert (gpointer p) +{ + g_return_if_fail (p != NULL); + if (ids == NULL) ids = g_hash_table_new (NULL, NULL); + g_assert (g_hash_table_lookup (ids, p) == NULL); + g_hash_table_insert (ids, p, GUINT_TO_POINTER (next_id++)); +} + +void id_remove (gpointer p) +{ + g_assert (g_hash_table_lookup (ids, p)); + g_hash_table_remove (ids, p); +} + +void gts_write_triangle (GtsTriangle * t, + GtsPoint * o, + FILE * fptr) +{ + gdouble xo = o ? o->x : 0.0; + gdouble yo = o ? o->y : 0.0; + gdouble zo = o ? o->z : 0.0; + + g_return_if_fail (t != NULL && fptr != NULL); + + fprintf (fptr, "(hdefine geometry \"t%d\" { =\n", id (t)); + fprintf (fptr, "OFF 3 1 0\n" + "%g %g %g\n%g %g %g\n%g %g %g\n3 0 1 2\n})\n" + "(geometry \"t%d\" { : \"t%d\"})\n" + "(normalization \"t%d\" none)\n", + GTS_POINT (GTS_SEGMENT (t->e1)->v1)->x - xo, + GTS_POINT (GTS_SEGMENT (t->e1)->v1)->y - yo, + GTS_POINT (GTS_SEGMENT (t->e1)->v1)->z - zo, + GTS_POINT (GTS_SEGMENT (t->e1)->v2)->x - xo, + GTS_POINT (GTS_SEGMENT (t->e1)->v2)->y - yo, + GTS_POINT (GTS_SEGMENT (t->e1)->v2)->z - zo, + GTS_POINT (gts_triangle_vertex (t))->x - xo, + GTS_POINT (gts_triangle_vertex (t))->y - yo, + GTS_POINT (gts_triangle_vertex (t))->z - zo, + id (t), id (t), id (t)); +} + +void gts_write_segment (GtsSegment * s, + GtsPoint * o, + FILE * fptr) +{ + gdouble xo = o ? o->x : 0.0; + gdouble yo = o ? o->y : 0.0; + gdouble zo = o ? o->z : 0.0; + + g_return_if_fail (s != NULL && fptr != NULL); + + fprintf (fptr, "(geometry \"s%d\" { =\n", id (s)); + fprintf (fptr, "VECT 1 2 0 2 0 %g %g %g %g %g %g })\n" + "(normalization \"s%d\" none)\n", + GTS_POINT (s->v1)->x - xo, + GTS_POINT (s->v1)->y - yo, + GTS_POINT (s->v1)->z - zo, + GTS_POINT (s->v2)->x - xo, + GTS_POINT (s->v2)->y - yo, + GTS_POINT (s->v2)->z - zo, + id (s)); +} +#endif /* DEBUG_FUNCTIONS */ diff --git a/gts/named.c b/gts/named.c new file mode 100644 index 0000000000..379f9f61b0 --- /dev/null +++ b/gts/named.c @@ -0,0 +1,188 @@ +/* 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 "gts.h" + +static void nvertex_read (GtsObject ** po, GtsFile * fp) +{ + if ((*po)->klass->parent_class->read) + (* (*po)->klass->parent_class->read) (po, fp); + + if (fp->type != '\n' && fp->type != GTS_ERROR) { + strncpy (GTS_NVERTEX (*po)->name, fp->token->str, GTS_NAME_LENGTH); + gts_file_next_token (fp); + } +} + +static void nvertex_write (GtsObject * o, FILE * fptr) +{ + GtsNVertex * nv = GTS_NVERTEX (o); + + (* o->klass->parent_class->write) (o, fptr); + if (nv->name[0] != '\0') + fprintf (fptr, " %s", nv->name); +} + +static void nvertex_class_init (GtsNVertexClass * klass) +{ + GTS_OBJECT_CLASS (klass)->read = nvertex_read; + GTS_OBJECT_CLASS (klass)->write = nvertex_write; +} + +static void nvertex_init (GtsNVertex * nvertex) +{ + nvertex->name[0] = '\0'; +} + +/** + * gts_nvertex_class: + * + * Returns: the #GtsNVertexClass. + */ +GtsNVertexClass * gts_nvertex_class (void) +{ + static GtsNVertexClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo nvertex_info = { + "GtsNVertex", + sizeof (GtsNVertex), + sizeof (GtsNVertexClass), + (GtsObjectClassInitFunc) nvertex_class_init, + (GtsObjectInitFunc) nvertex_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_vertex_class ()), + &nvertex_info); + } + + return klass; +} + +static void nedge_read (GtsObject ** po, GtsFile * fp) +{ + if (fp->type != GTS_STRING) { + gts_file_error (fp, "expecting a string (name)"); + return; + } + strncpy (GTS_NEDGE (*po)->name, fp->token->str, GTS_NAME_LENGTH); + gts_file_next_token (fp); +} + +static void nedge_write (GtsObject * o, FILE * fptr) +{ + GtsNEdge * ne = GTS_NEDGE (o); + + if (ne->name[0] != '\0') + fprintf (fptr, " %s", ne->name); +} + +static void nedge_class_init (GtsNEdgeClass * klass) +{ + GTS_OBJECT_CLASS (klass)->read = nedge_read; + GTS_OBJECT_CLASS (klass)->write = nedge_write; +} + +static void nedge_init (GtsNEdge * nedge) +{ + nedge->name[0] = '\0'; +} + +/** + * gts_nedge_class: + * + * Returns: the #GtsNEdgeClass. + */ +GtsNEdgeClass * gts_nedge_class (void) +{ + static GtsNEdgeClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo nedge_info = { + "GtsNEdge", + sizeof (GtsNEdge), + sizeof (GtsNEdgeClass), + (GtsObjectClassInitFunc) nedge_class_init, + (GtsObjectInitFunc) nedge_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_edge_class ()), + &nedge_info); + } + + return klass; +} + +static void nface_read (GtsObject ** po, GtsFile * fp) +{ + if (fp->type != GTS_STRING) { + gts_file_error (fp, "expecting a string (name)"); + return; + } + strncpy (GTS_NFACE (*po)->name, fp->token->str, GTS_NAME_LENGTH); + gts_file_next_token (fp); +} + +static void nface_write (GtsObject * o, FILE * fptr) +{ + GtsNFace * nf = GTS_NFACE (o); + + if (nf->name[0] != '\0') + fprintf (fptr, " %s", GTS_NFACE (o)->name); +} + +static void nface_class_init (GtsNFaceClass * klass) +{ + GTS_OBJECT_CLASS (klass)->read = nface_read; + GTS_OBJECT_CLASS (klass)->write = nface_write; +} + +static void nface_init (GtsNFace * nface) +{ + nface->name[0] = '\0'; +} + +/** + * gts_nface_class: + * + * Returns: the #GtsNFaceClass. + */ +GtsNFaceClass * gts_nface_class (void) +{ + static GtsNFaceClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo nface_info = { + "GtsNFace", + sizeof (GtsNFace), + sizeof (GtsNFaceClass), + (GtsObjectClassInitFunc) nface_class_init, + (GtsObjectInitFunc) nface_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (GTS_OBJECT_CLASS (gts_face_class ()), + &nface_info); + } + + return klass; +} diff --git a/gts/object.c b/gts/object.c new file mode 100644 index 0000000000..5970e50c71 --- /dev/null +++ b/gts/object.c @@ -0,0 +1,345 @@ +/* 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 "gts.h" +#include "gts-private.h" + +static GHashTable * class_table = NULL; + +static void gts_object_class_init (GtsObjectClass * klass, + GtsObjectClass * parent_class) +{ + if (parent_class) { + gts_object_class_init (klass, parent_class->parent_class); + if (parent_class->info.class_init_func) + (*parent_class->info.class_init_func) (klass); + } +} + +/** + * gts_object_class_new: + * @parent_class: a #GtsObjectClass. + * @info: a #GtsObjectClassInfo, description of the new class to create. + * + * Returns: a new #GtsObjectClass derived from @parent_class and described by + * @info. + */ +gpointer gts_object_class_new (GtsObjectClass * parent_class, + GtsObjectClassInfo * info) +{ + GtsObjectClass * klass; + + g_return_val_if_fail (info != NULL, NULL); + g_return_val_if_fail (parent_class == NULL || + info->object_size >= parent_class->info.object_size, + NULL); + g_return_val_if_fail (parent_class == NULL || + info->class_size >= parent_class->info.class_size, + NULL); + + klass = g_malloc0 (info->class_size); + klass->info = *info; + klass->parent_class = parent_class; + gts_object_class_init (klass, klass); + + if (!class_table) + class_table = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (class_table, klass->info.name, klass); + + return klass; +} + +/** + * gts_object_class_from_name: + * @name: the name of a #GtsObjectClass. + * + * Returns: the #GtsObjectClass with name @name or %NULL if it hasn't been + * instantiated yet. + */ +GtsObjectClass * gts_object_class_from_name (const gchar * name) +{ + g_return_val_if_fail (name != NULL, NULL); + + if (!class_table) + return NULL; + return g_hash_table_lookup (class_table, name); +} + +static void object_destroy (GtsObject * object) +{ +#ifdef DEBUG_IDENTITY +#ifdef DEBUG_LEAKS + fprintf (stderr, "destroy %s %p->%d\n", + object->klass->info.name, + object, + id (object)); +#endif + id_remove (object); +#endif + object->klass = NULL; + g_free (object); +} + +static void object_clone (GtsObject * clone, GtsObject * object) +{ + memcpy (clone, object, object->klass->info.object_size); + clone->reserved = NULL; +} + +static void object_class_init (GtsObjectClass * klass) +{ + klass->clone = object_clone; + klass->destroy = object_destroy; + klass->read = NULL; + klass->write = NULL; + klass->color = NULL; + klass->attributes = NULL; +} + +static void object_init (GtsObject * object) +{ + object->reserved = NULL; + object->flags = 0; +} + +/** + * gts_object_class: + * + * Returns: the #GtsObjectClass. + */ +GtsObjectClass * gts_object_class (void) +{ + static GtsObjectClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo object_info = { + "GtsObject", + sizeof (GtsObject), + sizeof (GtsObjectClass), + (GtsObjectClassInitFunc) object_class_init, + (GtsObjectInitFunc) object_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (NULL, &object_info); + } + + return klass; +} + +/** + * gts_object_check_cast: + * @object: a #GtsObject. + * @klass: a #GtsObjectClass. + * + * Returns: @object while emitting warnings if @object is not of class @klass. + */ +gpointer gts_object_check_cast (gpointer object, + gpointer klass) +{ + if (!object) { + g_warning ("invalid cast from (NULL) pointer to `%s'", + GTS_OBJECT_CLASS (klass)->info.name); + return object; + } + if (!((GtsObject *) object)->klass) { + g_warning ("invalid unclassed pointer in cast to `%s'", + GTS_OBJECT_CLASS (klass)->info.name); + return object; + } + if (!gts_object_is_from_class (object, klass)) { + g_warning ("invalid cast from `%s' to `%s'", + ((GtsObject *) object)->klass->info.name, + GTS_OBJECT_CLASS (klass)->info.name); + return object; + } + return object; +} + +/** + * gts_object_class_check_cast: + * @klass: a #GtsObjectClass. + * @from: a #GtsObjectClass. + * + * Returns: @klass while emitting warnings if @klass is not derived from + * @from. + */ +gpointer gts_object_class_check_cast (gpointer klass, + gpointer from) +{ + if (!klass) { + g_warning ("invalid cast from (NULL) pointer to `%s'", + GTS_OBJECT_CLASS (from)->info.name); + return klass; + } + if (!gts_object_class_is_from_class (klass, from)) { + g_warning ("invalid cast from `%s' to `%s'", + GTS_OBJECT_CLASS (klass)->info.name, + GTS_OBJECT_CLASS (from)->info.name); + return klass; + } + return klass; +} + +/** + * gts_object_init: + * @object: a #GtsObject. + * @klass: a #GtsObjectClass. + * + * Calls the init method of @klass with @object as argument. This is done + * recursively in the correct order (from the base class to the top). You + * should rarely need this function as it is called automatically by the + * constructor for each class. + */ +void gts_object_init (GtsObject * object, GtsObjectClass * klass) +{ + GtsObjectClass * parent_class; + + g_return_if_fail (object != NULL); + g_return_if_fail (klass != NULL); + + parent_class = klass->parent_class; + if (parent_class) + gts_object_init (object, parent_class); + if (klass->info.object_init_func) + (*klass->info.object_init_func) (object); +} + +/** + * gts_object_new: + * @klass: a #GtsObjectClass. + * + * Returns: a new initialized object of class @klass. + */ +GtsObject * gts_object_new (GtsObjectClass * klass) +{ + GtsObject * object; + + g_return_val_if_fail (klass != NULL, NULL); + + object = g_malloc0 (klass->info.object_size); + object->klass = klass; + gts_object_init (object, klass); + +#ifdef DEBUG_IDENTITY + id_insert (object); +#ifdef DEBUG_LEAKS + fprintf (stderr, "new %s %p->%d\n", klass->info.name, + object, + id (object)); +#endif +#endif + + return object; +} + +/** + * gts_object_clone: + * @object: a #GtsObject. + * + * Calls the clone method of @object. The call to this function will fail + * if no clone method exists for the given object. + * + * Returns: a new object clone of @object. + */ +GtsObject * gts_object_clone (GtsObject * object) +{ + GtsObject * clone; + + g_return_val_if_fail (object != NULL, NULL); + g_return_val_if_fail (object->klass->clone, NULL); + + clone = g_malloc0 (object->klass->info.object_size); + clone->klass = object->klass; + object_init (clone); + (* object->klass->clone) (clone, object); + +#ifdef DEBUG_IDENTITY + id_insert (clone); +#ifdef DEBUG_LEAKS + fprintf (stderr, "clone %s %p->%d\n", clone->klass->info.name, + clone, + id (clone)); +#endif +#endif + + return clone; +} + +/** + * gts_object_destroy: + * @object: a #GtsObject. + * + * Calls the destroy method of @object, freeing all memory allocated for it. + */ +void gts_object_destroy (GtsObject * object) +{ + g_assert (object->klass->destroy); + GTS_OBJECT_SET_FLAGS (object, GTS_DESTROYED); + (* object->klass->destroy) (object); +} + +/** + * gts_object_reset_reserved: + * @object: a #GtsObject. + * + * Reset the reserved field of @object. + */ +void gts_object_reset_reserved (GtsObject * object) +{ + g_return_if_fail (object != NULL); + + object->reserved = NULL; +} + +/** + * gts_object_attributes: + * @object: a #GtsObject. + * @from: a #GtsObject. + * + * Calls the attributes() method of @object using @from as source. + */ +void gts_object_attributes (GtsObject * object, GtsObject * from) +{ + g_return_if_fail (object != NULL); + + if (object->klass->attributes) + (* object->klass->attributes) (object, from); +} + +static void free_class (gchar * name, GtsObjectClass * klass) +{ + g_free (klass); +} + +/** + * gts_finalize: + * + * Free all the memory allocated by the object system of GTS. No other + * GTS function can be called after this function has been called. + */ +void gts_finalize (void) +{ + if (class_table) { + g_hash_table_foreach (class_table, (GHFunc) free_class, NULL); + g_hash_table_destroy (class_table); + class_table = NULL; + } +} diff --git a/gts/oocs.c b/gts/oocs.c new file mode 100644 index 0000000000..f0d76bf327 --- /dev/null +++ b/gts/oocs.c @@ -0,0 +1,387 @@ +/* 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 "gts.h" + +static void cluster_destroy (GtsObject * object) +{ + GtsCluster * c = GTS_CLUSTER (object); + + if (c->v && gts_vertex_is_unattached (c->v)) + gts_object_destroy (GTS_OBJECT (c->v)); + + /* do not forget to call destroy method of the parent */ + (* GTS_OBJECT_CLASS (gts_cluster_class ())->parent_class->destroy) (object); +} + +static void cluster_add (GtsCluster * c, GtsPoint * p, gpointer data) +{ + GtsPoint * cp; + + g_return_if_fail (c != NULL); + g_return_if_fail (c->v != NULL); + g_return_if_fail (p != NULL); + + cp = GTS_POINT (c->v); + + cp->x += p->x; + cp->y += p->y; + cp->z += p->z; + c->n++; +} + +static void cluster_update (GtsCluster * c) +{ + GtsPoint * p; + + g_return_if_fail (c != NULL); + g_return_if_fail (c->v != NULL); + + if (c->n > 1) { + p = GTS_POINT (c->v); + p->x /= c->n; + p->y /= c->n; + p->z /= c->n; + } +} + +static void cluster_class_init (GtsClusterClass * klass) +{ + klass->add = cluster_add; + klass->update = cluster_update; + + GTS_OBJECT_CLASS (klass)->destroy = cluster_destroy; +} + +static void cluster_init (GtsCluster * c) +{ + c->v = NULL; + c->n = 0; +} + +/** + * gts_cluster_class: + * + * Returns: the #GtsClusterClass. + */ +GtsClusterClass * gts_cluster_class (void) +{ + static GtsClusterClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo cluster_info = { + "GtsCluster", + sizeof (GtsCluster), + sizeof (GtsClusterClass), + (GtsObjectClassInitFunc) cluster_class_init, + (GtsObjectInitFunc) cluster_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (gts_object_class (), &cluster_info); + } + + return klass; +} + +/** + * gts_cluster_new: + * @klass: a #GtsClusterClass. + * @id: the id of the new cluster. + * @vklass: a #GtsVertexClass for the representative vertex of the cluster. + * + * Returns: a new #GtsCluster. + */ +GtsCluster * gts_cluster_new (GtsClusterClass * klass, + GtsClusterId id, + GtsVertexClass * vklass) +{ + GtsCluster * c; + + c = GTS_CLUSTER (gts_object_new (GTS_OBJECT_CLASS (klass))); + c->id = id; + c->v = gts_vertex_new (vklass, 0., 0., 0.); + + return c; +} + +/** + * gts_cluster_add: + * @c: a #GtsCluster. + * @p: a #GtsPoint. + * @data: data to pass to the add() virtual method of #GtsClusterClass. + * + * Adds point @p to cluster @c. + */ +void gts_cluster_add (GtsCluster * c, GtsPoint * p, gpointer data) +{ + g_return_if_fail (c != NULL); + g_return_if_fail (p != NULL); + + (* GTS_CLUSTER_CLASS (GTS_OBJECT (c)->klass)->add) (c, p, data); +} + +/** + * gts_cluster_update: + * @c: a #GtsCluster. + * + * Updates the position of the vertex representative of all the + * vertices added to @c. + */ +void gts_cluster_update (GtsCluster * c) +{ + g_return_if_fail (c != NULL); + + (* GTS_CLUSTER_CLASS (GTS_OBJECT (c)->klass)->update) (c); +} + +static void destroy_cluster (GtsClusterId * id, GtsObject * cluster) +{ + gts_object_destroy (cluster); +} + +static void cluster_grid_destroy (GtsObject * object) +{ + GtsClusterGrid * cluster_grid = GTS_CLUSTER_GRID (object); + + g_hash_table_foreach (cluster_grid->clusters, + (GHFunc) destroy_cluster, NULL); + g_hash_table_destroy (cluster_grid->clusters); + + (* GTS_OBJECT_CLASS (gts_cluster_grid_class ())->parent_class->destroy) + (object); +} + +static void cluster_grid_class_init (GtsClusterGridClass * klass) +{ + GTS_OBJECT_CLASS (klass)->destroy = cluster_grid_destroy; +} + +static gint cluster_id_equal (gconstpointer v1, + gconstpointer v2) +{ + const GtsClusterId * id1 = (const GtsClusterId *) v1; + const GtsClusterId * id2 = (const GtsClusterId *) v2; + return ((id1->x == id2->x) && (id1->y == id2->y) && (id1->z == id2->z)); +} + +static guint cluster_id_hash (gconstpointer key) +{ + const GtsClusterId * id = (const GtsClusterId *) key; + return id->x + id->y + id->z; +} + +static void cluster_grid_init (GtsClusterGrid * cluster_grid) +{ + cluster_grid->surface = NULL; + cluster_grid->bbox = NULL; + cluster_grid->cluster_class = gts_cluster_class (); + cluster_grid->clusters = g_hash_table_new (cluster_id_hash, + cluster_id_equal); +} + +/** + * gts_cluster_grid_class: + * + * Returns: the #GtsClusterGridClass. + */ +GtsClusterGridClass * gts_cluster_grid_class (void) +{ + static GtsClusterGridClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo cluster_grid_info = { + "GtsClusterGrid", + sizeof (GtsClusterGrid), + sizeof (GtsClusterGridClass), + (GtsObjectClassInitFunc) cluster_grid_class_init, + (GtsObjectInitFunc) cluster_grid_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (gts_object_class (), &cluster_grid_info); + } + + return klass; +} + +/** + * gts_cluster_grid_new: + * @klass: a #GtsClusterGridClass. + * @cluster_class: the klass to be used for the vertex clusters. + * @s: the simplified surface. + * @bbox: bounding box of the surface to be simplified. + * @delta: the size of one grid cell of the simplification grid. + * + * Returns: a new #GtsClusterGrid. + */ +GtsClusterGrid * gts_cluster_grid_new (GtsClusterGridClass * klass, + GtsClusterClass * cluster_class, + GtsSurface * s, + GtsBBox * bbox, + gdouble delta) +{ + GtsClusterGrid * cluster_grid; + GtsVector size; + + g_return_val_if_fail (klass != NULL, NULL); + g_return_val_if_fail (cluster_class != NULL, NULL); + g_return_val_if_fail (s != NULL, NULL); + g_return_val_if_fail (bbox != NULL, NULL); + g_return_val_if_fail (delta > 0., NULL); + + size[0] = ceil ((bbox->x2 - bbox->x1)/delta); + size[1] = ceil ((bbox->y2 - bbox->y1)/delta); + size[2] = ceil ((bbox->z2 - bbox->z1)/delta); + g_return_val_if_fail (size[0] <= 2.*G_MAXINT + 2. && + size[1] <= 2.*G_MAXINT + 2. && + size[2] <= 2.*G_MAXINT + 2., NULL); + cluster_grid = + GTS_CLUSTER_GRID (gts_object_new (GTS_OBJECT_CLASS (klass))); + cluster_grid->cluster_class = cluster_class; + cluster_grid->surface = s; + cluster_grid->bbox = bbox; + cluster_grid->size[0] = size[0]; + cluster_grid->size[1] = size[1]; + cluster_grid->size[2] = size[2]; + + return cluster_grid; +} + +static GtsClusterId cluster_index (GtsPoint * p, + GtsBBox * bb, + GtsVector n) +{ + GtsClusterId id = {0, 0, 0}; + + g_return_val_if_fail (p->x >= bb->x1 && p->x <= bb->x2, id); + g_return_val_if_fail (p->y >= bb->y1 && p->y <= bb->y2, id); + g_return_val_if_fail (p->z >= bb->z1 && p->z <= bb->z2, id); + + id.x = (guint) (p->x == bb->x2 ? n[0] - 1. : n[0]*(p->x - bb->x1)/(bb->x2 - bb->x1)); + id.y = (guint) (p->y == bb->y2 ? n[1] - 1. : n[1]*(p->y - bb->y1)/(bb->y2 - bb->y1)); + id.z = (guint) (p->z == bb->z2 ? n[2] - 1. : n[2]*(p->z - bb->z1)/(bb->z2 - bb->z1)); + + return id; +} + +static GtsCluster * cluster_grid_add_point (GtsClusterGrid * cluster_grid, + GtsPoint * p, + gpointer data) +{ + GtsClusterId id = cluster_index (p, + cluster_grid->bbox, + cluster_grid->size); + GtsCluster * c = g_hash_table_lookup (cluster_grid->clusters, &id); + + if (c == NULL) { + c = gts_cluster_new (cluster_grid->cluster_class, id, + cluster_grid->surface->vertex_class); + g_hash_table_insert (cluster_grid->clusters, &c->id, c); + } + + gts_cluster_add (c, p, data); + + return c; +} + +/** + * gts_cluster_grid_add_triangle: + * @cluster_grid: a #GtsClusterGrid. + * @p1: a #GtsPoint. + * @p2: a #GtsPoint. + * @p3: a #GtsPoint. + * @data: user data to pass to the cluster add() method. + * + * Adds the triangle defined by @p1, @p2 and @p3 to the respective clusters + * of @cluster_grid. + */ +void gts_cluster_grid_add_triangle (GtsClusterGrid * cluster_grid, + GtsPoint * p1, + GtsPoint * p2, + GtsPoint * p3, + gpointer data) +{ + GtsCluster * c1, * c2, * c3; + + g_return_if_fail (cluster_grid != NULL); + g_return_if_fail (p1 != NULL); + g_return_if_fail (p2 != NULL); + g_return_if_fail (p3 != NULL); + g_return_if_fail (cluster_grid->surface != NULL); + + c1 = cluster_grid_add_point (cluster_grid, p1, data); + c2 = cluster_grid_add_point (cluster_grid, p2, data); + c3 = cluster_grid_add_point (cluster_grid, p3, data); + + if (c1 != c2 && c2 != c3 && c3 != c1) { + GtsVertex * v1, * v2, * v3; + GtsEdge * e1, * e2, * e3; + gboolean new_edge = FALSE; + + v1 = c1->v; v2 = c2->v; v3 = c3->v; + + if ((e1 = GTS_EDGE (gts_vertices_are_connected (v1, v2))) == NULL) { + e1 = gts_edge_new (cluster_grid->surface->edge_class, v1, v2); + new_edge = TRUE; + } + if ((e2 = GTS_EDGE (gts_vertices_are_connected (v2, v3))) == NULL) { + e2 = gts_edge_new (cluster_grid->surface->edge_class, v2, v3); + new_edge = TRUE; + } + if ((e3 = GTS_EDGE (gts_vertices_are_connected (v3, v1))) == NULL) { + e3 = gts_edge_new (cluster_grid->surface->edge_class, v3, v1); + new_edge = TRUE; + } + if (new_edge || !gts_triangle_use_edges (e1, e2, e3)) + gts_surface_add_face (cluster_grid->surface, + gts_face_new (cluster_grid->surface->face_class, + e1, e2, e3)); + } +} + +static void update_cluster (gint * id, GtsCluster * cluster, GtsRange * stats) +{ + gts_cluster_update (cluster); + gts_range_add_value (stats, cluster->n); +} + +/** + * gts_cluster_grid_update: + * @cluster_grid: a #GtsClusterGrid. + * + * Updates the representative vertices of all the clusters of @cluster_grid. + * + * Returns: a #GtsRange describing the statistics for the number of vertices + * added to each cluster of @cluster_grid. + */ +GtsRange gts_cluster_grid_update (GtsClusterGrid * cluster_grid) +{ + GtsRange stats; + + gts_range_init (&stats); + g_return_val_if_fail (cluster_grid != NULL, stats); + + g_hash_table_foreach (cluster_grid->clusters, + (GHFunc) update_cluster, &stats); + gts_range_update (&stats); + + return stats; +} diff --git a/gts/partition.c b/gts/partition.c new file mode 100644 index 0000000000..3b73e6896e --- /dev/null +++ b/gts/partition.c @@ -0,0 +1,1219 @@ +/* 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 "gts.h" + +/* #define DEBUG */ + +/* Graph partition */ + +/** + * gts_graph_partition_edges_cut: + * @partition: a list of @GtsGraph representing a partition. + * + * Returns: the number of edges cut by the partition. + */ +guint gts_graph_partition_edges_cut (GSList * partition) +{ + guint cuts = 0; + + while (partition) { + cuts += gts_graph_edges_cut (partition->data); + partition = partition->next; + } + + return cuts/2; +} + +/** + * gts_graph_partition_edges_cut_weight: + * @partition: a list of @GtsGraph representing a partition. + * + * Returns: the total weight of the edges cut by the partition. + */ +gfloat gts_graph_partition_edges_cut_weight (GSList * partition) +{ + gfloat weight = 0.; + + while (partition) { + weight += gts_graph_edges_cut_weight (partition->data); + partition = partition->next; + } + + return weight/2.; +} + +/** + * gts_graph_partition_print_stats: + * @partition: a list of @GtsGraph representing a partition. + * @fp: a file pointer. + * + * Writes to @fp a summary of the properties of @partition. + */ +void gts_graph_partition_print_stats (GSList * partition, + FILE * fp) +{ + GtsRange weight; + GSList * i; + + g_return_if_fail (partition != NULL); + g_return_if_fail (fp != NULL); + + gts_range_init (&weight); + i = partition; + while (i) { + gts_range_add_value (&weight, gts_graph_weight (i->data)); + i = i->next; + } + gts_range_update (&weight); + + fprintf (fp, + "# parts: %d\n" + "# edge cuts: %5d edge cuts weight: %5g\n" + "# weight: ", + g_slist_length (partition), + gts_graph_partition_edges_cut (partition), + gts_graph_partition_edges_cut_weight (partition)); + gts_range_print (&weight, fp); + fputc ('\n', fp); +} + +/** + * gts_graph_partition_balance: + * @partition: a list of @GtsGraph representing a partition. + * + * Returns: the difference between the maximum and the minimum weight + * of the graphs in @partition. + */ +gfloat gts_graph_partition_balance (GSList * partition) +{ + gfloat wmin = G_MAXFLOAT; + gfloat wmax = - G_MAXFLOAT; + + g_return_val_if_fail (partition != NULL, 0.); + + while (partition) { + gfloat weight = gts_graph_weight (partition->data); + if (weight < wmin) + wmin = weight; + if (weight > wmax) + wmax = weight; + partition = partition->next; + } + return wmax - wmin; +} + +/** + * gts_graph_partition_clone: + * @partition: a list of @GtsGraph representing a partition. + * + * Returns: a new partition clone of @partition (i.e. a list of new + * graphs clones of the graphs in @partition). + */ +GSList * gts_graph_partition_clone (GSList * partition) +{ + GSList * cparts = NULL; + + while (partition) { + cparts = + g_slist_prepend (cparts, + gts_object_clone (GTS_OBJECT (partition->data))); + partition = partition->next; + } + return cparts; +} + +/** + * gts_graph_partition_destroy: + * @partition: a list of @GtsGraph representing a partition. + * + * Destroys all the graphs in @partition and frees @partition. + */ +void gts_graph_partition_destroy (GSList * partition) +{ + GSList * i = partition; + + while (i) { + gts_object_destroy (GTS_OBJECT (i->data)); + i = i->next; + } + g_slist_free (partition); +} + +static void find_smallest_degree (GtsGNode * n, gpointer * data) +{ + GtsGNode ** nmin = data[0]; + GtsGraph * g = data[1]; + guint * min = data[2]; + guint degree = gts_gnode_degree (n, g); + + if (degree < *min) { + *min = degree; + *nmin = n; + } +} + +static gint graph_comp_weight (GtsGraph * g1, GtsGraph * g2) +{ + if (gts_graph_weight (g1) > gts_graph_weight (g2)) + return 1; + return -1; +} + +static void partition_update (GSList * list, GtsGraph * g) +{ + GSList * i; + GtsGraph * g1; + GtsHeap * size_heap; + gboolean reinit = TRUE; + + /* initialize traversals */ + i = list; + while (i) { + GtsGNode * seed = GTS_OBJECT (i->data)->reserved; + GTS_OBJECT (seed)->reserved = + gts_graph_traverse_new (g, seed, GTS_BREADTH_FIRST, reinit); + reinit = FALSE; + i = i->next; + } + + size_heap = gts_heap_new ((GCompareFunc) graph_comp_weight); + i = list; + while (i) { + gts_heap_insert (size_heap, i->data); + i = i->next; + } + while ((g1 = gts_heap_remove_top (size_heap))) { + GtsGraphTraverse * t = GTS_OBJECT (GTS_OBJECT (g1)->reserved)->reserved; + GtsGNode * n = gts_graph_traverse_next (t); + if (n) { + gts_container_add (GTS_CONTAINER (g1), GTS_CONTAINEE (n)); + gts_heap_insert (size_heap, g1); + } + } + gts_heap_destroy (size_heap); + + /* destroy traversals */ + i = list; + while (i) { + GtsGNode * seed = GTS_OBJECT (i->data)->reserved; + gts_graph_traverse_destroy (GTS_OBJECT (seed)->reserved); + GTS_OBJECT (seed)->reserved = NULL; + i = i->next; + } +} + +static void better_seed (GtsGNode * n, gpointer * data) +{ + guint * sum = data[0]; + GtsGNode ** seed = data[1]; + GtsGraph * g = data[2]; + guint sum1 = gts_graph_distance_sum (g, n); + + if (sum1 < *sum) { + *sum = sum1; + *seed = n; + } +} + +static GtsGNode * graph_new_seed (GtsGraph * g, GtsGNode * seed) +{ + guint sum = gts_graph_distance_sum (g, seed); + gpointer data[3]; + GtsGNode * new_seed = seed; + + data[0] = ∑ + data[1] = &new_seed; + data[2] = g; + gts_gnode_foreach_neighbor (seed, g, (GtsFunc) better_seed, data); + + return new_seed; +} + +/** + * gts_graph_bubble_partition: + * @g: a #GtsGraph. + * @np: number of partitions. + * @niter: the maximum number of iterations. + * @step_info: a #GtsFunc or %NULL. + * @data: user data to pass to @step_info. + * + * An implementation of the "bubble partitioning algorithm" of + * Diekmann, Preis, Schlimbach and Walshaw (2000). The maximum number + * of iteration on the positions of the graph growing seeds is + * controlled by @niter. + * + * If not %NULL @step_info is called after each iteration on the seeds + * positions passing the partition (a GSList) as argument. + * + * Returns: a list of @np new #GtsGraph representing the partition. + */ +GSList * gts_graph_bubble_partition (GtsGraph * g, + guint np, + guint niter, + GtsFunc step_info, + gpointer data) +{ + GSList * list = NULL, * seeds = NULL; + GtsGNode * seed = NULL; + guint min = G_MAXINT/2 - 1; + gpointer info[3]; + GtsGraph * g1; + gboolean changed = TRUE; + + g_return_val_if_fail (g != NULL, NULL); + g_return_val_if_fail (np > 0, NULL); + + info[0] = &seed; + info[1] = g; + info[2] = &min; + gts_container_foreach (GTS_CONTAINER (g), + (GtsFunc) find_smallest_degree, + info); + if (seed == NULL) + return NULL; + + g1 = GTS_GRAPH (gts_object_new (GTS_OBJECT (g)->klass)); + gts_container_add (GTS_CONTAINER (g1), GTS_CONTAINEE (seed)); + list = g_slist_prepend (list, g1); + GTS_OBJECT (g1)->reserved = seed; + seeds = g_slist_prepend (seeds, seed); + + while (--np && seed) + if ((seed = gts_graph_farthest (g, seeds))) { + g1 = GTS_GRAPH (gts_object_new (GTS_OBJECT (g)->klass)); + gts_container_add (GTS_CONTAINER (g1), GTS_CONTAINEE (seed)); + list = g_slist_prepend (list, g1); + GTS_OBJECT (g1)->reserved = seed; + seeds = g_slist_prepend (seeds, seed); + } + g_slist_free (seeds); + + partition_update (list, g); + + while (changed && niter--) { + GSList * i; + + changed = FALSE; + i = list; + while (i) { + GtsGraph * g1 = i->data; + GtsGNode * seed = GTS_OBJECT (g1)->reserved; + GtsGNode * new_seed = graph_new_seed (g1, seed); + if (new_seed != seed) { + changed = TRUE; + GTS_OBJECT (g1)->reserved = new_seed; + } + i = i->next; + } + + if (changed) { + i = list; + while (i) { + GtsGraph * g1 = i->data; + GtsGNode * seed = GTS_OBJECT (g1)->reserved; + + gts_object_destroy (GTS_OBJECT (g1)); + i->data = g1 = GTS_GRAPH (gts_object_new (GTS_OBJECT (g)->klass)); + gts_container_add (GTS_CONTAINER (g1), GTS_CONTAINEE (seed)); + GTS_OBJECT (g1)->reserved = seed; + i = i->next; + } + partition_update (list, g); + if (step_info) + (* step_info) (list, data); + } + } + g_slist_foreach (list, (GFunc) gts_object_reset_reserved, NULL); + return list; +} + +/* Graph bisection */ + +static gdouble node_cost (GtsGNode * n, gpointer * data) +{ + GtsGraph * g = data[0]; + GtsGraph * g1 = data[1]; + GSList * i = GTS_SLIST_CONTAINER (n)->items; + gdouble cost = 0.; + + while (i) { + GtsGEdge * e = i->data; + GtsGNode * n1 = GTS_GNODE_NEIGHBOR (n, e); + + if (gts_containee_is_contained (GTS_CONTAINEE (n1), GTS_CONTAINER (g))) { + if (gts_containee_is_contained (GTS_CONTAINEE (n1), GTS_CONTAINER (g1))) + cost -= gts_gedge_weight (e); + else + cost += gts_gedge_weight (e); + } + i = i->next; + } + + return cost; +} + +static void add_neighbor (GtsGNode * n, GtsEHeap * heap) +{ + if (GTS_OBJECT (n)->reserved == n) + return; + if (GTS_OBJECT (n)->reserved) + gts_eheap_remove (heap, GTS_OBJECT (n)->reserved); + GTS_OBJECT (n)->reserved = gts_eheap_insert (heap, n); +} + +static void add_unused (GtsGNode * n, GtsGraph * g2) +{ + if (GTS_OBJECT (n)->reserved) + GTS_OBJECT (n)->reserved = NULL; + else + gts_container_add (GTS_CONTAINER (g2), GTS_CONTAINEE (n)); +} + +static gdouble degree_cost (GtsGNode * n, GtsGraph * g) +{ + return gts_gnode_degree (n, g); +} + +static void add_seed (GtsGNode * n, GtsEHeap * heap) +{ + gts_eheap_insert (heap, n); +} + +static void boundary_node1 (GtsGNode * n, GtsGraphBisection * bg) +{ + GSList * i = GTS_SLIST_CONTAINER (n)->items; + + while (i) { + GtsGNode * n1 = GTS_GNODE_NEIGHBOR (n, i->data); + if (gts_containee_is_contained (GTS_CONTAINEE (n1), + GTS_CONTAINER (bg->g2))) { + g_hash_table_insert (bg->bg1, n, n1); + return; + } + i = i->next; + } +} + +static void boundary_node2 (GtsGNode * n, GtsGraphBisection * bg) +{ + GSList * i = GTS_SLIST_CONTAINER (n)->items; + + while (i) { + GtsGNode * n1 = GTS_GNODE_NEIGHBOR (n, i->data); + if (gts_containee_is_contained (GTS_CONTAINEE (n1), + GTS_CONTAINER (bg->g1))) { + g_hash_table_insert (bg->bg2, n, n1); + return; + } + i = i->next; + } +} + +static void check_bg (GtsGNode * n, gpointer * data) +{ + GHashTable * bg = data[0]; + GtsGraph * g = data[1]; + gboolean * ok = data[2]; + guint * nb = data[3]; + guint nn = gts_gnode_degree (n, g); + + if (nn > 0) + (*nb)++; + if ((nn > 0 && !g_hash_table_lookup (bg, n)) || + (nn == 0 && g_hash_table_lookup (bg, n))) { + g_warning ("nn: %d lookup: %p\n", + nn, g_hash_table_lookup (bg, n)); + *ok = FALSE; + } +} + +/** + * gts_graph_bisection_check: + * @bg: a #GtsGraphBisection. + * + * Checks that the boundary of @bg is correctly defined (used for + * debugging purposes). + * + * Returns: %TRUE if @bg is ok, %FALSE otherwise. + */ +gboolean gts_graph_bisection_check (GtsGraphBisection * bg) +{ + gboolean ok = TRUE; + guint nb; + gpointer data[4]; + + g_return_val_if_fail (bg != NULL, FALSE); + + nb = 0; + data[0] = bg->bg1; + data[1] = bg->g2; + data[2] = &ok; + data[3] = &nb; + gts_container_foreach (GTS_CONTAINER (bg->g1), (GtsFunc) check_bg, data); + g_return_val_if_fail (g_hash_table_size (bg->bg1) == nb, FALSE); + + nb = 0; + data[0] = bg->bg2; + data[1] = bg->g1; + gts_container_foreach (GTS_CONTAINER (bg->g2), (GtsFunc) check_bg, data); + g_return_val_if_fail (g_hash_table_size (bg->bg2) == nb, FALSE); + + return ok; +} + +/** + * gts_graph_ggg_bisection: + * @g: a #GtsGraph. + * @ntry: the number of randomly selected initial seeds. + * + * An implementation of the "Greedy Graph Growing" algorithm of + * Karypis and Kumar (1997). + * + * @ntry randomly chosen seeds are used and the best partition is retained. + * + * Returns: a new #GtsGraphBisection of @g. + */ +GtsGraphBisection * gts_graph_ggg_bisection (GtsGraph * g, guint ntry) +{ + gfloat size, bestcost = G_MAXFLOAT, smin; + GtsGraph * bestg1 = NULL, * bestg2 = NULL; + gboolean balanced = FALSE; + GtsEHeap * degree_heap; + GtsGNode * seed; + GtsGraphBisection * bg; + + g_return_val_if_fail (g != NULL, NULL); + + bg = g_malloc (sizeof (GtsGraphBisection)); + bg->g = g; + + size = gts_graph_weight (g)/2.; + smin = 0.9*size; + + degree_heap = gts_eheap_new ((GtsKeyFunc) degree_cost, g); + gts_eheap_freeze (degree_heap); + gts_container_foreach (GTS_CONTAINER (g), (GtsFunc) add_seed, degree_heap); + gts_eheap_thaw (degree_heap); + + while (ntry && ((seed = gts_eheap_remove_top (degree_heap, NULL)))) { + GtsGraph * g1, * g2; + GtsGNode * n; + gdouble cost; + gpointer data[2]; + GtsEHeap * heap; + + g1 = gts_graph_new (GTS_GRAPH_CLASS (GTS_OBJECT (g)->klass), + g->node_class, g->edge_class); + g2 = gts_graph_new (GTS_GRAPH_CLASS (GTS_OBJECT (g)->klass), + g->node_class, g->edge_class); + + data[0] = g; + data[1] = g1; + heap = gts_eheap_new ((GtsKeyFunc) node_cost, data); + + gts_container_add (GTS_CONTAINER (g1), GTS_CONTAINEE (seed)); + GTS_OBJECT (seed)->reserved = seed; + gts_gnode_foreach_neighbor (seed, g, (GtsFunc) add_neighbor, heap); + + while ((n = gts_eheap_remove_top (heap, &cost))) + if (gts_graph_weight (g1) + gts_gnode_weight (n) <= size) { + gts_container_add (GTS_CONTAINER (g1), GTS_CONTAINEE (n)); + GTS_OBJECT (n)->reserved = n; + gts_gnode_foreach_neighbor (n, g, (GtsFunc) add_neighbor, heap); + } + else + GTS_OBJECT (n)->reserved = NULL; + gts_eheap_destroy (heap); + + gts_container_foreach (GTS_CONTAINER (g), (GtsFunc) add_unused, g2); + + cost = gts_graph_edges_cut_weight (g1); + if (!bestg1 || + (!balanced && gts_graph_weight (g1) >= smin) || + (cost < bestcost && gts_graph_weight (g1) >= smin)) { + if (bestg1) + bestcost = cost; + if (bestg1) + gts_object_destroy (GTS_OBJECT (bestg1)); + if (bestg2) + gts_object_destroy (GTS_OBJECT (bestg2)); + bestg1 = g1; + bestg2 = g2; + if (gts_graph_weight (g1) >= smin) + balanced = TRUE; + } + else { + gts_object_destroy (GTS_OBJECT (g1)); + gts_object_destroy (GTS_OBJECT (g2)); + } + + ntry--; + } + gts_eheap_destroy (degree_heap); + +#ifdef DEBUG + fprintf (stderr, "bestcost: %5g g1: %5g|%5d g2: %5g|%5d\n", + bestcost, + gts_graph_weight (bestg1), + gts_container_size (GTS_CONTAINER (bestg1)), + gts_graph_weight (bestg2), + gts_container_size (GTS_CONTAINER (bestg2))); +#endif + + g_assert (bestg1 != NULL); + bg->g1 = bestg1; + g_assert (bestg2 != NULL); + bg->g2 = bestg2; + + /* boundary nodes */ + bg->bg1 = g_hash_table_new (NULL, NULL); + gts_container_foreach (GTS_CONTAINER (bg->g1), (GtsFunc) boundary_node1, bg); + bg->bg2 = g_hash_table_new (NULL, NULL); + gts_container_foreach (GTS_CONTAINER (bg->g2), (GtsFunc) boundary_node2, bg); + + return bg; +} + +/** + * gts_graph_bfgg_bisection: + * @g: a #GtsGraph. + * @ntry: the number of randomly selected initial seeds. + * + * An implementation of a "Breadth-First Graph Growing" algorithm. + * + * @ntry randomly chosen seeds are used and the best partition is retained. + * + * Returns: a new #GtsGraphBisection of @g. + */ +GtsGraphBisection * gts_graph_bfgg_bisection (GtsGraph * g, guint ntry) +{ + gfloat size, bestcost = G_MAXFLOAT, smin; + GtsGraph * bestg1 = NULL, * bestg2 = NULL; + GtsEHeap * degree_heap; + GtsGNode * seed; + GtsGraphBisection * bg; + + g_return_val_if_fail (g != NULL, NULL); + + bg = g_malloc (sizeof (GtsGraphBisection)); + bg->g = g; + + size = gts_graph_weight (g)/2.; + smin = 0.9*size; + + degree_heap = gts_eheap_new ((GtsKeyFunc) degree_cost, g); + gts_eheap_freeze (degree_heap); + gts_container_foreach (GTS_CONTAINER (g), (GtsFunc) add_seed, degree_heap); + gts_eheap_thaw (degree_heap); + + while (ntry && ((seed = gts_eheap_remove_top (degree_heap, NULL)))) { + GtsGraph * g1, * g2; + GtsGNode * n; + gdouble cost; + GtsGraphTraverse * t = gts_graph_traverse_new (g, seed, + GTS_BREADTH_FIRST, TRUE); + + g1 = gts_graph_new (GTS_GRAPH_CLASS (GTS_OBJECT (g)->klass), + g->node_class, g->edge_class); + g2 = gts_graph_new (GTS_GRAPH_CLASS (GTS_OBJECT (g)->klass), + g->node_class, g->edge_class); + + while ((n = gts_graph_traverse_next (t))) + if (gts_graph_weight (g1) + gts_gnode_weight (n) <= size) { + gts_container_add (GTS_CONTAINER (g1), GTS_CONTAINEE (n)); + GTS_OBJECT (n)->reserved = n; + } + gts_graph_traverse_destroy (t); + + gts_container_foreach (GTS_CONTAINER (g), (GtsFunc) add_unused, g2); + + cost = gts_graph_edges_cut_weight (g1); + if (!bestg1 || (cost < bestcost && gts_graph_weight (g1) >= smin)) { + if (bestg1) + bestcost = cost; + if (bestg1) + gts_object_destroy (GTS_OBJECT (bestg1)); + if (bestg2) + gts_object_destroy (GTS_OBJECT (bestg2)); + bestg1 = g1; + bestg2 = g2; + } + else { + gts_object_destroy (GTS_OBJECT (g1)); + gts_object_destroy (GTS_OBJECT (g2)); + } + + ntry--; + } + gts_eheap_destroy (degree_heap); + +#ifdef DEBUG + fprintf (stderr, "bestcost: %5g g1: %5g|%5d g2: %5g|%5d\n", + bestcost, + gts_graph_weight (bestg1), + gts_container_size (GTS_CONTAINER (bestg1)), + gts_graph_weight (bestg2), + gts_container_size (GTS_CONTAINER (bestg2))); +#endif + + bg->g1 = bestg1; + bg->g2 = bestg2; + + /* boundary nodes */ + bg->bg1 = g_hash_table_new (NULL, NULL); + gts_container_foreach (GTS_CONTAINER (bg->g1), (GtsFunc) boundary_node1, bg); + bg->bg2 = g_hash_table_new (NULL, NULL); + gts_container_foreach (GTS_CONTAINER (bg->g2), (GtsFunc) boundary_node2, bg); + + return bg; +} + +static gdouble node_move_cost1 (GtsGNode * n, GtsGraphBisection * bg) +{ + return gts_gnode_move_cost (n, bg->g1, bg->g2); +} + +static gdouble node_move_cost2 (GtsGNode * n, GtsGraphBisection * bg) +{ + return gts_gnode_move_cost (n, bg->g2, bg->g1); +} + +static void build_heap (GtsGNode * n, GtsEHeap * heap) +{ + GTS_OBJECT (n)->reserved = gts_eheap_insert (heap, n); +} + +/** + * gts_graph_bisection_kl_refine: + * @bg: a #GtsGraphBisection. + * @mmax: the maximum number of unsuccessful successive moves. + * + * An implementation of the simplified Kernighan-Lin algorithm for + * graph bisection refinement as described in Karypis and Kumar + * (1997). + * + * The algorithm stops if @mmax consecutive modes do not lead to a + * decrease in the number of edges cut. This last @mmax moves are + * undone. + * + * Returns: the decrease in the weight of the edges cut by the bisection. + */ +gdouble gts_graph_bisection_kl_refine (GtsGraphBisection * bg, + guint mmax) +{ + GtsEHeap * h1, * h2; + GtsGNode * n; + guint nm = 0, i; + GtsGNode ** moves; + gdouble bestcost = 0., totalcost = 0., best_balance; + + g_return_val_if_fail (bg != NULL, 0.); + g_return_val_if_fail (mmax > 0, 0.); + + h1 = gts_eheap_new ((GtsKeyFunc) node_move_cost1, bg); + gts_eheap_freeze (h1); + gts_container_foreach (GTS_CONTAINER (bg->g1), (GtsFunc) build_heap, h1); + gts_eheap_thaw (h1); + + h2 = gts_eheap_new ((GtsKeyFunc) node_move_cost2, bg); + gts_eheap_freeze (h2); + gts_container_foreach (GTS_CONTAINER (bg->g2), (GtsFunc) build_heap, h2); + gts_eheap_thaw (h2); + + moves = g_malloc (sizeof (GtsGNode *)*mmax); + best_balance = fabs (gts_graph_weight (bg->g1) - gts_graph_weight (bg->g2)); + + do { + GtsGraph * g1, * g2; + gdouble cost; + + if (gts_graph_weight (bg->g1) > gts_graph_weight (bg->g2)) { + n = gts_eheap_remove_top (h1, &cost); + g1 = bg->g1; + g2 = bg->g2; + } + else { + n = gts_eheap_remove_top (h2, &cost); + g1 = bg->g2; + g2 = bg->g1; + } + if (n) { + GSList * i; + + GTS_OBJECT (n)->reserved = NULL; + gts_container_add (GTS_CONTAINER (g2), GTS_CONTAINEE (n)); + gts_container_remove (GTS_CONTAINER (g1), GTS_CONTAINEE (n)); + + totalcost += cost; + if (totalcost < bestcost) { + bestcost = totalcost; + nm = 0; + } + else if (totalcost == bestcost) { + gdouble balance = fabs (gts_graph_weight (g1) - gts_graph_weight (g2)); + + if (balance < best_balance) { + best_balance = balance; + nm = 0; + } + } + else + moves[nm++] = n; + + i = GTS_SLIST_CONTAINER (n)->items; + while (i) { + GtsGNode * n1 = GTS_GNODE_NEIGHBOR (n, i->data); + if (GTS_OBJECT (n1)->reserved && + gts_containee_is_contained (GTS_CONTAINEE (n1), + GTS_CONTAINER (bg->g))) { + GtsEHeap * h = + gts_containee_is_contained (GTS_CONTAINEE (n1), + GTS_CONTAINER (bg->g1)) ? h1 : h2; + gts_eheap_remove (h, GTS_OBJECT (n1)->reserved); + GTS_OBJECT (n1)->reserved = gts_eheap_insert (h, n1); + } + i = i->next; + } + } + } while (n && nm < mmax); + + gts_eheap_foreach (h1, (GFunc) gts_object_reset_reserved, NULL); + gts_eheap_foreach (h2, (GFunc) gts_object_reset_reserved, NULL); + gts_eheap_destroy (h1); + gts_eheap_destroy (h2); + + /* undo last nm moves */ + for (i = 0; i < nm; i++) { + GtsGNode * n = moves[i]; + GtsGraph * g1 = + gts_containee_is_contained (GTS_CONTAINEE (n), + GTS_CONTAINER (bg->g1)) ? bg->g1 : bg->g2; + GtsGraph * g2 = g1 == bg->g1 ? bg->g2 : bg->g1; + + gts_container_add (GTS_CONTAINER (g2), GTS_CONTAINEE (n)); + gts_container_remove (GTS_CONTAINER (g1), GTS_CONTAINEE (n)); + } + g_free (moves); + + return bestcost; +} + +static void build_bheap (GtsGNode * n, GtsGNode * n1, GtsEHeap * heap) +{ + GTS_OBJECT (n)->reserved = gts_eheap_insert (heap, n); +} + +static void update_neighbors (GtsGNode * n, GtsGraphBisection * bg, + GtsEHeap * h1, GtsEHeap * h2) +{ + GSList * i; + + i = GTS_SLIST_CONTAINER (n)->items; + while (i) { + GtsGNode * n1 = GTS_GNODE_NEIGHBOR (n, i->data); + if (gts_containee_is_contained (GTS_CONTAINEE (n1), + GTS_CONTAINER (bg->g))) { + GtsEHeap * h; + GtsGraph /* * g1,*/ * g2; + GHashTable * bg1; + + if (gts_containee_is_contained (GTS_CONTAINEE (n1), + GTS_CONTAINER (bg->g1))) { + h = h1; + //g1 = bg->g1; + g2 = bg->g2; + bg1 = bg->bg1; + } + else { + h = h2; + //g1 = bg->g2; + g2 = bg->g1; + bg1 = bg->bg2; + } + g_hash_table_remove (bg1, n1); + if (h && GTS_OBJECT (n1)->reserved && GTS_OBJECT (n1)->reserved != n1) { + gts_eheap_remove (h, GTS_OBJECT (n1)->reserved); + GTS_OBJECT (n1)->reserved = NULL; + } + if (gts_gnode_degree (n1, g2)) { + g_hash_table_insert (bg1, n1, n1); + if (h && GTS_OBJECT (n1)->reserved != n1) + GTS_OBJECT (n1)->reserved = gts_eheap_insert (h, n1); + } + } + i = i->next; + } +} + +/** + * gts_graph_bisection_bkl_refine: + * @bg: a #GtsGraphBisection. + * @mmax: the maximum number of unsuccessful successive moves. + * @imbalance: the maximum relative imbalance allowed between the + * weights of both halves of the partition. + * + * An implementation of the simplified boundary Kernighan-Lin + * algorithm for graph bisection refinement as described in Karypis + * and Kumar (1997). + * + * The algorithm stops if @mmax consecutive modes do not lead to a + * decrease in the number of edges cut. This last @mmax moves are + * undone. + * + * Returns: the decrease in the weight of the edges cut by the bisection. + */ +gdouble gts_graph_bisection_bkl_refine (GtsGraphBisection * bg, + guint mmax, + gfloat imbalance) +{ + GtsEHeap * h1, * h2; + GtsGNode * n; + guint nm = 0, i; + GtsGNode ** moves; + gdouble bestcost = 0., totalcost = 0., best_balance; + gboolean balanced = FALSE; + + g_return_val_if_fail (bg != NULL, 0.); + g_return_val_if_fail (mmax > 0, 0.); + g_return_val_if_fail (imbalance >= 0. && imbalance <= 1., 0.); + + h1 = gts_eheap_new ((GtsKeyFunc) node_move_cost1, bg); + gts_eheap_freeze (h1); + g_hash_table_foreach (bg->bg1, (GHFunc) build_bheap, h1); + gts_eheap_thaw (h1); + + h2 = gts_eheap_new ((GtsKeyFunc) node_move_cost2, bg); + gts_eheap_freeze (h2); + g_hash_table_foreach (bg->bg2, (GHFunc) build_bheap, h2); + gts_eheap_thaw (h2); + + moves = g_malloc (sizeof (GtsGNode *)*mmax); + imbalance *= gts_graph_weight (bg->g); + best_balance = fabs (gts_graph_weight (bg->g1) - gts_graph_weight (bg->g2)); + if (best_balance <= imbalance) + balanced = TRUE; + + do { + GtsGraph * g1, * g2; + GHashTable * bg1, * bg2; + gdouble cost; + + if (gts_graph_weight (bg->g1) > gts_graph_weight (bg->g2)) { + n = gts_eheap_remove_top (h1, &cost); + g1 = bg->g1; + g2 = bg->g2; + bg1 = bg->bg1; + bg2 = bg->bg2; + } + else { + n = gts_eheap_remove_top (h2, &cost); + g1 = bg->g2; + g2 = bg->g1; + bg1 = bg->bg2; + bg2 = bg->bg1; + } + if (n) { + gdouble balance; + + GTS_OBJECT (n)->reserved = n; + gts_container_add (GTS_CONTAINER (g2), GTS_CONTAINEE (n)); + gts_container_remove (GTS_CONTAINER (g1), GTS_CONTAINEE (n)); + g_hash_table_remove (bg1, n); + if (gts_gnode_degree (n, g1)) + g_hash_table_insert (bg2, n, n); + + update_neighbors (n, bg, h1, h2); + + totalcost += cost; + balance = fabs (gts_graph_weight (g1) - gts_graph_weight (g2)); + + if (!balanced && balance <= imbalance) { + bestcost = totalcost; + best_balance = balance; + balanced = TRUE; + nm = 0; + } + else if (totalcost < bestcost && + (balance < best_balance || balance <= imbalance)) { + bestcost = totalcost; + best_balance = balance; + nm = 0; + } + else if (totalcost == bestcost && balance < best_balance) { + best_balance = balance; + nm = 0; + } + else + moves[nm++] = n; + } + } while (n && nm < mmax); + + gts_container_foreach (GTS_CONTAINER (bg->g), + (GtsFunc) gts_object_reset_reserved, NULL); + gts_eheap_destroy (h1); + gts_eheap_destroy (h2); + + /* undo last nm moves */ + for (i = 0; i < nm; i++) { + GtsGNode * n = moves[i]; + GtsGraph * g1, * g2; + GHashTable * bg1, * bg2; + + if (gts_containee_is_contained (GTS_CONTAINEE (n), + GTS_CONTAINER (bg->g1))) { + g1 = bg->g1; + g2 = bg->g2; + bg1 = bg->bg1; + bg2 = bg->bg2; + } + else { + g1 = bg->g2; + g2 = bg->g1; + bg1 = bg->bg2; + bg2 = bg->bg1; + } + + gts_container_add (GTS_CONTAINER (g2), GTS_CONTAINEE (n)); + gts_container_remove (GTS_CONTAINER (g1), GTS_CONTAINEE (n)); + g_hash_table_remove (bg1, n); + if (gts_gnode_degree (n, g1)) + g_hash_table_insert (bg2, n, n); + + update_neighbors (n, bg, NULL, NULL); + } + g_free (moves); + + return bestcost; +} + +/* Multilevel partitioning */ + +static void bisection_children (GtsGNodeSplit * ns, GtsGraphBisection * bg) +{ + GtsGraph * g, * g1; + GHashTable * bbg; + GtsGNode * n1 = GTS_GNODE_SPLIT_N1 (ns); + GtsGNode * n2 = GTS_GNODE_SPLIT_N2 (ns); + + if (gts_containee_is_contained (GTS_CONTAINEE (ns->n), + GTS_CONTAINER (bg->g1))) { + g = bg->g1; + g1 = bg->g2; + bbg = bg->bg1; + } + else { + g = bg->g2; + g1 = bg->g1; + bbg = bg->bg2; + } + + gts_allow_floating_gnodes = TRUE; + gts_container_remove (GTS_CONTAINER (g), GTS_CONTAINEE (ns->n)); + gts_allow_floating_gnodes = FALSE; + gts_container_add (GTS_CONTAINER (g), GTS_CONTAINEE (n1)); + gts_container_add (GTS_CONTAINER (g), GTS_CONTAINEE (n2)); + + if (g_hash_table_lookup (bbg, ns->n)) { + g_hash_table_remove (bbg, ns->n); + if (gts_gnode_degree (n1, g1) > 0) + g_hash_table_insert (bbg, n1, n1); + if (gts_gnode_degree (n2, g1) > 0) + g_hash_table_insert (bbg, n2, n2); + } +} + +/** + * gts_graph_bisection_new: + * @wg: a #GtsWGraph. + * @ntry: the number of tries for the graph growing algorithm. + * @mmax: the number of unsucessful moves for the refinement algorithm. + * @nmin: the minimum number of nodes of the coarsest graph. + * @imbalance: the maximum relative imbalance allowed between the + * weights of both halves of the partition. + * + * An implementation of a multilevel bisection algorithm as presented + * in Karypis and Kumar (1997). A multilevel hierarchy of graphs is + * created using the #GtsPGraph object. The bisection of the coarsest + * graph is created using the gts_graph_ggg_bisection() function. The + * graph is then uncoarsened using gts_pgraph_down() and at each level + * the bisection is refined using gts_graph_bisection_bkl_refine(). + * + * Returns: a new #GtsGraphBisection of @wg. + */ +GtsGraphBisection * gts_graph_bisection_new (GtsWGraph * wg, + guint ntry, + guint mmax, + guint nmin, + gfloat imbalance) +{ + GtsGraph * g; + GtsPGraph * pg; + GtsGraphBisection * bg; + gdouble cost; + + g_return_val_if_fail (wg != NULL, NULL); + + g = GTS_GRAPH (wg); + pg = gts_pgraph_new (gts_pgraph_class (), g, + gts_gnode_split_class (), + gts_wgnode_class (), + gts_wgedge_class (), + nmin); + + bg = gts_graph_ggg_bisection (g, ntry); +#ifdef DEBUG + fprintf (stderr, "before size: %5d weight: %5g cuts: %5d cweight: %5g\n", + gts_container_size (GTS_CONTAINER (bg->g1)), + gts_graph_weight (bg->g1), + gts_graph_edges_cut (bg->g1), + gts_graph_edges_cut_weight (bg->g1)); + g_assert (gts_graph_bisection_check (bg)); +#endif + while ((cost = gts_graph_bisection_bkl_refine (bg, mmax, imbalance))) { +#ifdef DEBUG + fprintf (stderr, " cost: %g\n", cost); + g_assert (gts_graph_bisection_check (bg)); +#endif + } +#ifdef DEBUG + fprintf (stderr, "after size: %5d weight: %5g cuts: %5d cweight: %5g\n", + gts_container_size (GTS_CONTAINER (bg->g1)), + gts_graph_weight (bg->g1), + gts_graph_edges_cut (bg->g1), + gts_graph_edges_cut_weight (bg->g1)); +#endif + while (gts_pgraph_down (pg, (GtsFunc) bisection_children, bg)) { +#ifdef DEBUG + fprintf (stderr, "before size: %5d weight: %5g cuts: %5d cweight: %5g\n", + gts_container_size (GTS_CONTAINER (bg->g1)), + gts_graph_weight (bg->g1), + gts_graph_edges_cut (bg->g1), + gts_graph_edges_cut_weight (bg->g1)); +#endif + while ((cost = gts_graph_bisection_bkl_refine (bg, mmax, imbalance))) { +#ifdef DEBUG + fprintf (stderr, " cost: %g\n", cost); + g_assert (gts_graph_bisection_check (bg)); +#endif + } +#ifdef DEBUG + fprintf (stderr, "after size: %5d weight: %5g cuts: %5d cweight: %5g\n", + gts_container_size (GTS_CONTAINER (bg->g1)), + gts_graph_weight (bg->g1), + gts_graph_edges_cut (bg->g1), + gts_graph_edges_cut_weight (bg->g1)); +#endif + } + gts_object_destroy (GTS_OBJECT (pg)); + + return bg; +} + +/** + * gts_graph_bisection_destroy: + * @bg: a #GtsGraphBisection. + * @destroy_graphs: controls graph destruction. + * + * Frees all the memory allocated for @bg. If @destroy_graphs is %TRUE + * the graphs created by @bg are destroyed. + */ +void gts_graph_bisection_destroy (GtsGraphBisection * bg, + gboolean destroy_graphs) +{ + g_return_if_fail (bg != NULL); + + g_hash_table_destroy (bg->bg1); + g_hash_table_destroy (bg->bg2); + + if (destroy_graphs) { + gts_object_destroy (GTS_OBJECT (bg->g1)); + gts_object_destroy (GTS_OBJECT (bg->g2)); + } + + g_free (bg); +} + +static void recursive_bisection (GtsWGraph * wg, + guint np, + guint ntry, + guint mmax, + guint nmin, + gfloat imbalance, + GSList ** list) +{ + if (np == 0) + *list = g_slist_prepend (*list, wg); + else { + GtsGraphBisection * bg = + gts_graph_bisection_new (wg, ntry, mmax, nmin, imbalance); + GtsGraph * g1 = bg->g1; + GtsGraph * g2 = bg->g2; + + gts_object_destroy (GTS_OBJECT (wg)); + gts_graph_bisection_destroy (bg, FALSE); + recursive_bisection (GTS_WGRAPH (g1), np - 1, ntry, mmax, nmin, imbalance, + list); + recursive_bisection (GTS_WGRAPH (g2), np - 1, ntry, mmax, nmin, imbalance, + list); + } +} + +/** + * gts_graph_recursive_bisection: + * @wg: a #GtsWGraph. + * @n: the number of bisection levels. + * @ntry: the number of tries for the graph growing algorithm. + * @mmax: the number of unsucessful moves for the refinement algorithm. + * @nmin: the minimum number of nodes of the coarsest graph. + * @imbalance: the maximum relative imbalance allowed between the + * weights of both halves of the partition. + * + * Calls gts_graph_bisection_new() recursively in order to obtain a + * 2^@n partition of @wg. + * + * Returns: a list of 2^@n new #GtsGraph representing the partition. + */ +GSList * gts_graph_recursive_bisection (GtsWGraph * wg, + guint n, + guint ntry, + guint mmax, + guint nmin, + gfloat imbalance) +{ + GtsGraphBisection * bg; + GtsGraph * g1, * g2; + GSList * list = NULL; + + g_return_val_if_fail (wg != NULL, NULL); + g_return_val_if_fail (n > 0, NULL); + + bg = gts_graph_bisection_new (wg, ntry, mmax, nmin, imbalance); + g1 = bg->g1; + g2 = bg->g2; + gts_graph_bisection_destroy (bg, FALSE); + recursive_bisection (GTS_WGRAPH (g1), n - 1, ntry, mmax, nmin, imbalance, + &list); + recursive_bisection (GTS_WGRAPH (g2), n - 1, ntry, mmax, nmin, imbalance, + &list); + + return list; +} diff --git a/gts/pgraph.c b/gts/pgraph.c new file mode 100644 index 0000000000..6d1f6191f1 --- /dev/null +++ b/gts/pgraph.c @@ -0,0 +1,584 @@ +/* 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 "gts.h" + +/* GtsGNodeSplit */ + +static void gnode_split_destroy (GtsObject * object) +{ + GtsGNodeSplit * ns = GTS_GNODE_SPLIT (object); + + if (gts_container_size (GTS_CONTAINER (ns->n)) == 0) { + g_assert (GTS_SLIST_CONTAINEE (ns->n)->containers == NULL); + gts_object_destroy (GTS_OBJECT (ns->n)); + } + else { + /* GtsGNode * n1 = GTS_GNODE_SPLIT_N1 (ns); */ + /* GtsGNode * n2 = GTS_GNODE_SPLIT_N2 (ns); */ + + g_warning ("Memory deallocation for GtsGNodeSplit not fully implemented yet: memory leak!"); + } + + (* GTS_OBJECT_CLASS (gts_gnode_split_class ())->parent_class->destroy) + (object); +} + +static void gnode_split_class_init (GtsGNodeSplitClass * klass) +{ + GTS_OBJECT_CLASS (klass)->destroy = gnode_split_destroy; +} + +static void gnode_split_init (GtsGNodeSplit * ns) +{ + ns->n = NULL; + ns->n1 = ns->n2 = NULL; +} + +/** + * gts_gnode_split_class: + * + * Returns: the #GtsGNodeSplitClass. + */ +GtsGNodeSplitClass * gts_gnode_split_class (void) +{ + static GtsGNodeSplitClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo gnode_split_info = { + "GtsGNodeSplit", + sizeof (GtsGNodeSplit), + sizeof (GtsGNodeSplitClass), + (GtsObjectClassInitFunc) gnode_split_class_init, + (GtsObjectInitFunc) gnode_split_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (gts_object_class (), &gnode_split_info); + } + + return klass; +} + +/** + * gts_gnode_split_new: + * @klass: a #GtsGNodeSplitClass. + * @n: a #GtsGNode. + * @n1: a #GtsGNodeSplit or #GtsGNode. + * @n2: a #GtsGNodeSplit or #GtsGNode. + * + * Creates a new #GtsGNodeSplit which would collapse @n1 and @n2 into + * @n. The collapse itself is not performed. + * + * Returns: the new #GtsGNodeSplit. + */ +GtsGNodeSplit * gts_gnode_split_new (GtsGNodeSplitClass * klass, + GtsGNode * n, + GtsObject * n1, + GtsObject * n2) +{ + GtsGNodeSplit * ns; + + g_return_val_if_fail (klass != NULL, NULL); + g_return_val_if_fail (n != NULL, NULL); + g_return_val_if_fail (GTS_IS_GNODE_SPLIT (n1) || GTS_IS_GNODE (n1), NULL); + g_return_val_if_fail (GTS_IS_GNODE_SPLIT (n2) || GTS_IS_GNODE (n2), NULL); + + ns = GTS_GNODE_SPLIT (gts_object_new (GTS_OBJECT_CLASS (klass))); + ns->n = n; + ns->n1 = n1; + ns->n2 = n2; + + return ns; +} + +static void connect_edge (GtsGEdge * e, gpointer * data) +{ + GtsGNode * n = data[0]; + GtsGNode * n1 = data[1]; + GtsGNode * n2 = data[2]; + + if (GTS_OBJECT (e)->reserved || /* edge is disconnected */ + gts_gedge_connects (e, n1, n2)) + return; + if (e->n1 == n1 || e->n1 == n2) + e->n1 = n; + else if (e->n2 == n1 || e->n2 == n2) + e->n2 = n; + else + g_assert_not_reached (); + gts_container_add (GTS_CONTAINER (n), GTS_CONTAINEE (e)); +} + +/** + * gts_gnode_split_collapse: + * @ns: a #GtsGNodeSplit. + * @g: a #GtsGraph. + * @klass: a #GtsWGEdgeClass. + * + * Collapses the node split @ns. Any new edge created during the + * process will be of class @klass. + */ +void gts_gnode_split_collapse (GtsGNodeSplit * ns, + GtsGraph * g, + GtsWGEdgeClass * klass) +{ + GtsGNode * n1, * n2; + GSList * i; + gpointer data[3]; + + g_return_if_fail (ns != NULL); + g_return_if_fail (g != NULL); + g_return_if_fail (gts_container_size (GTS_CONTAINER (ns->n)) == 0); + + n1 = GTS_GNODE_SPLIT_N1 (ns); + n2 = GTS_GNODE_SPLIT_N2 (ns); + + /* look for triangles */ + i = GTS_SLIST_CONTAINER (n1)->items; + while (i) { + GtsGEdge * e13 = i->data; + GtsGNode * n3 = GTS_GNODE_NEIGHBOR (n1, e13); + if (n3 != n2) { + GSList * j = GTS_SLIST_CONTAINER (n3)->items; + while (j) { + GtsGEdge * e32 = j->data; + GSList * next = j->next; + GtsGNode * n4 = GTS_GNODE_NEIGHBOR (n3, e32); + if (n4 == n2) { /* found triangle n1 (e13) n3 (e32) n2 */ + gts_wgedge_new (klass, ns->n, n3, + gts_gedge_weight (e13) + gts_gedge_weight (e32)); + GTS_OBJECT (e13)->reserved = n3; + GTS_OBJECT (e32)->reserved = n3; + GTS_SLIST_CONTAINER (n3)->items = + g_slist_remove (GTS_SLIST_CONTAINER (n3)->items, e32); + } + j = next; + } + if (GTS_OBJECT (e13)->reserved == n3) + GTS_SLIST_CONTAINER (n3)->items = + g_slist_remove (GTS_SLIST_CONTAINER (n3)->items, e13); + } + i = i->next; + } + + /* connect edges to new node */ + data[0] = ns->n; + data[1] = n1; + data[2] = n2; + gts_container_foreach (GTS_CONTAINER (n1), (GtsFunc) connect_edge, data); + gts_container_foreach (GTS_CONTAINER (n2), (GtsFunc) connect_edge, data); + + gts_allow_floating_gnodes = TRUE; + gts_container_remove (GTS_CONTAINER (g), GTS_CONTAINEE (n1)); + gts_container_remove (GTS_CONTAINER (g), GTS_CONTAINEE (n2)); + gts_allow_floating_gnodes = FALSE; + gts_container_add (GTS_CONTAINER (g), GTS_CONTAINEE (ns->n)); +} + +static void restore_edge (GtsGEdge * e, gpointer * data) +{ + GtsGNode * n = data[0]; + GtsGNode * n1 = data[1]; + GtsGNode * n2 = data[2]; + GtsGNode * n3 = GTS_OBJECT (e)->reserved; + + if (n3) { /* e is a disconnected edge */ + GTS_OBJECT (e)->reserved = NULL; + gts_container_add (GTS_CONTAINER (n3), GTS_CONTAINEE (e)); + return; + } + + if (gts_gedge_connects (e, n1, n2)) + return; + + if (e->n1 == n) + e->n1 = n1; + else if (e->n2 == n) + e->n2 = n1; + else + g_assert_not_reached (); + GTS_SLIST_CONTAINER (n)->items = + g_slist_remove (GTS_SLIST_CONTAINER (n)->items, e); +} + +/** + * gts_gnode_split_expand: + * @ns: a #GtsGNodeSplit. + * @g: a #GtsGraph. + * + * Expands the node split ns adding the new nodes to @g. + */ +void gts_gnode_split_expand (GtsGNodeSplit * ns, + GtsGraph * g) +{ + GtsGNode * n1, * n2; + gpointer data[3]; + GSList * i; + + g_return_if_fail (ns != NULL); + g_return_if_fail (g != NULL); + g_return_if_fail (gts_containee_is_contained (GTS_CONTAINEE (ns->n), + GTS_CONTAINER (g))); + + n1 = GTS_GNODE_SPLIT_N1 (ns); + n2 = GTS_GNODE_SPLIT_N2 (ns); + + data[0] = ns->n; + data[1] = n1; + data[2] = n2; + gts_container_foreach (GTS_CONTAINER (n1), (GtsFunc) restore_edge, data); + data[1] = n2; + data[2] = n1; + gts_container_foreach (GTS_CONTAINER (n2), (GtsFunc) restore_edge, data); + + i = GTS_SLIST_CONTAINER (ns->n)->items; + while (i) { + GSList * next = i->next; + gts_container_remove (GTS_CONTAINER (ns->n), GTS_CONTAINEE (i->data)); + i = next; + } + g_assert (gts_container_size (GTS_CONTAINER (ns->n)) == 0); + + gts_allow_floating_gnodes = TRUE; + gts_container_remove (GTS_CONTAINER (g), GTS_CONTAINEE (ns->n)); + gts_allow_floating_gnodes = FALSE; + + gts_container_add (GTS_CONTAINER (g), GTS_CONTAINEE (n1)); + gts_container_add (GTS_CONTAINER (g), GTS_CONTAINEE (n2)); +} + +/* GtsPGraph */ + +static void pgraph_destroy (GtsObject * object) +{ + GtsPGraph * pg = GTS_PGRAPH (object); + guint i; + + for (i = 0; i < pg->split->len; i++) + gts_object_destroy (GTS_OBJECT (g_ptr_array_index (pg->split, i))); + g_ptr_array_free (pg->split, TRUE); + g_array_free (pg->levels, TRUE); + + (* GTS_OBJECT_CLASS (gts_pgraph_class ())->parent_class->destroy) (object); +} + +static void pgraph_class_init (GtsPGraphClass * klass) +{ + GTS_OBJECT_CLASS (klass)->destroy = pgraph_destroy; +} + +static void pgraph_init (GtsPGraph * pg) +{ + pg->g = NULL; + pg->split = g_ptr_array_new (); + pg->levels = g_array_new (FALSE, FALSE, sizeof (guint)); + pg->level = 0; + pg->split_class = gts_gnode_split_class (); + pg->edge_class = gts_wgedge_class (); + pg->pos = pg->min = 0; +} + +/** + * gts_pgraph_class: + * + * Returns: the #GtsPGraphClass. + */ +GtsPGraphClass * gts_pgraph_class (void) +{ + static GtsPGraphClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo pgraph_info = { + "GtsPGraph", + sizeof (GtsPGraph), + sizeof (GtsPGraphClass), + (GtsObjectClassInitFunc) pgraph_class_init, + (GtsObjectInitFunc) pgraph_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (gts_object_class (), &pgraph_info); + } + + return klass; +} + +static void match_neighbor (GtsGNode * n, gpointer * data) +{ + if (!GTS_OBJECT (n)->reserved) { + GtsGraph * g = data[0]; + GSList ** list = data[1]; + GSList * i = GTS_SLIST_CONTAINER (n)->items; + gfloat wmax = - G_MAXFLOAT; + GtsGEdge * emax = NULL; + + while (i) { + GtsGNode * n1 = GTS_GNODE_NEIGHBOR (n, i->data); + if (!GTS_OBJECT (n1)->reserved && + gts_gedge_weight (i->data) > wmax && + gts_containee_is_contained (GTS_CONTAINEE (n1), GTS_CONTAINER (g))) { + emax = i->data; + wmax = gts_gedge_weight (emax); + } + i = i->next; + } + if (emax) { + GtsGNode * n1 = GTS_GNODE_NEIGHBOR (n, emax); + + GTS_OBJECT (n1)->reserved = n; + GTS_OBJECT (n)->reserved = n1; + *list = g_slist_prepend (*list, emax); + } + } +} + +static GSList * maximal_matching (GtsGraph * g) +{ + GSList * list = NULL; + gpointer data[2]; + + data[0] = g; + data[1] = &list; + gts_container_foreach (GTS_CONTAINER (g), (GtsFunc) match_neighbor, data); + gts_container_foreach (GTS_CONTAINER (g), + (GtsFunc) gts_object_reset_reserved, + NULL); + + return list; +} + +/** + * gts_pgraph_new: + * @klass: a #GtsPGraphClass. + * @g: a #GtsGraph. + * @split_class: a #GtsGNodeSplitClass. + * @node_class: a #GtsWGNodeClass. + * @edge_class: a #GtsWGEdgeClass. + * @min: the minimum number of nodes. + * + * Creates a new multilevel approximation of graph @g. At each level a + * maximal matching is created using the Heavy Edge Matching (HEM) + * technique of Karypis and Kumar (1997). The newly created nodes are + * of type @node_class and their weight is set to the sum of the + * weights of their children. The newly created edges are of type + * @edge_class and their weight is set to the sum of the weight of the + * collapsed edges. The last level is reached when the maximal + * matching obtained would lead to a graph with less than @min nodes. + * + * Returns: the new #GtsPGraph containing the multilevel + * representation of @g. + */ +GtsPGraph * gts_pgraph_new (GtsPGraphClass * klass, + GtsGraph * g, + GtsGNodeSplitClass * split_class, + GtsWGNodeClass * node_class, + GtsWGEdgeClass * edge_class, + guint min) +{ + GtsPGraph * pg; + GSList * matching; + + g_return_val_if_fail (klass != NULL, NULL); + g_return_val_if_fail (g != NULL, NULL); + g_return_val_if_fail (split_class != NULL, NULL); + g_return_val_if_fail (node_class != NULL, NULL); + g_return_val_if_fail (edge_class != NULL, NULL); + + pg = GTS_PGRAPH (gts_object_new (GTS_OBJECT_CLASS (klass))); + pg->g = g; + pg->split_class = split_class; + pg->edge_class = edge_class; + + while (gts_container_size (GTS_CONTAINER (g)) > min && + (matching = maximal_matching (g))) { + GSList * i = matching; + guint size = gts_container_size (GTS_CONTAINER (g)); + + g_array_append_val (pg->levels, size); + + while (i && gts_container_size (GTS_CONTAINER (g)) > min) { + GtsGEdge * e = i->data; + GtsGNode * n = GTS_GNODE (gts_wgnode_new (node_class, + gts_gnode_weight (e->n1) + + gts_gnode_weight (e->n2))); + GtsGNodeSplit * ns = gts_gnode_split_new (split_class, n, + GTS_OBJECT (e->n1), + GTS_OBJECT (e->n2)); + gts_gnode_split_collapse (ns, g, edge_class); + g_ptr_array_add (pg->split, ns); + i = i->next; + } + g_slist_free (matching); + } + + pg->pos = pg->split->len; + pg->min = gts_container_size (GTS_CONTAINER (g)); + pg->level = pg->levels->len; + + return pg; +} + +/** + * gts_pgraph_add_node: + * @pg: a #GtsPGraph. + * + * Adds one node to the multilevel graph @pg by expanding the next + * available #GtsGNodeSplit. + * + * Returns: the expanded #GtsGNodeSplit or #NULL if all the + * #GtsGNodeSplit have already been expanded. + */ +GtsGNodeSplit * gts_pgraph_add_node (GtsPGraph * pg) +{ + GtsGNodeSplit * ns; + + g_return_val_if_fail (pg != NULL, NULL); + + if (pg->pos == 0) + return NULL; + + ns = g_ptr_array_index (pg->split, --pg->pos); + gts_gnode_split_expand (ns, pg->g); + + return ns; +} + +/** + * gts_pgraph_remove_node: + * @pg: a #GtsPGraph. + * + * Removes one node from the multilevel graph @pg by collapsing the + * first available #GtsGNodeSplit. + * + * Returns: the collapsed #GtsGNodeSplit or %NULL if all the + * #GtsGNodeSplit have already been collapsed. + */ +GtsGNodeSplit * gts_pgraph_remove_node (GtsPGraph * pg) +{ + GtsGNodeSplit * ns; + + g_return_val_if_fail (pg != NULL, NULL); + + if (pg->pos == pg->split->len) + return NULL; + + ns = g_ptr_array_index (pg->split, pg->pos++); + gts_gnode_split_collapse (ns, pg->g, pg->edge_class); + + return ns; +} + +/** + * gts_pgraph_max_node_number: + * @pg: a #GtsPGraph. + * + * Returns: the maximum number of nodes of @pg i.e. the number of + * nodes if all the #GtsGNodeSplit were expanded. + */ +guint gts_pgraph_max_node_number (GtsPGraph * pg) +{ + g_return_val_if_fail (pg != NULL, 0); + + return pg->min + pg->split->len; +} + +/** + * gts_pgraph_min_node_number: + * @pg: a #GtsPGraph. + * + * Returns: the minimum number of nodes of @pg i.e. the number of + * nodes if all the #GtsGNodeSplit were collapsed. + */ +guint gts_pgraph_min_node_number (GtsPGraph * pg) +{ + g_return_val_if_fail (pg != NULL, 0); + + return pg->min; +} + +/** + * gts_pgraph_set_node_number: + * @pg: a #GtsPGraph. + * @n: a number of nodes. + * + * Performs the required number of collapses or expansions to set the + * number of nodes of @pg to @n. + */ +void gts_pgraph_set_node_number (GtsPGraph * pg, guint n) +{ + g_return_if_fail (pg != NULL); + + n = pg->min + pg->split->len - n; + while (pg->pos > n && gts_pgraph_add_node (pg)) + ; + while (pg->pos < n && gts_pgraph_remove_node (pg)) + ; +} + +/** + * gts_pgraph_get_node_number: + * @pg: a #GtsPGraph. + * + * Returns: the current number of nodes of @pg. + */ +guint gts_pgraph_get_node_number (GtsPGraph * pg) +{ + g_return_val_if_fail (pg != NULL, 0); + + return pg->min + pg->split->len - pg->pos; +} + +/** + * gts_pgraph_down: + * @pg: a #GtsPGraph. + * @func: a #GtsFunc or %NULL. + * @data: user data to pass to @func. + * + * Performs the required number of expansions to go from the current + * level to the level immediately below. + * + * If @func is not %NULL, it is called after each #GtsGNodeSplit has + * been expanded. + * + * Returns: %FALSE if it is not possible to go down one level, %TRUE + * otherwise. + */ +gboolean gts_pgraph_down (GtsPGraph * pg, + GtsFunc func, + gpointer data) +{ + guint size; + + g_return_val_if_fail (pg != NULL, FALSE); + + if (pg->level == 0) + return FALSE; + + size = g_array_index (pg->levels, guint, --(pg->level)); + while (gts_container_size (GTS_CONTAINER (pg->g)) < size) { + GtsGNodeSplit * ns = gts_pgraph_add_node (pg); + + g_assert (ns); + if (func) + (* func) (ns, data); + } + return TRUE; +} + diff --git a/gts/point.c b/gts/point.c new file mode 100644 index 0000000000..42fce6948f --- /dev/null +++ b/gts/point.c @@ -0,0 +1,986 @@ +/* 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" +#include "gts-private.h" +#include "predicates.h" + +static void point_read (GtsObject ** o, GtsFile * f) +{ + GtsPoint * p = GTS_POINT (*o); + + if (GTS_POINT_CLASS ((*o)->klass)->binary) { + if (gts_file_read (f, &(p->x), sizeof (gdouble), 1) != 1) { + gts_file_error (f, "expecting a binary number (x coordinate)"); + return; + } + if (gts_file_read (f, &(p->y), sizeof (gdouble), 1) != 1) { + gts_file_error (f, "expecting a binary number (y coordinate)"); + return; + } + if (gts_file_read (f, &(p->z), sizeof (gdouble), 1) != 1) { + gts_file_error (f, "expecting a binary number (z coordinate)"); + return; + } + } + else { + if (f->type != GTS_INT && f->type != GTS_FLOAT) { + gts_file_error (f, "expecting a number (x coordinate)"); + return; + } + p->x = atof (f->token->str); + + gts_file_next_token (f); + if (f->type != GTS_INT && f->type != GTS_FLOAT) { + gts_file_error (f, "expecting a number (y coordinate)"); + return; + } + p->y = atof (f->token->str); + + gts_file_next_token (f); + if (f->type != GTS_INT && f->type != GTS_FLOAT) { + gts_file_error (f, "expecting a number (z coordinate)"); + return; + } + p->z = atof (f->token->str); + + gts_file_next_token (f); + } +} + +static void point_write (GtsObject * o, FILE * fptr) +{ + GtsPoint * p = GTS_POINT (o); + + if (GTS_POINT_CLASS ((o)->klass)->binary) { + fwrite (&(p->x), sizeof (gdouble), 1, fptr); + fwrite (&(p->y), sizeof (gdouble), 1, fptr); + fwrite (&(p->z), sizeof (gdouble), 1, fptr); + } + else + fprintf (fptr, "%.10g %.10g %.10g", p->x, p->y, p->z); +} + +static void point_class_init (GtsObjectClass * klass) +{ + klass->read = point_read; + klass->write = point_write; +} + +/** + * gts_point_class: + * + * Returns: the #GtsPointClass. + */ +GtsPointClass * gts_point_class (void) +{ + static GtsPointClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo point_info = { + "GtsPoint", + sizeof (GtsPoint), + sizeof (GtsPointClass), + (GtsObjectClassInitFunc) point_class_init, + (GtsObjectInitFunc) NULL, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (gts_object_class (), + &point_info); + } + + return klass; +} + +/** + * gts_point_new: + * @klass: a #GtsPointClass. + * @x: the x-coordinate. + * @y: the y-coordinate. + * @z: the z-coordinate. + * + * Returns: a new #GtsPoint. + */ +GtsPoint * gts_point_new (GtsPointClass * klass, + gdouble x, gdouble y, gdouble z) +{ + GtsPoint * p; + + p = GTS_POINT (gts_object_new (GTS_OBJECT_CLASS (klass))); + p->x = x; + p->y = y; + p->z = z; + + return p; +} + +/** + * gts_point_set: + * @p: a #GtsPoint. + * @x: the x-coordinate. + * @y: the y-coordinate. + * @z: the z-coordinate. + * + * Sets the coordinates of @p. + */ +void gts_point_set (GtsPoint * p, gdouble x, gdouble y, gdouble z) +{ + g_return_if_fail (p != NULL); + + p->x = x; + p->y = y; + p->z = z; +} + +/** + * gts_point_distance: + * @p1: a #GtsPoint. + * @p2: another #GtsPoint. + * + * Returns: the Euclidean distance between @p1 and @p2. + */ +gdouble gts_point_distance (GtsPoint * p1, GtsPoint * p2) +{ + g_return_val_if_fail (p1 != NULL && p2 != NULL, 0.0); + + return sqrt ((p1->x - p2->x)*(p1->x - p2->x) + + (p1->y - p2->y)*(p1->y - p2->y) + + (p1->z - p2->z)*(p1->z - p2->z)); +} + +/** + * gts_point_distance2: + * @p1: a #GtsPoint. + * @p2: another #GtsPoint. + * + * Returns: the square of the Euclidean distance between @p1 and @p2. + */ +gdouble gts_point_distance2 (GtsPoint * p1, GtsPoint * p2) +{ + g_return_val_if_fail (p1 != NULL && p2 != NULL, 0.0); + + return + (p1->x - p2->x)*(p1->x - p2->x) + + (p1->y - p2->y)*(p1->y - p2->y) + + (p1->z - p2->z)*(p1->z - p2->z); +} + +/** + * gts_point_orientation_3d: + * @p1: a #GtsPoint. + * @p2: a #GtsPoint. + * @p3: a #GtsPoint. + * @p4: a #GtsPoint. + * + * Checks if @p4 lies above, below or on the plane passing through the + * points @p1, @p2 and @p3. Below is defined so that @p1, @p2 and @p3 + * appear in counterclockwise order when viewed from above the + * plane. The returned value is an approximation of six times the + * signed volume of the tetrahedron defined by the four points. This + * function uses adaptive floating point arithmetic and is + * consequently geometrically robust. + * + * Returns: a positive value if @p4 lies below, a negative value if + * @p4 lies above the plane, zero if the four points are coplanar. + */ +gdouble gts_point_orientation_3d (GtsPoint * p1, + GtsPoint * p2, + GtsPoint * p3, + GtsPoint * p4) +{ + g_return_val_if_fail (p1 != NULL && p2 != NULL && + p3 != NULL && p4 != NULL, 0.0); + return orient3d ((gdouble *) &p1->x, + (gdouble *) &p2->x, + (gdouble *) &p3->x, + (gdouble *) &p4->x); +} + +/** + * gts_point_is_in_triangle: + * @p: a #GtsPoint. + * @t: a #GtsTriangle. + * + * Tests if the planar projection (x, y) of @p is inside, outside or + * on the boundary of the planar projection of @t. This function is + * geometrically robust. + * + * Returns: %GTS_IN if @p is inside @t, %GTS_ON if @p is on the boundary of + * @t, %GTS_OUT otherwise. + */ +GtsIntersect gts_point_is_in_triangle (GtsPoint * p, GtsTriangle * t) +{ + GtsVertex * v1, * v2, * v3; + gdouble d1, d2, d3; + + g_return_val_if_fail (p != NULL && t != NULL, FALSE); + + gts_triangle_vertices (t, &v1, &v2, &v3); + + d1 = gts_point_orientation (GTS_POINT (v1), GTS_POINT (v2), p); + if (d1 < 0.0) + return GTS_OUT; + d2 = gts_point_orientation (GTS_POINT (v2), GTS_POINT (v3), p); + if (d2 < 0.0) + return GTS_OUT; + d3 = gts_point_orientation (GTS_POINT (v3), GTS_POINT (v1), p); + if (d3 < 0.0) + return GTS_OUT; + if (d1 == 0.0 || d2 == 0.0 || d3 == 0.0) + return GTS_ON; + return GTS_IN; +} + +/** + * gts_point_in_triangle_circle: + * @p: a #GtsPoint. + * @t: a #GtsTriangle. + * + * Tests if the planar projection (x, y) of @p is inside or outside + * the circumcircle of the planar projection of @t. This function is + * geometrically robust. + * + * Returns: a positive number if @p lies inside, + * a negative number if @p lies outside and zero if @p lies on + * the circumcircle of @t. + */ +gdouble gts_point_in_triangle_circle (GtsPoint * p, GtsTriangle * t) +{ + GtsPoint * p1, * p2, * p3; + + g_return_val_if_fail (p != NULL && t != NULL, 0.0); + + gts_triangle_vertices (t, + (GtsVertex **) &p1, + (GtsVertex **) &p2, + (GtsVertex **) &p3); + + return incircle ((gdouble *) &p1->x, + (gdouble *) &p2->x, + (gdouble *) &p3->x, + (gdouble *) &p->x); +} + +/** + * gts_point_in_circle: + * @p: a #GtsPoint. + * @p1: a #GtsPoint. + * @p2: a #GtsPoint. + * @p3: a #GtsPoint. + * + * Tests if the planar projection (x, y) of @p is inside or outside the + * circle defined by the planar projection of @p1, @p2 and @p3. + * + * Returns: a positive number if @p lies inside, + * a negative number if @p lies outside and zero if @p lies on + * the circle. + */ +gdouble gts_point_in_circle (GtsPoint * p, + GtsPoint * p1, GtsPoint * p2, GtsPoint * p3) +{ + g_return_val_if_fail (p != NULL && p1 != NULL && p2 != NULL && p3 != NULL, + 0.0); + + return incircle ((gdouble *) &p1->x, + (gdouble *) &p2->x, + (gdouble *) &p3->x, + (gdouble *) &p->x); +} + +/** + * gts_point_in_sphere: + * @p: a #GtsPoint. + * @p1: a #GtsPoint. + * @p2: a #GtsPoint. + * @p3: a #GtsPoint. + * @p4: a #GtsPoint. + * + * Tests if @p is inside or outside the sphere defined by @p1, @p2, + * @p3 and @p4. + * + * Returns: a positive number if @p lies inside, + * a negative number if @p lies outside and zero if @p lies on + * the sphere. + */ +gdouble gts_point_in_sphere (GtsPoint * p, + GtsPoint * p1, GtsPoint * p2, GtsPoint * p3, GtsPoint * p4) +{ + g_return_val_if_fail (p != NULL && p1 != NULL && p2 != NULL && p3 != NULL && p4 != NULL, + 0.0); + + return insphere ((gdouble *) &p1->x, + (gdouble *) &p2->x, + (gdouble *) &p3->x, + (gdouble *) &p4->x, + (gdouble *) &p->x); +} + +/** + * gts_point_segment_distance2: + * @p: a #GtsPoint. + * @s: a #GtsSegment. + * + * Returns: the square of the minimun Euclidean distance between @p and @s. + */ +gdouble gts_point_segment_distance2 (GtsPoint * p, GtsSegment * s) +{ + gdouble t, ns2, x, y, z; + GtsPoint * p1, * p2; + + g_return_val_if_fail (p != NULL, 0.0); + g_return_val_if_fail (s != NULL, 0.0); + + p1 = GTS_POINT (s->v1); + p2 = GTS_POINT (s->v2); + ns2 = gts_point_distance2 (p1, p2); + if (ns2 == 0.0) + return gts_point_distance2 (p, p1); + t = ((p2->x - p1->x)*(p->x - p1->x) + + (p2->y - p1->y)*(p->y - p1->y) + + (p2->z - p1->z)*(p->z - p1->z))/ns2; + if (t > 1.0) + return gts_point_distance2 (p, p2); + if (t < 0.0) + return gts_point_distance2 (p, p1); + x = (1. - t)*p1->x + t*p2->x - p->x; + y = (1. - t)*p1->y + t*p2->y - p->y; + z = (1. - t)*p1->z + t*p2->z - p->z; + return x*x + y*y + z*z; +} + +/** + * gts_point_segment_distance: + * @p: a #GtsPoint. + * @s: a #GtsSegment. + * + * Returns: the minimun Euclidean distance between @p and @s. + */ +gdouble gts_point_segment_distance (GtsPoint * p, GtsSegment * s) +{ + g_return_val_if_fail (p != NULL, 0.0); + g_return_val_if_fail (s != NULL, 0.0); + + return sqrt (gts_point_segment_distance2 (p, s)); +} + +/** + * gts_point_segment_closest: + * @p: a #GtsPoint. + * @s: a #GtsSegment. + * @closest: a #GtsPoint. + * + * Set the coordinates of @closest to the coordinates of the point belonging + * to @s closest to @p. + */ +void gts_point_segment_closest (GtsPoint * p, + GtsSegment * s, + GtsPoint * closest) +{ + gdouble t, ns2; + GtsPoint * p1, * p2; + + g_return_if_fail (p != NULL); + g_return_if_fail (s != NULL); + g_return_if_fail (closest != NULL); + + p1 = GTS_POINT (s->v1); + p2 = GTS_POINT (s->v2); + ns2 = gts_point_distance2 (p1, p2); + + if (ns2 == 0.0) { + gts_point_set (closest, p1->x, p1->y, p1->z); + return; + } + + t = ((p2->x - p1->x)*(p->x - p1->x) + + (p2->y - p1->y)*(p->y - p1->y) + + (p2->z - p1->z)*(p->z - p1->z))/ns2; + + if (t > 1.0) + gts_point_set (closest, p2->x, p2->y, p2->z); + else if (t < 0.0) + gts_point_set (closest, p1->x, p1->y, p1->z); + else + gts_point_set (closest, + (1. - t)*p1->x + t*p2->x, + (1. - t)*p1->y + t*p2->y, + (1. - t)*p1->z + t*p2->z); +} + +/** + * gts_point_triangle_distance2: + * @p: a #GtsPoint. + * @t: a #GtsTriangle. + * + * Returns: the square of the minimun Euclidean distance between @p and @t. + */ +gdouble gts_point_triangle_distance2 (GtsPoint * p, GtsTriangle * t) +{ + GtsPoint * p1, * p2, * p3; + GtsEdge * e1, * e2, * e3; + GtsVector p1p2, p1p3, pp1; + gdouble A, B, C, D, E, det; + gdouble t1, t2; + gdouble x, y, z; + + g_return_val_if_fail (p != NULL, 0.0); + g_return_val_if_fail (t != NULL, 0.0); + + gts_triangle_vertices_edges (t, NULL, + (GtsVertex **) &p1, + (GtsVertex **) &p2, + (GtsVertex **) &p3, + &e1, &e2, &e3); + + gts_vector_init (p1p2, p1, p2); + gts_vector_init (p1p3, p1, p3); + gts_vector_init (pp1, p, p1); + + B = gts_vector_scalar (p1p3, p1p2); + E = gts_vector_scalar (p1p2, p1p2); + C = gts_vector_scalar (p1p3, p1p3); + + det = B*B - E*C; + if (det == 0.) { /* p1p2 and p1p3 are colinear */ + gdouble d1 = gts_point_segment_distance2 (p, GTS_SEGMENT (e1)); + gdouble d2 = gts_point_segment_distance2 (p, GTS_SEGMENT (e3)); + if (d1 < d2) + return d1; + return d2; + } + + A = gts_vector_scalar (p1p3, pp1); + D = gts_vector_scalar (p1p2, pp1); + + t1 = (D*C - A*B)/det; + t2 = (A*E - D*B)/det; + + if (t1 < 0.) + return gts_point_segment_distance2 (p, GTS_SEGMENT (e3)); + if (t2 < 0.) + return gts_point_segment_distance2 (p, GTS_SEGMENT (e1)); + if (t1 + t2 > 1.) + return gts_point_segment_distance2 (p, GTS_SEGMENT (e2)); + + x = pp1[0] + t1*p1p2[0] + t2*p1p3[0]; + y = pp1[1] + t1*p1p2[1] + t2*p1p3[1]; + z = pp1[2] + t1*p1p2[2] + t2*p1p3[2]; + + return x*x + y*y + z*z; +} + +/** + * gts_point_triangle_distance: + * @p: a #GtsPoint. + * @t: a #GtsTriangle. + * + * Returns: the minimun Euclidean distance between @p and @t. + */ +gdouble gts_point_triangle_distance (GtsPoint * p, GtsTriangle * t) +{ + g_return_val_if_fail (p != NULL, 0.0); + g_return_val_if_fail (t != NULL, 0.0); + + return sqrt (gts_point_triangle_distance2 (p, t)); +} + +/** + * gts_point_triangle_closest: + * @p: a #GtsPoint. + * @t: a #GtsTriangle. + * @closest: a #GtsPoint. + * + * Set the coordinates of @closest to those of the point belonging to @t and + * closest to @p. + */ +void gts_point_triangle_closest (GtsPoint * p, + GtsTriangle * t, + GtsPoint * closest) +{ + GtsPoint * p1, * p2, * p3; + GtsEdge * e1, * e2, * e3; + GtsVector p1p2, p1p3, pp1; + gdouble A, B, C, D, E, det; + gdouble t1, t2; + + g_return_if_fail (p != NULL); + g_return_if_fail (t != NULL); + g_return_if_fail (closest != NULL); + + gts_triangle_vertices_edges (t, NULL, + (GtsVertex **) &p1, + (GtsVertex **) &p2, + (GtsVertex **) &p3, + &e1, &e2, &e3); + + gts_vector_init (p1p2, p1, p2); + gts_vector_init (p1p3, p1, p3); + gts_vector_init (pp1, p, p1); + + B = gts_vector_scalar (p1p3, p1p2); + E = gts_vector_scalar (p1p2, p1p2); + C = gts_vector_scalar (p1p3, p1p3); + + det = B*B - E*C; + if (det == 0.) { /* p1p2 and p1p3 are colinear */ + GtsPoint * cp = + GTS_POINT (gts_object_new (GTS_OBJECT_CLASS (gts_point_class ()))); + gts_point_segment_closest (p, GTS_SEGMENT (e1), cp); + gts_point_segment_closest (p, GTS_SEGMENT (e3), closest); + + if (gts_point_distance2 (cp, p) < gts_point_distance2 (closest, p)) + gts_point_set (closest, cp->x, cp->y, cp->z); + gts_object_destroy (GTS_OBJECT (cp)); + return; + } + + A = gts_vector_scalar (p1p3, pp1); + D = gts_vector_scalar (p1p2, pp1); + + t1 = (D*C - A*B)/det; + t2 = (A*E - D*B)/det; + + if (t1 < 0.) + gts_point_segment_closest (p, GTS_SEGMENT (e3), closest); + else if (t2 < 0.) + gts_point_segment_closest (p, GTS_SEGMENT (e1), closest); + else if (t1 + t2 > 1.) + gts_point_segment_closest (p, GTS_SEGMENT (e2), closest); + else + gts_point_set (closest, + p1->x + t1*p1p2[0] + t2*p1p3[0], + p1->y + t1*p1p2[1] + t2*p1p3[1], + p1->z + t1*p1p2[2] + t2*p1p3[2]); +} + +/** + * gts_segment_triangle_intersection: + * @s: a #GtsSegment. + * @t: a #GtsTriangle. + * @boundary: if %TRUE, the boundary of @t is taken into account. + * @klass: a #GtsPointClass to be used for the new point. + * + * Checks if @s intersects @t. If this is the case, creates a new + * point pi intersection of @s with @t. + * + * This function is geometrically robust in the sense that it will not + * return a point if @s and @t do not intersect and will return a + * point if @s and @t do intersect. However, the point coordinates are + * subject to round-off errors. + * + * Note that this function will not return any point if @s is contained in + * the plane defined by @t. + * + * Returns: a summit of @t (if @boundary is set to %TRUE), one of the endpoints + * of @s or a new #GtsPoint, intersection of @s with @t or %NULL if @s + * and @t don't intersect. + */ +GtsPoint * gts_segment_triangle_intersection (GtsSegment * s, + GtsTriangle * t, + gboolean boundary, + GtsPointClass * klass) +{ + GtsPoint * A, * B, * C, * D, * E, * I; + gdouble ABCE, ABCD, ADCE, ABDE, BCDE; + gdouble c; + + g_return_val_if_fail (s != NULL, NULL); + g_return_val_if_fail (t != NULL, NULL); + g_return_val_if_fail (klass != NULL, NULL); + + A = GTS_POINT (GTS_SEGMENT (t->e1)->v1); + B = GTS_POINT (GTS_SEGMENT (t->e1)->v2); + C = GTS_POINT (gts_triangle_vertex (t)); + D = GTS_POINT (s->v1); + E = GTS_POINT (s->v2); + + ABCE = gts_point_orientation_3d (A, B, C, E); + ABCD = gts_point_orientation_3d (A, B, C, D); + if (ABCE < 0.0 || ABCD > 0.0) { + GtsPoint * tmpp; + gdouble tmp; + tmpp = E; E = D; D = tmpp; + tmp = ABCE; ABCE = ABCD; ABCD = tmp; + } + if (ABCE < 0.0 || ABCD > 0.0) + return NULL; + ADCE = gts_point_orientation_3d (A, D, C, E); + if ((boundary && ADCE < 0.) || (!boundary && ADCE <= 0.)) + return NULL; + ABDE = gts_point_orientation_3d (A, B, D, E); + if ((boundary && ABDE < 0.) || (!boundary && ABDE <= 0.)) + return NULL; + BCDE = gts_point_orientation_3d (B, C, D, E); + if ((boundary && BCDE < 0.) || (!boundary && BCDE <= 0.)) + return NULL; + if (ABCE == 0.0) { + if (ABCD == 0.0) + /* s is contained in the plane defined by t*/ + return NULL; + return E; + } + if (ABCD == 0.0) + return D; + if (boundary) { /* corners of @t */ + if (ABDE == 0.) { + if (ADCE == 0.) + return A; + if (BCDE == 0.) + return B; + } + else if (BCDE == 0. && ADCE == 0.) + return C; + } + c = ABCE/(ABCE - ABCD); + I = GTS_POINT (gts_object_new (GTS_OBJECT_CLASS (klass))); + gts_point_set (I, + E->x + c*(D->x - E->x), + E->y + c*(D->y - E->y), + E->z + c*(D->z - E->z)); + return I; +} + +/** + * gts_point_transform: + * @p: a #GtsPoint. + * @m: the #GtsMatrix representing the transformation to + * apply to the coordinates of @p. + * + * Transform the coordinates of @p according to @m. (p[] becomes m[][].p[]). + */ +void gts_point_transform (GtsPoint * p, GtsMatrix * m) +{ + gdouble x, y, z; + g_return_if_fail (p != NULL && m != NULL); + x = m[0][0]*p->x + m[0][1]*p->y + m[0][2]*p->z + m[0][3]; + y = m[1][0]*p->x + m[1][1]*p->y + m[1][2]*p->z + m[1][3]; + z = m[2][0]*p->x + m[2][1]*p->y + m[2][2]*p->z + m[2][3]; + p->x = x; p->y = y; p->z = z; +} + +/** + * gts_point_orientation: + * @p1: a #GtsPoint. + * @p2: a #GtsPoint. + * @p3: a #GtsPoint. + * + * Checks for orientation of the projection of three points on the + * (x,y) plane. The result is also an approximation of twice the + * signed area of the triangle defined by the three points. This + * function uses adaptive floating point arithmetic and is + * consequently geometrically robust. + * + * Returns: a positive value if @p1, @p2 and @p3 appear in + * counterclockwise order, a negative value if they appear in + * clockwise order and zero if they are colinear. + */ +gdouble gts_point_orientation (GtsPoint * p1, GtsPoint * p2, GtsPoint * p3) +{ + g_return_val_if_fail (p1 != NULL && p2 != NULL && p3 != NULL, 0.0); + + return orient2d ((gdouble *) &p1->x, + (gdouble *) &p2->x, + (gdouble *) &p3->x); +} + +static gboolean ray_intersects_triangle (GtsPoint * D, GtsPoint * E, + GtsTriangle * t) +{ + GtsPoint * A, * B, * C; + gint ABCE, ABCD, ADCE, ABDE, BCDE; + + gts_triangle_vertices (t, (GtsVertex **) &A, + (GtsVertex **) &B, + (GtsVertex **) &C); + + ABCE = gts_point_orientation_3d_sos (A, B, C, E); + ABCD = gts_point_orientation_3d_sos (A, B, C, D); + if (ABCE < 0 || ABCD > 0) { + GtsPoint * tmpp; + gint tmp; + + tmpp = E; E = D; D = tmpp; + tmp = ABCE; ABCE = ABCD; ABCD = tmp; + } + if (ABCE < 0 || ABCD > 0) + return FALSE; + ADCE = gts_point_orientation_3d_sos (A, D, C, E); + if (ADCE < 0) + return FALSE; + ABDE = gts_point_orientation_3d_sos (A, B, D, E); + if (ABDE < 0) + return FALSE; + BCDE = gts_point_orientation_3d_sos (B, C, D, E); + if (BCDE < 0) + return FALSE; + return TRUE; +} + +/** + * gts_point_is_inside_surface: + * @p: a #GtsPoint. + * @tree: a bounding box tree of the faces of a closed, orientable + * surface (see gts_bb_tree_surface()). + * @is_open: %TRUE if the surface defined by @tree is "open" i.e. its volume + * is negative, %FALSE otherwise. + * + * Returns: %TRUE if @p is inside the surface defined by @tree, %FALSE + * otherwise. + */ +gboolean gts_point_is_inside_surface (GtsPoint * p, + GNode * tree, + gboolean is_open) +{ + GSList * list, * i; + guint nc = 0; + GtsPoint * p1; + GtsBBox * bb; + + g_return_val_if_fail (p != NULL, FALSE); + g_return_val_if_fail (tree != NULL, FALSE); + + bb = tree->data; + p1 = gts_point_new (gts_point_class (), bb->x2 + fabs (bb->x2)/10., p->y, p->z); + i = list = gts_bb_tree_stabbed (tree, p); + while (i) { + GtsTriangle * t = GTS_TRIANGLE (GTS_BBOX (i->data)->bounded); + + if (ray_intersects_triangle (p, p1, t)) + nc++; + i = i->next; + } + g_slist_free (list); + gts_object_destroy (GTS_OBJECT (p1)); + + return is_open ? (nc % 2 == 0) : (nc % 2 != 0); +} + +#define SIGN(x) ((x) > 0. ? 1 : -1) +#define ORIENT1D(a,b) ((a) > (b) ? 1 : (a) < (b) ? -1 : 0) + +static gint sortp (gpointer * p, guint n) +{ + gint sign = 1; + guint i, j; + + for (i = 0; i < n - 1; i++) + for (j = 0; j < n - 1 - i; j++) + if (GPOINTER_TO_UINT (p[j+1]) < GPOINTER_TO_UINT (p[j])) { + gpointer tmp = p[j]; + + p[j] = p[j+1]; + p[j+1] = tmp; + sign = - sign; + } + return sign; +} + +/** + * gts_point_orientation_3d_sos: + * @p1: a #GtsPoint. + * @p2: a #GtsPoint. + * @p3: a #GtsPoint. + * @p4: a #GtsPoint. + * + * Checks if @p4 lies above or below the plane passing through the + * points @p1, @p2 and @p3. Below is defined so that @p1, @p2 and @p3 + * appear in counterclockwise order when viewed from above the + * plane. This function uses adaptive floating point arithmetic and is + * consequently geometrically robust. + * + * Simulation of Simplicity (SoS) is used to break ties when the + * orientation is degenerate (i.e. @p4 lies on the plane defined by + * @p1, @p2 and @p3). + * + * Returns: +1 if @p4 lies below, -1 if @p4 lies above the plane. + */ +gint gts_point_orientation_3d_sos (GtsPoint * p1, + GtsPoint * p2, + GtsPoint * p3, + GtsPoint * p4) +{ + gdouble o; + + g_return_val_if_fail (p1 != NULL && p2 != NULL && + p3 != NULL && p4 != NULL, 0); + + o = orient3d ((gdouble *) &p1->x, + (gdouble *) &p2->x, + (gdouble *) &p3->x, + (gdouble *) &p4->x); + if (o != 0.) + return SIGN (o); + else { + GtsPoint * p[4]; + gdouble a[2], b[2], c[2]; + gint sign; + + p[0] = p1; p[1] = p2; p[2] = p3; p[3] = p4; + sign = sortp ((gpointer *) p, 4); + + /* epsilon^1/8 */ + a[0] = p[1]->x; a[1] = p[1]->y; + b[0] = p[2]->x; b[1] = p[2]->y; + c[0] = p[3]->x; c[1] = p[3]->y; + o = orient2d (a, b, c); + if (o != 0.) + return SIGN (o)*sign; + + /* epsilon^1/4 */ + a[0] = p[1]->x; a[1] = p[1]->z; + b[0] = p[2]->x; b[1] = p[2]->z; + c[0] = p[3]->x; c[1] = p[3]->z; + o = orient2d (a, b, c); + if (o != 0.) + return - SIGN (o)*sign; + + /* epsilon^1/2 */ + a[0] = p[1]->y; a[1] = p[1]->z; + b[0] = p[2]->y; b[1] = p[2]->z; + c[0] = p[3]->y; c[1] = p[3]->z; + o = orient2d (a, b, c); + if (o != 0.) + return SIGN (o)*sign; + + /* epsilon */ + a[0] = p[0]->x; a[1] = p[0]->y; + b[0] = p[2]->x; b[1] = p[2]->y; + c[0] = p[3]->x; c[1] = p[3]->y; + o = orient2d (a, b, c); + if (o != 0.) + return - SIGN (o)*sign; + + /* epsilon^5/4 */ + o = ORIENT1D (p[2]->x, p[3]->x); + if (o != 0.) + return SIGN (o)*sign; + + /* epsilon^3/2 */ + o = ORIENT1D (p[2]->y, p[3]->y); + if (o != 0.) + return - SIGN (o)*sign; + + /* epsilon^2 */ + a[0] = p[0]->x; a[1] = p[0]->z; + b[0] = p[2]->x; b[1] = p[2]->z; + c[0] = p[3]->x; c[1] = p[3]->z; + o = orient2d (a, b, c); + if (o != 0.) + return SIGN (o)*sign; + + /* epsilon^5/2 */ + o = ORIENT1D (p[2]->z, p[3]->z); + if (o != 0.) + return SIGN (o)*sign; + + /* epsilon^4 */ + a[0] = p[0]->y; a[1] = p[0]->z; + b[0] = p[2]->y; b[1] = p[2]->z; + c[0] = p[3]->y; c[1] = p[3]->z; + o = orient2d (a, b, c); + if (o != 0.) + return - SIGN (o)*sign; + + /* epsilon^8 */ + a[0] = p[0]->x; a[1] = p[0]->y; + b[0] = p[1]->x; b[1] = p[1]->y; + c[0] = p[3]->x; c[1] = p[3]->y; + o = orient2d (a, b, c); + if (o != 0.) + return SIGN (o)*sign; + + /* epsilon^33/4 */ + o = ORIENT1D (p[1]->x, p[3]->x); + if (o != 0.) + return - SIGN (o)*sign; + + /* epsilon^17/2 */ + o = ORIENT1D (p[1]->y, p[3]->y); + if (o != 0.) + return SIGN (o)*sign; + + /* epsilon^10 */ + o = ORIENT1D (p[0]->x, p[3]->x); + if (o != 0.) + return SIGN (o)*sign; + + /* epsilon^21/2 */ + return sign; + } +} + +/** + * gts_point_orientation_sos: + * @p1: a #GtsPoint. + * @p2: a #GtsPoint. + * @p3: a #GtsPoint. + * + * Checks for orientation of the projection of three points on the + * (x,y) plane. + * + * Simulation of Simplicity (SoS) is used to break ties when the + * orientation is degenerate (i.e. @p3 lies on the line defined by + * @p1 and @p2). + * + * Returns: a positive value if @p1, @p2 and @p3 appear in + * counterclockwise order or a negative value if they appear in + * clockwise order. + */ +gint gts_point_orientation_sos (GtsPoint * p1, + GtsPoint * p2, + GtsPoint * p3) +{ + gdouble o; + + g_return_val_if_fail (p1 != NULL && p2 != NULL && p3 != NULL, 0); + + o = orient2d ((gdouble *) &p1->x, + (gdouble *) &p2->x, + (gdouble *) &p3->x); + if (o != 0.) + return SIGN (o); + else { + GtsPoint * p[3]; + gint sign; + + p[0] = p1; p[1] = p2; p[2] = p3; + sign = sortp ((gpointer *) p, 3); + + /* epsilon^1/4 */ + o = ORIENT1D (p[1]->x, p[2]->x); + if (o != 0.) + return - SIGN (o)*sign; + + /* epsilon^1/2 */ + o = ORIENT1D (p[1]->y, p[2]->y); + if (o != 0.) + return SIGN (o)*sign; + + /* epsilon */ + o = ORIENT1D (p[0]->x, p[2]->x); + if (o != 0.) + return SIGN (o)*sign; + + /* epsilon^3/2 */ + return sign; + } +} diff --git a/gts/predicates.c b/gts/predicates.c new file mode 100644 index 0000000000..7b7fcf27d0 --- /dev/null +++ b/gts/predicates.c @@ -0,0 +1,2742 @@ +/*****************************************************************************/ +/* */ +/* Routines for Arbitrary Precision Floating-point Arithmetic */ +/* and Fast Robust Geometric Predicates */ +/* (predicates.c) */ +/* */ +/* May 18, 1996 */ +/* */ +/* Placed in the public domain by */ +/* Jonathan Richard Shewchuk */ +/* School of Computer Science */ +/* Carnegie Mellon University */ +/* 5000 Forbes Avenue */ +/* Pittsburgh, Pennsylvania 15213-3891 */ +/* jrs@cs.cmu.edu */ +/* */ +/* This file contains C implementation of algorithms for exact addition */ +/* and multiplication of floating-point numbers, and predicates for */ +/* robustly performing the orientation and incircle tests used in */ +/* computational geometry. The algorithms and underlying theory are */ +/* described in Jonathan Richard Shewchuk. "Adaptive Precision Floating- */ +/* Point Arithmetic and Fast Robust Geometric Predicates." Technical */ +/* Report CMU-CS-96-140, School of Computer Science, Carnegie Mellon */ +/* University, Pittsburgh, Pennsylvania, May 1996. (Submitted to */ +/* Discrete & Computational Geometry.) */ +/* */ +/* This file, the paper listed above, and other information are available */ +/* from the Web page http://www.cs.cmu.edu/~quake/robust.html . */ +/* */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* */ +/* Using this code: */ +/* */ +/* First, read the short or long version of the paper (from the Web page */ +/* above). */ +/* */ +/* Be sure to call exactinit() once, before calling any of the arithmetic */ +/* functions or geometric predicates. Also be sure to turn on the */ +/* optimizer when compiling this file. */ +/* */ +/* */ +/* Several geometric predicates are defined. Their parameters are all */ +/* points. Each point is an array of two or three floating-point */ +/* numbers. The geometric predicates, described in the papers, are */ +/* */ +/* orient2d(pa, pb, pc) */ +/* orient2dfast(pa, pb, pc) */ +/* orient3d(pa, pb, pc, pd) */ +/* orient3dfast(pa, pb, pc, pd) */ +/* incircle(pa, pb, pc, pd) */ +/* incirclefast(pa, pb, pc, pd) */ +/* insphere(pa, pb, pc, pd, pe) */ +/* inspherefast(pa, pb, pc, pd, pe) */ +/* */ +/* Those with suffix "fast" are approximate, non-robust versions. Those */ +/* without the suffix are adaptive precision, robust versions. There */ +/* are also versions with the suffices "exact" and "slow", which are */ +/* non-adaptive, exact arithmetic versions, which I use only for timings */ +/* in my arithmetic papers. */ +/* */ +/* */ +/* An expansion is represented by an array of floating-point numbers, */ +/* sorted from smallest to largest magnitude (possibly with interspersed */ +/* zeros). The length of each expansion is stored as a separate integer, */ +/* and each arithmetic function returns an integer which is the length */ +/* of the expansion it created. */ +/* */ +/* Several arithmetic functions are defined. Their parameters are */ +/* */ +/* e, f Input expansions */ +/* elen, flen Lengths of input expansions (must be >= 1) */ +/* h Output expansion */ +/* b Input scalar */ +/* */ +/* The arithmetic functions are */ +/* */ +/* grow_expansion(elen, e, b, h) */ +/* grow_expansion_zeroelim(elen, e, b, h) */ +/* expansion_sum(elen, e, flen, f, h) */ +/* expansion_sum_zeroelim1(elen, e, flen, f, h) */ +/* expansion_sum_zeroelim2(elen, e, flen, f, h) */ +/* fast_expansion_sum(elen, e, flen, f, h) */ +/* fast_expansion_sum_zeroelim(elen, e, flen, f, h) */ +/* linear_expansion_sum(elen, e, flen, f, h) */ +/* linear_expansion_sum_zeroelim(elen, e, flen, f, h) */ +/* scale_expansion(elen, e, b, h) */ +/* scale_expansion_zeroelim(elen, e, b, h) */ +/* compress(elen, e, h) */ +/* */ +/* All of these are described in the long version of the paper; some are */ +/* described in the short version. All return an integer that is the */ +/* length of h. Those with suffix _zeroelim perform zero elimination, */ +/* and are recommended over their counterparts. The procedure */ +/* fast_expansion_sum_zeroelim() (or linear_expansion_sum_zeroelim() on */ +/* processors that do not use the round-to-even tiebreaking rule) is */ +/* recommended over expansion_sum_zeroelim(). Each procedure has a */ +/* little note next to it (in the code below) that tells you whether or */ +/* not the output expansion may be the same array as one of the input */ +/* expansions. */ +/* */ +/* */ +/* If you look around below, you'll also find macros for a bunch of */ +/* simple unrolled arithmetic operations, and procedures for printing */ +/* expansions (commented out because they don't work with all C */ +/* compilers) and for generating random floating-point numbers whose */ +/* significand bits are all random. Most of the macros have undocumented */ +/* requirements that certain of their parameters should not be the same */ +/* variable; for safety, better to make sure all the parameters are */ +/* distinct variables. Feel free to send email to jrs@cs.cmu.edu if you */ +/* have questions. */ +/* */ +/*****************************************************************************/ + +#include +#include +#include +#include "predicates.h" + +/* Use header file generated automatically by predicates_init. */ +//#define USE_PREDICATES_INIT + +#ifdef USE_PREDICATES_INIT +#include "predicates_init.h" +#endif /* USE_PREDICATES_INIT */ + +/* FPU control. We MUST have only double precision (not extended precision) */ +#include "rounding.h" + +/* On some machines, the exact arithmetic routines might be defeated by the */ +/* use of internal extended precision floating-point registers. Sometimes */ +/* this problem can be fixed by defining certain values to be volatile, */ +/* thus forcing them to be stored to memory and rounded off. This isn't */ +/* a great solution, though, as it slows the arithmetic down. */ +/* */ +/* To try this out, write "#define INEXACT volatile" below. Normally, */ +/* however, INEXACT should be defined to be nothing. ("#define INEXACT".) */ + +#define INEXACT /* Nothing */ +/* #define INEXACT volatile */ + +#define REAL double /* float or double */ +#define REALPRINT doubleprint +#define REALRAND doublerand +#define NARROWRAND narrowdoublerand +#define UNIFORMRAND uniformdoublerand + +/* Which of the following two methods of finding the absolute values is */ +/* fastest is compiler-dependent. A few compilers can inline and optimize */ +/* the fabs() call; but most will incur the overhead of a function call, */ +/* which is disastrously slow. A faster way on IEEE machines might be to */ +/* mask the appropriate bit, but that's difficult to do in C. */ + +#define Absolute(a) ((a) >= 0.0 ? (a) : -(a)) +/* #define Absolute(a) fabs(a) */ + +/* Many of the operations are broken up into two pieces, a main part that */ +/* performs an approximate operation, and a "tail" that computes the */ +/* roundoff error of that operation. */ +/* */ +/* The operations Fast_Two_Sum(), Fast_Two_Diff(), Two_Sum(), Two_Diff(), */ +/* Split(), and Two_Product() are all implemented as described in the */ +/* reference. Each of these macros requires certain variables to be */ +/* defined in the calling routine. The variables `bvirt', `c', `abig', */ +/* `_i', `_j', `_k', `_l', `_m', and `_n' are declared `INEXACT' because */ +/* they store the result of an operation that may incur roundoff error. */ +/* The input parameter `x' (or the highest numbered `x_' parameter) must */ +/* also be declared `INEXACT'. */ + +#define Fast_Two_Sum_Tail(a, b, x, y) \ + bvirt = x - a; \ + y = b - bvirt + +#define Fast_Two_Sum(a, b, x, y) \ + x = (REAL) (a + b); \ + Fast_Two_Sum_Tail(a, b, x, y) + +#define Fast_Two_Diff_Tail(a, b, x, y) \ + bvirt = a - x; \ + y = bvirt - b + +#define Fast_Two_Diff(a, b, x, y) \ + x = (REAL) (a - b); \ + Fast_Two_Diff_Tail(a, b, x, y) + +#define Two_Sum_Tail(a, b, x, y) \ + bvirt = (REAL) (x - a); \ + avirt = x - bvirt; \ + bround = b - bvirt; \ + around = a - avirt; \ + y = around + bround + +#define Two_Sum(a, b, x, y) \ + x = (REAL) (a + b); \ + Two_Sum_Tail(a, b, x, y) + +#define Two_Diff_Tail(a, b, x, y) \ + bvirt = (REAL) (a - x); \ + avirt = x + bvirt; \ + bround = bvirt - b; \ + around = a - avirt; \ + y = around + bround + +#define Two_Diff(a, b, x, y) \ + x = (REAL) (a - b); \ + Two_Diff_Tail(a, b, x, y) + +#define Split(a, ahi, alo) \ + c = (REAL) (splitter * a); \ + abig = (REAL) (c - a); \ + ahi = c - abig; \ + alo = a - ahi + +#define Two_Product_Tail(a, b, x, y) \ + Split(a, ahi, alo); \ + Split(b, bhi, blo); \ + err1 = x - (ahi * bhi); \ + err2 = err1 - (alo * bhi); \ + err3 = err2 - (ahi * blo); \ + y = (alo * blo) - err3 + +#define Two_Product(a, b, x, y) \ + x = (REAL) (a * b); \ + Two_Product_Tail(a, b, x, y) + +/* Two_Product_Presplit() is Two_Product() where one of the inputs has */ +/* already been split. Avoids redundant splitting. */ + +#define Two_Product_Presplit(a, b, bhi, blo, x, y) \ + x = (REAL) (a * b); \ + Split(a, ahi, alo); \ + err1 = x - (ahi * bhi); \ + err2 = err1 - (alo * bhi); \ + err3 = err2 - (ahi * blo); \ + y = (alo * blo) - err3 + +/* Two_Product_2Presplit() is Two_Product() where both of the inputs have */ +/* already been split. Avoids redundant splitting. */ + +#define Two_Product_2Presplit(a, ahi, alo, b, bhi, blo, x, y) \ + x = (REAL) (a * b); \ + err1 = x - (ahi * bhi); \ + err2 = err1 - (alo * bhi); \ + err3 = err2 - (ahi * blo); \ + y = (alo * blo) - err3 + +/* Square() can be done more quickly than Two_Product(). */ + +#define Square_Tail(a, x, y) \ + Split(a, ahi, alo); \ + err1 = x - (ahi * ahi); \ + err3 = err1 - ((ahi + ahi) * alo); \ + y = (alo * alo) - err3 + +#define Square(a, x, y) \ + x = (REAL) (a * a); \ + Square_Tail(a, x, y) + +/* Macros for summing expansions of various fixed lengths. These are all */ +/* unrolled versions of Expansion_Sum(). */ + +#define Two_One_Sum(a1, a0, b, x2, x1, x0) \ + Two_Sum(a0, b , _i, x0); \ + Two_Sum(a1, _i, x2, x1) + +#define Two_One_Diff(a1, a0, b, x2, x1, x0) \ + Two_Diff(a0, b , _i, x0); \ + Two_Sum( a1, _i, x2, x1) + +#define Two_Two_Sum(a1, a0, b1, b0, x3, x2, x1, x0) \ + Two_One_Sum(a1, a0, b0, _j, _0, x0); \ + Two_One_Sum(_j, _0, b1, x3, x2, x1) + +#define Two_Two_Diff(a1, a0, b1, b0, x3, x2, x1, x0) \ + Two_One_Diff(a1, a0, b0, _j, _0, x0); \ + Two_One_Diff(_j, _0, b1, x3, x2, x1) + +#define Four_One_Sum(a3, a2, a1, a0, b, x4, x3, x2, x1, x0) \ + Two_One_Sum(a1, a0, b , _j, x1, x0); \ + Two_One_Sum(a3, a2, _j, x4, x3, x2) + +#define Four_Two_Sum(a3, a2, a1, a0, b1, b0, x5, x4, x3, x2, x1, x0) \ + Four_One_Sum(a3, a2, a1, a0, b0, _k, _2, _1, _0, x0); \ + Four_One_Sum(_k, _2, _1, _0, b1, x5, x4, x3, x2, x1) + +#define Four_Four_Sum(a3, a2, a1, a0, b4, b3, b1, b0, x7, x6, x5, x4, x3, x2, \ + x1, x0) \ + Four_Two_Sum(a3, a2, a1, a0, b1, b0, _l, _2, _1, _0, x1, x0); \ + Four_Two_Sum(_l, _2, _1, _0, b4, b3, x7, x6, x5, x4, x3, x2) + +#define Eight_One_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b, x8, x7, x6, x5, x4, \ + x3, x2, x1, x0) \ + Four_One_Sum(a3, a2, a1, a0, b , _j, x3, x2, x1, x0); \ + Four_One_Sum(a7, a6, a5, a4, _j, x8, x7, x6, x5, x4) + +#define Eight_Two_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b1, b0, x9, x8, x7, \ + x6, x5, x4, x3, x2, x1, x0) \ + Eight_One_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b0, _k, _6, _5, _4, _3, _2, \ + _1, _0, x0); \ + Eight_One_Sum(_k, _6, _5, _4, _3, _2, _1, _0, b1, x9, x8, x7, x6, x5, x4, \ + x3, x2, x1) + +#define Eight_Four_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b4, b3, b1, b0, x11, \ + x10, x9, x8, x7, x6, x5, x4, x3, x2, x1, x0) \ + Eight_Two_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b1, b0, _l, _6, _5, _4, _3, \ + _2, _1, _0, x1, x0); \ + Eight_Two_Sum(_l, _6, _5, _4, _3, _2, _1, _0, b4, b3, x11, x10, x9, x8, \ + x7, x6, x5, x4, x3, x2) + +/* Macros for multiplying expansions of various fixed lengths. */ + +#define Two_One_Product(a1, a0, b, x3, x2, x1, x0) \ + Split(b, bhi, blo); \ + Two_Product_Presplit(a0, b, bhi, blo, _i, x0); \ + Two_Product_Presplit(a1, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x1); \ + Fast_Two_Sum(_j, _k, x3, x2) + +#define Four_One_Product(a3, a2, a1, a0, b, x7, x6, x5, x4, x3, x2, x1, x0) \ + Split(b, bhi, blo); \ + Two_Product_Presplit(a0, b, bhi, blo, _i, x0); \ + Two_Product_Presplit(a1, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x1); \ + Fast_Two_Sum(_j, _k, _i, x2); \ + Two_Product_Presplit(a2, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x3); \ + Fast_Two_Sum(_j, _k, _i, x4); \ + Two_Product_Presplit(a3, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x5); \ + Fast_Two_Sum(_j, _k, x7, x6) + +#define Two_Two_Product(a1, a0, b1, b0, x7, x6, x5, x4, x3, x2, x1, x0) \ + Split(a0, a0hi, a0lo); \ + Split(b0, bhi, blo); \ + Two_Product_2Presplit(a0, a0hi, a0lo, b0, bhi, blo, _i, x0); \ + Split(a1, a1hi, a1lo); \ + Two_Product_2Presplit(a1, a1hi, a1lo, b0, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, _1); \ + Fast_Two_Sum(_j, _k, _l, _2); \ + Split(b1, bhi, blo); \ + Two_Product_2Presplit(a0, a0hi, a0lo, b1, bhi, blo, _i, _0); \ + Two_Sum(_1, _0, _k, x1); \ + Two_Sum(_2, _k, _j, _1); \ + Two_Sum(_l, _j, _m, _2); \ + Two_Product_2Presplit(a1, a1hi, a1lo, b1, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _n, _0); \ + Two_Sum(_1, _0, _i, x2); \ + Two_Sum(_2, _i, _k, _1); \ + Two_Sum(_m, _k, _l, _2); \ + Two_Sum(_j, _n, _k, _0); \ + Two_Sum(_1, _0, _j, x3); \ + Two_Sum(_2, _j, _i, _1); \ + Two_Sum(_l, _i, _m, _2); \ + Two_Sum(_1, _k, _i, x4); \ + Two_Sum(_2, _i, _k, x5); \ + Two_Sum(_m, _k, x7, x6) + +/* An expansion of length two can be squared more quickly than finding the */ +/* product of two different expansions of length two, and the result is */ +/* guaranteed to have no more than six (rather than eight) components. */ + +#define Two_Square(a1, a0, x5, x4, x3, x2, x1, x0) \ + Square(a0, _j, x0); \ + _0 = a0 + a0; \ + Two_Product(a1, _0, _k, _1); \ + Two_One_Sum(_k, _1, _j, _l, _2, x1); \ + Square(a1, _j, _1); \ + Two_Two_Sum(_j, _1, _l, _2, x5, x4, x3, x2) + +#ifndef USE_PREDICATES_INIT + +static REAL splitter; /* = 2^ceiling(p / 2) + 1. Used to split floats in half. */ +/* A set of coefficients used to calculate maximum roundoff errors. */ +static REAL resulterrbound; +static REAL ccwerrboundA, ccwerrboundB, ccwerrboundC; +static REAL o3derrboundA, o3derrboundB, o3derrboundC; +static REAL iccerrboundA, iccerrboundB, iccerrboundC; +static REAL isperrboundA, isperrboundB, isperrboundC; + +void +gts_predicates_init() +{ + double half = 0.5; + double check = 1.0, lastcheck; + int every_other = 1; + /* epsilon = 2^(-p). Used to estimate roundoff errors. */ + double epsilon = 1.0; + + FPU_ROUND_DOUBLE; + + splitter = 1.; + + /* Repeatedly divide `epsilon' by two until it is too small to add to */ + /* one without causing roundoff. (Also check if the sum is equal to */ + /* the previous sum, for machines that round up instead of using exact */ + /* rounding. Not that this library will work on such machines anyway). */ + do { + lastcheck = check; + epsilon *= half; + if (every_other) { + splitter *= 2.0; + } + every_other = !every_other; + check = 1.0 + epsilon; + } while ((check != 1.0) && (check != lastcheck)); + splitter += 1.0; + /* Error bounds for orientation and incircle tests. */ + resulterrbound = (3.0 + 8.0 * epsilon) * epsilon; + ccwerrboundA = (3.0 + 16.0 * epsilon) * epsilon; + ccwerrboundB = (2.0 + 12.0 * epsilon) * epsilon; + ccwerrboundC = (9.0 + 64.0 * epsilon) * epsilon * epsilon; + o3derrboundA = (7.0 + 56.0 * epsilon) * epsilon; + o3derrboundB = (3.0 + 28.0 * epsilon) * epsilon; + o3derrboundC = (26.0 + 288.0 * epsilon) * epsilon * epsilon; + iccerrboundA = (10.0 + 96.0 * epsilon) * epsilon; + iccerrboundB = (4.0 + 48.0 * epsilon) * epsilon; + iccerrboundC = (44.0 + 576.0 * epsilon) * epsilon * epsilon; + isperrboundA = (16.0 + 224.0 * epsilon) * epsilon; + isperrboundB = (5.0 + 72.0 * epsilon) * epsilon; + isperrboundC = (71.0 + 1408.0 * epsilon) * epsilon * epsilon; + + + FPU_RESTORE; +} + +#endif /* USE_PREDICATES_INIT */ + +/*****************************************************************************/ +/* */ +/* doubleprint() Print the bit representation of a double. */ +/* */ +/* Useful for debugging exact arithmetic routines. */ +/* */ +/*****************************************************************************/ + +/* +void doubleprint(number) +double number; +{ + unsigned long long no; + unsigned long long sign, expo; + int exponent; + int i, bottomi; + + no = *(unsigned long long *) &number; + sign = no & 0x8000000000000000ll; + expo = (no >> 52) & 0x7ffll; + exponent = (int) expo; + exponent = exponent - 1023; + if (sign) { + printf("-"); + } else { + printf(" "); + } + if (exponent == -1023) { + printf( + "0.0000000000000000000000000000000000000000000000000000_ ( )"); + } else { + printf("1."); + bottomi = -1; + for (i = 0; i < 52; i++) { + if (no & 0x0008000000000000ll) { + printf("1"); + bottomi = i; + } else { + printf("0"); + } + no <<= 1; + } + printf("_%d (%d)", exponent, exponent - 1 - bottomi); + } +} +*/ + +/*****************************************************************************/ +/* */ +/* floatprint() Print the bit representation of a float. */ +/* */ +/* Useful for debugging exact arithmetic routines. */ +/* */ +/*****************************************************************************/ + +/* +void floatprint(number) +float number; +{ + unsigned no; + unsigned sign, expo; + int exponent; + int i, bottomi; + + no = *(unsigned *) &number; + sign = no & 0x80000000; + expo = (no >> 23) & 0xff; + exponent = (int) expo; + exponent = exponent - 127; + if (sign) { + printf("-"); + } else { + printf(" "); + } + if (exponent == -127) { + printf("0.00000000000000000000000_ ( )"); + } else { + printf("1."); + bottomi = -1; + for (i = 0; i < 23; i++) { + if (no & 0x00400000) { + printf("1"); + bottomi = i; + } else { + printf("0"); + } + no <<= 1; + } + printf("_%3d (%3d)", exponent, exponent - 1 - bottomi); + } +} +*/ + +/*****************************************************************************/ +/* */ +/* expansion_print() Print the bit representation of an expansion. */ +/* */ +/* Useful for debugging exact arithmetic routines. */ +/* */ +/*****************************************************************************/ + +/* +void expansion_print(elen, e) +int elen; +REAL *e; +{ + int i; + + for (i = elen - 1; i >= 0; i--) { + REALPRINT(e[i]); + if (i > 0) { + printf(" +\n"); + } else { + printf("\n"); + } + } +} +*/ + +/*****************************************************************************/ +/* */ +/* doublerand() Generate a double with random 53-bit significand and a */ +/* random exponent in [0, 511]. */ +/* */ +/*****************************************************************************/ + +/* +static double doublerand() +{ + double result; + double expo; + long a, b, c; + long i; + + a = random(); + b = random(); + c = random(); + result = (double) (a - 1073741824) * 8388608.0 + (double) (b >> 8); + for (i = 512, expo = 2; i <= 131072; i *= 2, expo = expo * expo) { + if (c & i) { + result *= expo; + } + } + return result; +} +*/ + +/*****************************************************************************/ +/* */ +/* narrowdoublerand() Generate a double with random 53-bit significand */ +/* and a random exponent in [0, 7]. */ +/* */ +/*****************************************************************************/ + +/* +static double narrowdoublerand() +{ + double result; + double expo; + long a, b, c; + long i; + + a = random(); + b = random(); + c = random(); + result = (double) (a - 1073741824) * 8388608.0 + (double) (b >> 8); + for (i = 512, expo = 2; i <= 2048; i *= 2, expo = expo * expo) { + if (c & i) { + result *= expo; + } + } + return result; +} +*/ + +/*****************************************************************************/ +/* */ +/* uniformdoublerand() Generate a double with random 53-bit significand. */ +/* */ +/*****************************************************************************/ + +/* +static double uniformdoublerand() +{ + double result; + long a, b; + + a = random(); + b = random(); + result = (double) (a - 1073741824) * 8388608.0 + (double) (b >> 8); + return result; +} +*/ + +/*****************************************************************************/ +/* */ +/* floatrand() Generate a float with random 24-bit significand and a */ +/* random exponent in [0, 63]. */ +/* */ +/*****************************************************************************/ + +/* +static float floatrand() +{ + float result; + float expo; + long a, c; + long i; + + a = random(); + c = random(); + result = (float) ((a - 1073741824) >> 6); + for (i = 512, expo = 2; i <= 16384; i *= 2, expo = expo * expo) { + if (c & i) { + result *= expo; + } + } + return result; +} +*/ + +/*****************************************************************************/ +/* */ +/* narrowfloatrand() Generate a float with random 24-bit significand and */ +/* a random exponent in [0, 7]. */ +/* */ +/*****************************************************************************/ + +/* +static float narrowfloatrand() +{ + float result; + float expo; + long a, c; + long i; + + a = random(); + c = random(); + result = (float) ((a - 1073741824) >> 6); + for (i = 512, expo = 2; i <= 2048; i *= 2, expo = expo * expo) { + if (c & i) { + result *= expo; + } + } + return result; +} +*/ + +/*****************************************************************************/ +/* */ +/* uniformfloatrand() Generate a float with random 24-bit significand. */ +/* */ +/*****************************************************************************/ + +/* +static float uniformfloatrand() +{ + float result; + long a; + + a = random(); + result = (float) ((a - 1073741824) >> 6); + return result; +} +*/ + +/*****************************************************************************/ +/* */ +/* fast_expansion_sum_zeroelim() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* If round-to-even is used (as with IEEE 754), maintains the strongly */ +/* nonoverlapping property. (That is, if e is strongly nonoverlapping, h */ +/* will be also.) Does NOT maintain the nonoverlapping or nonadjacent */ +/* properties. */ +/* */ +/*****************************************************************************/ + +static int fast_expansion_sum_zeroelim(int elen, REAL *e, + int flen, REAL *f, REAL *h) + /* h cannot be e or f. */ +{ + REAL Q; + INEXACT REAL Qnew; + INEXACT REAL hh; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + REAL enow, fnow; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + if ((fnow > enow) == (fnow > -enow)) { + Q = enow; + enow = e[++eindex]; + } else { + Q = fnow; + fnow = f[++findex]; + } + hindex = 0; + if ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Fast_Two_Sum(enow, Q, Qnew, hh); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, Q, Qnew, hh); + fnow = f[++findex]; + } + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + while ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Two_Sum(Q, enow, Qnew, hh); + enow = e[++eindex]; + } else { + Two_Sum(Q, fnow, Qnew, hh); + fnow = f[++findex]; + } + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + } + while (eindex < elen) { + Two_Sum(Q, enow, Qnew, hh); + enow = e[++eindex]; + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + while (findex < flen) { + Two_Sum(Q, fnow, Qnew, hh); + fnow = f[++findex]; + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* scale_expansion_zeroelim() Multiply an expansion by a scalar, */ +/* eliminating zero components from the */ +/* output expansion. */ +/* */ +/* Sets h = be. See either version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ + +static int scale_expansion_zeroelim(int elen, REAL *e, REAL b, REAL *h) + /* e and h cannot be the same. */ +{ + INEXACT REAL Q, sum; + REAL hh; + INEXACT REAL product1; + REAL product0; + int eindex, hindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + + Split(b, bhi, blo); + Two_Product_Presplit(e[0], b, bhi, blo, Q, hh); + hindex = 0; + if (hh != 0) { + h[hindex++] = hh; + } + for (eindex = 1; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Product_Presplit(enow, b, bhi, blo, product1, product0); + Two_Sum(Q, product0, sum, hh); + if (hh != 0) { + h[hindex++] = hh; + } + Fast_Two_Sum(product1, sum, Q, hh); + if (hh != 0) { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* estimate() Produce a one-word estimate of an expansion's value. */ +/* */ +/* See either version of my paper for details. */ +/* */ +/*****************************************************************************/ + +static REAL estimate(int elen, REAL *e) +{ + REAL Q; + int eindex; + + Q = e[0]; + for (eindex = 1; eindex < elen; eindex++) { + Q += e[eindex]; + } + return Q; +} + +/*****************************************************************************/ +/* */ +/* orient2dfast() Approximate 2D orientation test. Nonrobust. */ +/* orient2dexact() Exact 2D orientation test. Robust. */ +/* orient2dslow() Another exact 2D orientation test. Robust. */ +/* orient2d() Adaptive exact 2D orientation test. Robust. */ +/* */ +/* Return a positive value if the points pa, pb, and pc occur */ +/* in counterclockwise order; a negative value if they occur */ +/* in clockwise order; and zero if they are collinear. The */ +/* result is also a rough approximation of twice the signed */ +/* area of the triangle defined by the three points. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In orient2d() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, orient2d() is usually quite */ +/* fast, but will run more slowly when the input points are collinear or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +static REAL orient2dadapt(REAL *pa, REAL *pb, REAL *pc, REAL detsum) +{ + INEXACT REAL acx, acy, bcx, bcy; + REAL acxtail, acytail, bcxtail, bcytail; + INEXACT REAL detleft, detright; + REAL detlefttail, detrighttail; + REAL det, errbound; + REAL B[4], C1[8], C2[12], D[16]; + INEXACT REAL B3; + int C1length, C2length, Dlength; + REAL u[4]; + INEXACT REAL u3; + INEXACT REAL s1, t1; + REAL s0, t0; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + acx = (REAL) (pa[0] - pc[0]); + bcx = (REAL) (pb[0] - pc[0]); + acy = (REAL) (pa[1] - pc[1]); + bcy = (REAL) (pb[1] - pc[1]); + + Two_Product(acx, bcy, detleft, detlefttail); + Two_Product(acy, bcx, detright, detrighttail); + + Two_Two_Diff(detleft, detlefttail, detright, detrighttail, + B3, B[2], B[1], B[0]); + B[3] = B3; + + det = estimate(4, B); + errbound = ccwerrboundB * detsum; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pc[0], acx, acxtail); + Two_Diff_Tail(pb[0], pc[0], bcx, bcxtail); + Two_Diff_Tail(pa[1], pc[1], acy, acytail); + Two_Diff_Tail(pb[1], pc[1], bcy, bcytail); + + if ((acxtail == 0.0) && (acytail == 0.0) + && (bcxtail == 0.0) && (bcytail == 0.0)) { + return det; + } + + errbound = ccwerrboundC * detsum + resulterrbound * Absolute(det); + det += (acx * bcytail + bcy * acxtail) + - (acy * bcxtail + bcx * acytail); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Product(acxtail, bcy, s1, s0); + Two_Product(acytail, bcx, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + C1length = fast_expansion_sum_zeroelim(4, B, 4, u, C1); + + Two_Product(acx, bcytail, s1, s0); + Two_Product(acy, bcxtail, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + C2length = fast_expansion_sum_zeroelim(C1length, C1, 4, u, C2); + + Two_Product(acxtail, bcytail, s1, s0); + Two_Product(acytail, bcxtail, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + Dlength = fast_expansion_sum_zeroelim(C2length, C2, 4, u, D); + + return(D[Dlength - 1]); +} + +REAL orient2d(pa, pb, pc) +REAL *pa; +REAL *pb; +REAL *pc; +{ + REAL detleft, detright, det; + REAL detsum, errbound; + REAL orient; + + FPU_ROUND_DOUBLE; + + detleft = (pa[0] - pc[0]) * (pb[1] - pc[1]); + detright = (pa[1] - pc[1]) * (pb[0] - pc[0]); + det = detleft - detright; + + if (detleft > 0.0) { + if (detright <= 0.0) { + FPU_RESTORE; + return det; + } else { + detsum = detleft + detright; + } + } else if (detleft < 0.0) { + if (detright >= 0.0) { + FPU_RESTORE; + return det; + } else { + detsum = -detleft - detright; + } + } else { + FPU_RESTORE; + return det; + } + + errbound = ccwerrboundA * detsum; + if ((det >= errbound) || (-det >= errbound)) { + FPU_RESTORE; + return det; + } + + orient = orient2dadapt(pa, pb, pc, detsum); + FPU_RESTORE; + return orient; +} + +/*****************************************************************************/ +/* */ +/* orient3dfast() Approximate 3D orientation test. Nonrobust. */ +/* orient3dexact() Exact 3D orientation test. Robust. */ +/* orient3dslow() Another exact 3D orientation test. Robust. */ +/* orient3d() Adaptive exact 3D orientation test. Robust. */ +/* */ +/* Return a positive value if the point pd lies below the */ +/* plane passing through pa, pb, and pc; "below" is defined so */ +/* that pa, pb, and pc appear in counterclockwise order when */ +/* viewed from above the plane. Returns a negative value if */ +/* pd lies above the plane. Returns zero if the points are */ +/* coplanar. The result is also a rough approximation of six */ +/* times the signed volume of the tetrahedron defined by the */ +/* four points. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In orient3d() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, orient3d() is usually quite */ +/* fast, but will run more slowly when the input points are coplanar or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +static REAL orient3dadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, + REAL permanent) +{ + INEXACT REAL adx, bdx, cdx, ady, bdy, cdy, adz, bdz, cdz; + REAL det, errbound; + + INEXACT REAL bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1; + REAL bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0; + REAL bc[4], ca[4], ab[4]; + INEXACT REAL bc3, ca3, ab3; + REAL adet[8], bdet[8], cdet[8]; + int alen, blen, clen; + REAL abdet[16]; + int ablen; + REAL *finnow, *finother, *finswap; + REAL fin1[192], fin2[192]; + int finlength; + + REAL adxtail, bdxtail, cdxtail; + REAL adytail, bdytail, cdytail; + REAL adztail, bdztail, cdztail; + INEXACT REAL at_blarge, at_clarge; + INEXACT REAL bt_clarge, bt_alarge; + INEXACT REAL ct_alarge, ct_blarge; + REAL at_b[4], at_c[4], bt_c[4], bt_a[4], ct_a[4], ct_b[4]; + int at_blen, at_clen, bt_clen, bt_alen, ct_alen, ct_blen; + INEXACT REAL bdxt_cdy1, cdxt_bdy1, cdxt_ady1; + INEXACT REAL adxt_cdy1, adxt_bdy1, bdxt_ady1; + REAL bdxt_cdy0, cdxt_bdy0, cdxt_ady0; + REAL adxt_cdy0, adxt_bdy0, bdxt_ady0; + INEXACT REAL bdyt_cdx1, cdyt_bdx1, cdyt_adx1; + INEXACT REAL adyt_cdx1, adyt_bdx1, bdyt_adx1; + REAL bdyt_cdx0, cdyt_bdx0, cdyt_adx0; + REAL adyt_cdx0, adyt_bdx0, bdyt_adx0; + REAL bct[8], cat[8], abt[8]; + int bctlen, catlen, abtlen; + INEXACT REAL bdxt_cdyt1, cdxt_bdyt1, cdxt_adyt1; + INEXACT REAL adxt_cdyt1, adxt_bdyt1, bdxt_adyt1; + REAL bdxt_cdyt0, cdxt_bdyt0, cdxt_adyt0; + REAL adxt_cdyt0, adxt_bdyt0, bdxt_adyt0; + REAL u[4], v[12], w[16]; + INEXACT REAL u3; + int vlength, wlength; + REAL negate; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k; + REAL _0; + + adx = (REAL) (pa[0] - pd[0]); + bdx = (REAL) (pb[0] - pd[0]); + cdx = (REAL) (pc[0] - pd[0]); + ady = (REAL) (pa[1] - pd[1]); + bdy = (REAL) (pb[1] - pd[1]); + cdy = (REAL) (pc[1] - pd[1]); + adz = (REAL) (pa[2] - pd[2]); + bdz = (REAL) (pb[2] - pd[2]); + cdz = (REAL) (pc[2] - pd[2]); + + Two_Product(bdx, cdy, bdxcdy1, bdxcdy0); + Two_Product(cdx, bdy, cdxbdy1, cdxbdy0); + Two_Two_Diff(bdxcdy1, bdxcdy0, cdxbdy1, cdxbdy0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + alen = scale_expansion_zeroelim(4, bc, adz, adet); + + Two_Product(cdx, ady, cdxady1, cdxady0); + Two_Product(adx, cdy, adxcdy1, adxcdy0); + Two_Two_Diff(cdxady1, cdxady0, adxcdy1, adxcdy0, ca3, ca[2], ca[1], ca[0]); + ca[3] = ca3; + blen = scale_expansion_zeroelim(4, ca, bdz, bdet); + + Two_Product(adx, bdy, adxbdy1, adxbdy0); + Two_Product(bdx, ady, bdxady1, bdxady0); + Two_Two_Diff(adxbdy1, adxbdy0, bdxady1, bdxady0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + clen = scale_expansion_zeroelim(4, ab, cdz, cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, fin1); + + det = estimate(finlength, fin1); + errbound = o3derrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pd[0], adx, adxtail); + Two_Diff_Tail(pb[0], pd[0], bdx, bdxtail); + Two_Diff_Tail(pc[0], pd[0], cdx, cdxtail); + Two_Diff_Tail(pa[1], pd[1], ady, adytail); + Two_Diff_Tail(pb[1], pd[1], bdy, bdytail); + Two_Diff_Tail(pc[1], pd[1], cdy, cdytail); + Two_Diff_Tail(pa[2], pd[2], adz, adztail); + Two_Diff_Tail(pb[2], pd[2], bdz, bdztail); + Two_Diff_Tail(pc[2], pd[2], cdz, cdztail); + + if ((adxtail == 0.0) && (bdxtail == 0.0) && (cdxtail == 0.0) + && (adytail == 0.0) && (bdytail == 0.0) && (cdytail == 0.0) + && (adztail == 0.0) && (bdztail == 0.0) && (cdztail == 0.0)) { + return det; + } + + errbound = o3derrboundC * permanent + resulterrbound * Absolute(det); + det += (adz * ((bdx * cdytail + cdy * bdxtail) + - (bdy * cdxtail + cdx * bdytail)) + + adztail * (bdx * cdy - bdy * cdx)) + + (bdz * ((cdx * adytail + ady * cdxtail) + - (cdy * adxtail + adx * cdytail)) + + bdztail * (cdx * ady - cdy * adx)) + + (cdz * ((adx * bdytail + bdy * adxtail) + - (ady * bdxtail + bdx * adytail)) + + cdztail * (adx * bdy - ady * bdx)); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + finnow = fin1; + finother = fin2; + + if (adxtail == 0.0) { + if (adytail == 0.0) { + at_b[0] = 0.0; + at_blen = 1; + at_c[0] = 0.0; + at_clen = 1; + } else { + negate = -adytail; + Two_Product(negate, bdx, at_blarge, at_b[0]); + at_b[1] = at_blarge; + at_blen = 2; + Two_Product(adytail, cdx, at_clarge, at_c[0]); + at_c[1] = at_clarge; + at_clen = 2; + } + } else { + if (adytail == 0.0) { + Two_Product(adxtail, bdy, at_blarge, at_b[0]); + at_b[1] = at_blarge; + at_blen = 2; + negate = -adxtail; + Two_Product(negate, cdy, at_clarge, at_c[0]); + at_c[1] = at_clarge; + at_clen = 2; + } else { + Two_Product(adxtail, bdy, adxt_bdy1, adxt_bdy0); + Two_Product(adytail, bdx, adyt_bdx1, adyt_bdx0); + Two_Two_Diff(adxt_bdy1, adxt_bdy0, adyt_bdx1, adyt_bdx0, + at_blarge, at_b[2], at_b[1], at_b[0]); + at_b[3] = at_blarge; + at_blen = 4; + Two_Product(adytail, cdx, adyt_cdx1, adyt_cdx0); + Two_Product(adxtail, cdy, adxt_cdy1, adxt_cdy0); + Two_Two_Diff(adyt_cdx1, adyt_cdx0, adxt_cdy1, adxt_cdy0, + at_clarge, at_c[2], at_c[1], at_c[0]); + at_c[3] = at_clarge; + at_clen = 4; + } + } + if (bdxtail == 0.0) { + if (bdytail == 0.0) { + bt_c[0] = 0.0; + bt_clen = 1; + bt_a[0] = 0.0; + bt_alen = 1; + } else { + negate = -bdytail; + Two_Product(negate, cdx, bt_clarge, bt_c[0]); + bt_c[1] = bt_clarge; + bt_clen = 2; + Two_Product(bdytail, adx, bt_alarge, bt_a[0]); + bt_a[1] = bt_alarge; + bt_alen = 2; + } + } else { + if (bdytail == 0.0) { + Two_Product(bdxtail, cdy, bt_clarge, bt_c[0]); + bt_c[1] = bt_clarge; + bt_clen = 2; + negate = -bdxtail; + Two_Product(negate, ady, bt_alarge, bt_a[0]); + bt_a[1] = bt_alarge; + bt_alen = 2; + } else { + Two_Product(bdxtail, cdy, bdxt_cdy1, bdxt_cdy0); + Two_Product(bdytail, cdx, bdyt_cdx1, bdyt_cdx0); + Two_Two_Diff(bdxt_cdy1, bdxt_cdy0, bdyt_cdx1, bdyt_cdx0, + bt_clarge, bt_c[2], bt_c[1], bt_c[0]); + bt_c[3] = bt_clarge; + bt_clen = 4; + Two_Product(bdytail, adx, bdyt_adx1, bdyt_adx0); + Two_Product(bdxtail, ady, bdxt_ady1, bdxt_ady0); + Two_Two_Diff(bdyt_adx1, bdyt_adx0, bdxt_ady1, bdxt_ady0, + bt_alarge, bt_a[2], bt_a[1], bt_a[0]); + bt_a[3] = bt_alarge; + bt_alen = 4; + } + } + if (cdxtail == 0.0) { + if (cdytail == 0.0) { + ct_a[0] = 0.0; + ct_alen = 1; + ct_b[0] = 0.0; + ct_blen = 1; + } else { + negate = -cdytail; + Two_Product(negate, adx, ct_alarge, ct_a[0]); + ct_a[1] = ct_alarge; + ct_alen = 2; + Two_Product(cdytail, bdx, ct_blarge, ct_b[0]); + ct_b[1] = ct_blarge; + ct_blen = 2; + } + } else { + if (cdytail == 0.0) { + Two_Product(cdxtail, ady, ct_alarge, ct_a[0]); + ct_a[1] = ct_alarge; + ct_alen = 2; + negate = -cdxtail; + Two_Product(negate, bdy, ct_blarge, ct_b[0]); + ct_b[1] = ct_blarge; + ct_blen = 2; + } else { + Two_Product(cdxtail, ady, cdxt_ady1, cdxt_ady0); + Two_Product(cdytail, adx, cdyt_adx1, cdyt_adx0); + Two_Two_Diff(cdxt_ady1, cdxt_ady0, cdyt_adx1, cdyt_adx0, + ct_alarge, ct_a[2], ct_a[1], ct_a[0]); + ct_a[3] = ct_alarge; + ct_alen = 4; + Two_Product(cdytail, bdx, cdyt_bdx1, cdyt_bdx0); + Two_Product(cdxtail, bdy, cdxt_bdy1, cdxt_bdy0); + Two_Two_Diff(cdyt_bdx1, cdyt_bdx0, cdxt_bdy1, cdxt_bdy0, + ct_blarge, ct_b[2], ct_b[1], ct_b[0]); + ct_b[3] = ct_blarge; + ct_blen = 4; + } + } + + bctlen = fast_expansion_sum_zeroelim(bt_clen, bt_c, ct_blen, ct_b, bct); + wlength = scale_expansion_zeroelim(bctlen, bct, adz, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + + catlen = fast_expansion_sum_zeroelim(ct_alen, ct_a, at_clen, at_c, cat); + wlength = scale_expansion_zeroelim(catlen, cat, bdz, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + + abtlen = fast_expansion_sum_zeroelim(at_blen, at_b, bt_alen, bt_a, abt); + wlength = scale_expansion_zeroelim(abtlen, abt, cdz, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + + if (adztail != 0.0) { + vlength = scale_expansion_zeroelim(4, bc, adztail, v); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdztail != 0.0) { + vlength = scale_expansion_zeroelim(4, ca, bdztail, v); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdztail != 0.0) { + vlength = scale_expansion_zeroelim(4, ab, cdztail, v); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + if (adxtail != 0.0) { + if (bdytail != 0.0) { + Two_Product(adxtail, bdytail, adxt_bdyt1, adxt_bdyt0); + Two_One_Product(adxt_bdyt1, adxt_bdyt0, cdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (cdztail != 0.0) { + Two_One_Product(adxt_bdyt1, adxt_bdyt0, cdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if (cdytail != 0.0) { + negate = -adxtail; + Two_Product(negate, cdytail, adxt_cdyt1, adxt_cdyt0); + Two_One_Product(adxt_cdyt1, adxt_cdyt0, bdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (bdztail != 0.0) { + Two_One_Product(adxt_cdyt1, adxt_cdyt0, bdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + } + if (bdxtail != 0.0) { + if (cdytail != 0.0) { + Two_Product(bdxtail, cdytail, bdxt_cdyt1, bdxt_cdyt0); + Two_One_Product(bdxt_cdyt1, bdxt_cdyt0, adz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (adztail != 0.0) { + Two_One_Product(bdxt_cdyt1, bdxt_cdyt0, adztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if (adytail != 0.0) { + negate = -bdxtail; + Two_Product(negate, adytail, bdxt_adyt1, bdxt_adyt0); + Two_One_Product(bdxt_adyt1, bdxt_adyt0, cdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (cdztail != 0.0) { + Two_One_Product(bdxt_adyt1, bdxt_adyt0, cdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + } + if (cdxtail != 0.0) { + if (adytail != 0.0) { + Two_Product(cdxtail, adytail, cdxt_adyt1, cdxt_adyt0); + Two_One_Product(cdxt_adyt1, cdxt_adyt0, bdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (bdztail != 0.0) { + Two_One_Product(cdxt_adyt1, cdxt_adyt0, bdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if (bdytail != 0.0) { + negate = -cdxtail; + Two_Product(negate, bdytail, cdxt_bdyt1, cdxt_bdyt0); + Two_One_Product(cdxt_bdyt1, cdxt_bdyt0, adz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (adztail != 0.0) { + Two_One_Product(cdxt_bdyt1, cdxt_bdyt0, adztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + } + + if (adztail != 0.0) { + wlength = scale_expansion_zeroelim(bctlen, bct, adztail, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdztail != 0.0) { + wlength = scale_expansion_zeroelim(catlen, cat, bdztail, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdztail != 0.0) { + wlength = scale_expansion_zeroelim(abtlen, abt, cdztail, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + return finnow[finlength - 1]; +} + +REAL orient3d(pa, pb, pc, pd) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +{ + REAL adx, bdx, cdx, ady, bdy, cdy, adz, bdz, cdz; + REAL bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; + REAL det; + REAL permanent, errbound; + REAL orient; + + FPU_ROUND_DOUBLE; + + adx = pa[0] - pd[0]; + bdx = pb[0] - pd[0]; + cdx = pc[0] - pd[0]; + ady = pa[1] - pd[1]; + bdy = pb[1] - pd[1]; + cdy = pc[1] - pd[1]; + adz = pa[2] - pd[2]; + bdz = pb[2] - pd[2]; + cdz = pc[2] - pd[2]; + + bdxcdy = bdx * cdy; + cdxbdy = cdx * bdy; + + cdxady = cdx * ady; + adxcdy = adx * cdy; + + adxbdy = adx * bdy; + bdxady = bdx * ady; + + det = adz * (bdxcdy - cdxbdy) + + bdz * (cdxady - adxcdy) + + cdz * (adxbdy - bdxady); + + permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * Absolute(adz) + + (Absolute(cdxady) + Absolute(adxcdy)) * Absolute(bdz) + + (Absolute(adxbdy) + Absolute(bdxady)) * Absolute(cdz); + errbound = o3derrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + FPU_RESTORE; + return det; + } + + orient = orient3dadapt(pa, pb, pc, pd, permanent); + FPU_RESTORE; + return orient; +} + +/*****************************************************************************/ +/* */ +/* incirclefast() Approximate 2D incircle test. Nonrobust. */ +/* incircleexact() Exact 2D incircle test. Robust. */ +/* incircleslow() Another exact 2D incircle test. Robust. */ +/* incircle() Adaptive exact 2D incircle test. Robust. */ +/* */ +/* Return a positive value if the point pd lies inside the */ +/* circle passing through pa, pb, and pc; a negative value if */ +/* it lies outside; and zero if the four points are cocircular.*/ +/* The points pa, pb, and pc must be in counterclockwise */ +/* order, or the sign of the result will be reversed. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In incircle() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, incircle() is usually quite */ +/* fast, but will run more slowly when the input points are cocircular or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +static REAL incircleadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, + REAL permanent) +{ + INEXACT REAL adx, bdx, cdx, ady, bdy, cdy; + REAL det, errbound; + + INEXACT REAL bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1; + REAL bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0; + REAL bc[4], ca[4], ab[4]; + INEXACT REAL bc3, ca3, ab3; + REAL axbc[8], axxbc[16], aybc[8], ayybc[16], adet[32]; + int axbclen, axxbclen, aybclen, ayybclen, alen; + REAL bxca[8], bxxca[16], byca[8], byyca[16], bdet[32]; + int bxcalen, bxxcalen, bycalen, byycalen, blen; + REAL cxab[8], cxxab[16], cyab[8], cyyab[16], cdet[32]; + int cxablen, cxxablen, cyablen, cyyablen, clen; + REAL abdet[64]; + int ablen; + REAL fin1[1152], fin2[1152]; + REAL *finnow, *finother, *finswap; + int finlength; + + REAL adxtail, bdxtail, cdxtail, adytail, bdytail, cdytail; + INEXACT REAL adxadx1, adyady1, bdxbdx1, bdybdy1, cdxcdx1, cdycdy1; + REAL adxadx0, adyady0, bdxbdx0, bdybdy0, cdxcdx0, cdycdy0; + REAL aa[4], bb[4], cc[4]; + INEXACT REAL aa3, bb3, cc3; + INEXACT REAL ti1, tj1; + REAL ti0, tj0; + REAL u[4], v[4]; + INEXACT REAL u3, v3; + REAL temp8[8], temp16a[16], temp16b[16], temp16c[16]; + REAL temp32a[32], temp32b[32], temp48[48], temp64[64]; + int temp8len, temp16alen, temp16blen, temp16clen; + int temp32alen, temp32blen, temp48len, temp64len; + REAL axtbb[8], axtcc[8], aytbb[8], aytcc[8]; + int axtbblen, axtcclen, aytbblen, aytcclen; + REAL bxtaa[8], bxtcc[8], bytaa[8], bytcc[8]; + int bxtaalen, bxtcclen, bytaalen, bytcclen; + REAL cxtaa[8], cxtbb[8], cytaa[8], cytbb[8]; + int cxtaalen, cxtbblen, cytaalen, cytbblen; + REAL axtbc[8], aytbc[8], bxtca[8], bytca[8], cxtab[8], cytab[8]; + int axtbclen = 0, aytbclen = 0; + int bxtcalen = 0, bytcalen = 0; + int cxtablen = 0, cytablen = 0; + REAL axtbct[16], aytbct[16], bxtcat[16], bytcat[16], cxtabt[16], cytabt[16]; + int axtbctlen, aytbctlen, bxtcatlen, bytcatlen, cxtabtlen, cytabtlen; + REAL axtbctt[8], aytbctt[8], bxtcatt[8]; + REAL bytcatt[8], cxtabtt[8], cytabtt[8]; + int axtbcttlen, aytbcttlen, bxtcattlen, bytcattlen, cxtabttlen, cytabttlen; + REAL abt[8], bct[8], cat[8]; + int abtlen, bctlen, catlen; + REAL abtt[4], bctt[4], catt[4]; + int abttlen, bcttlen, cattlen; + INEXACT REAL abtt3, bctt3, catt3; + REAL negate; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + adx = (REAL) (pa[0] - pd[0]); + bdx = (REAL) (pb[0] - pd[0]); + cdx = (REAL) (pc[0] - pd[0]); + ady = (REAL) (pa[1] - pd[1]); + bdy = (REAL) (pb[1] - pd[1]); + cdy = (REAL) (pc[1] - pd[1]); + + Two_Product(bdx, cdy, bdxcdy1, bdxcdy0); + Two_Product(cdx, bdy, cdxbdy1, cdxbdy0); + Two_Two_Diff(bdxcdy1, bdxcdy0, cdxbdy1, cdxbdy0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + axbclen = scale_expansion_zeroelim(4, bc, adx, axbc); + axxbclen = scale_expansion_zeroelim(axbclen, axbc, adx, axxbc); + aybclen = scale_expansion_zeroelim(4, bc, ady, aybc); + ayybclen = scale_expansion_zeroelim(aybclen, aybc, ady, ayybc); + alen = fast_expansion_sum_zeroelim(axxbclen, axxbc, ayybclen, ayybc, adet); + + Two_Product(cdx, ady, cdxady1, cdxady0); + Two_Product(adx, cdy, adxcdy1, adxcdy0); + Two_Two_Diff(cdxady1, cdxady0, adxcdy1, adxcdy0, ca3, ca[2], ca[1], ca[0]); + ca[3] = ca3; + bxcalen = scale_expansion_zeroelim(4, ca, bdx, bxca); + bxxcalen = scale_expansion_zeroelim(bxcalen, bxca, bdx, bxxca); + bycalen = scale_expansion_zeroelim(4, ca, bdy, byca); + byycalen = scale_expansion_zeroelim(bycalen, byca, bdy, byyca); + blen = fast_expansion_sum_zeroelim(bxxcalen, bxxca, byycalen, byyca, bdet); + + Two_Product(adx, bdy, adxbdy1, adxbdy0); + Two_Product(bdx, ady, bdxady1, bdxady0); + Two_Two_Diff(adxbdy1, adxbdy0, bdxady1, bdxady0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + cxablen = scale_expansion_zeroelim(4, ab, cdx, cxab); + cxxablen = scale_expansion_zeroelim(cxablen, cxab, cdx, cxxab); + cyablen = scale_expansion_zeroelim(4, ab, cdy, cyab); + cyyablen = scale_expansion_zeroelim(cyablen, cyab, cdy, cyyab); + clen = fast_expansion_sum_zeroelim(cxxablen, cxxab, cyyablen, cyyab, cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, fin1); + + det = estimate(finlength, fin1); + errbound = iccerrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pd[0], adx, adxtail); + Two_Diff_Tail(pa[1], pd[1], ady, adytail); + Two_Diff_Tail(pb[0], pd[0], bdx, bdxtail); + Two_Diff_Tail(pb[1], pd[1], bdy, bdytail); + Two_Diff_Tail(pc[0], pd[0], cdx, cdxtail); + Two_Diff_Tail(pc[1], pd[1], cdy, cdytail); + if ((adxtail == 0.0) && (bdxtail == 0.0) && (cdxtail == 0.0) + && (adytail == 0.0) && (bdytail == 0.0) && (cdytail == 0.0)) { + return det; + } + + errbound = iccerrboundC * permanent + resulterrbound * Absolute(det); + det += ((adx * adx + ady * ady) * ((bdx * cdytail + cdy * bdxtail) + - (bdy * cdxtail + cdx * bdytail)) + + 2.0 * (adx * adxtail + ady * adytail) * (bdx * cdy - bdy * cdx)) + + ((bdx * bdx + bdy * bdy) * ((cdx * adytail + ady * cdxtail) + - (cdy * adxtail + adx * cdytail)) + + 2.0 * (bdx * bdxtail + bdy * bdytail) * (cdx * ady - cdy * adx)) + + ((cdx * cdx + cdy * cdy) * ((adx * bdytail + bdy * adxtail) + - (ady * bdxtail + bdx * adytail)) + + 2.0 * (cdx * cdxtail + cdy * cdytail) * (adx * bdy - ady * bdx)); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + finnow = fin1; + finother = fin2; + + if ((bdxtail != 0.0) || (bdytail != 0.0) + || (cdxtail != 0.0) || (cdytail != 0.0)) { + Square(adx, adxadx1, adxadx0); + Square(ady, adyady1, adyady0); + Two_Two_Sum(adxadx1, adxadx0, adyady1, adyady0, aa3, aa[2], aa[1], aa[0]); + aa[3] = aa3; + } + if ((cdxtail != 0.0) || (cdytail != 0.0) + || (adxtail != 0.0) || (adytail != 0.0)) { + Square(bdx, bdxbdx1, bdxbdx0); + Square(bdy, bdybdy1, bdybdy0); + Two_Two_Sum(bdxbdx1, bdxbdx0, bdybdy1, bdybdy0, bb3, bb[2], bb[1], bb[0]); + bb[3] = bb3; + } + if ((adxtail != 0.0) || (adytail != 0.0) + || (bdxtail != 0.0) || (bdytail != 0.0)) { + Square(cdx, cdxcdx1, cdxcdx0); + Square(cdy, cdycdy1, cdycdy0); + Two_Two_Sum(cdxcdx1, cdxcdx0, cdycdy1, cdycdy0, cc3, cc[2], cc[1], cc[0]); + cc[3] = cc3; + } + + if (adxtail != 0.0) { + axtbclen = scale_expansion_zeroelim(4, bc, adxtail, axtbc); + temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, 2.0 * adx, + temp16a); + + axtcclen = scale_expansion_zeroelim(4, cc, adxtail, axtcc); + temp16blen = scale_expansion_zeroelim(axtcclen, axtcc, bdy, temp16b); + + axtbblen = scale_expansion_zeroelim(4, bb, adxtail, axtbb); + temp16clen = scale_expansion_zeroelim(axtbblen, axtbb, -cdy, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) { + aytbclen = scale_expansion_zeroelim(4, bc, adytail, aytbc); + temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, 2.0 * ady, + temp16a); + + aytbblen = scale_expansion_zeroelim(4, bb, adytail, aytbb); + temp16blen = scale_expansion_zeroelim(aytbblen, aytbb, cdx, temp16b); + + aytcclen = scale_expansion_zeroelim(4, cc, adytail, aytcc); + temp16clen = scale_expansion_zeroelim(aytcclen, aytcc, -bdx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdxtail != 0.0) { + bxtcalen = scale_expansion_zeroelim(4, ca, bdxtail, bxtca); + temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, 2.0 * bdx, + temp16a); + + bxtaalen = scale_expansion_zeroelim(4, aa, bdxtail, bxtaa); + temp16blen = scale_expansion_zeroelim(bxtaalen, bxtaa, cdy, temp16b); + + bxtcclen = scale_expansion_zeroelim(4, cc, bdxtail, bxtcc); + temp16clen = scale_expansion_zeroelim(bxtcclen, bxtcc, -ady, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) { + bytcalen = scale_expansion_zeroelim(4, ca, bdytail, bytca); + temp16alen = scale_expansion_zeroelim(bytcalen, bytca, 2.0 * bdy, + temp16a); + + bytcclen = scale_expansion_zeroelim(4, cc, bdytail, bytcc); + temp16blen = scale_expansion_zeroelim(bytcclen, bytcc, adx, temp16b); + + bytaalen = scale_expansion_zeroelim(4, aa, bdytail, bytaa); + temp16clen = scale_expansion_zeroelim(bytaalen, bytaa, -cdx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdxtail != 0.0) { + cxtablen = scale_expansion_zeroelim(4, ab, cdxtail, cxtab); + temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, 2.0 * cdx, + temp16a); + + cxtbblen = scale_expansion_zeroelim(4, bb, cdxtail, cxtbb); + temp16blen = scale_expansion_zeroelim(cxtbblen, cxtbb, ady, temp16b); + + cxtaalen = scale_expansion_zeroelim(4, aa, cdxtail, cxtaa); + temp16clen = scale_expansion_zeroelim(cxtaalen, cxtaa, -bdy, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) { + cytablen = scale_expansion_zeroelim(4, ab, cdytail, cytab); + temp16alen = scale_expansion_zeroelim(cytablen, cytab, 2.0 * cdy, + temp16a); + + cytaalen = scale_expansion_zeroelim(4, aa, cdytail, cytaa); + temp16blen = scale_expansion_zeroelim(cytaalen, cytaa, bdx, temp16b); + + cytbblen = scale_expansion_zeroelim(4, bb, cdytail, cytbb); + temp16clen = scale_expansion_zeroelim(cytbblen, cytbb, -adx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + if ((adxtail != 0.0) || (adytail != 0.0)) { + if ((bdxtail != 0.0) || (bdytail != 0.0) + || (cdxtail != 0.0) || (cdytail != 0.0)) { + Two_Product(bdxtail, cdy, ti1, ti0); + Two_Product(bdx, cdytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -bdy; + Two_Product(cdxtail, negate, ti1, ti0); + negate = -bdytail; + Two_Product(cdx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + bctlen = fast_expansion_sum_zeroelim(4, u, 4, v, bct); + + Two_Product(bdxtail, cdytail, ti1, ti0); + Two_Product(cdxtail, bdytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, bctt3, bctt[2], bctt[1], bctt[0]); + bctt[3] = bctt3; + bcttlen = 4; + } else { + bct[0] = 0.0; + bctlen = 1; + bctt[0] = 0.0; + bcttlen = 1; + } + + if (adxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, adxtail, temp16a); + axtbctlen = scale_expansion_zeroelim(bctlen, bct, adxtail, axtbct); + temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, 2.0 * adx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (bdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, cc, adxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, bb, -adxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, adxtail, + temp32a); + axtbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adxtail, axtbctt); + temp16alen = scale_expansion_zeroelim(axtbcttlen, axtbctt, 2.0 * adx, + temp16a); + temp16blen = scale_expansion_zeroelim(axtbcttlen, axtbctt, adxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) { + temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, adytail, temp16a); + aytbctlen = scale_expansion_zeroelim(bctlen, bct, adytail, aytbct); + temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, 2.0 * ady, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, adytail, + temp32a); + aytbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adytail, aytbctt); + temp16alen = scale_expansion_zeroelim(aytbcttlen, aytbctt, 2.0 * ady, + temp16a); + temp16blen = scale_expansion_zeroelim(aytbcttlen, aytbctt, adytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if ((bdxtail != 0.0) || (bdytail != 0.0)) { + if ((cdxtail != 0.0) || (cdytail != 0.0) + || (adxtail != 0.0) || (adytail != 0.0)) { + Two_Product(cdxtail, ady, ti1, ti0); + Two_Product(cdx, adytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -cdy; + Two_Product(adxtail, negate, ti1, ti0); + negate = -cdytail; + Two_Product(adx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + catlen = fast_expansion_sum_zeroelim(4, u, 4, v, cat); + + Two_Product(cdxtail, adytail, ti1, ti0); + Two_Product(adxtail, cdytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, catt3, catt[2], catt[1], catt[0]); + catt[3] = catt3; + cattlen = 4; + } else { + cat[0] = 0.0; + catlen = 1; + catt[0] = 0.0; + cattlen = 1; + } + + if (bdxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, bdxtail, temp16a); + bxtcatlen = scale_expansion_zeroelim(catlen, cat, bdxtail, bxtcat); + temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, 2.0 * bdx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (cdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, aa, bdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, cc, -bdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, bdxtail, + temp32a); + bxtcattlen = scale_expansion_zeroelim(cattlen, catt, bdxtail, bxtcatt); + temp16alen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, 2.0 * bdx, + temp16a); + temp16blen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, bdxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) { + temp16alen = scale_expansion_zeroelim(bytcalen, bytca, bdytail, temp16a); + bytcatlen = scale_expansion_zeroelim(catlen, cat, bdytail, bytcat); + temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, 2.0 * bdy, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, bdytail, + temp32a); + bytcattlen = scale_expansion_zeroelim(cattlen, catt, bdytail, bytcatt); + temp16alen = scale_expansion_zeroelim(bytcattlen, bytcatt, 2.0 * bdy, + temp16a); + temp16blen = scale_expansion_zeroelim(bytcattlen, bytcatt, bdytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if ((cdxtail != 0.0) || (cdytail != 0.0)) { + if ((adxtail != 0.0) || (adytail != 0.0) + || (bdxtail != 0.0) || (bdytail != 0.0)) { + Two_Product(adxtail, bdy, ti1, ti0); + Two_Product(adx, bdytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -ady; + Two_Product(bdxtail, negate, ti1, ti0); + negate = -adytail; + Two_Product(bdx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + abtlen = fast_expansion_sum_zeroelim(4, u, 4, v, abt); + + Two_Product(adxtail, bdytail, ti1, ti0); + Two_Product(bdxtail, adytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, abtt3, abtt[2], abtt[1], abtt[0]); + abtt[3] = abtt3; + abttlen = 4; + } else { + abt[0] = 0.0; + abtlen = 1; + abtt[0] = 0.0; + abttlen = 1; + } + + if (cdxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, cdxtail, temp16a); + cxtabtlen = scale_expansion_zeroelim(abtlen, abt, cdxtail, cxtabt); + temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, 2.0 * cdx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (adytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, bb, cdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, aa, -cdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, cdxtail, + temp32a); + cxtabttlen = scale_expansion_zeroelim(abttlen, abtt, cdxtail, cxtabtt); + temp16alen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, 2.0 * cdx, + temp16a); + temp16blen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, cdxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) { + temp16alen = scale_expansion_zeroelim(cytablen, cytab, cdytail, temp16a); + cytabtlen = scale_expansion_zeroelim(abtlen, abt, cdytail, cytabt); + temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, 2.0 * cdy, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, cdytail, + temp32a); + cytabttlen = scale_expansion_zeroelim(abttlen, abtt, cdytail, cytabtt); + temp16alen = scale_expansion_zeroelim(cytabttlen, cytabtt, 2.0 * cdy, + temp16a); + temp16blen = scale_expansion_zeroelim(cytabttlen, cytabtt, cdytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + + return finnow[finlength - 1]; +} + +REAL incircle(pa, pb, pc, pd) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +{ + REAL adx, bdx, cdx, ady, bdy, cdy; + REAL bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; + REAL alift, blift, clift; + REAL det; + REAL permanent, errbound; + REAL inc; + + FPU_ROUND_DOUBLE; + + adx = pa[0] - pd[0]; + bdx = pb[0] - pd[0]; + cdx = pc[0] - pd[0]; + ady = pa[1] - pd[1]; + bdy = pb[1] - pd[1]; + cdy = pc[1] - pd[1]; + + bdxcdy = bdx * cdy; + cdxbdy = cdx * bdy; + alift = adx * adx + ady * ady; + + cdxady = cdx * ady; + adxcdy = adx * cdy; + blift = bdx * bdx + bdy * bdy; + + adxbdy = adx * bdy; + bdxady = bdx * ady; + clift = cdx * cdx + cdy * cdy; + + det = alift * (bdxcdy - cdxbdy) + + blift * (cdxady - adxcdy) + + clift * (adxbdy - bdxady); + + permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * alift + + (Absolute(cdxady) + Absolute(adxcdy)) * blift + + (Absolute(adxbdy) + Absolute(bdxady)) * clift; + errbound = iccerrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + FPU_RESTORE; + return det; + } + + inc = incircleadapt(pa, pb, pc, pd, permanent); + FPU_RESTORE; + return inc; +} + +/*****************************************************************************/ +/* */ +/* inspherefast() Approximate 3D insphere test. Nonrobust. */ +/* insphereexact() Exact 3D insphere test. Robust. */ +/* insphereslow() Another exact 3D insphere test. Robust. */ +/* insphere() Adaptive exact 3D insphere test. Robust. */ +/* */ +/* Return a positive value if the point pe lies inside the */ +/* sphere passing through pa, pb, pc, and pd; a negative value */ +/* if it lies outside; and zero if the five points are */ +/* cospherical. The points pa, pb, pc, and pd must be ordered */ +/* so that they have a positive orientation (as defined by */ +/* orient3d()), or the sign of the result will be reversed. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In insphere() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, insphere() is usually quite */ +/* fast, but will run more slowly when the input points are cospherical or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +static REAL insphereexact(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) +{ + INEXACT REAL axby1, bxcy1, cxdy1, dxey1, exay1; + INEXACT REAL bxay1, cxby1, dxcy1, exdy1, axey1; + INEXACT REAL axcy1, bxdy1, cxey1, dxay1, exby1; + INEXACT REAL cxay1, dxby1, excy1, axdy1, bxey1; + REAL axby0, bxcy0, cxdy0, dxey0, exay0; + REAL bxay0, cxby0, dxcy0, exdy0, axey0; + REAL axcy0, bxdy0, cxey0, dxay0, exby0; + REAL cxay0, dxby0, excy0, axdy0, bxey0; + REAL ab[4], bc[4], cd[4], de[4], ea[4]; + REAL ac[4], bd[4], ce[4], da[4], eb[4]; + REAL temp8a[8], temp8b[8], temp16[16]; + int temp8alen, temp8blen, temp16len; + REAL abc[24], bcd[24], cde[24], dea[24], eab[24]; + REAL abd[24], bce[24], cda[24], deb[24], eac[24]; + int abclen, bcdlen, cdelen, dealen, eablen; + int abdlen, bcelen, cdalen, deblen, eaclen; + REAL temp48a[48], temp48b[48]; + int temp48alen, temp48blen; + REAL abcd[96], bcde[96], cdea[96], deab[96], eabc[96]; + int abcdlen, bcdelen, cdealen, deablen, eabclen; + REAL temp192[192]; + REAL det384x[384], det384y[384], det384z[384]; + int xlen, ylen, zlen; + REAL detxy[768]; + int xylen; + REAL adet[1152], bdet[1152], cdet[1152], ddet[1152], edet[1152]; + int alen, blen, clen, dlen, elen; + REAL abdet[2304], cddet[2304], cdedet[3456]; + int ablen, cdlen; + REAL deter[5760]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]); + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(bxcy1, bxcy0, cxby1, cxby0, bc[3], bc[2], bc[1], bc[0]); + + Two_Product(pc[0], pd[1], cxdy1, cxdy0); + Two_Product(pd[0], pc[1], dxcy1, dxcy0); + Two_Two_Diff(cxdy1, cxdy0, dxcy1, dxcy0, cd[3], cd[2], cd[1], cd[0]); + + Two_Product(pd[0], pe[1], dxey1, dxey0); + Two_Product(pe[0], pd[1], exdy1, exdy0); + Two_Two_Diff(dxey1, dxey0, exdy1, exdy0, de[3], de[2], de[1], de[0]); + + Two_Product(pe[0], pa[1], exay1, exay0); + Two_Product(pa[0], pe[1], axey1, axey0); + Two_Two_Diff(exay1, exay0, axey1, axey0, ea[3], ea[2], ea[1], ea[0]); + + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Two_Diff(axcy1, axcy0, cxay1, cxay0, ac[3], ac[2], ac[1], ac[0]); + + Two_Product(pb[0], pd[1], bxdy1, bxdy0); + Two_Product(pd[0], pb[1], dxby1, dxby0); + Two_Two_Diff(bxdy1, bxdy0, dxby1, dxby0, bd[3], bd[2], bd[1], bd[0]); + + Two_Product(pc[0], pe[1], cxey1, cxey0); + Two_Product(pe[0], pc[1], excy1, excy0); + Two_Two_Diff(cxey1, cxey0, excy1, excy0, ce[3], ce[2], ce[1], ce[0]); + + Two_Product(pd[0], pa[1], dxay1, dxay0); + Two_Product(pa[0], pd[1], axdy1, axdy0); + Two_Two_Diff(dxay1, dxay0, axdy1, axdy0, da[3], da[2], da[1], da[0]); + + Two_Product(pe[0], pb[1], exby1, exby0); + Two_Product(pb[0], pe[1], bxey1, bxey0); + Two_Two_Diff(exby1, exby0, bxey1, bxey0, eb[3], eb[2], eb[1], eb[0]); + + temp8alen = scale_expansion_zeroelim(4, bc, pa[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, -pb[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ab, pc[2], temp8a); + abclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + abc); + + temp8alen = scale_expansion_zeroelim(4, cd, pb[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, -pc[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, bc, pd[2], temp8a); + bcdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + bcd); + + temp8alen = scale_expansion_zeroelim(4, de, pc[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ce, -pd[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, cd, pe[2], temp8a); + cdelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + cde); + + temp8alen = scale_expansion_zeroelim(4, ea, pd[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, da, -pe[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, de, pa[2], temp8a); + dealen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + dea); + + temp8alen = scale_expansion_zeroelim(4, ab, pe[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, eb, -pa[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ea, pb[2], temp8a); + eablen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + eab); + + temp8alen = scale_expansion_zeroelim(4, bd, pa[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, da, pb[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ab, pd[2], temp8a); + abdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + abd); + + temp8alen = scale_expansion_zeroelim(4, ce, pb[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, eb, pc[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, bc, pe[2], temp8a); + bcelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + bce); + + temp8alen = scale_expansion_zeroelim(4, da, pc[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, pd[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, cd, pa[2], temp8a); + cdalen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + cda); + + temp8alen = scale_expansion_zeroelim(4, eb, pd[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, pe[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, de, pb[2], temp8a); + deblen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + deb); + + temp8alen = scale_expansion_zeroelim(4, ac, pe[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ce, pa[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ea, pc[2], temp8a); + eaclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + eac); + + temp48alen = fast_expansion_sum_zeroelim(cdelen, cde, bcelen, bce, temp48a); + temp48blen = fast_expansion_sum_zeroelim(deblen, deb, bcdlen, bcd, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + bcdelen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, bcde); + xlen = scale_expansion_zeroelim(bcdelen, bcde, pa[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pa[0], det384x); + ylen = scale_expansion_zeroelim(bcdelen, bcde, pa[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pa[1], det384y); + zlen = scale_expansion_zeroelim(bcdelen, bcde, pa[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pa[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + alen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, adet); + + temp48alen = fast_expansion_sum_zeroelim(dealen, dea, cdalen, cda, temp48a); + temp48blen = fast_expansion_sum_zeroelim(eaclen, eac, cdelen, cde, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + cdealen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, cdea); + xlen = scale_expansion_zeroelim(cdealen, cdea, pb[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pb[0], det384x); + ylen = scale_expansion_zeroelim(cdealen, cdea, pb[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pb[1], det384y); + zlen = scale_expansion_zeroelim(cdealen, cdea, pb[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pb[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + blen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, bdet); + + temp48alen = fast_expansion_sum_zeroelim(eablen, eab, deblen, deb, temp48a); + temp48blen = fast_expansion_sum_zeroelim(abdlen, abd, dealen, dea, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + deablen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, deab); + xlen = scale_expansion_zeroelim(deablen, deab, pc[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pc[0], det384x); + ylen = scale_expansion_zeroelim(deablen, deab, pc[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pc[1], det384y); + zlen = scale_expansion_zeroelim(deablen, deab, pc[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pc[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + clen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, cdet); + + temp48alen = fast_expansion_sum_zeroelim(abclen, abc, eaclen, eac, temp48a); + temp48blen = fast_expansion_sum_zeroelim(bcelen, bce, eablen, eab, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + eabclen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, eabc); + xlen = scale_expansion_zeroelim(eabclen, eabc, pd[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pd[0], det384x); + ylen = scale_expansion_zeroelim(eabclen, eabc, pd[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pd[1], det384y); + zlen = scale_expansion_zeroelim(eabclen, eabc, pd[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pd[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + dlen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, ddet); + + temp48alen = fast_expansion_sum_zeroelim(bcdlen, bcd, abdlen, abd, temp48a); + temp48blen = fast_expansion_sum_zeroelim(cdalen, cda, abclen, abc, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + abcdlen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, abcd); + xlen = scale_expansion_zeroelim(abcdlen, abcd, pe[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pe[0], det384x); + ylen = scale_expansion_zeroelim(abcdlen, abcd, pe[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pe[1], det384y); + zlen = scale_expansion_zeroelim(abcdlen, abcd, pe[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pe[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + elen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, edet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + cdelen = fast_expansion_sum_zeroelim(cdlen, cddet, elen, edet, cdedet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdelen, cdedet, deter); + + return deter[deterlen - 1]; +} + +static REAL insphereadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe, + REAL permanent) +{ + INEXACT REAL aex, bex, cex, dex, aey, bey, cey, dey, aez, bez, cez, dez; + REAL det, errbound; + + INEXACT REAL aexbey1, bexaey1, bexcey1, cexbey1; + INEXACT REAL cexdey1, dexcey1, dexaey1, aexdey1; + INEXACT REAL aexcey1, cexaey1, bexdey1, dexbey1; + REAL aexbey0, bexaey0, bexcey0, cexbey0; + REAL cexdey0, dexcey0, dexaey0, aexdey0; + REAL aexcey0, cexaey0, bexdey0, dexbey0; + REAL ab[4], bc[4], cd[4], da[4], ac[4], bd[4]; + INEXACT REAL ab3, bc3, cd3, da3, ac3, bd3; + REAL abeps, bceps, cdeps, daeps, aceps, bdeps; + REAL temp8a[8], temp8b[8], temp8c[8], temp16[16], temp24[24], temp48[48]; + int temp8alen, temp8blen, temp8clen, temp16len, temp24len, temp48len; + REAL xdet[96], ydet[96], zdet[96], xydet[192]; + int xlen, ylen, zlen, xylen; + REAL adet[288], bdet[288], cdet[288], ddet[288]; + int alen, blen, clen, dlen; + REAL abdet[576], cddet[576]; + int ablen, cdlen; + REAL fin1[1152]; + int finlength; + + REAL aextail, bextail, cextail, dextail; + REAL aeytail, beytail, ceytail, deytail; + REAL aeztail, beztail, ceztail, deztail; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + aex = (REAL) (pa[0] - pe[0]); + bex = (REAL) (pb[0] - pe[0]); + cex = (REAL) (pc[0] - pe[0]); + dex = (REAL) (pd[0] - pe[0]); + aey = (REAL) (pa[1] - pe[1]); + bey = (REAL) (pb[1] - pe[1]); + cey = (REAL) (pc[1] - pe[1]); + dey = (REAL) (pd[1] - pe[1]); + aez = (REAL) (pa[2] - pe[2]); + bez = (REAL) (pb[2] - pe[2]); + cez = (REAL) (pc[2] - pe[2]); + dez = (REAL) (pd[2] - pe[2]); + + Two_Product(aex, bey, aexbey1, aexbey0); + Two_Product(bex, aey, bexaey1, bexaey0); + Two_Two_Diff(aexbey1, aexbey0, bexaey1, bexaey0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + + Two_Product(bex, cey, bexcey1, bexcey0); + Two_Product(cex, bey, cexbey1, cexbey0); + Two_Two_Diff(bexcey1, bexcey0, cexbey1, cexbey0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + + Two_Product(cex, dey, cexdey1, cexdey0); + Two_Product(dex, cey, dexcey1, dexcey0); + Two_Two_Diff(cexdey1, cexdey0, dexcey1, dexcey0, cd3, cd[2], cd[1], cd[0]); + cd[3] = cd3; + + Two_Product(dex, aey, dexaey1, dexaey0); + Two_Product(aex, dey, aexdey1, aexdey0); + Two_Two_Diff(dexaey1, dexaey0, aexdey1, aexdey0, da3, da[2], da[1], da[0]); + da[3] = da3; + + Two_Product(aex, cey, aexcey1, aexcey0); + Two_Product(cex, aey, cexaey1, cexaey0); + Two_Two_Diff(aexcey1, aexcey0, cexaey1, cexaey0, ac3, ac[2], ac[1], ac[0]); + ac[3] = ac3; + + Two_Product(bex, dey, bexdey1, bexdey0); + Two_Product(dex, bey, dexbey1, dexbey0); + Two_Two_Diff(bexdey1, bexdey0, dexbey1, dexbey0, bd3, bd[2], bd[1], bd[0]); + bd[3] = bd3; + + temp8alen = scale_expansion_zeroelim(4, cd, bez, temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, -cez, temp8b); + temp8clen = scale_expansion_zeroelim(4, bc, dez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, aex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, -aex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, aey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, -aey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, aez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, -aez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + alen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, adet); + + temp8alen = scale_expansion_zeroelim(4, da, cez, temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, dez, temp8b); + temp8clen = scale_expansion_zeroelim(4, cd, aez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, bex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, bex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, bey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, bey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, bez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, bez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + blen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, bdet); + + temp8alen = scale_expansion_zeroelim(4, ab, dez, temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, aez, temp8b); + temp8clen = scale_expansion_zeroelim(4, da, bez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, cex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, -cex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, cey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, -cey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, cez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, -cez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + clen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, cdet); + + temp8alen = scale_expansion_zeroelim(4, bc, aez, temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, -bez, temp8b); + temp8clen = scale_expansion_zeroelim(4, ab, cez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, dex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, dex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, dey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, dey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, dez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, dez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + dlen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, fin1); + + det = estimate(finlength, fin1); + errbound = isperrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pe[0], aex, aextail); + Two_Diff_Tail(pa[1], pe[1], aey, aeytail); + Two_Diff_Tail(pa[2], pe[2], aez, aeztail); + Two_Diff_Tail(pb[0], pe[0], bex, bextail); + Two_Diff_Tail(pb[1], pe[1], bey, beytail); + Two_Diff_Tail(pb[2], pe[2], bez, beztail); + Two_Diff_Tail(pc[0], pe[0], cex, cextail); + Two_Diff_Tail(pc[1], pe[1], cey, ceytail); + Two_Diff_Tail(pc[2], pe[2], cez, ceztail); + Two_Diff_Tail(pd[0], pe[0], dex, dextail); + Two_Diff_Tail(pd[1], pe[1], dey, deytail); + Two_Diff_Tail(pd[2], pe[2], dez, deztail); + if ((aextail == 0.0) && (aeytail == 0.0) && (aeztail == 0.0) + && (bextail == 0.0) && (beytail == 0.0) && (beztail == 0.0) + && (cextail == 0.0) && (ceytail == 0.0) && (ceztail == 0.0) + && (dextail == 0.0) && (deytail == 0.0) && (deztail == 0.0)) { + return det; + } + + errbound = isperrboundC * permanent + resulterrbound * Absolute(det); + abeps = (aex * beytail + bey * aextail) + - (aey * bextail + bex * aeytail); + bceps = (bex * ceytail + cey * bextail) + - (bey * cextail + cex * beytail); + cdeps = (cex * deytail + dey * cextail) + - (cey * dextail + dex * ceytail); + daeps = (dex * aeytail + aey * dextail) + - (dey * aextail + aex * deytail); + aceps = (aex * ceytail + cey * aextail) + - (aey * cextail + cex * aeytail); + bdeps = (bex * deytail + dey * bextail) + - (bey * dextail + dex * beytail); + det += (((bex * bex + bey * bey + bez * bez) + * ((cez * daeps + dez * aceps + aez * cdeps) + + (ceztail * da3 + deztail * ac3 + aeztail * cd3)) + + (dex * dex + dey * dey + dez * dez) + * ((aez * bceps - bez * aceps + cez * abeps) + + (aeztail * bc3 - beztail * ac3 + ceztail * ab3))) + - ((aex * aex + aey * aey + aez * aez) + * ((bez * cdeps - cez * bdeps + dez * bceps) + + (beztail * cd3 - ceztail * bd3 + deztail * bc3)) + + (cex * cex + cey * cey + cez * cez) + * ((dez * abeps + aez * bdeps + bez * daeps) + + (deztail * ab3 + aeztail * bd3 + beztail * da3)))) + + 2.0 * (((bex * bextail + bey * beytail + bez * beztail) + * (cez * da3 + dez * ac3 + aez * cd3) + + (dex * dextail + dey * deytail + dez * deztail) + * (aez * bc3 - bez * ac3 + cez * ab3)) + - ((aex * aextail + aey * aeytail + aez * aeztail) + * (bez * cd3 - cez * bd3 + dez * bc3) + + (cex * cextail + cey * ceytail + cez * ceztail) + * (dez * ab3 + aez * bd3 + bez * da3))); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + return insphereexact(pa, pb, pc, pd, pe); +} + +REAL insphere(pa, pb, pc, pd, pe) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +REAL *pe; +{ + REAL aex, bex, cex, dex; + REAL aey, bey, cey, dey; + REAL aez, bez, cez, dez; + REAL aexbey, bexaey, bexcey, cexbey, cexdey, dexcey, dexaey, aexdey; + REAL aexcey, cexaey, bexdey, dexbey; + REAL alift, blift, clift, dlift; + REAL ab, bc, cd, da, ac, bd; + REAL abc, bcd, cda, dab; + REAL aezplus, bezplus, cezplus, dezplus; + REAL aexbeyplus, bexaeyplus, bexceyplus, cexbeyplus; + REAL cexdeyplus, dexceyplus, dexaeyplus, aexdeyplus; + REAL aexceyplus, cexaeyplus, bexdeyplus, dexbeyplus; + REAL det; + REAL permanent, errbound; + REAL ins; + + FPU_ROUND_DOUBLE; + + aex = pa[0] - pe[0]; + bex = pb[0] - pe[0]; + cex = pc[0] - pe[0]; + dex = pd[0] - pe[0]; + aey = pa[1] - pe[1]; + bey = pb[1] - pe[1]; + cey = pc[1] - pe[1]; + dey = pd[1] - pe[1]; + aez = pa[2] - pe[2]; + bez = pb[2] - pe[2]; + cez = pc[2] - pe[2]; + dez = pd[2] - pe[2]; + + aexbey = aex * bey; + bexaey = bex * aey; + ab = aexbey - bexaey; + bexcey = bex * cey; + cexbey = cex * bey; + bc = bexcey - cexbey; + cexdey = cex * dey; + dexcey = dex * cey; + cd = cexdey - dexcey; + dexaey = dex * aey; + aexdey = aex * dey; + da = dexaey - aexdey; + + aexcey = aex * cey; + cexaey = cex * aey; + ac = aexcey - cexaey; + bexdey = bex * dey; + dexbey = dex * bey; + bd = bexdey - dexbey; + + abc = aez * bc - bez * ac + cez * ab; + bcd = bez * cd - cez * bd + dez * bc; + cda = cez * da + dez * ac + aez * cd; + dab = dez * ab + aez * bd + bez * da; + + alift = aex * aex + aey * aey + aez * aez; + blift = bex * bex + bey * bey + bez * bez; + clift = cex * cex + cey * cey + cez * cez; + dlift = dex * dex + dey * dey + dez * dez; + + det = (dlift * abc - clift * dab) + (blift * cda - alift * bcd); + + aezplus = Absolute(aez); + bezplus = Absolute(bez); + cezplus = Absolute(cez); + dezplus = Absolute(dez); + aexbeyplus = Absolute(aexbey); + bexaeyplus = Absolute(bexaey); + bexceyplus = Absolute(bexcey); + cexbeyplus = Absolute(cexbey); + cexdeyplus = Absolute(cexdey); + dexceyplus = Absolute(dexcey); + dexaeyplus = Absolute(dexaey); + aexdeyplus = Absolute(aexdey); + aexceyplus = Absolute(aexcey); + cexaeyplus = Absolute(cexaey); + bexdeyplus = Absolute(bexdey); + dexbeyplus = Absolute(dexbey); + permanent = ((cexdeyplus + dexceyplus) * bezplus + + (dexbeyplus + bexdeyplus) * cezplus + + (bexceyplus + cexbeyplus) * dezplus) + * alift + + ((dexaeyplus + aexdeyplus) * cezplus + + (aexceyplus + cexaeyplus) * dezplus + + (cexdeyplus + dexceyplus) * aezplus) + * blift + + ((aexbeyplus + bexaeyplus) * dezplus + + (bexdeyplus + dexbeyplus) * aezplus + + (dexaeyplus + aexdeyplus) * bezplus) + * clift + + ((bexceyplus + cexbeyplus) * aezplus + + (cexaeyplus + aexceyplus) * bezplus + + (aexbeyplus + bexaeyplus) * cezplus) + * dlift; + errbound = isperrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + FPU_RESTORE; + return det; + } + + ins = insphereadapt(pa, pb, pc, pd, pe, permanent); + FPU_RESTORE; + return ins; +} diff --git a/gts/predicates.h b/gts/predicates.h new file mode 100644 index 0000000000..8b026ed80f --- /dev/null +++ b/gts/predicates.h @@ -0,0 +1,41 @@ +/* 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. + */ +/* Header file for robust predicates by Jonathan Richard Shewchuk */ + +#ifndef __PREDICATES_H__ +#define __PREDICATES_H__ + +double orient2d (double * pa, + double * pb, + double * pc); +double orient3d (double * pa, + double * pb, + double * pc, + double * pd); +double incircle (double * pa, + double * pb, + double * pc, + double * pd); +double insphere (double * pa, + double * pb, + double * pc, + double * pd, + double * pe); + +#endif /* __PREDICATES_H__ */ diff --git a/gts/psurface.c b/gts/psurface.c new file mode 100644 index 0000000000..6db3ae27f2 --- /dev/null +++ b/gts/psurface.c @@ -0,0 +1,471 @@ +/* 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); + } +} diff --git a/gts/refine.c b/gts/refine.c new file mode 100644 index 0000000000..eab585dd48 --- /dev/null +++ b/gts/refine.c @@ -0,0 +1,423 @@ +/* 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 "gts.h" + +/** + * gts_vertex_encroaches_edge: + * @v: a #GtsVertex. + * @e: a #GtsEdge. + * + * Returns: %TRUE if @v is strictly contained in the diametral circle of @e, + * %FALSE otherwise. + */ +gboolean gts_vertex_encroaches_edge (GtsVertex * v, GtsEdge * e) +{ + GtsPoint * p, * p1, * p2; + + g_return_val_if_fail (v != NULL, FALSE); + g_return_val_if_fail (e != NULL, FALSE); + + p = GTS_POINT (v); + p1 = GTS_POINT (GTS_SEGMENT (e)->v1); + p2 = GTS_POINT (GTS_SEGMENT (e)->v2); + + if ((p1->x - p->x)*(p2->x - p->x) + (p1->y - p->y)*(p2->y - p->y) < 0.0) + return TRUE; + return FALSE; +} + +/** + * gts_edge_is_encroached: + * @e: a #GtsEdge. + * @s: a #GtsSurface describing a (constrained) Delaunay triangulation. + * @encroaches: a #GtsEncroachFunc. + * @data: user data to be passed to @encroaches. + * + * Returns: a #GtsVertex belonging to @s and encroaching upon @e + * (as defined by @encroaches) or %NULL if there is none. + */ +GtsVertex * gts_edge_is_encroached (GtsEdge * e, + GtsSurface * s, + GtsEncroachFunc encroaches, + gpointer data) +{ + GSList * i; + + g_return_val_if_fail (e != NULL, NULL); + g_return_val_if_fail (s != NULL, NULL); + g_return_val_if_fail (encroaches != NULL, NULL); + + i = e->triangles; + while (i) { + GtsFace * f = i->data; + if (GTS_IS_FACE (f) && gts_face_has_parent_surface (f, s)) { + GtsVertex * v = gts_triangle_vertex_opposite (GTS_TRIANGLE (f), e); + if ((* encroaches) (v, e, s, data)) + return v; + } + i = i->next; + } + + return NULL; +} + +#define ALREADY_ENCROACHED(c) (GTS_OBJECT (c)->reserved) + +static void vertex_encroaches (GtsVertex * v, + GtsSurface * surface, + GtsFifo * encroached, + GtsEncroachFunc encroaches, + gpointer data) +{ + GSList * triangles, * i; + + g_return_if_fail (v != NULL); + g_return_if_fail (surface != NULL); + g_return_if_fail (encroached != NULL); + g_return_if_fail (encroaches != NULL); + + i = triangles = gts_vertex_triangles (v, NULL); + while (i) { + GtsFace * f = i->data; + if (GTS_IS_FACE (f) && gts_face_has_parent_surface (f, surface)) { + GtsEdge * e = gts_triangle_edge_opposite (i->data, v); + if (!ALREADY_ENCROACHED (e) && + GTS_IS_CONSTRAINT (e) && + (* encroaches) (v, e, surface, data)) { + gts_fifo_push (encroached, e); + ALREADY_ENCROACHED (e) = encroached; + } + } + i = i->next; + } + g_slist_free (triangles); +} + +static void make_encroached_fifo (GtsEdge * e, gpointer * datas) +{ + GtsFifo * fifo = datas[0]; + GtsSurface * s = datas[1]; + GtsEncroachFunc encroaches = (GtsEncroachFunc) datas[2]; + gpointer data = datas[3]; + + if (GTS_IS_CONSTRAINT (e) && + gts_edge_is_encroached (e, s, encroaches, data)) { + gts_fifo_push (fifo, e); + ALREADY_ENCROACHED (e) = fifo; + } +} + +#define SQUARE_ROOT_TWO 1.41421356237309504880168872420969807856967187 +#define DISTANCE_2D(v1, v2) (sqrt ((GTS_POINT (v2)->x - GTS_POINT (v1)->x)*\ + (GTS_POINT (v2)->x - GTS_POINT (v1)->x) +\ + (GTS_POINT (v2)->y - GTS_POINT (v1)->y)*\ + (GTS_POINT (v2)->y - GTS_POINT (v1)->y))) + +/* finds where to split the given edge to avoid infinite cycles. (see + Shewchuk's thesis for details */ +static GtsVertex * split_edge (GtsEdge * e, + GtsSurface * surface) +{ + GSList * i = e->triangles; + GtsEdge * c = NULL; + + /* look for constraints touching e */ + while (i && !c) { + GtsTriangle * t = i->data; + if (GTS_IS_FACE (t) && + gts_face_has_parent_surface (GTS_FACE (t), surface)) { + GtsEdge * e1, * e2; + if (t->e1 == e) { e1 = t->e2; e2 = t->e3; } + else if (t->e2 == e) { e1 = t->e1; e2 = t->e3; } + else { e1 = t->e1; e2 = t->e2; } + if (GTS_IS_CONSTRAINT (e1) && !GTS_IS_CONSTRAINT (e2)) + c = e1; + else if (GTS_IS_CONSTRAINT (e2) && !GTS_IS_CONSTRAINT (e1)) + c = e2; + } + i = i->next; + } + if (c) { + /* use power of two concentric shells */ + GtsVertex * v1 = GTS_SEGMENT (e)->v1; + GtsVertex * v2 = GTS_SEGMENT (e)->v2; + gdouble l = DISTANCE_2D (v1, v2); + gdouble nearestpower = 1., split; + + while (l > SQUARE_ROOT_TWO*nearestpower) + nearestpower *= 2.; + while (l < SQUARE_ROOT_TWO*nearestpower/2.) + nearestpower /= 2.; + split = nearestpower/l/2.; + + if (GTS_SEGMENT (c)->v1 == v2 || GTS_SEGMENT (c)->v2 == v2) + split = 1. - split; + return gts_vertex_new (surface->vertex_class, + (1. - split)*GTS_POINT (v1)->x + + split*GTS_POINT (v2)->x, + (1. - split)*GTS_POINT (v1)->y + + split*GTS_POINT (v2)->y, + (1. - split)*GTS_POINT (v1)->z + + split*GTS_POINT (v2)->z); + } + else + return gts_segment_midvertex (GTS_SEGMENT (e), surface->vertex_class); +} + +static gint split_encroached (GtsSurface * surface, + GtsFifo * encroached, + gint steiner_max, + GtsEncroachFunc encroaches, + gpointer data) +{ + GtsSegment * s; + + while (steiner_max-- != 0 && (s = gts_fifo_pop (encroached))) { + GtsVertex *add_vertex_returned; + GtsVertex * v = split_edge (GTS_EDGE (s), surface); + GtsFace * boundary = gts_edge_is_boundary (GTS_EDGE (s), surface); + GtsFace * f = boundary; +#if 1 + GtsEdge * e1 = GTS_EDGE (gts_object_clone (GTS_OBJECT (s))); + GtsEdge * e2 = GTS_EDGE (gts_object_clone (GTS_OBJECT (s))); + + GTS_SEGMENT (e1)->v1 = s->v1; + s->v1->segments = g_slist_prepend (s->v1->segments, e1); + GTS_SEGMENT (e1)->v2 = v; + v->segments = g_slist_prepend (v->segments, e1); + + GTS_SEGMENT (e2)->v1 = v; + v->segments = g_slist_prepend (v->segments, e2); + GTS_SEGMENT (e2)->v2 = s->v2; + s->v2->segments = g_slist_prepend (s->v2->segments, e2); +#else + GtsEdge * e1 = gts_edge_new (GTS_EDGE_CLASS (GTS_OBJECT (s)->klass), + s->v1, v); + GtsEdge * e2 = gts_edge_new (GTS_EDGE_CLASS (GTS_OBJECT (s)->klass), + v, s->v2); +#endif + + GTS_OBJECT (s)->klass = GTS_OBJECT_CLASS (surface->edge_class); + + if (f == NULL) + f = gts_edge_has_parent_surface (GTS_EDGE (s), surface); + g_assert (f != NULL); + add_vertex_returned = gts_delaunay_add_vertex_to_face (surface, v, f); + g_assert (add_vertex_returned == NULL); + + if (boundary) + gts_object_destroy (GTS_OBJECT (s)); + + vertex_encroaches (v, surface, encroached, encroaches, data); + + if (gts_edge_is_encroached (e1, surface, encroaches, data)) { + gts_fifo_push (encroached, e1); + ALREADY_ENCROACHED (e1) = encroached; + } + if (gts_edge_is_encroached (e2, surface, encroaches, data)) { + gts_fifo_push (encroached, e2); + ALREADY_ENCROACHED (e2) = encroached; + } + } + + return steiner_max; +} + +/** + * gts_delaunay_conform: + * @surface: a #GtsSurface describing a constrained Delaunay triangulation. + * @steiner_max: maximum number of Steiner points. + * @encroaches: a #GtsEncroachFunc. + * @data: user-data to pass to @encroaches. + * + * Recursively split constraints of @surface which are encroached by + * vertices of @surface (see Shewchuk 96 for details). The split + * constraints are destroyed and replaced by a set of new constraints + * of the same class. If gts_vertex_encroaches_edge() is used for + * @encroaches, the resulting surface will be Delaunay conforming. + * + * If @steiner_max is positive or nul, the recursive splitting + * procedure will stop when this maximum number of Steiner points is + * reached. In that case the resulting surface will not necessarily be + * Delaunay conforming. + * + * Returns: the number of remaining encroached edges. If @steiner_max + * is set to a negative value and gts_vertex_encroaches_edge() is used + * for @encroaches this should always be zero. + */ +guint gts_delaunay_conform (GtsSurface * surface, + gint steiner_max, + GtsEncroachFunc encroaches, + gpointer data) +{ + GtsFifo * encroached; + gpointer datas[4]; + guint encroached_number; + + g_return_val_if_fail (surface != NULL, 0); + g_return_val_if_fail (surface != NULL, 0); + g_return_val_if_fail (encroaches != NULL, 0); + + datas[0] = encroached = gts_fifo_new (); + datas[1] = surface; + datas[2] = encroaches; + datas[3] = data; + gts_surface_foreach_edge (surface, (GtsFunc) make_encroached_fifo, datas); + + split_encroached (surface, + encroached, + steiner_max, + encroaches, data); + gts_fifo_foreach (encroached, (GtsFunc) gts_object_reset_reserved, NULL); + encroached_number = gts_fifo_size (encroached); + gts_fifo_destroy (encroached); + return encroached_number; +} + +#define EHEAP_PAIR(f) (GTS_OBJECT (f)->reserved) + +static void heap_surface_add_face (GtsSurface * s, GtsFace * f) +{ + GtsEHeap * heap = GTS_OBJECT (s)->reserved; + gdouble key = gts_eheap_key (heap, f); + + if (key != 0.) + EHEAP_PAIR (f) = gts_eheap_insert_with_key (heap, f, key); + + if (GTS_SURFACE_CLASS (GTS_OBJECT (s)->klass->parent_class)->add_face) + (* GTS_SURFACE_CLASS (GTS_OBJECT (s)->klass->parent_class)->add_face) + (s, f); +} + +static void heap_surface_remove_face (GtsSurface * s, GtsFace * f) +{ + GtsEHeap * heap = GTS_OBJECT (s)->reserved; + + if (EHEAP_PAIR (f)) + gts_eheap_remove (heap, EHEAP_PAIR (f)); + + if (GTS_SURFACE_CLASS (GTS_OBJECT (s)->klass->parent_class)->remove_face) + (* GTS_SURFACE_CLASS (GTS_OBJECT (s)->klass->parent_class)->remove_face) + (s, f); +} + +static void heap_surface_class_init (GtsSurfaceClass * klass) +{ + klass->add_face = heap_surface_add_face; + klass->remove_face = heap_surface_remove_face; +} + +static GtsObjectClass * heap_surface_class_new (GtsObjectClass * parent_class) +{ + GtsObjectClassInfo heap_surface_info; + + heap_surface_info = parent_class->info; + heap_surface_info.class_init_func = (GtsObjectClassInitFunc) + heap_surface_class_init; + return gts_object_class_new (parent_class, + &heap_surface_info); +} + +static void make_face_heap (GtsFace * f, GtsEHeap * heap) +{ + gdouble key = gts_eheap_key (heap, f); + + if (key != 0.) + EHEAP_PAIR (f) = gts_eheap_insert_with_key (heap, f, key); +} + +/** + * gts_delaunay_refine: + * @surface: a #GtsSurface describing a conforming Delaunay triangulation. + * @steiner_max: maximum number of Steiner points. + * @encroaches: a #GtsEncroachFunc. + * @encroach_data: user-data to pass to @encroaches. + * @cost: a #GtsKeyFunc used to sort the faces during refinement. + * @cost_data: user-data to pass to @cost. + * + * An implementation of the refinement algorithm described in Ruppert + * (1995) and Shewchuk (1996). + * + * Returns: the number of unrefined faces of @surface left. Should be zero + * if @steiner_max is set to a negative value. + */ +guint gts_delaunay_refine (GtsSurface * surface, + gint steiner_max, + GtsEncroachFunc encroaches, + gpointer encroach_data, + GtsKeyFunc cost, + gpointer cost_data) +{ + GtsObjectClass * heap_surface_class; + GtsObjectClass * original_class; + GtsEHeap * heap; + GtsFifo * encroached; + GtsFace * f; + guint unrefined_number; + + g_return_val_if_fail (surface != NULL, 0); + g_return_val_if_fail (encroaches != NULL, 0); + g_return_val_if_fail (cost != NULL, 0); + + original_class = GTS_OBJECT (surface)->klass; + heap_surface_class = heap_surface_class_new (original_class); + GTS_OBJECT (surface)->klass = heap_surface_class; + + heap = gts_eheap_new (cost, cost_data); + gts_surface_foreach_face (surface, (GtsFunc) make_face_heap, heap); + encroached = gts_fifo_new (); + + GTS_OBJECT (surface)->reserved = heap; + + while (steiner_max-- != 0 && (f = gts_eheap_remove_top (heap, NULL))) { + GtsVertex *add_vertex_returned; + GtsVertex * c = + GTS_VERTEX (gts_triangle_circumcircle_center (GTS_TRIANGLE (f), + GTS_POINT_CLASS (surface->vertex_class))); + EHEAP_PAIR (f) = NULL; + g_assert (c != NULL); + add_vertex_returned = gts_delaunay_add_vertex (surface, c, f); + g_assert (add_vertex_returned == NULL); + + vertex_encroaches (c, surface, encroached, encroaches, encroach_data); + if (!gts_fifo_is_empty (encroached)) { + gts_delaunay_remove_vertex (surface, c); + steiner_max = split_encroached (surface, + encroached, + steiner_max, + encroaches, + encroach_data); + } + } + + unrefined_number = gts_eheap_size (heap); + gts_eheap_foreach (heap, (GFunc) gts_object_reset_reserved, NULL); + gts_eheap_destroy (heap); + + gts_fifo_foreach (encroached, (GtsFunc) gts_object_reset_reserved, NULL); + gts_fifo_destroy (encroached); + + GTS_OBJECT (surface)->klass = original_class; + GTS_OBJECT (surface)->reserved = NULL; + g_free (heap_surface_class); + + return unrefined_number; +} diff --git a/gts/rounding.h b/gts/rounding.h new file mode 100644 index 0000000000..053b32fffe --- /dev/null +++ b/gts/rounding.h @@ -0,0 +1,85 @@ +/* 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 "config.h" + +#ifdef HAVE_FPU_CONTROL_H +# include +# ifdef _FPU_EXTENDED +# if !defined(__alpha__) || !defined(__GLIBC__) +# if defined(__arm__) + static fpu_control_t fpu_round_double = _FPU_DEFAULT; +# else + static fpu_control_t fpu_round_double = + (_FPU_DEFAULT & ~ _FPU_EXTENDED)|_FPU_DOUBLE; +# endif + static fpu_control_t fpu_init; +# define FPU_ROUND_DOUBLE { _FPU_GETCW(fpu_init);\ + _FPU_SETCW(fpu_round_double); } +# define FPU_RESTORE {_FPU_SETCW(fpu_init);} +# else /* __alpha__ && __GLIBC__ */ +# define FPU_ROUND_DOUBLE +# define FPU_RESTORE +# endif /* __alpha__ && __GLIBC__ */ +# else /* not FPU_EXTENDED */ +# define FPU_ROUND_DOUBLE +# define FPU_RESTORE +# endif /* not FPU_EXTENDED */ +#else /* not HAVE_FPU_CONTROL_H */ +# ifdef __FreeBSD__ +# include +# define FPU_ROUND_DOUBLE (fpsetprec(FP_PD)) +# define FPU_RESTORE (fpsetprec(FP_PE)) +# else /* not __FreeBSD__ */ +# ifdef WIN32 +# ifdef _MSC_VER +# include + static unsigned int fpu_init; +# define FPU_ROUND_DOUBLE (fpu_init = _controlfp (0, 0),\ + _controlfp (_PC_53, MCW_PC)) +# define FPU_RESTORE (_controlfp (fpu_init, 0xfffff)) +# elif __MINGW32__ +# include + static unsigned int fpu_init; +# define FPU_ROUND_DOUBLE (fpu_init = _controlfp (0, 0),\ + _controlfp (_PC_53, _MCW_PC)) +# define FPU_RESTORE (_controlfp (fpu_init, 0xfffff)) +# else /* not _MSC_VER or __MINGW32__ */ +# error "You need MSVC or MinGW for the Win32 version" +# endif /* not _MSC_VER or __MINGW32__ */ +# else /* not WIN32 */ +# ifdef __CYGWIN__ + typedef unsigned int fpu_control_t __attribute__ ((__mode__ (__HI__))); + static fpu_control_t fpu_round_double = 0x027f; + static fpu_control_t fpu_init; +# define _FPU_GETCW(cw) __asm__ ("fnstcw %0" : "=m" (*&cw)) +# define _FPU_SETCW(cw) __asm__ ("fldcw %0" : : "m" (*&cw)) +# define FPU_ROUND_DOUBLE { _FPU_GETCW(fpu_init);\ + _FPU_SETCW(fpu_round_double); } +# define FPU_RESTORE { _FPU_SETCW(fpu_init);} +# else /* not __CYGWIN__ */ +# ifdef CPP_HAS_WARNING +# warning "Unknown CPU: assuming default double precision rounding" +# endif /* CPP_HAS_WARNING */ +# define FPU_ROUND_DOUBLE +# define FPU_RESTORE +# endif /* not __CYGWIN__ */ +# endif /* not WIN32 */ +# endif /* not __FreeBSD__ */ +#endif /* not HAVE_FPU_CONTROL_H */ diff --git a/gts/segment.c b/gts/segment.c new file mode 100644 index 0000000000..58a05403a8 --- /dev/null +++ b/gts/segment.c @@ -0,0 +1,233 @@ +/* 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 "gts.h" + +static void segment_destroy (GtsObject * object) +{ + GtsSegment * segment = GTS_SEGMENT (object); + GtsVertex * v1 = segment->v1; + GtsVertex * v2 = segment->v2; + + v1->segments = g_slist_remove (v1->segments, segment); + if (!GTS_OBJECT_DESTROYED (v1) && + !gts_allow_floating_vertices && v1->segments == NULL) + gts_object_destroy (GTS_OBJECT (v1)); + + v2->segments = g_slist_remove (v2->segments, segment); + if (!GTS_OBJECT_DESTROYED (v2) && + !gts_allow_floating_vertices && v2->segments == NULL) + gts_object_destroy (GTS_OBJECT (v2)); + + (* GTS_OBJECT_CLASS (gts_segment_class ())->parent_class->destroy) (object); +} + +static void segment_class_init (GtsObjectClass * klass) +{ + klass->destroy = segment_destroy; +} + +static void segment_init (GtsSegment * segment) +{ + segment->v1 = segment->v2 = NULL; +} + +/** + * gts_segment_class: + * + * Returns: the #GtsSegmentClass. + */ +GtsSegmentClass * gts_segment_class (void) +{ + static GtsSegmentClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo segment_info = { + "GtsSegment", + sizeof (GtsSegment), + sizeof (GtsSegmentClass), + (GtsObjectClassInitFunc) segment_class_init, + (GtsObjectInitFunc) segment_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (gts_object_class (), + &segment_info); + } + + return klass; +} + +/** + * gts_segment_new: + * @klass: a #GtsSegmentClass. + * @v1: a #GtsVertex. + * @v2: another #GtsVertex different from @v1. + * + * Returns: a new #GtsSegment linking @v1 and @v2. + */ +GtsSegment * gts_segment_new (GtsSegmentClass * klass, + GtsVertex * v1, GtsVertex * v2) +{ + GtsSegment * s; + + g_return_val_if_fail (v1 != NULL, NULL); + g_return_val_if_fail (v2 != NULL, NULL); + g_return_val_if_fail (v1 != v2, NULL); + + s = GTS_SEGMENT (gts_object_new (GTS_OBJECT_CLASS (klass))); + s->v1 = v1; + s->v2 = v2; + v1->segments = g_slist_prepend (v1->segments, s); + v2->segments = g_slist_prepend (v2->segments, s); + + return s; +} + +/** + * gts_segment_is_duplicate: + * @s: a #GtsSegment. + * + * Returns: the first #GtsSegment different from @s which shares the + * same endpoints or %NULL if there is none. + */ +GtsSegment * gts_segment_is_duplicate (GtsSegment * s) +{ + GSList * i; + GtsVertex * v2; + + g_return_val_if_fail (s != NULL, NULL); + + v2 = s->v2; + i = s->v1->segments; + if (s->v1 == v2) /* s is degenerate: special treatment */ + while (i) { + GtsSegment * s1 = i->data; + if (s1 != s && s1->v1 == v2 && s1->v2 == v2) + return s1; + i = i->next; + } + else /* s is not degenerate */ + while (i) { + GtsSegment * s1 = i->data; + if (s1 != s && (s1->v1 == v2 || s1->v2 == v2)) + return s1; + i = i->next; + } + return NULL; +} + +/** + * gts_segments_are_intersecting: + * @s1: a #GtsSegment. + * @s2: a #GtsSegment. + * + * Returns: %GTS_IN if @s1 and @s2 are intersecting, %GTS_ON if one of the + * endpoints of @s1 (resp. @s2) lies on @s2 (resp. @s1), %GTS_OUT otherwise. + */ +GtsIntersect gts_segments_are_intersecting (GtsSegment * s1, GtsSegment * s2) +{ + GtsPoint * p1, * p2, * p3, * p4; + gdouble d1, d2, d3, d4; + + g_return_val_if_fail (s1 != NULL && s2 != NULL, FALSE); + + p1 = GTS_POINT (s1->v1); p2 = GTS_POINT (s1->v2); + p3 = GTS_POINT (s2->v1); p4 = GTS_POINT (s2->v2); + d1 = gts_point_orientation (p1, p2, p3); + d2 = gts_point_orientation (p1, p2, p4); + if ((d1 > 0.0 && d2 > 0.0) || + (d1 < 0.0 && d2 < 0.0)) + return GTS_OUT; + d3 = gts_point_orientation (p3, p4, p1); + d4 = gts_point_orientation (p3, p4, p2); + if ((d3 > 0.0 && d4 > 0.0) || + (d3 < 0.0 && d4 < 0.0)) + return GTS_OUT; + if (d1 == 0.0 || d2 == 0.0 || d3 == 0.0 || d4 == 0.0) + return GTS_ON; + return GTS_IN; +} + +/** + * gts_segment_midvertex: + * @s: a #GtsSegment. + * @klass: a #GtsVertexClass to be used for the new vertex. + * + * Returns: a new #GtsVertex, midvertex of @s. + */ +GtsVertex * gts_segment_midvertex (GtsSegment * s, GtsVertexClass * klass) +{ + GtsPoint * p1, * p2; + + g_return_val_if_fail (s != NULL, NULL); + g_return_val_if_fail (klass != NULL, NULL); + + p1 = GTS_POINT (s->v1); p2 = GTS_POINT (s->v2); + return gts_vertex_new (klass, + (p1->x + p2->x)/2., + (p1->y + p2->y)/2., + (p1->z + p2->z)/2.); +} + +/** + * gts_segments_from_vertices: + * @vertices: a list of #GtsVertex. + * + * Returns: a list of unique #GtsSegment which have one of their vertices in + * @vertices. + */ +GSList * gts_segments_from_vertices (GSList * vertices) +{ + GHashTable * hash; + GSList * segments = NULL, * i; + + hash = g_hash_table_new (NULL, NULL); + i = vertices; + while (i) { + GSList * j = GTS_VERTEX (i->data)->segments; + while (j) { + GtsSegment * s = j->data; + if (g_hash_table_lookup (hash, s) == NULL) { + segments = g_slist_prepend (segments, s); + g_hash_table_insert (hash, s, i); + } + j = j->next; + } + i = i->next; + } + g_hash_table_destroy (hash); + return segments; +} + +/** + * gts_segment_is_ok: + * @s: a #GtsSegment. + * + * Returns: %TRUE if @s is not degenerate (i.e. @s->v1 != @s->v2) and not + * duplicate, %FALSE otherwise. + */ +gboolean gts_segment_is_ok (GtsSegment * s) +{ + g_return_val_if_fail (s != NULL, FALSE); + g_return_val_if_fail (s->v1 != s->v2, FALSE); + g_return_val_if_fail (!gts_segment_is_duplicate (s), FALSE); + g_return_val_if_fail (GTS_OBJECT (s)->reserved == NULL, FALSE); + return TRUE; +} diff --git a/gts/split.c b/gts/split.c new file mode 100644 index 0000000000..b7bee77fe5 --- /dev/null +++ b/gts/split.c @@ -0,0 +1,1843 @@ +/* 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 DYNAMIC_SPLIT +#define NEW + +/* #define DEBUG + #define DEBUG_HEXPAND + #define DEBUG_EXPAND */ + +struct _GtsSplitCFace { + GtsFace * f; + GtsTriangle ** a1, ** a2; +}; + +typedef struct _CFace CFace; +typedef struct _CFaceClass CFaceClass; + +struct _CFace { + GtsObject object; + + GtsSplit * parent_split; + GtsTriangle * t; + guint flags; +}; +/* the size of the CFace structure must be smaller or equal to the size + of the GtsFace structure as both structures use the same memory location */ + +struct _CFaceClass { + GtsObjectClass parent_class; +}; + +#define IS_CFACE(obj) (gts_object_is_from_class (obj, cface_class ())) +#define CFACE(obj) ((CFace *) obj) +#define CFACE_ORIENTATION(cf) ((cf)->flags & 0x1) +#define CFACE_ORIENTATION_DIRECT(cf) ((cf)->flags |= 0x1) +#define CFACE_VVS(cf) ((cf)->flags & 0x2) +#define CFACE_VVS_DIRECT(cf) ((cf)->flags |= 0x2) +#define CFACE_E1 0x4 +#define CFACE_E2 0x8 +#define CFACE_KEEP_VVS 0x10 + +#define ROTATE_ORIENT(e, e1, e2, e3) { if (e1 == e) { e1 = e2; e2 = e3; }\ + else if (e2 == e) { e2 = e1; e1 = e3; }\ + else g_assert (e3 == e); } +#define SEGMENT_USE_VERTEX(s, v) ((s)->v1 == v || (s)->v2 == v) +#define TRIANGLE_REPLACE_EDGE(t, e, with) { if ((t)->e1 == e)\ + (t)->e1 = with;\ + else if ((t)->e2 == e)\ + (t)->e2 = with;\ + else {\ + g_assert ((t)->e3 == e);\ + (t)->e3 = with;\ + }\ + } + +#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 GtsObjectClass * cface_class (void) +{ + static GtsObjectClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo cface_info = { + "GtsCFace", + sizeof (CFace), + sizeof (CFaceClass), + (GtsObjectClassInitFunc) NULL, + (GtsObjectInitFunc) NULL, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (gts_object_class (), &cface_info); + g_assert (sizeof (CFace) <= sizeof (GtsFace)); + } + + return klass; +} + +/* Replace @e with @with for all the triangles using @e but @f. + Destroys @e and removes it from @heap (if not %NULL). + Returns a triangle using e different from f or %NULL. */ +static GtsTriangle * replace_edge_collapse (GtsEdge * e, + GtsEdge * with, + CFace * cf, + GtsEHeap * heap +#ifdef DYNAMIC_SPLIT + , GtsTriangle *** a1 +#endif +#ifdef NEW + , guint edge_flag +#endif + ) +{ + GSList * i; + GtsTriangle * rt = NULL; +#ifdef DYNAMIC_SPLIT + guint size; + GtsTriangle ** a; +#endif + +#ifdef NEW + i = e->triangles; + e->triangles = NULL; + size = g_slist_length (i)*sizeof (GtsTriangle *); + *a1 = a = g_malloc (size > 0 ? size : sizeof (GtsTriangle *)); + while (i) { + GtsTriangle * t = i->data; + GSList * next = i->next; + if (t != ((GtsTriangle *) cf)) { + if (IS_CFACE (t)) { + i->next = e->triangles; + e->triangles = i; + /* set the edge given by edge_flag (CFACE_E1 or CFACE_E2) */ + GTS_OBJECT (t)->reserved = GUINT_TO_POINTER (edge_flag); + cf->flags |= CFACE_KEEP_VVS; + } + else { + TRIANGLE_REPLACE_EDGE (t, e, with); + i->next = with->triangles; + with->triangles = i; + rt = t; + *(a++) = t; + } + } + else + g_slist_free_1 (i); + i = next; + } + *a = NULL; + if (!e->triangles) { + if (heap) + HEAP_REMOVE_OBJECT (heap, e); + gts_object_destroy (GTS_OBJECT (e)); + } +#else /* not NEW */ + i = e->triangles; +#ifdef DYNAMIC_SPLIT + size = g_slist_length (i)*sizeof (GtsTriangle *); + *a1 = a = g_malloc (size > 0 ? size : sizeof (GtsTriangle *)); +#endif + while (i) { + GtsTriangle * t = i->data; + GSList * next = i->next; + if (t != ((GtsTriangle *) cf)) { + TRIANGLE_REPLACE_EDGE (t, e, with); + i->next = with->triangles; + with->triangles = i; + rt = t; +#ifdef DYNAMIC_SPLIT + *(a++) = t; +#endif + } + else + g_slist_free_1 (i); + i = next; + } +#ifdef DYNAMIC_SPLIT + *a = NULL; +#endif + if (heap) + HEAP_REMOVE_OBJECT (heap, e); + e->triangles = NULL; + gts_object_destroy (GTS_OBJECT (e)); +#endif /* NEW */ + + return rt; +} + +static CFace * cface_new (GtsFace * f, + GtsEdge * e, + GtsVertex * v1, + GtsVertex * v2, + GtsSplit * vs, + GtsEHeap * heap, + GtsEdgeClass * klass +#ifdef DYNAMIC_SPLIT + , GtsSplitCFace * scf +#endif + ) +{ + CFace * cf; + GtsVertex * v; + GtsEdge * e1, * e2, * e3, * vvs; + GSList * i; + GtsTriangle * t, * t1 = NULL, * t2 = NULL; + guint flags; + + g_return_val_if_fail (f != NULL, NULL); +#ifndef NEW + g_return_val_if_fail (GTS_IS_FACE (f), NULL); +#endif + g_return_val_if_fail (e != NULL, NULL); + g_return_val_if_fail (vs != NULL, NULL); + + t = ((GtsTriangle *) f); + if (heap) + g_return_val_if_fail (!gts_triangle_is_duplicate (t), NULL); + +#ifdef NEW + /* get CFACE_E1 and CFACE_E2 info */ + flags = GPOINTER_TO_UINT (GTS_OBJECT (f)->reserved); +#endif + GTS_OBJECT_SET_FLAGS (f, GTS_DESTROYED); + + i = f->surfaces; + while (i) { + GSList * next = i->next; + gts_surface_remove_face (i->data, f); + i = next; + } + g_slist_free (f->surfaces); + + e1 = t->e1; e2 = t->e2; e3 = t->e3; + ROTATE_ORIENT (e, e1, e2, e3); + + cf = (CFace *) f; +#ifndef NEW + GTS_OBJECT (cf)->klass = cface_class (); +#else + cf->flags = flags; +#endif + gts_object_init (GTS_OBJECT (cf), cface_class ()); + cf->parent_split = vs; + + if (SEGMENT_USE_VERTEX (GTS_SEGMENT (e1), v2)) { + CFACE_ORIENTATION_DIRECT (cf); /* v1->v2->v */ + e3 = e1; e1 = e2; e2 = e3; + } + v = GTS_SEGMENT (e1)->v1 == v1 ? + GTS_SEGMENT (e1)->v2 : GTS_SEGMENT (e1)->v1; +#ifdef NEW + if ((cf->flags & CFACE_E1) || (cf->flags & CFACE_E2)) { + vvs = GTS_EDGE (gts_vertices_are_connected (vs->v, v)); + g_assert (vvs != NULL); + } else +#endif + vvs = gts_edge_new (klass, v, vs->v); + + t1 = replace_edge_collapse (e1, vvs, cf, heap +#ifdef DYNAMIC_SPLIT + , &scf->a1 +#endif +#ifdef NEW + , CFACE_E1 +#endif + ); + t2 = replace_edge_collapse (e2, vvs, cf, heap +#ifdef DYNAMIC_SPLIT + , &scf->a2 +#endif +#ifdef NEW + , CFACE_E2 +#endif + ); + t = cf->t = t1 ? t1 : t2; + g_assert (t); + + /* set up flags necessary to find vvs */ + if (t->e1 == vvs) e2 = t->e2; + else if (t->e2 == vvs) e2 = t->e3; + else { + g_assert (t->e3 == vvs); + e2 = t->e1; + } + if (SEGMENT_USE_VERTEX (GTS_SEGMENT (e2), v)) + CFACE_VVS_DIRECT (cf); + + return cf; +} + +static void find_vvs (GtsVertex * vs, + GtsTriangle * t, + GtsVertex ** v, GtsEdge ** vvs, + gboolean orientation) +{ + GtsEdge * e1 = t->e1, * e2 = t->e2, * e3 = t->e3, * tmp; + + if (SEGMENT_USE_VERTEX (GTS_SEGMENT (e2), vs)) { + tmp = e1; e1 = e2; e2 = e3; e3 = tmp; + } + else if (SEGMENT_USE_VERTEX (GTS_SEGMENT (e3), vs)) { + tmp = e1; e1 = e3; e3 = e2; e2 = tmp; + } + else + g_assert (SEGMENT_USE_VERTEX (GTS_SEGMENT (e1), vs)); + if (SEGMENT_USE_VERTEX (GTS_SEGMENT (e2), vs) || + !gts_segments_touch (GTS_SEGMENT (e1), GTS_SEGMENT (e2))) { + tmp = e1; e1 = e2; e2 = e3; e3 = tmp; + g_assert (gts_segments_touch (GTS_SEGMENT (e1), GTS_SEGMENT (e2))); + } + + *vvs = orientation ? e1 : e3; + + if (GTS_SEGMENT (*vvs)->v1 != vs) { + g_assert (GTS_SEGMENT (*vvs)->v2 == vs); + *v = GTS_SEGMENT (*vvs)->v1; + } + else + *v = GTS_SEGMENT (*vvs)->v2; +} + +static void replace_edge_expand (GtsEdge * e, + GtsEdge * with, + GtsTriangle ** a, + GtsVertex * v) +{ + GtsTriangle ** i = a, * t; + + while ((t = *(i++))) { +#ifdef DEBUG_EXPAND + g_assert (!IS_CFACE (t)); + fprintf (stderr, "replacing %p->%d: e: %p->%d with: %p->%d\n", + t, id (t), e, id (e), with, id (with)); +#endif + TRIANGLE_REPLACE_EDGE (t, e, with); + with->triangles = g_slist_prepend (with->triangles, t); + if (GTS_OBJECT (t)->reserved) { + /* apart from the triangles having e as an edge, t is the only + triangle using v */ + g_assert (GTS_OBJECT (t)->reserved == v); + GTS_OBJECT (t)->reserved = NULL; + } + else + GTS_OBJECT (t)->reserved = v; + } +} + +static void cface_expand (CFace * cf, + GtsTriangle ** a1, + GtsTriangle ** a2, + GtsEdge * e, + GtsVertex * v1, + GtsVertex * v2, + GtsVertex * vs, + GtsEdgeClass * klass) +{ + GtsVertex * v; + GtsEdge * e1, * e2, * vvs; + gboolean orientation; + guint flags; + + g_return_if_fail (cf != NULL); + g_return_if_fail (IS_CFACE (cf)); + g_return_if_fail (e != NULL); + g_return_if_fail (vs != NULL); + + flags = cf->flags; + orientation = CFACE_ORIENTATION (cf); + + find_vvs (vs, cf->t, &v, &vvs, CFACE_VVS (cf)); + +#ifdef NEW + if (flags & CFACE_E1) + e1 = GTS_EDGE (gts_vertices_are_connected (v1, v)); + else + e1 = gts_edge_new (klass, v, v1); + if (flags & CFACE_E2) + e2 = GTS_EDGE (gts_vertices_are_connected (v2, v)); + else + e2 = gts_edge_new (klass, v, v2); +#else + e1 = gts_edge_new (v, v1); + e2 = gts_edge_new (v, v2); +#endif + + replace_edge_expand (vvs, e1, a1, v1); + replace_edge_expand (vvs, e2, a2, v2); + +#ifdef NEW + if (!(flags & CFACE_KEEP_VVS)) { + g_slist_free (vvs->triangles); + vvs->triangles = NULL; + gts_object_destroy (GTS_OBJECT (vvs)); + } +#else + g_slist_free (vvs->triangles); + vvs->triangles = NULL; + gts_object_destroy (GTS_OBJECT (vvs)); +#endif + + /* gts_face_new : because I am "creating" a face */ + GTS_OBJECT (cf)->klass = GTS_OBJECT_CLASS (gts_face_class ()); + gts_object_init (GTS_OBJECT (cf), GTS_OBJECT (cf)->klass); + + if (orientation) + gts_triangle_set (GTS_TRIANGLE (cf), e, e2, e1); + else + gts_triangle_set (GTS_TRIANGLE (cf), e, e1, e2); +} + +static void split_destroy (GtsObject * object) +{ + GtsSplit * vs = GTS_SPLIT (object); + guint i = vs->ncf; + GtsSplitCFace * cf = vs->cfaces; + + while (i--) { + if (IS_CFACE (cf->f)) + gts_object_destroy (GTS_OBJECT (cf->f)); + g_free (cf->a1); + g_free (cf->a2); + cf++; + } + g_free (vs->cfaces); + + if (!gts_allow_floating_vertices && vs->v && vs->v->segments == NULL) + gts_object_destroy (GTS_OBJECT (vs->v)); + + (* GTS_OBJECT_CLASS (gts_split_class ())->parent_class->destroy) (object); +} + +static void split_class_init (GtsObjectClass * klass) +{ + klass->destroy = split_destroy; +} + +static void split_init (GtsSplit * split) +{ + split->v1 = split->v2 = NULL; + split->v = NULL; + split->cfaces = NULL; + split->ncf = 0; +} + +/** + * gts_split_class: + * + * Returns: the #GtsSplitClass. + */ +GtsSplitClass * gts_split_class (void) +{ + static GtsSplitClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo split_info = { + "GtsSplit", + sizeof (GtsSplit), + sizeof (GtsSplitClass), + (GtsObjectClassInitFunc) split_class_init, + (GtsObjectInitFunc) split_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (gts_object_class (), + &split_info); + } + + return klass; +} + +#ifdef DEBUG +static gboolean edge_collapse_is_valid (GtsEdge * e) +{ + GSList * i; + + g_return_val_if_fail (e != NULL, FALSE); + + if (gts_segment_is_duplicate (GTS_SEGMENT (e))) { + g_warning ("collapsing duplicate edge"); + return FALSE; + } + + i = GTS_SEGMENT (e)->v1->segments; + while (i) { + GtsEdge * e1 = i->data; + if (e1 != e && GTS_IS_EDGE (e1)) { + GtsEdge * e2 = NULL; + GSList * j = GTS_SEGMENT (e1)->v1 == GTS_SEGMENT (e)->v1 ? + GTS_SEGMENT (e1)->v2->segments : GTS_SEGMENT (e1)->v1->segments; + while (j && !e2) { + GtsEdge * e1 = j->data; + if (GTS_IS_EDGE (e1) && + (GTS_SEGMENT (e1)->v1 == GTS_SEGMENT (e)->v2 || + GTS_SEGMENT (e1)->v2 == GTS_SEGMENT (e)->v2)) + e2 = e1; + j = j->next; + } + if (e2 && !gts_triangle_use_edges (e, e1, e2)) { + g_warning ("collapsing empty triangle"); + return FALSE; + } + } + i = i->next; + } + + if (gts_edge_is_boundary (e, NULL)) { + GtsTriangle * t = e->triangles->data; + if (gts_edge_is_boundary (t->e1, NULL) && + gts_edge_is_boundary (t->e2, NULL) && + gts_edge_is_boundary (t->e3, NULL)) { + g_warning ("collapsing single triangle"); + return FALSE; + } + } + else { + if (gts_vertex_is_boundary (GTS_SEGMENT (e)->v1, NULL) && + gts_vertex_is_boundary (GTS_SEGMENT (e)->v2, NULL)) { + g_warning ("collapsing two sides of a strip"); + return FALSE; + } + if (gts_edge_belongs_to_tetrahedron (e)) { + g_warning ("collapsing tetrahedron"); + return FALSE; + } + } + + return TRUE; +} +#endif /* DEBUG */ + +/* Not currently used. May be useful for some debug code */ +#ifdef DEBUG +static void print_split (GtsSplit * vs, FILE * fptr) +{ + guint j; + GtsSplitCFace * cf; + + g_return_if_fail (vs != NULL); + g_return_if_fail (fptr != NULL); + + fprintf (fptr, "%p: v: %p v1: %p v2: %p ncf: %u cfaces: %p\n", + vs, vs->v, vs->v1, vs->v2, vs->ncf, vs->cfaces); + cf = vs->cfaces; + j = vs->ncf; + while (j--) { + fprintf (stderr, " f: %p a1: %p a2: %p\n", + cf->f, cf->a1, cf->a2); + cf++; + } +} +#endif + +/** + * gts_split_collapse: + * @vs: a #GtsSplit. + * @klass: a #GtsEdgeClass. + * @heap: a #GtsEHeap or %NULL. + * + * Collapses the vertex split @vs. Any new edge created during the process will + * be of class @klass. If heap is not %NULL, the new edges will be inserted + * into it and the destroyed edges will be removed from it. + */ +void gts_split_collapse (GtsSplit * vs, + GtsEdgeClass * klass, + GtsEHeap * heap) +{ + GtsEdge * e; + GtsVertex * v, * v1, * v2; + GSList * i, * end; +#ifdef DYNAMIC_SPLIT + GtsSplitCFace * cf; + guint j; +#endif +#ifdef DEBUG + gboolean invalid = FALSE; + static guint ninvalid = 0; +#endif + + g_return_if_fail (vs != NULL); + g_return_if_fail (klass != NULL); + + v = vs->v; + + g_return_if_fail (v->segments == NULL); + + /* we don't want to destroy vertices */ + gts_allow_floating_vertices = TRUE; + + v1 = GTS_SPLIT_V1 (vs); + v2 = GTS_SPLIT_V2 (vs); + e = GTS_EDGE (gts_vertices_are_connected (v1, v2)); + g_assert (e != NULL); + +#ifdef DEBUG + fprintf (stderr, "collapsing %p: v1: %p v2: %p v: %p\n", vs, v1, v2, v); + if (!edge_collapse_is_valid (e)) { + char fname[80]; + FILE * fptr; + GSList * triangles, * i; + + g_warning ("invalid edge collapse"); + invalid = TRUE; + sprintf (fname, "invalid.%d", ninvalid); + fptr = fopen (fname, "wt"); + gts_write_segment (GTS_SEGMENT (e), GTS_POINT (v), fptr); + triangles = gts_vertex_triangles (v1, NULL); + triangles = gts_vertex_triangles (v2, triangles); + i = triangles; + while (i) { + gts_write_triangle (i->data, GTS_POINT (v), fptr); + i = i->next; + } + g_slist_free (triangles); + fclose (fptr); + } +#endif + + i = e->triangles; +#ifdef DYNAMIC_SPLIT + cf = vs->cfaces; + j = vs->ncf; + while (j--) { + g_free (cf->a1); + g_free (cf->a2); + cf++; + } + g_free (vs->cfaces); + + vs->ncf = g_slist_length (i); + g_assert (vs->ncf > 0); + cf = vs->cfaces = g_malloc (vs->ncf*sizeof (GtsSplitCFace)); +#endif /* DYNAMIC_SPLIT */ +#ifdef NEW + while (i) { + cf->f = i->data; + g_assert (GTS_IS_FACE (cf->f)); + GTS_OBJECT (cf->f)->klass = GTS_OBJECT_CLASS (cface_class ()); + cf++; + i = i->next; + } + i = e->triangles; + cf = vs->cfaces; + while (i) { + cface_new (i->data, e, v1, v2, vs, heap, klass, cf); +#ifdef DEBUG + fprintf (stderr, "cface: %p->%d t: %p->%d a1: ", + cf->f, id (cf->f), CFACE (cf->f)->t, id (CFACE (cf->f)->t)); + { + GtsTriangle * t, ** a; + a = cf->a1; + while ((t = *(a++))) + fprintf (stderr, "%p->%d ", t, id (t)); + fprintf (stderr, "a2: "); + a = cf->a2; + while ((t = *(a++))) + fprintf (stderr, "%p->%d ", t, id (t)); + fprintf (stderr, "\n"); + } +#endif + cf++; + i = i->next; + } +#else /* not NEW */ + while (i) { + cface_new (i->data, e, v1, v2, vs, heap +#ifdef DYNAMIC_SPLIT + , cf +#endif /* DYNAMIC_SPLIT */ + ); +#ifdef DYNAMIC_SPLIT + cf->f = i->data; + cf++; +#endif /* DYNAMIC_SPLIT */ + i = i->next; + } +#endif /* NEW */ + g_slist_free (e->triangles); + e->triangles = NULL; + gts_object_destroy (GTS_OBJECT (e)); + + gts_allow_floating_vertices = FALSE; + + end = NULL; + i = v1->segments; + while (i) { + GtsSegment * s = i->data; + if (s->v1 == v1) + s->v1 = v; + else + s->v2 = v; + end = i; + i = i->next; + } + if (end) { + end->next = v->segments; + v->segments = v1->segments; + v1->segments = NULL; + } + + end = NULL; + i = v2->segments; + while (i) { + GtsSegment * s = i->data; + if (s->v1 == v2) + s->v1 = v; + else + s->v2 = v; + end = i; + i = i->next; + } + if (end) { + end->next = v->segments; + v->segments = v2->segments; + v2->segments = NULL; + } + +#ifdef DEBUG + if (invalid) { + char fname[80]; + FILE * fptr; + GSList * triangles, * i; + GtsSurface * surface = NULL; + + sprintf (fname, "invalid_after.%d", ninvalid); + fptr = fopen (fname, "wt"); + triangles = gts_vertex_triangles (v, NULL); + i = triangles; + while (i) { + GtsTriangle * t = i->data; + fprintf (stderr, "checking %p->%d\n", t, id (t)); + g_assert (GTS_IS_FACE (t)); + gts_write_triangle (t, GTS_POINT (v), fptr); + surface = GTS_FACE (t)->surfaces->data; + if (gts_triangle_is_duplicate (t)) + fprintf (stderr, "%p->%d is duplicate\n", t, id (t)); + if (gts_segment_is_duplicate (GTS_SEGMENT (t->e1))) + fprintf (stderr, "e1 of %p->%d is duplicate\n", t, id (t)); + if (gts_segment_is_duplicate (GTS_SEGMENT (t->e2))) + fprintf (stderr, "e2 of %p->%d is duplicate\n", t, id (t)); + if (gts_segment_is_duplicate (GTS_SEGMENT (t->e3))) + fprintf (stderr, "e3 of %p->%d is duplicate\n", t, id (t)); + i = i->next; + } + fclose (fptr); + g_slist_free (triangles); +#if 0 + gts_split_expand (vs, surface); + + sprintf (fname, "invalid_after_after.%d", ninvalid); + fptr = fopen (fname, "wt"); + triangles = gts_vertex_triangles (v1, NULL); + triangles = gts_vertex_triangles (v2, triangles); + i = triangles; + while (i) { + GtsTriangle * t = i->data; + gts_write_triangle (t, GTS_POINT (v), fptr); + surface = GTS_FACE (t)->surfaces->data; + if (gts_triangle_is_duplicate (t)) + fprintf (stderr, "%p->%d is duplicate\n", t, id (t)); + if (gts_segment_is_duplicate (GTS_SEGMENT (t->e1))) + fprintf (stderr, "e1 of %p->%d is duplicate\n", t, id (t)); + if (gts_segment_is_duplicate (GTS_SEGMENT (t->e2))) + fprintf (stderr, "e2 of %p->%d is duplicate\n", t, id (t)); + if (gts_segment_is_duplicate (GTS_SEGMENT (t->e3))) + fprintf (stderr, "e3 of %p->%d is duplicate\n", t, id (t)); + i = i->next; + } + fclose (fptr); + g_slist_free (triangles); + + exit (1); +#endif + ninvalid++; + } +#endif +} + +/** + * gts_split_expand: + * @vs: a #GtsSplit. + * @s: a #GtsSurface. + * @klass: a #GtsEdgeClass. + * + * Expands the vertex split @vs adding the newly created faces to @s. Any + * new edge will be of class @klass. + */ +void gts_split_expand (GtsSplit * vs, + GtsSurface * s, + GtsEdgeClass * klass) +{ + GSList * i; + GtsEdge * e; + GtsVertex * v, * v1, * v2; + gboolean changed = FALSE; + GtsSplitCFace * cf; + guint j; + + g_return_if_fail (vs != NULL); + g_return_if_fail (s != NULL); + g_return_if_fail (klass != NULL); + + /* we don't want to destroy vertices */ + gts_allow_floating_vertices = TRUE; + + v1 = GTS_SPLIT_V1 (vs); + v2 = GTS_SPLIT_V2 (vs); + v = vs->v; +#ifdef DEBUG_EXPAND + fprintf (stderr, "expanding %p->%d: v1: %p->%d v2: %p->%d v: %p->%d\n", + vs, id (vs), v1, id (v1), v2, id (v2), v, id (v)); +#endif + e = gts_edge_new (klass, v1, v2); + cf = vs->cfaces; + j = vs->ncf; + while (j--) { + cface_expand (CFACE (cf->f), cf->a1, cf->a2, e, v1, v2, v, klass); + gts_surface_add_face (s, cf->f); + cf++; + } + + gts_allow_floating_vertices = FALSE; + + /* this part is described by figure "expand.fig" */ + i = v->segments; + while (i) { + GtsEdge * e1 = i->data; + GtsVertex * with = NULL; + GSList * j = e1->triangles, * next = i->next; + // fprintf (stderr, "e1: %p->%d\n", e1, id (e1)); + while (j && !with) { + with = GTS_OBJECT (j->data)->reserved; + j = j->next; + } + if (with) { + j = e1->triangles; + while (j) { + GtsTriangle * t = j->data; + if (GTS_OBJECT (t)->reserved) { + g_assert (GTS_OBJECT (t)->reserved == with); + GTS_OBJECT (t)->reserved = NULL; + } + else + GTS_OBJECT (t)->reserved = with; + j = j->next; + } + if (GTS_SEGMENT (e1)->v1 == v) + GTS_SEGMENT (e1)->v1 = with; + else + GTS_SEGMENT (e1)->v2 = with; + + v->segments = g_slist_remove_link (v->segments, i); + i->next = with->segments; + with->segments = i; + changed = TRUE; + } + if (next) + i = next; + else { + /* check for infinite loop (the crossed out case in + figure "expand.fig") */ + g_assert (changed); + changed = FALSE; + i = v->segments; + } + } +} + +#ifndef DYNAMIC_SPLIT +static void cface_neighbors (GtsSplitCFace * cf, + GtsEdge * e, + GtsVertex * v1, + GtsVertex * v2) +{ + GtsTriangle * t = GTS_TRIANGLE (cf->f), ** a; + GtsEdge * e1 = t->e1, * e2 = t->e2, * e3 = t->e3; + GSList * i; + guint size; + + ROTATE_ORIENT (e, e1, e2, e3); + if (SEGMENT_USE_VERTEX (GTS_SEGMENT (e1), v2)) { + e3 = e1; e1 = e2; e2 = e3; + } + + i = e1->triangles; + size = g_slist_length (i)*sizeof (GtsTriangle *); + a = cf->a1 = g_malloc (size > 0 ? size : sizeof (GtsTriangle *)); + while (i) { + if (i->data != t) + *(a++) = i->data; + i = i->next; + } + *a = NULL; + + i = e2->triangles; + size = g_slist_length (i)*sizeof (GtsTriangle *); + a = cf->a2 = g_malloc (size > 0 ? size : sizeof (GtsTriangle *)); + while (i) { + if (i->data != t) + *(a++) = i->data; + i = i->next; + } + *a = NULL; +} +#endif /*ifndef DYNAMIC_SPLIT */ + +/** + * gts_split_new: + * @klass: a #GtsSplitClass. + * @v: a #GtsVertex. + * @o1: either a #GtsVertex or a #GtsSplit. + * @o2: either a #GtsVertex or a #GtsSplit. + * + * Creates a new #GtsSplit which would collapse @o1 and @o2 into @v. The + * collapse itself is not performed. + * + * Returns: the new #GtsSplit. + */ +GtsSplit * gts_split_new (GtsSplitClass * klass, + GtsVertex * v, + GtsObject * o1, + GtsObject * o2) +{ + GtsSplit * vs; +#ifndef DYNAMIC_SPLIT + GtsVertex * v1, * v2; + GtsEdge * e; + GSList * i; + GtsSplitCFace * cf; +#endif + + g_return_val_if_fail (klass != NULL, NULL); + g_return_val_if_fail (v != NULL, NULL); + g_return_val_if_fail (GTS_IS_SPLIT (o1) || GTS_IS_VERTEX (o1), NULL); + g_return_val_if_fail (GTS_IS_SPLIT (o2) || GTS_IS_VERTEX (o2), NULL); + + vs = GTS_SPLIT (gts_object_new (GTS_OBJECT_CLASS (klass))); + vs->v = v; + vs->v1 = o1; + vs->v2 = o2; +#ifdef DYNAMIC_SPLIT + vs->ncf = 0; + vs->cfaces = NULL; +#else + v1 = GTS_SPLIT_V1 (vs); + v2 = GTS_SPLIT_V2 (vs); + e = GTS_EDGE (gts_vertices_are_connected (v1, v2)); + g_assert (e != NULL); + i = e->triangles; + vs->ncf = g_slist_length (i); + g_assert (vs->ncf > 0); + cf = vs->cfaces = g_malloc (vs->ncf*sizeof (GtsSplitCFace)); + while (i) { + cf->f = i->data; + cface_neighbors (cf, e, v1, v2); + i = i->next; + cf++; + } +#endif + + return vs; +} + +static gboolean +split_traverse_pre_order (GtsSplit * vs, + GtsSplitTraverseFunc func, + gpointer data) +{ + if (func (vs, data)) + return TRUE; + if (GTS_IS_SPLIT (vs->v1) && + split_traverse_pre_order (GTS_SPLIT (vs->v1), func, data)) + return TRUE; + if (GTS_IS_SPLIT (vs->v2) && + split_traverse_pre_order (GTS_SPLIT (vs->v2), func, data)) + return TRUE; + return FALSE; +} + +static gboolean +split_depth_traverse_pre_order (GtsSplit * vs, + guint depth, + GtsSplitTraverseFunc func, + gpointer data) +{ + if (func (vs, data)) + return TRUE; + + depth--; + if (!depth) + return FALSE; + + if (GTS_IS_SPLIT (vs->v1) && + split_depth_traverse_pre_order (GTS_SPLIT (vs->v1), depth, func, data)) + return TRUE; + if (GTS_IS_SPLIT (vs->v2) && + split_depth_traverse_pre_order (GTS_SPLIT (vs->v2), depth, func, data)) + return TRUE; + return FALSE; +} + +static gboolean +split_traverse_post_order (GtsSplit * vs, + GtsSplitTraverseFunc func, + gpointer data) +{ + if (GTS_IS_SPLIT (vs->v1) && + split_traverse_post_order (GTS_SPLIT (vs->v1), func, data)) + return TRUE; + if (GTS_IS_SPLIT (vs->v2) && + split_traverse_post_order (GTS_SPLIT (vs->v2), func, data)) + return TRUE; + if (func (vs, data)) + return TRUE; + return FALSE; +} + +static gboolean +split_depth_traverse_post_order (GtsSplit * vs, + guint depth, + GtsSplitTraverseFunc func, + gpointer data) +{ + depth--; + if (depth) { + if (GTS_IS_SPLIT (vs->v1) && + split_depth_traverse_post_order (GTS_SPLIT (vs->v1), + depth, func, data)) + return TRUE; + if (GTS_IS_SPLIT (vs->v2) && + split_depth_traverse_post_order (GTS_SPLIT (vs->v2), + depth, func, data)) + return TRUE; + } + if (func (vs, data)) + return TRUE; + return FALSE; +} + +/** + * gts_split_traverse: + * @root: the #GtsSplit to start the traversal from. + * @order: the order in which nodes are visited - G_PRE_ORDER or G_POST_ORDER. + * @depth: the maximum depth of the traversal. Nodes below this depth + * will not be visited. If depth is -1 all nodes in the tree are + * visited. If depth is 1, only the root is visited. If depth is 2, + * the root and its children are visited. And so on. + * @func: the function to call for each visited #GtsHSplit. + * @data: user data to pass to the function. + * + * Traverses the #GtsSplit tree having @root as root. Calls @func for each + * #GtsSplit of the tree in the order specified by @order. If order is set + * to G_PRE_ORDER @func is called for the #GtsSplit then its children, if order + * is set to G_POST_ORDER @func is called for the children and then for the + * #GtsSplit. + */ +void gts_split_traverse (GtsSplit * root, + GTraverseType order, + gint depth, + GtsSplitTraverseFunc func, + gpointer data) +{ + g_return_if_fail (root != NULL); + g_return_if_fail (func != NULL); + g_return_if_fail (order < G_LEVEL_ORDER); + g_return_if_fail (depth == -1 || depth > 0); + + switch (order) { + case G_PRE_ORDER: + if (depth < 0) + split_traverse_pre_order (root, func, data); + else + split_depth_traverse_pre_order (root, depth, func, data); + break; + case G_POST_ORDER: + if (depth < 0) + split_traverse_post_order (root, func, data); + else + split_depth_traverse_post_order (root, depth, func, data); + break; + default: + g_assert_not_reached (); + } +} + +/** + * gts_split_height: + * @root: a #GtsSplit. + * + * Returns: the maximum height of the vertex split tree having @root as root. + */ +guint gts_split_height (GtsSplit * root) +{ + guint height = 0, tmp_height; + + g_return_val_if_fail (root != NULL, 0); + + if (GTS_IS_SPLIT (root->v1)) { + tmp_height = gts_split_height (GTS_SPLIT (root->v1)); + if (tmp_height > height) + height = tmp_height; + } + if (GTS_IS_SPLIT (root->v2)) { + tmp_height = gts_split_height (GTS_SPLIT (root->v2)); + if (tmp_height > height) + height = tmp_height; + } + + return height + 1; +} + +#ifndef DYNAMIC_SPLIT +static gboolean list_array_are_identical (GSList * list, + gpointer * array, + gpointer excluded) +{ + while (list) { + gpointer data = list->data; + if (data != excluded) { + gboolean found = FALSE; + gpointer * a = array; + + while (!found && *a) + if (*(a++) == data) + found = TRUE; + if (!found) + return FALSE; + } + list = list->next; + } + return TRUE; +} +#endif /* ifndef DYNAMIC_SPLIT */ + +#ifndef NEW +gboolean gts_split_is_collapsable (GtsSplit * vs) +{ + guint i; + GtsSplitCFace * cf; + GtsVertex * v1, * v2; + GtsEdge * e; + + g_return_val_if_fail (vs != NULL, FALSE); + + v1 = GTS_SPLIT_V1 (vs); + v2 = GTS_SPLIT_V2 (vs); + g_return_val_if_fail ((e = GTS_EDGE (gts_vertices_are_connected (v1, v2))), + FALSE); + +#ifdef DYNAMIC_SPLIT + if (!gts_edge_collapse_is_valid (e)) + return FALSE; +#else + i = vs->ncf; + cf = vs->cfaces; + while (i--) { + GtsTriangle * t = GTS_TRIANGLE (cf->f); + GtsEdge * e1 = t->e1, * e2 = t->e2, * e3 = t->e3; + + ROTATE_ORIENT (e, e1, e2, e3); + if (SEGMENT_USE_VERTEX (GTS_SEGMENT (e1), v2)) { + e3 = e1; e1 = e2; e2 = e3; + } + + if (!list_array_are_identical (e1->triangles, (gpointer *) cf->a1, t)) + return FALSE; + if (!list_array_are_identical (e2->triangles, (gpointer *) cf->a2, t)) + return FALSE; + + cf++; + } +#endif + return TRUE; +} +#endif /* not NEW */ + +#ifdef DEBUG_HEXPAND +static guint expand_level = 0; + +static void expand_indent (FILE * fptr) +{ + guint i = expand_level; + while (i--) + fputc (' ', fptr); +} +#endif + +/** + * gts_hsplit_force_expand: + * @hs: a #GtsHSplit. + * @hsurface: a #GtsHSurface. + * + * Forces the expansion of @hs by first expanding all its dependencies not + * already expanded. + */ +void gts_hsplit_force_expand (GtsHSplit * hs, + GtsHSurface * hsurface) +{ + guint i; + GtsSplitCFace * cf; + + g_return_if_fail (hs != NULL); + g_return_if_fail (hsurface != NULL); + g_return_if_fail (hs->nchild == 0); + +#ifdef DEBUG_HEXPAND + expand_level += 2; +#endif + + if (hs->parent && hs->parent->nchild == 0) { +#ifdef DEBUG_HEXPAND + expand_indent (stderr); + fprintf (stderr, "expand parent %p\n", hs->parent); +#endif + gts_hsplit_force_expand (hs->parent, hsurface); + } + + i = GTS_SPLIT (hs)->ncf; + cf = GTS_SPLIT (hs)->cfaces; + while (i--) { + GtsTriangle ** j, * t; + + j = cf->a1; + while ((t = *(j++))) + if (IS_CFACE (t)) { +#ifdef DEBUG_HEXPAND + expand_indent (stderr); + fprintf (stderr, "expand a1: cf->f: %p t: %p parent_split: %p\n", + cf->f, + t, + GTS_HSPLIT (CFACE (t)->parent_split)); +#endif + gts_hsplit_force_expand (GTS_HSPLIT (CFACE (t)->parent_split), + hsurface); +#ifdef DEBUG_HEXPAND + g_assert (!IS_CFACE (t)); +#endif + } + j = cf->a2; + while ((t = *(j++))) + if (IS_CFACE (t)) { +#ifdef DEBUG_HEXPAND + expand_indent (stderr); + fprintf (stderr, "expand a2: cf->f: %p t: %p parent_split: %p\n", + cf->f, + t, + GTS_HSPLIT (CFACE (t)->parent_split)); +#endif + gts_hsplit_force_expand (GTS_HSPLIT (CFACE (t)->parent_split), + hsurface); + } + cf++; + } + + gts_hsplit_expand (hs, hsurface); + +#ifdef DEBUG_HEXPAND + expand_level -= 2; + expand_indent (stderr); + fprintf (stderr, "%p expanded\n", hs); +#endif +} + +static void index_object (GtsObject * o, guint * n) +{ + o->reserved = GUINT_TO_POINTER ((*n)++); +} + +static void index_face (GtsFace * f, gpointer * data) +{ + guint * nf = data[1]; + + g_hash_table_insert (data[0], f, GUINT_TO_POINTER ((*nf)++)); +} + +/** + * gts_psurface_write: + * @ps: a #GtsPSurface. + * @fptr: a file pointer. + * + * Writes to @fptr a GTS progressive surface description. + */ +void gts_psurface_write (GtsPSurface * ps, FILE * fptr) +{ + guint nv = 1; + guint nf = 1; + GHashTable * hash; + gpointer data[2]; + + g_return_if_fail (ps != NULL); + g_return_if_fail (fptr != NULL); + g_return_if_fail (GTS_PSURFACE_IS_CLOSED (ps)); + + while (gts_psurface_remove_vertex (ps)) + ; + + GTS_POINT_CLASS (ps->s->vertex_class)->binary = FALSE; + gts_surface_write (ps->s, fptr); + + gts_surface_foreach_vertex (ps->s, (GtsFunc) index_object, &nv); + hash = g_hash_table_new (NULL, NULL); + data[0] = hash; + data[1] = &nf; + gts_surface_foreach_face (ps->s, (GtsFunc) index_face, data); + + fprintf (fptr, "%u\n", ps->split->len); + while (ps->pos) { + GtsSplit * vs = g_ptr_array_index (ps->split, --ps->pos); + GtsSplitCFace * scf = vs->cfaces; + GtsVertex * v1, * v2; + guint i = vs->ncf; + + fprintf (fptr, "%u %u", + GPOINTER_TO_UINT (GTS_OBJECT (vs->v)->reserved), + vs->ncf); + if (GTS_OBJECT (vs)->klass->write) + (*GTS_OBJECT (vs)->klass->write) (GTS_OBJECT (vs), fptr); + fputc ('\n', fptr); + + v1 = GTS_IS_SPLIT (vs->v1) ? GTS_SPLIT (vs->v1)->v : GTS_VERTEX (vs->v1); + GTS_OBJECT (v1)->reserved = GUINT_TO_POINTER (nv++); + v2 = GTS_IS_SPLIT (vs->v2) ? GTS_SPLIT (vs->v2)->v : GTS_VERTEX (vs->v2); + GTS_OBJECT (v2)->reserved = GUINT_TO_POINTER (nv++); + + (*GTS_OBJECT (v1)->klass->write) (GTS_OBJECT (v1), fptr); + fputc ('\n', fptr); + + (*GTS_OBJECT (v2)->klass->write) (GTS_OBJECT (v2), fptr); + fputc ('\n', fptr); + + while (i--) { + CFace * cf = CFACE (scf->f); + GtsTriangle ** a, * t; + + fprintf (fptr, "%u %u", + GPOINTER_TO_UINT (g_hash_table_lookup (hash, cf->t)), + cf->flags); + if (GTS_OBJECT_CLASS (ps->s->face_class)->write) + (*GTS_OBJECT_CLASS (ps->s->face_class)->write) (GTS_OBJECT (cf), fptr); + fputc ('\n', fptr); + + a = scf->a1; + while ((t = *(a++))) + fprintf (fptr, "%u ", + GPOINTER_TO_UINT (g_hash_table_lookup (hash, t))); + fprintf (fptr, "\n"); + + a = scf->a2; + while ((t = *(a++))) + fprintf (fptr, "%u ", + GPOINTER_TO_UINT (g_hash_table_lookup (hash, t))); + fprintf (fptr, "\n"); + + g_hash_table_insert (hash, cf, GUINT_TO_POINTER (nf++)); + + scf++; + } + + gts_split_expand (vs, ps->s, ps->s->edge_class); + } + + gts_surface_foreach_vertex (ps->s, + (GtsFunc) gts_object_reset_reserved, NULL); + g_hash_table_destroy (hash); +} + +static guint surface_read (GtsSurface * surface, + GtsFile * f, + GPtrArray * vertices, + GPtrArray * faces) +{ + GtsEdge ** edges; + guint n, nv, ne, nf; + + g_return_val_if_fail (surface != NULL, 1); + g_return_val_if_fail (f != NULL, 1); + + if (f->type != GTS_INT) { + gts_file_error (f, "expecting an integer (number of vertices)"); + return f->line; + } + nv = atoi (f->token->str); + + gts_file_next_token (f); + if (f->type != GTS_INT) { + gts_file_error (f, "expecting an integer (number of edges)"); + return f->line; + } + ne = atoi (f->token->str); + + gts_file_next_token (f); + if (f->type != GTS_INT) { + gts_file_error (f, "expecting an integer (number of faces)"); + return f->line; + } + nf = atoi (f->token->str); + + gts_file_next_token (f); + if (f->type == GTS_STRING) { + if (f->type != GTS_STRING) { + gts_file_error (f, "expecting a string (GtsSurfaceClass)"); + return f->line; + } + gts_file_next_token (f); + if (f->type != GTS_STRING) { + gts_file_error (f, "expecting a string (GtsFaceClass)"); + return f->line; + } + gts_file_next_token (f); + if (f->type != GTS_STRING) { + gts_file_error (f, "expecting a string (GtsEdgeClass)"); + return f->line; + } + gts_file_next_token (f); + if (f->type != GTS_STRING) { + gts_file_error (f, "expecting a string (GtsVertexClass)"); + return f->line; + } + if (!strcmp (f->token->str, "GtsVertexBinary")) + GTS_POINT_CLASS (surface->vertex_class)->binary = TRUE; + else + gts_file_first_token_after (f, '\n'); + } + else + gts_file_first_token_after (f, '\n'); + + g_ptr_array_set_size (vertices, nv); + g_ptr_array_set_size (faces, nf); + /* allocate nv + 1 just in case nv == 0 */ + edges = g_malloc ((ne + 1)*sizeof (GtsEdge *)); + + n = 0; + while (n < nv && f->type != GTS_ERROR) { + GtsObject * new_vertex = + gts_object_new (GTS_OBJECT_CLASS (surface->vertex_class)); + + (* GTS_OBJECT_CLASS (surface->vertex_class)->read) (&new_vertex, f); + if (f->type != GTS_ERROR) { + if (!GTS_POINT_CLASS (surface->vertex_class)->binary) + gts_file_first_token_after (f, '\n'); + g_ptr_array_index (vertices, n++) = new_vertex; + } + else + gts_object_destroy (new_vertex); + } + if (f->type == GTS_ERROR) + nv = n; + if (GTS_POINT_CLASS (surface->vertex_class)->binary) + gts_file_first_token_after (f, '\n'); + + n = 0; + while (n < ne && f->type != GTS_ERROR) { + guint p1, p2; + + if (f->type != GTS_INT) + gts_file_error (f, "expecting an integer (first vertex index)"); + else { + p1 = atoi (f->token->str); + if (p1 == 0 || p1 > nv) + gts_file_error (f, "vertex index `%d' is out of range `[1,%d]'", + p1, nv); + else { + gts_file_next_token (f); + if (f->type != GTS_INT) + gts_file_error (f, "expecting an integer (second vertex index)"); + else { + p2 = atoi (f->token->str); + if (p2 == 0 || p2 > nv) + gts_file_error (f, "vertex index `%d' is out of range `[1,%d]'", + p2, nv); + else { + GtsEdge * new_edge = + gts_edge_new (surface->edge_class, + g_ptr_array_index (vertices, p1 - 1), + g_ptr_array_index (vertices, p2 - 1)); + + gts_file_next_token (f); + if (f->type != '\n') + if (GTS_OBJECT_CLASS (surface->edge_class)->read) + (*GTS_OBJECT_CLASS (surface->edge_class)->read) + ((GtsObject **) &new_edge, f); + gts_file_first_token_after (f, '\n'); + edges[n++] = new_edge; + } + } + } + } + } + if (f->type == GTS_ERROR) + ne = n; + + n = 0; + while (n < nf && f->type != GTS_ERROR) { + guint s1, s2, s3; + + if (f->type != GTS_INT) + gts_file_error (f, "expecting an integer (first edge index)"); + else { + s1 = atoi (f->token->str); + if (s1 == 0 || s1 > ne) + gts_file_error (f, "edge index `%d' is out of range `[1,%d]'", + s1, ne); + else { + gts_file_next_token (f); + if (f->type != GTS_INT) + gts_file_error (f, "expecting an integer (second edge index)"); + else { + s2 = atoi (f->token->str); + if (s2 == 0 || s2 > ne) + gts_file_error (f, "edge index `%d' is out of range `[1,%d]'", + s2, ne); + else { + gts_file_next_token (f); + if (f->type != GTS_INT) + gts_file_error (f, "expecting an integer (third edge index)"); + else { + s3 = atoi (f->token->str); + if (s3 == 0 || s3 > ne) + gts_file_error (f, "edge index `%d' is out of range `[1,%d]'", + s3, ne); + else { + GtsFace * new_face = gts_face_new (surface->face_class, + edges[s1 - 1], + edges[s2 - 1], + edges[s3 - 1]); + + gts_file_next_token (f); + if (f->type != '\n') + if (GTS_OBJECT_CLASS (surface->face_class)->read) + (*GTS_OBJECT_CLASS (surface->face_class)->read) + ((GtsObject **) &new_face, f); + gts_file_first_token_after (f, '\n'); + gts_surface_add_face (surface, new_face); + g_ptr_array_index (faces, n++) = new_face; + } + } + } + } + } + } + } + + g_free (edges); + + if (f->type == GTS_ERROR) { + gts_allow_floating_vertices = TRUE; + while (nv) + gts_object_destroy (GTS_OBJECT (g_ptr_array_index (vertices, nv-- - 1))); + gts_allow_floating_vertices = FALSE; + return f->line; + } + + return 0; +} + +/** + * gts_psurface_open: + * @klass: a #GtsPSurfaceClass. + * @s: a #GtsSurface. + * @split_class: a #GtsSplitClass to use for the #GtsSplit. + * @f: a #GtsFile. + * + * Creates a new #GtsPSurface prepared for input from the file @f + * containing a valid GTS representation of a progressive surface. The initial + * shape of the progressive surface is loaded into @s. + * + * Before being usable as such this progressive surface must be closed using + * gts_psurface_close(). While open however, the functions + * gts_psurface_get_vertex_number(), gts_psurface_min_vertex_number() and + * gts_psurface_max_vertex_number() can still be used. + * + * Returns: a new #GtsPSurface or %NULL if there was a format error while + * reading the file, in which case @f contains information about the error. + */ +GtsPSurface * gts_psurface_open (GtsPSurfaceClass * klass, + GtsSurface * s, + GtsSplitClass * split_class, + GtsFile * f) +{ + GtsPSurface * ps; + + g_return_val_if_fail (klass != NULL, NULL); + g_return_val_if_fail (s != NULL, NULL); + g_return_val_if_fail (split_class != NULL, NULL); + g_return_val_if_fail (f != NULL, NULL); + + ps = GTS_PSURFACE (gts_object_new (GTS_OBJECT_CLASS (klass))); + ps->s = s; + ps->split_class = split_class; + + ps->vertices = g_ptr_array_new (); + ps->faces = g_ptr_array_new (); + + if (surface_read (s, f, ps->vertices, ps->faces)) { + ps->s = NULL; + gts_object_destroy (GTS_OBJECT (ps)); + return NULL; + } + + ps->min = gts_surface_vertex_number (ps->s); + ps->pos = 0; + + if (f->type == GTS_INT) { + gint ns = atoi (f->token->str); + + if (ns > 0) { + g_ptr_array_set_size (ps->split, ns); + gts_file_first_token_after (f, '\n'); + } + } + + return ps; +} + +/** + * gts_psurface_read_vertex: + * @ps: a #GtsPSurface prealably created with gts_psurface_open(). + * @fp: a #GtsFile. + * + * Reads in one vertex split operation from @fp and performs the expansion. + * + * If an error occurs while reading the file, the @error field of @fp is set. + * + * Returns: the newly created #GtsSplit or %NULL if no vertex split could be + * read from @fp. + */ +GtsSplit * gts_psurface_read_vertex (GtsPSurface * ps, GtsFile * fp) +{ + guint nv, ncf; + GtsSplit * vs, * parent; + GtsSplitCFace * scf; + + g_return_val_if_fail (ps != NULL, NULL); + g_return_val_if_fail (fp != NULL, NULL); + g_return_val_if_fail (!GTS_PSURFACE_IS_CLOSED (ps), NULL); + + if (ps->pos >= ps->split->len) + return NULL; + + if (fp->type == GTS_NONE) + return NULL; + if (fp->type != GTS_INT) { + gts_file_error (fp, "expecting an integer (vertex index)"); + return NULL; + } + nv = atoi (fp->token->str); + if (nv == 0 || nv > ps->vertices->len) { + gts_file_error (fp, "vertex index `%d' is out of range `[1,%d]'", + nv, ps->vertices->len); + return NULL; + } + + gts_file_next_token (fp); + if (fp->type != GTS_INT) { + gts_file_error (fp, "expecting an integer (ncf)"); + return NULL; + } + ncf = atoi (fp->token->str); + + vs = GTS_SPLIT (gts_object_new (GTS_OBJECT_CLASS (ps->split_class))); + + vs->v = g_ptr_array_index (ps->vertices, nv - 1); + vs->v1 = vs->v2 = NULL; + vs->cfaces = NULL; + vs->ncf = 0; + + gts_file_next_token (fp); + if (fp->type != '\n') + if (GTS_OBJECT (vs)->klass->read) + (* GTS_OBJECT (vs)->klass->read) ((GtsObject **) &vs, fp); + gts_file_first_token_after (fp, '\n'); + + if (fp->type != GTS_ERROR) { + vs->v1 = gts_object_new (GTS_OBJECT_CLASS (ps->s->vertex_class)); + (* GTS_OBJECT_CLASS (ps->s->vertex_class)->read) (&(vs->v1), fp); + if (fp->type != GTS_ERROR) { + vs->v1->reserved = vs; + g_ptr_array_add (ps->vertices, vs->v1); + + gts_file_first_token_after (fp, '\n'); + + vs->v2 = gts_object_new (GTS_OBJECT_CLASS (ps->s->vertex_class)); + (*GTS_OBJECT_CLASS (ps->s->vertex_class)->read) (&(vs->v2), fp); + if (fp->type != GTS_ERROR) { + vs->v2->reserved = vs; + g_ptr_array_add (ps->vertices, vs->v2); + gts_file_first_token_after (fp, '\n'); + } + } + } + + if (fp->type != GTS_ERROR) { + scf = vs->cfaces = g_malloc (sizeof (GtsSplitCFace)*ncf); + while (fp->type != GTS_ERROR && ncf--) { + guint it, flags; + GtsFace * f; + CFace * cf; + GPtrArray * a; + + if (fp->type != GTS_INT) + gts_file_error (fp, "expecting an integer (face index)"); + else { + it = atoi (fp->token->str); + if (it == 0 || it > ps->faces->len) + gts_file_error (fp, "face index `%d' is out of range `[1,%d]'", + it, ps->faces->len); + else { + gts_file_next_token (fp); + if (fp->type != GTS_INT) + gts_file_error (fp, "expecting an integer (flags)"); + else { + flags = atoi (fp->token->str); + f = + GTS_FACE (gts_object_new (GTS_OBJECT_CLASS (ps->s->face_class))); + + gts_file_next_token (fp); + if (fp->type != '\n') + if (GTS_OBJECT (f)->klass->read) + (*GTS_OBJECT (f)->klass->read) ((GtsObject **) &f, fp); + gts_file_first_token_after (fp, '\n'); + if (fp->type != GTS_ERROR) { + scf->f = f; + + cf = (CFace *) f; + GTS_OBJECT (cf)->klass = GTS_OBJECT_CLASS (cface_class ()); + cf->parent_split = vs; + cf->t = g_ptr_array_index (ps->faces, it - 1); + cf->flags = flags; + + a = g_ptr_array_new (); + do { + if (fp->type != GTS_INT) + gts_file_error (fp, "expecting an integer (face index)"); + else { + it = atoi (fp->token->str); + if (it > ps->faces->len) + gts_file_error (fp, + "face index `%d' is out of range `[1,%d]'", + it, ps->faces->len); + else { + g_ptr_array_add (a, g_ptr_array_index (ps->faces, + it - 1)); + gts_file_next_token (fp); + } + } + } while (fp->type != GTS_ERROR && fp->type != '\n'); + gts_file_first_token_after (fp, '\n'); + g_ptr_array_add (a, NULL); + scf->a1 = (GtsTriangle **) a->pdata; + g_ptr_array_free (a, FALSE); + + if (fp->type != GTS_ERROR) { + a = g_ptr_array_new (); + do { + if (fp->type != GTS_INT) + gts_file_error (fp, "expecting an integer (face index)"); + else { + it = atoi (fp->token->str); + if (it > ps->faces->len) + gts_file_error (fp, + "face index `%d' is out of range `[1,%d]'", + it, ps->faces->len); + else { + g_ptr_array_add (a, g_ptr_array_index (ps->faces, + it - 1)); + gts_file_next_token (fp); + } + } + } while (fp->type != GTS_ERROR && fp->type != '\n'); + gts_file_first_token_after (fp, '\n'); + g_ptr_array_add (a, NULL); + scf->a2 = (GtsTriangle **) a->pdata; + g_ptr_array_free (a, FALSE); + + g_ptr_array_add (ps->faces, f); + + vs->ncf++; + scf++; + } + } + } + } + } + } + } + + if (fp->type != GTS_ERROR) { + if ((parent = GTS_OBJECT (vs->v)->reserved)) { + GTS_OBJECT (vs->v)->reserved = NULL; + if (parent->v1 == GTS_OBJECT (vs->v)) + parent->v1 = GTS_OBJECT (vs); + else { + g_assert (parent->v2 == GTS_OBJECT (vs->v)); + parent->v2 = GTS_OBJECT (vs); + } + } + g_ptr_array_index (ps->split, ps->pos++) = vs; + gts_split_expand (vs, ps->s, ps->s->edge_class); + + return vs; + } + + if (vs->v1) gts_object_destroy (vs->v1); + if (vs->v2) gts_object_destroy (vs->v2); + gts_object_destroy (GTS_OBJECT (vs)); + + return NULL; +} + +/** + * gts_psurface_close: + * @ps: a #GtsPSurface prealably created with gts_psurface_open(). + * + * Closes a progressive surface. + */ +void gts_psurface_close (GtsPSurface * ps) +{ + g_return_if_fail (ps != NULL); + g_return_if_fail (!GTS_PSURFACE_IS_CLOSED (ps)); + + g_ptr_array_free (ps->vertices, TRUE); + g_ptr_array_free (ps->faces, TRUE); + ps->faces = ps->vertices = NULL; + + gts_surface_foreach_vertex (ps->s, + (GtsFunc) gts_object_reset_reserved, NULL); + if (ps->pos > 0) + g_ptr_array_set_size (ps->split, ps->pos); + if (ps->split->len > 1) { + guint i, half = ps->split->len/2, n = ps->split->len - 1; + + for (i = 0; i < half; i++) { + gpointer p1 = g_ptr_array_index (ps->split, i); + gpointer p2 = g_ptr_array_index (ps->split, n - i); + g_ptr_array_index (ps->split, n - i) = p1; + g_ptr_array_index (ps->split, i) = p2; + } + } + ps->pos = 0; +} diff --git a/gts/stripe.c b/gts/stripe.c new file mode 100644 index 0000000000..7e98a9cdce --- /dev/null +++ b/gts/stripe.c @@ -0,0 +1,766 @@ +/* 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; +} diff --git a/gts/surface.c b/gts/surface.c new file mode 100644 index 0000000000..34c5cbe7f3 --- /dev/null +++ b/gts/surface.c @@ -0,0 +1,2743 @@ +/* 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 +#include "gts.h" + +#include "gts-private.h" + +static void destroy_foreach_face (GtsFace * f, GtsSurface * s) +{ + f->surfaces = g_slist_remove (f->surfaces, s); + if (!GTS_OBJECT_DESTROYED (f) && + !gts_allow_floating_faces && f->surfaces == NULL) + gts_object_destroy (GTS_OBJECT (f)); +} + +static void surface_destroy (GtsObject * object) +{ + GtsSurface * surface = GTS_SURFACE (object); + + gts_surface_foreach_face (surface, (GtsFunc) destroy_foreach_face, surface); +#ifdef USE_SURFACE_BTREE + g_tree_destroy (surface->faces); +#else /* not USE_SURFACE_BTREE */ + g_hash_table_destroy (surface->faces); +#endif /* not USE_SURFACE_BTREE */ + + (* GTS_OBJECT_CLASS (gts_surface_class ())->parent_class->destroy) (object); +} + +static void surface_write (GtsObject * object, FILE * fptr) +{ + fprintf (fptr, " %s %s %s %s", + object->klass->info.name, + GTS_OBJECT_CLASS (GTS_SURFACE (object)->face_class)->info.name, + GTS_OBJECT_CLASS (GTS_SURFACE (object)->edge_class)->info.name, + GTS_POINT_CLASS (GTS_SURFACE (object)->vertex_class)->binary ? + "GtsVertexBinary" : + GTS_OBJECT_CLASS (GTS_SURFACE (object)->vertex_class)->info.name); +} + +static void surface_class_init (GtsSurfaceClass * klass) +{ + GTS_OBJECT_CLASS (klass)->destroy = surface_destroy; + GTS_OBJECT_CLASS (klass)->write = surface_write; + klass->add_face = NULL; + klass->remove_face = NULL; +} + +#ifdef USE_SURFACE_BTREE +static gint compare_pointers (gconstpointer a, gconstpointer b) +{ + if (GPOINTER_TO_UINT (a) < GPOINTER_TO_UINT (b)) + return -1; + if (GPOINTER_TO_UINT (a) > GPOINTER_TO_UINT (b)) + return 1; + return 0; +} +#endif /* USE_SURFACE_BTREE */ + +static void surface_init (GtsSurface * surface) +{ +#ifdef USE_SURFACE_BTREE + surface->faces = g_tree_new (compare_pointers); +#else /* not USE_SURFACE_BTREE */ + surface->faces = g_hash_table_new (NULL, NULL); +#endif /* not USE_SURFACE_BTREE */ + surface->vertex_class = gts_vertex_class (); + surface->edge_class = gts_edge_class (); + surface->face_class = gts_face_class (); + surface->keep_faces = FALSE; +} + +/** + * gts_surface_class: + * + * Returns: the #GtsSurfaceClass. + */ +GtsSurfaceClass * gts_surface_class (void) +{ + static GtsSurfaceClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo surface_info = { + "GtsSurface", + sizeof (GtsSurface), + sizeof (GtsSurfaceClass), + (GtsObjectClassInitFunc) surface_class_init, + (GtsObjectInitFunc) surface_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (gts_object_class (), &surface_info); + } + + return klass; +} + +/** + * gts_surface_new: + * @klass: a #GtsSurfaceClass. + * @face_class: a #GtsFaceClass. + * @edge_class: a #GtsEdgeClass. + * @vertex_class: a #GtsVertexClass. + * + * Returns: a new empty #GtsSurface. + */ +GtsSurface * gts_surface_new (GtsSurfaceClass * klass, + GtsFaceClass * face_class, + GtsEdgeClass * edge_class, + GtsVertexClass * vertex_class) +{ + GtsSurface * s; + + s = GTS_SURFACE (gts_object_new (GTS_OBJECT_CLASS (klass))); + s->vertex_class = vertex_class; + s->edge_class = edge_class; + s->face_class = face_class; + + return s; +} + +/** + * gts_surface_add_face: + * @s: a #GtsSurface. + * @f: a #GtsFace. + * + * Adds face @f to surface @s. + */ +void gts_surface_add_face (GtsSurface * s, GtsFace * f) +{ + g_return_if_fail (s != NULL); + g_return_if_fail (f != NULL); + + g_assert (s->keep_faces == FALSE); + +#ifdef USE_SURFACE_BTREE + if (!g_tree_lookup (s->faces, f)) { + f->surfaces = g_slist_prepend (f->surfaces, s); + g_tree_insert (s->faces, f, f); + } +#else /* not USE_SURFACE_BTREE */ + if (!g_hash_table_lookup (s->faces, f)) { + f->surfaces = g_slist_prepend (f->surfaces, s); + g_hash_table_insert (s->faces, f, f); + } +#endif /* not USE_SURFACE_BTREE */ + + if (GTS_SURFACE_CLASS (GTS_OBJECT (s)->klass)->add_face) + (* GTS_SURFACE_CLASS (GTS_OBJECT (s)->klass)->add_face) (s, f); +} + +/** + * gts_surface_remove_face: + * @s: a #GtsSurface. + * @f: a #GtsFace. + * + * Removes face @f from surface @s. + */ +void gts_surface_remove_face (GtsSurface * s, + GtsFace * f) +{ + g_return_if_fail (s != NULL); + g_return_if_fail (f != NULL); + + g_assert (s->keep_faces == FALSE); + +#ifdef USE_SURFACE_BTREE + g_tree_remove (s->faces, f); +#else /* not USE_SURFACE_BTREE */ + g_hash_table_remove (s->faces, f); +#endif /* not USE_SURFACE_BTREE */ + + f->surfaces = g_slist_remove (f->surfaces, s); + + if (GTS_SURFACE_CLASS (GTS_OBJECT (s)->klass)->remove_face) + (* GTS_SURFACE_CLASS (GTS_OBJECT (s)->klass)->remove_face) (s, f); + + if (!GTS_OBJECT_DESTROYED (f) && + !gts_allow_floating_faces && + f->surfaces == NULL) + gts_object_destroy (GTS_OBJECT (f)); +} + +/** + * gts_surface_read: + * @surface: a #GtsSurface. + * @f: a #GtsFile. + * + * Add to @surface the data read from @f. The format of the file pointed to + * by @f is as described in gts_surface_write(). + * + * Returns: 0 if successful or the line number at which the parsing + * stopped in case of error (in which case the @error field of @f is + * set to a description of the error which occured). + */ +/* Update split.c/surface_read() if modifying this function */ +guint gts_surface_read (GtsSurface * surface, GtsFile * f) +{ + GtsVertex ** vertices; + GtsEdge ** edges; + guint n, nv, ne, nf; + + g_return_val_if_fail (surface != NULL, 1); + g_return_val_if_fail (f != NULL, 1); + + if (f->type != GTS_INT) { + gts_file_error (f, "expecting an integer (number of vertices)"); + return f->line; + } + nv = atoi (f->token->str); + + gts_file_next_token (f); + if (f->type != GTS_INT) { + gts_file_error (f, "expecting an integer (number of edges)"); + return f->line; + } + ne = atoi (f->token->str); + + gts_file_next_token (f); + if (f->type != GTS_INT) { + gts_file_error (f, "expecting an integer (number of faces)"); + return f->line; + } + nf = atoi (f->token->str); + + gts_file_next_token (f); + if (f->type == GTS_STRING) { + if (f->type != GTS_STRING) { + gts_file_error (f, "expecting a string (GtsSurfaceClass)"); + return f->line; + } + gts_file_next_token (f); + if (f->type != GTS_STRING) { + gts_file_error (f, "expecting a string (GtsFaceClass)"); + return f->line; + } + gts_file_next_token (f); + if (f->type != GTS_STRING) { + gts_file_error (f, "expecting a string (GtsEdgeClass)"); + return f->line; + } + gts_file_next_token (f); + if (f->type != GTS_STRING) { + gts_file_error (f, "expecting a string (GtsVertexClass)"); + return f->line; + } + if (!strcmp (f->token->str, "GtsVertexBinary")) + GTS_POINT_CLASS (surface->vertex_class)->binary = TRUE; + else { + GTS_POINT_CLASS (surface->vertex_class)->binary = FALSE; + gts_file_first_token_after (f, '\n'); + } + } + else + gts_file_first_token_after (f, '\n'); + + if (nf <= 0) + return 0; + + /* allocate nv + 1 just in case nv == 0 */ + vertices = g_malloc ((nv + 1)*sizeof (GtsVertex *)); + edges = g_malloc ((ne + 1)*sizeof (GtsEdge *)); + + n = 0; + while (n < nv && f->type != GTS_ERROR) { + GtsObject * new_vertex = + gts_object_new (GTS_OBJECT_CLASS (surface->vertex_class)); + + (* GTS_OBJECT_CLASS (surface->vertex_class)->read) (&new_vertex, f); + if (f->type != GTS_ERROR) { + if (!GTS_POINT_CLASS (surface->vertex_class)->binary) + gts_file_first_token_after (f, '\n'); + vertices[n++] = GTS_VERTEX (new_vertex); + } + else + gts_object_destroy (new_vertex); + } + if (f->type == GTS_ERROR) + nv = n; + if (GTS_POINT_CLASS (surface->vertex_class)->binary) + gts_file_first_token_after (f, '\n'); + + n = 0; + while (n < ne && f->type != GTS_ERROR) { + guint p1, p2; + + if (f->type != GTS_INT) + gts_file_error (f, "expecting an integer (first vertex index)"); + else { + p1 = atoi (f->token->str); + if (p1 == 0 || p1 > nv) + gts_file_error (f, "vertex index `%d' is out of range `[1,%d]'", + p1, nv); + else { + gts_file_next_token (f); + if (f->type != GTS_INT) + gts_file_error (f, "expecting an integer (second vertex index)"); + else { + p2 = atoi (f->token->str); + if (p2 == 0 || p2 > nv) + gts_file_error (f, "vertex index `%d' is out of range `[1,%d]'", + p2, nv); + else { + GtsEdge * new_edge = + gts_edge_new (surface->edge_class, + vertices[p1 - 1], vertices[p2 - 1]); + + gts_file_next_token (f); + if (f->type != '\n') + if (GTS_OBJECT_CLASS (surface->edge_class)->read) + (*GTS_OBJECT_CLASS (surface->edge_class)->read) + ((GtsObject **) &new_edge, f); + gts_file_first_token_after (f, '\n'); + edges[n++] = new_edge; + } + } + } + } + } + if (f->type == GTS_ERROR) + ne = n; + + n = 0; + while (n < nf && f->type != GTS_ERROR) { + guint s1, s2, s3; + + if (f->type != GTS_INT) + gts_file_error (f, "expecting an integer (first edge index)"); + else { + s1 = atoi (f->token->str); + if (s1 == 0 || s1 > ne) + gts_file_error (f, "edge index `%d' is out of range `[1,%d]'", + s1, ne); + else { + gts_file_next_token (f); + if (f->type != GTS_INT) + gts_file_error (f, "expecting an integer (second edge index)"); + else { + s2 = atoi (f->token->str); + if (s2 == 0 || s2 > ne) + gts_file_error (f, "edge index `%d' is out of range `[1,%d]'", + s2, ne); + else { + gts_file_next_token (f); + if (f->type != GTS_INT) + gts_file_error (f, "expecting an integer (third edge index)"); + else { + s3 = atoi (f->token->str); + if (s3 == 0 || s3 > ne) + gts_file_error (f, "edge index `%d' is out of range `[1,%d]'", + s3, ne); + else { + GtsFace * new_face = gts_face_new (surface->face_class, + edges[s1 - 1], + edges[s2 - 1], + edges[s3 - 1]); + + gts_file_next_token (f); + if (f->type != '\n') + if (GTS_OBJECT_CLASS (surface->face_class)->read) + (*GTS_OBJECT_CLASS (surface->face_class)->read) + ((GtsObject **) &new_face, f); + gts_file_first_token_after (f, '\n'); + gts_surface_add_face (surface, new_face); + n++; + } + } + } + } + } + } + } + + if (f->type == GTS_ERROR) { + gts_allow_floating_vertices = TRUE; + while (nv) + gts_object_destroy (GTS_OBJECT (vertices[nv-- - 1])); + gts_allow_floating_vertices = FALSE; + } + + g_free (vertices); + g_free (edges); + + if (f->type == GTS_ERROR) + return f->line; + return 0; +} + +static void sum_area (GtsFace * f, gdouble * area) { + *area += gts_triangle_area (GTS_TRIANGLE (f)); +} + +/** + * gts_surface_area: + * @s: a #GtsSurface. + * + * Returns: the area of @s obtained as the sum of the signed areas of its + * faces. + */ +gdouble gts_surface_area (GtsSurface * s) +{ + gdouble area = 0.0; + gts_surface_foreach_face (s, (GtsFunc)sum_area, &area); + return area; +} + +/** + * gts_range_init: + * @r: a #GtsRange. + * + * Initializes a #GtsRange. + */ +void gts_range_init (GtsRange * r) +{ + g_return_if_fail (r != NULL); + + r->max = - G_MAXDOUBLE; + r->min = G_MAXDOUBLE; + r->sum = r->sum2 = 0.0; + r->n = 0; +} + +/** + * gts_range_reset: + * @r: a #GtsRange. + * + * Sets all the fields of @r to 0. + */ +void gts_range_reset (GtsRange * r) +{ + g_return_if_fail (r != NULL); + + r->max = 0.0; + r->min = 0.0; + r->sum = r->sum2 = 0.0; + r->n = 0; +} + +/** + * gts_range_add_value: + * @r: a #GtsRange. + * @val: a value to add to @r. + * + * Adds @val to @r. + */ +void gts_range_add_value (GtsRange * r, gdouble val) +{ + g_return_if_fail (r != NULL); + + if (val < r->min) r->min = val; + if (val > r->max) r->max = val; + r->sum += val; + r->sum2 += val*val; + r->n++; +} + +/** + * gts_range_update: + * @r: a #GtsRange. + * + * Updates the fields of @r. + */ +void gts_range_update (GtsRange * r) +{ + g_return_if_fail (r != NULL); + + if (r->n > 0) { + if (r->sum2 - r->sum*r->sum/(gdouble) r->n >= 0.) + r->stddev = sqrt ((r->sum2 - r->sum*r->sum/(gdouble) r->n) + /(gdouble) r->n); + else + r->stddev = 0.; + r->mean = r->sum/(gdouble) r->n; + } + else + r->min = r->max = r->mean = r->stddev = 0.; +} + +/** + * gts_range_print: + * @r: a #GtsRange. + * @fptr: a file pointer. + * + * Writes a text representation of @r in @fptr. + */ +void gts_range_print (GtsRange * r, FILE * fptr) +{ + g_return_if_fail (r != NULL); + g_return_if_fail (fptr != NULL); + fprintf (fptr, "min: %g mean: %g | %g max: %g", + r->min, r->mean, r->stddev, r->max); +} + +static void stats_foreach_vertex (GtsVertex * v, GtsSurfaceStats * stats) +{ + GSList * i = v->segments; + guint nedges = 0; + + while (i) { + if (GTS_IS_EDGE (i->data) && + gts_edge_has_parent_surface (i->data, stats->parent)) + nedges++; + i = i->next; + } + gts_range_add_value (&stats->edges_per_vertex, nedges); +} + +static void stats_foreach_edge (GtsEdge * e, GtsSurfaceStats * stats) +{ + guint nt = gts_edge_face_number (e, stats->parent); + + if (gts_segment_is_duplicate (GTS_SEGMENT (e))) + stats->n_duplicate_edges++; + if (nt == 1) + stats->n_boundary_edges++; + else if (nt > 2) + stats->n_non_manifold_edges++; + gts_range_add_value (&stats->faces_per_edge, nt); +} + +static void stats_foreach_face (GtsTriangle * t, GtsSurfaceStats * stats) +{ + if (!gts_face_is_compatible (GTS_FACE (t), stats->parent)) + stats->n_incompatible_faces++; + if (gts_triangle_is_duplicate (t)) + stats->n_duplicate_faces++; + stats->n_faces++; +} + +/** + * gts_surface_stats: + * @s: a #GtsSurface. + * @stats: a #GtsSurfaceStats. + * + * Fills @stats with the statistics relevant to surface @s. + */ +void gts_surface_stats (GtsSurface * s, GtsSurfaceStats * stats) +{ + g_return_if_fail (s != NULL); + g_return_if_fail (stats != NULL); + + stats->parent = s; + stats->n_faces = 0; + stats->n_incompatible_faces = 0; + stats->n_duplicate_faces = 0; + stats->n_duplicate_edges = 0; + stats->n_boundary_edges = 0; + stats->n_non_manifold_edges = 0; + gts_range_init (&stats->edges_per_vertex); + gts_range_init (&stats->faces_per_edge); + + gts_surface_foreach_vertex (s, (GtsFunc) stats_foreach_vertex, stats); + gts_surface_foreach_edge (s, (GtsFunc) stats_foreach_edge, stats); + gts_surface_foreach_face (s, (GtsFunc) stats_foreach_face, stats); + + gts_range_update (&stats->edges_per_vertex); + gts_range_update (&stats->faces_per_edge); +} + +static void quality_foreach_edge (GtsSegment * s, + GtsSurfaceQualityStats * stats) +{ + GSList * i = GTS_EDGE (s)->triangles; + + gts_range_add_value (&stats->edge_length, + gts_point_distance (GTS_POINT (s->v1), + GTS_POINT (s->v2))); + while (i) { + GSList * j = i->next; + while (j) { + gts_range_add_value (&stats->edge_angle, + fabs (gts_triangles_angle (i->data, j->data))); + j = j->next; + } + i = i->next; + } +} + +static void quality_foreach_face (GtsTriangle * t, + GtsSurfaceQualityStats * stats) +{ + gts_range_add_value (&stats->face_quality, gts_triangle_quality (t)); + gts_range_add_value (&stats->face_area, gts_triangle_area (t)); +} + +/** + * gts_surface_quality_stats: + * @s: a #GtsSurface. + * @stats: a #GtsSurfaceQualityStats. + * + * Fills @stats with quality statistics relevant to surface @s. + */ +void gts_surface_quality_stats (GtsSurface * s, GtsSurfaceQualityStats * stats) +{ + g_return_if_fail (s != NULL); + g_return_if_fail (stats != NULL); + + stats->parent = s; + gts_range_init (&stats->face_quality); + gts_range_init (&stats->face_area); + gts_range_init (&stats->edge_length); + gts_range_init (&stats->edge_angle); + + gts_surface_foreach_edge (s, (GtsFunc) quality_foreach_edge, stats); + gts_surface_foreach_face (s, (GtsFunc) quality_foreach_face, stats); + + gts_range_update (&stats->face_quality); + gts_range_update (&stats->face_area); + gts_range_update (&stats->edge_length); + gts_range_update (&stats->edge_angle); +} + +/** + * gts_surface_print_stats: + * @s: a #GtsSurface. + * @fptr: a file pointer. + * + * Writes in the file pointed to by @fptr the statistics for surface @s. + */ +void gts_surface_print_stats (GtsSurface * s, FILE * fptr) +{ + GtsSurfaceStats stats; + GtsSurfaceQualityStats qstats; + + g_return_if_fail (s != NULL); + g_return_if_fail (fptr != NULL); + + gts_surface_stats (s, &stats); + gts_surface_quality_stats (s, &qstats); + + fprintf (fptr, + "# vertices: %u edges: %u faces: %u\n" + "# Connectivity statistics\n" + "# incompatible faces: %u\n" + "# duplicate faces: %u\n" + "# boundary edges: %u\n" + "# duplicate edges: %u\n" + "# non-manifold edges: %u\n", + stats.edges_per_vertex.n, + stats.faces_per_edge.n, + stats.n_faces, + stats.n_incompatible_faces, + stats.n_duplicate_faces, + stats.n_boundary_edges, + stats.n_duplicate_edges, + stats.n_non_manifold_edges); + fputs ("# edges per vertex: ", fptr); + gts_range_print (&stats.edges_per_vertex, fptr); + fputs ("\n# faces per edge: ", fptr); + gts_range_print (&stats.faces_per_edge, fptr); + fputs ("\n# Geometric statistics\n# face quality: ", fptr); + gts_range_print (&qstats.face_quality, fptr); + fputs ("\n# face area : ", fptr); + gts_range_print (&qstats.face_area, fptr); + fputs ("\n# edge length : ", fptr); + gts_range_print (&qstats.edge_length, fptr); + fputc ('\n', fptr); +} + +static void write_vertex (GtsPoint * p, gpointer * data) +{ + (*GTS_OBJECT (p)->klass->write) (GTS_OBJECT (p), (FILE *) data[0]); + if (!GTS_POINT_CLASS (GTS_OBJECT (p)->klass)->binary) + fputc ('\n', (FILE *) data[0]); + g_hash_table_insert (data[2], p, + GUINT_TO_POINTER (++(*((guint *) data[1])))); +} + +static void write_edge (GtsSegment * s, gpointer * data) +{ + fprintf ((FILE *) data[0], "%u %u", + GPOINTER_TO_UINT (g_hash_table_lookup (data[2], s->v1)), + GPOINTER_TO_UINT (g_hash_table_lookup (data[2], s->v2))); + if (GTS_OBJECT (s)->klass->write) + (*GTS_OBJECT (s)->klass->write) (GTS_OBJECT (s), (FILE *) data[0]); + fputc ('\n', (FILE *) data[0]); + g_hash_table_insert (data[3], s, + GUINT_TO_POINTER (++(*((guint *) data[1])))); +} + +static void write_face (GtsTriangle * t, gpointer * data) +{ + fprintf (data[0], "%u %u %u", + GPOINTER_TO_UINT (g_hash_table_lookup (data[3], t->e1)), + GPOINTER_TO_UINT (g_hash_table_lookup (data[3], t->e2)), + GPOINTER_TO_UINT (g_hash_table_lookup (data[3], t->e3))); + if (GTS_OBJECT (t)->klass->write) + (*GTS_OBJECT (t)->klass->write) (GTS_OBJECT (t), data[0]); + fputc ('\n', data[0]); +} + +/** + * gts_surface_write: + * @s: a #GtsSurface. + * @fptr: a file pointer. + * + * Writes in the file @fptr an ASCII representation of @s. The file + * format is as follows. + * + * All the lines beginning with #GTS_COMMENTS are ignored. The first line + * contains three unsigned integers separated by spaces. The first + * integer is the number of vertices, nv, the second is the number of + * edges, ne and the third is the number of faces, nf. + * + * Follows nv lines containing the x, y and z coordinates of the + * vertices. Follows ne lines containing the two indices (starting + * from one) of the vertices of each edge. Follows nf lines containing + * the three ordered indices (also starting from one) of the edges of + * each face. + * + * The format described above is the least common denominator to all + * GTS files. Consistent with an object-oriented approach, the GTS + * file format is extensible. Each of the lines of the file can be + * extended with user-specific attributes accessible through the + * read() and write() virtual methods of each of the objects written + * (surface, vertices, edges or faces). When read with different + * object classes, these extra attributes are just ignored. + */ +void gts_surface_write (GtsSurface * s, FILE * fptr) +{ + guint n; + gpointer data[4]; + GHashTable * vindex, * eindex; + GtsSurfaceStats stats; + + g_return_if_fail (s != NULL); + g_return_if_fail (fptr != NULL); + + data[0] = fptr; + data[1] = &n; + data[2] = vindex = g_hash_table_new (NULL, NULL); + data[3] = eindex = g_hash_table_new (NULL, NULL); + + gts_surface_stats (s, &stats); + fprintf (fptr, "%u %u %u", + stats.edges_per_vertex.n, + stats.faces_per_edge.n, + stats.n_faces); + if (GTS_OBJECT (s)->klass->write) + (*GTS_OBJECT (s)->klass->write) (GTS_OBJECT (s), fptr); + fputc ('\n', fptr); + n = 0; + gts_surface_foreach_vertex (s, (GtsFunc) write_vertex, data); + n = 0; + if (GTS_POINT_CLASS (s->vertex_class)->binary) + fputc ('\n', fptr); + gts_surface_foreach_edge (s, (GtsFunc) write_edge, data); + gts_surface_foreach_face (s, (GtsFunc) write_face, data); + g_hash_table_destroy (vindex); + g_hash_table_destroy (eindex); +} + +static void write_vertex_oogl (GtsPoint * p, gpointer * data) +{ + FILE * fp = data[0]; + + fprintf (fp, "%g %g %g", p->x, p->y, p->z); + if (GTS_OBJECT (p)->klass->color) { + GtsColor c = (* GTS_OBJECT (p)->klass->color) (GTS_OBJECT (p)); + fprintf (fp, " %g %g %g 1.0\n", c.r, c.g, c.b); + } + else + fputc ('\n', fp); + GTS_OBJECT (p)->reserved = GUINT_TO_POINTER ((*((guint *) data[1]))++); +} + +static void write_face_oogl (GtsTriangle * t, FILE * fp) +{ + GtsVertex * v1, * v2, * v3; + gts_triangle_vertices (t, &v1, &v2, &v3); + fprintf (fp, "3 %u %u %u", + GPOINTER_TO_UINT (GTS_OBJECT (v1)->reserved), + GPOINTER_TO_UINT (GTS_OBJECT (v2)->reserved), + GPOINTER_TO_UINT (GTS_OBJECT (v3)->reserved)); + if (GTS_OBJECT (t)->klass->color) { + GtsColor c = (* GTS_OBJECT (t)->klass->color) (GTS_OBJECT (t)); + fprintf (fp, " %g %g %g\n", c.r, c.g, c.b); + } + else + fputc ('\n', fp); +} + +/** + * gts_surface_write_oogl: + * @s: a #GtsSurface. + * @fptr: a file pointer. + * + * Writes in the file @fptr an OOGL (Geomview) representation of @s. + */ +void gts_surface_write_oogl (GtsSurface * s, FILE * fptr) +{ + guint n = 0; + gpointer data[2]; + GtsSurfaceStats stats; + + g_return_if_fail (s != NULL); + g_return_if_fail (fptr != NULL); + + data[0] = fptr; + data[1] = &n; + + gts_surface_stats (s, &stats); + if (GTS_OBJECT_CLASS (s->vertex_class)->color) + fputs ("COFF ", fptr); + else + fputs ("OFF ", fptr); + fprintf (fptr, "%u %u %u\n", + stats.edges_per_vertex.n, + stats.n_faces, + stats.faces_per_edge.n); + gts_surface_foreach_vertex (s, (GtsFunc) write_vertex_oogl, data); + gts_surface_foreach_face (s, (GtsFunc) write_face_oogl, fptr); + gts_surface_foreach_vertex (s, (GtsFunc) gts_object_reset_reserved, NULL); +} + +static void write_vertex_vtk (GtsPoint * p, gpointer * data) +{ + FILE * fp = data[0]; + + fprintf (fp, "%g %g %g\n", p->x, p->y, p->z); + GTS_OBJECT (p)->reserved = GUINT_TO_POINTER ((*((guint *) data[1]))++); +} + +static void write_face_vtk (GtsTriangle * t, FILE * fp) +{ + GtsVertex * v1, * v2, * v3; + gts_triangle_vertices (t, &v1, &v2, &v3); + fprintf (fp, "3 %u %u %u\n", + GPOINTER_TO_UINT (GTS_OBJECT (v1)->reserved), + GPOINTER_TO_UINT (GTS_OBJECT (v2)->reserved), + GPOINTER_TO_UINT (GTS_OBJECT (v3)->reserved)); +} + +/** + * gts_surface_write_vtk: + * @s: a #GtsSurface. + * @fptr: a file pointer. + * + * Writes in the file @fptr a VTK representation of @s. + */ +void gts_surface_write_vtk (GtsSurface * s, FILE * fptr) +{ + guint n = 0; + gpointer data[2]; + GtsSurfaceStats stats; + + g_return_if_fail (s != NULL); + g_return_if_fail (fptr != NULL); + + data[0] = fptr; + data[1] = &n; + + gts_surface_stats (s, &stats); + fprintf (fptr, + "# vtk DataFile Version 2.0\n" + "Generated by GTS\n" + "ASCII\n" + "DATASET POLYDATA\n" + "POINTS %u float\n", + stats.edges_per_vertex.n); + gts_surface_foreach_vertex (s, (GtsFunc) write_vertex_vtk, data); + fprintf (fptr, + "POLYGONS %u %u\n", + stats.n_faces, stats.n_faces*4); + gts_surface_foreach_face (s, (GtsFunc) write_face_vtk, fptr); + gts_surface_foreach_vertex (s, (GtsFunc) gts_object_reset_reserved, NULL); +} + +static void write_edge_oogl_boundary (GtsSegment * s, gpointer * data) +{ + if (!gts_edge_is_boundary (GTS_EDGE (s), data[1])) + return; + + if (GTS_OBJECT (s)->klass->color) { + GtsColor c = (* GTS_OBJECT (s)->klass->color) (GTS_OBJECT (s)); + fprintf (data[0], "VECT 1 2 1 2 1 %g %g %g %g %g %g %g %g %g 1.\n", + GTS_POINT (s->v1)->x, GTS_POINT (s->v1)->y, GTS_POINT (s->v1)->z, + GTS_POINT (s->v2)->x, GTS_POINT (s->v2)->y, GTS_POINT (s->v2)->z, + c.r, c.g, c.b); + } + else + fprintf (data[0], "VECT 1 2 0 2 0 %g %g %g %g %g %g\n", + GTS_POINT (s->v1)->x, GTS_POINT (s->v1)->y, GTS_POINT (s->v1)->z, + GTS_POINT (s->v2)->x, GTS_POINT (s->v2)->y, GTS_POINT (s->v2)->z); +} + +/** + * gts_surface_write_oogl_boundary: + * @s: a #GtsSurface. + * @fptr: a file pointer. + * + * Writes in the file @fptr an OOGL (Geomview) representation of the + * boundary of @s. + */ +void gts_surface_write_oogl_boundary (GtsSurface * s, FILE * fptr) +{ + gpointer data[2]; + + g_return_if_fail (s != NULL); + g_return_if_fail (fptr != NULL); + + data[0] = fptr; + data[1] = s; + fputs ("LIST {\n", fptr); + gts_surface_foreach_edge (s, (GtsFunc) write_edge_oogl_boundary, data); + fputs ("}\n", fptr); +} + +#ifdef USE_SURFACE_BTREE +static gint vertex_foreach_face (GtsTriangle * t, + gpointer t_data, + gpointer * info) +#else /* not USE_SURFACE_BTREE */ +static void vertex_foreach_face (GtsTriangle * t, + gpointer t_data, + gpointer * info) +#endif /* not USE_SURFACE_BTREE */ +{ + GHashTable * hash = info[0]; + gpointer data = info[1]; + GtsFunc func = (GtsFunc) info[2]; + GtsSegment + * s1 = GTS_SEGMENT (t->e1); + + if (!g_hash_table_lookup (hash, s1->v1)) { + (*func) (s1->v1, data); + g_hash_table_insert (hash, s1->v1, GINT_TO_POINTER (-1)); + } + if (!g_hash_table_lookup (hash, s1->v2)) { + (*func) (s1->v2, data); + g_hash_table_insert (hash, s1->v2, GINT_TO_POINTER (-1)); + } + if (!g_hash_table_lookup (hash, gts_triangle_vertex (t))) { + (*func) (gts_triangle_vertex (t), data); + g_hash_table_insert (hash, gts_triangle_vertex (t), + GINT_TO_POINTER (-1)); + } +#ifdef USE_SURFACE_BTREE + return FALSE; +#endif /* USE_SURFACE_BTREE */ +} + +/** + * gts_surface_foreach_vertex: + * @s: a #GtsSurface. + * @func: a #GtsFunc. + * @data: user data to be passed to @func. + * + * Calls @func once for each vertex of @s. + */ +void gts_surface_foreach_vertex (GtsSurface * s, GtsFunc func, gpointer data) +{ + gpointer info[3]; + + g_return_if_fail (s != NULL); + g_return_if_fail (func != NULL); + + /* forbid removal of faces */ + s->keep_faces = TRUE; + info[0] = g_hash_table_new (NULL, NULL); + info[1] = data; + info[2] = func; +#ifdef USE_SURFACE_BTREE + g_tree_traverse (s->faces, (GTraverseFunc) vertex_foreach_face, G_IN_ORDER, + info); +#else /* not USE_SURFACE_BTREE */ + g_hash_table_foreach (s->faces, (GHFunc) vertex_foreach_face, info); +#endif /* not USE_SURFACE_BTREE */ + g_hash_table_destroy (info[0]); + /* allow removal of faces */ + s->keep_faces = FALSE; +} + +#ifdef USE_SURFACE_BTREE +static gint edge_foreach_face (GtsTriangle * t, + gpointer t_data, + gpointer * info) +#else /* not USE_SURFACE_BTREE */ +static void edge_foreach_face (GtsTriangle * t, + gpointer t_data, + gpointer * info) +#endif /* not USE_SURFACE_BTREE */ +{ + GHashTable * hash = info[0]; + gpointer data = info[1]; + GtsFunc func = (GtsFunc) info[2]; + + if (!g_hash_table_lookup (hash, t->e1)) { + (*func) (t->e1, data); + g_hash_table_insert (hash, t->e1, GINT_TO_POINTER (-1)); + } + if (!g_hash_table_lookup (hash, t->e2)) { + (*func) (t->e2, data); + g_hash_table_insert (hash, t->e2, GINT_TO_POINTER (-1)); + } + if (!g_hash_table_lookup (hash, t->e3)) { + (*func) (t->e3, data); + g_hash_table_insert (hash, t->e3, GINT_TO_POINTER (-1)); + } +#ifdef USE_SURFACE_BTREE + return FALSE; +#endif /* not USE_SURFACE_BTREE */ +} + +/** + * gts_surface_foreach_edge: + * @s: a #GtsSurface. + * @func: a #GtsFunc. + * @data: user data to be passed to @func. + * + * Calls @func once for each edge of @s. + */ +void gts_surface_foreach_edge (GtsSurface * s, GtsFunc func, gpointer data) +{ + gpointer info[3]; + + g_return_if_fail (s != NULL); + g_return_if_fail (func != NULL); + + /* forbid removal of faces */ + s->keep_faces = TRUE; + info[0] = g_hash_table_new (NULL, NULL); + info[1] = data; + info[2] = func; +#ifdef USE_SURFACE_BTREE + g_tree_traverse (s->faces, (GTraverseFunc) edge_foreach_face, G_IN_ORDER, + info); +#else /* not USE_SURFACE_BTREE */ + g_hash_table_foreach (s->faces, (GHFunc) edge_foreach_face, info); +#endif /* not USE_SURFACE_BTREE */ + g_hash_table_destroy (info[0]); + /* allow removal of faces */ + s->keep_faces = FALSE; +} + +#ifdef USE_SURFACE_BTREE +static gint foreach_face (GtsFace * f, + gpointer t_data, + gpointer * info) +#else /* not USE_SURFACE_BTREE */ +static void foreach_face (GtsFace * f, + gpointer t_data, + gpointer * info) +#endif /* not USE_SURFACE_BTREE */ +{ + (*((GtsFunc) info[0])) (f, info[1]); +#ifdef USE_SURFACE_BTREE + return FALSE; +#endif /* USE_SURFACE_BTREE */ +} + +/** + * gts_surface_foreach_face: + * @s: a #GtsSurface. + * @func: a #GtsFunc. + * @data: user data to be passed to @func. + * + * Calls @func once for each face of @s. + */ +void gts_surface_foreach_face (GtsSurface * s, + GtsFunc func, + gpointer data) +{ + gpointer info[2]; + + g_return_if_fail (s != NULL); + g_return_if_fail (func != NULL); + + /* forbid removal of faces */ + s->keep_faces = TRUE; + info[0] = func; + info[1] = data; +#ifdef USE_SURFACE_BTREE + g_tree_traverse (s->faces, (GTraverseFunc) foreach_face, G_IN_ORDER, + info); +#else /* not USE_SURFACE_BTREE */ + g_hash_table_foreach (s->faces, (GHFunc) foreach_face, info); +#endif /* not USE_SURFACE_BTREE */ + /* allow removal of faces */ + s->keep_faces = FALSE; +} + +#ifdef USE_SURFACE_BTREE +static gint foreach_face_remove (GtsFace * f, + gpointer t_data, + gpointer * info) +{ + if ((*((GtsFunc) info[0])) (f, info[1])) { + GtsSurface * s = info[2]; + guint * n = info[3]; + + f->surfaces = g_slist_remove (f->surfaces, s); + if (!GTS_OBJECT_DESTROYED (f) && + !gts_allow_floating_faces && + f->surfaces == NULL) + gts_object_destroy (GTS_OBJECT (f)); + + if (GTS_SURFACE_CLASS (GTS_OBJECT (s)->klass)->remove_face) + (* GTS_SURFACE_CLASS (GTS_OBJECT (s)->klass)->remove_face) (s, f); + + g_tree_remove (s->faces, f); + (*n)++; + } + return FALSE; +} +#else /* not USE_SURFACE_BTREE */ +static gboolean foreach_face_remove (GtsFace * f, + gpointer t_data, + gpointer * info) +{ + if ((*((GtsFunc) info[0])) (f, info[1])) { + GtsSurface * s = info[2]; + + f->surfaces = g_slist_remove (f->surfaces, s); + if (!GTS_OBJECT_DESTROYED (f) && + !gts_allow_floating_faces && + f->surfaces == NULL) + gts_object_destroy (GTS_OBJECT (f)); + + if (GTS_SURFACE_CLASS (GTS_OBJECT (s)->klass)->remove_face) + (* GTS_SURFACE_CLASS (GTS_OBJECT (s)->klass)->remove_face) (s, f); + + return TRUE; + } + return FALSE; +} +#endif /* not USE_SURFACE_BTREE */ + +/** + * gts_surface_foreach_face_remove: + * @s: a #GtsSurface. + * @func: a #GtsFunc. + * @data: user data to be passed to @func. + * + * Calls @func once for each face of @s. If @func returns %TRUE the + * corresponding face is removed from @s (and destroyed if it does not + * belong to any other surface and #gts_allow_floating_faces is set to + * %FALSE). + * + * Returns: the number of faces removed from @s. + */ +guint gts_surface_foreach_face_remove (GtsSurface * s, + GtsFunc func, + gpointer data) +{ + gpointer info[4]; + guint n = 0; + + g_return_val_if_fail (s != NULL, 0); + g_return_val_if_fail (func != NULL, 0); + + /* forbid removal of faces */ + s->keep_faces = TRUE; + info[0] = func; + info[1] = data; + info[2] = s; +#ifdef USE_SURFACE_BTREE + info[3] = &n; + g_tree_traverse (s->faces, (GTraverseFunc) foreach_face_remove, G_PRE_ORDER, + info); +#else /* not USE_SURFACE_BTREE */ + n = g_hash_table_foreach_remove (s->faces, + (GHRFunc) foreach_face_remove, + info); +#endif /* not USE_SURFACE_BTREE */ + /* allow removal of faces */ + s->keep_faces = FALSE; + + return n; +} + +static void midvertex_insertion (GtsEdge * e, + GtsSurface * surface, + GtsEHeap * heap, + GtsRefineFunc refine_func, + gpointer refine_data, + GtsVertexClass * vertex_class, + GtsEdgeClass * edge_class) +{ + GtsVertex * midvertex; + GtsEdge * e1, * e2; + GSList * i; + + midvertex = (*refine_func) (e, vertex_class, refine_data); + e1 = gts_edge_new (edge_class, GTS_SEGMENT (e)->v1, midvertex); + gts_eheap_insert (heap, e1); + e2 = gts_edge_new (edge_class, GTS_SEGMENT (e)->v2, midvertex); + gts_eheap_insert (heap, e2); + + /* creates new faces and modifies old ones */ + i = e->triangles; + while (i) { + GtsTriangle * t = i->data; + GtsVertex * v1, * v2, * v3; + GtsEdge * te2, * te3, * ne, * tmp; + + gts_triangle_vertices_edges (t, e, &v1, &v2, &v3, &e, &te2, &te3); + ne = gts_edge_new (edge_class, midvertex, v3); + gts_eheap_insert (heap, ne); + if (GTS_SEGMENT (e1)->v1 == v2) { + tmp = e1; e1 = e2; e2 = tmp; + } + e1->triangles = g_slist_prepend (e1->triangles, t); + ne->triangles = g_slist_prepend (ne->triangles, t); + te2->triangles = g_slist_remove (te2->triangles, t); + t->e1 = e1; t->e2 = ne; t->e3 = te3; + gts_surface_add_face (surface, + gts_face_new (surface->face_class, e2, te2, ne)); + i = i->next; + } + /* destroys edge */ + g_slist_free (e->triangles); + e->triangles = NULL; + gts_object_destroy (GTS_OBJECT (e)); +} + +static gdouble edge_length2_inverse (GtsSegment * s) +{ + return - gts_point_distance2 (GTS_POINT (s->v1), GTS_POINT (s->v2)); +} + +static void create_heap_refine (GtsEdge * e, GtsEHeap * heap) +{ + gts_eheap_insert (heap, e); +} + +/** + * gts_surface_refine: + * @surface: a #GtsSurface. + * @cost_func: a function returning the cost for a given edge. + * @cost_data: user data to be passed to @cost_func. + * @refine_func: a #GtsRefineFunc. + * @refine_data: user data to be passed to @refine_func. + * @stop_func: a #GtsStopFunc. + * @stop_data: user data to be passed to @stop_func. + * + * Refine @surface using a midvertex insertion technique. All the + * edges of @surface are ordered according to @cost_func. The edges + * are then processed in order until @stop_func returns %TRUE. Each + * edge is split in two and new edges and faces are created. + * + * If @cost_func is set to %NULL, the edges are sorted according + * to their length squared (the longest is on top). + * + * If @refine_func is set to %NULL gts_segment_midvertex() is used. + * + */ +void gts_surface_refine (GtsSurface * surface, + GtsKeyFunc cost_func, + gpointer cost_data, + GtsRefineFunc refine_func, + gpointer refine_data, + GtsStopFunc stop_func, + gpointer stop_data) +{ + GtsEHeap * heap; + GtsEdge * e; + gdouble top_cost; + + g_return_if_fail (surface != NULL); + g_return_if_fail (stop_func != NULL); + + if (cost_func == NULL) + cost_func = (GtsKeyFunc) edge_length2_inverse; + if (refine_func == NULL) + refine_func = (GtsRefineFunc) gts_segment_midvertex; + + heap = gts_eheap_new (cost_func, cost_data); + gts_eheap_freeze (heap); + gts_surface_foreach_edge (surface, (GtsFunc) create_heap_refine, heap); + gts_eheap_thaw (heap); + while ((e = gts_eheap_remove_top (heap, &top_cost)) && + !(*stop_func) (top_cost, + gts_eheap_size (heap) + + gts_edge_face_number (e, surface) + 2, + stop_data)) + midvertex_insertion (e, surface, heap, refine_func, refine_data, + surface->vertex_class, surface->edge_class); + gts_eheap_destroy (heap); +} + +static GSList * edge_triangles (GtsEdge * e1, GtsEdge * e) +{ + GSList * i = e1->triangles; + GSList * triangles = NULL; + + while (i) { + GtsTriangle * t = i->data; + if (t->e1 == e || t->e2 == e || t->e3 == e) { + GtsEdge * e2; + GSList * j; + if (t->e1 == e) { + if (t->e2 == e1) + e2 = t->e3; + else + e2 = t->e2; + } + else if (t->e2 == e) { + if (t->e3 == e1) + e2 = t->e1; + else + e2 = t->e3; + } + else { + if (t->e2 == e1) + e2 = t->e1; + else + e2 = t->e2; + } + j = e2->triangles; + while (j) { + GtsTriangle * t = j->data; + if (t->e1 != e && t->e2 != e && t->e3 != e) + triangles = g_slist_prepend (triangles, t); + j = j->next; + } + } + else + triangles = g_slist_prepend (triangles, t); + i = i->next; + } + return triangles; +} + +static void replace_vertex (GSList * i, GtsVertex * v1, GtsVertex * v) +{ + while (i) { + GtsSegment * s = i->data; + if (s->v1 == v1) + s->v1 = v; + else + s->v2 = v; + i = i->next; + } +} + +/** + * gts_edge_collapse_creates_fold: + * @e: a #GtsEdge. + * @v: a #GtsVertex. + * @max: the maximum value of the square of the cosine of the angle between + * two triangles. + * + * Returns: %TRUE if collapsing edge @e to vertex @v would create + * faces making an angle the cosine squared of which would be larger than max, + * %FALSE otherwise. + */ +gboolean gts_edge_collapse_creates_fold (GtsEdge * e, + GtsVertex * v, + gdouble max) +{ + GtsVertex * v1, * v2; + GtsSegment * s; + GSList * i; + gboolean folded = FALSE; + + g_return_val_if_fail (e != NULL, TRUE); + g_return_val_if_fail (v != NULL, TRUE); + + s = GTS_SEGMENT (e); + v1 = s->v1; + v2 = s->v2; + replace_vertex (v1->segments, v1, v); + replace_vertex (v2->segments, v2, v); + + i = v1->segments; + while (i && !folded) { + GtsSegment * s = i->data; + if (GTS_IS_EDGE (s)) { + GtsEdge * e1 = GTS_EDGE (s); + if (e1 != e) { + GSList * triangles = edge_triangles (e1, e); + folded = gts_triangles_are_folded (triangles, s->v1, s->v2, max); + g_slist_free (triangles); + } + } + i = i->next; + } + + i = v2->segments; + while (i && !folded) { + GtsSegment * s = i->data; + if (GTS_IS_EDGE (s)) { + GtsEdge * e1 = GTS_EDGE (s); + if (e1 != e) { + GSList * triangles = edge_triangles (e1, e); + folded = gts_triangles_are_folded (triangles, s->v1, s->v2, max); + g_slist_free (triangles); + } + } + i = i->next; + } +#if 1 + if (!folded) { + GSList * triangles = gts_vertex_triangles (v1, NULL); + i = triangles = gts_vertex_triangles (v2, triangles); + while (i && !folded) { + GtsTriangle * t = i->data; + if (t->e1 != e && t->e2 != e && t->e3 != e) { + GtsEdge * e1 = gts_triangle_edge_opposite (t, v); + g_assert (e1); + folded = gts_triangles_are_folded (e1->triangles, + GTS_SEGMENT (e1)->v1, + GTS_SEGMENT (e1)->v2, + max); + } + i = i->next; + } + g_slist_free (triangles); + } +#endif + replace_vertex (v1->segments, v, v1); + replace_vertex (v2->segments, v, v2); + return folded; +} + +/** + * gts_edge_collapse_is_valid: + * @e: a #GtsEdge. + * + * An implementation of the topological constraints described in the + * "Mesh Optimization" article of Hoppe et al (1993). + * + * Returns: %TRUE if @e can be collapsed without violation of the topological + * constraints, %FALSE otherwise. + */ +gboolean gts_edge_collapse_is_valid (GtsEdge * e) +{ + GSList * i; + + g_return_val_if_fail (e != NULL, FALSE); + + i = GTS_SEGMENT (e)->v1->segments; + while (i) { + GtsEdge * e1 = i->data; + if (e1 != e && GTS_IS_EDGE (e1)) { + GtsEdge * e2 = NULL; + GSList * j = GTS_SEGMENT (e1)->v1 == GTS_SEGMENT (e)->v1 ? + GTS_SEGMENT (e1)->v2->segments : GTS_SEGMENT (e1)->v1->segments; + while (j && !e2) { + GtsEdge * e1 = j->data; + if (GTS_IS_EDGE (e1) && + (GTS_SEGMENT (e1)->v1 == GTS_SEGMENT (e)->v2 || + GTS_SEGMENT (e1)->v2 == GTS_SEGMENT (e)->v2)) + e2 = e1; + j = j->next; + } + if (e2 && !gts_triangle_use_edges (e, e1, e2)) + return FALSE; + } + i = i->next; + } + + if (gts_edge_is_boundary (e, NULL)) { + GtsTriangle * t = e->triangles->data; + if (gts_edge_is_boundary (t->e1, NULL) && + gts_edge_is_boundary (t->e2, NULL) && + gts_edge_is_boundary (t->e3, NULL)) + return FALSE; + } + else { + if (gts_vertex_is_boundary (GTS_SEGMENT (e)->v1, NULL) && + gts_vertex_is_boundary (GTS_SEGMENT (e)->v2, NULL)) + return FALSE; + if (gts_edge_belongs_to_tetrahedron (e)) + return FALSE; + } + + return TRUE; +} + +#define HEAP_INSERT_EDGE(h, e) (GTS_OBJECT (e)->reserved = gts_eheap_insert (h, e)) +#define HEAP_REMOVE_EDGE(h, e) (gts_eheap_remove (h, GTS_OBJECT (e)->reserved),\ + GTS_OBJECT (e)->reserved = NULL) + +static GtsVertex * edge_collapse (GtsEdge * e, + GtsEHeap * heap, + GtsCoarsenFunc coarsen_func, + gpointer coarsen_data, + GtsVertexClass * klass, + gdouble maxcosine2) +{ + GSList * i; + GtsVertex * v1 = GTS_SEGMENT (e)->v1, * v2 = GTS_SEGMENT (e)->v2, * mid; + + /* 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)) { + GTS_OBJECT (e)->reserved = + gts_eheap_insert_with_key (heap, e, G_MAXDOUBLE); + return NULL; + } + + mid = (*coarsen_func) (e, klass, 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; + } + + gts_object_destroy (GTS_OBJECT (e)); + + gts_vertex_replace (v1, mid); + gts_object_destroy (GTS_OBJECT (v1)); + gts_vertex_replace (v2, mid); + gts_object_destroy (GTS_OBJECT (v2)); + + /* destroy duplicate edges */ + i = mid->segments; + while (i) { + GtsEdge * e1 = i->data; + GtsEdge * duplicate; + while ((duplicate = gts_edge_is_duplicate (e1))) { + gts_edge_replace (duplicate, GTS_EDGE (e1)); + HEAP_REMOVE_EDGE (heap, duplicate); + gts_object_destroy (GTS_OBJECT (duplicate)); + } + i = i->next; + if (!e1->triangles) { + /* e1 is the result of the collapse of one edge of a pair of identical + faces (it should not happen unless duplicate triangles are present in + the initial surface) */ + g_warning ("file %s: line %d (%s): probably duplicate triangle.", + __FILE__, __LINE__, G_GNUC_PRETTY_FUNCTION); + HEAP_REMOVE_EDGE (heap, e1); + gts_object_destroy (GTS_OBJECT (e1)); + if (i == NULL) /* mid has been destroyed */ + mid = NULL; + } + } + + return mid; +} + +/* + * I don't see where this code is ever used, but keep it for a bit + * in case it is needed for debugging + */ +#ifdef GTS_NEED_UPDATE_CLOSEST_NEIGHBORS +static void update_closest_neighbors (GtsVertex * v, GtsEHeap * heap) +{ + GSList * i = v->segments; + + while (i) { + GtsSegment * s = i->data; + if (GTS_IS_EDGE (s)) { + HEAP_REMOVE_EDGE (heap, GTS_EDGE (s)); + HEAP_INSERT_EDGE (heap, GTS_EDGE (s)); + } + i = i->next; + } +} +#endif + +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; + HEAP_REMOVE_EDGE (heap, e); + HEAP_INSERT_EDGE (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_EDGE (heap, e); +} + +/** + * gts_surface_coarsen: + * @surface: a #GtsSurface. + * @cost_func: a function returning the cost for a given edge. + * @cost_data: user data to be passed to @cost_func. + * @coarsen_func: a #GtsCoarsenVertexFunc. + * @coarsen_data: user data to be passed to @coarsen_func. + * @stop_func: a #GtsStopFunc. + * @stop_data: user data to be passed to @stop_func. + * @minangle: minimum angle between two neighboring triangles. + * + * The edges of @surface are sorted according to @cost_func to + * create a priority heap (a #GtsEHeap). The edges are extracted in + * turn from the top of the heap and collapsed (i.e. the vertices are + * replaced by the vertex returned by the @coarsen_func function) + * until the @stop_func functions returns %TRUE. + * + * If @cost_func is set to %NULL, the edges are sorted according + * to their length squared (the shortest is on top). + * + * If @coarsen_func is set to %NULL gts_segment_midvertex() is used. + * + * The minimum angle is used to avoid introducing faces which would be folded. + */ +void gts_surface_coarsen (GtsSurface * surface, + GtsKeyFunc cost_func, + gpointer cost_data, + GtsCoarsenFunc coarsen_func, + gpointer coarsen_data, + GtsStopFunc stop_func, + gpointer stop_data, + gdouble minangle) +{ + GtsEHeap * heap; + GtsEdge * e; + gdouble top_cost; + gdouble maxcosine2; + + g_return_if_fail (surface != NULL); + g_return_if_fail (stop_func != NULL); + + 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 (e, heap, coarsen_func, coarsen_data, + surface->vertex_class, maxcosine2); + if (v != NULL) + update_2nd_closest_neighbors (v, heap); + } + 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); +} + +/** + * gts_coarsen_stop_number: + * @cost: the cost of the edge collapse considered. + * @nedge: the current number of edges of the surface being simplified. + * @min_number: a pointer to the minimum number of edges desired for the + * surface being simplified. + * + * This function is to be used as the @stop_func argument of + * gts_surface_coarsen() or gts_psurface_new(). + * + * Returns: %TRUE if the edge collapse would create a surface with a smaller + * number of edges than given by @min_number, %FALSE otherwise. + */ +gboolean gts_coarsen_stop_number (gdouble cost, + guint nedge, + guint * min_number) +{ + g_return_val_if_fail (min_number != NULL, TRUE); + + if (nedge < *min_number) + return TRUE; + return FALSE; +} + +/** + * gts_coarsen_stop_cost: + * @cost: the cost of the edge collapse considered. + * @nedge: the current number of edges of the surface being simplified. + * @max_cost: a pointer to the maximum cost allowed for an edge collapse. + * + * This function is to be used as the @stop_func argument of + * gts_surface_coarsen() or gts_psurface_new(). + * + * Returns: %TRUE if the cost of the edge collapse considered is larger than + * given by @max_cost, %FALSE otherwise. + */ +gboolean gts_coarsen_stop_cost (gdouble cost, + guint nedge, + gdouble * max_cost) +{ + g_return_val_if_fail (max_cost != NULL, TRUE); + + if (cost > *max_cost) + return TRUE; + return FALSE; +} + +#define GTS_M_ICOSAHEDRON_X /* sqrt(sqrt(5)+1)/sqrt(2*sqrt(5)) */ \ + 0.850650808352039932181540497063011072240401406 +#define GTS_M_ICOSAHEDRON_Y /* sqrt(2)/sqrt(5+sqrt(5)) */ \ + 0.525731112119133606025669084847876607285497935 +#define GTS_M_ICOSAHEDRON_Z 0.0 + +static guint generate_icosahedron (GtsSurface * s) +{ + GtsVertex * v01 = gts_vertex_new (s->vertex_class, + +GTS_M_ICOSAHEDRON_Z, +GTS_M_ICOSAHEDRON_X, -GTS_M_ICOSAHEDRON_Y); + GtsVertex * v02 = gts_vertex_new (s->vertex_class, + +GTS_M_ICOSAHEDRON_X, +GTS_M_ICOSAHEDRON_Y, +GTS_M_ICOSAHEDRON_Z); + GtsVertex * v03 = gts_vertex_new (s->vertex_class, + +GTS_M_ICOSAHEDRON_Y, +GTS_M_ICOSAHEDRON_Z, -GTS_M_ICOSAHEDRON_X); + GtsVertex * v04 = gts_vertex_new (s->vertex_class, + +GTS_M_ICOSAHEDRON_Y, +GTS_M_ICOSAHEDRON_Z, +GTS_M_ICOSAHEDRON_X); + GtsVertex * v05 = gts_vertex_new (s->vertex_class, + +GTS_M_ICOSAHEDRON_X, -GTS_M_ICOSAHEDRON_Y, +GTS_M_ICOSAHEDRON_Z); + GtsVertex * v06 = gts_vertex_new (s->vertex_class, + +GTS_M_ICOSAHEDRON_Z, +GTS_M_ICOSAHEDRON_X, +GTS_M_ICOSAHEDRON_Y); + GtsVertex * v07 = gts_vertex_new (s->vertex_class, + -GTS_M_ICOSAHEDRON_Y, +GTS_M_ICOSAHEDRON_Z, +GTS_M_ICOSAHEDRON_X); + GtsVertex * v08 = gts_vertex_new (s->vertex_class, + +GTS_M_ICOSAHEDRON_Z, -GTS_M_ICOSAHEDRON_X, -GTS_M_ICOSAHEDRON_Y); + GtsVertex * v09 = gts_vertex_new (s->vertex_class, + -GTS_M_ICOSAHEDRON_X, +GTS_M_ICOSAHEDRON_Y, +GTS_M_ICOSAHEDRON_Z); + GtsVertex * v10 = gts_vertex_new (s->vertex_class, + -GTS_M_ICOSAHEDRON_Y, +GTS_M_ICOSAHEDRON_Z, -GTS_M_ICOSAHEDRON_X); + GtsVertex * v11 = gts_vertex_new (s->vertex_class, + -GTS_M_ICOSAHEDRON_X, -GTS_M_ICOSAHEDRON_Y, +GTS_M_ICOSAHEDRON_Z); + GtsVertex * v12 = gts_vertex_new (s->vertex_class, + +GTS_M_ICOSAHEDRON_Z, -GTS_M_ICOSAHEDRON_X, +GTS_M_ICOSAHEDRON_Y); + + GtsEdge * e01 = gts_edge_new (s->edge_class, v01, v02); + GtsEdge * e02 = gts_edge_new (s->edge_class, v03, v02); + GtsEdge * e03 = gts_edge_new (s->edge_class, v01, v03); + GtsEdge * e04 = gts_edge_new (s->edge_class, v04, v05); + GtsEdge * e05 = gts_edge_new (s->edge_class, v02, v05); + GtsEdge * e06 = gts_edge_new (s->edge_class, v04, v02); + GtsEdge * e07 = gts_edge_new (s->edge_class, v06, v07); + GtsEdge * e08 = gts_edge_new (s->edge_class, v04, v07); + GtsEdge * e09 = gts_edge_new (s->edge_class, v06, v04); + GtsEdge * e10 = gts_edge_new (s->edge_class, v08, v03); + GtsEdge * e11 = gts_edge_new (s->edge_class, v03, v05); + GtsEdge * e12 = gts_edge_new (s->edge_class, v08, v05); + GtsEdge * e13 = gts_edge_new (s->edge_class, v06, v09); + GtsEdge * e14 = gts_edge_new (s->edge_class, v07, v09); + GtsEdge * e15 = gts_edge_new (s->edge_class, v08, v10); + GtsEdge * e16 = gts_edge_new (s->edge_class, v03, v10); + GtsEdge * e17 = gts_edge_new (s->edge_class, v06, v01); + GtsEdge * e18 = gts_edge_new (s->edge_class, v01, v09); + GtsEdge * e19 = gts_edge_new (s->edge_class, v08, v11); + GtsEdge * e20 = gts_edge_new (s->edge_class, v10, v11); + GtsEdge * e21 = gts_edge_new (s->edge_class, v06, v02); + GtsEdge * e22 = gts_edge_new (s->edge_class, v12, v11); + GtsEdge * e23 = gts_edge_new (s->edge_class, v12, v08); + GtsEdge * e24 = gts_edge_new (s->edge_class, v12, v07); + GtsEdge * e25 = gts_edge_new (s->edge_class, v07, v11); + GtsEdge * e26 = gts_edge_new (s->edge_class, v12, v04); + GtsEdge * e27 = gts_edge_new (s->edge_class, v09, v11); + GtsEdge * e28 = gts_edge_new (s->edge_class, v10, v09); + GtsEdge * e29 = gts_edge_new (s->edge_class, v12, v05); + GtsEdge * e30 = gts_edge_new (s->edge_class, v01, v10); + + gts_surface_add_face (s, gts_face_new (s->face_class, e01, e02, e03)); + gts_surface_add_face (s, gts_face_new (s->face_class, e04, e05, e06)); + gts_surface_add_face (s, gts_face_new (s->face_class, e07, e08, e09)); + gts_surface_add_face (s, gts_face_new (s->face_class, e10, e11, e12)); + gts_surface_add_face (s, gts_face_new (s->face_class, e13, e14, e07)); + gts_surface_add_face (s, gts_face_new (s->face_class, e15, e16, e10)); + gts_surface_add_face (s, gts_face_new (s->face_class, e17, e18, e13)); + gts_surface_add_face (s, gts_face_new (s->face_class, e19, e20, e15)); + gts_surface_add_face (s, gts_face_new (s->face_class, e21, e01, e17)); + gts_surface_add_face (s, gts_face_new (s->face_class, e22, e19, e23)); + gts_surface_add_face (s, gts_face_new (s->face_class, e09, e06, e21)); + gts_surface_add_face (s, gts_face_new (s->face_class, e24, e25, e22)); + gts_surface_add_face (s, gts_face_new (s->face_class, e26, e08, e24)); + gts_surface_add_face (s, gts_face_new (s->face_class, e20, e27, e28)); + gts_surface_add_face (s, gts_face_new (s->face_class, e29, e04, e26)); + gts_surface_add_face (s, gts_face_new (s->face_class, e14, e27, e25)); + gts_surface_add_face (s, gts_face_new (s->face_class, e23, e12, e29)); + gts_surface_add_face (s, gts_face_new (s->face_class, e02, e05, e11)); + gts_surface_add_face (s, gts_face_new (s->face_class, e30, e28, e18)); + gts_surface_add_face (s, gts_face_new (s->face_class, e03, e16, e30)); + + return 0; +} + +static GtsVertex * unit_sphere_arc_midvertex (GtsSegment * s, + GtsVertexClass * vertex_class) +{ + GtsPoint * p1, * p2; + gdouble x, y, z, norm; + + p1 = GTS_POINT (s->v1); p2 = GTS_POINT (s->v2); + + x = 0.5*(p1->x + p2->x); + y = 0.5*(p1->y + p2->y); + z = 0.5*(p1->z + p2->z); + + norm = x*x + y*y + z*z; + norm = sqrt (norm); + + x /= norm; y /= norm; z /= norm; + + return gts_vertex_new (vertex_class, x, y, z); +} + +static void tessellate_face (GtsFace * f, + GtsSurface * s, + GtsRefineFunc refine_func, + gpointer refine_data, + GtsVertexClass * vertex_class, + GtsEdgeClass * edge_class) +{ + GtsTriangle * t; + GtsEdge * e1, * e2, * e3; /* former edges */ + GtsVertex * v1, * v2, * v3; /* initial vertices */ + GtsVertex * v4, * v5, * v6; /* new vertices */ + GtsEdge * e56, * e64, * e45; /* new inside edges */ + GtsEdge * e24, * e34, * e35, * e15, * e16, * e26; /* new border edges */ + GSList * dum; + GtsEdge * edum; + + t = GTS_TRIANGLE (f); + e1 = t->e1; e2 = t->e2; e3 = t->e3; + + if (GTS_SEGMENT (e1)->v2 == GTS_SEGMENT (e2)->v1) { + v1 = GTS_SEGMENT (e2)->v2; + v2 = GTS_SEGMENT (e1)->v1; + v3 = GTS_SEGMENT (e1)->v2; + } + else if (GTS_SEGMENT (e1)->v2 == GTS_SEGMENT (e2)->v2) { + v1 = GTS_SEGMENT (e2)->v1; + v2 = GTS_SEGMENT (e1)->v1; + v3 = GTS_SEGMENT (e1)->v2; + } + else if (GTS_SEGMENT (e1)->v1 == GTS_SEGMENT (e2)->v1) { + v1 = GTS_SEGMENT (e2)->v2; + v2 = GTS_SEGMENT (e1)->v2; + v3 = GTS_SEGMENT (e1)->v1; + } + else if (GTS_SEGMENT (e1)->v1 == GTS_SEGMENT (e2)->v2) { + v1 = GTS_SEGMENT (e2)->v1; + v2 = GTS_SEGMENT (e1)->v2; + v3 = GTS_SEGMENT (e1)->v1; + } + else { + v1 = v2 = v3 = NULL; + g_assert_not_reached (); + } + + e1->triangles = g_slist_remove (e1->triangles, t); + e2->triangles = g_slist_remove (e2->triangles, t); + e3->triangles = g_slist_remove (e3->triangles, t); + + if (GTS_OBJECT (e1)->reserved) { + dum = (GTS_OBJECT (e1)->reserved); + e24 = dum->data; + e34 = dum->next->data; + v4 = GTS_SEGMENT (e24)->v2; + if (GTS_SEGMENT (e24)->v1 == v3) { + edum = e34; e34 = e24; e24 = edum; + } + } + else { + v4 = (*refine_func) (e1, vertex_class, refine_data); + e24 = gts_edge_new (edge_class, v2, v4); + e34 = gts_edge_new (edge_class, v3, v4); + dum = g_slist_append (NULL, e24); + dum = g_slist_append (dum, e34); + GTS_OBJECT (e1)->reserved = dum; + } + if (GTS_OBJECT (e2)->reserved) { + dum = (GTS_OBJECT (e2)->reserved); + e35 = dum->data; + e15 = dum->next->data; + v5 = GTS_SEGMENT (e35)->v2; + if (GTS_SEGMENT (e35)->v1 == v1) { + edum = e15; e15 = e35; e35 = edum; + } + } + else { + v5 = (*refine_func) (e2, vertex_class, refine_data); + e35 = gts_edge_new (edge_class, v3, v5); + e15 = gts_edge_new (edge_class, v1, v5); + dum = g_slist_append (NULL, e35); + dum = g_slist_append (dum, e15); + GTS_OBJECT (e2)->reserved = dum; + } + if (GTS_OBJECT (e3)->reserved) { + dum = (GTS_OBJECT (e3)->reserved); + e16 = dum->data; + e26 = dum->next->data; + v6 = GTS_SEGMENT (e16)->v2; + if (GTS_SEGMENT (e16)->v1 == v2) { + edum = e16; e16 = e26; e26 = edum; + } + } + else { + v6 = (*refine_func) (e3, vertex_class, refine_data); + e16 = gts_edge_new (edge_class, v1, v6); + e26 = gts_edge_new (edge_class, v2, v6); + dum = g_slist_append (NULL, e16); + dum = g_slist_append (dum, e26); + GTS_OBJECT (e3)->reserved = dum; + } + + if (e1->triangles == NULL) { + g_slist_free (GTS_OBJECT (e1)->reserved); + GTS_OBJECT (e1)->reserved = NULL; + gts_object_destroy (GTS_OBJECT (e1)); + e1 = NULL; + } + if (e2->triangles == NULL) { + g_slist_free (GTS_OBJECT (e2)->reserved); + GTS_OBJECT (e2)->reserved = NULL; + gts_object_destroy (GTS_OBJECT (e2)); + e2 = NULL; + } + if (e3->triangles == NULL) { + g_slist_free (GTS_OBJECT (e3)->reserved); + GTS_OBJECT (e3)->reserved = NULL; + gts_object_destroy (GTS_OBJECT (e3)); + e3 = NULL; + } + + e56 = gts_edge_new (edge_class, v5, v6); + e64 = gts_edge_new (edge_class, v6, v4); + e45 = gts_edge_new (edge_class, v4, v5); + t->e1 = e56; e56->triangles = g_slist_prepend (e56->triangles, t); + t->e2 = e64; e64->triangles = g_slist_prepend (e64->triangles, t); + t->e3 = e45; e45->triangles = g_slist_prepend (e45->triangles, t); + + gts_surface_add_face (s, gts_face_new (s->face_class, e16, e56, e15)); + gts_surface_add_face (s, gts_face_new (s->face_class, e26, e24, e64)); + gts_surface_add_face (s, gts_face_new (s->face_class, e45, e34, e35)); +} + +static void create_array_tessellate (GtsFace * f, GPtrArray * array) +{ + g_ptr_array_add (array, f); +} + +/** + * gts_surface_tessellate: + * @s: a #GtsSurface. + * @refine_func: a #GtsRefineFunc. + * @refine_data: user data to be passed to @refine_func. + * + * Tessellate each triangle of @s with 4 triangles: + * the number of triangles is increased by a factor of 4. + * http://mathworld.wolfram.com/GeodesicDome.html + * + * If @refine_func is set to %NULL a mid arc function is used: if + * the surface is a polyhedron with the unit sphere as circum sphere, + * then gts_surface_tessellate() corresponds to a geodesation step + * (see gts_surface_generate_sphere()). + * + */ +void gts_surface_tessellate (GtsSurface * s, + GtsRefineFunc refine_func, + gpointer refine_data) +{ + GPtrArray * array; + guint i; + + g_return_if_fail (s != NULL); + + if (refine_func == NULL) /* tessellate_surface == geodesate_surface */ + refine_func = (GtsRefineFunc) unit_sphere_arc_midvertex; + + array = g_ptr_array_new (); + gts_surface_foreach_face (s, (GtsFunc) create_array_tessellate, array); + for(i = 0; i < array->len; i++) + tessellate_face (g_ptr_array_index (array, i), + s, refine_func, refine_data, + s->vertex_class, s->edge_class); + g_ptr_array_free (array, TRUE); +} + +/** + * gts_surface_generate_sphere: + * @s: a #GtsSurface. + * @geodesation_order: a #guint. + * + * Add a triangulated unit sphere generated by recursive subdivision to @s. + * First approximation is an isocahedron; each level of refinement + * (@geodesation_order) increases the number of triangles by a factor of 4. + * http://mathworld.wolfram.com/GeodesicDome.html + * + * Returns: @s. + */ +GtsSurface * gts_surface_generate_sphere (GtsSurface * s, + guint geodesation_order) +{ + guint cgo; + + g_return_val_if_fail (s != NULL, NULL); + g_return_val_if_fail (geodesation_order != 0, NULL); + + generate_icosahedron (s); + + for (cgo = 1; cgo < geodesation_order; cgo++) + gts_surface_tessellate (s, NULL, NULL); + + return s; +} + +static void foreach_vertex_copy (GtsPoint * p, GtsVertexClass * klass) +{ + GTS_OBJECT (p)->reserved = gts_vertex_new (klass, p->x, p->y, p->z); +} + +static void foreach_edge_copy (GtsSegment * s, GtsEdgeClass * klass) +{ + GTS_OBJECT (s)->reserved = gts_edge_new (klass, + GTS_OBJECT (s->v1)->reserved, + GTS_OBJECT (s->v2)->reserved); +} + +static void foreach_face_copy (GtsTriangle * t, + GtsSurface * s) +{ + gts_surface_add_face (s, gts_face_new (s->face_class, + GTS_OBJECT (t->e1)->reserved, + GTS_OBJECT (t->e2)->reserved, + GTS_OBJECT (t->e3)->reserved)); +} + +/** + * gts_surface_copy: + * @s1: a #GtsSurface. + * @s2: a #GtsSurface. + * + * Add a copy of all the faces, edges and vertices of @s2 to @s1. + * + * Returns: @s1. + */ +GtsSurface * gts_surface_copy (GtsSurface * s1, GtsSurface * s2) +{ + g_return_val_if_fail (s1 != NULL, NULL); + g_return_val_if_fail (s2 != NULL, NULL); + + gts_surface_foreach_vertex (s2, (GtsFunc) foreach_vertex_copy, + s1->vertex_class); + gts_surface_foreach_edge (s2, (GtsFunc) foreach_edge_copy, s1->edge_class); + gts_surface_foreach_face (s2, (GtsFunc) foreach_face_copy, s1); + + gts_surface_foreach_vertex (s2, (GtsFunc) gts_object_reset_reserved, NULL); + gts_surface_foreach_edge (s2, (GtsFunc) gts_object_reset_reserved, NULL); + + return s1; +} + +static void merge_foreach_face (GtsFace * f, + GtsSurface * s) +{ + gts_surface_add_face (s, f); +} + +/** + * gts_surface_merge: + * @s: a #GtsSurface. + * @with: another #GtsSurface. + * + * Adds all the faces of @with which do not already belong to @s + * to @s. + */ +void gts_surface_merge (GtsSurface * s, GtsSurface * with) +{ + g_return_if_fail (s != NULL); + g_return_if_fail (with != NULL); + + gts_surface_foreach_face (with, (GtsFunc) merge_foreach_face, s); +} + +static void manifold_foreach_edge (GtsEdge * e, gpointer * data) +{ + gboolean * is_manifold = data[0]; + + if (*is_manifold) { + if (gts_edge_face_number (e, data[1]) > 2) + *is_manifold = FALSE; + } +} + +/** + * gts_surface_is_manifold: + * @s: a #GtsSurface. + * + * Returns: %TRUE if the surface is a manifold, %FALSE otherwise. + */ +gboolean gts_surface_is_manifold (GtsSurface * s) +{ + gboolean is_manifold = TRUE; + gpointer data[2]; + + g_return_val_if_fail (s != NULL, FALSE); + + data[0] = &is_manifold; + data[1] = s; + gts_surface_foreach_edge (s, (GtsFunc) manifold_foreach_edge, data); + return is_manifold; +} + +static void closed_foreach_edge (GtsEdge * e, gpointer * data) +{ + gboolean * is_closed = data[0]; + + if (*is_closed) { + if (gts_edge_face_number (e, data[1]) != 2) + *is_closed = FALSE; + } +} + +/** + * gts_surface_is_closed: + * @s: a #GtsSurface. + * + * Returns: %TRUE if @s is a closed surface, %FALSE otherwise. Note that a + * closed surface is also a manifold. + */ +gboolean gts_surface_is_closed (GtsSurface * s) +{ + gboolean is_closed = TRUE; + gpointer data[2]; + + g_return_val_if_fail (s != NULL, FALSE); + + data[0] = &is_closed; + data[1] = s; + gts_surface_foreach_edge (s, (GtsFunc) closed_foreach_edge, data); + return is_closed; +} + +static void orientable_foreach_edge (GtsEdge * e, gpointer * data) +{ + gboolean * is_orientable = data[0]; + + if (*is_orientable) { + GtsSurface * surface = data[1]; + GtsFace * f1 = NULL, * f2 = NULL; + GSList * i = e->triangles; + while (i && *is_orientable) { + GtsFace * f = i->data; + if (GTS_IS_FACE (f) && gts_face_has_parent_surface (f, surface)) { + if (!f1) f1 = f; + else if (!f2) f2 = f; + else *is_orientable = FALSE; + } + i = i->next; + } + if (f1 && f2 && !gts_triangles_are_compatible (GTS_TRIANGLE (f1), + GTS_TRIANGLE (f2), e)) + *is_orientable = FALSE; + } +} + +/** + * gts_surface_is_orientable: + * @s: a #GtsSurface. + * + * Returns: %TRUE if all the faces of @s have compatible orientation + * as checked by gts_faces_are_compatible(), %FALSE otherwise. Note that + * an orientable surface is also a manifold. + */ +gboolean gts_surface_is_orientable (GtsSurface * s) +{ + gboolean is_orientable = TRUE; + gpointer data[2]; + + g_return_val_if_fail (s != NULL, FALSE); + + data[0] = &is_orientable; + data[1] = s; + gts_surface_foreach_edge (s, (GtsFunc) orientable_foreach_edge, data); + return is_orientable; +} + +static void volume_foreach_face (GtsTriangle * t, + gdouble * volume) +{ + GtsVertex * va, * vb, * vc; + GtsPoint * pa, * pb, * pc; + + gts_triangle_vertices (t, &va, &vb, &vc); + pa = GTS_POINT (va); + pb = GTS_POINT (vb); + pc = GTS_POINT (vc); + + *volume += (pa->x * (pb->y * pc->z - pb->z * pc->y) + + pb->x * (pc->y * pa->z - pc->z * pa->y) + + pc->x * (pa->y * pb->z - pa->z * pb->y)); +} + +/** + * gts_surface_volume: + * @s: a #GtsSurface. + * + * Returns: the signed volume of the domain bounded by the surface @s. It + * makes sense only if @s is a closed and orientable manifold. + */ +gdouble gts_surface_volume (GtsSurface * s) +{ + gdouble volume = 0.0; + + g_return_val_if_fail (s != NULL, 0.0); + + gts_surface_foreach_face (s, (GtsFunc) volume_foreach_face, &volume); + + return volume/6.; +} + +static void center_of_mass_foreach_face (GtsTriangle * t, + gpointer * data) +{ + GtsVertex * v1, * v2, * v3; + GtsPoint * p1, * p2, * p3; + gdouble x1, y1, z1, x2, y2, z2, nx, ny, nz; + gdouble * volume = data[0]; + gdouble * cm = data[1]; + + gts_triangle_vertices (t, &v1, &v2, &v3); + p1 = GTS_POINT (v1); + p2 = GTS_POINT (v2); + p3 = GTS_POINT (v3); + + x1 = p2->x - p1->x; + y1 = p2->y - p1->y; + z1 = p2->z - p1->z; + + x2 = p3->x - p1->x; + y2 = p3->y - p1->y; + z2 = p3->z - p1->z; + + nx = y1*z2 - z1*y2; + ny = z1*x2 - x1*z2; + nz = x1*y2 - y1*x2; + + cm[0] += nx*(p1->x*p1->x + p2->x*p2->x + p3->x*p3->x + + p1->x*p2->x + p1->x*p3->x + p2->x*p3->x); + cm[1] += ny*(p1->y*p1->y + p2->y*p2->y + p3->y*p3->y + + p1->y*p2->y + p1->y*p3->y + p2->y*p3->y); + cm[2] += nz*(p1->z*p1->z + p2->z*p2->z + p3->z*p3->z + + p1->z*p2->z + p1->z*p3->z + p2->z*p3->z); + + *volume += nx*(p1->x + p2->x + p3->x); +} + + +/** + * gts_surface_center_of_mass: + * @s: a #GtsSurface. + * @cm: a #GtsVector. + * + * Fills @cm with the coordinates of the center of mass of @s. + * + * Returns: the signed volume of the domain bounded by the surface @s. + */ +gdouble gts_surface_center_of_mass (GtsSurface * s, + GtsVector cm) +{ + gdouble volume = 0.; + gpointer data[2]; + + g_return_val_if_fail (s != NULL, 0.0); + + data[0] = &volume; + data[1] = &(cm[0]); + cm[0] = cm[1] = cm[2] = 0.; + gts_surface_foreach_face (s, (GtsFunc) center_of_mass_foreach_face, data); + + if (volume != 0.) { + cm[0] /= 4.*volume; + cm[1] /= 4.*volume; + cm[2] /= 4.*volume; + } + + return volume/6.; +} + +static void center_of_area_foreach_face (GtsTriangle * t, + gpointer * data) +{ + GtsVertex * v1, * v2, * v3; + GtsPoint * p1, * p2, * p3; + gdouble a; + gdouble * area = data[0]; + gdouble * cm = data[1]; + + gts_triangle_vertices (t, &v1, &v2, &v3); + p1 = GTS_POINT (v1); + p2 = GTS_POINT (v2); + p3 = GTS_POINT (v3); + + a = gts_triangle_area (t); + cm[0] += a*(p1->x + p2->x + p3->x); + cm[1] += a*(p1->y + p2->y + p3->y); + cm[2] += a*(p1->z + p2->z + p3->z); + *area += a; +} + + +/** + * gts_surface_center_of_area: + * @s: a #GtsSurface. + * @cm: a #GtsVector. + * + * Fills @cm with the coordinates of the center of area of @s. + * + * Returns: the area of surface @s. + */ +gdouble gts_surface_center_of_area (GtsSurface * s, + GtsVector cm) +{ + gdouble area = 0.; + gpointer data[2]; + + g_return_val_if_fail (s != NULL, 0.0); + + data[0] = &area; + data[1] = &(cm[0]); + cm[0] = cm[1] = cm[2] = 0.; + gts_surface_foreach_face (s, (GtsFunc) center_of_area_foreach_face, data); + + if (area != 0.) { + cm[0] /= 3.*area; + cm[1] /= 3.*area; + cm[2] /= 3.*area; + } + + return area; +} + +static void number_foreach (gpointer data, guint * n) +{ + (*n)++; +} + +/** + * gts_surface_vertex_number: + * @s: a #GtsSurface. + * + * Returns: the number of vertices of @s. + */ +guint gts_surface_vertex_number (GtsSurface * s) +{ + guint n = 0; + + g_return_val_if_fail (s != NULL, 0); + + gts_surface_foreach_vertex (s, (GtsFunc) number_foreach, &n); + + return n; +} + +/** + * gts_surface_edge_number: + * @s: a #GtsSurface. + * + * Returns: the number of edges of @s. + */ +guint gts_surface_edge_number (GtsSurface * s) +{ + guint n = 0; + + g_return_val_if_fail (s != NULL, 0); + + gts_surface_foreach_edge (s, (GtsFunc) number_foreach, &n); + + return n; +} + +/** + * gts_surface_face_number: + * @s: a #GtsSurface. + * + * Returns: the number of faces of @s + */ +guint gts_surface_face_number (GtsSurface * s) +{ + g_return_val_if_fail (s != NULL, 0); + +#ifdef USE_SURFACE_BTREE + return g_tree_nnodes (s->faces); +#else /* not USE_SURFACE_BTREE */ + return g_hash_table_size (s->faces); +#endif /* not USE_SURFACE_BTREE */ +} + +static void build_list_face (GtsTriangle * t, GSList ** list) +{ + *list = g_slist_prepend (*list, gts_bbox_triangle (gts_bbox_class (), t)); +} + +static void build_list_boundary (GtsEdge * e, GSList ** list) +{ + if (gts_edge_is_boundary (e, NULL)) + *list = g_slist_prepend (*list, gts_bbox_segment (gts_bbox_class (), + GTS_SEGMENT (e))); +} + +/** + * gts_surface_distance: + * @s1: a #GtsSurface. + * @s2: a #GtsSurface. + * @delta: a spatial increment defined as the percentage of the diagonal + * of the bounding box of @s2. + * @face_range: a #GtsRange. + * @boundary_range: a #GtsRange. + * + * Using the gts_bb_tree_surface_distance() and + * gts_bb_tree_surface_boundary_distance() functions fills @face_range + * and @boundary_range with the min, max and average Euclidean + * (minimum) distances between the faces of @s1 and the faces of @s2 + * and between the boundary edges of @s1 and @s2. + */ +void gts_surface_distance (GtsSurface * s1, GtsSurface * s2, gdouble delta, + GtsRange * face_range, GtsRange * boundary_range) +{ + GNode * face_tree, * boundary_tree; + GSList * bboxes; + + g_return_if_fail (s1 != NULL); + g_return_if_fail (s2 != NULL); + g_return_if_fail (delta > 0. && delta < 1.); + g_return_if_fail (face_range != NULL); + g_return_if_fail (boundary_range != NULL); + + bboxes = NULL; + gts_surface_foreach_face (s2, (GtsFunc) build_list_face, &bboxes); + if (bboxes != NULL) { + face_tree = gts_bb_tree_new (bboxes); + g_slist_free (bboxes); + + gts_bb_tree_surface_distance (face_tree, s1, + (GtsBBoxDistFunc) gts_point_triangle_distance, + delta, face_range); + gts_bb_tree_destroy (face_tree, TRUE); + + bboxes = NULL; + gts_surface_foreach_edge (s2, (GtsFunc) build_list_boundary, &bboxes); + if (bboxes != NULL) { + boundary_tree = gts_bb_tree_new (bboxes); + g_slist_free (bboxes); + + gts_bb_tree_surface_boundary_distance (boundary_tree, + s1, + (GtsBBoxDistFunc) gts_point_segment_distance, + delta, boundary_range); + gts_bb_tree_destroy (boundary_tree, TRUE); + } + else + gts_range_reset (boundary_range); + } + else { + gts_range_reset (face_range); + gts_range_reset (boundary_range); + } +} + +static void surface_boundary (GtsEdge * e, gpointer * data) +{ + GSList ** list = data[0]; + + if (gts_edge_is_boundary (e, data[1])) + *list = g_slist_prepend (*list, e); +} + +/** + * gts_surface_boundary: + * @surface: a #GtsSurface. + * + * Returns: a list of #GtsEdge boundary of @surface. + */ +GSList * gts_surface_boundary (GtsSurface * surface) +{ + GSList * list = NULL; + gpointer data[2]; + + g_return_val_if_fail (surface != NULL, NULL); + + data[0] = &list; + data[1] = surface; + gts_surface_foreach_edge (surface, (GtsFunc) surface_boundary, data); + + return list; +} + +struct _GtsSurfaceTraverse { + GtsFifo * q; + GtsSurface * s; +}; + +/** + * gts_surface_traverse_new: + * @s: a #GtsSurface. + * @f: a #GtsFace belonging to @s. + * + * Returns: a new #GtsSurfaceTraverse, initialized to start traversing + * from face @f of surface @s. + */ +GtsSurfaceTraverse * gts_surface_traverse_new (GtsSurface * s, + GtsFace * f) +{ + GtsSurfaceTraverse * t; + + g_return_val_if_fail (s != NULL, NULL); + g_return_val_if_fail (f != NULL, NULL); + g_return_val_if_fail (gts_face_has_parent_surface (f, s), NULL); + + t = g_malloc (sizeof (GtsSurfaceTraverse)); + t->q = gts_fifo_new (); + t->s = s; + GTS_OBJECT (f)->reserved = GUINT_TO_POINTER (1); + gts_fifo_push (t->q, f); + return t; +} + +static void push_neighbor (GtsFace * v, gpointer * data) +{ + if (!GTS_OBJECT (v)->reserved) { + GTS_OBJECT (v)->reserved = + GUINT_TO_POINTER (GPOINTER_TO_UINT (GTS_OBJECT (data[1])->reserved) + 1); + gts_fifo_push (data[0], v); + } +} + +/** + * gts_surface_traverse_next: + * @t: a #GtsSurfaceTraverse. + * @level: a pointer to a guint or %NULL. + * + * Returns: the next face of the traversal in breadth-first order or + * %NULL if no faces are left. If @level if not %NULL, it is filled + * with the level of the returned face (0 for the initial face, 1 for + * its neighbors and so on). + */ +GtsFace * gts_surface_traverse_next (GtsSurfaceTraverse * t, + guint * level) +{ + GtsFace * u; + + g_return_val_if_fail (t != NULL, NULL); + + u = gts_fifo_pop (t->q); + if (u) { + gpointer data[2]; + + if (level) + *level = GPOINTER_TO_UINT (GTS_OBJECT (u)->reserved); + data[0] = t->q; + data[1] = u; + gts_face_foreach_neighbor (u, t->s, (GtsFunc) push_neighbor, data); + } + return u; +} + +/** + * gts_surface_traverse_destroy: + * @t: a #GtsSurfaceTraverse. + * + * Frees all the memory allocated for @t. + */ +void gts_surface_traverse_destroy (GtsSurfaceTraverse * t) +{ + g_return_if_fail (t != NULL); + + gts_surface_foreach_face (t->s, (GtsFunc) gts_object_reset_reserved, NULL); + gts_fifo_destroy (t->q); + g_free (t); +} + +static void traverse_manifold (GtsTriangle * t, GtsSurface * s) +{ + if (g_slist_length (GTS_FACE (t)->surfaces) > 1) + return; + + gts_surface_add_face (s, GTS_FACE (t)); + if (g_slist_length (t->e1->triangles) == 2) { + if (t->e1->triangles->data != t) + traverse_manifold (t->e1->triangles->data, s); + else + traverse_manifold (t->e1->triangles->next->data, s); + } + if (g_slist_length (t->e2->triangles) == 2) { + if (t->e2->triangles->data != t) + traverse_manifold (t->e2->triangles->data, s); + else + traverse_manifold (t->e2->triangles->next->data, s); + } + if (g_slist_length (t->e3->triangles) == 2) { + if (t->e3->triangles->data != t) + traverse_manifold (t->e3->triangles->data, s); + else + traverse_manifold (t->e3->triangles->next->data, s); + } +} + +static void non_manifold_edges (GtsEdge * e, gpointer * data) +{ + GtsSurface * s = data[0]; + GSList ** non_manifold = data[1]; + + if (gts_edge_face_number (e, s) > 2) { + GSList * i = e->triangles; + + while (i) { + if (gts_face_has_parent_surface (i->data, s) && + !g_slist_find (*non_manifold, i->data)) + *non_manifold = g_slist_prepend (*non_manifold, i->data); + i = i->next; + } + } +} + +static void traverse_boundary (GtsEdge * e, gpointer * data) +{ + GtsSurface * orig = data[0]; + GSList ** components = data[1]; + GtsFace * f = gts_edge_is_boundary (e, orig); + + if (f != NULL && g_slist_length (f->surfaces) == 1) { + GtsSurface * s = + gts_surface_new (GTS_SURFACE_CLASS (GTS_OBJECT (orig)->klass), + orig->face_class, + orig->edge_class, + orig->vertex_class); + GSList * non_manifold = NULL, * i; + gpointer data[2]; + + *components = g_slist_prepend (*components, s); + data[0] = s; + data[1] = &non_manifold; + traverse_manifold (GTS_TRIANGLE (f), s); + + gts_surface_foreach_edge (s, (GtsFunc) non_manifold_edges, data); + i = non_manifold; + while (i) { + gts_surface_remove_face (s, i->data); + i = i->next; + } + g_slist_free (non_manifold); + } +} + +static void traverse_remaining (GtsFace * f, gpointer * data) +{ + GtsSurface * orig = data[0]; + GSList ** components = data[1]; + + if (g_slist_length (f->surfaces) == 1) { + GtsSurface * s = + gts_surface_new (GTS_SURFACE_CLASS (GTS_OBJECT (orig)->klass), + orig->face_class, + orig->edge_class, + orig->vertex_class); + GSList * non_manifold = NULL, * i; + gpointer data[2]; + + *components = g_slist_prepend (*components, s); + data[0] = s; + data[1] = &non_manifold; + traverse_manifold (GTS_TRIANGLE (f), s); + + gts_surface_foreach_edge (s, (GtsFunc) non_manifold_edges, data); + i = non_manifold; + while (i) { + gts_surface_remove_face (s, i->data); + i = i->next; + } + g_slist_free (non_manifold); + } +} + +/** + * gts_surface_split: + * @s: a #GtsSurface. + * + * Splits a surface into connected and manifold components. + * + * Returns: a list of new #GtsSurface. + */ +GSList * gts_surface_split (GtsSurface * s) +{ + gpointer data[2]; + GSList * components = NULL; + + g_return_val_if_fail (s != NULL, NULL); + + data[0] = s; + data[1] = &components; + + /* boundary components */ + gts_surface_foreach_edge (s, (GtsFunc) traverse_boundary, data); + + /* remaining components */ + gts_surface_foreach_face (s, (GtsFunc) traverse_remaining, data); + + return components; +} diff --git a/gts/triangle.c b/gts/triangle.c new file mode 100644 index 0000000000..5213a51265 --- /dev/null +++ b/gts/triangle.c @@ -0,0 +1,1094 @@ +/* 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 "gts.h" + +static void triangle_destroy (GtsObject * object) +{ + GtsTriangle * triangle = GTS_TRIANGLE (object); + GtsEdge * e1 = triangle->e1; + GtsEdge * e2 = triangle->e2; + GtsEdge * e3 = triangle->e3; + + e1->triangles = g_slist_remove (e1->triangles, triangle); + if (!GTS_OBJECT_DESTROYED (e1) && + !gts_allow_floating_edges && e1->triangles == NULL) + gts_object_destroy (GTS_OBJECT (e1)); + + e2->triangles = g_slist_remove (e2->triangles, triangle); + if (!GTS_OBJECT_DESTROYED (e2) && + !gts_allow_floating_edges && e2->triangles == NULL) + gts_object_destroy (GTS_OBJECT (e2)); + + e3->triangles = g_slist_remove (e3->triangles, triangle); + if (!GTS_OBJECT_DESTROYED (e3) && + !gts_allow_floating_edges && e3->triangles == NULL) + gts_object_destroy (GTS_OBJECT (e3)); + + (* GTS_OBJECT_CLASS (gts_triangle_class ())->parent_class->destroy) (object); +} + +static void triangle_class_init (GtsObjectClass * klass) +{ + klass->destroy = triangle_destroy; +} + +static void triangle_init (GtsTriangle * triangle) +{ + triangle->e1 = triangle->e2 = triangle->e3 = NULL; +} + +/** + * gts_triangle_class: + * + * Returns: the #GtsTriangleClass. + */ +GtsTriangleClass * gts_triangle_class (void) +{ + static GtsTriangleClass * klass = NULL; + + if (klass == NULL) { + GtsObjectClassInfo triangle_info = { + "GtsTriangle", + sizeof (GtsTriangle), + sizeof (GtsTriangleClass), + (GtsObjectClassInitFunc) triangle_class_init, + (GtsObjectInitFunc) triangle_init, + (GtsArgSetFunc) NULL, + (GtsArgGetFunc) NULL + }; + klass = gts_object_class_new (gts_object_class (), + &triangle_info); + } + + return klass; +} + +/** + * gts_triangle_set: + * @triangle: a #GtsTriangle. + * @e1: a #GtsEdge. + * @e2: another #GtsEdge touching @e1. + * @e3: another #GtsEdge touching both @e1 and @e2. + * + * Sets the edge of @triangle to @e1, @e2 and @e3 while checking that they + * define a valid triangle. + */ +void gts_triangle_set (GtsTriangle * triangle, + GtsEdge * e1, + GtsEdge * e2, + GtsEdge * e3) +{ + g_return_if_fail (e1 != NULL); + g_return_if_fail (e2 != NULL); + g_return_if_fail (e3 != NULL); + g_return_if_fail (e1 != e2 && e1 != e3 && e2 != e3); + + triangle->e1 = e1; + triangle->e2 = e2; + triangle->e3 = e3; + + if (GTS_SEGMENT (e1)->v1 == GTS_SEGMENT (e2)->v1) + g_return_if_fail (gts_segment_connect (GTS_SEGMENT (e3), + GTS_SEGMENT (e1)->v2, + GTS_SEGMENT (e2)->v2)); + else if (GTS_SEGMENT (e1)->v2 == GTS_SEGMENT (e2)->v1) + g_return_if_fail (gts_segment_connect (GTS_SEGMENT (e3), + GTS_SEGMENT (e1)->v1, + GTS_SEGMENT (e2)->v2)); + else if (GTS_SEGMENT (e1)->v2 == GTS_SEGMENT (e2)->v2) + g_return_if_fail (gts_segment_connect (GTS_SEGMENT (e3), + GTS_SEGMENT (e1)->v1, + GTS_SEGMENT (e2)->v1)); + else if (GTS_SEGMENT (e1)->v1 == GTS_SEGMENT (e2)->v2) + g_return_if_fail (gts_segment_connect (GTS_SEGMENT (e3), + GTS_SEGMENT (e1)->v2, + GTS_SEGMENT (e2)->v1)); + else + g_assert_not_reached (); + + e1->triangles = g_slist_prepend (e1->triangles, triangle); + e2->triangles = g_slist_prepend (e2->triangles, triangle); + e3->triangles = g_slist_prepend (e3->triangles, triangle); +} + +/** + * gts_triangle_new: + * @klass: a #GtsTriangleClass. + * @e1: a #GtsEdge. + * @e2: another #GtsEdge touching @e1. + * @e3: another #GtsEdge touching both @e1 and @e2. + * + * Returns: a new #GtsTriangle having @e1, @e2 and @e3 as edges. + */ +GtsTriangle * gts_triangle_new (GtsTriangleClass * klass, + GtsEdge * e1, + GtsEdge * e2, + GtsEdge * e3) +{ + GtsTriangle * t; + + t = GTS_TRIANGLE (gts_object_new (GTS_OBJECT_CLASS (klass))); + gts_triangle_set (t, e1, e2, e3); + + return t; +} + +/** + * gts_triangle_vertex_opposite: + * @t: a #GtsTriangle. + * @e: a #GtsEdge used by @t. + * + * This function fails if @e is not an edge of @t. + * + * Returns: a #GtsVertex, vertex of @t which does not belong to @e. + */ +GtsVertex * gts_triangle_vertex_opposite (GtsTriangle * t, GtsEdge * e) +{ + g_return_val_if_fail (t != NULL, NULL); + g_return_val_if_fail (e != NULL, NULL); + + if (t->e1 == e) { + GtsVertex * v = GTS_SEGMENT (t->e2)->v1; + if (v != GTS_SEGMENT (e)->v1 && v != GTS_SEGMENT (e)->v2) + return v; + return GTS_SEGMENT (t->e2)->v2; + } + if (t->e2 == e) { + GtsVertex * v = GTS_SEGMENT (t->e1)->v1; + if (v != GTS_SEGMENT (e)->v1 && v != GTS_SEGMENT (e)->v2) + return v; + return GTS_SEGMENT (t->e1)->v2; + } + if (t->e3 == e) { + GtsVertex * v = GTS_SEGMENT (t->e2)->v1; + if (v != GTS_SEGMENT (e)->v1 && v != GTS_SEGMENT (e)->v2) + return v; + return GTS_SEGMENT (t->e2)->v2; + } + g_assert_not_reached (); + return NULL; +} + +/** + * gts_triangle_edge_opposite: + * @t: a #GtsTriangle. + * @v: a #GtsVertex of @t. + * + * Returns: the edge of @t opposite @v or %NULL if @v is not a vertice of @t. + */ +GtsEdge * gts_triangle_edge_opposite (GtsTriangle * t, GtsVertex * v) +{ + GtsSegment * s1, * s2, * s3; + + g_return_val_if_fail (t != NULL, NULL); + g_return_val_if_fail (v != NULL, NULL); + + s1 = GTS_SEGMENT (t->e1); + s2 = GTS_SEGMENT (t->e2); + + if (s1->v1 != v && s1->v2 != v) { + if (s2->v1 != v && s2->v2 != v) + return NULL; + return t->e1; + } + if (s2->v1 != v && s2->v2 != v) + return t->e2; + s3 = GTS_SEGMENT (t->e3); + g_assert (s3->v1 != v && s3->v2 != v); + return t->e3; +} + +/** + * gts_triangles_angle: + * @t1: a #GtsTriangle. + * @t2: a #GtsTriangle. + * + * Returns: the value (in radians) of the angle between @t1 and @t2. + */ +gdouble gts_triangles_angle (GtsTriangle * t1, + GtsTriangle * t2) +{ + gdouble nx1, ny1, nz1, nx2, ny2, nz2; + gdouble pvx, pvy, pvz; + gdouble theta; + + g_return_val_if_fail (t1 != NULL && t2 != NULL, 0.0); + + gts_triangle_normal (t1, &nx1, &ny1, &nz1); + gts_triangle_normal (t2, &nx2, &ny2, &nz2); + + pvx = ny1*nz2 - nz1*ny2; + pvy = nz1*nx2 - nx1*nz2; + pvz = nx1*ny2 - ny1*nx2; + + theta = atan2 (sqrt (pvx*pvx + pvy*pvy + pvz*pvz), + nx1*nx2 + ny1*ny2 + nz1*nz2) - M_PI; + return theta < - M_PI ? theta + 2.*M_PI : theta; +} + +/** + * gts_triangles_are_compatible: + * @t1: a #GtsTriangle. + * @t2: a #GtsTriangle. + * @e: a #GtsEdge used by both @t1 and @t2. + * + * Checks if @t1 and @t2 have compatible orientations i.e. if @t1 and + * @t2 can be part of the same surface without conflict in the surface + * normal orientation. + * + * Returns: %TRUE if @t1 and @t2 are compatible, %FALSE otherwise. + */ +gboolean gts_triangles_are_compatible (GtsTriangle * t1, + GtsTriangle * t2, + GtsEdge * e) +{ + GtsEdge * e1 = NULL, * e2 = NULL; + + g_return_val_if_fail (t1 != NULL, FALSE); + g_return_val_if_fail (t2 != NULL, FALSE); + g_return_val_if_fail (e != NULL, FALSE); + + if (t1->e1 == e) e1 = t1->e2; + else if (t1->e2 == e) e1 = t1->e3; + else if (t1->e3 == e) e1 = t1->e1; + else + g_assert_not_reached (); + if (t2->e1 == e) e2 = t2->e2; + else if (t2->e2 == e) e2 = t2->e3; + else if (t2->e3 == e) e2 = t2->e1; + else + g_assert_not_reached (); + if (GTS_SEGMENT (e1)->v1 == GTS_SEGMENT (e2)->v1 || + GTS_SEGMENT (e1)->v1 == GTS_SEGMENT (e2)->v2 || + GTS_SEGMENT (e1)->v2 == GTS_SEGMENT (e2)->v1 || + GTS_SEGMENT (e1)->v2 == GTS_SEGMENT (e2)->v2) + return FALSE; + return TRUE; +} + +/** + * gts_triangle_area: + * @t: a #GtsTriangle. + * + * Returns: the area of the triangle @t. + */ +gdouble gts_triangle_area (GtsTriangle * t) +{ + gdouble x, y, z; + + g_return_val_if_fail (t != NULL, 0.0); + + gts_triangle_normal (t, &x, &y, &z); + + return sqrt (x*x + y*y + z*z)/2.; +} + +/** + * gts_triangle_perimeter: + * @t: a #GtsTriangle. + * + * Returns: the perimeter of the triangle @t. + */ +gdouble gts_triangle_perimeter (GtsTriangle * t) +{ + GtsVertex * v; + + g_return_val_if_fail (t != NULL, 0.0); + + v = gts_triangle_vertex (t); + return + gts_point_distance (GTS_POINT (GTS_SEGMENT (t->e1)->v1), + GTS_POINT (GTS_SEGMENT (t->e1)->v2)) + + gts_point_distance (GTS_POINT (GTS_SEGMENT (t->e1)->v1), + GTS_POINT (v)) + + gts_point_distance (GTS_POINT (GTS_SEGMENT (t->e1)->v2), + GTS_POINT (v)); +} + +/* perimeter of the equilateral triangle of area unity */ +#define GOLDEN_PERIMETER 4.5590141139 + +/** + * gts_triangle_quality: + * @t: a #GtsTriangle. + * + * The quality of a triangle is defined as the ratio of the square + * root of its surface area to its perimeter relative to this same + * ratio for an equilateral triangle with the same area. The quality + * is then one for an equilateral triangle and tends to zero for a + * very stretched triangle. + * + * Returns: the quality of the triangle @t. + */ +gdouble gts_triangle_quality (GtsTriangle * t) +{ + gdouble perimeter; + + g_return_val_if_fail (t != NULL, 0.0); + + perimeter = gts_triangle_perimeter (t); + return perimeter > 0.0 ? + GOLDEN_PERIMETER*sqrt (gts_triangle_area (t))/perimeter : + 0.0; +} + +/** + * gts_triangle_normal: + * @t: a #GtsTriangle. + * @x: the x coordinate of the normal. + * @y: the y coordinate of the normal. + * @z: the z coordinate of the normal. + * + * Computes the coordinates of the oriented normal of @t as the + * cross-product of two edges, using the left-hand rule. The normal is + * not normalized. If this triangle is part of a closed and oriented + * surface, the normal points to the outside of the surface. + */ +void gts_triangle_normal (GtsTriangle * t, + gdouble * x, + gdouble * y, + gdouble * z) +{ + GtsVertex * v1, * v2 = NULL, * v3 = NULL; + GtsPoint * p1, * p2, * p3; + gdouble x1, y1, z1, x2, y2, z2; + + g_return_if_fail (t != NULL); + + v1 = GTS_SEGMENT (t->e1)->v1; + if (GTS_SEGMENT (t->e1)->v1 == GTS_SEGMENT (t->e2)->v1) { + v2 = GTS_SEGMENT (t->e2)->v2; + v3 = GTS_SEGMENT (t->e1)->v2; + } + else if (GTS_SEGMENT (t->e1)->v2 == GTS_SEGMENT (t->e2)->v2) { + v2 = GTS_SEGMENT (t->e1)->v2; + v3 = GTS_SEGMENT (t->e2)->v1; + } + else if (GTS_SEGMENT (t->e1)->v1 == GTS_SEGMENT (t->e2)->v2) { + v2 = GTS_SEGMENT (t->e2)->v1; + v3 = GTS_SEGMENT (t->e1)->v2; + } + else if (GTS_SEGMENT (t->e1)->v2 == GTS_SEGMENT (t->e2)->v1) { + v2 = GTS_SEGMENT (t->e1)->v2; + v3 = GTS_SEGMENT (t->e2)->v2; + } + else { + fprintf (stderr, "t: %p t->e1: %p t->e2: %p t->e3: %p t->e1->v1: %p t->e1->v2: %p t->e2->v1: %p t->e2->v2: %p t->e3->v1: %p t->e3->v2: %p\n", + t, t->e1, t->e2, + t->e3, GTS_SEGMENT (t->e1)->v1, GTS_SEGMENT (t->e1)->v2, + GTS_SEGMENT (t->e2)->v1, GTS_SEGMENT (t->e2)->v2, + GTS_SEGMENT (t->e3)->v1, GTS_SEGMENT (t->e3)->v2); + g_assert_not_reached (); + } + + p1 = GTS_POINT (v1); + p2 = GTS_POINT (v2); + p3 = GTS_POINT (v3); + + x1 = p2->x - p1->x; + y1 = p2->y - p1->y; + z1 = p2->z - p1->z; + + x2 = p3->x - p1->x; + y2 = p3->y - p1->y; + z2 = p3->z - p1->z; + + *x = y1*z2 - z1*y2; + *y = z1*x2 - x1*z2; + *z = x1*y2 - y1*x2; +} + +/** + * gts_triangle_orientation: + * @t: a #GtsTriangle. + * + * Checks for the orientation of the plane (x,y) projection of a + * triangle. See gts_point_orientation() for details. This function + * is geometrically robust. + * + * Returns: a number depending on the orientation of the vertices of @t. + */ +gdouble gts_triangle_orientation (GtsTriangle * t) +{ + GtsVertex * v1, * v2 = NULL, * v3 = NULL; + + g_return_val_if_fail (t != NULL, 0.0); + + v1 = GTS_SEGMENT (t->e1)->v1; + if (GTS_SEGMENT (t->e1)->v1 == GTS_SEGMENT (t->e2)->v1) { + v2 = GTS_SEGMENT (t->e2)->v2; + v3 = GTS_SEGMENT (t->e1)->v2; + } + else if (GTS_SEGMENT (t->e1)->v2 == GTS_SEGMENT (t->e2)->v2) { + v2 = GTS_SEGMENT (t->e1)->v2; + v3 = GTS_SEGMENT (t->e2)->v1; + } + else if (GTS_SEGMENT (t->e1)->v1 == GTS_SEGMENT (t->e2)->v2) { + v2 = GTS_SEGMENT (t->e2)->v1; + v3 = GTS_SEGMENT (t->e1)->v2; + } + else if (GTS_SEGMENT (t->e1)->v2 == GTS_SEGMENT (t->e2)->v1) { + v2 = GTS_SEGMENT (t->e1)->v2; + v3 = GTS_SEGMENT (t->e2)->v2; + } + else + g_assert_not_reached (); + return gts_point_orientation (GTS_POINT (v1), + GTS_POINT (v2), + GTS_POINT (v3)); +} + +/** + * gts_triangle_revert: + * @t: a #GtsTriangle. + * + * Changes the orientation of triangle @t, turning it inside out. + */ +void gts_triangle_revert (GtsTriangle * t) +{ + GtsEdge * e; + + g_return_if_fail (t != NULL); + + e = t->e1; + t->e1 = t->e2; + t->e2 = e; +} + +/** + * gts_triangles_from_edges: + * @edges: a list of #GtsEdge. + * + * Builds a list of unique triangles which have one of their edges in @edges. + * + * Returns: the list of triangles. + */ +GSList * gts_triangles_from_edges (GSList * edges) +{ + GHashTable * hash; + GSList * triangles = NULL, * i; + + hash = g_hash_table_new (NULL, NULL); + i = edges; + while (i) { + GSList * j = GTS_EDGE (i->data)->triangles; + while (j) { + GtsTriangle * t = j->data; + if (g_hash_table_lookup (hash, t) == NULL) { + triangles = g_slist_prepend (triangles, t); + g_hash_table_insert (hash, t, i); + } + j = j->next; + } + i = i->next; + } + g_hash_table_destroy (hash); + + return triangles; +} + +/** + * gts_triangle_vertices_edges: + * @t: a #GtsTriangle. + * @e: a #GtsEdge belonging to the edges of @t or %NULL. + * @v1: a #GtsVertex used by @t. + * @v2: a #GtsVertex used by @t. + * @v3: a #GtsVertex used by @t. + * @e1: a #GtsEdge used by @t. + * @e2: a #GtsEdge used by @t. + * @e3: a #GtsEdge used by @t. + * + * Given @t and @e, returns @v1, @v2, @v3, @e1, @e2 and @e3. @e1 + * has @v1 and @v2 as vertices, @e2 has @v2 and @v3 as vertices + * and @e3 has @v3 and @v1 as vertices. @v1, @v2 and @v3 respects + * the orientation of @t. If @e is not NULL, @e1 and @e are + * identical. + */ +void gts_triangle_vertices_edges (GtsTriangle * t, + GtsEdge * e, + GtsVertex ** v1, + GtsVertex ** v2, + GtsVertex ** v3, + GtsEdge ** e1, + GtsEdge ** e2, + GtsEdge ** e3) +{ + GtsEdge * ee1, * ee2; + + g_return_if_fail (t != NULL); + + if (e == t->e1 || e == NULL) { + *e1 = ee1 = t->e1; *e2 = ee2 = t->e2; *e3 = t->e3; + } + else if (e == t->e2) { + *e1 = ee1 = e; *e2 = ee2 = t->e3; *e3 = t->e1; + } + else if (e == t->e3) { + *e1 = ee1 = e; *e2 = ee2 = t->e1; *e3 = t->e2; + } + else { + g_assert_not_reached (); + ee1 = ee2 = NULL; /* to avoid complaints from the compiler */ + } + if (GTS_SEGMENT (ee1)->v2 == GTS_SEGMENT (ee2)->v1) { + *v1 = GTS_SEGMENT (ee1)->v1; + *v2 = GTS_SEGMENT (ee1)->v2; + *v3 = GTS_SEGMENT (ee2)->v2; + } + else if (GTS_SEGMENT (ee1)->v2 == GTS_SEGMENT (ee2)->v2) { + *v1 = GTS_SEGMENT (ee1)->v1; + *v2 = GTS_SEGMENT (ee1)->v2; + *v3 = GTS_SEGMENT (ee2)->v1; + } + else if (GTS_SEGMENT (ee1)->v1 == GTS_SEGMENT (ee2)->v1) { + *v1 = GTS_SEGMENT (ee1)->v2; + *v2 = GTS_SEGMENT (ee1)->v1; + *v3 = GTS_SEGMENT (ee2)->v2; + } + else if (GTS_SEGMENT (ee1)->v1 == GTS_SEGMENT (ee2)->v2) { + *v1 = GTS_SEGMENT (ee1)->v2; + *v2 = GTS_SEGMENT (ee1)->v1; + *v3 = GTS_SEGMENT (ee2)->v1; + } + else + g_assert_not_reached (); +} + +/* sqrt(3) */ +#define SQRT3 1.73205080757 + +/** + * gts_triangle_enclosing: + * @klass: the class of the new triangle. + * @points: a list of #GtsPoint. + * @scale: a scaling factor (must be larger than one). + * + * Builds a new triangle (including new vertices and edges) enclosing + * the plane projection of all the points in @points. This triangle is + * equilateral and encloses a rectangle defined by the maximum and + * minimum x and y coordinates of the points. @scale is an homothetic + * scaling factor. If equal to one, the triangle encloses exactly the + * enclosing rectangle. + * + * Returns: a new #GtsTriangle. + */ +GtsTriangle * gts_triangle_enclosing (GtsTriangleClass * klass, + GSList * points, gdouble scale) +{ + gdouble xmax, xmin, ymax, ymin; + gdouble xo, yo, r; + GtsVertex * v1, * v2, * v3; + GtsEdge * e1, * e2, * e3; + + if (points == NULL) + return NULL; + + xmax = xmin = GTS_POINT (points->data)->x; + ymax = ymin = GTS_POINT (points->data)->y; + points = points->next; + while (points) { + GtsPoint * p = points->data; + if (p->x > xmax) xmax = p->x; + else if (p->x < xmin) xmin = p->x; + if (p->y > ymax) ymax = p->y; + else if (p->y < ymin) ymin = p->y; + points = points->next; + } + xo = (xmax + xmin)/2.; + yo = (ymax + ymin)/2.; + r = scale*sqrt((xmax - xo)*(xmax - xo) + (ymax - yo)*(ymax - yo)); + if (r == 0.0) r = scale; + v1 = gts_vertex_new (gts_vertex_class (), + xo + r*SQRT3, yo - r, 0.0); + v2 = gts_vertex_new (gts_vertex_class (), + xo, yo + 2.*r, 0.0); + v3 = gts_vertex_new (gts_vertex_class (), + xo - r*SQRT3, yo - r, 0.0); + e1 = gts_edge_new (gts_edge_class (), v1, v2); + e2 = gts_edge_new (gts_edge_class (), v2, v3); + e3 = gts_edge_new (gts_edge_class (), v3, v1); + return gts_triangle_new (gts_triangle_class (), e1, e2, e3); +} + +/** + * gts_triangle_neighbor_number: + * @t: a #GtsTriangle. + * + * Returns: the number of triangles neighbors of @t. + */ +guint gts_triangle_neighbor_number (GtsTriangle * t) +{ + GSList * i; + guint nn = 0; + GtsEdge * ee[4], ** e = ee; + + g_return_val_if_fail (t != NULL, 0); + + ee[0] = t->e1; ee[1] = t->e2; ee[2] = t->e3; ee[3] = NULL; + while (*e) { + i = (*e++)->triangles; + while (i) { + GtsTriangle * t1 = i->data; + if (t1 != t) + nn++; + i = i->next; + } + } + return nn; +} + +/** + * gts_triangle_neighbors: + * @t: a #GtsTriangle. + * + * Returns: a list of #GtsTriangle neighbors of @t. + */ +GSList * gts_triangle_neighbors (GtsTriangle * t) +{ + GSList * i, * list = NULL; + GtsEdge * ee[4], ** e = ee; + + g_return_val_if_fail (t != NULL, NULL); + + ee[0] = t->e1; ee[1] = t->e2; ee[2] = t->e3; ee[3] = NULL; + while (*e) { + i = (*e++)->triangles; + while (i) { + GtsTriangle * t1 = i->data; + if (t1 != t) + list = g_slist_prepend (list, t1); + i = i->next; + } + } + return list; +} + +/** + * gts_triangles_common_edge: + * @t1: a #GtsTriangle. + * @t2: a #GtsTriangle. + * + * Returns: a #GtsEdge common to both @t1 and @t2 or %NULL if @t1 and @t2 + * do not share any edge. + */ +GtsEdge * gts_triangles_common_edge (GtsTriangle * t1, + GtsTriangle * t2) +{ + g_return_val_if_fail (t1 != NULL, NULL); + g_return_val_if_fail (t2 != NULL, NULL); + + if (t1->e1 == t2->e1 || t1->e1 == t2->e2 || t1->e1 == t2->e3) + return t1->e1; + if (t1->e2 == t2->e1 || t1->e2 == t2->e2 || t1->e2 == t2->e3) + return t1->e2; + if (t1->e3 == t2->e1 || t1->e3 == t2->e2 || t1->e3 == t2->e3) + return t1->e3; + return NULL; +} + +/** + * gts_triangle_is_duplicate: + * @t: a #GtsTriangle. + * + * Returns: a #GtsTriangle different from @t but sharing all its edges + * with @t or %NULL if there is none. + */ +GtsTriangle * gts_triangle_is_duplicate (GtsTriangle * t) +{ + GSList * i; + GtsEdge * e2, * e3; + + g_return_val_if_fail (t != NULL, NULL); + + e2 = t->e2; + e3 = t->e3; + i = t->e1->triangles; + while (i) { + GtsTriangle * t1 = i->data; + if (t1 != t && + (t1->e1 == e2 || t1->e2 == e2 || t1->e3 == e2) && + (t1->e1 == e3 || t1->e2 == e3 || t1->e3 == e3)) + return t1; + i = i->next; + } + + return NULL; +} + +/** + * gts_triangle_use_edges: + * @e1: a #GtsEdge. + * @e2: a #GtsEdge. + * @e3: a #GtsEdge. + * + * Returns: a #GtsTriangle having @e1, @e2 and @e3 as edges or %NULL if @e1, + * @e2 and @e3 are not part of any triangle. + */ +GtsTriangle * gts_triangle_use_edges (GtsEdge * e1, + GtsEdge * e2, + GtsEdge * e3) +{ + GSList * i; + + g_return_val_if_fail (e1 != NULL, NULL); + g_return_val_if_fail (e2 != NULL, NULL); + g_return_val_if_fail (e3 != NULL, NULL); + + i = e1->triangles; + while (i) { + GtsTriangle * t = i->data; + if ((t->e1 == e2 && (t->e2 == e3 || t->e3 == e3)) || + (t->e2 == e2 && (t->e1 == e3 || t->e3 == e3)) || + (t->e3 == e2 && (t->e1 == e3 || t->e2 == e3))) + return t; + i = i->next; + } + + return NULL; +} + +/** + * gts_triangle_is_ok: + * @t: a #GtsTriangle. + * + * Returns: %TRUE if @t is a non-degenerate, non-duplicate triangle, + * %FALSE otherwise. + */ +gboolean gts_triangle_is_ok (GtsTriangle * t) +{ + g_return_val_if_fail (t != NULL, FALSE); + g_return_val_if_fail (t->e1 != NULL, FALSE); + g_return_val_if_fail (t->e2 != NULL, FALSE); + g_return_val_if_fail (t->e3 != NULL, FALSE); + g_return_val_if_fail (t->e1 != t->e2 && t->e1 != t->e3 && t->e2 != t->e3, + FALSE); + g_return_val_if_fail (gts_segments_touch (GTS_SEGMENT (t->e1), + GTS_SEGMENT (t->e2)), + FALSE); + g_return_val_if_fail (gts_segments_touch (GTS_SEGMENT (t->e1), + GTS_SEGMENT (t->e3)), + FALSE); + g_return_val_if_fail (gts_segments_touch (GTS_SEGMENT (t->e2), + GTS_SEGMENT (t->e3)), + FALSE); + g_return_val_if_fail (GTS_SEGMENT (t->e1)->v1 != GTS_SEGMENT (t->e1)->v2, + FALSE); + g_return_val_if_fail (GTS_SEGMENT (t->e2)->v1 != GTS_SEGMENT (t->e2)->v2, + FALSE); + g_return_val_if_fail (GTS_SEGMENT (t->e3)->v1 != GTS_SEGMENT (t->e3)->v2, + FALSE); + g_return_val_if_fail (GTS_OBJECT (t)->reserved == NULL, FALSE); + g_return_val_if_fail (!gts_triangle_is_duplicate (t), FALSE); + return TRUE; +} + +/** + * gts_triangle_vertices: + * @t: a #GtsTriangle. + * @v1: a pointer on a #GtsVertex. + * @v2: a pointer on a #GtsVertex. + * @v3: a pointer on a #GtsVertex. + * + * Fills @v1, @v2 and @v3 with the oriented set of vertices, summits of @t. + */ +void gts_triangle_vertices (GtsTriangle * t, + GtsVertex ** v1, GtsVertex ** v2, GtsVertex ** v3) +{ + GtsSegment * e1, * e2; + + g_return_if_fail (t != NULL); + g_return_if_fail (v1 != NULL && v2 != NULL && v3 != NULL); + + e1 = GTS_SEGMENT (t->e1); + e2 = GTS_SEGMENT (t->e2); + + if (GTS_SEGMENT (e1)->v2 == GTS_SEGMENT (e2)->v1) { + *v1 = GTS_SEGMENT (e1)->v1; + *v2 = GTS_SEGMENT (e1)->v2; + *v3 = GTS_SEGMENT (e2)->v2; + } + else if (GTS_SEGMENT (e1)->v2 == GTS_SEGMENT (e2)->v2) { + *v1 = GTS_SEGMENT (e1)->v1; + *v2 = GTS_SEGMENT (e1)->v2; + *v3 = GTS_SEGMENT (e2)->v1; + } + else if (GTS_SEGMENT (e1)->v1 == GTS_SEGMENT (e2)->v1) { + *v1 = GTS_SEGMENT (e1)->v2; + *v2 = GTS_SEGMENT (e1)->v1; + *v3 = GTS_SEGMENT (e2)->v2; + } + else { + *v1 = GTS_SEGMENT (e1)->v2; + *v2 = GTS_SEGMENT (e1)->v1; + *v3 = GTS_SEGMENT (e2)->v1; + } +} + +/** + * gts_triangle_circumcircle_center: + * @t: a #GtsTriangle. + * @point_class: a #GtsPointClass. + * + * Returns: a new #GtsPoint, center of the circumscribing circle of @t or + * %NULL if the circumscribing circle is not defined. + */ +GtsPoint * gts_triangle_circumcircle_center (GtsTriangle * t, + GtsPointClass * point_class) +{ + GtsVertex * v1, * v2, * v3; + gdouble xa, ya, xb, yb, xc, yc; + gdouble xd, yd, xe, ye; + gdouble xad, yad, xae, yae; + gdouble det; + + g_return_val_if_fail (t != NULL, NULL); + g_return_val_if_fail (point_class != NULL, NULL); + + gts_triangle_vertices (t, &v1, &v2, &v3); + + xa = GTS_POINT (v1)->x; ya = GTS_POINT (v1)->y; + xb = GTS_POINT (v2)->x; yb = GTS_POINT (v2)->y; + xc = GTS_POINT (v3)->x; yc = GTS_POINT (v3)->y; + xd = (xa + xb)/2.; yd = (ya + yb)/2.; + xe = (xa + xc)/2.; ye = (ya + yc)/2.; + xad = xd - xa; yad = yd - ya; + xae = xe - xa; yae = ye - ya; + det = xad*yae - xae*yad; + if (det == 0.) + return NULL; + return gts_point_new (point_class, + (yae*yad*(yd - ye) + xad*yae*xd - xae*yad*xe)/det, + -(xae*xad*(xd - xe) + yad*xae*yd - yae*xad*ye)/det, + 0.); +} + +/* square of the maximum area ratio admissible */ +#define AREA_RATIO_MAX2 1e8 + +static gboolean points_are_folded (GtsPoint * A, + GtsPoint * B, + GtsPoint * C, + GtsPoint * D, + gdouble max) +{ + GtsVector AB, AC, AD; + GtsVector n1, n2; + gdouble nn1, nn2, n1n2; + + gts_vector_init (AB, A, B); + gts_vector_init (AC, A, C); + gts_vector_init (AD, A, D); + gts_vector_cross (n1, AB, AC); + gts_vector_cross (n2, AD, AB); + + nn1 = gts_vector_scalar (n1, n1); + nn2 = gts_vector_scalar (n2, n2); + if (nn1 >= AREA_RATIO_MAX2*nn2 || nn2 >= AREA_RATIO_MAX2*nn1) + return TRUE; + n1n2 = gts_vector_scalar (n1, n2); + if (n1n2 > 0.) + return FALSE; + if (n1n2*n1n2/(nn1*nn2) > max) + return TRUE; + return FALSE; +} + +static GtsVertex * triangle_use_vertices (GtsTriangle * t, + GtsVertex * A, + GtsVertex * B) +{ + GtsVertex + * v1 = GTS_SEGMENT (t->e1)->v1, + * v2 = GTS_SEGMENT (t->e1)->v2, + * v3 = gts_triangle_vertex (t); + + if (v1 == A) { + if (v2 == B) + return v3; + g_assert (v3 == B); + return v2; + } + if (v2 == A) { + if (v1 == B) + return v3; + g_assert (v3 == B); + return v1; + } + if (v3 == A) { + if (v1 == B) + return v2; + g_assert (v2 == B); + return v1; + } + g_assert_not_reached (); + return NULL; +} + +/** + * gts_triangles_are_folded: + * @triangles: a list of #GtsTriangle. + * @A: a #GtsVertex. + * @B: another #GtsVertex. + * @max: the maximum value of the square of the cosine of the angle between + * two triangles. + * + * Given a list of triangles sharing @A and @B as vertices, checks if any + * two triangles in the list make an angle larger than a given value defined + * by @max. + * + * Returns: %TRUE if any pair of triangles in @triangles makes an angle larger + * than the maximum value, %FALSE otherwise. + */ +gboolean gts_triangles_are_folded (GSList * triangles, + GtsVertex * A, GtsVertex * B, + gdouble max) +{ + GSList * i; + + g_return_val_if_fail (A != NULL, TRUE); + g_return_val_if_fail (B != NULL, TRUE); + + i = triangles; + while (i) { + GtsVertex * C = triangle_use_vertices (i->data, A, B); + GSList * j = i->next; + while (j) { + GtsVertex * D = triangle_use_vertices (j->data, A, B); + if (points_are_folded (GTS_POINT (A), + GTS_POINT (B), + GTS_POINT (C), + GTS_POINT (D), + max)) + return TRUE; + j = j->next; + } + i = i->next; + } + return FALSE; +} + +/** + * gts_triangle_is_stabbed: + * @t: a #GtsTriangle. + * @p: a #GtsPoint. + * @orientation: a pointer or %NULL. + * + * Returns: one of the vertices of @t, one of the edges of @t or @t if + * any of these are stabbed by the ray starting at @p (included) and + * ending at (@p->x, @p->y, +infty), %NULL otherwise. If the ray is + * contained in the plane of the triangle %NULL is also returned. If + * @orientation is not %NULL, it is set to the value of the + * orientation of @p relative to @t (as given by + * gts_point_orientation_3d()). + */ +GtsObject * gts_triangle_is_stabbed (GtsTriangle * t, + GtsPoint * p, + gdouble * orientation) +{ + GtsVertex * v1, * v2, * v3, * inverted = NULL; + GtsEdge * e1, * e2, * e3, * tmp; + gdouble o, o1, o2, o3; + + g_return_val_if_fail (t != NULL, NULL); + g_return_val_if_fail (p != NULL, NULL); + + gts_triangle_vertices_edges (t, NULL, &v1, &v2, &v3, &e1, &e2, &e3); + o = gts_point_orientation (GTS_POINT (v1), GTS_POINT (v2), GTS_POINT (v3)); + if (o == 0.) + return NULL; + if (o < 0.) { + inverted = v1; + v1 = v2; + v2 = inverted; + tmp = e2; + e2 = e3; + e3 = tmp; + } + o = gts_point_orientation_3d (GTS_POINT (v1), + GTS_POINT (v2), + GTS_POINT (v3), + p); + if (o < 0.) + return NULL; + o1 = gts_point_orientation (GTS_POINT (v1), GTS_POINT (v2), p); + if (o1 < 0.) + return NULL; + o2 = gts_point_orientation (GTS_POINT (v2), GTS_POINT (v3), p); + if (o2 < 0.) + return NULL; + o3 = gts_point_orientation (GTS_POINT (v3), GTS_POINT (v1), p); + if (o3 < 0.) + return NULL; + if (orientation) *orientation = inverted ? -o : o; + if (o1 == 0.) { + if (o2 == 0.) + return GTS_OBJECT (v2); + if (o3 == 0.) + return GTS_OBJECT (v1); + return GTS_OBJECT (e1); + } + if (o2 == 0.) { + if (o3 == 0.) + return GTS_OBJECT (v3); + return GTS_OBJECT (e2); + } + if (o3 == 0.) + return GTS_OBJECT (e3); + return GTS_OBJECT (t); +} + +/** + * gts_triangle_interpolate_height: + * @t: a #GtsTriangle. + * @p: a #GtsPoint. + * + * Fills the z-coordinate of point @p belonging to the plane + * projection of triangle @t with the linearly interpolated value of + * the z-coordinates of the vertices of @t. + */ +void gts_triangle_interpolate_height (GtsTriangle * t, GtsPoint * p) +{ + GtsPoint * p1, * p2, * p3; + gdouble x1, x2, y1, y2, det; + + g_return_if_fail (t != NULL); + g_return_if_fail (p != NULL); + + p1 = GTS_POINT (GTS_SEGMENT (t->e1)->v1); + p2 = GTS_POINT (GTS_SEGMENT (t->e1)->v2); + p3 = GTS_POINT (gts_triangle_vertex (t)); + + x1 = p2->x - p1->x; + y1 = p2->y - p1->y; + x2 = p3->x - p1->x; + y2 = p3->y - p1->y; + det = x1*y2 - x2*y1; + if (det == 0.) + p->z = (p1->z + p2->z + p3->z)/3.; + else { + gdouble x = p->x - p1->x; + gdouble y = p->y - p1->y; + gdouble a = (x*y2 - y*x2)/det; + gdouble b = (y*x1 - x*y1)/det; + + p->z = (1. - a - b)*p1->z + a*p2->z + b*p3->z; + } +} diff --git a/gts/tribox3.c b/gts/tribox3.c new file mode 100644 index 0000000000..c0ea778974 --- /dev/null +++ b/gts/tribox3.c @@ -0,0 +1,192 @@ +/** + * History: + * 2004-10-27 Stephane Popinet: changed float to double + */ + +/********************************************************/ +/* AABB-triangle overlap test code */ +/* by Tomas Akenine-Möller */ +/* Function: int triBoxOverlap(float boxcenter[3], */ +/* float boxhalfsize[3],float triverts[3][3]); */ +/* History: */ +/* 2001-03-05: released the code in its first version */ +/* 2001-06-18: changed the order of the tests, faster */ +/* */ +/* Acknowledgement: Many thanks to Pierre Terdiman for */ +/* suggestions and discussions on how to optimize code. */ +/* Thanks to David Hunt for finding a ">="-bug! */ +/********************************************************/ +#include +#include + +#define X 0 +#define Y 1 +#define Z 2 + +#define CROSS(dest,v1,v2) \ + dest[0]=v1[1]*v2[2]-v1[2]*v2[1]; \ + dest[1]=v1[2]*v2[0]-v1[0]*v2[2]; \ + dest[2]=v1[0]*v2[1]-v1[1]*v2[0]; + +#define DOT(v1,v2) (v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2]) + +#define SUB(dest,v1,v2) \ + dest[0]=v1[0]-v2[0]; \ + dest[1]=v1[1]-v2[1]; \ + dest[2]=v1[2]-v2[2]; + +#define FINDMINMAX(x0,x1,x2,min,max) \ + min = max = x0; \ + if(x1max) max=x1;\ + if(x2max) max=x2; + +int planeBoxOverlap(double normal[3], double vert[3], double maxbox[3]) // -NJMP- +{ + int q; + double vmin[3],vmax[3],v; + for(q=X;q<=Z;q++) + { + v=vert[q]; // -NJMP- + if(normal[q]>0.0f) + { + vmin[q]=-maxbox[q] - v; // -NJMP- + vmax[q]= maxbox[q] - v; // -NJMP- + } + else + { + vmin[q]= maxbox[q] - v; // -NJMP- + vmax[q]=-maxbox[q] - v; // -NJMP- + } + } + if(DOT(normal,vmin)>0.0f) return 0; // -NJMP- + if(DOT(normal,vmax)>=0.0f) return 1; // -NJMP- + + return 0; +} + + +/*======================== X-tests ========================*/ +#define AXISTEST_X01(a, b, fa, fb) \ + p0 = a*v0[Y] - b*v0[Z]; \ + p2 = a*v2[Y] - b*v2[Z]; \ + if(p0rad || max<-rad) return 0; + +#define AXISTEST_X2(a, b, fa, fb) \ + p0 = a*v0[Y] - b*v0[Z]; \ + p1 = a*v1[Y] - b*v1[Z]; \ + if(p0rad || max<-rad) return 0; + +/*======================== Y-tests ========================*/ +#define AXISTEST_Y02(a, b, fa, fb) \ + p0 = -a*v0[X] + b*v0[Z]; \ + p2 = -a*v2[X] + b*v2[Z]; \ + if(p0rad || max<-rad) return 0; + +#define AXISTEST_Y1(a, b, fa, fb) \ + p0 = -a*v0[X] + b*v0[Z]; \ + p1 = -a*v1[X] + b*v1[Z]; \ + if(p0rad || max<-rad) return 0; + +/*======================== Z-tests ========================*/ + +#define AXISTEST_Z12(a, b, fa, fb) \ + p1 = a*v1[X] - b*v1[Y]; \ + p2 = a*v2[X] - b*v2[Y]; \ + if(p2rad || max<-rad) return 0; + +#define AXISTEST_Z0(a, b, fa, fb) \ + p0 = a*v0[X] - b*v0[Y]; \ + p1 = a*v1[X] - b*v1[Y]; \ + if(p0rad || max<-rad) return 0; + +int triBoxOverlap(double boxcenter[3],double boxhalfsize[3],double triverts[3][3]) +{ + + /* use separating axis theorem to test overlap between triangle and box */ + /* need to test for overlap in these directions: */ + /* 1) the {x,y,z}-directions (actually, since we use the AABB of the triangle */ + /* we do not even need to test these) */ + /* 2) normal of the triangle */ + /* 3) crossproduct(edge from tri, {x,y,z}-directin) */ + /* this gives 3x3=9 more tests */ + double v0[3],v1[3],v2[3]; +// double axis[3]; + double min,max,p0,p1,p2,rad,fex,fey,fez; // -NJMP- "d" local variable removed + double normal[3],e0[3],e1[3],e2[3]; + + /* This is the fastest branch on Sun */ + /* move everything so that the boxcenter is in (0,0,0) */ + SUB(v0,triverts[0],boxcenter); + SUB(v1,triverts[1],boxcenter); + SUB(v2,triverts[2],boxcenter); + + /* compute triangle edges */ + SUB(e0,v1,v0); /* tri edge 0 */ + SUB(e1,v2,v1); /* tri edge 1 */ + SUB(e2,v0,v2); /* tri edge 2 */ + + /* Bullet 3: */ + /* test the 9 tests first (this was faster) */ + fex = fabsf(e0[X]); + fey = fabsf(e0[Y]); + fez = fabsf(e0[Z]); + AXISTEST_X01(e0[Z], e0[Y], fez, fey); + AXISTEST_Y02(e0[Z], e0[X], fez, fex); + AXISTEST_Z12(e0[Y], e0[X], fey, fex); + + fex = fabsf(e1[X]); + fey = fabsf(e1[Y]); + fez = fabsf(e1[Z]); + AXISTEST_X01(e1[Z], e1[Y], fez, fey); + AXISTEST_Y02(e1[Z], e1[X], fez, fex); + AXISTEST_Z0(e1[Y], e1[X], fey, fex); + + fex = fabsf(e2[X]); + fey = fabsf(e2[Y]); + fez = fabsf(e2[Z]); + AXISTEST_X2(e2[Z], e2[Y], fez, fey); + AXISTEST_Y1(e2[Z], e2[X], fez, fex); + AXISTEST_Z12(e2[Y], e2[X], fey, fex); + + /* Bullet 1: */ + /* first test overlap in the {x,y,z}-directions */ + /* find min, max of the triangle each direction, and test for overlap in */ + /* that direction -- this is equivalent to testing a minimal AABB around */ + /* the triangle against the AABB */ + + /* test in X-direction */ + FINDMINMAX(v0[X],v1[X],v2[X],min,max); + if(min>boxhalfsize[X] || max<-boxhalfsize[X]) return 0; + + /* test in Y-direction */ + FINDMINMAX(v0[Y],v1[Y],v2[Y],min,max); + if(min>boxhalfsize[Y] || max<-boxhalfsize[Y]) return 0; + + /* test in Z-direction */ + FINDMINMAX(v0[Z],v1[Z],v2[Z],min,max); + if(min>boxhalfsize[Z] || max<-boxhalfsize[Z]) return 0; + + /* Bullet 2: */ + /* test if the box intersects the plane of the triangle */ + /* compute plane equation of triangle: normal*x+d=0 */ + CROSS(normal,e0,e1); + // -NJMP- (line removed here) + if(!planeBoxOverlap(normal,v0,boxhalfsize)) return 0; // -NJMP- + + return 1; /* box and triangle overlaps */ +} + diff --git a/gts/vertex.c b/gts/vertex.c new file mode 100644 index 0000000000..d312869f82 --- /dev/null +++ b/gts/vertex.c @@ -0,0 +1,780 @@ +/* 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 "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; +} + diff --git a/gts/vopt.c b/gts/vopt.c new file mode 100644 index 0000000000..4bc448e1c0 --- /dev/null +++ b/gts/vopt.c @@ -0,0 +1,522 @@ +/* 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 "gts.h" + +/* #define DEBUG_VOPT */ + +/* compute the normal (nx, ny, nz) as the cross-product of the first two + oriented edges and the norm nt = |t| as (v1xv2).v3 */ +static void triangle_normal (GtsTriangle * t, + gdouble * nx, + gdouble * ny, + gdouble * nz, + gdouble * nt) +{ + GtsPoint * p1, * p2 = NULL, * p3 = NULL; + gdouble x1, y1, z1, x2, y2, z2; + + g_return_if_fail (t != NULL); + + p1 = GTS_POINT (GTS_SEGMENT (t->e1)->v1); + if (GTS_SEGMENT (t->e1)->v1 == GTS_SEGMENT (t->e2)->v1) { + p2 = GTS_POINT (GTS_SEGMENT (t->e2)->v2); + p3 = GTS_POINT (GTS_SEGMENT (t->e1)->v2); + } + else if (GTS_SEGMENT (t->e1)->v2 == GTS_SEGMENT (t->e2)->v2) { + p2 = GTS_POINT (GTS_SEGMENT (t->e1)->v2); + p3 = GTS_POINT (GTS_SEGMENT (t->e2)->v1); + } + else if (GTS_SEGMENT (t->e1)->v1 == GTS_SEGMENT (t->e2)->v2) { + p2 = GTS_POINT (GTS_SEGMENT (t->e2)->v1); + p3 = GTS_POINT (GTS_SEGMENT (t->e1)->v2); + } + else if (GTS_SEGMENT (t->e1)->v2 == GTS_SEGMENT (t->e2)->v1) { + p2 = GTS_POINT (GTS_SEGMENT (t->e1)->v2); + p3 = GTS_POINT (GTS_SEGMENT (t->e2)->v2); + } + else + g_assert_not_reached (); + + x1 = p2->x - p1->x; + y1 = p2->y - p1->y; + z1 = p2->z - p1->z; + + x2 = p3->x - p1->x; + y2 = p3->y - p1->y; + z2 = p3->z - p1->z; + + *nt = ((p1->y*p2->z - p1->z*p2->y)*p3->x + + (p1->z*p2->x - p1->x*p2->z)*p3->y + + (p1->x*p2->y - p1->y*p2->x)*p3->z); + *nx = y1*z2 - z1*y2; + *ny = z1*x2 - x1*z2; + *nz = x1*y2 - y1*x2; +} + +static void boundary_preservation (GtsEdge * edge, + GtsFace * f, + GtsVector e1, GtsVector e2, + GtsMatrix * H, GtsVector c) +{ + GtsTriangle * t = GTS_TRIANGLE (f); + GtsEdge * edge2; + GtsVertex * v1 = GTS_SEGMENT (edge)->v1, * v2 = GTS_SEGMENT (edge)->v2; + GtsPoint * p1, * p2; + GtsVector e, e3; + + /* find orientation of segment */ + edge2 = edge == t->e1 ? t->e2 : edge == t->e2 ? t->e3 : t->e1; + if (v2 != GTS_SEGMENT (edge2)->v1 && v2 != GTS_SEGMENT (edge2)->v2) { + v2 = v1; v1 = GTS_SEGMENT (edge)->v2; + } + p1 = GTS_POINT (v1); + p2 = GTS_POINT (v2); + + e[0] = p2->x - p1->x; + e[1] = p2->y - p1->y; + e[2] = p2->z - p1->z; + + e1[0] += e[0]; + e1[1] += e[1]; + e1[2] += e[2]; + + e3[0] = p2->y*p1->z - p2->z*p1->y; + e3[1] = p2->z*p1->x - p2->x*p1->z; + e3[2] = p2->x*p1->y - p2->y*p1->x; + + e2[0] += e3[0]; + e2[1] += e3[1]; + e2[2] += e3[2]; + + H[0][0] += e[1]*e[1] + e[2]*e[2]; + H[0][1] -= e[0]*e[1]; + H[0][2] -= e[0]*e[2]; + H[1][0] = H[0][1]; + H[1][1] += e[0]*e[0] + e[2]*e[2]; + H[1][2] -= e[1]*e[2]; + H[2][0] = H[0][2]; + H[2][1] = H[1][2]; + H[2][2] += e[0]*e[0] + e[1]*e[1]; + + c[0] += e[1]*e3[2] - e[2]*e3[1]; + c[1] += e[2]*e3[0] - e[0]*e3[2]; + c[2] += e[0]*e3[1] - e[1]*e3[0]; +} + +static gdouble boundary_cost (GtsEdge * edge, + GtsFace * f, + GtsVertex * v) +{ + GtsTriangle * t = GTS_TRIANGLE (f); + GtsEdge * edge2; + GtsVertex * v1 = GTS_SEGMENT (edge)->v1, * v2 = GTS_SEGMENT (edge)->v2; + GtsPoint * p1, * p2; + GtsVector e; + GtsPoint * p = GTS_POINT (v); + + /* find orientation of segment */ + edge2 = edge == t->e1 ? t->e2 : edge == t->e2 ? t->e3 : t->e1; + if (v2 != GTS_SEGMENT (edge2)->v1 && v2 != GTS_SEGMENT (edge2)->v2) { + v2 = v1; v1 = GTS_SEGMENT (edge)->v2; + } + p1 = GTS_POINT (v1); + p2 = GTS_POINT (v2); + + e[0] = (p2->y - p1->y)*(p->z - p2->z) - (p2->z - p1->z)*(p->y - p2->y); + e[1] = (p2->z - p1->z)*(p->x - p2->x) - (p2->x - p1->x)*(p->z - p2->z); + e[2] = (p2->x - p1->x)*(p->y - p2->y) - (p2->y - p1->y)*(p->x - p2->x); + + return e[0]*e[0] + e[1]*e[1] + e[2]*e[2]; +} + +static gdouble edge_boundary_cost (GtsEdge * e, GtsVertex * v) +{ + gdouble cost = 0.; + GSList * i; + + i = GTS_SEGMENT (e)->v1->segments; + while (i) { + GtsFace * f; + if (GTS_IS_EDGE (i->data) && + (f = gts_edge_is_boundary (i->data, NULL))) + cost += boundary_cost (i->data, f, v); + i = i->next; + } + i = GTS_SEGMENT (e)->v2->segments; + while (i) { + GtsFace * f; + if (i->data != e && + GTS_IS_EDGE (i->data) && + (f = gts_edge_is_boundary (i->data, NULL))) + cost += boundary_cost (i->data, f, v); + i = i->next; + } + + return cost/4.; +} + +static gdouble edge_volume_cost (GtsEdge * e, GtsVertex * v) +{ + GSList * i, * triangles; + gdouble n1, n2, n3, nt; + gdouble cost = 0.0, a; + + triangles = gts_vertex_triangles (GTS_SEGMENT (e)->v1, NULL); + triangles = gts_vertex_triangles (GTS_SEGMENT (e)->v2, triangles); + + i = triangles; + while (i) { + if (GTS_IS_FACE (i->data)) { + triangle_normal (i->data, &n1, &n2, &n3, &nt); + a = GTS_POINT (v)->x*n1 + + GTS_POINT (v)->y*n2 + + GTS_POINT (v)->z*n3 - nt; + cost += a*a; + } + i = i->next; + } + g_slist_free (triangles); + + return cost/36.; +} + +static gdouble edge_shape_cost (GtsEdge * e, GtsVertex * v) +{ + GSList * list, * i; + GtsVertex + * v1 = GTS_SEGMENT (e)->v1, + * v2 = GTS_SEGMENT (e)->v2; + gdouble cost = 0.; + + list = gts_vertex_neighbors (v1, NULL, NULL); + list = gts_vertex_neighbors (v2, list, NULL); + i = list; + while (i) { + GtsPoint * p = i->data; + if (p != GTS_POINT (v1) && p != GTS_POINT (v2)) + cost += gts_point_distance2 (p, GTS_POINT (v)); + i = i->next; + } + g_slist_free (list); + + return cost; +} + +/** + * gts_volume_optimized_vertex: + * @edge: a #GtsEdge. + * @klass: a #GtsVertexClass to be used for the new vertex. + * @params: a #GtsVolumeOptimizedParms. + * + * Returns: a #GtsVertex which can be used to replace @edge for an + * edge collapse operation. The position of the vertex is optimized in + * order to minimize the changes in area and volume for the surface + * using @edge. The volume enclosed by the surface is locally + * preserved. For more details see "Fast and memory efficient + * polygonal simplification" (1998) and "Evaluation of memoryless + * simplification" (1999) by Lindstrom and Turk. + */ +GtsVertex * gts_volume_optimized_vertex (GtsEdge * edge, + GtsVertexClass * klass, + GtsVolumeOptimizedParams * params) +{ + GSList * triangles, * i; + gdouble sn1 = 0., sn2 = 0., sn3 = 0.; + gdouble sn11 = 0., sn22 = 0., sn33 = 0.; + gdouble sn12 = 0., sn13 = 0., sn23 = 0.; + gdouble st = 0., stn1 = 0., stn2 = 0., stn3 = 0.; + gdouble n1, n2, n3, nt; + GtsMatrix * A, * Ai; + GtsVector A1, b; + GtsVector e1 = {0., 0., 0.}, e2 = {0., 0., 0.}; + GtsMatrix * Hb; + GtsVector cb = {0., 0., 0.}; + GtsVertex * v; + GtsVertex * v1, * v2; + guint n = 0, nb = 0; +#ifdef DEBUG_VOPT + guint nold = 0; +#endif + + g_return_val_if_fail (edge != NULL, NULL); + g_return_val_if_fail (klass != NULL, NULL); + g_return_val_if_fail (params != NULL, NULL); + + A = gts_matrix_zero (NULL); + Hb = gts_matrix_zero (NULL); + v1 = GTS_SEGMENT (edge)->v1; + v2 = GTS_SEGMENT (edge)->v2; + + /* boundary preservation */ + i = v1->segments; + while (i) { + GtsEdge * edge1 = i->data; + GtsFace * f; + if (GTS_IS_EDGE (edge1) && + (f = gts_edge_is_boundary (edge1, NULL))) { + boundary_preservation (edge1, f, e1, e2, Hb, cb); + nb++; + } + i = i->next; + } + i = v2->segments; + while (i) { + GtsEdge * edge1 = i->data; + GtsFace * f; + if (edge1 != edge && + GTS_IS_EDGE (edge1) && + (f = gts_edge_is_boundary (edge1, NULL))) { + boundary_preservation (edge1, f, e1, e2, Hb, cb); + nb++; + } + i = i->next; + } + if (nb > 0) { + GtsMatrix * H = gts_matrix_new ( + e1[2]*e1[2] + e1[1]*e1[1], - e1[0]*e1[1], - e1[0]*e1[2], 0., + - e1[0]*e1[1], e1[2]*e1[2] + e1[0]*e1[0], - e1[1]*e1[2], 0., + - e1[0]*e1[2], - e1[1]*e1[2], e1[1]*e1[1] + e1[0]*e1[0], 0., + 0., 0., 0., 0.); + GtsVector c; + + c[0] = e1[1]*e2[2] - e1[2]*e2[1]; + c[1] = e1[2]*e2[0] - e1[0]*e2[2]; + c[2] = e1[0]*e2[1] - e1[1]*e2[0]; + n = gts_matrix_quadratic_optimization (A, b, n, H, c); + gts_matrix_destroy (H); + } + + g_assert (n <= 2); + +#ifdef DEBUG_VOPT + if (n != nold) { + fprintf (stderr, "--- boundary preservation ---\n"); + gts_matrix_print (A, stderr); + gts_vector_print (b, stderr); + nold = n; + } +#endif + + /* volume preservation */ + triangles = gts_vertex_triangles (v1, NULL); + triangles = gts_vertex_triangles (v2, triangles); + + i = triangles; + while (i) { + if (GTS_IS_FACE (i->data)) { + triangle_normal (i->data, &n1, &n2, &n3, &nt); + sn1 += n1; sn2 += n2; sn3 += n3; + sn11 += n1*n1; sn22 += n2*n2; sn33 += n3*n3; + sn12 += n1*n2; sn13 += n1*n3; sn23 += n2*n3; + st += nt; stn1 += nt*n1; stn2 += nt*n2; stn3 += nt*n3; + } + i = i->next; + } + g_slist_free (triangles); + + A1[0] = sn1; A1[1] = sn2; A1[2] = sn3; + n = gts_matrix_compatible_row (A, b, n, A1, st); + +#ifdef DEBUG_VOPT + if (n != nold) { + fprintf (stderr, "--- volume preservation ---\n"); + gts_matrix_print (A, stderr); + gts_vector_print (b, stderr); + nold = n; + } +#endif + +#if 1 /* Weighted average of volume and boundary optimization */ + if (n < 3) { + /* volume optimization and boundary optimization */ + GtsMatrix * H = gts_matrix_new (sn11, sn12, sn13, 0., + sn12, sn22, sn23, 0., + sn13, sn23, sn33, 0., + 0., 0., 0., 0.); + GtsVector c; + gdouble le = 9.*params->boundary_weight* + gts_point_distance2 (GTS_POINT (v1), + GTS_POINT (v2)); + guint i, j; + + c[0] = - stn1; c[1] = - stn2; c[2] = - stn3; + if (nb > 0) + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) + H[i][j] = params->volume_weight*H[i][j] + le*Hb[i][j]; + c[i] = params->volume_weight*c[i] + le*cb[i]; + } + n = gts_matrix_quadratic_optimization (A, b, n, H, c); + gts_matrix_destroy (H); + } + +#ifdef DEBUG_VOPT + if (n != nold) { + fprintf (stderr, "--- volume and boundary optimization ---\n"); + gts_matrix_print (A, stderr); + gts_vector_print (b, stderr); + nold = n; + } +#endif + + if (n < 3) { + /* triangle shape optimization */ + gdouble nv = 0.0; + GtsMatrix * H; + GtsVector c = {0., 0., 0.}; + GSList * list, * i; + + list = gts_vertex_neighbors (v1, NULL, NULL); + list = gts_vertex_neighbors (v2, list, NULL); + + i = list; + while (i) { + GtsPoint * p1 = i->data; + if (p1 != GTS_POINT (v1) && p1 != GTS_POINT (v2)) { + nv += 1.0; + c[0] -= p1->x; + c[1] -= p1->y; + c[2] -= p1->z; + } + i = i->next; + } + g_slist_free (list); + + H = gts_matrix_new (nv, 0., 0., 0., + 0., nv, 0., 0., + 0., 0., nv, 0., + 0., 0., 0., 0.); + n = gts_matrix_quadratic_optimization (A, b, n, H, c); + gts_matrix_destroy (H); + } + +#ifdef DEBUG_VOPT + if (n != nold) { + fprintf (stderr, "--- triangle shape optimization ---\n"); + gts_matrix_print (A, stderr); + gts_vector_print (b, stderr); + nold = n; + } +#endif +#else /* Weighted average of volume, boundary and shape optimization */ + if (n < 3) { + /* volume optimization, boundary and shape optimization */ + GtsMatrix * H; + GtsVector c; + gdouble l2 = gts_point_distance2 (GTS_POINT (v1), + GTS_POINT (v2)); + gdouble wv = params->volume_weight/32.; + gdouble wb = params->boundary_weight/4.*l2; + gdouble ws = params->shape_weight*l2*l2; + + gdouble nv = 0.0; + GtsVector cs = {0., 0., 0.}; + GSList * list, * i; + + list = gts_vertex_neighbors (v1, NULL, NULL); + list = gts_vertex_neighbors (v2, list, NULL); + + i = list; + while (i) { + GtsPoint * p1 = i->data; + if (p1 != GTS_POINT (v1) && p1 != GTS_POINT (v2)) { + nv += 1.0; + cs[0] -= p1->x; + cs[1] -= p1->y; + cs[2] -= p1->z; + } + i = i->next; + } + g_slist_free (list); + + H = gts_matrix_new (wv*sn11 + wb*Hb[0][0] + ws*nv, + wv*sn12 + wb*Hb[0][1], + wv*sn13 + wb*Hb[0][2], + wv*sn12 + wb*Hb[1][0], + wv*sn22 + wb*Hb[1][1] + ws*nv, + wv*sn23 + wb*Hb[1][2], + wv*sn13 + wb*Hb[2][0], + wv*sn23 + wb*Hb[2][1], + wv*sn33 + wb*Hb[2][2] + ws*nv); + + c[0] = - wv*stn1 + wb*cb[0] + ws*cs[0]; + c[1] = - wv*stn2 + wb*cb[1] + ws*cs[1]; + c[2] = - wv*stn3 + wb*cb[2] + ws*cs[2]; + + n = gts_matrix_quadratic_optimization (A, b, n, H, c); + gts_matrix_destroy (H); + } + +#ifdef DEBUG_VOPT + if (n != nold) { + fprintf (stderr, "--- volume, boundary and shape optimization ---\n"); + gts_matrix_print (A, stderr); + gts_vector_print (b, stderr); + nold = n; + } +#endif +#endif /* Weighted average of volume, boundary and shape optimization */ + + g_assert (n == 3); + Ai = gts_matrix3_inverse (A); + g_assert (Ai != NULL); + + v = gts_vertex_new (klass, + Ai[0][0]*b[0] + Ai[0][1]*b[1] + Ai[0][2]*b[2], + Ai[1][0]*b[0] + Ai[1][1]*b[1] + Ai[1][2]*b[2], + Ai[2][0]*b[0] + Ai[2][1]*b[1] + Ai[2][2]*b[2]); + + gts_matrix_destroy (A); + gts_matrix_destroy (Ai); + gts_matrix_destroy (Hb); + + return v; +} + +/** + * gts_volume_optimized_cost: + * @e: a #GtsEdge. + * @params: a #GtsVolumeOptimizedParams. + * + * Returns: the cost for the collapse of @e as minimized by the function + * gts_volume_optimized_vertex(). + */ +gdouble gts_volume_optimized_cost (GtsEdge * e, + GtsVolumeOptimizedParams * params) +{ + GtsVertex * v; + gdouble cost; + gdouble length2; + + g_return_val_if_fail (e != NULL, G_MAXDOUBLE); + g_return_val_if_fail (params != NULL, G_MAXDOUBLE); + + v = gts_volume_optimized_vertex (e, gts_vertex_class (), params); + + length2 = gts_point_distance2 (GTS_POINT (GTS_SEGMENT (e)->v1), + GTS_POINT (GTS_SEGMENT (e)->v2)); + cost = + params->volume_weight*edge_volume_cost (e, v) + + params->boundary_weight*length2*edge_boundary_cost (e, v) + + params->shape_weight*length2*length2*edge_shape_cost (e, v); + gts_object_destroy (GTS_OBJECT (v)); + + return cost; +} diff --git a/src/Makefile.am b/src/Makefile.am index 57da5f4414..3fc265b396 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -203,6 +203,8 @@ pcb_DEPENDENCIES = @HIDLIBS@ if WITH_TOPOROUTER PCB_SRCS += toporouter.c toporouter.h +pcb_LDADD += ../gts/libgts.a +pcb_DEPENDENCIES += ../gts/libgts.a endif # All these -I$(top_srcdir) in this file are for globalconst.h. @@ -225,6 +227,9 @@ core_lists.h : ${PCB_SRCS} Makefile (for f in ${PCB_SRCS} ; do cat $(srcdir)/$$f ; done) | grep "^REGISTER" > $@.tmp mv $@.tmp $@ +# for globalconst.h +INCLUDES= -I$(top_srcdir) -I$(srcdir)/icons -I$(srcdir)/../gts + DEFS= -DLOCALEDIR=\"$(localedir)\" @DEFS@ EXTRA_DIST= \ diff --git a/src/toporouter.c b/src/toporouter.c index ac2043bbd5..6a012a3d3d 100644 --- a/src/toporouter.c +++ b/src/toporouter.c @@ -7687,6 +7687,8 @@ toporouter_new(void) ltime=time(NULL); + gts_predicates_init(); + Message(_("Topological Autorouter\n")); Message(_("Started %s"),asctime(localtime(<ime))); Message(_("-------------------------------------\n")); diff --git a/src/toporouter.h b/src/toporouter.h index 23e6fb341b..6fecdf08d5 100644 --- a/src/toporouter.h +++ b/src/toporouter.h @@ -55,7 +55,7 @@ #include "undo.h" #include "global.h" -#include +#include "gts.h" #include #include -- 2.11.4.GIT