From 6d7954e167432af22e6f4556b3817f5717383265 Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Thu, 22 Jan 2009 02:02:46 -0800 Subject: [PATCH] Fix an issue with edge intersection testing: if a vertex from edge A touches edge B, but does not share a vertex with edge B, then that's an intersection. Adjust the ear clipping so that it generates strip-like triangulations, not fan-like. And rearrange deck chairs on the bridge-finding code, which is still pathetically slow. It may not be possible to get reasonable performance without kd tree type acceleration. [git-p4: depot-paths = "//depot/solvespace/": change = 1901] --- polygon.cpp | 26 ++++++++++++-- polygon.h | 2 ++ srf/ratpoly.cpp | 4 +-- srf/triangulate.cpp | 83 +++++++++++++++++++++++++++++++++------------ 4 files changed, 88 insertions(+), 27 deletions(-) diff --git a/polygon.cpp b/polygon.cpp index ae67b189..df7ad343 100644 --- a/polygon.cpp +++ b/polygon.cpp @@ -187,9 +187,19 @@ bool SEdgeList::AnyEdgeCrosses(Vector a, Vector b) { &t, &tse); if(skew) continue; - if(t > t_eps && t < (1 - t_eps) && - tse > tse_eps && tse < (1 - tse_eps)) - { + bool inOrEdge0 = (t > -t_eps) && (t < (1 + t_eps)); + bool inOrEdge1 = (tse > -tse_eps) && (tse < (1 + tse_eps)); + + if(inOrEdge0 && inOrEdge1) { + if((se->a).Equals(a) || (se->b).Equals(a) || + (se->a).Equals(b) || (se->b).Equals(b)) + { + // Not an intersection if we share an endpoint with an edge + continue; + } + // But it's an intersection if a vertex of one edge lies on the + // inside of the other (or if they cross away from either's + // vertex). return true; } } @@ -222,6 +232,16 @@ void SContour::CopyInto(SContour *dest) { } } +void SContour::FindPointWithMinX(void) { + SPoint *sp; + xminPt = Vector::From(1e10, 1e10, 1e10); + for(sp = l.First(); sp; sp = l.NextAfter(sp)) { + if(sp->p.x < xminPt.x) { + xminPt = sp->p; + } + } +} + Vector SContour::ComputeNormal(void) { Vector n = Vector::From(0, 0, 0); diff --git a/polygon.h b/polygon.h index cd7e4974..77170120 100644 --- a/polygon.h +++ b/polygon.h @@ -43,6 +43,7 @@ class SContour { public: int tag; int timesEnclosed; + Vector xminPt; List l; void AddPoint(Vector p); @@ -54,6 +55,7 @@ public: bool AllPointsInPlane(Vector n, double d, Vector *notCoplanarAt); void OffsetInto(SContour *dest, double r); void CopyInto(SContour *dest); + void FindPointWithMinX(void); bool IsEar(int bp); bool BridgeToContour(SContour *sc, SEdgeList *el, List *vl); diff --git a/srf/ratpoly.cpp b/srf/ratpoly.cpp index 1eff6d74..31e315c9 100644 --- a/srf/ratpoly.cpp +++ b/srf/ratpoly.cpp @@ -499,7 +499,7 @@ void SSurface::TriangulateInto(SShell *shell, SMesh *sm) { st->FlipNormal(); } } - + el.Clear(); poly.Clear(); } @@ -559,7 +559,7 @@ SShell SShell::FromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1) { STrimBy stb1 = STrimBy::EntireCurve(&ret, hc1); // The translated curves trim the flat top and bottom surfaces. - (ret.surface.FindById(hs0))->trim.Add(&stb0); +// (ret.surface.FindById(hs0))->trim.Add(&stb0); (ret.surface.FindById(hs1))->trim.Add(&stb1); // The translated curves also trim the surface of extrusion. diff --git a/srf/triangulate.cpp b/srf/triangulate.cpp index 446adf29..ea7d1463 100644 --- a/srf/triangulate.cpp +++ b/srf/triangulate.cpp @@ -3,6 +3,8 @@ void SPolygon::UvTriangulateInto(SMesh *m) { if(l.n <= 0) return; + SDWORD in = GetMilliseconds(); + normal = Vector::From(0, 0, 1); while(l.n > 0) { @@ -45,34 +47,36 @@ void SPolygon::UvTriangulateInto(SMesh *m) { if(top->ContainsPointProjdToNormal(normal, sc->l.elem[0].p)) { sc->tag = 2; sc->MakeEdgesInto(&el); + sc->FindPointWithMinX(); } } - bool holesExist, mergedHole; - do { - holesExist = false; - mergedHole = false; + dbp("finished finding holes: %d ms", GetMilliseconds() - in); + for(;;) { + double xmin = 1e10; + SContour *scmin = NULL; for(sc = l.First(); sc; sc = l.NextAfter(sc)) { if(sc->tag != 2) continue; - holesExist = true; - if(merged.BridgeToContour(sc, &el, &vl)) { - // Merged it in, so we're done with it. - sc->tag = 3; - mergedHole = true; - } else { - // Looks like we can't merge it yet, but that can happen; - // we may have to merge the holes in a specific order. + if(sc->xminPt.x < xmin) { + xmin = sc->xminPt.x; + scmin = sc; } } - if(holesExist && !mergedHole) { - dbp("holes exist, yet couldn't merge one"); + if(!scmin) break; + + if(!merged.BridgeToContour(scmin, &el, &vl)) { + dbp("couldn't merge our hole"); return; } - } while(holesExist); + dbp(" bridged to contour: %d ms", GetMilliseconds() - in); + scmin->tag = 3; + } + dbp("finished merging holes: %d ms", GetMilliseconds() - in); merged.UvTriangulateInto(m); + dbp("finished ear clippping: %d ms", GetMilliseconds() - in); merged.l.Clear(); el.Clear(); vl.Clear(); @@ -84,10 +88,35 @@ void SPolygon::UvTriangulateInto(SMesh *m) { bool SContour::BridgeToContour(SContour *sc, SEdgeList *avoidEdges, List *avoidPts) { + int i, j; + + // Start looking for a bridge on our new hole near its leftmost (min x) + // point. + int sco = 0; + for(i = 0; i < (sc->l.n - 1); i++) { + if((sc->l.elem[i].p).EqualsExactly(sc->xminPt)) { + sco = i; + } + } + + // And start looking on our merged contour at whichever point is nearest + // to the leftmost point of the new segment. + int thiso = 0; + double dmin = 1e10; + for(i = 0; i < l.n; i++) { + Vector p = l.elem[i].p; + double d = (p.Minus(sc->xminPt)).MagSquared(); + if(d < dmin) { + dmin = d; + thiso = i; + } + } + int thisp, scp; Vector a, b, *f; - for(thisp = 0; thisp < l.n; thisp++) { + for(i = 0; i < l.n; i++) { + thisp = WRAP(i+thiso, l.n); a = l.elem[thisp].p; for(f = avoidPts->First(); f; f = avoidPts->NextAfter(f)) { @@ -95,7 +124,8 @@ bool SContour::BridgeToContour(SContour *sc, } if(f) continue; - for(scp = 0; scp < (sc->l.n - 1); scp++) { + for(j = 0; j < (sc->l.n - 1); j++) { + scp = WRAP(j+sco, (sc->l.n - 1)); b = sc->l.elem[scp].p; for(f = avoidPts->First(); f; f = avoidPts->NextAfter(f)) { @@ -116,7 +146,6 @@ bool SContour::BridgeToContour(SContour *sc, haveEdge: SContour merged; ZERO(&merged); - int i, j; for(i = 0; i < l.n; i++) { merged.AddPoint(l.elem[i].p); if(i == thisp) { @@ -187,7 +216,7 @@ bool SContour::IsEar(int bp) { void SContour::ClipEarInto(SMesh *m, int bp) { int ap = WRAP(bp-1, l.n), cp = WRAP(bp+1, l.n); - + STriangle tr; ZERO(&tr); tr.a = l.elem[ap].p; @@ -207,26 +236,36 @@ void SContour::ClipEarInto(SMesh *m, int bp) { void SContour::UvTriangulateInto(SMesh *m) { int i; + // First, calculate the ear-ness of all the points for(i = 0; i < l.n; i++) { (l.elem[i]).ear = IsEar(i) ? SPoint::EAR : SPoint::NOT_EAR; } + bool toggle = false; while(l.n > 3) { + // Some points may have changed ear-ness, so recalculate for(i = 0; i < l.n; i++) { if(l.elem[i].ear == SPoint::UNKNOWN) { (l.elem[i]).ear = IsEar(i) ? SPoint::EAR : SPoint::NOT_EAR; } } + + // And find a candidate ear; alternate the starting position so + // we generate strip-like triangulations instead of fan-like + int ear = -1; + toggle = !toggle; + int offset = toggle ? -1 : 0; for(i = 0; i < l.n; i++) { - if(l.elem[i].ear == SPoint::EAR) { + ear = WRAP(i+offset, l.n); + if(l.elem[ear].ear == SPoint::EAR) { break; } } - if(i >= l.n) { + if(ear < 0) { dbp("couldn't find an ear! fail"); return; } - ClipEarInto(m, i); + ClipEarInto(m, ear); } ClipEarInto(m, 0); // add the last triangle