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]
solver
Jonathan Westhues 2009-04-13 20:19:23 -08:00
parent 22afc5ea15
commit 775653a75d
12 changed files with 456 additions and 108 deletions

16
dsc.h
View File

@ -6,6 +6,7 @@ typedef unsigned long DWORD;
typedef unsigned char BYTE; typedef unsigned char BYTE;
class Vector; class Vector;
class Vector4;
class Point2d; class Point2d;
class hEntity; class hEntity;
class hParam; class hParam;
@ -92,6 +93,21 @@ public:
Vector origin, double cameraTan); Vector origin, double cameraTan);
Point2d Project2d(Vector u, Vector v); Point2d Project2d(Vector u, Vector v);
Point2d ProjectXy(void); 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 { class Point2d {

View File

@ -62,59 +62,53 @@ void SolveSpace::ExportSectionTo(char *filename) {
n = n.WithMagnitude(1); n = n.WithMagnitude(1);
d = origin.Dot(n); 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; SEdgeList el;
ZERO(&el); ZERO(&el);
root->MakeNakedEdgesInto(&el); SBezierList bl;
m.Clear(); ZERO(&bl);
g->runningShell.MakeSectionEdgesInto(n, d,
&el, SS.exportPwlCurves ? NULL : &bl);
el.CullExtraneousEdges();
// And write the edges. // And write the edges.
VectorFileWriter *out = VectorFileWriter::ForFile(filename); VectorFileWriter *out = VectorFileWriter::ForFile(filename);
if(out) { if(out) {
// parallel projection (no perspective), and no mesh // parallel projection (no perspective), and no mesh
ExportLinesAndMesh(&el, NULL, ExportLinesAndMesh(&el, &bl, NULL,
u, v, n, origin, 0, u, v, n, origin, 0,
out); out);
} }
el.Clear(); el.Clear();
bl.Clear();
} }
void SolveSpace::ExportViewTo(char *filename) { void SolveSpace::ExportViewTo(char *filename) {
int i; int i;
SEdgeList edges; SEdgeList edges;
ZERO(&edges); ZERO(&edges);
for(i = 0; i < SS.entity.n; i++) { SBezierList beziers;
Entity *e = &(SS.entity.elem[i]); ZERO(&beziers);
if(!e->IsVisible()) continue;
e->GenerateEdges(&edges);
}
SMesh *sm = NULL; SMesh *sm = NULL;
if(SS.GW.showShaded) { if(SS.GW.showShaded) {
sm = &((SS.GetGroup(SS.GW.activeGroup))->runningMesh); 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;
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) { if(SS.GW.showEdges) {
SEdgeList *selr = &((SS.GetGroup(SS.GW.activeGroup))->runningEdges); SEdgeList *selr = &((SS.GetGroup(SS.GW.activeGroup))->runningEdges);
SEdge *se; SEdge *se;
@ -130,14 +124,15 @@ void SolveSpace::ExportViewTo(char *filename) {
VectorFileWriter *out = VectorFileWriter::ForFile(filename); VectorFileWriter *out = VectorFileWriter::ForFile(filename);
if(out) { if(out) {
ExportLinesAndMesh(&edges, sm, ExportLinesAndMesh(&edges, &beziers, sm,
u, v, n, origin, SS.cameraTangent*SS.GW.scale, u, v, n, origin, SS.cameraTangent*SS.GW.scale,
out); out);
} }
edges.Clear(); 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 u, Vector v, Vector n,
Vector origin, double cameraTan, Vector origin, double cameraTan,
VectorFileWriter *out) 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); (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 cutter radius compensation is requested, then perform it now
if(fabs(SS.exportOffset) > LENGTH_EPS) { if(fabs(SS.exportOffset) > LENGTH_EPS) {
// assemble those edges into a polygon, and clear the edge list // 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 // Now write the lines and triangles to the output file
out->Output(sel, &sms); out->Output(sel, sbl, &sms);
smp.Clear(); smp.Clear();
sms.Clear(); sms.Clear();
@ -301,9 +307,10 @@ VectorFileWriter *VectorFileWriter::ForFile(char *filename) {
return ret; return ret;
} }
void VectorFileWriter::Output(SEdgeList *sel, SMesh *sm) { void VectorFileWriter::Output(SEdgeList *sel, SBezierList *sbl, SMesh *sm) {
STriangle *tr; STriangle *tr;
SEdge *e; SEdge *e;
SBezier *b;
// First calculate the bounding box. // First calculate the bounding box.
ptMin = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE); 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); (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(); StartFile();
if(sm && SS.exportShadedTriangles) { 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); 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(); FinishAndCloseFile();
} }
void VectorFileWriter::BezierAsPwl(SBezier *sb) {
List<Vector> 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 // Routines for DXF export
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -405,9 +438,14 @@ void DxfFileWriter::LineSegment(double x0, double y0, double x1, double y1) {
x0, y0, 0.0, x0, y0, 0.0,
x1, y1, 0.0); x1, y1, 0.0);
} }
void DxfFileWriter::Triangle(STriangle *tr) { void DxfFileWriter::Triangle(STriangle *tr) {
} }
void DxfFileWriter::Bezier(SBezier *sb) {
BezierAsPwl(sb);
}
void DxfFileWriter::FinishAndCloseFile(void) { void DxfFileWriter::FinishAndCloseFile(void) {
fprintf(f, fprintf(f,
" 0\r\n" " 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(x0 - ptMin.x), MmToPoints(y0 - ptMin.y),
MmToPoints(x1 - ptMin.x), MmToPoints(y1 - ptMin.y)); MmToPoints(x1 - ptMin.x), MmToPoints(y1 - ptMin.y));
} }
void EpsFileWriter::Triangle(STriangle *tr) { void EpsFileWriter::Triangle(STriangle *tr) {
fprintf(f, fprintf(f,
"%.3f %.3f %.3f setrgbcolor\r\n" "%.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)); MmToPoints(tr->c.x - ptMin.x), MmToPoints(tr->c.y - ptMin.y));
} }
void EpsFileWriter::Bezier(SBezier *sb) {
BezierAsPwl(sb);
}
void EpsFileWriter::FinishAndCloseFile(void) { void EpsFileWriter::FinishAndCloseFile(void) {
fprintf(f, fprintf(f,
"\r\n" "\r\n"
@ -521,6 +564,7 @@ void SvgFileWriter::LineSegment(double x0, double y0, double x1, double y1) {
(x0 - ptMin.x), (ptMax.y - y0), (x0 - ptMin.x), (ptMax.y - y0),
(x1 - ptMin.x), (ptMax.y - y1)); (x1 - ptMin.x), (ptMax.y - y1));
} }
void SvgFileWriter::Triangle(STriangle *tr) { void SvgFileWriter::Triangle(STriangle *tr) {
// crispEdges turns of anti-aliasing, which tends to cause hairline // crispEdges turns of anti-aliasing, which tends to cause hairline
// cracks between triangles; but there still is some cracking, so // 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)); RED(tr->meta.color), GREEN(tr->meta.color), BLUE(tr->meta.color));
} }
void SvgFileWriter::Bezier(SBezier *sb) {
BezierAsPwl(sb);
}
void SvgFileWriter::FinishAndCloseFile(void) { void SvgFileWriter::FinishAndCloseFile(void) {
fprintf(f, "\r\n</svg>\r\n"); fprintf(f, "\r\n</svg>\r\n");
fclose(f); 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, "PU%d,%d;\r\n", (int)MmToHpglUnits(x0), (int)MmToHpglUnits(y0));
fprintf(f, "PD%d,%d;\r\n", (int)MmToHpglUnits(x1), (int)MmToHpglUnits(y1)); fprintf(f, "PD%d,%d;\r\n", (int)MmToHpglUnits(x1), (int)MmToHpglUnits(y1));
} }
void HpglFileWriter::Triangle(STriangle *tr) { void HpglFileWriter::Triangle(STriangle *tr) {
// HPGL does not support filled triangles }
void HpglFileWriter::Bezier(SBezier *sb) {
BezierAsPwl(sb);
} }
void HpglFileWriter::FinishAndCloseFile(void) { void HpglFileWriter::FinishAndCloseFile(void) {

View File

@ -270,9 +270,25 @@ void GraphicsWindow::LoopOverPoints(
int i, j; int i, j;
for(i = 0; i < SS.entity.n; i++) { for(i = 0; i < SS.entity.n; i++) {
Entity *e = &(SS.entity.elem[i]); Entity *e = &(SS.entity.elem[i]);
if(!e->IsPoint()) continue;
if(!e->IsVisible()) continue; if(!e->IsVisible()) continue;
if(e->IsPoint()) {
HandlePointForZoomToFit(e->PointGetNum(), pmax, pmin, wmin, div); 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); Group *g = SS.GetGroup(activeGroup);
for(i = 0; i < g->runningMesh.l.n; i++) { for(i = 0; i < g->runningMesh.l.n; i++) {

View File

@ -56,8 +56,8 @@ void SolveSpace::Init(char *cmdLine) {
drawBackFaces = CnfThawDWORD(1, "DrawBackFaces"); drawBackFaces = CnfThawDWORD(1, "DrawBackFaces");
// Export shaded triangles in a 2d view // Export shaded triangles in a 2d view
exportShadedTriangles = CnfThawDWORD(1, "ExportShadedTriangles"); exportShadedTriangles = CnfThawDWORD(1, "ExportShadedTriangles");
// Export exact curves (instead of pwl) when possible // Export pwl curves (instead of exact) always
exportExactCurves = CnfThawDWORD(0, "ExportExactCurves"); exportPwlCurves = CnfThawDWORD(0, "ExportPwlCurves");
// Show toolbar in the graphics window // Show toolbar in the graphics window
showToolbar = CnfThawDWORD(1, "ShowToolbar"); showToolbar = CnfThawDWORD(1, "ShowToolbar");
// Recent files menus // Recent files menus
@ -124,8 +124,8 @@ void SolveSpace::Exit(void) {
CnfFreezeDWORD(drawBackFaces, "DrawBackFaces"); CnfFreezeDWORD(drawBackFaces, "DrawBackFaces");
// Export shaded triangles in a 2d view // Export shaded triangles in a 2d view
CnfFreezeDWORD(exportShadedTriangles, "ExportShadedTriangles"); CnfFreezeDWORD(exportShadedTriangles, "ExportShadedTriangles");
// Export exact curves (instead of pwl) when possible // Export pwl curves (instead of exact) always
CnfFreezeDWORD(exportExactCurves, "ExportExactCurves"); CnfFreezeDWORD(exportPwlCurves, "ExportPwlCurves");
// Show toolbar in the graphics window // Show toolbar in the graphics window
CnfFreezeDWORD(showToolbar, "ShowToolbar"); CnfFreezeDWORD(showToolbar, "ShowToolbar");

View File

@ -329,8 +329,6 @@ public:
Vector TransformIntPoint(int x, int y); Vector TransformIntPoint(int x, int y);
void LineSegment(int x0, int y0, int x1, int y1); 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 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 { class TtfFontList {
@ -352,8 +350,10 @@ public:
static bool StringEndsIn(char *str, char *ending); static bool StringEndsIn(char *str, char *ending);
static VectorFileWriter *ForFile(char *file); 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 LineSegment(double x0, double y0, double x1, double y1) = 0;
virtual void Triangle(STriangle *tr) = 0; virtual void Triangle(STriangle *tr) = 0;
virtual void StartFile(void) = 0; virtual void StartFile(void) = 0;
@ -363,6 +363,7 @@ class DxfFileWriter : public VectorFileWriter {
public: public:
void LineSegment(double x0, double y0, double x1, double y1); void LineSegment(double x0, double y0, double x1, double y1);
void Triangle(STriangle *tr); void Triangle(STriangle *tr);
void Bezier(SBezier *sb);
void StartFile(void); void StartFile(void);
void FinishAndCloseFile(void); void FinishAndCloseFile(void);
}; };
@ -371,6 +372,7 @@ public:
static double MmToPoints(double mm); static double MmToPoints(double mm);
void LineSegment(double x0, double y0, double x1, double y1); void LineSegment(double x0, double y0, double x1, double y1);
void Triangle(STriangle *tr); void Triangle(STriangle *tr);
void Bezier(SBezier *sb);
void StartFile(void); void StartFile(void);
void FinishAndCloseFile(void); void FinishAndCloseFile(void);
}; };
@ -378,6 +380,7 @@ class SvgFileWriter : public VectorFileWriter {
public: public:
void LineSegment(double x0, double y0, double x1, double y1); void LineSegment(double x0, double y0, double x1, double y1);
void Triangle(STriangle *tr); void Triangle(STriangle *tr);
void Bezier(SBezier *sb);
void StartFile(void); void StartFile(void);
void FinishAndCloseFile(void); void FinishAndCloseFile(void);
}; };
@ -386,6 +389,7 @@ public:
static double MmToHpglUnits(double mm); static double MmToHpglUnits(double mm);
void LineSegment(double x0, double y0, double x1, double y1); void LineSegment(double x0, double y0, double x1, double y1);
void Triangle(STriangle *tr); void Triangle(STriangle *tr);
void Bezier(SBezier *sb);
void StartFile(void); void StartFile(void);
void FinishAndCloseFile(void); void FinishAndCloseFile(void);
}; };
@ -451,7 +455,7 @@ public:
int drawBackFaces; int drawBackFaces;
int showToolbar; int showToolbar;
int exportShadedTriangles; int exportShadedTriangles;
int exportExactCurves; int exportPwlCurves;
int CircleSides(double r); int CircleSides(double r);
typedef enum { typedef enum {
@ -508,7 +512,7 @@ public:
void ExportMeshTo(char *file); void ExportMeshTo(char *file);
void ExportViewTo(char *file); void ExportViewTo(char *file);
void ExportSectionTo(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, Vector u, Vector v, Vector n, Vector origin,
double cameraTan, double cameraTan,
VectorFileWriter *out); VectorFileWriter *out);

View File

@ -4,39 +4,63 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#include "../solvespace.h" #include "../solvespace.h"
SBezier SBezier::From(Vector p0, Vector p1) { SBezier SBezier::From(Vector4 p0, Vector4 p1) {
SBezier ret; SBezier ret;
ZERO(&ret); ZERO(&ret);
ret.deg = 1; ret.deg = 1;
ret.weight[0] = ret.weight[1] = 1; ret.weight[0] = p0.w;
ret.ctrl[0] = p0; ret.ctrl [0] = p0.PerspectiveProject();
ret.ctrl[1] = p1; ret.weight[1] = p1.w;
ret.ctrl [1] = p1.PerspectiveProject();
return ret; return ret;
} }
SBezier SBezier::From(Vector p0, Vector p1, Vector p2) { SBezier SBezier::From(Vector4 p0, Vector4 p1, Vector4 p2) {
SBezier ret; SBezier ret;
ZERO(&ret); ZERO(&ret);
ret.deg = 2; ret.deg = 2;
ret.weight[0] = ret.weight[1] = ret.weight[2] = 1; ret.weight[0] = p0.w;
ret.ctrl[0] = p0; ret.ctrl [0] = p0.PerspectiveProject();
ret.ctrl[1] = p1; ret.weight[1] = p1.w;
ret.ctrl[2] = p2; ret.ctrl [1] = p1.PerspectiveProject();
ret.weight[2] = p2.w;
ret.ctrl [2] = p2.PerspectiveProject();
return ret; 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; SBezier ret;
ZERO(&ret); ZERO(&ret);
ret.deg = 3; ret.deg = 3;
ret.weight[0] = ret.weight[1] = ret.weight[2] = ret.weight[3] = 1; ret.weight[0] = p0.w;
ret.ctrl[0] = p0; ret.ctrl [0] = p0.PerspectiveProject();
ret.ctrl[1] = p1; ret.weight[1] = p1.w;
ret.ctrl[2] = p2; ret.ctrl [1] = p1.PerspectiveProject();
ret.ctrl[3] = p3; ret.weight[2] = p2.w;
ret.ctrl [2] = p2.PerspectiveProject();
ret.weight[3] = p3.w;
ret.ctrl [3] = p3.PerspectiveProject();
return ret; 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) { Vector SBezier::Start(void) {
return ctrl[0]; return ctrl[0];
} }
@ -73,6 +97,34 @@ SBezier SBezier::TransformedBy(Vector t, Quaternion q) {
return ret; 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) { bool SBezier::Equals(SBezier *b) {
// We just test of identical degree and control points, even though two // We just test of identical degree and control points, even though two
// curves could still be coincident (even sharing endpoints). // curves could still be coincident (even sharing endpoints).

View File

@ -107,6 +107,98 @@ Vector SBezier::PointAt(double t) {
return pt; 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<Vector> *l) { void SBezier::MakePwlInto(List<Vector> *l) {
l->Add(&(ctrl[0])); l->Add(&(ctrl[0]));
MakePwlWorker(l, 0.0, 1.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; 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 = ((num_u.ScaledBy(den)).Minus(num.ScaledBy(den_u)));
*tu = tu->ScaledBy(1.0/(den*den)); *tu = tu->ScaledBy(1.0/(den*den));

View File

@ -174,21 +174,13 @@ bool SSurface::LineEntirelyOutsideBbox(Vector a, Vector b, bool segment) {
return false; return false;
} }
void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv, //-----------------------------------------------------------------------------
SShell *useCurvesFrom) // 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)
{ {
STrimBy *stb;
for(stb = trim.First(); stb; stb = trim.NextAfter(stb)) {
SCurve *sc = shell->curve.FindById(stb->curve);
// We have the option to use the curves from another shell; this
// is relevant when generating the coincident edges while doing the
// Booleans, since the curves from the output shell will be split
// against any intersecting surfaces (and the originals aren't).
if(useCurvesFrom) {
sc = useCurvesFrom->curve.FindById(sc->newH);
}
Vector prev, prevuv, ptuv; Vector prev, prevuv, ptuv;
bool inCurve = false, empty = true; bool inCurve = false, empty = true;
double u = 0, v = 0; double u = 0, v = 0;
@ -227,6 +219,68 @@ void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv,
if(inCurve) dbp("trim was unterminated"); if(inCurve) dbp("trim was unterminated");
if(empty) dbp("trim was empty"); 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)
{
STrimBy *stb;
for(stb = trim.First(); stb; stb = trim.NextAfter(stb)) {
SCurve *sc = shell->curve.FindById(stb->curve);
// We have the option to use the curves from another shell; this
// is relevant when generating the coincident edges while doing the
// Booleans, since the curves from the output shell will be split
// against any intersecting surfaces (and the originals aren't).
if(useCurvesFrom) {
sc = useCurvesFrom->curve.FindById(sc->newH);
}
MakeTrimEdgesInto(sel, asUv, sc, stb);
}
}
//-----------------------------------------------------------------------------
// 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);
}
}
} }
void SSurface::TriangulateInto(SShell *shell, SMesh *sm) { void SSurface::TriangulateInto(SShell *shell, SMesh *sm) {
@ -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) { void SShell::TriangulateInto(SMesh *sm) {
SSurface *s; SSurface *s;
for(s = surface.First(); s; s = surface.NextAfter(s)) { for(s = surface.First(); s; s = surface.NextAfter(s)) {

View File

@ -50,7 +50,8 @@ public:
}; };
// Stuff for rational polynomial curves, of degree one to three. These are // 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 { class SBezier {
public: public:
int tag; int tag;
@ -59,6 +60,10 @@ public:
double weight[4]; double weight[4];
Vector PointAt(double t); 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 Start(void);
Vector Finish(void); Vector Finish(void);
bool Equals(SBezier *b); bool Equals(SBezier *b);
@ -69,10 +74,15 @@ public:
void Reverse(void); void Reverse(void);
SBezier TransformedBy(Vector t, Quaternion q); 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, Vector p3);
static SBezier From(Vector p0, Vector p1, Vector p2); static SBezier From(Vector p0, Vector p1, Vector p2);
static SBezier From(Vector p0, Vector p1); 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 { class SBezierList {
@ -233,8 +243,10 @@ public:
Vector *start, Vector *finish); Vector *start, Vector *finish);
void TriangulateInto(SShell *shell, SMesh *sm); void TriangulateInto(SShell *shell, SMesh *sm);
void MakeTrimEdgesInto(SEdgeList *sel, bool asUv, SCurve *sc, STrimBy *stb);
void MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv, void MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv,
SShell *useCurvesFrom=NULL); SShell *useCurvesFrom=NULL);
void MakeSectionEdgesInto(SShell *shell, SEdgeList *sel, SBezierList *sbl);
void MakeClassifyingBsp(SShell *shell); void MakeClassifyingBsp(SShell *shell);
double ChordToleranceForEdge(Vector a, Vector b); double ChordToleranceForEdge(Vector a, Vector b);
@ -282,6 +294,8 @@ public:
void TriangulateInto(SMesh *sm); void TriangulateInto(SMesh *sm);
void MakeEdgesInto(SEdgeList *sel); void MakeEdgesInto(SEdgeList *sel);
void MakeSectionEdgesInto(Vector n, double d,
SEdgeList *sel, SBezierList *sbl);
void Clear(void); void Clear(void);
}; };

View File

@ -610,8 +610,8 @@ void TextWindow::ScreenChangeShadedTriangles(int link, DWORD v) {
SS.exportShadedTriangles = !SS.exportShadedTriangles; SS.exportShadedTriangles = !SS.exportShadedTriangles;
InvalidateGraphics(); InvalidateGraphics();
} }
void TextWindow::ScreenChangeExactCurves(int link, DWORD v) { void TextWindow::ScreenChangePwlCurves(int link, DWORD v) {
SS.exportExactCurves = !SS.exportExactCurves; SS.exportPwlCurves = !SS.exportPwlCurves;
InvalidateGraphics(); InvalidateGraphics();
} }
void TextWindow::ShowConfiguration(void) { void TextWindow::ShowConfiguration(void) {
@ -681,14 +681,14 @@ void TextWindow::ShowConfiguration(void) {
&ScreenChangeShadedTriangles, &ScreenChangeShadedTriangles,
(!SS.exportShadedTriangles ? "" : "no"), (!SS.exportShadedTriangles ? "" : "no"),
(!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", "%Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E",
&ScreenChangeExactCurves, &ScreenChangePwlCurves,
(SS.exportExactCurves ? "" : "yes"), (SS.exportPwlCurves ? "" : "yes"),
(SS.exportExactCurves ? "yes" : ""), (SS.exportPwlCurves ? "yes" : ""),
&ScreenChangeExactCurves, &ScreenChangePwlCurves,
(!SS.exportExactCurves ? "" : "no"), (!SS.exportPwlCurves ? "" : "no"),
(!SS.exportExactCurves ? "no" : "")); (!SS.exportPwlCurves ? "no" : ""));
Printf(false, ""); Printf(false, "");
Printf(false, "%Ft draw back faces: " Printf(false, "%Ft draw back faces: "

2
ui.h
View File

@ -145,7 +145,7 @@ public:
static void ScreenGoToWebsite(int link, DWORD v); static void ScreenGoToWebsite(int link, DWORD v);
static void ScreenChangeBackFaces(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 ScreenChangeShadedTriangles(int link, DWORD v);
static void ScreenStepDimSteps(int link, DWORD v); static void ScreenStepDimSteps(int link, DWORD v);

View File

@ -537,6 +537,10 @@ Point2d Vector::ProjectXy(void) {
return p; return p;
} }
Vector4 Vector::Project4d(void) {
return Vector4::From(1, x, y, z);
}
double Vector::DivPivoting(Vector delta) { double Vector::DivPivoting(Vector delta) {
double mx = fabs(delta.x), my = fabs(delta.y), mz = fabs(delta.z); 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); 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 Point2d::From(double x, double y) {
Point2d r; Point2d r;
r.x = x; r.y = y; r.x = x; r.y = y;