//----------------------------------------------------------------------------- // In our shell, find all surfaces that are coincident with the prototype // surface (with same or opposite normal, as specified), and copy all of // their trim polygons into el. The edges are returned in uv coordinates for // the prototype surface. //----------------------------------------------------------------------------- void SShell::MakeCoincidentEdgesInto(SSurface *proto, bool sameNormal, SEdgeList *el, SShell *useCurvesFrom) { SSurface *ss; for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) { if(proto->CoincidentWith(ss, sameNormal)) { ss->MakeEdgesInto(this, el, SSurface::MakeAs::XYZ, useCurvesFrom); } } SEdge *se; for(se = el->l.First(); se; se = el->l.NextAfter(se)) { double ua, va, ub, vb; proto->ClosestPointTo(se->a, &ua, &va); proto->ClosestPointTo(se->b, &ub, &vb); if(sameNormal) { se->a = Vector::From(ua, va, 0); se->b = Vector::From(ub, vb, 0); } else { // Flip normal, so flip all edge directions se->b = Vector::From(ua, va, 0); se->a = Vector::From(ub, vb, 0); } } }
void SShell::MakeSectionEdgesInto(Vector n, double d, SEdgeList *sel, SBezierList *sbl) { SSurface *s; for(s = surface.First(); s; s = surface.NextAfter(s)) { if(s->CoincidentWithPlane(n, d)) { s->MakeSectionEdgesInto(this, sel, sbl); } } }
void SShell::AllPointsIntersecting(Vector a, Vector b, List<SInter> *il, bool seg, bool trimmed, bool inclTangent) { SSurface *ss; for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) { ss->AllPointsIntersecting(a, b, il, seg, trimmed, inclTangent); } }
void SShell::Clear(void) { SSurface *s; for(s = surface.First(); s; s = surface.NextAfter(s)) { s->Clear(); } surface.Clear(); SCurve *c; for(c = curve.First(); c; c = curve.NextAfter(c)) { c->Clear(); } curve.Clear(); }
void SSurface::EdgeNormalsWithinSurface(Point2d auv, Point2d buv, Vector *pt, Vector *enin, Vector *enout, Vector *surfn, uint32_t auxA, SShell *shell, SShell *sha, SShell *shb) { // the midpoint of the edge Point2d muv = (auv.Plus(buv)).ScaledBy(0.5); *pt = PointAt(muv); // If this edge just approximates a curve, then refine our midpoint so // so that it actually lies on that curve too. Otherwise stuff like // point-on-face tests will fail, since the point won't actually lie // on the other face. hSCurve hc = { auxA }; SCurve *sc = shell->curve.FindById(hc); if(sc->isExact && sc->exact.deg != 1) { double t; sc->exact.ClosestPointTo(*pt, &t, false); *pt = sc->exact.PointAt(t); ClosestPointTo(*pt, &muv); } else if(!sc->isExact) { SSurface *trimmedA = sc->GetSurfaceA(sha, shb), *trimmedB = sc->GetSurfaceB(sha, shb); *pt = trimmedA->ClosestPointOnThisAndSurface(trimmedB, *pt); ClosestPointTo(*pt, &muv); } *surfn = NormalAt(muv.x, muv.y); // Compute the edge's inner normal in xyz space. Vector ab = (PointAt(auv)).Minus(PointAt(buv)), enxyz = (ab.Cross(*surfn)).WithMagnitude(SS.ChordTolMm()); // And based on that, compute the edge's inner normal in uv space. This // vector is perpendicular to the edge in xyz, but not necessarily in uv. Vector tu, tv; TangentsAt(muv.x, muv.y, &tu, &tv); Point2d enuv; enuv.x = enxyz.Dot(tu) / tu.MagSquared(); enuv.y = enxyz.Dot(tv) / tv.MagSquared(); // Compute the inner and outer normals of this edge (within the srf), // in xyz space. These are not necessarily antiparallel, if the // surface is curved. Vector pin = PointAt(muv.Minus(enuv)), pout = PointAt(muv.Plus(enuv)); *enin = pin.Minus(*pt), *enout = pout.Minus(*pt); }
//----------------------------------------------------------------------------- // When we split line segments wherever they intersect a surface, we introduce // extra pwl points. This may create very short edges that could be removed // without violating the chord tolerance. Those are ugly, and also break // stuff in the Booleans. So remove them. //----------------------------------------------------------------------------- void SCurve::RemoveShortSegments(SSurface *srfA, SSurface *srfB) { // Three, not two; curves are pwl'd to at least two edges (three points) // even if not necessary, to avoid square holes. if(pts.n <= 3) return; pts.ClearTags(); Vector prev = pts.elem[0].p; int i, a; for(i = 1; i < pts.n - 1; i++) { SCurvePt *sct = &(pts.elem[i]), *scn = &(pts.elem[i+1]); if(sct->vertex) { prev = sct->p; continue; } bool mustKeep = false; // We must check against both surfaces; the piecewise linear edge // may have a different chord tolerance in the two surfaces. (For // example, a circle in the surface of a cylinder is just a straight // line, so it always has perfect chord tol, but that circle in // a plane is a circle so it doesn't). for(a = 0; a < 2; a++) { SSurface *srf = (a == 0) ? srfA : srfB; Vector puv, nuv; srf->ClosestPointTo(prev, &(puv.x), &(puv.y)); srf->ClosestPointTo(scn->p, &(nuv.x), &(nuv.y)); if(srf->ChordToleranceForEdge(nuv, puv) > SS.ChordTolMm()) { mustKeep = true; } } if(mustKeep) { prev = sct->p; } else { sct->tag = 1; // and prev is unchanged, since there's no longer any point // in between } } pts.RemoveTagged(); }
SSurface SSurface::FromTransformationOf(SSurface *a, Vector t, Quaternion q, double scale, bool includingTrims) { SSurface ret = {}; ret.h = a->h; ret.color = a->color; ret.face = a->face; ret.degm = a->degm; ret.degn = a->degn; int i, j; for(i = 0; i <= 3; i++) { for(j = 0; j <= 3; j++) { ret.ctrl[i][j] = a->ctrl[i][j]; ret.ctrl[i][j] = (ret.ctrl[i][j]).ScaledBy(scale); ret.ctrl[i][j] = (q.Rotate(ret.ctrl[i][j])).Plus(t); ret.weight[i][j] = a->weight[i][j]; } } if(includingTrims) { STrimBy *stb; for(stb = a->trim.First(); stb; stb = a->trim.NextAfter(stb)) { STrimBy n = *stb; n.start = n.start.ScaledBy(scale); n.finish = n.finish.ScaledBy(scale); n.start = (q.Rotate(n.start)) .Plus(t); n.finish = (q.Rotate(n.finish)).Plus(t); ret.trim.Add(&n); } } if(scale < 0) { // If we mirror every surface of a shell, then it will end up inside // out. So fix that here. ret.Reverse(); } return ret; }
void SShell::TriangulateInto(SMesh *sm) { SSurface *s; for(s = surface.First(); s; s = surface.NextAfter(s)) { s->TriangulateInto(this, sm); } }
void SShell::MakeEdgesInto(SEdgeList *sel) { SSurface *s; for(s = surface.First(); s; s = surface.NextAfter(s)) { s->MakeEdgesInto(this, sel, SSurface::AS_XYZ); } }
void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, RgbaColor color, Group *group) { 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. Vector pto; double md = VERY_NEGATIVE; for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) { SBezier *sb; for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) { // Choose the point farthest from the axis; we'll get garbage // if we choose a point that lies on the axis, for example. // (And our surface will be self-intersecting if the sketch // spans the axis, so don't worry about that.) Vector p = sb->Start(); double d = p.DistanceToLine(pt, axis); if(d > md) { md = d; pto = p; } } } Vector ptc = pto.ClosestPointOnLine(pt, axis), up = (pto.Minus(ptc)).WithMagnitude(1), vp = (sbls->normal).Cross(up); if(vp.Dot(axis) < 0) { axis = axis.ScaledBy(-1); } // Now we actually build and trim the surfaces. for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) { int i, j; SBezier *sb, *prev; List<Revolved> hsl = {}; for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) { Revolved revs; for(j = 0; j < 4; j++) { if(sb->deg == 1 && (sb->ctrl[0]).DistanceToLine(pt, axis) < LENGTH_EPS && (sb->ctrl[1]).DistanceToLine(pt, axis) < LENGTH_EPS) { // This is a line on the axis of revolution; it does // not contribute a surface. revs.d[j].v = 0; } else { SSurface ss = SSurface::FromRevolutionOf(sb, pt, axis, (PI/2)*j, (PI/2)*(j+1)); ss.color = color; if(sb->entity != 0) { hEntity he; he.v = sb->entity; hEntity hface = group->Remap(he, Group::REMAP_LINE_TO_FACE); if(SK.entity.FindByIdNoOops(hface) != NULL) { ss.face = hface.v; } } revs.d[j] = surface.AddAndAssignId(&ss); } } hsl.Add(&revs); } for(i = 0; i < sbl->l.n; i++) { Revolved revs = hsl.elem[i], revsp = hsl.elem[WRAP(i-1, sbl->l.n)]; sb = &(sbl->l.elem[i]); prev = &(sbl->l.elem[WRAP(i-1, sbl->l.n)]); for(j = 0; j < 4; j++) { SCurve sc; Quaternion qs = Quaternion::From(axis, (PI/2)*j); // we want Q*(x - p) + p = Q*x + (p - Q*p) Vector ts = pt.Minus(qs.Rotate(pt)); // If this input curve generate a surface, then trim that // surface with the rotated version of the input curve. if(revs.d[j].v) { sc = {}; sc.isExact = true; sc.exact = sb->TransformedBy(ts, qs, 1.0); (sc.exact).MakePwlInto(&(sc.pts)); sc.surfA = revs.d[j]; sc.surfB = revs.d[WRAP(j-1, 4)]; hSCurve hcb = curve.AddAndAssignId(&sc); STrimBy stb; stb = STrimBy::EntireCurve(this, hcb, true); (surface.FindById(sc.surfA))->trim.Add(&stb); stb = STrimBy::EntireCurve(this, hcb, false); (surface.FindById(sc.surfB))->trim.Add(&stb); } // And if this input curve and the one after it both generated // surfaces, then trim both of those by the appropriate // circle. if(revs.d[j].v && revsp.d[j].v) { SSurface *ss = surface.FindById(revs.d[j]); sc = {}; sc.isExact = true; sc.exact = SBezier::From(ss->ctrl[0][0], ss->ctrl[0][1], ss->ctrl[0][2]); sc.exact.weight[1] = ss->weight[0][1]; (sc.exact).MakePwlInto(&(sc.pts)); sc.surfA = revs.d[j]; sc.surfB = revsp.d[j]; hSCurve hcc = curve.AddAndAssignId(&sc); STrimBy stb; stb = STrimBy::EntireCurve(this, hcc, false); (surface.FindById(sc.surfA))->trim.Add(&stb); stb = STrimBy::EntireCurve(this, hcc, true); (surface.FindById(sc.surfB))->trim.Add(&stb); } } } 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(srf->ctrl[0][0], srf->ctrl[1][0]); swap(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; } } } }
//----------------------------------------------------------------------------- // Trim this surface against the specified shell, in the way that's appropriate // for the specified Boolean operation type (and which operand we are). We // also need a pointer to the shell that contains our own surface, since that // contains our original trim curves. //----------------------------------------------------------------------------- SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, SShell *sha, SShell *shb, SShell *into, int type) { bool opA = (parent == sha); SShell *agnst = opA ? shb : sha; SSurface ret; // The returned surface is identical, just the trim curves change ret = *this; ret.trim = {}; // First, build a list of the existing trim curves; update them to use // the split curves. STrimBy *stb; for(stb = trim.First(); stb; stb = trim.NextAfter(stb)) { STrimBy stn = *stb; stn.curve = (parent->curve.FindById(stn.curve))->newH; ret.trim.Add(&stn); } if(type == SShell::AS_DIFFERENCE && !opA) { // The second operand of a Boolean difference gets turned inside out ret.Reverse(); } // Build up our original trim polygon; remember the coordinates could // be changed if we just flipped the surface normal, and we are using // the split curves (not the original curves). SEdgeList orig = {}; ret.MakeEdgesInto(into, &orig, AS_UV); ret.trim.Clear(); // which means that we can't necessarily use the old BSP... SBspUv *origBsp = SBspUv::From(&orig, &ret); // And now intersect the other shell against us SEdgeList inter = {}; SSurface *ss; for(ss = agnst->surface.First(); ss; ss = agnst->surface.NextAfter(ss)) { SCurve *sc; for(sc = into->curve.First(); sc; sc = into->curve.NextAfter(sc)) { if(sc->source != SCurve::FROM_INTERSECTION) continue; if(opA) { 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; for(i = 1; i < sc->pts.n; i++) { Vector a = sc->pts.elem[i-1].p, b = sc->pts.elem[i].p; Point2d auv, buv; ss->ClosestPointTo(a, &(auv.x), &(auv.y)); ss->ClosestPointTo(b, &(buv.x), &(buv.y)); int c = (ss->bsp) ? ss->bsp->ClassifyEdge(auv, buv, ss) : SBspUv::OUTSIDE; if(c != SBspUv::OUTSIDE) { Vector ta = Vector::From(0, 0, 0); Vector tb = Vector::From(0, 0, 0); ret.ClosestPointTo(a, &(ta.x), &(ta.y)); ret.ClosestPointTo(b, &(tb.x), &(tb.y)); Vector tn = ret.NormalAt(ta.x, ta.y); Vector sn = ss->NormalAt(auv.x, auv.y); // We are subtracting the portion of our surface that // lies in the shell, so the in-plane edge normal should // point opposite to the surface normal. bool bkwds = true; if((tn.Cross(b.Minus(a))).Dot(sn) < 0) bkwds = !bkwds; if(type == SShell::AS_DIFFERENCE && !opA) bkwds = !bkwds; if(bkwds) { inter.AddEdge(tb, ta, sc->h.v, 1); } else { inter.AddEdge(ta, tb, sc->h.v, 0); } } } } } // Record all the points where more than two edges join, which I will call // the choosing points. If two edges join at a non-choosing point, then // they must either both be kept or both be discarded (since that would // otherwise create an open contour). SPointList choosing = {}; SEdge *se; for(se = orig.l.First(); se; se = orig.l.NextAfter(se)) { choosing.IncrementTagFor(se->a); choosing.IncrementTagFor(se->b); } for(se = inter.l.First(); se; se = inter.l.NextAfter(se)) { choosing.IncrementTagFor(se->a); choosing.IncrementTagFor(se->b); } SPoint *sp; for(sp = choosing.l.First(); sp; sp = choosing.l.NextAfter(sp)) { if(sp->tag == 2) { sp->tag = 1; } else { sp->tag = 0; } } choosing.l.RemoveTagged(); // The list of edges to trim our new surface, a combination of edges from // our original and intersecting edge lists. SEdgeList final = {}; while(orig.l.n > 0) { SEdgeList chain = {}; FindChainAvoiding(&orig, &chain, &choosing); // Arbitrarily choose an edge within the chain to classify; they // should all be the same, though. se = &(chain.l.elem[chain.l.n/2]); Point2d auv = (se->a).ProjectXy(), buv = (se->b).ProjectXy(); Vector pt, enin, enout, surfn; ret.EdgeNormalsWithinSurface(auv, buv, &pt, &enin, &enout, &surfn, se->auxA, into, sha, shb); int indir_shell, outdir_shell, indir_orig, outdir_orig; indir_orig = SShell::INSIDE; outdir_orig = SShell::OUTSIDE; agnst->ClassifyEdge(&indir_shell, &outdir_shell, ret.PointAt(auv), ret.PointAt(buv), pt, enin, enout, surfn); if(KeepEdge(type, opA, indir_shell, outdir_shell, indir_orig, outdir_orig)) { for(se = chain.l.First(); se; se = chain.l.NextAfter(se)) { final.AddEdge(se->a, se->b, se->auxA, se->auxB); } } chain.Clear(); }
void Group::GenerateShellAndMesh(void) { bool prevBooleanFailed = booleanFailed; booleanFailed = false; Group *srcg = this; thisShell.Clear(); thisMesh.Clear(); runningShell.Clear(); runningMesh.Clear(); // Don't attempt a lathe or extrusion unless the source section is good: // planar and not self-intersecting. bool haveSrc = true; if(type == EXTRUDE || type == LATHE) { Group *src = SK.GetGroup(opA); if(src->polyError.how != POLY_GOOD) { haveSrc = false; } } if(type == TRANSLATE || type == ROTATE) { // A step and repeat gets merged against the group's prevous group, // not our own previous group. srcg = SK.GetGroup(opA); GenerateForStepAndRepeat<SShell>(&(srcg->thisShell), &thisShell); GenerateForStepAndRepeat<SMesh> (&(srcg->thisMesh), &thisMesh); } else if(type == EXTRUDE && haveSrc) { Group *src = SK.GetGroup(opA); Vector translate = Vector::From(h.param(0), h.param(1), h.param(2)); Vector tbot, ttop; if(subtype == ONE_SIDED) { tbot = Vector::From(0, 0, 0); ttop = translate.ScaledBy(2); } else { tbot = translate.ScaledBy(-1); ttop = translate.ScaledBy(1); } SBezierLoopSetSet *sblss = &(src->bezierLoops); SBezierLoopSet *sbls; for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) { int is = thisShell.surface.n; // Extrude this outer contour (plus its inner contours, if present) thisShell.MakeFromExtrusionOf(sbls, tbot, ttop, color); // And for any plane faces, annotate the model with the entity for // that face, so that the user can select them with the mouse. Vector onOrig = sbls->point; int i; for(i = is; 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 == is || i == (is + 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 = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { if(e->group.v != opA.v) continue; if(e->type != Entity::LINE_SEGMENT) continue; Vector a = SK.GetEntity(e->point[0])->PointGetNum(), b = SK.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 && haveSrc) { Group *src = SK.GetGroup(opA); Vector pt = SK.GetEntity(predef.origin)->PointGetNum(), axis = SK.GetEntity(predef.entityB)->VectorGetNum(); axis = axis.WithMagnitude(1); SBezierLoopSetSet *sblss = &(src->bezierLoops); SBezierLoopSet *sbls; for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) { thisShell.MakeFromRevolutionOf(sbls, pt, axis, color, this); } } else if(type == LINKED) { // The imported shell or mesh are copied over, with the appropriate // transformation applied. We also must remap the face entities. Vector offset = { SK.GetParam(h.param(0))->val, SK.GetParam(h.param(1))->val, SK.GetParam(h.param(2))->val }; Quaternion q = { SK.GetParam(h.param(3))->val, SK.GetParam(h.param(4))->val, SK.GetParam(h.param(5))->val, SK.GetParam(h.param(6))->val }; thisMesh.MakeFromTransformationOf(&impMesh, offset, q, scale); thisMesh.RemapFaces(this, 0); thisShell.MakeFromTransformationOf(&impShell, offset, q, scale); thisShell.RemapFaces(this, 0); } if(srcg->meshCombine != COMBINE_AS_ASSEMBLE) { 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. Group *prevg = srcg->RunningMeshGroup(); if(prevg->runningMesh.IsEmpty() && thisMesh.IsEmpty() && !forceToMesh) { SShell *prevs = &(prevg->runningShell); GenerateForBoolean<SShell>(prevs, &thisShell, &runningShell, srcg->meshCombine); if(srcg->meshCombine != COMBINE_AS_ASSEMBLE) { runningShell.MergeCoincidentSurfaces(); } // If the Boolean failed, then we should note that in the text screen // for this group. booleanFailed = runningShell.booleanFailed; if(booleanFailed != prevBooleanFailed) { SS.ScheduleShowTW(); } } else { SMesh prevm, thism; prevm = {}; thism = {}; prevm.MakeFromCopyOf(&(prevg->runningMesh)); prevg->runningShell.TriangulateInto(&prevm); thism.MakeFromCopyOf(&thisMesh); thisShell.TriangulateInto(&thism); SMesh outm = {}; GenerateForBoolean<SMesh>(&prevm, &thism, &outm, srcg->meshCombine); // And make sure that the output mesh is vertex-to-vertex. SKdNode *root = SKdNode::From(&outm); root->SnapToMesh(&outm); root->MakeMeshInto(&runningMesh); outm.Clear(); thism.Clear(); prevm.Clear(); } displayDirty = true; }
void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, SShell *into) { Vector amax, amin, bmax, bmin; GetAxisAlignedBounding(&amax, &amin); b->GetAxisAlignedBounding(&bmax, &bmin); if(Vector::BoundingBoxesDisjoint(amax, amin, bmax, bmin)) { // They cannot possibly intersect, no curves to generate return; } Vector alongt, alongb; SBezier oft, ofb; bool isExtdt = this->IsExtrusion(&oft, &alongt), isExtdb = b->IsExtrusion(&ofb, &alongb); if(degm == 1 && degn == 1 && b->degm == 1 && b->degn == 1) { // Line-line intersection; it's a plane or nothing. Vector na = NormalAt(0, 0).WithMagnitude(1), nb = b->NormalAt(0, 0).WithMagnitude(1); double da = na.Dot(PointAt(0, 0)), db = nb.Dot(b->PointAt(0, 0)); Vector dl = na.Cross(nb); if(dl.Magnitude() < LENGTH_EPS) return; // parallel planes dl = dl.WithMagnitude(1); Vector p = Vector::AtIntersectionOfPlanes(na, da, nb, db); // Trim it to the region 0 <= {u,v} <= 1 for each plane; not strictly // necessary, since line will be split and excess edges culled, but // this improves speed and robustness. int i; double tmax = VERY_POSITIVE, tmin = VERY_NEGATIVE; for(i = 0; i < 2; i++) { SSurface *s = (i == 0) ? this : b; Vector tu, tv; s->TangentsAt(0, 0, &tu, &tv); double up, vp, ud, vd; s->ClosestPointTo(p, &up, &vp); ud = (dl.Dot(tu)) / tu.MagSquared(); vd = (dl.Dot(tv)) / tv.MagSquared(); // so u = up + t*ud // v = vp + t*vd if(ud > LENGTH_EPS) { tmin = max(tmin, -up/ud); tmax = min(tmax, (1 - up)/ud); } else if(ud < -LENGTH_EPS) { tmax = min(tmax, -up/ud); tmin = max(tmin, (1 - up)/ud); } else { if(up < -LENGTH_EPS || up > 1 + LENGTH_EPS) { // u is constant, and outside [0, 1] tmax = VERY_NEGATIVE; } } if(vd > LENGTH_EPS) { tmin = max(tmin, -vp/vd); tmax = min(tmax, (1 - vp)/vd); } else if(vd < -LENGTH_EPS) { tmax = min(tmax, -vp/vd); tmin = max(tmin, (1 - vp)/vd); } else { if(vp < -LENGTH_EPS || vp > 1 + LENGTH_EPS) { // v is constant, and outside [0, 1] tmax = VERY_NEGATIVE; } } } 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); } } else if((degm == 1 && degn == 1 && isExtdb) || (b->degm == 1 && b->degn == 1 && isExtdt)) { // The intersection between a plane and a surface of extrusion SSurface *splane, *sext; if(degm == 1 && degn == 1) { splane = this; sext = b; } else { splane = b; sext = this; } Vector n = splane->NormalAt(0, 0).WithMagnitude(1), along; double d = n.Dot(splane->PointAt(0, 0)); SBezier bezier; (void)sext->IsExtrusion(&bezier, &along); if(fabs(n.Dot(along)) < LENGTH_EPS) { // Direction of extrusion is parallel to plane; so intersection // is zero or more lines. Build a line within the plane, and // normal to the direction of extrusion, and intersect that line // against the surface; each intersection point corresponds to // a line. Vector pm, alu, p0, dp; // a point halfway along the extrusion pm = ((sext->ctrl[0][0]).Plus(sext->ctrl[0][1])).ScaledBy(0.5); alu = along.WithMagnitude(1); dp = (n.Cross(along)).WithMagnitude(1); // n, alu, and dp form an orthogonal csys; set n component to // place it on the plane, alu component to lie halfway along // extrusion, and dp component doesn't matter so zero p0 = n.ScaledBy(d).Plus(alu.ScaledBy(pm.Dot(alu))); List<SInter> inters = {}; sext->AllPointsIntersecting(p0, p0.Plus(dp), &inters, /*asSegment=*/false, /*trimmed=*/false, /*inclTangent=*/true); SInter *si; for(si = inters.First(); si; si = inters.NextAfter(si)) { Vector al = along.ScaledBy(0.5); SBezier bezier; bezier = SBezier::From((si->p).Minus(al), (si->p).Plus(al)); AddExactIntersectionCurve(&bezier, b, agnstA, agnstB, into); } inters.Clear(); } else { // Direction of extrusion is not parallel to plane; so // intersection is projection of extruded curve into our plane. int i; for(i = 0; i <= bezier.deg; i++) { Vector p0 = bezier.ctrl[i], p1 = p0.Plus(along); bezier.ctrl[i] = Vector::AtIntersectionOfPlaneAndLine(n, d, p0, p1, NULL); } AddExactIntersectionCurve(&bezier, b, agnstA, agnstB, into); } } else if(isExtdt && isExtdb && sqrt(fabs(alongt.Dot(alongb))) > sqrt(alongt.Magnitude() * alongb.Magnitude()) - LENGTH_EPS) { // Two surfaces of extrusion along the same axis. So they might // intersect along some number of lines parallel to the axis. Vector axis = alongt.WithMagnitude(1); List<SInter> inters = {}; List<Vector> lv = {}; double a_axis0 = ( ctrl[0][0]).Dot(axis), a_axis1 = ( ctrl[0][1]).Dot(axis), b_axis0 = (b->ctrl[0][0]).Dot(axis), b_axis1 = (b->ctrl[0][1]).Dot(axis); if(a_axis0 > a_axis1) swap(a_axis0, a_axis1); if(b_axis0 > b_axis1) swap(b_axis0, b_axis1); double ab_axis0 = max(a_axis0, b_axis0), ab_axis1 = min(a_axis1, b_axis1); if(fabs(ab_axis0 - ab_axis1) < LENGTH_EPS) { // The line would be zero-length return; } Vector axis0 = axis.ScaledBy(ab_axis0), axis1 = axis.ScaledBy(ab_axis1), axisc = (axis0.Plus(axis1)).ScaledBy(0.5); oft.MakePwlInto(&lv); int i; for(i = 0; i < lv.n - 1; i++) { Vector pa = lv.elem[i], pb = lv.elem[i+1]; pa = pa.Minus(axis.ScaledBy(pa.Dot(axis))); pb = pb.Minus(axis.ScaledBy(pb.Dot(axis))); pa = pa.Plus(axisc); pb = pb.Plus(axisc); b->AllPointsIntersecting(pa, pb, &inters, /*asSegment=*/true,/*trimmed=*/false, /*inclTangent=*/false); } SInter *si; for(si = inters.First(); si; si = inters.NextAfter(si)) { Vector p = (si->p).Minus(axis.ScaledBy((si->p).Dot(axis))); double ub, vb; b->ClosestPointTo(p, &ub, &vb, /*mustConverge=*/true); SSurface plane; plane = SSurface::FromPlane(p, axis.Normal(0), axis.Normal(1)); b->PointOnSurfaces(this, &plane, &ub, &vb); p = b->PointAt(ub, vb); SBezier bezier; bezier = SBezier::From(p.Plus(axis0), p.Plus(axis1)); AddExactIntersectionCurve(&bezier, b, agnstA, agnstB, into); } inters.Clear(); lv.Clear(); } else { // Try intersecting the surfaces numerically, by a marching algorithm. // First, we find all the intersections between a surface and the // boundary of the other surface. SPointList spl = {}; int a; for(a = 0; a < 2; a++) { SShell *shA = (a == 0) ? agnstA : agnstB; SSurface *srfA = (a == 0) ? this : b, *srfB = (a == 0) ? b : this; SEdgeList el = {}; srfA->MakeEdgesInto(shA, &el, MakeAs::XYZ, NULL); SEdge *se; for(se = el.l.First(); se; se = el.l.NextAfter(se)) { List<SInter> lsi = {}; srfB->AllPointsIntersecting(se->a, se->b, &lsi, /*asSegment=*/true, /*trimmed=*/true, /*inclTangent=*/false); if(lsi.n == 0) continue; // Find the other surface that this curve trims. hSCurve hsc = { (uint32_t)se->auxA }; SCurve *sc = shA->curve.FindById(hsc); hSSurface hother = (sc->surfA.v == srfA->h.v) ? sc->surfB : sc->surfA; SSurface *other = shA->surface.FindById(hother); SInter *si; for(si = lsi.First(); si; si = lsi.NextAfter(si)) { Vector p = si->p; double u, v; srfB->ClosestPointTo(p, &u, &v); srfB->PointOnSurfaces(srfA, other, &u, &v); p = srfB->PointAt(u, v); if(!spl.ContainsPoint(p)) { SPoint sp; sp.p = p; // We also need the edge normal, so that we know in // which direction to march. srfA->ClosestPointTo(p, &u, &v); Vector n = srfA->NormalAt(u, v); sp.auxv = n.Cross((se->b).Minus(se->a)); sp.auxv = (sp.auxv).WithMagnitude(1); spl.l.Add(&sp); } } lsi.Clear(); } el.Clear(); } while(spl.l.n >= 2) { SCurve sc = {}; sc.surfA = h; sc.surfB = b->h; sc.isExact = false; sc.source = SCurve::Source::INTERSECTION; Vector start = spl.l.elem[0].p, startv = spl.l.elem[0].auxv; spl.l.ClearTags(); spl.l.elem[0].tag = 1; spl.l.RemoveTagged(); // Our chord tolerance is whatever the user specified double maxtol = SS.ChordTolMm(); int maxsteps = max(300, SS.GetMaxSegments()*3); // The curve starts at our starting point. SCurvePt padd = {}; padd.vertex = true; padd.p = start; sc.pts.Add(&padd); Point2d pa, pb; Vector np, npc = Vector::From(0, 0, 0); bool fwd = false; // Better to start with a too-small step, so that we don't miss // features of the curve entirely. double tol, step = maxtol; for(a = 0; a < maxsteps; a++) { ClosestPointTo(start, &pa); b->ClosestPointTo(start, &pb); Vector na = NormalAt(pa).WithMagnitude(1), nb = b->NormalAt(pb).WithMagnitude(1); if(a == 0) { Vector dp = nb.Cross(na); if(dp.Dot(startv) < 0) { // We want to march in the more inward direction. fwd = true; } else { fwd = false; } } int i; for(i = 0; i < 20; i++) { Vector dp = nb.Cross(na); if(!fwd) dp = dp.ScaledBy(-1); dp = dp.WithMagnitude(step); np = start.Plus(dp); npc = ClosestPointOnThisAndSurface(b, np); tol = (npc.Minus(np)).Magnitude(); if(tol > maxtol*0.8) { step *= 0.90; } else { step /= 0.90; } if((tol < maxtol) && (tol > maxtol/2)) { // If we meet the chord tolerance test, and we're // not too fine, then we break out. break; } } SPoint *sp; for(sp = spl.l.First(); sp; sp = spl.l.NextAfter(sp)) { if((sp->p).OnLineSegment(start, npc, 2*SS.ChordTolMm())) { sp->tag = 1; a = maxsteps; npc = sp->p; } } padd.p = npc; padd.vertex = (a == maxsteps); sc.pts.Add(&padd); start = npc; } spl.l.RemoveTagged(); // And now we split and insert the curve SCurve split = sc.MakeCopySplitAgainst(agnstA, agnstB, this, b); sc.Clear(); into->curve.AddAndAssignId(&split); } spl.Clear(); } }
//----------------------------------------------------------------------------- // Does the given point lie on our shell? There are many cases; inside and // outside are obvious, but then there's all the edge-on-edge and edge-on-face // possibilities. // // To calculate, we intersect a ray through p with our shell, and classify // using the closest intersection point. If the ray hits a surface on edge, // then just reattempt in a different random direction. //----------------------------------------------------------------------------- bool SShell::ClassifyEdge(int *indir, int *outdir, Vector ea, Vector eb, Vector p, Vector edge_n_in, Vector edge_n_out, Vector surf_n) { List<SInter> l; ZERO(&l); srand(0); // First, check for edge-on-edge int edge_inters = 0; Vector inter_surf_n[2], inter_edge_n[2]; SSurface *srf; for(srf = surface.First(); srf; srf = surface.NextAfter(srf)) { if(srf->LineEntirelyOutsideBbox(ea, eb, true)) continue; SEdgeList *sel = &(srf->edges); SEdge *se; for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) { if((ea.Equals(se->a) && eb.Equals(se->b)) || (eb.Equals(se->a) && ea.Equals(se->b)) || p.OnLineSegment(se->a, se->b)) { if(edge_inters < 2) { // Edge-on-edge case Point2d pm; srf->ClosestPointTo(p, &pm, false); // A vector normal to the surface, at the intersection point inter_surf_n[edge_inters] = srf->NormalAt(pm); // A vector normal to the intersecting edge (but within the // intersecting surface) at the intersection point, pointing // out. inter_edge_n[edge_inters] = (inter_surf_n[edge_inters]).Cross((se->b).Minus((se->a))); } edge_inters++; } } } if(edge_inters == 2) { // TODO, make this use the appropriate curved normals double dotp[2]; for(int i = 0; i < 2; i++) { dotp[i] = edge_n_out.DirectionCosineWith(inter_surf_n[i]); } if(fabs(dotp[1]) < DOTP_TOL) { SWAP(double, dotp[0], dotp[1]); SWAP(Vector, inter_surf_n[0], inter_surf_n[1]); SWAP(Vector, inter_edge_n[0], inter_edge_n[1]); } int coinc = (surf_n.Dot(inter_surf_n[0])) > 0 ? COINC_SAME : COINC_OPP; if(fabs(dotp[0]) < DOTP_TOL && fabs(dotp[1]) < DOTP_TOL) { // This is actually an edge on face case, just that the face // is split into two pieces joining at our edge. *indir = coinc; *outdir = coinc; } else if(fabs(dotp[0]) < DOTP_TOL && dotp[1] > DOTP_TOL) { if(edge_n_out.Dot(inter_edge_n[0]) > 0) { *indir = coinc; *outdir = OUTSIDE; } else { *indir = INSIDE; *outdir = coinc; } } else if(fabs(dotp[0]) < DOTP_TOL && dotp[1] < -DOTP_TOL) { if(edge_n_out.Dot(inter_edge_n[0]) > 0) { *indir = coinc; *outdir = INSIDE; } else { *indir = OUTSIDE; *outdir = coinc; } } else if(dotp[0] > DOTP_TOL && dotp[1] > DOTP_TOL) { *indir = INSIDE; *outdir = OUTSIDE; } else if(dotp[0] < -DOTP_TOL && dotp[1] < -DOTP_TOL) { *indir = OUTSIDE; *outdir = INSIDE; } else { // Edge is tangent to the shell at shell's edge, so can't be // a boundary of the surface. return false; } return true; } if(edge_inters != 0) dbp("bad, edge_inters=%d", edge_inters); // Next, check for edge-on-surface. The ray-casting for edge-inside-shell // would catch this too, but test separately, for speed (since many edges // are on surface) and for numerical stability, so we don't pick up // the additional error from the line intersection. for(srf = surface.First(); srf; srf = surface.NextAfter(srf)) { if(srf->LineEntirelyOutsideBbox(ea, eb, true)) continue; Point2d puv; srf->ClosestPointTo(p, &(puv.x), &(puv.y), false); Vector pp = srf->PointAt(puv); if((pp.Minus(p)).Magnitude() > LENGTH_EPS) continue; Point2d dummy = { 0, 0 }; int c = srf->bsp->ClassifyPoint(puv, dummy, srf); if(c == SBspUv::OUTSIDE) continue; // Edge-on-face (unless edge-on-edge above superceded) Point2d pin, pout; srf->ClosestPointTo(p.Plus(edge_n_in), &pin, false); srf->ClosestPointTo(p.Plus(edge_n_out), &pout, false); Vector surf_n_in = srf->NormalAt(pin), surf_n_out = srf->NormalAt(pout); *indir = ClassifyRegion(edge_n_in, surf_n_in, surf_n); *outdir = ClassifyRegion(edge_n_out, surf_n_out, surf_n); return true; } // Edge is not on face or on edge; so it's either inside or outside // the shell, and we'll determine which by raycasting. int cnt = 0; for(;;) { // Cast a ray in a random direction (two-sided so that we test if // the point lies on a surface, but use only one side for in/out // testing) Vector ray = Vector::From(Random(1), Random(1), Random(1)); AllPointsIntersecting( p.Minus(ray), p.Plus(ray), &l, false, true, false); // no intersections means it's outside *indir = OUTSIDE; *outdir = OUTSIDE; double dmin = VERY_POSITIVE; bool onEdge = false; edge_inters = 0; SInter *si; for(si = l.First(); si; si = l.NextAfter(si)) { double t = ((si->p).Minus(p)).DivPivoting(ray); if(t*ray.Magnitude() < -LENGTH_EPS) { // wrong side, doesn't count continue; } double d = ((si->p).Minus(p)).Magnitude(); // We actually should never hit this case; it should have been // handled above. if(d < LENGTH_EPS && si->onEdge) { edge_inters++; } if(d < dmin) { dmin = d; // Edge does not lie on surface; either strictly inside // or strictly outside if((si->surfNormal).Dot(ray) > 0) { *indir = INSIDE; *outdir = INSIDE; } else { *indir = OUTSIDE; *outdir = OUTSIDE; } onEdge = si->onEdge; } } l.Clear(); // If the point being tested lies exactly on an edge of the shell, // then our ray always lies on edge, and that's okay. Otherwise // try again in a different random direction. if(!onEdge) break; 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); SS.nakedEdges.AddEdge(ea, eb); break; } } return true; }
void StepFileWriter::ExportSurfacesTo(char *file) { Group *g = SK.GetGroup(SS.GW.activeGroup); SShell *shell = &(g->runningShell); if(shell->surface.n == 0) { Error("The model does not contain any surfaces to export.%s", g->runningMesh.l.n > 0 ? "\n\nThe model does contain triangles from a mesh, but " "a triangle mesh cannot be exported as a STEP file. Try " "File -> Export Mesh... instead." : ""); return; } f = fopen(file, "wb"); if(!f) { Error("Couldn't write to '%s'", file); return; } WriteHeader(); WriteProductHeader(); ZERO(&advancedFaces); SSurface *ss; for(ss = shell->surface.First(); ss; ss = shell->surface.NextAfter(ss)) { if(ss->trim.n == 0) continue; // Get all of the loops of Beziers that trim our surface (with each // Bezier split so that we use the section as t goes from 0 to 1), and // the piecewise linearization of those loops in xyz space. SBezierList sbl; ZERO(&sbl); ss->MakeSectionEdgesInto(shell, NULL, &sbl); // Apply the export scale factor. ss->ScaleSelfBy(1.0/SS.exportScale); sbl.ScaleSelfBy(1.0/SS.exportScale); ExportSurface(ss, &sbl); sbl.Clear(); } fprintf(f, "#%d=CLOSED_SHELL('',(", id); int *af; for(af = advancedFaces.First(); af; af = advancedFaces.NextAfter(af)) { fprintf(f, "#%d", *af); if(advancedFaces.NextAfter(af) != NULL) fprintf(f, ","); } fprintf(f, "));\n"); fprintf(f, "#%d=MANIFOLD_SOLID_BREP('brep',#%d);\n", id+1, id); fprintf(f, "#%d=ADVANCED_BREP_SHAPE_REPRESENTATION('',(#%d,#170),#168);\n", id+2, id+1); fprintf(f, "#%d=SHAPE_REPRESENTATION_RELATIONSHIP($,$,#169,#%d);\n", id+3, id+2); WriteFooter(); fclose(f); advancedFaces.Clear(); }