diff --git a/draw.cpp b/draw.cpp index 077c224..f14b69b 100644 --- a/draw.cpp +++ b/draw.cpp @@ -1023,14 +1023,8 @@ void GraphicsWindow::Paint(int w, int h) { // And the naked edges, if the user did Analyze -> Show Naked Edges. glLineWidth(7); - glEnable(GL_LINE_STIPPLE); - glLineStipple(1, 0x5555); glColor3d(1, 0, 0); glxDrawEdges(&(SS.nakedEdges)); - glLineStipple(1, 0xaaaa); - glColor3d(0, 0, 0); - glxDrawEdges(&(SS.nakedEdges)); - glDisable(GL_LINE_STIPPLE); // Then redraw whatever the mouse is hovering over, highlighted. glDisable(GL_DEPTH_TEST); diff --git a/dsc.h b/dsc.h index e138f7c..68dde2d 100644 --- a/dsc.h +++ b/dsc.h @@ -15,6 +15,8 @@ public: // a + (vx)*i + (vy)*j + (vz)*k double w, vx, vy, vz; + static const Quaternion IDENTITY; + static Quaternion From(double w, double vx, double vy, double vz); static Quaternion From(hParam w, hParam vx, hParam vy, hParam vz); static Quaternion From(Vector u, Vector v); @@ -54,8 +56,7 @@ public: bool *parallel); static Vector AtIntersectionOfPlanes(Vector na, double da, Vector nb, double db, - Vector nc, double dc, - bool *parallel); + Vector nc, double dc, bool *parallel); double Element(int i); bool Equals(Vector v, double tol=LENGTH_EPS); diff --git a/graphicswin.cpp b/graphicswin.cpp index a3f9e47..a503585 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -307,8 +307,8 @@ void GraphicsWindow::ZoomToFit(void) { if(dy != 0) scaley = 0.9*height/dy; scale = min(scalex, scaley); - scale = min(100, scale); - scale = max(0.001, scale); + scale = min(300, scale); + scale = max(0.003, scale); } // Then do another run, considering the perspective. diff --git a/groupmesh.cpp b/groupmesh.cpp index 97b0e40..440ad80 100644 --- a/groupmesh.cpp +++ b/groupmesh.cpp @@ -55,6 +55,12 @@ void Group::GenerateShellForStepAndRepeat(void) { Group *src = SS.GetGroup(opA); SShell *srcs = &(src->thisShell); // the shell to step and repeat + SShell workA, workB; + ZERO(&workA); + ZERO(&workB); + SShell *soFar = &workA, *scratch = &workB; + soFar->MakeFromCopyOf(src->PreviousGroupShell()); + int n = (int)valA, a0 = 0; if(subtype == ONE_SIDED && skipFirst) { a0++; n++; @@ -64,25 +70,37 @@ void Group::GenerateShellForStepAndRepeat(void) { int ap = a*2 - (subtype == ONE_SIDED ? 0 : (n-1)); int remap = (a == (n - 1)) ? REMAP_LAST : a; + SShell transd; + ZERO(&transd); if(type == TRANSLATE) { Vector trans = Vector::From(h.param(0), h.param(1), h.param(2)); trans = trans.ScaledBy(ap); - + Quaternion q = Quaternion::From(1, 0, 0, 0); + transd.MakeFromTransformationOf(srcs, trans, q); } else { Vector trans = Vector::From(h.param(0), h.param(1), h.param(2)); double theta = ap * SS.GetParam(h.param(3))->val; double c = cos(theta), s = sin(theta); Vector axis = Vector::From(h.param(4), h.param(5), h.param(6)); Quaternion q = Quaternion::From(c, s*axis.x, s*axis.y, s*axis.z); - + // Rotation is centered at t; so A(x - t) + t = Ax + (t - At) + transd.MakeFromTransformationOf(srcs, + trans.Minus(q.Rotate(trans)), q); } if(src->meshCombine == COMBINE_AS_DIFFERENCE) { - + scratch->MakeFromDifferenceOf(soFar, &transd); } else { - + scratch->MakeFromUnionOf(soFar, &transd); } + SWAP(SShell *, scratch, soFar); + + scratch->Clear(); + transd.Clear(); } + + runningShell.Clear(); + runningShell = *soFar; } void Group::GenerateShellAndMesh(void) { @@ -105,6 +123,55 @@ void Group::GenerateShellAndMesh(void) { } thisShell.MakeFromExtrusionOf(&(src->bezierLoopSet), tbot, ttop, color); + Vector onOrig = src->bezierLoopSet.point; + // And for any plane faces, annotate the model with the entity for + // that face, so that the user can select them with the mouse. + int i; + for(i = 0; i < thisShell.surface.n; i++) { + SSurface *ss = &(thisShell.surface.elem[i]); + hEntity face = Entity::NO_ENTITY; + + Vector p = ss->PointAt(0, 0), + n = ss->NormalAt(0, 0).WithMagnitude(1); + double d = n.Dot(p); + + if(i == 0 || i == 1) { + // These are the top and bottom of the shell. + if(fabs((onOrig.Plus(ttop)).Dot(n) - d) < LENGTH_EPS) { + face = Remap(Entity::NO_ENTITY, REMAP_TOP); + ss->face = face.v; + } + if(fabs((onOrig.Plus(tbot)).Dot(n) - d) < LENGTH_EPS) { + face = Remap(Entity::NO_ENTITY, REMAP_BOTTOM); + ss->face = face.v; + } + continue; + } + + // So these are the sides + if(ss->degm != 1 || ss->degn != 1) continue; + + Entity *e; + for(e = SS.entity.First(); e; e = SS.entity.NextAfter(e)) { + if(e->group.v != opA.v) continue; + if(e->type != Entity::LINE_SEGMENT) continue; + + Vector a = SS.GetEntity(e->point[0])->PointGetNum(), + b = SS.GetEntity(e->point[1])->PointGetNum(); + a = a.Plus(ttop); + b = b.Plus(ttop); + // Could get taken backwards, so check all cases. + if((a.Equals(ss->ctrl[0][0]) && b.Equals(ss->ctrl[1][0])) || + (b.Equals(ss->ctrl[0][0]) && a.Equals(ss->ctrl[1][0])) || + (a.Equals(ss->ctrl[0][1]) && b.Equals(ss->ctrl[1][1])) || + (b.Equals(ss->ctrl[0][1]) && a.Equals(ss->ctrl[1][1]))) + { + face = Remap(e->h, REMAP_LINE_TO_FACE); + ss->face = face.v; + break; + } + } + } } else if(type == LATHE) { Group *src = SS.GetGroup(opA); @@ -138,7 +205,6 @@ void Group::GenerateShellAndMesh(void) { } } - runningMesh.Clear(); runningShell.Clear(); // If this group contributes no new mesh, then our running mesh is the @@ -174,6 +240,7 @@ void Group::GenerateShellAndMesh(void) { } done: + runningMesh.Clear(); runningShell.TriangulateInto(&runningMesh); emphEdges.Clear(); if(h.v == SS.GW.activeGroup.v && SS.edgeColor != 0) { diff --git a/polygon.cpp b/polygon.cpp index cb7c18d..c74b783 100644 --- a/polygon.cpp +++ b/polygon.cpp @@ -222,6 +222,31 @@ intersects: return cnt; } +//----------------------------------------------------------------------------- +// Remove unnecessary edges: if two are anti-parallel then remove both, and if +// two are parallel then remove one. +//----------------------------------------------------------------------------- +void SEdgeList::CullExtraneousEdges(void) { + l.ClearTags(); + int i, j; + for(i = 0; i < l.n; i++) { + SEdge *se = &(l.elem[i]); + for(j = i+1; j < l.n; j++) { + SEdge *set = &(l.elem[j]); + if((set->a).Equals(se->a) && (set->b).Equals(se->b)) { + // Two parallel edges exist; so keep only the first one. + set->tag = 1; + } + if((set->a).Equals(se->b) && (set->b).Equals(se->a)) { + // Two anti-parallel edges exist; so keep neither. + se->tag = 1; + set->tag = 1; + } + } + } + l.RemoveTagged(); +} + void SContour::AddPoint(Vector p) { SPoint sp; sp.tag = 0; diff --git a/polygon.h b/polygon.h index a4cd00c..bd22149 100644 --- a/polygon.h +++ b/polygon.h @@ -26,6 +26,7 @@ public: bool AssembleContour(Vector first, Vector last, SContour *dest, SEdge *errorAt, bool keepDir); int AnyEdgeCrossings(Vector a, Vector b, Vector *pi=NULL); + void CullExtraneousEdges(void); }; class SPoint { diff --git a/srf/boolean.cpp b/srf/boolean.cpp index 2cb4f7c..edde3d9 100644 --- a/srf/boolean.cpp +++ b/srf/boolean.cpp @@ -302,8 +302,12 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *agnst, SShell *parent, SEdgeList sameNormal, oppositeNormal; ZERO(&sameNormal); ZERO(&oppositeNormal); - agnst->MakeCoincidentEdgesInto(&ret, true, &sameNormal); - agnst->MakeCoincidentEdgesInto(&ret, false, &oppositeNormal); + agnst->MakeCoincidentEdgesInto(&ret, true, &sameNormal, into); + agnst->MakeCoincidentEdgesInto(&ret, false, &oppositeNormal, into); + // and cull parallel or anti-parallel pairs, which may occur if multiple + // surfaces are coincident with ours + sameNormal.CullExtraneousEdges(); + oppositeNormal.CullExtraneousEdges(); // and build the trees for quick in-polygon testing SBspUv *sameBsp = SBspUv::From(&sameNormal); SBspUv *oppositeBsp = SBspUv::From(&oppositeNormal); @@ -318,9 +322,9 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *agnst, SShell *parent, for(sc = into->curve.First(); sc; sc = into->curve.NextAfter(sc)) { if(sc->source != SCurve::FROM_INTERSECTION) continue; if(opA) { - if(sc->surfB.v != h.v || sc->surfA.v != ss->h.v) continue; - } else { if(sc->surfA.v != h.v || sc->surfB.v != ss->h.v) continue; + } else { + if(sc->surfB.v != h.v || sc->surfA.v != ss->h.v) continue; } int i; @@ -400,14 +404,9 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *agnst, SShell *parent, } else if(c_shell == SShell::EDGE_TANGENT) { continue; } - + if(KeepEdge(type, opA, tag)) { final.AddEdge(se->a, se->b, se->auxA, se->auxB); - } else { - if(I == 1) { - dbp("orig vs. shell: %d (l=%g)", - c_shell, ((se->b).Minus(se->a)).Magnitude()); - } } } @@ -436,33 +435,23 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *agnst, SShell *parent, } } - // Cull extraneous edges; duplicates or anti-parallel pairs - final.l.ClearTags(); - int i, j; - for(i = 0; i < final.l.n; i++) { - se = &(final.l.elem[i]); - for(j = i+1; j < final.l.n; j++) { - SEdge *set = &(final.l.elem[j]); - if((set->a).Equals(se->a) && (set->b).Equals(se->b)) { - // Two parallel edges exist; so keep only the first one. This - // can happen if our surface intersects the shell at an edge, - // so that we get two copies of the intersection edge. - set->tag = 1; - } - if((set->a).Equals(se->b) && (set->b).Equals(se->a)) { - // Two anti-parallel edges exist; so keep neither. - se->tag = 1; - set->tag = 1; - } - } - } - final.l.RemoveTagged(); - -// if(I == 0) DEBUGEDGELIST(&final, &ret); + // Cull extraneous edges; duplicates or anti-parallel pairs. In particular, + // we can get duplicate edges if our surface intersects the other shell + // at an edge, so that both surfaces intersect coincident (and both + // generate an intersection edge). + final.CullExtraneousEdges(); // Use our reassembled edges to trim the new surface. ret.TrimFromEdgeList(&final); + SPolygon poly; + ZERO(&poly); + final.l.ClearTags(); + if(!final.AssemblePolygon(&poly, NULL, true)) { + DEBUGEDGELIST(&inter, &ret); + } + poly.Clear(); + sameNormal.Clear(); oppositeNormal.Clear(); final.Clear(); @@ -478,20 +467,20 @@ void SShell::CopySurfacesTrimAgainst(SShell *against, SShell *into, for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) { SSurface ssn; ssn = ss->MakeCopyTrimAgainst(against, this, into, type, opA); - into->surface.AddAndAssignId(&ssn); + ss->newH = into->surface.AddAndAssignId(&ssn); I++; } } void SShell::MakeIntersectionCurvesAgainst(SShell *agnst, SShell *into) { SSurface *sa; - for(sa = agnst->surface.First(); sa; sa = agnst->surface.NextAfter(sa)) { + for(sa = surface.First(); sa; sa = surface.NextAfter(sa)) { SSurface *sb; - for(sb = surface.First(); sb; sb = surface.NextAfter(sb)) { + for(sb = agnst->surface.First(); sb; sb = agnst->surface.NextAfter(sb)){ // Intersect every surface from our shell against every surface // from agnst; this will add zero or more curves to the curve // list for into. - sa->IntersectAgainst(sb, agnst, this, into); + sa->IntersectAgainst(sb, this, agnst, into); } FLAG++; } @@ -517,17 +506,34 @@ void SShell::MakeFromBoolean(SShell *a, SShell *b, int type) { // the surfaces in B (which is all of the intersection curves). a->MakeIntersectionCurvesAgainst(b, this); - I = 100; if(b->surface.n == 0 || a->surface.n == 0) { // Then trim and copy the surfaces a->CopySurfacesTrimAgainst(b, this, type, true); b->CopySurfacesTrimAgainst(a, this, type, false); } else { - I = 0; a->CopySurfacesTrimAgainst(b, this, type, true); b->CopySurfacesTrimAgainst(a, this, type, false); } + // Now that we've copied the surfaces, we know their new hSurfaces, so + // rewrite the curves to refer to the surfaces by their handles in the + // result. + SCurve *sc; + for(sc = curve.First(); sc; sc = curve.NextAfter(sc)) { + if(sc->source == SCurve::FROM_A) { + sc->surfA = a->surface.FindById(sc->surfA)->newH; + sc->surfB = a->surface.FindById(sc->surfB)->newH; + } else if(sc->source == SCurve::FROM_B) { + sc->surfA = b->surface.FindById(sc->surfA)->newH; + sc->surfB = b->surface.FindById(sc->surfB)->newH; + } else if(sc->source == SCurve::FROM_INTERSECTION) { + sc->surfA = a->surface.FindById(sc->surfA)->newH; + sc->surfB = b->surface.FindById(sc->surfB)->newH; + } else { + oops(); + } + } + // And clean up the piecewise linear things we made as a calculation aid a->CleanupAfterBoolean(); b->CleanupAfterBoolean(); @@ -682,6 +688,13 @@ int SBspUv::ClassifyPoint(Point2d p, Point2d eb) { } int SBspUv::ClassifyEdge(Point2d ea, Point2d eb) { - return ClassifyPoint((ea.Plus(eb)).ScaledBy(0.5), eb); + int ret = ClassifyPoint((ea.Plus(eb)).ScaledBy(0.5), eb); + if(ret == EDGE_OTHER) { + // Perhaps the edge is tangent at its midpoint (and we screwed up + // somewhere earlier and failed to split it); try a different + // point on the edge. + ret = ClassifyPoint(ea.Plus((eb.Minus(ea)).ScaledBy(0.294)), eb); + } + return ret; } diff --git a/srf/ratpoly.cpp b/srf/ratpoly.cpp index dcd05bd..8a80cb0 100644 --- a/srf/ratpoly.cpp +++ b/srf/ratpoly.cpp @@ -144,15 +144,10 @@ Vector SBezier::PointAt(double t) { } void SBezier::MakePwlInto(List *l) { - MakePwlInto(l, Vector::From(0, 0, 0)); + l->Add(&(ctrl[0])); + MakePwlWorker(l, 0.0, 1.0); } -void SBezier::MakePwlInto(List *l, Vector offset) { - Vector p = (ctrl[0]).Plus(offset); - l->Add(&p); - - MakePwlWorker(l, 0.0, 1.0, offset); -} -void SBezier::MakePwlWorker(List *l, double ta, double tb, Vector off) { +void SBezier::MakePwlWorker(List *l, double ta, double tb) { Vector pa = PointAt(ta); Vector pb = PointAt(tb); @@ -169,12 +164,11 @@ void SBezier::MakePwlWorker(List *l, double ta, double tb, Vector off) { double step = 1.0/SS.maxSegments; if((tb - ta) < step || d < SS.ChordTolMm()) { // A previous call has already added the beginning of our interval. - pb = pb.Plus(off); l->Add(&pb); } else { double tm = (ta + tb) / 2; - MakePwlWorker(l, ta, tm, off); - MakePwlWorker(l, tm, tb, off); + MakePwlWorker(l, ta, tm); + MakePwlWorker(l, tm, tb); } } @@ -206,6 +200,18 @@ SBezier SBezier::TransformedBy(Vector t, Quaternion q) { return ret; } +bool SBezier::Equals(SBezier *b) { + // We just test of identical degree and control points, even though two + // curves could still be coincident (even sharing endpoints). + if(deg != b->deg) return false; + int i; + for(i = 0; i <= deg; i++) { + if(!(ctrl[i]).Equals(b->ctrl[i])) return false; + if(fabs(weight[i] - b->weight[i]) > LENGTH_EPS) return false; + } + return true; +} + void SBezierList::Clear(void) { l.Clear(); } @@ -375,6 +381,8 @@ SCurve SCurve::FromTransformationOf(SCurve *a, Vector t, Quaternion q) { ret.h = a->h; ret.isExact = a->isExact; ret.exact = (a->exact).TransformedBy(t, q); + ret.surfA = a->surfA; + ret.surfB = a->surfB; Vector *p; for(p = a->pts.First(); p; p = a->pts.NextAfter(p)) { @@ -454,7 +462,7 @@ bool SSurface::IsExtrusion(SBezier *of, Vector *alongp) { } bool SSurface::IsCylinder(Vector *center, Vector *axis, double *r, - double *dtheta) + Vector *start, Vector *finish) { SBezier sb; if(!IsExtrusion(&sb, axis)) return false; @@ -480,10 +488,13 @@ bool SSurface::IsCylinder(Vector *center, Vector *axis, double *r, pb2 = (sb.ctrl[2]).Project2d(u, v).Minus(c2); double thetaa = atan2(pa2.y, pa2.x), // in fact always zero due to csys - thetab = atan2(pb2.y, pb2.x); - *dtheta = WRAP_NOT_0(thetab - thetaa, 2*PI); + thetab = atan2(pb2.y, pb2.x), + dtheta = WRAP_NOT_0(thetab - thetaa, 2*PI); - if(fabs(sb.weight[1] - cos(*dtheta/2)) > LENGTH_EPS) return false; + if(fabs(sb.weight[1] - cos(dtheta/2)) > LENGTH_EPS) return false; + + *start = (sb.ctrl[0]).Minus(*center); + *finish = (sb.ctrl[2]).Minus(*center); return true; } @@ -743,11 +754,20 @@ void SSurface::GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) { } } -void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv) { +void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv, + SShell *useCurvesFrom) { STrimBy *stb; for(stb = trim.First(); stb; stb = trim.NextAfter(stb)) { SCurve *sc = shell->curve.FindById(stb->curve); + // We have the option to use the curves from another shell; this + // is relevant when generating the coincident edges while doing the + // Booleans, since the curves from the output shell will be split + // against any intersecting surfaces (and the originals aren't). + if(useCurvesFrom) { + sc = useCurvesFrom->curve.FindById(sc->newH); + } + Vector prev, prevuv, ptuv; bool inCurve = false, empty = true; double u = 0, v = 0; @@ -783,9 +803,8 @@ void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv) { if(pt->Equals(stb->start)) inCurve = true; if(pt->Equals(stb->finish)) inCurve = false; } - if(inCurve || empty) { - dbp("trim was empty or unterminated"); - } + if(inCurve) dbp("trim was unterminated"); + if(empty) dbp("trim was empty"); } } @@ -910,13 +929,17 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, // Translate the curve by t0 and t1 to produce two trim curves SCurve sc; ZERO(&sc); - sb->MakePwlInto(&(sc.pts), t0); + sc.isExact = true; + sc.exact = sb->TransformedBy(t0, Quaternion::IDENTITY); + (sc.exact).MakePwlInto(&(sc.pts)); sc.surfA = hs0; sc.surfB = hsext; hSCurve hc0 = curve.AddAndAssignId(&sc); ZERO(&sc); - sb->MakePwlInto(&(sc.pts), t1); + sc.isExact = true; + sc.exact = sb->TransformedBy(t1, Quaternion::IDENTITY); + (sc.exact).MakePwlInto(&(sc.pts)); sc.surfA = hs1; sc.surfB = hsext; hSCurve hc1 = curve.AddAndAssignId(&sc); @@ -936,10 +959,10 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, // And form the trim line Vector pt = sb->Finish(); - Vector p0 = pt.Plus(t0), p1 = pt.Plus(t1); ZERO(&sc); - sc.pts.Add(&p0); - sc.pts.Add(&p1); + sc.isExact = true; + sc.exact = SBezier::From(pt.Plus(t0), pt.Plus(t1)); + (sc.exact).MakePwlInto(&(sc.pts)); hSCurve hl = curve.AddAndAssignId(&sc); // save this for later TrimLine tl; @@ -969,10 +992,7 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, } void SShell::MakeFromCopyOf(SShell *a) { - Vector t = Vector::From(0, 0, 0); - Quaternion q = Quaternion::From(1, 0, 0, 0); - - MakeFromTransformationOf(a, t, q); + MakeFromTransformationOf(a, Vector::From(0, 0, 0), Quaternion::IDENTITY); } void SShell::MakeFromTransformationOf(SShell *a, Vector t, Quaternion q) { diff --git a/srf/surface.h b/srf/surface.h index 8544bd3..a022e14 100644 --- a/srf/surface.h +++ b/srf/surface.h @@ -61,9 +61,9 @@ public: Vector PointAt(double t); Vector Start(void); Vector Finish(void); + bool Equals(SBezier *b); void MakePwlInto(List *l); - void MakePwlInto(List *l, Vector offset); - void MakePwlWorker(List *l, double ta, double tb, Vector offset); + void MakePwlWorker(List *l, double ta, double tb); void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax); void Reverse(void); @@ -170,6 +170,10 @@ class SSurface { public: hSSurface h; + // Same as newH for the curves; record what a surface gets renamed to + // when I copy things over. + hSSurface newH; + int color; DWORD face; @@ -222,10 +226,12 @@ public: bool CoincidentWithPlane(Vector n, double d); bool CoincidentWith(SSurface *ss, bool sameNormal); bool IsExtrusion(SBezier *of, Vector *along); - bool IsCylinder(Vector *center, Vector *axis, double *r, double *dtheta); + bool IsCylinder(Vector *center, Vector *axis, double *r, + Vector *start, Vector *finish); void TriangulateInto(SShell *shell, SMesh *sm); - void MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv); + void MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv, + SShell *useCurvesFrom=NULL); void MakeClassifyingBsp(SShell *shell); double ChordToleranceForEdge(Vector a, Vector b); @@ -254,7 +260,7 @@ public: void AllPointsIntersecting(Vector a, Vector b, List *il, bool seg, bool trimmed); void MakeCoincidentEdgesInto(SSurface *proto, bool sameNormal, - SEdgeList *el); + SEdgeList *el, SShell *useCurvesFrom); void CleanupAfterBoolean(void); static const int INSIDE = 100; diff --git a/srf/surfinter.cpp b/srf/surfinter.cpp index 9c3507d..658b60e 100644 --- a/srf/surfinter.cpp +++ b/srf/surfinter.cpp @@ -7,13 +7,47 @@ void SSurface::AddExactIntersectionCurve(SBezier *sb, SSurface *srfB, { SCurve sc; ZERO(&sc); + // Important to keep the order of (surfA, surfB) consistent; when we later + // rewrite the identifiers, we rewrite surfA from A and surfB from B. sc.surfA = h; sc.surfB = srfB->h; - sb->MakePwlInto(&(sc.pts)); + sc.exact = *sb; + sc.isExact = true; - // Now split the line where it intersects our existing surfaces - SCurve split = sc.MakeCopySplitAgainst(agnstA, agnstB, this, srfB); - sc.Clear(); + // Now we have to piecewise linearize the curve. If there's already an + // identical curve in the shell, then follow that pwl exactly, otherwise + // calculate from scratch. + SCurve split, *existing = NULL, *se; + SBezier sbrev = *sb; + sbrev.Reverse(); + bool backwards = false; + for(se = into->curve.First(); se; se = into->curve.NextAfter(se)) { + if(se->isExact) { + if(sb->Equals(&(se->exact))) { + existing = se; + break; + } + if(sbrev.Equals(&(se->exact))) { + existing = se; + backwards = true; + break; + } + } + } + if(existing) { + Vector *v; + for(v = existing->pts.First(); v; v = existing->pts.NextAfter(v)) { + sc.pts.Add(v); + } + if(backwards) sc.pts.Reverse(); + split = sc; + ZERO(&sc); + } else { + sb->MakePwlInto(&(sc.pts)); + // and split the line where it intersects our existing surfaces + split = sc.MakeCopySplitAgainst(agnstA, agnstB, this, srfB); + sc.Clear(); + } if(0 && sb->deg == 1) { dbp(" "); @@ -26,6 +60,8 @@ void SSurface::AddExactIntersectionCurve(SBezier *sb, SSurface *srfB, prev = v; } } + // Nothing should be generating zero-len edges. + if((sb->Start()).Equals(sb->Finish())) oops(); split.source = SCurve::FROM_INTERSECTION; into->curve.AddAndAssignId(&split); @@ -98,7 +134,7 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, } } - if(tmax > tmin) { + if(tmax > tmin + LENGTH_EPS) { SBezier bezier = SBezier::From(p.Plus(dl.ScaledBy(tmin)), p.Plus(dl.ScaledBy(tmax))); AddExactIntersectionCurve(&bezier, b, agnstA, agnstB, into); @@ -415,8 +451,8 @@ void SSurface::AllPointsIntersecting(Vector a, Vector b, // All the intersections between the line and the surface; either special // cases that we can quickly solve in closed form, or general numerical. - Vector center, axis; - double radius, dtheta; + Vector center, axis, start, finish; + double radius; if(degm == 1 && degn == 1) { // Against a plane, easy. Vector n = NormalAt(0, 0).WithMagnitude(1); @@ -432,7 +468,7 @@ void SSurface::AllPointsIntersecting(Vector a, Vector b, ClosestPointTo(p, &(inter.p.x), &(inter.p.y)); inters.Add(&inter); } - } else if(IsCylinder(¢er, &axis, &radius, &dtheta) && 0) { + } else if(IsCylinder(¢er, &axis, &radius, &start, &finish) && 0) { // XXX, cylinder is easy in closed form } else { // General numerical solution by subdivision, fallback @@ -564,8 +600,9 @@ int SShell::ClassifyPoint(Vector p, Vector pout) { // then our ray always lies on edge, and that's okay. Otherwise // try again in a different random direction. if((edge_inters == 2) || !onEdge) break; - if(cnt++ > 20) { + if(cnt++ > 5) { dbp("can't find a ray that doesn't hit on edge!"); + dbp("on edge = %d, edge_inters = %d", onEdge, edge_inters); break; } } @@ -626,12 +663,12 @@ bool SSurface::CoincidentWithPlane(Vector n, double d) { // the prototype surface. //----------------------------------------------------------------------------- void SShell::MakeCoincidentEdgesInto(SSurface *proto, bool sameNormal, - SEdgeList *el) + SEdgeList *el, SShell *useCurvesFrom) { SSurface *ss; for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) { if(proto->CoincidentWith(ss, sameNormal)) { - ss->MakeEdgesInto(this, el, false); + ss->MakeEdgesInto(this, el, false, useCurvesFrom); } } diff --git a/util.cpp b/util.cpp index c5bd80f..d42f9d4 100644 --- a/util.cpp +++ b/util.cpp @@ -100,6 +100,8 @@ void MakeMatrix(double *mat, double a11, double a12, double a13, double a14, mat[15] = a44; } +const Quaternion Quaternion::IDENTITY = { 1, 0, 0, 0 }; + Quaternion Quaternion::From(double w, double vx, double vy, double vz) { Quaternion q; q.w = w; diff --git a/wishlist.txt b/wishlist.txt index 4bbb8b9..656c6d7 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -3,17 +3,16 @@ marching algorithm for surface intersection surfaces of revolution (lathed) cylinder-line special cases exact boundaries when near pwl trim -step and repeat rotate/translate tangent intersections short pwl edge avoidance -faces -take consistent pwl with coincident faces exact curve export (at least for dxf) hidden line removal from mesh line styles (color, thickness) +assembly ----- loop detection incremental regen of entities? +IGES and STEP export