diff --git a/Makefile b/Makefile index fb3c409..a68318d 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,7 @@ SRFOBJS = $(OBJDIR)\ratpoly.obj \ $(OBJDIR)\triangulate.obj \ $(OBJDIR)\boolean.obj \ $(OBJDIR)\surfinter.obj \ + $(OBJDIR)\merge.obj \ RES = $(OBJDIR)\resource.res @@ -56,7 +57,7 @@ LIBS = user32.lib gdi32.lib comctl32.lib advapi32.lib shell32.lib opengl32.lib g all: $(OBJDIR)/solvespace.exe @cp $(OBJDIR)/solvespace.exe . - solvespace alext.slvs + solvespace t8.slvs clean: rm -f obj/* diff --git a/groupmesh.cpp b/groupmesh.cpp index 1896c43..25306d4 100644 --- a/groupmesh.cpp +++ b/groupmesh.cpp @@ -168,6 +168,7 @@ void Group::GenerateShellAndMesh(void) { GenerateForStepAndRepeat (prev, toStep, &runningShell, src->meshCombine); + runningShell.MergeCoincidentSurfaces(); } else { SMesh prevm, stepm; ZERO(&prevm); @@ -287,6 +288,8 @@ void Group::GenerateShellAndMesh(void) { thisShell.RemapFaces(this, 0); } + thisShell.MergeCoincidentSurfaces(); + // So now we've got the mesh or shell for this group. Combine it with // the previous group's mesh or shell with the requested Boolean, and // we're done. @@ -295,6 +298,7 @@ void Group::GenerateShellAndMesh(void) { if(pg->runningMesh.IsEmpty() && thisMesh.IsEmpty() && !forceToMesh) { SShell *prevs = &(pg->runningShell); GenerateForBoolean(prevs, &thisShell, &runningShell); + runningShell.MergeCoincidentSurfaces(); // If the Boolean failed, then we should note that in the text screen // for this group. diff --git a/srf/boolean.cpp b/srf/boolean.cpp index a487aa4..30789dc 100644 --- a/srf/boolean.cpp +++ b/srf/boolean.cpp @@ -109,7 +109,7 @@ void SShell::CopyCurvesSplitAgainst(bool opA, SShell *agnst, SShell *into) { } } -void SSurface::TrimFromEdgeList(SEdgeList *el) { +void SSurface::TrimFromEdgeList(SEdgeList *el, bool asUv) { el->l.ClearTags(); STrimBy stb; @@ -151,9 +151,12 @@ void SSurface::TrimFromEdgeList(SEdgeList *el) { } } while(merged); + if(asUv) { + stb.start = PointAt(stb.start.x, stb.start.y); + stb.finish = PointAt(stb.finish.x, stb.finish.y); + } + // And add the merged trim, with xyz (not uv like the polygon) pts - stb.start = PointAt(stb.start.x, stb.start.y); - stb.finish = PointAt(stb.finish.x, stb.finish.y); trim.Add(&stb); } } @@ -444,7 +447,7 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *agnst, SShell *parent, final.CullExtraneousEdges(); // Use our reassembled edges to trim the new surface. - ret.TrimFromEdgeList(&final); + ret.TrimFromEdgeList(&final, true); SPolygon poly; ZERO(&poly); diff --git a/srf/merge.cpp b/srf/merge.cpp new file mode 100644 index 0000000..5255129 --- /dev/null +++ b/srf/merge.cpp @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// Routines to merge multiple coincident surfaces (each with their own trim +// curves) into a single surface, with all of the trim curves. +//----------------------------------------------------------------------------- +#include "../solvespace.h" + +void SShell::MergeCoincidentSurfaces(void) { + surface.ClearTags(); + + int i, j; + SSurface *si, *sj; + + for(i = 0; i < surface.n; i++) { + si = &(surface.elem[i]); + if(si->tag) continue; + + SEdgeList sel; + ZERO(&sel); + + bool merged = false; + + for(j = i + 1; j < surface.n; j++) { + sj = &(surface.elem[j]); + if(sj->tag) continue; + if(!sj->CoincidentWith(si, true)) continue; + if(sj->color != si->color) continue; + // But we do merge surfaces with different face entities, since + // otherwise we'd hardly ever merge anything. + + // This surface is coincident, so it gets merged. + sj->tag = 1; + merged = true; + sj->MakeEdgesInto(this, &sel, false); + sj->trim.Clear(); + + // All the references to this surface get replaced with the new srf + SCurve *sc; + for(sc = curve.First(); sc; sc = curve.NextAfter(sc)) { + if(sc->surfA.v == sj->h.v) sc->surfA = si->h; + if(sc->surfB.v == sj->h.v) sc->surfB = si->h; + } + } + + if(merged) { + si->MakeEdgesInto(this, &sel, false); + sel.CullExtraneousEdges(); + si->trim.Clear(); + si->TrimFromEdgeList(&sel, false); + + // And we must choose control points such that all the trims lie + // with u and v in [0, 1], so that the bbox tests work. + Vector u, v, n; + si->TangentsAt(0.5, 0.5, &u, &v); + u = u.WithMagnitude(1); + v = v.WithMagnitude(1); + n = si->NormalAt(0.5, 0.5).WithMagnitude(1); + v = (n.Cross(u)).WithMagnitude(1); + + double umax = VERY_NEGATIVE, umin = VERY_POSITIVE, + vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE; + SEdge *se; + for(se = sel.l.First(); se; se = sel.l.NextAfter(se)) { + double ut = (se->a).Dot(u), vt = (se->a).Dot(v); + umax = max(umax, ut); + vmax = max(vmax, vt); + umin = min(umin, ut); + vmin = min(vmin, vt); + } + + // We move in the +v direction as v goes from 0 to 1, and in the + // +u direction as u goes from 0 to 1. So our normal ends up + // pointed the same direction. + double nt = (si->ctrl[0][0]).Dot(n); + si->ctrl[0][0] = + Vector::From(umin, vmin, nt).ScaleOutOfCsys(u, v, n); + si->ctrl[0][1] = + Vector::From(umin, vmax, nt).ScaleOutOfCsys(u, v, n); + si->ctrl[1][1] = + Vector::From(umax, vmax, nt).ScaleOutOfCsys(u, v, n); + si->ctrl[1][0] = + Vector::From(umax, vmin, nt).ScaleOutOfCsys(u, v, n); + } + } + + surface.RemoveTagged(); +} + diff --git a/srf/surface.cpp b/srf/surface.cpp index ed33593..20fa20e 100644 --- a/srf/surface.cpp +++ b/srf/surface.cpp @@ -67,6 +67,8 @@ SSurface SSurface::FromRevolutionOf(SBezier *sb, Vector pt, Vector axis, { SSurface ret; ZERO(&ret); + + ret.degm = sb->deg; ret.degn = 2; @@ -499,6 +501,8 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, ZERO(this); SBezierLoop *sbl; + int i0 = surface.n, i; + // Normalize the axis direction so that the direction of revolution // ends up parallel to the normal of the sketch, on the side of the // axis where the sketch is. @@ -619,6 +623,86 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, hsl.Clear(); } + + for(i = i0; i < surface.n; i++) { + SSurface *srf = &(surface.elem[i]); + + // Revolution of a line; this is potentially a plane, which we can + // rewrite to have degree (1, 1). + if(srf->degm == 1 && srf->degn == 2) { + // close start, far start, far finish + Vector cs, fs, ff; + double d0, d1; + d0 = (srf->ctrl[0][0]).DistanceToLine(pt, axis); + d1 = (srf->ctrl[1][0]).DistanceToLine(pt, axis); + + if(d0 > d1) { + cs = srf->ctrl[1][0]; + fs = srf->ctrl[0][0]; + ff = srf->ctrl[0][2]; + } else { + cs = srf->ctrl[0][0]; + fs = srf->ctrl[1][0]; + ff = srf->ctrl[1][2]; + } + + // origin close, origin far + Vector oc = cs.ClosestPointOnLine(pt, axis), + of = fs.ClosestPointOnLine(pt, axis); + + if(oc.Equals(of)) { + // This is a plane, not a (non-degenerate) cone. + Vector oldn = srf->NormalAt(0.5, 0.5); + + Vector u = fs.Minus(of), v; + + v = (axis.Cross(u)).WithMagnitude(1); + + double vm = (ff.Minus(of)).Dot(v); + v = v.ScaledBy(vm); + + srf->degm = 1; + srf->degn = 1; + srf->ctrl[0][0] = of; + srf->ctrl[0][1] = of.Plus(u); + srf->ctrl[1][0] = of.Plus(v); + srf->ctrl[1][1] = of.Plus(u).Plus(v); + srf->weight[0][0] = 1; + srf->weight[0][1] = 1; + srf->weight[1][0] = 1; + srf->weight[1][1] = 1; + + if(oldn.Dot(srf->NormalAt(0.5, 0.5)) < 0) { + SWAP(Vector, srf->ctrl[0][0], srf->ctrl[1][0]); + SWAP(Vector, srf->ctrl[0][1], srf->ctrl[1][1]); + } + continue; + } + + if(fabs(d0 - d1) < LENGTH_EPS) { + // This is a cylinder; so transpose it so that we'll recognize + // it as a surface of extrusion. + SSurface sn = *srf; + + // Transposing u and v flips the normal, so reverse u to + // flip it again and put it back where we started. + sn.degm = 2; + sn.degn = 1; + int dm, dn; + for(dm = 0; dm <= 1; dm++) { + for(dn = 0; dn <= 2; dn++) { + sn.ctrl [dn][dm] = srf->ctrl [1-dm][dn]; + sn.weight[dn][dm] = srf->weight[1-dm][dn]; + } + } + + *srf = sn; + continue; + } + } + + } + } void SShell::MakeFromCopyOf(SShell *a) { diff --git a/srf/surface.h b/srf/surface.h index e5a2555..dc5b432 100644 --- a/srf/surface.h +++ b/srf/surface.h @@ -194,6 +194,7 @@ public: // A rational polynomial surface in Bezier form. class SSurface { public: + int tag; hSSurface h; // Same as newH for the curves; record what a surface gets renamed to @@ -226,7 +227,7 @@ public: DWORD auxA, SShell *shell); SSurface MakeCopyTrimAgainst(SShell *against, SShell *parent, SShell *into, int type, bool opA); - void TrimFromEdgeList(SEdgeList *el); + void TrimFromEdgeList(SEdgeList *el, bool asUv); void IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, SShell *into); void AddExactIntersectionCurve(SBezier *sb, SSurface *srfB, @@ -330,6 +331,7 @@ public: void MakeFromCopyOf(SShell *a); void MakeFromTransformationOf(SShell *a, Vector trans, Quaternion q); void MakeFromAssemblyOf(SShell *a, SShell *b); + void MergeCoincidentSurfaces(void); void TriangulateInto(SMesh *sm); void MakeEdgesInto(SEdgeList *sel); diff --git a/srf/surfinter.cpp b/srf/surfinter.cpp index b166659..49a4309 100644 --- a/srf/surfinter.cpp +++ b/srf/surfinter.cpp @@ -58,7 +58,8 @@ void SSurface::AddExactIntersectionCurve(SBezier *sb, SSurface *srfB, dbp("split.pts.n =%d", split.pts.n); for(v = split.pts.First(); v; v = split.pts.NextAfter(v)) { if(prev) { - SS.nakedEdges.AddEdge(prev->p, v->p); + Vector e = (prev->p).Minus(v->p).WithMagnitude(-1); + SS.nakedEdges.AddEdge((prev->p).Plus(e), (v->p).Minus(e)); } prev = v; }