diff --git a/dsc.h b/dsc.h index 05e531fe..560ed994 100644 --- a/dsc.h +++ b/dsc.h @@ -6,6 +6,7 @@ typedef unsigned long DWORD; typedef unsigned char BYTE; class Vector; +class Vector4; class Point2d; class hEntity; class hParam; @@ -92,6 +93,21 @@ public: Vector origin, double cameraTan); Point2d Project2d(Vector u, Vector v); Point2d ProjectXy(void); + Vector4 Project4d(void); +}; + +class Vector4 { +public: + double w, x, y, z; + + static Vector4 From(double w, double x, double y, double z); + static Vector4 From(double w, Vector v3); + static Vector4 Blend(Vector4 a, Vector4 b, double t); + + Vector4 Plus(Vector4 b); + Vector4 Minus(Vector4 b); + Vector4 ScaledBy(double s); + Vector PerspectiveProject(void); }; class Point2d { diff --git a/export.cpp b/export.cpp index 15acf88a..603d08c7 100644 --- a/export.cpp +++ b/export.cpp @@ -62,57 +62,51 @@ void SolveSpace::ExportSectionTo(char *filename) { n = n.WithMagnitude(1); d = origin.Dot(n); - SMesh m; - ZERO(&m); - m.MakeFromCopy(&(g->runningMesh)); - - // Delete all triangles in the mesh that do not lie in our export plane. - m.l.ClearTags(); - int i; - for(i = 0; i < m.l.n; i++) { - STriangle *tr = &(m.l.elem[i]); - - if((fabs(n.Dot(tr->a) - d) >= LENGTH_EPS) || - (fabs(n.Dot(tr->b) - d) >= LENGTH_EPS) || - (fabs(n.Dot(tr->c) - d) >= LENGTH_EPS)) - { - tr->tag = 1; - } - } - m.l.RemoveTagged(); - - // Select the naked edges in our resulting open mesh. - SKdNode *root = SKdNode::From(&m); SEdgeList el; ZERO(&el); - root->MakeNakedEdgesInto(&el); - m.Clear(); + SBezierList bl; + ZERO(&bl); + + g->runningShell.MakeSectionEdgesInto(n, d, + &el, SS.exportPwlCurves ? NULL : &bl); + + el.CullExtraneousEdges(); // And write the edges. VectorFileWriter *out = VectorFileWriter::ForFile(filename); if(out) { // parallel projection (no perspective), and no mesh - ExportLinesAndMesh(&el, NULL, + ExportLinesAndMesh(&el, &bl, NULL, u, v, n, origin, 0, out); } el.Clear(); + bl.Clear(); } void SolveSpace::ExportViewTo(char *filename) { int i; SEdgeList edges; ZERO(&edges); + SBezierList beziers; + ZERO(&beziers); + + SMesh *sm = NULL; + if(SS.GW.showShaded) { + sm = &((SS.GetGroup(SS.GW.activeGroup))->runningMesh); + } + for(i = 0; i < SS.entity.n; i++) { Entity *e = &(SS.entity.elem[i]); if(!e->IsVisible()) continue; - e->GenerateEdges(&edges); - } - - SMesh *sm = NULL; - if(SS.GW.showShaded) { - sm = &((SS.GetGroup(SS.GW.activeGroup))->runningMesh); + if(SS.exportPwlCurves || (sm && !SS.GW.showHdnLines)) { + // We will be doing hidden line removal, which we can't do on + // exact curves; so we need things broken down to pwls. + e->GenerateEdges(&edges); + } else { + e->GenerateBezierCurves(&beziers); + } } if(SS.GW.showEdges) { @@ -130,14 +124,15 @@ void SolveSpace::ExportViewTo(char *filename) { VectorFileWriter *out = VectorFileWriter::ForFile(filename); if(out) { - ExportLinesAndMesh(&edges, sm, + ExportLinesAndMesh(&edges, &beziers, sm, u, v, n, origin, SS.cameraTangent*SS.GW.scale, out); } edges.Clear(); + beziers.Clear(); } -void SolveSpace::ExportLinesAndMesh(SEdgeList *sel, SMesh *sm, +void SolveSpace::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm, Vector u, Vector v, Vector n, Vector origin, double cameraTan, VectorFileWriter *out) @@ -153,6 +148,17 @@ void SolveSpace::ExportLinesAndMesh(SEdgeList *sel, SMesh *sm, (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 @@ -252,7 +258,7 @@ void SolveSpace::ExportLinesAndMesh(SEdgeList *sel, SMesh *sm, } // Now write the lines and triangles to the output file - out->Output(sel, &sms); + out->Output(sel, sbl, &sms); smp.Clear(); sms.Clear(); @@ -301,9 +307,10 @@ VectorFileWriter *VectorFileWriter::ForFile(char *filename) { return ret; } -void VectorFileWriter::Output(SEdgeList *sel, SMesh *sm) { +void VectorFileWriter::Output(SEdgeList *sel, SBezierList *sbl, SMesh *sm) { STriangle *tr; SEdge *e; + SBezier *b; // First calculate the bounding box. ptMin = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE); @@ -321,6 +328,14 @@ void VectorFileWriter::Output(SEdgeList *sel, SMesh *sm) { (tr->c).MakeMaxMin(&ptMax, &ptMin); } } + if(sbl) { + for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) { + int i; + for(i = 0; i <= b->deg; i++) { + (b->ctrl[i]).MakeMaxMin(&ptMax, &ptMin); + } + } + } StartFile(); if(sm && SS.exportShadedTriangles) { @@ -333,9 +348,27 @@ void VectorFileWriter::Output(SEdgeList *sel, SMesh *sm) { LineSegment(e->a.x, e->a.y, e->b.x, e->b.y); } } + if(sbl) { + for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) { + Bezier(b); + } + } FinishAndCloseFile(); } +void VectorFileWriter::BezierAsPwl(SBezier *sb) { + List lv; + ZERO(&lv); + sb->MakePwlInto(&lv); + int i; + for(i = 1; i < lv.n; i++) { + LineSegment(lv.elem[i-1].x, lv.elem[i-1].y, + lv.elem[i ].x, lv.elem[i ].y); + } + lv.Clear(); +} + + //----------------------------------------------------------------------------- // Routines for DXF export //----------------------------------------------------------------------------- @@ -405,9 +438,14 @@ void DxfFileWriter::LineSegment(double x0, double y0, double x1, double y1) { x0, y0, 0.0, x1, y1, 0.0); } + void DxfFileWriter::Triangle(STriangle *tr) { } +void DxfFileWriter::Bezier(SBezier *sb) { + BezierAsPwl(sb); +} + void DxfFileWriter::FinishAndCloseFile(void) { fprintf(f, " 0\r\n" @@ -455,6 +493,7 @@ void EpsFileWriter::LineSegment(double x0, double y0, double x1, double y1) { MmToPoints(x0 - ptMin.x), MmToPoints(y0 - ptMin.y), MmToPoints(x1 - ptMin.x), MmToPoints(y1 - ptMin.y)); } + void EpsFileWriter::Triangle(STriangle *tr) { fprintf(f, "%.3f %.3f %.3f setrgbcolor\r\n" @@ -487,6 +526,10 @@ void EpsFileWriter::Triangle(STriangle *tr) { MmToPoints(tr->c.x - ptMin.x), MmToPoints(tr->c.y - ptMin.y)); } +void EpsFileWriter::Bezier(SBezier *sb) { + BezierAsPwl(sb); +} + void EpsFileWriter::FinishAndCloseFile(void) { fprintf(f, "\r\n" @@ -521,6 +564,7 @@ void SvgFileWriter::LineSegment(double x0, double y0, double x1, double y1) { (x0 - ptMin.x), (ptMax.y - y0), (x1 - ptMin.x), (ptMax.y - y1)); } + void SvgFileWriter::Triangle(STriangle *tr) { // crispEdges turns of anti-aliasing, which tends to cause hairline // cracks between triangles; but there still is some cracking, so @@ -538,6 +582,10 @@ void SvgFileWriter::Triangle(STriangle *tr) { RED(tr->meta.color), GREEN(tr->meta.color), BLUE(tr->meta.color)); } +void SvgFileWriter::Bezier(SBezier *sb) { + BezierAsPwl(sb); +} + void SvgFileWriter::FinishAndCloseFile(void) { fprintf(f, "\r\n\r\n"); fclose(f); @@ -559,8 +607,12 @@ void HpglFileWriter::LineSegment(double x0, double y0, double x1, double y1) { fprintf(f, "PU%d,%d;\r\n", (int)MmToHpglUnits(x0), (int)MmToHpglUnits(y0)); fprintf(f, "PD%d,%d;\r\n", (int)MmToHpglUnits(x1), (int)MmToHpglUnits(y1)); } + void HpglFileWriter::Triangle(STriangle *tr) { - // HPGL does not support filled triangles +} + +void HpglFileWriter::Bezier(SBezier *sb) { + BezierAsPwl(sb); } void HpglFileWriter::FinishAndCloseFile(void) { diff --git a/graphicswin.cpp b/graphicswin.cpp index d8cd115f..dbaf69f7 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -270,9 +270,25 @@ void GraphicsWindow::LoopOverPoints( int i, j; for(i = 0; i < SS.entity.n; i++) { Entity *e = &(SS.entity.elem[i]); - if(!e->IsPoint()) continue; if(!e->IsVisible()) continue; - HandlePointForZoomToFit(e->PointGetNum(), pmax, pmin, wmin, div); + if(e->IsPoint()) { + HandlePointForZoomToFit(e->PointGetNum(), pmax, pmin, wmin, div); + } else if(e->type == Entity::CIRCLE) { + // Lots of entities can extend outside the bbox of their points, + // but circles are particularly bad. We want to get things halfway + // reasonable without the mesh, because a zoom to fit is used to + // set the zoom level to set the chord tol. + double r = e->CircleGetRadiusNum(); + Vector c = SS.GetEntity(e->point[0])->PointGetNum(); + Quaternion q = SS.GetEntity(e->normal)->NormalGetNum(); + for(j = 0; j < 4; j++) { + Vector p = (j == 0) ? (c.Plus(q.RotationU().ScaledBy( r))) : + (j == 1) ? (c.Plus(q.RotationU().ScaledBy(-r))) : + (j == 2) ? (c.Plus(q.RotationV().ScaledBy( r))) : + (c.Plus(q.RotationV().ScaledBy(-r))); + HandlePointForZoomToFit(p, pmax, pmin, wmin, div); + } + } } Group *g = SS.GetGroup(activeGroup); for(i = 0; i < g->runningMesh.l.n; i++) { diff --git a/solvespace.cpp b/solvespace.cpp index 5c029f7c..1ce0814f 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -56,8 +56,8 @@ void SolveSpace::Init(char *cmdLine) { drawBackFaces = CnfThawDWORD(1, "DrawBackFaces"); // Export shaded triangles in a 2d view exportShadedTriangles = CnfThawDWORD(1, "ExportShadedTriangles"); - // Export exact curves (instead of pwl) when possible - exportExactCurves = CnfThawDWORD(0, "ExportExactCurves"); + // Export pwl curves (instead of exact) always + exportPwlCurves = CnfThawDWORD(0, "ExportPwlCurves"); // Show toolbar in the graphics window showToolbar = CnfThawDWORD(1, "ShowToolbar"); // Recent files menus @@ -124,8 +124,8 @@ void SolveSpace::Exit(void) { CnfFreezeDWORD(drawBackFaces, "DrawBackFaces"); // Export shaded triangles in a 2d view CnfFreezeDWORD(exportShadedTriangles, "ExportShadedTriangles"); - // Export exact curves (instead of pwl) when possible - CnfFreezeDWORD(exportExactCurves, "ExportExactCurves"); + // Export pwl curves (instead of exact) always + CnfFreezeDWORD(exportPwlCurves, "ExportPwlCurves"); // Show toolbar in the graphics window CnfFreezeDWORD(showToolbar, "ShowToolbar"); diff --git a/solvespace.h b/solvespace.h index 30d4cad0..843ab326 100644 --- a/solvespace.h +++ b/solvespace.h @@ -329,8 +329,6 @@ public: Vector TransformIntPoint(int x, int y); void LineSegment(int x0, int y0, int x1, int y1); void Bezier(int x0, int y0, int x1, int y1, int x2, int y2); - void BezierPwl(double ta, double tb, Vector p0, Vector p1, Vector p2); - Vector BezierEval(double t, Vector p0, Vector p1, Vector p2); }; class TtfFontList { @@ -352,8 +350,10 @@ public: static bool StringEndsIn(char *str, char *ending); static VectorFileWriter *ForFile(char *file); - void Output(SEdgeList *sel, SMesh *sm); + void Output(SEdgeList *sel, SBezierList *sbl, SMesh *sm); + void BezierAsPwl(SBezier *sb); + virtual void Bezier(SBezier *sb) = 0; virtual void LineSegment(double x0, double y0, double x1, double y1) = 0; virtual void Triangle(STriangle *tr) = 0; virtual void StartFile(void) = 0; @@ -363,6 +363,7 @@ class DxfFileWriter : public VectorFileWriter { public: void LineSegment(double x0, double y0, double x1, double y1); void Triangle(STriangle *tr); + void Bezier(SBezier *sb); void StartFile(void); void FinishAndCloseFile(void); }; @@ -371,6 +372,7 @@ public: static double MmToPoints(double mm); void LineSegment(double x0, double y0, double x1, double y1); void Triangle(STriangle *tr); + void Bezier(SBezier *sb); void StartFile(void); void FinishAndCloseFile(void); }; @@ -378,6 +380,7 @@ class SvgFileWriter : public VectorFileWriter { public: void LineSegment(double x0, double y0, double x1, double y1); void Triangle(STriangle *tr); + void Bezier(SBezier *sb); void StartFile(void); void FinishAndCloseFile(void); }; @@ -386,6 +389,7 @@ public: static double MmToHpglUnits(double mm); void LineSegment(double x0, double y0, double x1, double y1); void Triangle(STriangle *tr); + void Bezier(SBezier *sb); void StartFile(void); void FinishAndCloseFile(void); }; @@ -451,7 +455,7 @@ public: int drawBackFaces; int showToolbar; int exportShadedTriangles; - int exportExactCurves; + int exportPwlCurves; int CircleSides(double r); typedef enum { @@ -508,7 +512,7 @@ public: void ExportMeshTo(char *file); void ExportViewTo(char *file); void ExportSectionTo(char *file); - void ExportLinesAndMesh(SEdgeList *sel, SMesh *sm, + void ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm, Vector u, Vector v, Vector n, Vector origin, double cameraTan, VectorFileWriter *out); diff --git a/srf/curve.cpp b/srf/curve.cpp index ccc0f53d..21f0542b 100644 --- a/srf/curve.cpp +++ b/srf/curve.cpp @@ -4,39 +4,63 @@ //----------------------------------------------------------------------------- #include "../solvespace.h" -SBezier SBezier::From(Vector p0, Vector p1) { +SBezier SBezier::From(Vector4 p0, Vector4 p1) { SBezier ret; ZERO(&ret); ret.deg = 1; - ret.weight[0] = ret.weight[1] = 1; - ret.ctrl[0] = p0; - ret.ctrl[1] = p1; + ret.weight[0] = p0.w; + ret.ctrl [0] = p0.PerspectiveProject(); + ret.weight[1] = p1.w; + ret.ctrl [1] = p1.PerspectiveProject(); return ret; } -SBezier SBezier::From(Vector p0, Vector p1, Vector p2) { +SBezier SBezier::From(Vector4 p0, Vector4 p1, Vector4 p2) { SBezier ret; ZERO(&ret); ret.deg = 2; - ret.weight[0] = ret.weight[1] = ret.weight[2] = 1; - ret.ctrl[0] = p0; - ret.ctrl[1] = p1; - ret.ctrl[2] = p2; + ret.weight[0] = p0.w; + ret.ctrl [0] = p0.PerspectiveProject(); + ret.weight[1] = p1.w; + ret.ctrl [1] = p1.PerspectiveProject(); + ret.weight[2] = p2.w; + ret.ctrl [2] = p2.PerspectiveProject(); return ret; } -SBezier SBezier::From(Vector p0, Vector p1, Vector p2, Vector p3) { +SBezier SBezier::From(Vector4 p0, Vector4 p1, Vector4 p2, Vector4 p3) { SBezier ret; ZERO(&ret); ret.deg = 3; - ret.weight[0] = ret.weight[1] = ret.weight[2] = ret.weight[3] = 1; - ret.ctrl[0] = p0; - ret.ctrl[1] = p1; - ret.ctrl[2] = p2; - ret.ctrl[3] = p3; + ret.weight[0] = p0.w; + ret.ctrl [0] = p0.PerspectiveProject(); + ret.weight[1] = p1.w; + ret.ctrl [1] = p1.PerspectiveProject(); + ret.weight[2] = p2.w; + ret.ctrl [2] = p2.PerspectiveProject(); + ret.weight[3] = p3.w; + ret.ctrl [3] = p3.PerspectiveProject(); return ret; } +SBezier SBezier::From(Vector p0, Vector p1) { + return SBezier::From(p0.Project4d(), + p1.Project4d()); +} + +SBezier SBezier::From(Vector p0, Vector p1, Vector p2) { + return SBezier::From(p0.Project4d(), + p1.Project4d(), + p2.Project4d()); +} + +SBezier SBezier::From(Vector p0, Vector p1, Vector p2, Vector p3) { + return SBezier::From(p0.Project4d(), + p1.Project4d(), + p2.Project4d(), + p3.Project4d()); +} + Vector SBezier::Start(void) { return ctrl[0]; } @@ -73,6 +97,34 @@ SBezier SBezier::TransformedBy(Vector t, Quaternion q) { return ret; } +//----------------------------------------------------------------------------- +// Apply a perspective transformation to a rational Bezier curve, calculating +// the new weights as required. +//----------------------------------------------------------------------------- +SBezier SBezier::InPerspective(Vector u, Vector v, Vector n, + Vector origin, double cameraTan) +{ + Quaternion q = Quaternion::From(u, v); + q = q.Inverse(); + // we want Q*(p - o) = Q*p - Q*o + SBezier ret = this->TransformedBy(q.Rotate(origin).ScaledBy(-1), q); + int i; + for(i = 0; i <= deg; i++) { + Vector4 ct = Vector4::From(ret.weight[i], ret.ctrl[i]); + // so the desired curve, before perspective, is + // (x/w, y/w, z/w) + // and after perspective is + // ((x/w)/(1 - (z/w)*cameraTan, ... + // = (x/(w - z*cameraTan), ... + // so we want to let w' = w - z*cameraTan + ct.w = ct.w - ct.z*cameraTan; + + ret.ctrl[i] = ct.PerspectiveProject(); + ret.weight[i] = ct.w; + } + return ret; +} + bool SBezier::Equals(SBezier *b) { // We just test of identical degree and control points, even though two // curves could still be coincident (even sharing endpoints). diff --git a/srf/ratpoly.cpp b/srf/ratpoly.cpp index 98c9ad01..a7387769 100644 --- a/srf/ratpoly.cpp +++ b/srf/ratpoly.cpp @@ -107,6 +107,98 @@ Vector SBezier::PointAt(double t) { return pt; } +Vector SBezier::TangentAt(double t) { + Vector pt = Vector::From(0, 0, 0), pt_p = Vector::From(0, 0, 0); + double d = 0, d_p = 0; + + int i; + for(i = 0; i <= deg; i++) { + double B = Bernstein(i, deg, t), + Bp = BernsteinDerivative(i, deg, t); + + pt = pt.Plus(ctrl[i].ScaledBy(B*weight[i])); + d += weight[i]*B; + + pt_p = pt_p.Plus(ctrl[i].ScaledBy(Bp*weight[i])); + d_p += weight[i]*Bp; + } + + // quotient rule; f(t) = n(t)/d(t), so f' = (n'*d - n*d')/(d^2) + Vector ret; + ret = (pt_p.ScaledBy(d)).Minus(pt.ScaledBy(d_p)); + ret = ret.ScaledBy(1.0/(d*d)); + return ret; +} + +void SBezier::ClosestPointTo(Vector p, double *t) { + int i; + double minDist = VERY_POSITIVE; + *t = 0; + double res = (deg <= 2) ? 7.0 : 20.0; + for(i = 0; i < (int)res; i++) { + double tryt = (i/res); + + Vector tryp = PointAt(tryt); + double d = (tryp.Minus(p)).Magnitude(); + if(d < minDist) { + *t = tryt; + minDist = d; + } + } + + Vector p0; + for(i = 0; i < 15; i++) { + p0 = PointAt(*t); + if(p0.Equals(p, RATPOLY_EPS)) { + return; + } + + Vector dp = TangentAt(*t); + Vector pc = p.ClosestPointOnLine(p0, dp); + *t += (pc.Minus(p0)).DivPivoting(dp); + } + dbp("didn't converge (closest point on bezier curve)"); +} + +void SBezier::SplitAt(double t, SBezier *bef, SBezier *aft) { + Vector4 ct[4]; + int i; + for(i = 0; i <= deg; i++) { + ct[i] = Vector4::From(weight[i], ctrl[i]); + } + + switch(deg) { + case 1: { + Vector4 cts = Vector4::Blend(ct[0], ct[1], t); + *bef = SBezier::From(ct[0], cts); + *aft = SBezier::From(cts, ct[1]); + break; + } + case 2: { + Vector4 ct01 = Vector4::Blend(ct[0], ct[1], t), + ct12 = Vector4::Blend(ct[1], ct[2], t), + cts = Vector4::Blend(ct01, ct12, t); + + *bef = SBezier::From(ct[0], ct01, cts); + *aft = SBezier::From(cts, ct12, ct[2]); + break; + } + case 3: { + Vector4 ct01 = Vector4::Blend(ct[0], ct[1], t), + ct12 = Vector4::Blend(ct[1], ct[2], t), + ct23 = Vector4::Blend(ct[2], ct[3], t), + ct01_12 = Vector4::Blend(ct01, ct12, t), + ct12_23 = Vector4::Blend(ct12, ct23, t), + cts = Vector4::Blend(ct01_12, ct12_23, t); + + *bef = SBezier::From(ct[0], ct01, ct01_12, cts); + *aft = SBezier::From(cts, ct12_23, ct23, ct[3]); + break; + } + default: oops(); + } +} + void SBezier::MakePwlInto(List *l) { l->Add(&(ctrl[0])); MakePwlWorker(l, 0.0, 1.0); @@ -180,7 +272,7 @@ void SSurface::TangentsAt(double u, double v, Vector *tu, Vector *tv) { den_v += weight[i][j]*Bi*Bjp; } } - // Quotient rule; f(t) = n(t)/d(t), so f' = (n'*d - n*d')/(d^2) + // quotient rule; f(t) = n(t)/d(t), so f' = (n'*d - n*d')/(d^2) *tu = ((num_u.ScaledBy(den)).Minus(num.ScaledBy(den_u))); *tu = tu->ScaledBy(1.0/(den*den)); diff --git a/srf/surface.cpp b/srf/surface.cpp index c43cd693..62d2e4de 100644 --- a/srf/surface.cpp +++ b/srf/surface.cpp @@ -174,6 +174,58 @@ bool SSurface::LineEntirelyOutsideBbox(Vector a, Vector b, bool segment) { return false; } +//----------------------------------------------------------------------------- +// Generate the piecewise linear approximation of the trim stb, which applies +// to the curve sc. +//----------------------------------------------------------------------------- +void SSurface::MakeTrimEdgesInto(SEdgeList *sel, bool asUv, + SCurve *sc, STrimBy *stb) +{ + Vector prev, prevuv, ptuv; + bool inCurve = false, empty = true; + double u = 0, v = 0; + + int i, first, last, increment; + if(stb->backwards) { + first = sc->pts.n - 1; + last = 0; + increment = -1; + } else { + first = 0; + last = sc->pts.n - 1; + increment = 1; + } + for(i = first; i != (last + increment); i += increment) { + Vector *pt = &(sc->pts.elem[i]); + if(asUv) { + ClosestPointTo(*pt, &u, &v); + ptuv = Vector::From(u, v, 0); + if(inCurve) { + sel->AddEdge(prevuv, ptuv, sc->h.v, stb->backwards); + empty = false; + } + prevuv = ptuv; + } else { + if(inCurve) { + sel->AddEdge(prev, *pt, sc->h.v, stb->backwards); + empty = false; + } + prev = *pt; + } + + if(pt->Equals(stb->start)) inCurve = true; + if(pt->Equals(stb->finish)) inCurve = false; + } + if(inCurve) dbp("trim was unterminated"); + if(empty) dbp("trim was empty"); +} + +//----------------------------------------------------------------------------- +// Generate all of our trim curves, in piecewise linear form. We can do +// so in either uv or xyz coordinates. And if requested, then we can use +// the split curves from useCurvesFrom instead of the curves in our own +// shell. +//----------------------------------------------------------------------------- void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv, SShell *useCurvesFrom) { @@ -189,43 +241,45 @@ void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv, sc = useCurvesFrom->curve.FindById(sc->newH); } - Vector prev, prevuv, ptuv; - bool inCurve = false, empty = true; - double u = 0, v = 0; - - int i, first, last, increment; - if(stb->backwards) { - first = sc->pts.n - 1; - last = 0; - increment = -1; - } else { - first = 0; - last = sc->pts.n - 1; - increment = 1; - } - for(i = first; i != (last + increment); i += increment) { - Vector *pt = &(sc->pts.elem[i]); - if(asUv) { - ClosestPointTo(*pt, &u, &v); - ptuv = Vector::From(u, v, 0); - if(inCurve) { - sel->AddEdge(prevuv, ptuv, sc->h.v, stb->backwards); - empty = false; - } - prevuv = ptuv; - } else { - if(inCurve) { - sel->AddEdge(prev, *pt, sc->h.v, stb->backwards); - empty = false; - } - prev = *pt; - } + MakeTrimEdgesInto(sel, asUv, sc, stb); + } +} - if(pt->Equals(stb->start)) inCurve = true; - if(pt->Equals(stb->finish)) inCurve = false; +//----------------------------------------------------------------------------- +// 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) { + 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 { + MakeTrimEdgesInto(sel, false, sc, stb); } - if(inCurve) dbp("trim was unterminated"); - if(empty) dbp("trim was empty"); } } @@ -439,6 +493,17 @@ void SShell::MakeEdgesInto(SEdgeList *sel) { } } +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::TriangulateInto(SMesh *sm) { SSurface *s; for(s = surface.First(); s; s = surface.NextAfter(s)) { diff --git a/srf/surface.h b/srf/surface.h index 894ce9c7..4b91e2f6 100644 --- a/srf/surface.h +++ b/srf/surface.h @@ -50,7 +50,8 @@ public: }; // Stuff for rational polynomial curves, of degree one to three. These are -// our inputs. +// our inputs, and are also calculated for certain exact surface-surface +// intersections. class SBezier { public: int tag; @@ -59,6 +60,10 @@ public: double weight[4]; Vector PointAt(double t); + Vector TangentAt(double t); + void ClosestPointTo(Vector p, double *t); + void SplitAt(double t, SBezier *bef, SBezier *aft); + Vector Start(void); Vector Finish(void); bool Equals(SBezier *b); @@ -69,10 +74,15 @@ public: void Reverse(void); SBezier TransformedBy(Vector t, Quaternion q); + SBezier InPerspective(Vector u, Vector v, Vector n, + Vector origin, double cameraTan); static SBezier From(Vector p0, Vector p1, Vector p2, Vector p3); static SBezier From(Vector p0, Vector p1, Vector p2); static SBezier From(Vector p0, Vector p1); + static SBezier From(Vector4 p0, Vector4 p1, Vector4 p2, Vector4 p3); + static SBezier From(Vector4 p0, Vector4 p1, Vector4 p2); + static SBezier From(Vector4 p0, Vector4 p1); }; class SBezierList { @@ -233,8 +243,10 @@ public: Vector *start, Vector *finish); void TriangulateInto(SShell *shell, SMesh *sm); + void MakeTrimEdgesInto(SEdgeList *sel, bool asUv, SCurve *sc, STrimBy *stb); void MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv, SShell *useCurvesFrom=NULL); + void MakeSectionEdgesInto(SShell *shell, SEdgeList *sel, SBezierList *sbl); void MakeClassifyingBsp(SShell *shell); double ChordToleranceForEdge(Vector a, Vector b); @@ -282,6 +294,8 @@ public: void TriangulateInto(SMesh *sm); void MakeEdgesInto(SEdgeList *sel); + void MakeSectionEdgesInto(Vector n, double d, + SEdgeList *sel, SBezierList *sbl); void Clear(void); }; diff --git a/textscreens.cpp b/textscreens.cpp index f7dbc670..c26ba269 100644 --- a/textscreens.cpp +++ b/textscreens.cpp @@ -610,8 +610,8 @@ void TextWindow::ScreenChangeShadedTriangles(int link, DWORD v) { SS.exportShadedTriangles = !SS.exportShadedTriangles; InvalidateGraphics(); } -void TextWindow::ScreenChangeExactCurves(int link, DWORD v) { - SS.exportExactCurves = !SS.exportExactCurves; +void TextWindow::ScreenChangePwlCurves(int link, DWORD v) { + SS.exportPwlCurves = !SS.exportPwlCurves; InvalidateGraphics(); } void TextWindow::ShowConfiguration(void) { @@ -681,14 +681,14 @@ void TextWindow::ShowConfiguration(void) { &ScreenChangeShadedTriangles, (!SS.exportShadedTriangles ? "" : "no"), (!SS.exportShadedTriangles ? "no" : "")); - Printf(false, "%Ft export exact curves in DXF: " + Printf(false, "%Ft curves as piecewise linear: " "%Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E", - &ScreenChangeExactCurves, - (SS.exportExactCurves ? "" : "yes"), - (SS.exportExactCurves ? "yes" : ""), - &ScreenChangeExactCurves, - (!SS.exportExactCurves ? "" : "no"), - (!SS.exportExactCurves ? "no" : "")); + &ScreenChangePwlCurves, + (SS.exportPwlCurves ? "" : "yes"), + (SS.exportPwlCurves ? "yes" : ""), + &ScreenChangePwlCurves, + (!SS.exportPwlCurves ? "" : "no"), + (!SS.exportPwlCurves ? "no" : "")); Printf(false, ""); Printf(false, "%Ft draw back faces: " diff --git a/ui.h b/ui.h index 473f7c7c..90583e84 100644 --- a/ui.h +++ b/ui.h @@ -145,7 +145,7 @@ public: static void ScreenGoToWebsite(int link, DWORD v); static void ScreenChangeBackFaces(int link, DWORD v); - static void ScreenChangeExactCurves(int link, DWORD v); + static void ScreenChangePwlCurves(int link, DWORD v); static void ScreenChangeShadedTriangles(int link, DWORD v); static void ScreenStepDimSteps(int link, DWORD v); diff --git a/util.cpp b/util.cpp index 5efafb7c..94d3a1c1 100644 --- a/util.cpp +++ b/util.cpp @@ -537,6 +537,10 @@ Point2d Vector::ProjectXy(void) { return p; } +Vector4 Vector::Project4d(void) { + return Vector4::From(1, x, y, z); +} + double Vector::DivPivoting(Vector delta) { double mx = fabs(delta.x), my = fabs(delta.y), mz = fabs(delta.z); @@ -731,6 +735,39 @@ Vector Vector::AtIntersectionOfPlanes(Vector na, double da, return Vector::From(detx/det, dety/det, detz/det); } +Vector4 Vector4::From(double w, double x, double y, double z) { + Vector4 ret; + ret.w = w; + ret.x = x; + ret.y = y; + ret.z = z; + return ret; +} + +Vector4 Vector4::From(double w, Vector v) { + return Vector4::From(w, w*v.x, w*v.y, w*v.z); +} + +Vector4 Vector4::Blend(Vector4 a, Vector4 b, double t) { + return (a.ScaledBy(1 - t)).Plus(b.ScaledBy(t)); +} + +Vector4 Vector4::Plus(Vector4 b) { + return Vector4::From(w + b.w, x + b.x, y + b.y, z + b.z); +} + +Vector4 Vector4::Minus(Vector4 b) { + return Vector4::From(w - b.w, x - b.x, y - b.y, z - b.z); +} + +Vector4 Vector4::ScaledBy(double s) { + return Vector4::From(w*s, x*s, y*s, z*s); +} + +Vector Vector4::PerspectiveProject(void) { + return Vector::From(x / w, y / w, z / w); +} + Point2d Point2d::From(double x, double y) { Point2d r; r.x = x; r.y = y;