diff --git a/export.cpp b/export.cpp
index 5bc5c9c..fdd17a5 100644
--- a/export.cpp
+++ b/export.cpp
@@ -248,7 +248,8 @@ void SolveSpace::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm,
// Generate the edges where a curved surface turns from front-facing
// to back-facing.
if(SS.GW.showEdges) {
- root->MakeTurningEdgesInto(sel);
+ root->MakeCertainEdgesInto(sel, SKdNode::TURNING_EDGES,
+ false, NULL, NULL);
}
root->ClearTags();
@@ -864,8 +865,9 @@ void SvgFileWriter::StartFile(void) {
"\r\n"
"
Exported SVG\r\n"
"\r\n",
- ptMax.x - ptMin.x, ptMax.y - ptMin.y,
- ptMax.x - ptMin.x, ptMax.y - ptMin.y);
+ (ptMax.x - ptMin.x) + 1, (ptMax.y - ptMin.y) + 1,
+ (ptMax.x - ptMin.x) + 1, (ptMax.y - ptMin.y) + 1);
+ // A little bit of extra space for the stroke width.
}
void SvgFileWriter::LineSegment(double x0, double y0, double x1, double y1) {
diff --git a/graphicswin.cpp b/graphicswin.cpp
index a536f3a..d937f39 100644
--- a/graphicswin.cpp
+++ b/graphicswin.cpp
@@ -252,6 +252,9 @@ void GraphicsWindow::HandlePointForZoomToFit(Vector p,
{
double w;
Vector pp = ProjectPoint4(p, &w);
+ // If div is true, then we calculate a perspective projection of the point.
+ // If not, then we do a parallel projection regardless of the current
+ // scale factor.
if(div) {
pp = pp.ScaledBy(1.0/w);
}
@@ -262,15 +265,15 @@ void GraphicsWindow::HandlePointForZoomToFit(Vector p,
pmin->y = min(pmin->y, pp.y);
*wmin = min(*wmin, w);
}
-void GraphicsWindow::LoopOverPoints(
- Point2d *pmax, Point2d *pmin, double *wmin, bool div)
+void GraphicsWindow::LoopOverPoints(Point2d *pmax, Point2d *pmin, double *wmin,
+ bool div, bool includingInvisibles)
{
HandlePointForZoomToFit(Vector::From(0, 0, 0), pmax, pmin, wmin, div);
int i, j;
for(i = 0; i < SK.entity.n; i++) {
Entity *e = &(SK.entity.elem[i]);
- if(!e->IsVisible()) continue;
+ if(!(e->IsVisible() || includingInvisibles)) continue;
if(e->IsPoint()) {
HandlePointForZoomToFit(e->PointGetNum(), pmax, pmin, wmin, div);
} else if(e->type == Entity::CIRCLE) {
@@ -306,11 +309,11 @@ void GraphicsWindow::LoopOverPoints(
}
}
}
-void GraphicsWindow::ZoomToFit(void) {
+void GraphicsWindow::ZoomToFit(bool includingInvisibles) {
// On the first run, ignore perspective.
Point2d pmax = { -1e12, -1e12 }, pmin = { 1e12, 1e12 };
double wmin = 1;
- LoopOverPoints(&pmax, &pmin, &wmin, false);
+ LoopOverPoints(&pmax, &pmin, &wmin, false, includingInvisibles);
double xm = (pmax.x + pmin.x)/2, ym = (pmax.y + pmin.y)/2;
double dx = pmax.x - pmin.x, dy = pmax.y - pmin.y;
@@ -335,7 +338,7 @@ void GraphicsWindow::ZoomToFit(void) {
pmax.x = -1e12; pmax.y = -1e12;
pmin.x = 1e12; pmin.y = 1e12;
wmin = 1;
- LoopOverPoints(&pmax, &pmin, &wmin, true);
+ LoopOverPoints(&pmax, &pmin, &wmin, true, includingInvisibles);
// Adjust the scale so that no points are behind the camera
if(wmin < 0.1) {
@@ -359,7 +362,7 @@ void GraphicsWindow::MenuView(int id) {
break;
case MNU_ZOOM_TO_FIT:
- SS.GW.ZoomToFit();
+ SS.GW.ZoomToFit(false);
break;
case MNU_NEAREST_ORTHO:
@@ -718,6 +721,10 @@ void GraphicsWindow::ToggleBool(int link, DWORD v) {
// so not meaningful to show them and hide the shaded.
if(!SS.GW.showShaded) SS.GW.showFaces = false;
+ // We might need to regenerate the mesh and edge list, since the edges
+ // wouldn't have been generated if they were previously hidden.
+ if(SS.GW.showEdges) (SK.GetGroup(SS.GW.activeGroup))->displayDirty = true;
+
SS.GenerateAll();
InvalidateGraphics();
SS.later.showTW = true;
diff --git a/groupmesh.cpp b/groupmesh.cpp
index 81d20cf..c360c38 100644
--- a/groupmesh.cpp
+++ b/groupmesh.cpp
@@ -321,6 +321,9 @@ void Group::GenerateShellAndMesh(void) {
}
void Group::GenerateDisplayItems(void) {
+ // This is potentially slow (since we've got to triangulate a shell, or
+ // to find the emphasized edges for a mesh), so we will run it only
+ // if its inputs have changed.
if(displayDirty) {
displayMesh.Clear();
runningShell.TriangulateInto(&displayMesh);
@@ -335,7 +338,11 @@ void Group::GenerateDisplayItems(void) {
}
displayEdges.Clear();
- runningShell.MakeEdgesInto(&displayEdges);
+
+ if(SS.GW.showEdges) {
+ runningShell.MakeEdgesInto(&displayEdges);
+ displayMesh.MakeEmphasizedEdgesInto(&displayEdges);
+ }
displayDirty = false;
}
diff --git a/mesh.cpp b/mesh.cpp
index eb2bf3a..42d8a49 100644
--- a/mesh.cpp
+++ b/mesh.cpp
@@ -77,11 +77,28 @@ void SMesh::MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d) {
// Select the naked edges in our resulting open mesh.
SKdNode *root = SKdNode::From(&m);
root->SnapToMesh(&m);
- root->MakeNakedEdgesInto(sel, false, NULL, NULL);
+ root->MakeCertainEdgesInto(sel, SKdNode::NAKED_OR_SELF_INTER_EDGES,
+ false, NULL, NULL);
m.Clear();
}
+void SMesh::MakeEmphasizedEdgesInto(SEdgeList *sel) {
+ SKdNode *root = SKdNode::From(this);
+ root->MakeCertainEdgesInto(sel, SKdNode::EMPHASIZED_EDGES,
+ false, NULL, NULL);
+}
+
+//-----------------------------------------------------------------------------
+// When we are called, all of the triangles from l.elem[start] to the end must
+// be coplanar. So we try to find a set of fewer triangles that covers the
+// exact same area, in order to reduce the number of triangles in the mesh.
+// We use this after a triangle has been split against the BSP.
+//
+// This is really ugly code; basically it just pastes things together to
+// form convex polygons, merging collinear edges when possible, then
+// triangulates the convex poly.
+//-----------------------------------------------------------------------------
void SMesh::Simplify(int start) {
int maxTriangles = (l.n - start) + 10;
@@ -756,7 +773,8 @@ void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt) {
// mesh, otherwise not.
//-----------------------------------------------------------------------------
void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int cnt,
- bool coplanarIsInter, bool *inter, bool *fwd)
+ bool coplanarIsInter, bool *inter, bool *fwd,
+ DWORD *face)
{
if(gt && lt) {
double ac = a.Element(which),
@@ -764,12 +782,12 @@ void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int cnt,
if(ac < c + KDTREE_EPS ||
bc < c + KDTREE_EPS)
{
- lt->FindEdgeOn(a, b, n, cnt, coplanarIsInter, inter, fwd);
+ lt->FindEdgeOn(a, b, n, cnt, coplanarIsInter, inter, fwd, face);
}
if(ac > c - KDTREE_EPS ||
bc > c - KDTREE_EPS)
{
- gt->FindEdgeOn(a, b, n, cnt, coplanarIsInter, inter, fwd);
+ gt->FindEdgeOn(a, b, n, cnt, coplanarIsInter, inter, fwd, face);
}
return;
}
@@ -794,6 +812,8 @@ void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int cnt,
} else {
*fwd = false;
}
+ // And record the triangle's face
+ *face = tr->meta.face;
} else if(((a.Equals(tr->a) && b.Equals(tr->b)) ||
(a.Equals(tr->b) && b.Equals(tr->c)) ||
(a.Equals(tr->c) && b.Equals(tr->a))))
@@ -848,15 +868,16 @@ void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int cnt,
}
//-----------------------------------------------------------------------------
-// Report all naked edges of the mesh (i.e., edges that don't join up to
-// a single anti-parallel edge of another triangle), and all edges that
-// intersect another triangle. If coplanarIsInter, then edges coplanar with
-// another triangle and within it are reported, otherwise not. We report
-// in *inter and *leaky whether the mesh is self-intersecting or leaky
-// (having naked edges) respectively.
+// Pick certain classes of edges out from our mesh. These might be:
+// * naked edges (i.e., edges with no anti-parallel neighbor) and self-
+// intersecting edges (i.e., edges that cross another triangle)
+// * turning edges (i.e., edges where a front-facing triangle joins
+// a back-facing triangle)
+// * emphasized edges (i.e., edges where a triangle from one face joins
+// a triangle from a different face)
//-----------------------------------------------------------------------------
-void SKdNode::MakeNakedEdgesInto(SEdgeList *sel, bool coplanarIsInter,
- bool *inter, bool *leaky)
+void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how,
+ bool coplanarIsInter, bool *inter, bool *leaky)
{
if(inter) *inter = false;
if(leaky) *leaky = false;
@@ -871,57 +892,51 @@ void SKdNode::MakeNakedEdgesInto(SEdgeList *sel, bool coplanarIsInter,
for(i = 0; i < m.l.n; i++) {
STriangle *tr = &(m.l.elem[i]);
- for(j = 0; j < 3; j++) {
- Vector a = (j == 0) ? tr->a : ((j == 1) ? tr->b : tr->c);
- Vector b = (j == 0) ? tr->b : ((j == 1) ? tr->c : tr->a);
-
- int n = 0, nOther = 0;
- bool thisIntersects = false, fwd;
- FindEdgeOn(a, b, &n, cnt, coplanarIsInter, &thisIntersects, &fwd);
- if(n != 1) {
- sel->AddEdge(a, b);
- if(leaky) *leaky = true;
- }
- if(thisIntersects) {
- sel->AddEdge(a, b);
- if(inter) *inter = true;
- }
-
- cnt++;
- }
- }
-
- m.Clear();
-}
-
-//-----------------------------------------------------------------------------
-// Report all the edges of the mesh where a front- and back-facing triangle
-// join. These edges should be drawn when we generate a wireframe drawing
-// of the part.
-//-----------------------------------------------------------------------------
-void SKdNode::MakeTurningEdgesInto(SEdgeList *sel) {
- SMesh m;
- ZERO(&m);
- ClearTags();
- MakeMeshInto(&m);
-
- int cnt = 1234;
- int i, j;
- for(i = 0; i < m.l.n; i++) {
- STriangle *tr = &(m.l.elem[i]);
- if(tr->Normal().z > LENGTH_EPS) continue;
- // So this is a back-facing triangle
-
for(j = 0; j < 3; j++) {
Vector a = (j == 0) ? tr->a : ((j == 1) ? tr->b : tr->c);
Vector b = (j == 0) ? tr->b : ((j == 1) ? tr->c : tr->a);
int n = 0;
- bool inter, fwd;
- FindEdgeOn(a, b, &n, cnt, true, &inter, &fwd);
- if(n == 1) {
- // and its neighbour is front-facing, so generate the edge.
- if(fwd) sel->AddEdge(a, b);
+ bool thisIntersects = false, fwd;
+ DWORD face;
+ FindEdgeOn(a, b, &n, cnt, coplanarIsInter,
+ &thisIntersects, &fwd, &face);
+
+ switch(how) {
+ case NAKED_OR_SELF_INTER_EDGES:
+ if(n != 1) {
+ sel->AddEdge(a, b);
+ if(leaky) *leaky = true;
+ }
+ if(thisIntersects) {
+ sel->AddEdge(a, b);
+ if(inter) *inter = true;
+ }
+ break;
+
+ case TURNING_EDGES:
+ if((tr->Normal().z < LENGTH_EPS) &&
+ (n == 1) &&
+ fwd)
+ {
+ // This triangle is back-facing (or on edge), and
+ // this edge has exactly one mate, and that mate is
+ // front-facing. So this is a turning edge.
+ sel->AddEdge(a, b);
+ }
+ break;
+
+ case EMPHASIZED_EDGES:
+ if(tr->meta.face != face && n == 1) {
+ // The two triangles that join at this edge come from
+ // different faces; either really different faces,
+ // or one is from a face and the other is zero (i.e.,
+ // not from a face).
+ sel->AddEdge(a, b);
+ }
+ break;
+
+ default: oops();
}
cnt++;
diff --git a/polygon.h b/polygon.h
index 7177174..a6ce271 100644
--- a/polygon.h
+++ b/polygon.h
@@ -192,6 +192,7 @@ public:
void MakeFromAssemblyOf(SMesh *a, SMesh *b);
void MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d);
+ void MakeEmphasizedEdgesInto(SEdgeList *sel);
bool IsEmpty(void);
void RemapFaces(Group *g, int remap);
@@ -232,10 +233,13 @@ public:
void ClearTags(void);
void FindEdgeOn(Vector a, Vector b, int *n, int cnt, bool coplanarIsInter,
- bool *inter, bool *fwd);
- void MakeNakedEdgesInto(SEdgeList *sel, bool coplanarIsInter,
+ bool *inter, bool *fwd,
+ DWORD *face);
+ static const int NAKED_OR_SELF_INTER_EDGES = 100;
+ static const int TURNING_EDGES = 200;
+ static const int EMPHASIZED_EDGES = 300;
+ void MakeCertainEdgesInto(SEdgeList *sel, int how, bool coplanarIsInter,
bool *inter, bool *leaky);
- void MakeTurningEdgesInto(SEdgeList *sel);
void OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt);
void SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr);
diff --git a/solvespace.cpp b/solvespace.cpp
index 149de4e..2a0025a 100644
--- a/solvespace.cpp
+++ b/solvespace.cpp
@@ -213,13 +213,15 @@ void SolveSpace::AfterNewFile(void) {
GW.height = h;
// The triangles haven't been generated yet, but zoom to fit the entities
- // roughly in the window, since that sets the mesh tolerance.
- GW.ZoomToFit();
+ // roughly in the window, since that sets the mesh tolerance. Consider
+ // invisible entities, so we still get something reasonable if the only
+ // thing visible is the not-yet-generated surfaces.
+ GW.ZoomToFit(true);
GenerateAll(0, INT_MAX);
later.showTW = true;
// Then zoom to fit again, to fit the triangles
- GW.ZoomToFit();
+ GW.ZoomToFit(false);
UpdateWindowTitle();
}
@@ -420,7 +422,9 @@ void SolveSpace::MenuAnalyze(int id) {
SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh);
SKdNode *root = SKdNode::From(m);
bool inters, leaks;
- root->MakeNakedEdgesInto(&(SS.nakedEdges), true, &inters, &leaks);
+ root->MakeCertainEdgesInto(&(SS.nakedEdges),
+ SKdNode::NAKED_OR_SELF_INTER_EDGES, true, &inters, &leaks);
+
InvalidateGraphics();
char *intersMsg = inters ?
@@ -446,7 +450,9 @@ void SolveSpace::MenuAnalyze(int id) {
SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh);
SKdNode *root = SKdNode::From(m);
bool inters, leaks;
- root->MakeNakedEdgesInto(&(SS.nakedEdges), false, &inters, &leaks);
+ root->MakeCertainEdgesInto(&(SS.nakedEdges),
+ SKdNode::NAKED_OR_SELF_INTER_EDGES, false, &inters, &leaks);
+
InvalidateGraphics();
if(inters) {
diff --git a/ui.h b/ui.h
index fa0ebaa..ffa05c4 100644
--- a/ui.h
+++ b/ui.h
@@ -301,8 +301,9 @@ public:
Vector VectorFromProjs(Vector rightUpForward);
void HandlePointForZoomToFit(Vector p, Point2d *pmax, Point2d *pmin,
double *wmin, bool div);
- void LoopOverPoints(Point2d *pmax, Point2d *pmin, double *wmin, bool div);
- void ZoomToFit(void);
+ void LoopOverPoints(Point2d *pmax, Point2d *pmin, double *wmin, bool div,
+ bool includingInvisibles);
+ void ZoomToFit(bool includingInvisibles);
hGroup activeGroup;
void EnsureValidActives(void);
diff --git a/wishlist.txt b/wishlist.txt
index a8b01a9..471b8d7 100644
--- a/wishlist.txt
+++ b/wishlist.txt
@@ -1,12 +1,11 @@
marching algorithm for surface intersection
tangent intersections
-put back the meshes
-----
line styles (color, thickness)
loop detection
-incremental regen of entities?
IGES and STEP export
+incremental regen of entities?