From d6d198ee4017a887fa3bbe127a842efdfe83353a Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Fri, 8 May 2009 00:33:04 -0800 Subject: [PATCH] Add triangulation of surfaces with compound curvature; I just build a grid of quads, with adaptive spacing. The quads that lie entirely within the trim polygon are triangulated and knocked out from the polygon, and then the polygon is triangulated. That works okay, though rather slow. But there are issues with surfaces of revolution that touch the axis, since they end up with a singularity. That will require some thought. [git-p4: depot-paths = "//depot/solvespace/": change = 1951] --- polygon.cpp | 5 +- polygon.h | 1 + solvespace.cpp | 4 +- srf/surface.cpp | 18 ++++- srf/surface.h | 3 + srf/triangulate.cpp | 156 ++++++++++++++++++++++++++++++++++++++++++++ wishlist.txt | 1 - 7 files changed, 181 insertions(+), 7 deletions(-) diff --git a/polygon.cpp b/polygon.cpp index 7e3e031..b727cc9 100644 --- a/polygon.cpp +++ b/polygon.cpp @@ -170,7 +170,10 @@ int SEdgeList::AnyEdgeCrossings(Vector a, Vector b, Vector *ppi) { dist_a = (se->a).DistanceToLine(a, d), dist_b = (se->b).DistanceToLine(a, d); - if(fabs(dist_a - dist_b) < LENGTH_EPS) { + // Can't just test if dist_a equals dist_b; they could be on opposite + // sides, since it's unsigned. + double m = sqrt(d.Magnitude()*dse.Magnitude()); + if(sqrt(fabs(d.Dot(dse))) > (m - LENGTH_EPS)) { // The edges are parallel. if(fabs(dist_a) > LENGTH_EPS) { // and not coincident, so can't be interesecting diff --git a/polygon.h b/polygon.h index caccb91..60d8c51 100644 --- a/polygon.h +++ b/polygon.h @@ -91,6 +91,7 @@ public: Vector AnyPoint(void); void OffsetInto(SPolygon *dest, double r); void UvTriangulateInto(SMesh *m, SSurface *srf); + void UvGridTriangulateInto(SMesh *m, SSurface *srf); }; class STriangle { diff --git a/solvespace.cpp b/solvespace.cpp index 6cd8098..eb6677c 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -40,9 +40,9 @@ void SolveSpace::Init(char *cmdLine) { lightDir[1].y = CnfThawFloat( 0.0f, "LightDir_1_Up" ); lightDir[1].z = CnfThawFloat( 0.0f, "LightDir_1_Forward" ); // Chord tolerance - chordTol = CnfThawFloat(2.0f, "ChordTolerance"); + chordTol = CnfThawFloat(3.0f, "ChordTolerance"); // Max pwl segments to generate - maxSegments = CnfThawDWORD(40, "MaxSegments"); + maxSegments = CnfThawDWORD(10, "MaxSegments"); // View units viewUnits = (Unit)CnfThawDWORD((DWORD)UNIT_MM, "ViewUnits"); // Camera tangent (determines perspective) diff --git a/srf/surface.cpp b/srf/surface.cpp index eff0b6f..320a91b 100644 --- a/srf/surface.cpp +++ b/srf/surface.cpp @@ -299,9 +299,21 @@ void SSurface::TriangulateInto(SShell *shell, SMesh *sm) { ZERO(&poly); if(el.AssemblePolygon(&poly, NULL, true)) { int i, start = sm->l.n; - // Curved surfaces are triangulated in such a way as to minimize - // deviation between edges and surface; but doesn't matter for planes. - poly.UvTriangulateInto(sm, (degm == 1 && degn == 1) ? NULL : this); + if(degm == 1 && degn == 1) { + // A plane; triangulate any old way + poly.UvTriangulateInto(sm, NULL); + } else if(degm == 1 || degn == 1) { + // A surface with curvature along one direction only; so + // choose the triangulation with chords that lie as much + // as possible within the surface. And since the trim curves + // have been pwl'd to within the desired chord tol, that will + // produce a surface good to within roughly that tol. + poly.UvTriangulateInto(sm, this); + } else { + // A surface with compound curvature. So we must overlay a + // two-dimensional grid, and triangulate around that. + poly.UvGridTriangulateInto(sm, this); + } STriMeta meta = { face, color }; for(i = start; i < sm->l.n; i++) { diff --git a/srf/surface.h b/srf/surface.h index 332f246..0dfdf5d 100644 --- a/srf/surface.h +++ b/srf/surface.h @@ -255,6 +255,9 @@ public: void MakeSectionEdgesInto(SShell *shell, SEdgeList *sel, SBezierList *sbl); void MakeClassifyingBsp(SShell *shell); double ChordToleranceForEdge(Vector a, Vector b); + void MakeTriangulationGridInto(List *l, double vs, double vf, + bool swapped); + Vector PointAtMaybeSwapped(double u, double v, bool swapped); void Reverse(void); void Clear(void); diff --git a/srf/triangulate.cpp b/srf/triangulate.cpp index b942e00..016cc52 100644 --- a/srf/triangulate.cpp +++ b/srf/triangulate.cpp @@ -120,6 +120,29 @@ bool SContour::BridgeToContour(SContour *sc, int thisp, scp; Vector a, b, *f; + + // First check if the contours share a point; in that case we should + // merge them there, without a bridge. + 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)) { + if(f->Equals(a)) break; + } + if(f) continue; + + for(j = 0; j < (sc->l.n - 1); j++) { + scp = WRAP(j+sco, (sc->l.n - 1)); + b = sc->l.elem[scp].p; + + if(a.Equals(b)) { + goto haveEdge; + } + } + } + + // If that fails, look for a bridge that does not intersect any edges. for(i = 0; i < l.n; i++) { thisp = WRAP(i+thiso, l.n); a = l.elem[thisp].p; @@ -145,6 +168,7 @@ bool SContour::BridgeToContour(SContour *sc, } } } + // Tried all the possibilities, didn't find an edge return false; @@ -329,4 +353,136 @@ double SSurface::ChordToleranceForEdge(Vector a, Vector b) { return sqrt(worst); } +Vector SSurface::PointAtMaybeSwapped(double u, double v, bool swapped) { + if(swapped) { + return PointAt(v, u); + } else { + return PointAt(u, v); + } +} + +void SSurface::MakeTriangulationGridInto(List *l, double vs, double vf, + bool swapped) +{ + double worst = 0; + + // Try piecewise linearizing four curves, at u = 0, 1/3, 2/3, 1; choose + // the worst chord tolerance of any of those. + int i; + for(i = 0; i <= 3; i++) { + double u = i/3.0; + + // This chord test should be identical to the one in SBezier::MakePwl + // to make the piecewise linear edges line up with the grid more or + // less. + Vector ps = PointAtMaybeSwapped(u, vs, swapped), + pf = PointAtMaybeSwapped(u, vf, swapped); + + double vm1 = (2*vs + vf) / 3, + vm2 = (vs + 2*vf) / 3; + + Vector pm1 = PointAtMaybeSwapped(u, vm1, swapped), + pm2 = PointAtMaybeSwapped(u, vm2, swapped); + + worst = max(worst, pm1.DistanceToLine(ps, pf.Minus(ps))); + worst = max(worst, pm2.DistanceToLine(ps, pf.Minus(ps))); + } + + double step = 1.0/SS.maxSegments; + if((vf - vs) < step || worst < SS.ChordTolMm()) { + l->Add(&vf); + } else { + MakeTriangulationGridInto(l, vs, (vs+vf)/2, swapped); + MakeTriangulationGridInto(l, (vs+vf)/2, vf, swapped); + } +} + +void SPolygon::UvGridTriangulateInto(SMesh *mesh, SSurface *srf) { + SEdgeList orig; + ZERO(&orig); + MakeEdgesInto(&orig); + + SEdgeList holes; + ZERO(&holes); + + normal = Vector::From(0, 0, 1); + FixContourDirections(); + + // Build a rectangular grid, with horizontal and vertical lines in the + // uv plane. The spacing of these lines is adaptive, so calculate that. + List li, lj; + ZERO(&li); + ZERO(&lj); + double v = 0; + li.Add(&v); + srf->MakeTriangulationGridInto(&li, 0, 1, true); + lj.Add(&v); + srf->MakeTriangulationGridInto(&lj, 0, 1, false); + + // Now iterate over each quad in the grid. If it's outside the polygon, + // or if it intersects the polygon, then we discard it. Otherwise we + // generate two triangles in the mesh, and cut it out of our polygon. + int i, j; + for(i = 0; i < (li.n - 1); i++) { + for(j = 0; j < (lj.n - 1); j++) { + double us = li.elem[i], uf = li.elem[i+1], + vs = lj.elem[j], vf = lj.elem[j+1]; + + Vector a = Vector::From(us, vs, 0), + b = Vector::From(us, vf, 0), + c = Vector::From(uf, vf, 0), + d = Vector::From(uf, vs, 0); + + if(orig.AnyEdgeCrossings(a, b, NULL) || + orig.AnyEdgeCrossings(b, c, NULL) || + orig.AnyEdgeCrossings(c, d, NULL) || + orig.AnyEdgeCrossings(d, a, NULL)) + { + continue; + } + + // There's no intersections, so it doesn't matter which point + // we decide to test. + if(!this->ContainsPoint(a)) { + continue; + } + + // Add the quad to our mesh + STriangle tr; + ZERO(&tr); + tr.a = a; + tr.b = b; + tr.c = c; + mesh->AddTriangle(&tr); + tr.a = a; + tr.b = c; + tr.c = d; + mesh->AddTriangle(&tr); + + holes.AddEdge(a, b); + holes.AddEdge(b, c); + holes.AddEdge(c, d); + holes.AddEdge(d, a); + } + } + + holes.CullExtraneousEdges(); + SPolygon hp; + ZERO(&hp); + holes.AssemblePolygon(&hp, NULL, true); + + SContour *sc; + for(sc = hp.l.First(); sc; sc = hp.l.NextAfter(sc)) { + l.Add(sc); + } + + orig.Clear(); + holes.Clear(); + li.Clear(); + lj.Clear(); + hp.l.Clear(); + + UvTriangulateInto(mesh, srf); +} + diff --git a/wishlist.txt b/wishlist.txt index 0de6bda..1913e56 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,6 +1,5 @@ marching algorithm for surface intersection -surfaces of revolution (lathed) boundary avoidance when casting ray for point-in-shell tangent intersections short pwl edge avoidance