Solvespace
490 строк · 16.5 Кб
1//-----------------------------------------------------------------------------
2// Anything involving surfaces and sets of surfaces (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
9SSurface SSurface::FromExtrusionOf(SBezier *sb, Vector t0, Vector t1) {
10SSurface ret = {};
11
12ret.degm = sb->deg;
13ret.degn = 1;
14
15int i;
16for(i = 0; i <= ret.degm; i++) {
17ret.ctrl[i][0] = (sb->ctrl[i]).Plus(t0);
18ret.weight[i][0] = sb->weight[i];
19
20ret.ctrl[i][1] = (sb->ctrl[i]).Plus(t1);
21ret.weight[i][1] = sb->weight[i];
22}
23
24return ret;
25}
26
27bool SSurface::IsExtrusion(SBezier *of, Vector *alongp) const {
28int i;
29
30if(degn != 1) return false;
31
32Vector along = (ctrl[0][1]).Minus(ctrl[0][0]);
33for(i = 0; i <= degm; i++) {
34if((fabs(weight[i][1] - weight[i][0]) < LENGTH_EPS) &&
35((ctrl[i][1]).Minus(ctrl[i][0])).Equals(along))
36{
37continue;
38}
39return false;
40}
41
42// yes, we are a surface of extrusion; copy the original curve and return
43if(of) {
44for(i = 0; i <= degm; i++) {
45of->weight[i] = weight[i][0];
46of->ctrl[i] = ctrl[i][0];
47}
48of->deg = degm;
49*alongp = along;
50}
51return true;
52}
53
54bool SSurface::IsCylinder(Vector *axis, Vector *center, double *r,
55Vector *start, Vector *finish) const
56{
57SBezier sb;
58if(!IsExtrusion(&sb, axis)) return false;
59if(!sb.IsCircle(*axis, center, r)) return false;
60
61*start = sb.ctrl[0];
62*finish = sb.ctrl[2];
63return true;
64}
65
66// Create a surface patch by revolving and possibly translating a curve.
67// Works for sections up to but not including 180 degrees.
68SSurface SSurface::FromRevolutionOf(SBezier *sb, Vector pt, Vector axis, double thetas,
69double thetaf, double dists,
70double distf) { // s is start, f is finish
71SSurface ret = {};
72ret.degm = sb->deg;
73ret.degn = 2;
74
75double dtheta = fabs(WRAP_SYMMETRIC(thetaf - thetas, 2*PI));
76double w = cos(dtheta / 2);
77
78// Revolve the curve about the z axis
79int i;
80for(i = 0; i <= ret.degm; i++) {
81Vector p = sb->ctrl[i];
82
83Vector ps = p.RotatedAbout(pt, axis, thetas),
84pf = p.RotatedAbout(pt, axis, thetaf);
85
86// The middle control point should be at the intersection of the tangents at ps and pf.
87// This is equivalent but works for 0 <= angle < 180 degrees.
88Vector mid = ps.Plus(pf).ScaledBy(0.5);
89Vector c = ps.ClosestPointOnLine(pt, axis);
90Vector ct = mid.Minus(c).ScaledBy(1 / (w * w)).Plus(c);
91
92// not sure this is needed
93if(ps.Equals(pf)) {
94ps = c;
95ct = c;
96pf = c;
97}
98// moving along the axis can create hilical surfaces (or straight extrusion if
99// thetas==thetaf)
100ret.ctrl[i][0] = ps.Plus(axis.ScaledBy(dists));
101ret.ctrl[i][1] = ct.Plus(axis.ScaledBy((dists + distf) / 2));
102ret.ctrl[i][2] = pf.Plus(axis.ScaledBy(distf));
103
104ret.weight[i][0] = sb->weight[i];
105ret.weight[i][1] = sb->weight[i] * w;
106ret.weight[i][2] = sb->weight[i];
107}
108
109return ret;
110}
111
112SSurface SSurface::FromPlane(Vector pt, Vector u, Vector v) {
113SSurface ret = {};
114
115ret.degm = 1;
116ret.degn = 1;
117
118ret.weight[0][0] = ret.weight[0][1] = 1;
119ret.weight[1][0] = ret.weight[1][1] = 1;
120
121ret.ctrl[0][0] = pt;
122ret.ctrl[0][1] = pt.Plus(u);
123ret.ctrl[1][0] = pt.Plus(v);
124ret.ctrl[1][1] = pt.Plus(v).Plus(u);
125
126return ret;
127}
128
129SSurface SSurface::FromTransformationOf(SSurface *a, Vector t, Quaternion q, double scale,
130bool includingTrims)
131{
132bool needRotate = !EXACT(q.vx == 0.0 && q.vy == 0.0 && q.vz == 0.0 && q.w == 1.0);
133bool needTranslate = !EXACT(t.x == 0.0 && t.y == 0.0 && t.z == 0.0);
134bool needScale = !EXACT(scale == 1.0);
135
136SSurface ret = {};
137ret.h = a->h;
138ret.color = a->color;
139ret.face = a->face;
140
141ret.degm = a->degm;
142ret.degn = a->degn;
143int i, j;
144for(i = 0; i <= 3; i++) {
145for(j = 0; j <= 3; j++) {
146Vector ctrl = a->ctrl[i][j];
147if(needScale) {
148ctrl = ctrl.ScaledBy(scale);
149}
150if(needRotate) {
151ctrl = q.Rotate(ctrl);
152}
153if(needTranslate) {
154ctrl = ctrl.Plus(t);
155}
156ret.ctrl[i][j] = ctrl;
157ret.weight[i][j] = a->weight[i][j];
158}
159}
160
161if(includingTrims) {
162STrimBy *stb;
163ret.trim.ReserveMore(a->trim.n);
164for(stb = a->trim.First(); stb; stb = a->trim.NextAfter(stb)) {
165STrimBy n = *stb;
166if(needScale) {
167n.start = n.start.ScaledBy(scale);
168n.finish = n.finish.ScaledBy(scale);
169}
170if(needRotate) {
171n.start = q.Rotate(n.start);
172n.finish = q.Rotate(n.finish);
173}
174if(needTranslate) {
175n.start = n.start.Plus(t);
176n.finish = n.finish.Plus(t);
177}
178ret.trim.Add(&n);
179}
180}
181
182if(scale < 0) {
183// If we mirror every surface of a shell, then it will end up inside
184// out. So fix that here.
185ret.Reverse();
186}
187
188return ret;
189}
190
191void SSurface::GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const {
192*ptMax = Vector::From(VERY_NEGATIVE, VERY_NEGATIVE, VERY_NEGATIVE);
193*ptMin = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE);
194
195int i, j;
196for(i = 0; i <= degm; i++) {
197for(j = 0; j <= degn; j++) {
198(ctrl[i][j]).MakeMaxMin(ptMax, ptMin);
199}
200}
201}
202
203bool SSurface::LineEntirelyOutsideBbox(Vector a, Vector b, bool asSegment) const {
204Vector amax, amin;
205GetAxisAlignedBounding(&amax, &amin);
206if(!Vector::BoundingBoxIntersectsLine(amax, amin, a, b, asSegment)) {
207// The line segment could fail to intersect the bbox, but lie entirely
208// within it and intersect the surface.
209if(a.OutsideAndNotOn(amax, amin) && b.OutsideAndNotOn(amax, amin)) {
210return true;
211}
212}
213return false;
214}
215
216//-----------------------------------------------------------------------------
217// Generate the piecewise linear approximation of the trim stb, which applies
218// to the curve sc.
219//-----------------------------------------------------------------------------
220void SSurface::MakeTrimEdgesInto(SEdgeList *sel, MakeAs flags,
221SCurve *sc, STrimBy *stb)
222{
223Vector prev = Vector::From(0, 0, 0);
224bool inCurve = false, empty = true;
225double u = 0, v = 0;
226
227int i, first, last, increment;
228if(stb->backwards) {
229first = sc->pts.n - 1;
230last = 0;
231increment = -1;
232} else {
233first = 0;
234last = sc->pts.n - 1;
235increment = 1;
236}
237for(i = first; i != (last + increment); i += increment) {
238Vector tpt, *pt = &(sc->pts[i].p);
239
240if(flags == MakeAs::UV) {
241ClosestPointTo(*pt, &u, &v);
242tpt = Vector::From(u, v, 0);
243} else {
244tpt = *pt;
245}
246
247if(inCurve) {
248sel->AddEdge(prev, tpt, sc->h.v, stb->backwards);
249empty = false;
250}
251
252prev = tpt; // either uv or xyz, depending on flags
253
254if(pt->Equals(stb->start)) inCurve = true;
255if(pt->Equals(stb->finish)) inCurve = false;
256}
257if(inCurve) dbp("trim was unterminated");
258if(empty) dbp("trim was empty");
259}
260
261//-----------------------------------------------------------------------------
262// Generate all of our trim curves, in piecewise linear form. We can do
263// so in either uv or xyz coordinates. And if requested, then we can use
264// the split curves from useCurvesFrom instead of the curves in our own
265// shell.
266//-----------------------------------------------------------------------------
267void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, MakeAs flags,
268SShell *useCurvesFrom)
269{
270STrimBy *stb;
271for(stb = trim.First(); stb; stb = trim.NextAfter(stb)) {
272SCurve *sc = shell->curve.FindById(stb->curve);
273
274// We have the option to use the curves from another shell; this
275// is relevant when generating the coincident edges while doing the
276// Booleans, since the curves from the output shell will be split
277// against any intersecting surfaces (and the originals aren't).
278if(useCurvesFrom) {
279sc = useCurvesFrom->curve.FindById(sc->newH);
280}
281
282MakeTrimEdgesInto(sel, flags, sc, stb);
283}
284}
285
286//-----------------------------------------------------------------------------
287// Compute the exact tangent to the intersection curve between two surfaces,
288// by taking the cross product of the surface normals. We choose the direction
289// of this tangent so that its dot product with dir is positive.
290//-----------------------------------------------------------------------------
291Vector SSurface::ExactSurfaceTangentAt(Vector p, SSurface *srfA, SSurface *srfB, Vector dir)
292{
293Point2d puva, puvb;
294srfA->ClosestPointTo(p, &puva);
295srfB->ClosestPointTo(p, &puvb);
296Vector ts = (srfA->NormalAt(puva)).Cross(
297(srfB->NormalAt(puvb)));
298ts = ts.WithMagnitude(1);
299if(ts.Dot(dir) < 0) {
300ts = ts.ScaledBy(-1);
301}
302return ts;
303}
304
305//-----------------------------------------------------------------------------
306// Report our trim curves. If a trim curve is exact and sbl is not null, then
307// add its exact form to sbl. Otherwise, add its piecewise linearization to
308// sel.
309//-----------------------------------------------------------------------------
310void SSurface::MakeSectionEdgesInto(SShell *shell, SEdgeList *sel, SBezierList *sbl)
311{
312STrimBy *stb;
313for(stb = trim.First(); stb; stb = trim.NextAfter(stb)) {
314SCurve *sc = shell->curve.FindById(stb->curve);
315SBezier *sb = &(sc->exact);
316
317if(sbl && sc->isExact && (sb->deg != 1 || !sel)) {
318double ts, tf;
319if(stb->backwards) {
320sb->ClosestPointTo(stb->start, &tf);
321sb->ClosestPointTo(stb->finish, &ts);
322} else {
323sb->ClosestPointTo(stb->start, &ts);
324sb->ClosestPointTo(stb->finish, &tf);
325}
326SBezier junk_bef, keep_aft;
327sb->SplitAt(ts, &junk_bef, &keep_aft);
328// In the kept piece, the range that used to go from ts to 1
329// now goes from 0 to 1; so rescale tf appropriately.
330tf = (tf - ts)/(1 - ts);
331
332SBezier keep_bef, junk_aft;
333keep_aft.SplitAt(tf, &keep_bef, &junk_aft);
334
335sbl->l.Add(&keep_bef);
336} else if(sbl && !sel && !sc->isExact) {
337// We must approximate this trim curve, as piecewise cubic sections.
338SSurface *srfA = shell->surface.FindById(sc->surfA);
339SSurface *srfB = shell->surface.FindById(sc->surfB);
340
341Vector s = stb->backwards ? stb->finish : stb->start,
342f = stb->backwards ? stb->start : stb->finish;
343
344int sp, fp;
345for(sp = 0; sp < sc->pts.n; sp++) {
346if(s.Equals(sc->pts[sp].p)) break;
347}
348if(sp >= sc->pts.n) return;
349for(fp = sp; fp < sc->pts.n; fp++) {
350if(f.Equals(sc->pts[fp].p)) break;
351}
352if(fp >= sc->pts.n) return;
353// So now the curve we want goes from elem[sp] to elem[fp]
354
355while(sp < fp) {
356// Initially, we'll try approximating the entire trim curve
357// as a single Bezier segment
358int fpt = fp;
359
360for(;;) {
361// So construct a cubic Bezier with the correct endpoints
362// and tangents for the current span.
363Vector st = sc->pts[sp].p,
364ft = sc->pts[fpt].p,
365sf = ft.Minus(st);
366double m = sf.Magnitude() / 3;
367
368Vector stan = ExactSurfaceTangentAt(st, srfA, srfB, sf),
369ftan = ExactSurfaceTangentAt(ft, srfA, srfB, sf);
370
371SBezier sb = SBezier::From(st,
372st.Plus (stan.WithMagnitude(m)),
373ft.Minus(ftan.WithMagnitude(m)),
374ft);
375
376// And test how much this curve deviates from the
377// intermediate points (if any).
378int i;
379bool tooFar = false;
380for(i = sp + 1; i <= (fpt - 1); i++) {
381Vector p = sc->pts[i].p;
382double t;
383sb.ClosestPointTo(p, &t, /*mustConverge=*/false);
384Vector pp = sb.PointAt(t);
385if((pp.Minus(p)).Magnitude() > SS.ChordTolMm()/2) {
386tooFar = true;
387break;
388}
389}
390
391if(tooFar) {
392// Deviates by too much, so try a shorter span
393fpt--;
394continue;
395} else {
396// Okay, so use this piece and break.
397sbl->l.Add(&sb);
398break;
399}
400}
401
402// And continue interpolating, starting wherever the curve
403// we just generated finishes.
404sp = fpt;
405}
406} else {
407if(sel) MakeTrimEdgesInto(sel, MakeAs::XYZ, sc, stb);
408}
409}
410}
411
412void SSurface::TriangulateInto(SShell *shell, SMesh *sm) {
413SEdgeList el = {};
414
415MakeEdgesInto(shell, &el, MakeAs::UV);
416
417SPolygon poly = {};
418if(el.AssemblePolygon(&poly, NULL, /*keepDir=*/true)) {
419int i, start = sm->l.n;
420if(degm == 1 && degn == 1) {
421// A surface with curvature along one direction only; so
422// choose the triangulation with chords that lie as much
423// as possible within the surface. And since the trim curves
424// have been pwl'd to within the desired chord tol, that will
425// produce a surface good to within roughly that tol.
426//
427// If this is just a plane (degree (1, 1)) then the triangulation
428// code will notice that, and not bother checking chord tols.
429poly.UvTriangulateInto(sm, this);
430} else {
431// A surface with compound curvature. So we must overlay a
432// two-dimensional grid, and triangulate around that.
433poly.UvGridTriangulateInto(sm, this);
434}
435
436STriMeta meta = { face, color };
437for(i = start; i < sm->l.n; i++) {
438STriangle *st = &(sm->l[i]);
439st->meta = meta;
440st->an = NormalAt(st->a.x, st->a.y);
441st->bn = NormalAt(st->b.x, st->b.y);
442st->cn = NormalAt(st->c.x, st->c.y);
443st->a = PointAt(st->a.x, st->a.y);
444st->b = PointAt(st->b.x, st->b.y);
445st->c = PointAt(st->c.x, st->c.y);
446// Works out that my chosen contour direction is inconsistent with
447// the triangle direction, sigh.
448st->FlipNormal();
449}
450} else {
451dbp("failed to assemble polygon to trim nurbs surface in uv space");
452}
453
454el.Clear();
455poly.Clear();
456}
457
458//-----------------------------------------------------------------------------
459// Reverse the parametrisation of one of our dimensions, which flips the
460// normal. We therefore must reverse all our trim curves too. The uv
461// coordinates change, but trim curves are stored as xyz so nothing happens
462//-----------------------------------------------------------------------------
463void SSurface::Reverse() {
464int i, j;
465for(i = 0; i < (degm+1)/2; i++) {
466for(j = 0; j <= degn; j++) {
467swap(ctrl[i][j], ctrl[degm-i][j]);
468swap(weight[i][j], weight[degm-i][j]);
469}
470}
471
472STrimBy *stb;
473for(stb = trim.First(); stb; stb = trim.NextAfter(stb)) {
474stb->backwards = !stb->backwards;
475swap(stb->start, stb->finish);
476}
477}
478
479void SSurface::ScaleSelfBy(double s) {
480int i, j;
481for(i = 0; i <= degm; i++) {
482for(j = 0; j <= degn; j++) {
483ctrl[i][j] = ctrl[i][j].ScaledBy(s);
484}
485}
486}
487
488void SSurface::Clear() {
489trim.Clear();
490}
491
492
493