int StepFileWriter::ExportCurveLoop(SBezierLoop *loop, bool inner) { if(loop->l.n < 1) oops(); List<int> listOfTrims; ZERO(&listOfTrims); SBezier *sb = &(loop->l.elem[loop->l.n - 1]); // Generate "exactly closed" contours, with the same vertex id for the // finish of a previous edge and the start of the next one. So we need // the finish of the last Bezier in the loop before we start our process. fprintf(f, "#%d=CARTESIAN_POINT('',(%.10f,%.10f,%.10f));\n", id, CO(sb->Finish())); fprintf(f, "#%d=VERTEX_POINT('',#%d);\n", id+1, id); int lastFinish = id + 1, prevFinish = lastFinish; id += 2; for(sb = loop->l.First(); sb; sb = loop->l.NextAfter(sb)) { int curveId = ExportCurve(sb); int thisFinish; if(loop->l.NextAfter(sb) != NULL) { fprintf(f, "#%d=CARTESIAN_POINT('',(%.10f,%.10f,%.10f));\n", id, CO(sb->Finish())); fprintf(f, "#%d=VERTEX_POINT('',#%d);\n", id+1, id); thisFinish = id + 1; id += 2; } else { thisFinish = lastFinish; } fprintf(f, "#%d=EDGE_CURVE('',#%d,#%d,#%d,%s);\n", id, prevFinish, thisFinish, curveId, ".T."); fprintf(f, "#%d=ORIENTED_EDGE('',*,*,#%d,.T.);\n", id+1, id); int oe = id+1; listOfTrims.Add(&oe); id += 2; prevFinish = thisFinish; } fprintf(f, "#%d=EDGE_LOOP('',(", id); int *oe; for(oe = listOfTrims.First(); oe; oe = listOfTrims.NextAfter(oe)) { fprintf(f, "#%d", *oe); if(listOfTrims.NextAfter(oe) != NULL) fprintf(f, ","); } fprintf(f, "));\n"); int fb = id + 1; fprintf(f, "#%d=%s('',#%d,.T.);\n", fb, inner ? "FACE_BOUND" : "FACE_OUTER_BOUND", id); id += 2; listOfTrims.Clear(); return fb; }
void SBezierLoop::GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax) { SBezier *sb; for(sb = l.First(); sb; sb = l.NextAfter(sb)) { sb->GetBoundingProjd(u, orig, umin, umax); } }
void SBezierLoop::Reverse(void) { l.Reverse(); SBezier *sb; for(sb = l.First(); sb; sb = l.NextAfter(sb)) { // If we didn't reverse each curve, then the next curve in list would // share your start, not your finish. sb->Reverse(); } }
bool SSurface::IsCylinder(Vector *axis, Vector *center, double *r, Vector *start, Vector *finish) { SBezier sb; if(!IsExtrusion(&sb, axis)) return false; if(!sb.IsCircle(*axis, center, r)) return false; *start = sb.ctrl[0]; *finish = sb.ctrl[2]; return true; }
void SBezierLoop::MakePwlInto(SContour *sc, double chordTol) { SBezier *sb; for(sb = l.First(); sb; sb = l.NextAfter(sb)) { sb->MakePwlInto(sc, chordTol); // Avoid double points at join between Beziers; except that // first and last points should be identical. if(l.NextAfter(sb) != NULL) { sc->l.RemoveLast(1); } } // Ensure that it's exactly closed, not just within a numerical tolerance. if((sc->l.elem[sc->l.n - 1].p).Equals(sc->l.elem[0].p)) { sc->l.elem[sc->l.n - 1] = sc->l.elem[0]; } }
//----------------------------------------------------------------------------- // Assemble curves in sbl into a single loop. The curves may appear in any // direction (start to finish, or finish to start), and will be reversed if // necessary. The curves in the returned loop are removed from sbl, even if // the loop cannot be closed. //----------------------------------------------------------------------------- SBezierLoop SBezierLoop::FromCurves(SBezierList *sbl, bool *allClosed, SEdge *errorAt) { SBezierLoop loop; ZERO(&loop); if(sbl->l.n < 1) return loop; sbl->l.ClearTags(); SBezier *first = &(sbl->l.elem[0]); first->tag = 1; loop.l.Add(first); Vector start = first->Start(); Vector hanging = first->Finish(); int auxA = first->auxA; sbl->l.RemoveTagged(); while(sbl->l.n > 0 && !hanging.Equals(start)) { int i; bool foundNext = false; for(i = 0; i < sbl->l.n; i++) { SBezier *test = &(sbl->l.elem[i]); if((test->Finish()).Equals(hanging) && test->auxA == auxA) { test->Reverse(); // and let the next test catch it } if((test->Start()).Equals(hanging) && test->auxA == auxA) { test->tag = 1; loop.l.Add(test); hanging = test->Finish(); sbl->l.RemoveTagged(); foundNext = true; break; } } 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)) { *allClosed = true; } else { // We ran out of edges without forming a closed loop. errorAt->a = hanging; errorAt->b = start; *allClosed = false; } return loop; }
//----------------------------------------------------------------------------- // If our list contains multiple identical Beziers (in either forward or // reverse order), then cull them. //----------------------------------------------------------------------------- void SBezierList::CullIdenticalBeziers(void) { int i, j; l.ClearTags(); for(i = 0; i < l.n; i++) { SBezier *bi = &(l.elem[i]), bir; bir = *bi; bir.Reverse(); for(j = i + 1; j < l.n; j++) { SBezier *bj = &(l.elem[j]); if(bj->Equals(bi) || bj->Equals(&bir)) { bi->tag = 1; bj->tag = 1; } } } l.RemoveTagged(); }
void Entity::GenerateEdges(SEdgeList *el, bool includingConstruction) { if(construction && !includingConstruction) return; SBezierList sbl; ZERO(&sbl); GenerateBezierCurves(&sbl); int i, j; for(i = 0; i < sbl.l.n; i++) { SBezier *sb = &(sbl.l.elem[i]); List<Vector> lv; ZERO(&lv); sb->MakePwlInto(&lv); for(j = 1; j < lv.n; j++) { el->AddEdge(lv.elem[j-1], lv.elem[j], style.v); } lv.Clear(); } sbl.Clear(); }
void VectorFileWriter::BezierAsNonrationalCubic(SBezier *sb, int depth) { Vector t0 = sb->TangentAt(0), t1 = sb->TangentAt(1); // The curve is correct, and the first derivatives are correct, at the // endpoints. SBezier bnr = SBezier::From( sb->Start(), sb->Start().Plus(t0.ScaledBy(1.0/3)), sb->Finish().Minus(t1.ScaledBy(1.0/3)), sb->Finish()); double tol = SS.ChordTolMm() / SS.exportScale; // Arbitrary choice, but make it a little finer than pwl tolerance since // it should be easier to achieve that with the smooth curves. tol /= 2; bool closeEnough = true; int i; for(i = 1; i <= 3; i++) { double t = i/4.0; Vector p0 = sb->PointAt(t), pn = bnr.PointAt(t); double d = (p0.Minus(pn)).Magnitude(); if(d > tol) { closeEnough = false; } } if(closeEnough || depth > 3) { Bezier(&bnr); } else { SBezier bef, aft; sb->SplitAt(0.5, &bef, &aft); BezierAsNonrationalCubic(&bef, depth+1); BezierAsNonrationalCubic(&aft, depth+1); } }
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; } } } }
void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, RgbaColor color) { // Make the extrusion direction consistent with respect to the normal // of the sketch we're extruding. if((t0.Minus(t1)).Dot(sbls->normal) < 0) { swap(t0, t1); } // Define a coordinate system to contain the original sketch, and get // a bounding box in that csys Vector n = sbls->normal.ScaledBy(-1); Vector u = n.Normal(0), v = n.Normal(1); Vector orig = sbls->point; double umax = 1e-10, umin = 1e10; sbls->GetBoundingProjd(u, orig, &umin, &umax); double vmax = 1e-10, vmin = 1e10; sbls->GetBoundingProjd(v, orig, &vmin, &vmax); // and now fix things up so that all u and v lie between 0 and 1 orig = orig.Plus(u.ScaledBy(umin)); orig = orig.Plus(v.ScaledBy(vmin)); u = u.ScaledBy(umax - umin); v = v.ScaledBy(vmax - vmin); // So we can now generate the top and bottom surfaces of the extrusion, // planes within a translated (and maybe mirrored) version of that csys. SSurface s0, s1; s0 = SSurface::FromPlane(orig.Plus(t0), u, v); s0.color = color; s1 = SSurface::FromPlane(orig.Plus(t1).Plus(u), u.ScaledBy(-1), v); s1.color = color; hSSurface hs0 = surface.AddAndAssignId(&s0), hs1 = surface.AddAndAssignId(&s1); // Now go through the input curves. For each one, generate its surface // of extrusion, its two translated trim curves, and one trim line. We // go through by loops so that we can assign the lines correctly. SBezierLoop *sbl; for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) { SBezier *sb; List<TrimLine> trimLines = {}; for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) { // Generate the surface of extrusion of this curve, and add // it to the list SSurface ss = SSurface::FromExtrusionOf(sb, t0, t1); ss.color = color; hSSurface hsext = surface.AddAndAssignId(&ss); // Translate the curve by t0 and t1 to produce two trim curves SCurve sc = {}; sc.isExact = true; sc.exact = sb->TransformedBy(t0, Quaternion::IDENTITY, 1.0); (sc.exact).MakePwlInto(&(sc.pts)); sc.surfA = hs0; sc.surfB = hsext; hSCurve hc0 = curve.AddAndAssignId(&sc); sc = {}; sc.isExact = true; sc.exact = sb->TransformedBy(t1, Quaternion::IDENTITY, 1.0); (sc.exact).MakePwlInto(&(sc.pts)); sc.surfA = hs1; sc.surfB = hsext; hSCurve hc1 = curve.AddAndAssignId(&sc); STrimBy stb0, stb1; // The translated curves trim the flat top and bottom surfaces. stb0 = STrimBy::EntireCurve(this, hc0, false); stb1 = STrimBy::EntireCurve(this, hc1, true); (surface.FindById(hs0))->trim.Add(&stb0); (surface.FindById(hs1))->trim.Add(&stb1); // The translated curves also trim the surface of extrusion. stb0 = STrimBy::EntireCurve(this, hc0, true); stb1 = STrimBy::EntireCurve(this, hc1, false); (surface.FindById(hsext))->trim.Add(&stb0); (surface.FindById(hsext))->trim.Add(&stb1); // And form the trim line Vector pt = sb->Finish(); sc = {}; 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; tl.hc = hl; tl.hs = hsext; trimLines.Add(&tl); } int i; for(i = 0; i < trimLines.n; i++) { TrimLine *tl = &(trimLines.elem[i]); SSurface *ss = surface.FindById(tl->hs); TrimLine *tlp = &(trimLines.elem[WRAP(i-1, trimLines.n)]); STrimBy stb; stb = STrimBy::EntireCurve(this, tl->hc, true); ss->trim.Add(&stb); stb = STrimBy::EntireCurve(this, tlp->hc, false); ss->trim.Add(&stb); (curve.FindById(tl->hc))->surfA = ss->h; (curve.FindById(tlp->hc))->surfB = ss->h; } trimLines.Clear(); } }
//----------------------------------------------------------------------------- // Report our trim curves. If a trim curve is exact and sbl is not null, then // add its exact form to sbl. Otherwise, add its piecewise linearization to // sel. //----------------------------------------------------------------------------- void SSurface::MakeSectionEdgesInto(SShell *shell, SEdgeList *sel, SBezierList *sbl) { STrimBy *stb; for(stb = trim.First(); stb; stb = trim.NextAfter(stb)) { SCurve *sc = shell->curve.FindById(stb->curve); SBezier *sb = &(sc->exact); if(sbl && sc->isExact && (sb->deg != 1 || !sel)) { double ts, tf; if(stb->backwards) { sb->ClosestPointTo(stb->start, &tf); sb->ClosestPointTo(stb->finish, &ts); } else { sb->ClosestPointTo(stb->start, &ts); sb->ClosestPointTo(stb->finish, &tf); } SBezier junk_bef, keep_aft; sb->SplitAt(ts, &junk_bef, &keep_aft); // In the kept piece, the range that used to go from ts to 1 // now goes from 0 to 1; so rescale tf appropriately. tf = (tf - ts)/(1 - ts); SBezier keep_bef, junk_aft; keep_aft.SplitAt(tf, &keep_bef, &junk_aft); sbl->l.Add(&keep_bef); } else if(sbl && !sel && !sc->isExact) { // We must approximate this trim curve, as piecewise cubic sections. SSurface *srfA = shell->surface.FindById(sc->surfA), *srfB = shell->surface.FindById(sc->surfB); Vector s = stb->backwards ? stb->finish : stb->start, f = stb->backwards ? stb->start : stb->finish; int sp, fp; for(sp = 0; sp < sc->pts.n; sp++) { if(s.Equals(sc->pts.elem[sp].p)) break; } if(sp >= sc->pts.n) return; for(fp = sp; fp < sc->pts.n; fp++) { if(f.Equals(sc->pts.elem[fp].p)) break; } if(fp >= sc->pts.n) return; // So now the curve we want goes from elem[sp] to elem[fp] while(sp < fp) { // Initially, we'll try approximating the entire trim curve // as a single Bezier segment int fpt = fp; for(;;) { // So construct a cubic Bezier with the correct endpoints // and tangents for the current span. Vector st = sc->pts.elem[sp].p, ft = sc->pts.elem[fpt].p, sf = ft.Minus(st); double m = sf.Magnitude() / 3; Vector stan = ExactSurfaceTangentAt(st, srfA, srfB, sf), ftan = ExactSurfaceTangentAt(ft, srfA, srfB, sf); SBezier sb = SBezier::From(st, st.Plus (stan.WithMagnitude(m)), ft.Minus(ftan.WithMagnitude(m)), ft); // And test how much this curve deviates from the // intermediate points (if any). int i; bool tooFar = false; for(i = sp + 1; i <= (fpt - 1); i++) { Vector p = sc->pts.elem[i].p; double t; sb.ClosestPointTo(p, &t, false); Vector pp = sb.PointAt(t); if((pp.Minus(p)).Magnitude() > SS.ChordTolMm()/2) { tooFar = true; break; } } if(tooFar) { // Deviates by too much, so try a shorter span fpt--; continue; } else { // Okay, so use this piece and break. sbl->l.Add(&sb); break; } } // And continue interpolating, starting wherever the curve // we just generated finishes. sp = fpt; } } else { if(sel) MakeTrimEdgesInto(sel, AS_XYZ, sc, stb); } } }
hEntity GraphicsWindow::SplitCubic(hEntity he, Vector pinter) { // Save the original endpoints, since we're about to delete this entity. Entity *e01 = SK.GetEntity(he); SBezierList sbl; ZERO(&sbl); e01->GenerateBezierCurves(&sbl); hEntity hep0 = e01->point[0], hep1 = e01->point[3+e01->extraPoints], hep0n = Entity::NO_ENTITY, // the new start point hep1n = Entity::NO_ENTITY, // the new finish point hepin = Entity::NO_ENTITY; // the intersection point // The curve may consist of multiple cubic segments. So find which one // contains the intersection point. double t; int i, j; for(i = 0; i < sbl.l.n; i++) { SBezier *sb = &(sbl.l.elem[i]); if(sb->deg != 3) oops(); sb->ClosestPointTo(pinter, &t, false); if(pinter.Equals(sb->PointAt(t))) { // Split that segment at the intersection. SBezier b0i, bi1, b01 = *sb; b01.SplitAt(t, &b0i, &bi1); // Add the two cubic segments this one gets split into. hRequest r0i = AddRequest(Request::CUBIC, false), ri1 = AddRequest(Request::CUBIC, false); // Don't get entities till after adding, realloc issues Entity *e0i = SK.GetEntity(r0i.entity(0)), *ei1 = SK.GetEntity(ri1.entity(0)); for(j = 0; j <= 3; j++) { SK.GetEntity(e0i->point[j])->PointForceTo(b0i.ctrl[j]); } for(j = 0; j <= 3; j++) { SK.GetEntity(ei1->point[j])->PointForceTo(bi1.ctrl[j]); } Constraint::ConstrainCoincident(e0i->point[3], ei1->point[0]); if(i == 0) hep0n = e0i->point[0]; hep1n = ei1->point[3]; hepin = e0i->point[3]; } else { hRequest r = AddRequest(Request::CUBIC, false); Entity *e = SK.GetEntity(r.entity(0)); for(j = 0; j <= 3; j++) { SK.GetEntity(e->point[j])->PointForceTo(sb->ctrl[j]); } if(i == 0) hep0n = e->point[0]; hep1n = e->point[3]; } } sbl.Clear(); ReplacePointInConstraints(hep0, hep0n); ReplacePointInConstraints(hep1, hep1n); return hepin; }
void SBezierList::ScaleSelfBy(double s) { SBezier *sb; for(sb = l.First(); sb; sb = l.NextAfter(sb)) { sb->ScaleSelfBy(s); } }
void SSurface::AddExactIntersectionCurve(SBezier *sb, SSurface *srfB, SShell *agnstA, SShell *agnstB, SShell *into) { SCurve 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; sc.exact = *sb; sc.isExact = true; // 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) { SCurvePt *v; for(v = existing->pts.First(); v; v = existing->pts.NextAfter(v)) { sc.pts.Add(v); } if(backwards) sc.pts.Reverse(); split = sc; 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(); } // Test if the curve lies entirely outside one of the SCurvePt *scpt; bool withinA = false, withinB = false; for(scpt = split.pts.First(); scpt; scpt = split.pts.NextAfter(scpt)) { double tol = 0.01; Point2d puv; ClosestPointTo(scpt->p, &puv); if(puv.x > -tol && puv.x < 1 + tol && puv.y > -tol && puv.y < 1 + tol) { withinA = true; } srfB->ClosestPointTo(scpt->p, &puv); if(puv.x > -tol && puv.x < 1 + tol && puv.y > -tol && puv.y < 1 + tol) { withinB = true; } // Break out early, no sense wasting time if we already have the answer. if(withinA && withinB) break; } if(!(withinA && withinB)) { // Intersection curve lies entirely outside one of the surfaces, so // it's fake. split.Clear(); return; } #if 0 if(sb->deg == 2) { dbp(" "); SCurvePt *prev = NULL, *v; dbp("split.pts.n = %d", split.pts.n); for(v = split.pts.First(); v; v = split.pts.NextAfter(v)) { if(prev) { Vector e = (prev->p).Minus(v->p).WithMagnitude(0); SS.nakedEdges.AddEdge((prev->p).Plus(e), (v->p).Minus(e)); } prev = v; } } #endif // 0 ssassert(!(sb->Start()).Equals(sb->Finish()), "Unexpected zero-length edge"); split.source = SCurve::Source::INTERSECTION; into->curve.AddAndAssignId(&split); }
void SolveSpace::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm, Vector u, Vector v, Vector n, Vector origin, double cameraTan, VectorFileWriter *out) { double s = 1.0 / SS.exportScale; // Project into the export plane; so when we're done, z doesn't matter, // and x and y are what goes in the DXF. SEdge *e; for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) { // project into the specified csys, and apply export scale (e->a) = e->a.InPerspective(u, v, n, origin, cameraTan).ScaledBy(s); (e->b) = e->b.InPerspective(u, v, n, origin, cameraTan).ScaledBy(s); } SBezier *b; if(sbl) { for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) { *b = b->InPerspective(u, v, n, origin, cameraTan); int i; for(i = 0; i <= b->deg; i++) { b->ctrl[i] = (b->ctrl[i]).ScaledBy(s); } } } // If cutter radius compensation is requested, then perform it now if(fabs(SS.exportOffset) > LENGTH_EPS) { // assemble those edges into a polygon, and clear the edge list SPolygon sp; ZERO(&sp); sel->AssemblePolygon(&sp, NULL); sel->Clear(); SPolygon compd; ZERO(&compd); sp.normal = Vector::From(0, 0, -1); sp.FixContourDirections(); sp.OffsetInto(&compd, SS.exportOffset*s); sp.Clear(); compd.MakeEdgesInto(sel); compd.Clear(); } // Now the triangle mesh; project, then build a BSP to perform // occlusion testing and generated the shaded surfaces. SMesh smp; ZERO(&smp); if(sm) { Vector l0 = (SS.lightDir[0]).WithMagnitude(1), l1 = (SS.lightDir[1]).WithMagnitude(1); STriangle *tr; for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) { STriangle tt = *tr; tt.a = (tt.a).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s); tt.b = (tt.b).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s); tt.c = (tt.c).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s); // And calculate lighting for the triangle Vector n = tt.Normal().WithMagnitude(1); double lighting = SS.ambientIntensity + max(0, (SS.lightIntensity[0])*(n.Dot(l0))) + max(0, (SS.lightIntensity[1])*(n.Dot(l1))); double r = min(1, REDf (tt.meta.color)*lighting), g = min(1, GREENf(tt.meta.color)*lighting), b = min(1, BLUEf (tt.meta.color)*lighting); tt.meta.color = RGBf(r, g, b); smp.AddTriangle(&tt); } } // Use the BSP routines to generate the split triangles in paint order. SBsp3 *bsp = SBsp3::FromMesh(&smp); SMesh sms; ZERO(&sms); bsp->GenerateInPaintOrder(&sms); // And cull the back-facing triangles STriangle *tr; sms.l.ClearTags(); for(tr = sms.l.First(); tr; tr = sms.l.NextAfter(tr)) { Vector n = tr->Normal(); if(n.z < 0) { tr->tag = 1; } } sms.l.RemoveTagged(); // And now we perform hidden line removal if requested SEdgeList hlrd; ZERO(&hlrd); if(sm && !SS.GW.showHdnLines) { SKdNode *root = SKdNode::From(&smp); // Generate the edges where a curved surface turns from front-facing // to back-facing. if(SS.GW.showEdges) { root->MakeCertainEdgesInto(sel, SKdNode::TURNING_EDGES, false, NULL, NULL); } root->ClearTags(); int cnt = 1234; SEdge *se; for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) { if(se->auxA == Style::CONSTRAINT) { // Constraints should not get hidden line removed; they're // always on top. hlrd.AddEdge(se->a, se->b, se->auxA); continue; } SEdgeList out; ZERO(&out); // Split the original edge against the mesh out.AddEdge(se->a, se->b, se->auxA); root->OcclusionTestLine(*se, &out, cnt); // the occlusion test splits unnecessarily; so fix those out.MergeCollinearSegments(se->a, se->b); cnt++; // And add the results to our output SEdge *sen; for(sen = out.l.First(); sen; sen = out.l.NextAfter(sen)) { hlrd.AddEdge(sen->a, sen->b, sen->auxA); } out.Clear(); } sel = &hlrd; } // We kept the line segments and Beziers separate until now; but put them // all together, and also project everything into the xy plane, since not // all export targets ignore the z component of the points. for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) { SBezier sb = SBezier::From(e->a, e->b); sb.auxA = e->auxA; sbl->l.Add(&sb); } for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) { for(int i = 0; i <= b->deg; i++) { b->ctrl[i].z = 0; } } // If possible, then we will assemble these output curves into loops. They // will then get exported as closed paths. SBezierLoopSetSet sblss; ZERO(&sblss); SBezierList leftovers; ZERO(&leftovers); SSurface srf = SSurface::FromPlane(Vector::From(0, 0, 0), Vector::From(1, 0, 0), Vector::From(0, 1, 0)); SPolygon spxyz; ZERO(&spxyz); bool allClosed; SEdge notClosedAt; sbl->l.ClearTags(); sblss.FindOuterFacesFrom(sbl, &spxyz, &srf, SS.ChordTolMm()*s, &allClosed, ¬ClosedAt, NULL, NULL, &leftovers); for(b = leftovers.l.First(); b; b = leftovers.l.NextAfter(b)) { sblss.AddOpenPath(b); } // Now write the lines and triangles to the output file out->Output(&sblss, &sms); leftovers.Clear(); spxyz.Clear(); sblss.Clear(); smp.Clear(); sms.Clear(); hlrd.Clear(); }