diff --git a/drawentity.cpp b/drawentity.cpp index 7828c94..5000c72 100644 --- a/drawentity.cpp +++ b/drawentity.cpp @@ -192,6 +192,12 @@ void Entity::GeneratePolyCurves(SPolyCurveList *spcl) { double r = CircleGetRadiusNum(); double thetaa, thetab, dtheta; + if(r < LENGTH_EPS) { + // If a circle or an arc gets dragged through zero radius, + // then we just don't generate anything. + break; + } + if(type == CIRCLE) { thetaa = 0; thetab = 2*PI; diff --git a/dsc.h b/dsc.h index 0c80980..54b9229 100644 --- a/dsc.h +++ b/dsc.h @@ -134,6 +134,13 @@ public: n = dest; // and elemsAllocated is untouched, because we didn't resize } + + void Reverse(void) { + int i; + for(i = 0; i < (n/2); i++) { + SWAP(T, elem[i], elem[(n-1)-i]); + } + } }; // A list, where each element has an integer identifier. The list is kept diff --git a/generate.cpp b/generate.cpp index e8d8b9f..fedb847 100644 --- a/generate.cpp +++ b/generate.cpp @@ -222,7 +222,7 @@ void SolveSpace::GenerateAll(int first, int last, bool andFindFree) { // The group falls inside the range, so really solve it, // and then regenerate the mesh based on the solved stuff. SolveGroup(g->h, andFindFree); - g->GeneratePolygon(); + g->GenerateLoops(); g->GenerateMesh(); g->clean = true; } else { diff --git a/groupmesh.cpp b/groupmesh.cpp index 94c555b..86bdb23 100644 --- a/groupmesh.cpp +++ b/groupmesh.cpp @@ -2,112 +2,50 @@ #define gs (SS.GW.gs) -bool Group::AssemblePolygon(SPolygon *p, SEdge *error) { - SEdgeList edges; ZERO(&edges); +bool Group::AssembleLoops(void) { + SPolyCurveList spcl; + ZERO(&spcl); + int i; for(i = 0; i < SS.entity.n; i++) { Entity *e = &(SS.entity.elem[i]); if(e->group.v != h.v) continue; + if(e->construction) continue; - e->GenerateEdges(&edges); + e->GeneratePolyCurves(&spcl); } - bool ret = edges.AssemblePolygon(p, error); - edges.Clear(); - return ret; + + bool allClosed; + curveLoops = SPolyCurveLoops::From(&spcl, &poly, + &allClosed, &(polyError.notClosedAt)); + spcl.Clear(); + return allClosed; } -void Group::GeneratePolygon(void) { +void Group::GenerateLoops(void) { poly.Clear(); + curveLoops.Clear(); if(type == DRAWING_3D || type == DRAWING_WORKPLANE || type == ROTATE || type == TRANSLATE || type == IMPORTED) { - if(AssemblePolygon(&poly, &(polyError.notClosedAt))) { + if(AssembleLoops()) { polyError.how = POLY_GOOD; - poly.normal = poly.ComputeNormal(); - poly.FixContourDirections(); if(!poly.AllPointsInPlane(&(polyError.notCoplanarAt))) { // The edges aren't all coplanar; so not a good polygon polyError.how = POLY_NOT_COPLANAR; poly.Clear(); + curveLoops.Clear(); } } else { polyError.how = POLY_NOT_CLOSED; poly.Clear(); + curveLoops.Clear(); } } } -void Group::GetTrajectory(hGroup hg, SContour *traj, SPolygon *section) { - if(section->IsEmpty()) return; - - SEdgeList edges; ZERO(&edges); - int i, j; - - for(i = 0; i < SS.entity.n; i++) { - Entity *e = &(SS.entity.elem[i]); - if(e->group.v != hg.v) continue; - e->GenerateEdges(&edges); - } - - Vector pn = (section->normal).WithMagnitude(1); - double pd = pn.Dot(section->AnyPoint()); - - // Find the start of the trajectory - Vector first, last; - for(i = 0; i < edges.l.n; i++) { - SEdge *se = &(edges.l.elem[i]); - - bool startA = true, startB = true; - for(j = 0; j < edges.l.n; j++) { - if(i == j) continue; - SEdge *set = &(edges.l.elem[j]); - if((set->a).Equals(se->a)) startA = false; - if((set->b).Equals(se->a)) startA = false; - if((set->a).Equals(se->b)) startB = false; - if((set->b).Equals(se->b)) startB = false; - } - if(startA || startB) { - // It's possible for both to be true, if only one segment exists - if(startA) { - first = se->a; - last = se->b; - } else { - first = se->b; - last = se->a; - } - se->tag = 1; - break; - } - } - if(i >= edges.l.n) goto cleanup; - edges.AssembleContour(first, last, traj, NULL); - if(traj->l.n < 1) goto cleanup; - - // Starting and ending points of the trajectory - Vector ps, pf; - ps = traj->l.elem[0].p; - pf = traj->l.elem[traj->l.n - 1].p; - // Distances of those points to the section plane - double ds = fabs(pn.Dot(ps) - pd), df = fabs(pn.Dot(pf) - pd); - if(ds < LENGTH_EPS && df < LENGTH_EPS) { - if(section->WindingNumberForPoint(pf) > 0) { - // Both the start and finish lie on the section plane; let the - // start be the one that's somewhere within the section. Use - // winding > 0, not odd/even, since it's natural e.g. to sweep - // a ring to make a pipe, and draw the trajectory through the - // center of the ring. - traj->Reverse(); - } - } else if(ds > df) { - // The starting point is the endpoint that's closer to the plane - traj->Reverse(); - } -cleanup: - edges.Clear(); -} - void Group::AddQuadWithNormal(STriMeta meta, Vector out, Vector a, Vector b, Vector c, Vector d) { diff --git a/polygon.cpp b/polygon.cpp index c12e25c..b7cbd6e 100644 --- a/polygon.cpp +++ b/polygon.cpp @@ -181,7 +181,6 @@ bool SContour::IsClockwiseProjdToNormal(Vector n) { area += ((v0 + v1)/2)*(u1 - u0); } - return (area < 0); } @@ -224,13 +223,7 @@ bool SContour::AllPointsInPlane(Vector n, double d, Vector *notCoplanarAt) { } void SContour::Reverse(void) { - int i; - for(i = 0; i < (l.n / 2); i++) { - int i2 = (l.n - 1) - i; - SPoint t = l.elem[i2]; - l.elem[i2] = l.elem[i]; - l.elem[i] = t; - } + l.Reverse(); } @@ -277,6 +270,9 @@ int SPolygon::WindingNumberForPoint(Vector p) { } void SPolygon::FixContourDirections(void) { + // At output, the contour's tag will be 1 if we reversed it, else 0. + l.ClearTags(); + // Outside curve looks counterclockwise, projected against our normal. int i, j; for(i = 0; i < l.n; i++) { @@ -296,6 +292,7 @@ void SPolygon::FixContourDirections(void) { bool clockwise = sc->IsClockwiseProjdToNormal(normal); if(clockwise && outer || (!clockwise && !outer)) { sc->Reverse(); + sc->tag = 1; } } } diff --git a/polygon.h b/polygon.h index cfbd5ac..71b201a 100644 --- a/polygon.h +++ b/polygon.h @@ -34,6 +34,7 @@ public: class SContour { public: + int tag; List l; void AddPoint(Vector p); diff --git a/sketch.h b/sketch.h index c6db90a..39eec4a 100644 --- a/sketch.h +++ b/sketch.h @@ -136,7 +136,8 @@ public: bool negateV; } predef; - SPolygon poly; + SPolygon poly; + SPolyCurveLoops curveLoops; static const int POLY_GOOD = 0; static const int POLY_NOT_CLOSED = 1; static const int POLY_NOT_COPLANAR = 2; @@ -198,12 +199,12 @@ public: void AddEq(IdList *l, Expr *expr, int index); void GenerateEquations(IdList *l); - // Assembling piecewise linear sections into polygons - bool AssemblePolygon(SPolygon *p, SEdge *error); - void GeneratePolygon(void); + // Assembling the curves into loops, and into a piecewise linear polygon + // at the same time. + bool AssembleLoops(void); + void GenerateLoops(void); // And the mesh stuff SMesh *PreviousGroupMesh(void); - void GetTrajectory(hGroup hg, SContour *traj, SPolygon *section); void AddQuadWithNormal(STriMeta meta, Vector out, Vector a, Vector b, Vector c, Vector d); void GenerateMeshForStepAndRepeat(void); diff --git a/srf/ratpoly.cpp b/srf/ratpoly.cpp index c716970..d457e2a 100644 --- a/srf/ratpoly.cpp +++ b/srf/ratpoly.cpp @@ -77,7 +77,7 @@ Vector SPolyCurve::Finish(void) { return ctrl[deg]; } -Vector SPolyCurve::EvalAt(double t) { +Vector SPolyCurve::PointAt(double t) { Vector pt = Vector::From(0, 0, 0); double d = 0; @@ -97,15 +97,15 @@ void SPolyCurve::MakePwlInto(List *l) { } void SPolyCurve::MakePwlWorker(List *l, double ta, double tb) { - Vector pa = EvalAt(ta); - Vector pb = EvalAt(tb); + Vector pa = PointAt(ta); + Vector pb = PointAt(tb); // Can't test in the middle, or certain cubics would break. double tm1 = (2*ta + tb) / 3; double tm2 = (ta + 2*tb) / 3; - Vector pm1 = EvalAt(tm1); - Vector pm2 = EvalAt(tm2); + Vector pm1 = PointAt(tm1); + Vector pm2 = PointAt(tm2); double d = max(pm1.DistanceToLine(pa, pb.Minus(pa)), pm2.DistanceToLine(pa, pb.Minus(pa))); @@ -135,7 +135,8 @@ void SPolyCurveList::Clear(void) { l.Clear(); } -SPolyCurveLoop SPolyCurveLoop::FromCurves(SPolyCurveList *spcl, bool *notClosed) +SPolyCurveLoop SPolyCurveLoop::FromCurves(SPolyCurveList *spcl, + bool *allClosed, SEdge *errorAt) { SPolyCurveLoop loop; ZERO(&loop); @@ -153,35 +154,114 @@ SPolyCurveLoop SPolyCurveLoop::FromCurves(SPolyCurveList *spcl, bool *notClosed) while(spcl->l.n > 0 && !hanging.Equals(start)) { int i; + bool foundNext = false; for(i = 0; i < spcl->l.n; i++) { SPolyCurve *test = &(spcl->l.elem[i]); if((test->Finish()).Equals(hanging)) { test->Reverse(); + // and let the next test catch it } if((test->Start()).Equals(hanging)) { test->tag = 1; loop.l.Add(test); hanging = test->Finish(); spcl->l.RemoveTagged(); + foundNext = true; break; } } - if(i >= spcl->l.n) { - // Didn't find the next curve in the loop - *notClosed = true; + if(!foundNext) { + // The loop completed without finding the hanging edge, so + // it's an open loop + errorAt->a = hanging; + errorAt->b = start; + *allClosed = false; return loop; } } if(hanging.Equals(start)) { - *notClosed = false; - } else { - *notClosed = true; + *allClosed = true; + } else { + // We ran out of edges without forming a closed loop. + errorAt->a = hanging; + errorAt->b = start; + *allClosed = false; } return loop; } +void SPolyCurveLoop::Reverse(void) { + l.Reverse(); +} + +void SPolyCurveLoop::MakePwlInto(SContour *sc) { + List lv; + ZERO(&lv); + + int i, j; + for(i = 0; i < l.n; i++) { + SPolyCurve *spc = &(l.elem[i]); + spc->MakePwlInto(&lv); + + // Each curve's piecewise linearization includes its endpoints, + // which we don't want to duplicate (creating zero-len edges). + for(j = (i == 0 ? 0 : 1); j < lv.n; j++) { + sc->AddPoint(lv.elem[j]); + } + lv.Clear(); + } + // Ensure that it's exactly closed, not just within a numerical tolerance. + sc->l.elem[sc->l.n - 1] = sc->l.elem[0]; +} + +SPolyCurveLoops SPolyCurveLoops::From(SPolyCurveList *spcl, SPolygon *poly, + bool *allClosed, SEdge *errorAt) +{ + int i; + SPolyCurveLoops ret; + ZERO(&ret); + + while(spcl->l.n > 0) { + bool thisClosed; + SPolyCurveLoop loop; + loop = SPolyCurveLoop::FromCurves(spcl, &thisClosed, errorAt); + if(!thisClosed) { + ret.Clear(); + *allClosed = false; + return ret; + } + + ret.l.Add(&loop); + poly->AddEmptyContour(); + loop.MakePwlInto(&(poly->l.elem[poly->l.n-1])); + } + + poly->normal = poly->ComputeNormal(); + ret.normal = poly->normal; + poly->FixContourDirections(); + + for(i = 0; i < poly->l.n; i++) { + if(poly->l.elem[i].tag) { + // We had to reverse this contour in order to fix the poly + // contour directions; so need to do the same with the curves. + ret.l.elem[i].Reverse(); + } + } + + *allClosed = true; + return ret; +} + +void SPolyCurveLoops::Clear(void) { + int i; + for(i = 0; i < l.n; i++) { + (l.elem[i]).Clear(); + } + l.Clear(); +} + SSurface SSurface::FromExtrusionOf(SPolyCurve *spc, Vector t0, Vector t1) { SSurface ret; ZERO(&ret); @@ -205,11 +285,10 @@ SShell SShell::FromExtrusionOf(SPolyCurveList *spcl, Vector t0, Vector t1) { SShell ret; ZERO(&ret); - // Find the plane that contains our input section. + // Group the input curves into loops, not necessarily in the right order. - // Group the input curves into loops; this will reverse some of the - // curves if necessary for consistent (but not necessarily correct yet) - // direction. + + // Find the plane that contains our input section. // Generate a polygon from the curves, and use this to test how many // times each loop is enclosed. Then set the direction (cw/ccw) to diff --git a/srf/surface.h b/srf/surface.h index 8cf55be..6cf59cb 100644 --- a/srf/surface.h +++ b/srf/surface.h @@ -2,8 +2,9 @@ #ifndef __SURFACE_H #define __SURFACE_H -// Utility function +// Utility functions, Bernstein polynomials of order 1-3 and their derivatives. double Bernstein(int k, int deg, double t); +double BernsteinDerivative(int k, int deg, double t); class hSSurface { public: @@ -24,7 +25,7 @@ public: Vector ctrl[4]; double weight[4]; - Vector EvalAt(double t); + Vector PointAt(double t); Vector Start(void); Vector Finish(void); void MakePwlInto(List *l); @@ -48,11 +49,24 @@ class SPolyCurveLoop { public: List l; - bool IsClockwiseProjdToNormal(Vector n); + inline void Clear(void) { l.Clear(); } + void Reverse(void); + void MakePwlInto(SContour *sc); - static SPolyCurveLoop FromCurves(SPolyCurveList *spcl, bool *notClosed); + static SPolyCurveLoop FromCurves(SPolyCurveList *spcl, + bool *allClosed, SEdge *errorAt); }; +class SPolyCurveLoops { +public: + List l; + Vector normal; + + static SPolyCurveLoops From(SPolyCurveList *spcl, SPolygon *poly, + bool *allClosed, SEdge *errorAt); + + void Clear(void); +}; // Stuff for the surface trim curves: piecewise linear class SCurve { @@ -84,12 +98,17 @@ public: int degm, degn; Vector ctrl[4][4]; double weight[4][4]; - Vector out00; // outer normal at ctrl[0][0] List trim; static SSurface FromExtrusionOf(SPolyCurve *spc, Vector t0, Vector t1); + void ClosestPointTo(Vector p, double *u, double *v); + Vector PointAt(double u, double v); + Vector TangentWrtUAt(double u, double v); + Vector TangentWrtVAt(double u, double v); + Vector NormalAt(double u, double v); + void TriangulateInto(SMesh *sm); }; diff --git a/undoredo.cpp b/undoredo.cpp index 00928cc..67bab9a 100644 --- a/undoredo.cpp +++ b/undoredo.cpp @@ -49,6 +49,7 @@ void SolveSpace::PushFromCurrentOnto(UndoStack *uk) { dest.vvMeshClean = false; ZERO(&(dest.solved)); ZERO(&(dest.poly)); + ZERO(&(dest.curveLoops)); ZERO(&(dest.polyError)); ZERO(&(dest.thisMesh)); ZERO(&(dest.runningMesh)); @@ -91,6 +92,7 @@ void SolveSpace::PopOntoCurrentFrom(UndoStack *uk) { for(i = 0; i < group.n; i++) { Group *g = &(group.elem[i]); g->poly.Clear(); + g->curveLoops.Clear(); g->thisMesh.Clear(); g->runningMesh.Clear(); g->meshError.interferesAt.Clear();