From 842645d61f0c2e12d6071efed6c34aa840819e81 Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Thu, 28 May 2009 21:40:17 -0800 Subject: [PATCH] Put back code to generate emphasized edges from a mesh; so now we can show edges for both meshes and shells, and export them and hidden line remove and all the usual stuff. And fix the zoom to fit on startup, so that it considers hidden entities too. That avoids the problem where things get generated at stupid chord tolerance because no entities were visible and the mesh of course did not yet exist. [git-p4: depot-paths = "//depot/solvespace/": change = 1961] --- export.cpp | 8 +-- graphicswin.cpp | 21 +++++--- groupmesh.cpp | 9 +++- mesh.cpp | 131 +++++++++++++++++++++++++++--------------------- polygon.h | 10 ++-- solvespace.cpp | 16 ++++-- ui.h | 5 +- wishlist.txt | 3 +- 8 files changed, 122 insertions(+), 81 deletions(-) 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?