diff --git a/Example/example.c b/Example/example.c index acbc388..2d4e23b 100644 --- a/Example/example.c +++ b/Example/example.c @@ -56,6 +56,7 @@ void poolFree( void* userData, void* ptr ) int run = 1; +int cdt = 0; static void key(GLFWwindow* window, int key, int scancode, int action, int mods) { @@ -65,6 +66,8 @@ static void key(GLFWwindow* window, int key, int scancode, int action, int mods) glfwSetWindowShouldClose(window, GL_TRUE); if (key == GLFW_KEY_SPACE && action == GLFW_PRESS) run = !run; + if (key == GLFW_KEY_C && action == GLFW_PRESS) + cdt = !cdt; } int main(int argc, char *argv[]) @@ -79,14 +82,15 @@ int main(int argc, char *argv[]) float t = 0.0f, pt = 0.0f; TESSalloc ma; TESStesselator* tess = 0; - const int nvp = 6; + const int nvp = 3; unsigned char* vflags = 0; - int nvflags = 0; #ifdef USE_POOL struct MemPool pool; unsigned char mem[1024*1024]; + int nvflags = 0; #else int allocated = 0; + double t0 = 0, t1 = 0; #endif TESS_NOTUSED(argc); TESS_NOTUSED(argv); @@ -104,7 +108,7 @@ int main(int argc, char *argv[]) if (!fg) return -1; printf("go...\n"); - + // Flip y for (it = bg; it != NULL; it = it->next) for (i = 0; i < it->npts; ++i) @@ -138,7 +142,7 @@ int main(int argc, char *argv[]) it->pts[i*2+1] -= cy; } } - + // Find BG bounds. bounds[0] = bounds[2] = bg->pts[0]; bounds[1] = bounds[3] = bg->pts[1]; @@ -154,7 +158,7 @@ int main(int argc, char *argv[]) if (y > bounds[3]) bounds[3] = y; } } - + #ifdef USE_POOL pool.size = 0; @@ -168,6 +172,8 @@ int main(int argc, char *argv[]) #else + t0 = glfwGetTime(); + memset(&ma, 0, sizeof(ma)); ma.memalloc = stdAlloc; ma.memfree = stdFree; @@ -178,6 +184,8 @@ int main(int argc, char *argv[]) if (!tess) return -1; + tessSetOption(tess, TESS_CONSTRAINED_DELAUNAY_TRIANGULATION, 1); + // Offset the foreground shape to center of the bg. offx = (bounds[2]+bounds[0])/2; offy = (bounds[3]+bounds[1])/2; @@ -189,7 +197,7 @@ int main(int argc, char *argv[]) it->pts[i*2+1] += offy; } } - + // Add contours. for (it = bg; it != NULL; it = it->next) tessAddContour(tess, 2, it->pts, sizeof(float)*2, it->npts); @@ -197,10 +205,14 @@ int main(int argc, char *argv[]) tessAddContour(tess, 2, it->pts, sizeof(float)*2, it->npts); if (!tessTesselate(tess, TESS_WINDING_POSITIVE, TESS_POLYGONS, nvp, 2, 0)) return -1; + + t1 = glfwGetTime(); + + printf("Time: %.3f ms\n", (t1 - t0) * 1000.0f); printf("Memory used: %.1f kB\n", allocated/1024.0f); - + #endif - + mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); width = mode->width - 40; height = mode->height - 80; @@ -221,17 +233,27 @@ int main(int argc, char *argv[]) view[2] = cx + w*1.2f; view[1] = cy - w*1.2f*(float)height/(float)width; view[3] = cy + w*1.2f*(float)height/(float)width; - + glfwSetTime(0); while (!glfwWindowShouldClose(window)) { - float ct = (float)glfwGetTime(); + int winWidth, winHeight; + int fbWidth, fbHeight; + float pxr, ct; + + glfwGetWindowSize(window, &winWidth, &winHeight); + glfwGetFramebufferSize(window, &fbWidth, &fbHeight); + + // Calculate pixel ration for hi-dpi devices. + pxr = (float)fbWidth / (float)winWidth; + + ct = (float)glfwGetTime(); if (run) t += ct - pt; pt = ct; - + // Update and render - glViewport(0, 0, width, height); + glViewport(0, 0, fbWidth, fbHeight); glClearColor(0.3f, 0.3f, 0.32f, 1.0f); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glEnable(GL_BLEND); @@ -251,9 +273,11 @@ int main(int argc, char *argv[]) tess = tessNewTess(&ma); if (tess) { + tessSetOption(tess, TESS_CONSTRAINED_DELAUNAY_TRIANGULATION, cdt); + offx = (view[2]+view[0])/2 + sinf(t) * (view[2]-view[0])/2; offy = (view[3]+view[1])/2 + cosf(t*3.13f) * (view[3]-view[1])/6; - + for (it = fg; it != NULL; it = it->next) { for (i = 0; i < it->npts; ++i) @@ -293,7 +317,7 @@ int main(int argc, char *argv[]) nvflags = nverts; vflags = (unsigned char*)malloc(sizeof(unsigned char)*nvflags); } - + if (vflags) { // Vertex indices describe the order the indices were added and can be used @@ -303,7 +327,7 @@ int main(int argc, char *argv[]) for (i = 0; i < nverts; ++i) vflags[i] = vinds[i] == TESS_UNDEF ? 1 : 0; } - + for (i = 0; i < nelems; ++i) { int b = elems[i*2]; @@ -314,9 +338,9 @@ int main(int argc, char *argv[]) tess = 0; } else - tess = 0; + tess = 0; } -#endif +#endif // Draw tesselated pieces. if (tess) @@ -326,7 +350,7 @@ int main(int argc, char *argv[]) const int* elems = tessGetElements(tess); const int nverts = tessGetVertexCount(tess); const int nelems = tessGetElementCount(tess); - + // Draw polygons. glColor4ub(255,255,255,128); for (i = 0; i < nelems; ++i) @@ -337,7 +361,10 @@ int main(int argc, char *argv[]) glVertex2f(verts[p[j]*2], verts[p[j]*2+1]); glEnd(); } - + + glLineWidth(1.0f * pxr); + glPointSize(3.0f * pxr); + glColor4ub(0,0,0,16); for (i = 0; i < nelems; ++i) { @@ -347,9 +374,8 @@ int main(int argc, char *argv[]) glVertex2f(verts[p[j]*2], verts[p[j]*2+1]); glEnd(); } - + glColor4ub(0,0,0,128); - glPointSize(3.0f); glBegin(GL_POINTS); for (i = 0; i < nverts; ++i) { @@ -360,21 +386,22 @@ int main(int argc, char *argv[]) glVertex2f(verts[i*2], verts[i*2+1]); } glEnd(); + glPointSize(1.0f); } - + glEnable(GL_DEPTH_TEST); glfwSwapBuffers(window); glfwPollEvents(); } - + if (tess) tessDeleteTess(tess); - + if (vflags) free(vflags); - - svgDelete(bg); - svgDelete(fg); + + svgDelete(bg); + svgDelete(fg); glfwTerminate(); return 0; diff --git a/Include/tesselator.h b/Include/tesselator.h index c27541e..38e05f1 100755 --- a/Include/tesselator.h +++ b/Include/tesselator.h @@ -1,5 +1,5 @@ /* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) +** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) ** Copyright (C) [dates of first publication] Silicon Graphics, Inc. ** All Rights Reserved. ** @@ -9,10 +9,10 @@ ** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies ** of the Software, and to permit persons to whom the Software is furnished to do so, ** subject to the following conditions: -** +** ** The above copyright notice including the dates of first publication and either this ** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. +** included in all copies or substantial portions of the Software. ** ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, ** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A @@ -20,7 +20,7 @@ ** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE ** OR OTHER DEALINGS IN THE SOFTWARE. -** +** ** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not ** be used in advertising or otherwise to promote the sale, use or other dealings in ** this Software without prior written authorization from Silicon Graphics, Inc. @@ -106,7 +106,7 @@ enum TessWindingRule // } // glEnd(); // } -// + enum TessElementType { TESS_POLYGONS, @@ -114,6 +114,16 @@ enum TessElementType TESS_BOUNDARY_CONTOURS, }; + +// TESS_CONSTRAINED_DELAUNAY_TRIANGULATION +// If enabled, the initial triagulation is improved with non-robust Constrained Delayney triangulation. +// Disable by default. + +enum TessOption +{ + TESS_CONSTRAINED_DELAUNAY_TRIANGULATION, +}; + typedef float TESSreal; typedef int TESSindex; typedef struct TESStesselator TESStesselator; @@ -133,12 +143,12 @@ typedef struct TESSalloc TESSalloc; // how often to allocate memory from the system versus how much extra space the system // should allocate. Reasonable defaults are show in commects below, they will be used if // the bucket sizes are zero. -// +// // The use may left the memrealloc to be null. In that case, the tesselator will not try to // dynamically grow int's internal arrays. The tesselator only needs the reallocation when it // has found intersecting segments and needs to add new vertex. This defency can be cured by // allocating some extra vertices beforehand. The 'extraVertices' variable allows to specify -// number of expected extra vertices. +// number of expected extra vertices. struct TESSalloc { void *(*memalloc)( void *userData, unsigned int size ); @@ -184,6 +194,12 @@ void tessDeleteTess( TESStesselator *tess ); // count - number of vertices in contour. void tessAddContour( TESStesselator *tess, int size, const void* pointer, int stride, int count ); +// tessSetOption() - Toggles optional tessellation parameters +// Parameters: +// option - one of TessOption +// value - 1 if enabled, 0 if disabled. +void tessSetOption( TESStesselator *tess, int option, int value ); + // tessTesselate() - tesselate contours. // Parameters: // tess - pointer to tesselator object. @@ -207,7 +223,7 @@ const TESSreal* tessGetVertices( TESStesselator *tess ); // Every point added using tessAddContour() will get a new index starting at 0. // New vertices generated at the intersections of segments are assigned value TESS_UNDEF. const TESSindex* tessGetVertexIndices( TESStesselator *tess ); - + // tessGetElementCount() - Returns number of elements in the the tesselated output. int tessGetElementCount( TESStesselator *tess ); diff --git a/Source/geom.c b/Source/geom.c index a49ea17..97a5046 100755 --- a/Source/geom.c +++ b/Source/geom.c @@ -1,5 +1,5 @@ /* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) +** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) ** Copyright (C) [dates of first publication] Silicon Graphics, Inc. ** All Rights Reserved. ** @@ -9,10 +9,10 @@ ** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies ** of the Software, and to permit persons to whom the Software is furnished to do so, ** subject to the following conditions: -** +** ** The above copyright notice including the dates of first publication and either this ** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. +** included in all copies or substantial portions of the Software. ** ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, ** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A @@ -20,7 +20,7 @@ ** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE ** OR OTHER DEALINGS IN THE SOFTWARE. -** +** ** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not ** be used in advertising or otherwise to promote the sale, use or other dealings in ** this Software without prior written authorization from Silicon Graphics, Inc. @@ -33,6 +33,7 @@ #include #include "mesh.h" #include "geom.h" +#include int tesvertLeq( TESSvertex *u, TESSvertex *v ) { @@ -259,3 +260,30 @@ void tesedgeIntersect( TESSvertex *o1, TESSvertex *d1, v->t = Interpolate( z1, o2->t, z2, d2->t ); } } + +/* + Calculate the angle between v1-v2 and v1-v0 + */ +TESSreal calcAngle( TESSvertex *v0, TESSvertex *v1, TESSvertex *v2 ) +{ + TESSreal num, den, ax, ay, bx, by; + ax = v2->s - v1->s; + ay = v2->t - v1->t; + bx = v0->s - v1->s; + by = v0->t - v1->t; + num = ax * bx + ay * by; + den = sqrt( ax * ax + ay * ay ) * sqrt( bx * bx + by * by ); + if ( den > 0.0 ) num /= den; + if ( num < -1.0 ) num = -1.0; + if ( num > 1.0 ) num = 1.0; + return acos( num ); +} + +/* + Returns 1 is edge is locally delaunay + */ +int tesedgeIsLocallyDelaunay( TESShalfEdge *e ) +{ + return (calcAngle(e->Lnext->Org, e->Lnext->Lnext->Org, e->Org) + + calcAngle(e->Sym->Lnext->Org, e->Sym->Lnext->Lnext->Org, e->Sym->Org)) < (M_PI + 0.01); +} diff --git a/Source/geom.h b/Source/geom.h index cf83897..c29a932 100755 --- a/Source/geom.h +++ b/Source/geom.h @@ -59,6 +59,7 @@ #define EdgeGoesLeft(e) VertLeq( (e)->Dst, (e)->Org ) #define EdgeGoesRight(e) VertLeq( (e)->Org, (e)->Dst ) +#define EdgeIsInternal(e) e->Rface && e->Rface->inside #define ABS(x) ((x) < 0 ? -(x) : (x)) #define VertL1dist(u,v) (ABS(u->s - v->s) + ABS(u->t - v->t)) @@ -72,5 +73,6 @@ TESSreal testransEval( TESSvertex *u, TESSvertex *v, TESSvertex *w ); TESSreal testransSign( TESSvertex *u, TESSvertex *v, TESSvertex *w ); int tesvertCCW( TESSvertex *u, TESSvertex *v, TESSvertex *w ); void tesedgeIntersect( TESSvertex *o1, TESSvertex *d1, TESSvertex *o2, TESSvertex *d2, TESSvertex *v ); +int tesedgeIsLocallyDelaunay( TESShalfEdge *e ); #endif diff --git a/Source/mesh.c b/Source/mesh.c index 06a8ced..15ce620 100755 --- a/Source/mesh.c +++ b/Source/mesh.c @@ -80,6 +80,7 @@ static TESShalfEdge *MakeEdge( TESSmesh* mesh, TESShalfEdge *eNext ) e->Lface = NULL; e->winding = 0; e->activeRegion = NULL; + e->mark = 0; eSym->Sym = e; eSym->Onext = eSym; @@ -88,6 +89,7 @@ static TESShalfEdge *MakeEdge( TESSmesh* mesh, TESShalfEdge *eNext ) eSym->Lface = NULL; eSym->winding = 0; eSym->activeRegion = NULL; + eSym->mark = 0; return e; } @@ -748,6 +750,83 @@ int tessMeshMergeConvexFaces( TESSmesh *mesh, int maxVertsPerFace ) return 1; } +void tessMeshFlipEdge( TESSmesh *mesh, TESShalfEdge *edge ) +{ + TESShalfEdge *a0 = edge; + TESShalfEdge *a1 = a0->Lnext; + TESShalfEdge *a2 = a1->Lnext; + TESShalfEdge *b0 = edge->Sym; + TESShalfEdge *b1 = b0->Lnext; + TESShalfEdge *b2 = b1->Lnext; + + TESSvertex *aOrg = a0->Org; + TESSvertex *aOpp = a2->Org; + TESSvertex *bOrg = b0->Org; + TESSvertex *bOpp = b2->Org; + + TESSface *fa = a0->Lface; + TESSface *fb = b0->Lface; + + assert(EdgeIsInternal(edge)); + assert(a2->Lnext == a0); + assert(b2->Lnext == b0); + + a0->Org = bOpp; + a0->Onext = b1->Sym; + b0->Org = aOpp; + b0->Onext = a1->Sym; + a2->Onext = b0; + b2->Onext = a0; + b1->Onext = a2->Sym; + a1->Onext = b2->Sym; + + a0->Lnext = a2; + a2->Lnext = b1; + b1->Lnext = a0; + + b0->Lnext = b2; + b2->Lnext = a1; + a1->Lnext = b0; + + a1->Lface = fb; + b1->Lface = fa; + + fa->anEdge = a0; + fb->anEdge = b0; + + if (aOrg->anEdge == a0) aOrg->anEdge = b1; + if (bOrg->anEdge == b0) bOrg->anEdge = a1; + + assert( a0->Lnext->Onext->Sym == a0 ); + assert( a0->Onext->Sym->Lnext == a0 ); + assert( a0->Org->anEdge->Org == a0->Org ); + + + assert( a1->Lnext->Onext->Sym == a1 ); + assert( a1->Onext->Sym->Lnext == a1 ); + assert( a1->Org->anEdge->Org == a1->Org ); + + assert( a2->Lnext->Onext->Sym == a2 ); + assert( a2->Onext->Sym->Lnext == a2 ); + assert( a2->Org->anEdge->Org == a2->Org ); + + assert( b0->Lnext->Onext->Sym == b0 ); + assert( b0->Onext->Sym->Lnext == b0 ); + assert( b0->Org->anEdge->Org == b0->Org ); + + assert( b1->Lnext->Onext->Sym == b1 ); + assert( b1->Onext->Sym->Lnext == b1 ); + assert( b1->Org->anEdge->Org == b1->Org ); + + assert( b2->Lnext->Onext->Sym == b2 ); + assert( b2->Onext->Sym->Lnext == b2 ); + assert( b2->Org->anEdge->Org == b2->Org ); + + assert(aOrg->anEdge->Org == aOrg); + assert(bOrg->anEdge->Org == bOrg); + + assert(a0->Oprev->Onext->Org == a0->Org); +} #ifdef DELETE_BY_ZAPPING diff --git a/Source/mesh.h b/Source/mesh.h index c492916..479c66f 100755 --- a/Source/mesh.h +++ b/Source/mesh.h @@ -143,6 +143,7 @@ struct TESShalfEdge { ActiveRegion *activeRegion; /* a region with this upper edge (sweep.c) */ int winding; /* change in winding number when crossing from the right face to the left face */ + int mark; /* Used by the Edge Flip algorithm */ }; #define Rface Sym->Lface @@ -155,7 +156,6 @@ struct TESShalfEdge { #define Dnext Rprev->Sym /* 3 pointers */ #define Rnext Oprev->Sym /* 3 pointers */ - struct TESSmesh { TESSvertex vHead; /* dummy header for vertex list */ TESSface fHead; /* dummy header for face list */ @@ -258,6 +258,8 @@ int tessMeshMergeConvexFaces( TESSmesh *mesh, int maxVertsPerFace ); void tessMeshDeleteMesh( TESSalloc* alloc, TESSmesh *mesh ); void tessMeshZapFace( TESSmesh *mesh, TESSface *fZap ); +void tessMeshFlipEdge( TESSmesh *mesh, TESShalfEdge *edge ); + #ifdef NDEBUG #define tessMeshCheckMesh( mesh ) #else diff --git a/Source/tess.c b/Source/tess.c index 67c038f..8059bf4 100755 --- a/Source/tess.c +++ b/Source/tess.c @@ -1,5 +1,5 @@ /* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) +** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) ** Copyright (C) [dates of first publication] Silicon Graphics, Inc. ** All Rights Reserved. ** @@ -9,10 +9,10 @@ ** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies ** of the Software, and to permit persons to whom the Software is furnished to do so, ** subject to the following conditions: -** +** ** The above copyright notice including the dates of first publication and either this ** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. +** included in all copies or substantial portions of the Software. ** ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, ** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A @@ -20,7 +20,7 @@ ** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE ** OR OTHER DEALINGS IN THE SOFTWARE. -** +** ** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not ** be used in advertising or otherwise to promote the sale, use or other dealings in ** this Software without prior written authorization from Silicon Graphics, Inc. @@ -186,7 +186,7 @@ extern int RandomSweep; #define S_UNIT_X (RandomSweep ? (2*drand48()-1) : 1.0) #define S_UNIT_Y (RandomSweep ? (2*drand48()-1) : 0.0) #else -#if defined(SLANTED_SWEEP) +#if defined(SLANTED_SWEEP) /* The "feature merging" is not intended to be complete. There are * special cases where edges are nearly parallel to the sweep line * which are not implemented. The algorithm should still behave @@ -295,7 +295,7 @@ void tessProjectPolygon( TESStesselator *tess ) * (what else would it do??) The region must consist of a single * loop of half-edges (see mesh.h) oriented CCW. "Monotone" in this * case means that any vertical line intersects the interior of the -* region in a single interval. +* region in a single interval. * * Tessellation consists of adding interior edges (actually pairs of * half-edges), to split the region into non-overlapping triangles. @@ -374,7 +374,6 @@ int tessMeshTessellateMonoRegion( TESSmesh *mesh, TESSface *face ) return 1; } - /* tessMeshTessellateInterior( mesh ) tessellates each region of * the mesh which is marked "inside" the polygon. Each such region * must be monotone. @@ -391,11 +390,125 @@ int tessMeshTessellateInterior( TESSmesh *mesh ) if ( !tessMeshTessellateMonoRegion( mesh, f ) ) return 0; } } - return 1; } +typedef struct EdgeStackNode EdgeStackNode; +typedef struct EdgeStack EdgeStack; + +struct EdgeStackNode { + TESShalfEdge *edge; + EdgeStackNode *next; +}; + +struct EdgeStack { + EdgeStackNode *top; + struct BucketAlloc *nodeBucket; +}; + +int stackInit( EdgeStack *stack, TESSalloc *alloc ) +{ + stack->top = NULL; + stack->nodeBucket = createBucketAlloc( alloc, "CDT nodes", sizeof(EdgeStackNode), 512 ); + return stack->nodeBucket != NULL; +} + +void stackDelete( EdgeStack *stack ) +{ + deleteBucketAlloc( stack->nodeBucket ); +} + +int stackEmpty( EdgeStack *stack ) +{ + return stack->top == NULL; +} + +void stackPush( EdgeStack *stack, TESShalfEdge *e ) +{ + EdgeStackNode *node = (EdgeStackNode *)bucketAlloc( stack->nodeBucket ); + if ( ! node ) return; + node->edge = e; + node->next = stack->top; + stack->top = node; +} + +TESShalfEdge *stackPop( EdgeStack *stack ) +{ + TESShalfEdge *e = NULL; + EdgeStackNode *node = stack->top; + if (node) { + stack->top = node->next; + e = node->edge; + bucketFree( stack->nodeBucket, node ); + } + return e; +} + + +// Starting with a valid triangulation, uses the Edge Flip algorithm to +// refine the triangulation into a Constrained Delaunay Triangulation. +void tessMeshRefineDelaunay( TESSmesh *mesh, TESSalloc *alloc ) +{ + // At this point, we have a valid, but not optimal, triangulation. + // We refine the triangulation using the Edge Flip algorithm + // + // 1) Find all internal edges + // 2) Mark all dual edges + // 3) insert all dual edges into a queue + + TESSface *f; + EdgeStack stack; + TESShalfEdge *e; + int maxFaces = 0, maxIter = 0, iter = 0; + + stackInit(&stack, alloc); + + for( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) { + if ( f->inside) { + e = f->anEdge; + do { + e->mark = EdgeIsInternal(e); // Mark internal edges + if (e->mark && !e->Sym->mark) stackPush(&stack, e); // Insert into queue + e = e->Lnext; + } while (e != f->anEdge); + maxFaces++; + } + } + + // The algorithm should converge on O(n^2), since the predicate is not robust, + // we'll save guard against infinite loop. + maxIter = maxFaces * maxFaces; + + // Pop stack until we find a reversed edge + // Flip the reversed edge, and insert any of the four opposite edges + // which are internal and not already in the stack (!marked) + while (!stackEmpty(&stack) && iter < maxIter) { + e = stackPop(&stack); + e->mark = e->Sym->mark = 0; + if (!tesedgeIsLocallyDelaunay(e)) { + TESShalfEdge *edges[4]; + int i; + tessMeshFlipEdge(mesh, e); + // for each opposite edge + edges[0] = e->Lnext; + edges[1] = e->Lprev; + edges[2] = e->Sym->Lnext; + edges[3] = e->Sym->Lprev; + for (i = 0; i < 4; i++) { + if (!edges[i]->mark && EdgeIsInternal(edges[i])) { + edges[i]->mark = edges[i]->Sym->mark = 1; + stackPush(&stack, edges[i]); + } + } + } + iter++; + } + + stackDelete(&stack); +} + + /* tessMeshDiscardExterior( mesh ) zaps (ie. sets to NULL) all faces * which are not marked "inside" the polygon. Since further mesh operations * on NULL faces are not allowed, the main purpose is to clean up the @@ -485,7 +598,7 @@ TESStesselator* tessNewTess( TESSalloc* alloc ) if (alloc == NULL) alloc = &defaulAlloc; - + /* Only initialize fields which can be changed by the api. Other fields * are initialized where they are used. */ @@ -506,7 +619,7 @@ TESStesselator* tessNewTess( TESSalloc* alloc ) tess->alloc.dictNodeBucketSize = 512; if (tess->alloc.regionBucketSize == 0) tess->alloc.regionBucketSize = 256; - + tess->normal[0] = 0; tess->normal[1] = 0; tess->normal[2] = 0; @@ -530,7 +643,7 @@ TESStesselator* tessNewTess( TESSalloc* alloc ) tess->outOfMemory = 0; tess->vertexIndexCounter = 0; - + tess->vertices = 0; tess->vertexIndices = 0; tess->vertexCount = 0; @@ -542,9 +655,9 @@ TESStesselator* tessNewTess( TESSalloc* alloc ) void tessDeleteTess( TESStesselator *tess ) { - + struct TESSalloc alloc = tess->alloc; - + deleteBucketAlloc( tess->regionPool ); if( tess->mesh != NULL ) { @@ -623,7 +736,7 @@ void OutputPolymesh( TESStesselator *tess, TESSmesh *mesh, int elementType, int edge = edge->Lnext; } while (edge != f->anEdge); - + assert( faceVerts <= polySize ); f->n = maxFaceCount; @@ -640,7 +753,7 @@ void OutputPolymesh( TESStesselator *tess, TESSmesh *mesh, int elementType, int tess->outOfMemory = 1; return; } - + tess->vertexCount = maxVertexCount; tess->vertices = (TESSreal*)tess->alloc.memalloc( tess->alloc.userData, sizeof(TESSreal) * tess->vertexCount * vertexSize ); @@ -657,7 +770,7 @@ void OutputPolymesh( TESStesselator *tess, TESSmesh *mesh, int elementType, int tess->outOfMemory = 1; return; } - + // Output vertices. for ( v = mesh->vHead.next; v != &mesh->vHead; v = v->next ) { @@ -679,7 +792,7 @@ void OutputPolymesh( TESStesselator *tess, TESSmesh *mesh, int elementType, int for ( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) { if ( !f->inside ) continue; - + // Store polygon edge = f->anEdge; faceVerts = 0; @@ -748,7 +861,7 @@ void OutputContours( TESStesselator *tess, TESSmesh *mesh, int vertexSize ) tess->outOfMemory = 1; return; } - + tess->vertices = (TESSreal*)tess->alloc.memalloc( tess->alloc.userData, sizeof(TESSreal) * tess->vertexCount * vertexSize ); if (!tess->vertices) @@ -764,7 +877,7 @@ void OutputContours( TESStesselator *tess, TESSmesh *mesh, int vertexSize ) tess->outOfMemory = 1; return; } - + verts = tess->vertices; elements = tess->elements; vertInds = tess->vertexIndices; @@ -865,6 +978,17 @@ void tessAddContour( TESStesselator *tess, int size, const void* vertices, } } +void tessSetOption( TESStesselator *tess, int option, int value ) +{ + switch(option) + { + case TESS_CONSTRAINED_DELAUNAY_TRIANGULATION: + tess->processCDT = value > 0 ? 1 : 0; + break; + } +} + + int tessTesselate( TESStesselator *tess, int windingRule, int elementType, int polySize, int vertexSize, const TESSreal* normal ) { @@ -885,7 +1009,7 @@ int tessTesselate( TESStesselator *tess, int windingRule, int elementType, } tess->vertexIndexCounter = 0; - + if (normal) { tess->normal[0] = normal[0]; @@ -900,7 +1024,7 @@ int tessTesselate( TESStesselator *tess, int windingRule, int elementType, if (vertexSize > 3) vertexSize = 3; - if (setjmp(tess->env) != 0) { + if (setjmp(tess->env) != 0) { /* come back here if out of memory */ return 0; } @@ -934,7 +1058,9 @@ int tessTesselate( TESStesselator *tess, int windingRule, int elementType, if (elementType == TESS_BOUNDARY_CONTOURS) { rc = tessMeshSetWindingNumber( mesh, 1, TRUE ); } else { - rc = tessMeshTessellateInterior( mesh ); + rc = tessMeshTessellateInterior( mesh ); + if (rc != 0 && tess->processCDT != 0) + tessMeshRefineDelaunay( mesh, &tess->alloc ); } if (rc == 0) longjmp(tess->env,1); /* could've used a label */ diff --git a/Source/tess.h b/Source/tess.h index c8c747b..c1f981f 100755 --- a/Source/tess.h +++ b/Source/tess.h @@ -1,5 +1,5 @@ /* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) +** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) ** Copyright (C) [dates of first publication] Silicon Graphics, Inc. ** All Rights Reserved. ** @@ -9,10 +9,10 @@ ** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies ** of the Software, and to permit persons to whom the Software is furnished to do so, ** subject to the following conditions: -** +** ** The above copyright notice including the dates of first publication and either this ** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. +** included in all copies or substantial portions of the Software. ** ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, ** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A @@ -20,7 +20,7 @@ ** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE ** OR OTHER DEALINGS IN THE SOFTWARE. -** +** ** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not ** be used in advertising or otherwise to promote the sale, use or other dealings in ** this Software without prior written authorization from Silicon Graphics, Inc. @@ -61,6 +61,8 @@ struct TESStesselator { TESSreal bmin[2]; TESSreal bmax[2]; + int processCDT; /* option to run Constrained Delayney pass. */ + /*** state needed for the line sweep ***/ int windingRule; /* rule for determining polygon interior */ @@ -71,7 +73,7 @@ struct TESStesselator { struct BucketAlloc* regionPool; TESSindex vertexIndexCounter; - + TESSreal *vertices; TESSindex *vertexIndices; int vertexCount; @@ -79,7 +81,7 @@ struct TESStesselator { int elementCount; TESSalloc alloc; - + jmp_buf env; /* place to jump to when memAllocs fail */ }; diff --git a/alg_outline.md b/alg_outline.md index 449ca2a..9a4a2bd 100644 --- a/alg_outline.md +++ b/alg_outline.md @@ -223,3 +223,11 @@ triangles into fans and strips. We do this using a greedy approach. The triangulation itself is not optimized to reduce the number of primitives; we just try to get a reasonable decomposition of the computed triangulation. + +Optionally, it's possible to output a Constrained Delaunay Triangulation. +This is done by doing a delaunay refinement with the normal triangulation as +a basis. The Edge Flip algorithm is used, which is guaranteed to terminate in O(n^2). + +Note: We don't use robust predicates to check if edges are locally +delaunay, but currently us a naive epsilon of 0.01 radians to ensure +termination.