Solvespace
614 строк · 23.3 Кб
1//-----------------------------------------------------------------------------
2// Anything involving NURBS shells (i.e., shells); except
3// for the real math, which is in ratpoly.cpp.
4//
5// Copyright 2008-2013 Jonathan Westhues.
6//-----------------------------------------------------------------------------
7#include "../solvespace.h"8
9typedef struct {10hSCurve hc;11hSSurface hs;12} TrimLine;13
14
15void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, RgbaColor color)16{
17// Make the extrusion direction consistent with respect to the normal18// of the sketch we're extruding.19if((t0.Minus(t1)).Dot(sbls->normal) < 0) {20swap(t0, t1);21}22
23// Define a coordinate system to contain the original sketch, and get24// a bounding box in that csys25Vector n = sbls->normal.ScaledBy(-1);26Vector u = n.Normal(0), v = n.Normal(1);27Vector orig = sbls->point;28double umax = VERY_NEGATIVE, umin = VERY_POSITIVE;29sbls->GetBoundingProjd(u, orig, &umin, &umax);30double vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE;31sbls->GetBoundingProjd(v, orig, &vmin, &vmax);32// and now fix things up so that all u and v lie between 0 and 133orig = orig.Plus(u.ScaledBy(umin));34orig = orig.Plus(v.ScaledBy(vmin));35u = u.ScaledBy(umax - umin);36v = v.ScaledBy(vmax - vmin);37
38// So we can now generate the top and bottom surfaces of the extrusion,39// planes within a translated (and maybe mirrored) version of that csys.40SSurface s0, s1;41s0 = SSurface::FromPlane(orig.Plus(t0), u, v);42s0.color = color;43s1 = SSurface::FromPlane(orig.Plus(t1).Plus(u), u.ScaledBy(-1), v);44s1.color = color;45hSSurface hs0 = surface.AddAndAssignId(&s0),46hs1 = surface.AddAndAssignId(&s1);47
48// Now go through the input curves. For each one, generate its surface49// of extrusion, its two translated trim curves, and one trim line. We50// go through by loops so that we can assign the lines correctly.51SBezierLoop *sbl;52for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {53SBezier *sb;54List<TrimLine> trimLines = {};55
56for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {57// Generate the surface of extrusion of this curve, and add58// it to the list59SSurface ss = SSurface::FromExtrusionOf(sb, t0, t1);60ss.color = color;61hSSurface hsext = surface.AddAndAssignId(&ss);62
63// Translate the curve by t0 and t1 to produce two trim curves64SCurve sc = {};65sc.isExact = true;66sc.exact = sb->TransformedBy(t0, Quaternion::IDENTITY, 1.0);67(sc.exact).MakePwlInto(&(sc.pts));68sc.surfA = hs0;69sc.surfB = hsext;70hSCurve hc0 = curve.AddAndAssignId(&sc);71
72sc = {};73sc.isExact = true;74sc.exact = sb->TransformedBy(t1, Quaternion::IDENTITY, 1.0);75(sc.exact).MakePwlInto(&(sc.pts));76sc.surfA = hs1;77sc.surfB = hsext;78hSCurve hc1 = curve.AddAndAssignId(&sc);79
80STrimBy stb0, stb1;81// The translated curves trim the flat top and bottom surfaces.82stb0 = STrimBy::EntireCurve(this, hc0, /*backwards=*/false);83stb1 = STrimBy::EntireCurve(this, hc1, /*backwards=*/true);84(surface.FindById(hs0))->trim.Add(&stb0);85(surface.FindById(hs1))->trim.Add(&stb1);86
87// The translated curves also trim the surface of extrusion.88stb0 = STrimBy::EntireCurve(this, hc0, /*backwards=*/true);89stb1 = STrimBy::EntireCurve(this, hc1, /*backwards=*/false);90(surface.FindById(hsext))->trim.Add(&stb0);91(surface.FindById(hsext))->trim.Add(&stb1);92
93// And form the trim line94Vector pt = sb->Finish();95sc = {};96sc.isExact = true;97sc.exact = SBezier::From(pt.Plus(t0), pt.Plus(t1));98(sc.exact).MakePwlInto(&(sc.pts));99hSCurve hl = curve.AddAndAssignId(&sc);100// save this for later101TrimLine tl;102tl.hc = hl;103tl.hs = hsext;104trimLines.Add(&tl);105}106
107int i;108for(i = 0; i < trimLines.n; i++) {109TrimLine *tl = &(trimLines[i]);110SSurface *ss = surface.FindById(tl->hs);111
112TrimLine *tlp = &(trimLines[WRAP(i-1, trimLines.n)]);113
114STrimBy stb;115stb = STrimBy::EntireCurve(this, tl->hc, /*backwards=*/true);116ss->trim.Add(&stb);117stb = STrimBy::EntireCurve(this, tlp->hc, /*backwards=*/false);118ss->trim.Add(&stb);119
120(curve.FindById(tl->hc))->surfA = ss->h;121(curve.FindById(tlp->hc))->surfB = ss->h;122}123trimLines.Clear();124}125}
126
127bool SShell::CheckNormalAxisRelationship(SBezierLoopSet *sbls, Vector pt, Vector axis, double da, double dx)128// Check that the direction of revolution/extrusion ends up parallel to the normal of
129// the sketch, on the side of the axis where the sketch is.
130{
131SBezierLoop *sbl;132Vector pto;133double md = VERY_NEGATIVE;134for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {135SBezier *sb;136for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {137// Choose the point farthest from the axis; we'll get garbage138// if we choose a point that lies on the axis, for example.139// (And our surface will be self-intersecting if the sketch140// spans the axis, so don't worry about that.)141for(int i = 0; i <= sb->deg; i++) {142Vector p = sb->ctrl[i];143double d = p.DistanceToLine(pt, axis);144if(d > md) {145md = d;146pto = p;147}148}149}150}151Vector ptc = pto.ClosestPointOnLine(pt, axis),152up = axis.Cross(pto.Minus(ptc)).ScaledBy(da),153vp = up.Plus(axis.ScaledBy(dx));154
155return (vp.Dot(sbls->normal) > 0);156}
157
158// sketch must not contain the axis of revolution as a non-construction line for helix
159void SShell::MakeFromHelicalRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis,160RgbaColor color, Group *group, double angles,161double anglef, double dists, double distf) {162int i0 = surface.n; // number of pre-existing surfaces163SBezierLoop *sbl;164// for testing - hard code the axial distance, and number of sections.165// distance will need to be parameters in the future.166double dist = distf - dists;167int sections = (int)(fabs(anglef - angles) / (PI / 2) + 1);168double wedge = (anglef - angles) / sections;169int startMapping = Group::REMAP_LATHE_START, endMapping = Group::REMAP_LATHE_END;170
171if(CheckNormalAxisRelationship(sbls, pt, axis, anglef-angles, distf-dists)) {172swap(angles, anglef);173swap(dists, distf);174dist = -dist;175wedge = -wedge;176swap(startMapping, endMapping);177}178
179// Define a coordinate system to contain the original sketch, and get180// a bounding box in that csys181Vector n = sbls->normal.ScaledBy(-1);182Vector u = n.Normal(0), v = n.Normal(1);183Vector orig = sbls->point;184double umax = VERY_NEGATIVE, umin = VERY_POSITIVE;185sbls->GetBoundingProjd(u, orig, &umin, &umax);186double vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE;187sbls->GetBoundingProjd(v, orig, &vmin, &vmax);188// and now fix things up so that all u and v lie between 0 and 1189orig = orig.Plus(u.ScaledBy(umin));190orig = orig.Plus(v.ScaledBy(vmin));191u = u.ScaledBy(umax - umin);192v = v.ScaledBy(vmax - vmin);193
194// So we can now generate the end caps of the extrusion within195// a translated and rotated (and maybe mirrored) version of that csys.196SSurface s0, s1;197s0 = SSurface::FromPlane(orig.RotatedAbout(pt, axis, angles).Plus(axis.ScaledBy(dists)),198u.RotatedAbout(axis, angles), v.RotatedAbout(axis, angles));199s0.color = color;200
201hEntity face0 = group->Remap(Entity::NO_ENTITY, startMapping);202s0.face = face0.v;203
204s1 = SSurface::FromPlane(205orig.Plus(u).RotatedAbout(pt, axis, anglef).Plus(axis.ScaledBy(distf)),206u.ScaledBy(-1).RotatedAbout(axis, anglef), v.RotatedAbout(axis, anglef));207s1.color = color;208
209hEntity face1 = group->Remap(Entity::NO_ENTITY, endMapping);210s1.face = face1.v;211
212hSSurface hs0 = surface.AddAndAssignId(&s0);213hSSurface hs1 = surface.AddAndAssignId(&s1);214
215// Now we actually build and trim the swept surfaces. One loop at a time.216for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {217int i, j;218SBezier *sb;219List<std::vector<hSSurface>> hsl = {};220
221// This is where all the NURBS are created and Remapped to the generating curve222for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {223std::vector<hSSurface> revs(sections);224for(j = 0; j < sections; j++) {225if((dist == 0) && sb->deg == 1 &&226(sb->ctrl[0]).DistanceToLine(pt, axis) < LENGTH_EPS &&227(sb->ctrl[1]).DistanceToLine(pt, axis) < LENGTH_EPS) {228// This is a line on the axis of revolution; it does229// not contribute a surface.230revs[j].v = 0;231} else {232SSurface ss = SSurface::FromRevolutionOf(233sb, pt, axis, angles + (wedge)*j, angles + (wedge) * (j + 1),234dists + j * dist / sections, dists + (j + 1) * dist / sections);235ss.color = color;236if(sb->entity != 0) {237hEntity he;238he.v = sb->entity;239hEntity hface = group->Remap(he, Group::REMAP_LINE_TO_FACE);240if(SK.entity.FindByIdNoOops(hface) != NULL) {241ss.face = hface.v;242}243}244revs[j] = surface.AddAndAssignId(&ss);245}246}247hsl.Add(&revs);248}249// Still the same loop. Need to create trim curves250for(i = 0; i < sbl->l.n; i++) {251std::vector<hSSurface> revs = hsl[i], revsp = hsl[WRAP(i - 1, sbl->l.n)];252
253sb = &(sbl->l[i]);254
255// we will need the grid t-values for this entire row of surfaces256List<double> t_values;257t_values = {};258if (revs[0].v) {259double ps = 0.0;260t_values.Add(&ps);261(surface.FindById(revs[0]))->MakeTriangulationGridInto(262&t_values, 0.0, 1.0, true, 0);263}264// we generate one more curve than we did surfaces265for(j = 0; j <= sections; j++) {266SCurve sc;267Quaternion qs = Quaternion::From(axis, angles + wedge * j);268// we want Q*(x - p) + p = Q*x + (p - Q*p)269Vector ts =270pt.Minus(qs.Rotate(pt)).Plus(axis.ScaledBy(dists + j * dist / sections));271
272// If this input curve generated a surface, then trim that273// surface with the rotated version of the input curve.274if(revs[0].v) { // not d[j] because crash on j==sections275sc = {};276sc.isExact = true;277sc.exact = sb->TransformedBy(ts, qs, 1.0);278// make the PWL for the curve based on t value list279for(int x = 0; x < t_values.n; x++) {280SCurvePt scpt;281scpt.tag = 0;282scpt.p = sc.exact.PointAt(t_values[x]);283scpt.vertex = (x == 0) || (x == (t_values.n - 1));284sc.pts.Add(&scpt);285}286
287// the surfaces already exists so trim with this curve288if(j < sections) {289sc.surfA = revs[j];290} else {291sc.surfA = hs1; // end cap292}293
294if(j > 0) {295sc.surfB = revs[j - 1];296} else {297sc.surfB = hs0; // staring cap298}299
300hSCurve hcb = curve.AddAndAssignId(&sc);301
302STrimBy stb;303stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true);304(surface.FindById(sc.surfA))->trim.Add(&stb);305stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false);306(surface.FindById(sc.surfB))->trim.Add(&stb);307} else if(j == 0) { // curve was on the rotation axis and is shared by the end caps.308sc = {};309sc.isExact = true;310sc.exact = sb->TransformedBy(ts, qs, 1.0);311(sc.exact).MakePwlInto(&(sc.pts));312sc.surfA = hs1; // end cap313sc.surfB = hs0; // staring cap314hSCurve hcb = curve.AddAndAssignId(&sc);315
316STrimBy stb;317stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true);318(surface.FindById(sc.surfA))->trim.Add(&stb);319stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false);320(surface.FindById(sc.surfB))->trim.Add(&stb);321}322
323// And if this input curve and the one after it both generated324// surfaces, then trim both of those by the appropriate325// curve based on the control points.326if((j < sections) && revs[j].v && revsp[j].v) {327SSurface *ss = surface.FindById(revs[j]);328
329sc = {};330sc.isExact = true;331sc.exact = SBezier::From(ss->ctrl[0][0], ss->ctrl[0][1], ss->ctrl[0][2]);332sc.exact.weight[1] = ss->weight[0][1];333double max_dt = 0.5;334if (sc.exact.deg > 1) max_dt = 0.125;335(sc.exact).MakePwlInto(&(sc.pts), 0.0, max_dt);336sc.surfA = revs[j];337sc.surfB = revsp[j];338
339hSCurve hcc = curve.AddAndAssignId(&sc);340
341STrimBy stb;342stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/false);343(surface.FindById(sc.surfA))->trim.Add(&stb);344stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/true);345(surface.FindById(sc.surfB))->trim.Add(&stb);346}347}348t_values.Clear();349}350
351hsl.Clear();352}353
354if(dist == 0) {355MakeFirstOrderRevolvedSurfaces(pt, axis, i0);356}357}
358
359void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, RgbaColor color,360Group *group) {361int i0 = surface.n; // number of pre-existing surfaces362SBezierLoop *sbl;363
364if(CheckNormalAxisRelationship(sbls, pt, axis, 1.0, 0.0)) {365axis = axis.ScaledBy(-1);366}367
368// Now we actually build and trim the surfaces.369for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {370int i, j;371SBezier *sb;372List<std::vector<hSSurface>> hsl = {};373
374for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {375std::vector<hSSurface> revs(4);376for(j = 0; j < 4; j++) {377if(sb->deg == 1 &&378(sb->ctrl[0]).DistanceToLine(pt, axis) < LENGTH_EPS &&379(sb->ctrl[1]).DistanceToLine(pt, axis) < LENGTH_EPS)380{381// This is a line on the axis of revolution; it does382// not contribute a surface.383revs[j].v = 0;384} else {385SSurface ss = SSurface::FromRevolutionOf(sb, pt, axis, (PI / 2) * j,386(PI / 2) * (j + 1), 0.0, 0.0);387ss.color = color;388if(sb->entity != 0) {389hEntity he;390he.v = sb->entity;391hEntity hface = group->Remap(he, Group::REMAP_LINE_TO_FACE);392if(SK.entity.FindByIdNoOops(hface) != NULL) {393ss.face = hface.v;394}395}396revs[j] = surface.AddAndAssignId(&ss);397}398}399hsl.Add(&revs);400}401
402for(i = 0; i < sbl->l.n; i++) {403std::vector<hSSurface> revs = hsl[i],404revsp = hsl[WRAP(i-1, sbl->l.n)];405
406sb = &(sbl->l[i]);407
408for(j = 0; j < 4; j++) {409SCurve sc;410Quaternion qs = Quaternion::From(axis, (PI/2)*j);411// we want Q*(x - p) + p = Q*x + (p - Q*p)412Vector ts = pt.Minus(qs.Rotate(pt));413
414// If this input curve generate a surface, then trim that415// surface with the rotated version of the input curve.416if(revs[j].v) {417sc = {};418sc.isExact = true;419sc.exact = sb->TransformedBy(ts, qs, 1.0);420(sc.exact).MakePwlInto(&(sc.pts));421sc.surfA = revs[j];422sc.surfB = revs[WRAP(j-1, 4)];423
424hSCurve hcb = curve.AddAndAssignId(&sc);425
426STrimBy stb;427stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true);428(surface.FindById(sc.surfA))->trim.Add(&stb);429stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false);430(surface.FindById(sc.surfB))->trim.Add(&stb);431}432
433// And if this input curve and the one after it both generated434// surfaces, then trim both of those by the appropriate435// circle.436if(revs[j].v && revsp[j].v) {437SSurface *ss = surface.FindById(revs[j]);438
439sc = {};440sc.isExact = true;441sc.exact = SBezier::From(ss->ctrl[0][0],442ss->ctrl[0][1],443ss->ctrl[0][2]);444sc.exact.weight[1] = ss->weight[0][1];445(sc.exact).MakePwlInto(&(sc.pts));446sc.surfA = revs[j];447sc.surfB = revsp[j];448
449hSCurve hcc = curve.AddAndAssignId(&sc);450
451STrimBy stb;452stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/false);453(surface.FindById(sc.surfA))->trim.Add(&stb);454stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/true);455(surface.FindById(sc.surfB))->trim.Add(&stb);456}457}458}459
460hsl.Clear();461}462
463MakeFirstOrderRevolvedSurfaces(pt, axis, i0);464}
465
466void SShell::MakeFirstOrderRevolvedSurfaces(Vector pt, Vector axis, int i0) {467int i;468
469for(i = i0; i < surface.n; i++) {470SSurface *srf = &(surface[i]);471
472// Revolution of a line; this is potentially a plane, which we can473// rewrite to have degree (1, 1).474if(srf->degm == 1 && srf->degn == 2) {475// close start, far start, far finish476Vector cs, fs, ff;477double d0, d1;478d0 = (srf->ctrl[0][0]).DistanceToLine(pt, axis);479d1 = (srf->ctrl[1][0]).DistanceToLine(pt, axis);480
481if(d0 > d1) {482cs = srf->ctrl[1][0];483fs = srf->ctrl[0][0];484ff = srf->ctrl[0][2];485} else {486cs = srf->ctrl[0][0];487fs = srf->ctrl[1][0];488ff = srf->ctrl[1][2];489}490
491// origin close, origin far492Vector oc = cs.ClosestPointOnLine(pt, axis),493of = fs.ClosestPointOnLine(pt, axis);494
495if(oc.Equals(of)) {496// This is a plane, not a (non-degenerate) cone.497Vector oldn = srf->NormalAt(0.5, 0.5);498
499Vector u = fs.Minus(of), v;500
501v = (axis.Cross(u)).WithMagnitude(1);502
503double vm = (ff.Minus(of)).Dot(v);504v = v.ScaledBy(vm);505
506srf->degm = 1;507srf->degn = 1;508srf->ctrl[0][0] = of;509srf->ctrl[0][1] = of.Plus(u);510srf->ctrl[1][0] = of.Plus(v);511srf->ctrl[1][1] = of.Plus(u).Plus(v);512srf->weight[0][0] = 1;513srf->weight[0][1] = 1;514srf->weight[1][0] = 1;515srf->weight[1][1] = 1;516
517if(oldn.Dot(srf->NormalAt(0.5, 0.5)) < 0) {518swap(srf->ctrl[0][0], srf->ctrl[1][0]);519swap(srf->ctrl[0][1], srf->ctrl[1][1]);520}521continue;522}523
524if(fabs(d0 - d1) < LENGTH_EPS) {525// This is a cylinder; so transpose it so that we'll recognize526// it as a surface of extrusion.527SSurface sn = *srf;528
529// Transposing u and v flips the normal, so reverse u to530// flip it again and put it back where we started.531sn.degm = 2;532sn.degn = 1;533int dm, dn;534for(dm = 0; dm <= 1; dm++) {535for(dn = 0; dn <= 2; dn++) {536sn.ctrl [dn][dm] = srf->ctrl [1-dm][dn];537sn.weight[dn][dm] = srf->weight[1-dm][dn];538}539}540
541*srf = sn;542continue;543}544}545}546}
547
548void SShell::MakeFromCopyOf(SShell *a) {549ssassert(this != a, "Can't make from copy of self");550MakeFromTransformationOf(a,551Vector::From(0, 0, 0), Quaternion::IDENTITY, 1.0);552}
553
554void SShell::MakeFromTransformationOf(SShell *a,555Vector t, Quaternion q, double scale)556{
557booleanFailed = false;558surface.ReserveMore(a->surface.n);559for(SSurface &s : a->surface) {560SSurface n;561n = SSurface::FromTransformationOf(&s, t, q, scale, /*includingTrims=*/true);562surface.Add(&n); // keeping the old ID563}564
565curve.ReserveMore(a->curve.n);566for(SCurve &c : a->curve) {567SCurve n;568n = SCurve::FromTransformationOf(&c, t, q, scale);569curve.Add(&n); // keeping the old ID570}571}
572
573void SShell::MakeEdgesInto(SEdgeList *sel) {574for(SSurface &s : surface) {575s.MakeEdgesInto(this, sel, SSurface::MakeAs::XYZ);576}577}
578
579void SShell::MakeSectionEdgesInto(Vector n, double d, SEdgeList *sel, SBezierList *sbl)580{
581for(SSurface &s : surface) {582if(s.CoincidentWithPlane(n, d)) {583s.MakeSectionEdgesInto(this, sel, sbl);584}585}586}
587
588void SShell::TriangulateInto(SMesh *sm) {589#pragma omp parallel for590for(int i=0; i<surface.n; i++) {591SSurface *s = &surface[i];592SMesh m;593s->TriangulateInto(this, &m);594#pragma omp critical595sm->MakeFromCopyOf(&m);596m.Clear();597}598}
599
600bool SShell::IsEmpty() const {601return surface.IsEmpty();602}
603
604void SShell::Clear() {605for(SSurface &s : surface) {606s.Clear();607}608surface.Clear();609
610for(SCurve &c : curve) {611c.Clear();612}613curve.Clear();614}
615