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?