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