//----------------------------------------------------------------------------- // 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; }
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; } } } }