From 775653a75dc8318c948eafed4dd8593c4b61242d Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Mon, 13 Apr 2009 20:19:23 -0800 Subject: [PATCH] Add beginnings of exact curve export. We take the trim curves in our specified section plane; we then split them according to the start and endpoints of each STrimBy, using de Castejau's algorithm. These sections get projected (possibly in perspective, which I do correctly) into 2d and exported. Except, for now they just get pwl'd in the export files. That's the fallback, since it works for any file format. But that's the place to add special cases for circles etc., or to export them exactly. DXF supports the latter, but very painfully since I would need to write a later-versioned file, which requires thousands of lines of baggage. I'll probably stick with arcs. [git-p4: depot-paths = "//depot/solvespace/": change = 1936] --- dsc.h | 16 ++++++ export.cpp | 122 ++++++++++++++++++++++++++++++------------- graphicswin.cpp | 20 ++++++- solvespace.cpp | 8 +-- solvespace.h | 14 +++-- srf/curve.cpp | 82 +++++++++++++++++++++++------ srf/ratpoly.cpp | 94 ++++++++++++++++++++++++++++++++- srf/surface.cpp | 135 +++++++++++++++++++++++++++++++++++------------- srf/surface.h | 16 +++++- textscreens.cpp | 18 +++---- ui.h | 2 +- util.cpp | 37 +++++++++++++ 12 files changed, 456 insertions(+), 108 deletions(-) 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;