diff --git a/src/export.cpp b/src/export.cpp index 1e39627..b6baa9a 100644 --- a/src/export.cpp +++ b/src/export.cpp @@ -328,7 +328,8 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s // to back-facing. if(SS.GW.showEdges) { root->MakeCertainEdgesInto(sel, SKdNode::TURNING_EDGES, - false, NULL, NULL); + /*coplanarIsInter=*/false, NULL, NULL, + GW.showOutlines ? Style::OUTLINE : Style::SOLID_EDGE); } root->ClearTags(); diff --git a/src/glhelper.cpp b/src/glhelper.cpp index 8f706ee..e4f6bb0 100644 --- a/src/glhelper.cpp +++ b/src/glhelper.cpp @@ -201,7 +201,7 @@ static void FatLineEndcap(Vector p, Vector u, Vector v) } void ssglLine(const Vector &a, const Vector &b, double pixelWidth, bool maybeFat) { - if(!maybeFat || pixelWidth < 3.0) { + if(!maybeFat || pixelWidth <= 3.0) { glBegin(GL_LINES); ssglVertex3v(a); ssglVertex3v(b); @@ -213,7 +213,7 @@ void ssglLine(const Vector &a, const Vector &b, double pixelWidth, bool maybeFat void ssglPoint(Vector p, double pixelSize) { - if(/*!maybeFat || */pixelSize < 3.0) { + if(/*!maybeFat || */pixelSize <= 3.0) { glBegin(GL_LINES); Vector u = SS.GW.projRight.WithMagnitude(pixelSize / SS.GW.scale / 2.0); ssglVertex3v(p.Minus(u)); @@ -587,6 +587,22 @@ void ssglDrawEdges(SEdgeList *el, bool endpointsToo, hStyle hs) } } +void ssglDrawOutlines(SOutlineList *sol, Vector projDir, hStyle hs) +{ + double lineWidth = Style::Width(hs); + int stippleType = Style::PatternType(hs); + double stippleScale = Style::StippleScaleMm(hs); + ssglLineWidth((float)lineWidth); + ssglColorRGB(Style::Color(hs)); + + sol->FillOutlineTags(projDir); + for(SOutline *so = sol->l.First(); so; so = sol->l.NextAfter(so)) { + if(!so->tag) continue; + ssglStippledLine(so->a, so->b, lineWidth, stippleType, stippleScale, + /*maybeFat=*/true); + } +} + void ssglDebugMesh(SMesh *m) { int i; diff --git a/src/graphicswin.cpp b/src/graphicswin.cpp index 894c48f..dc8d70f 100644 --- a/src/graphicswin.cpp +++ b/src/graphicswin.cpp @@ -233,6 +233,7 @@ void GraphicsWindow::Init(void) { showShaded = true; showEdges = true; showMesh = false; + showOutlines = false; showTextWindow = true; ShowTextWindow(showTextWindow); diff --git a/src/group.cpp b/src/group.cpp index db0cf4b..1e612a6 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -29,6 +29,7 @@ void Group::Clear(void) { runningShell.Clear(); displayMesh.Clear(); displayEdges.Clear(); + displayOutlines.Clear(); impMesh.Clear(); impShell.Clear(); impEntity.Clear(); diff --git a/src/groupmesh.cpp b/src/groupmesh.cpp index 8889b24..80f9f57 100644 --- a/src/groupmesh.cpp +++ b/src/groupmesh.cpp @@ -378,12 +378,14 @@ void Group::GenerateDisplayItems(void) { displayMesh.MakeFromCopyOf(&(pg->displayMesh)); displayEdges.Clear(); + displayOutlines.Clear(); if(SS.GW.showEdges) { SEdge *se; SEdgeList *src = &(pg->displayEdges); for(se = src->l.First(); se; se = src->l.NextAfter(se)) { displayEdges.l.Add(se); } + displayOutlines.MakeFromCopyOf(&pg->displayOutlines); } } else { // We do contribute new solid model, so we have to triangulate the @@ -401,17 +403,20 @@ void Group::GenerateDisplayItems(void) { } displayEdges.Clear(); + displayOutlines.Clear(); if(SS.GW.showEdges) { if(runningMesh.l.n > 0) { // Triangle mesh only; no shell or emphasized edges. - runningMesh.MakeCertainEdgesInto(&displayEdges, SKdNode::EMPHASIZED_EDGES); + runningMesh.MakeCertainEdgesAndOutlinesInto( + &displayEdges, &displayOutlines, SKdNode::EMPHASIZED_EDGES); } else { if(SS.exportMode) { - displayMesh.MakeCertainEdgesInto(&displayEdges, SKdNode::SHARP_EDGES); + displayMesh.MakeCertainEdgesAndOutlinesInto( + &displayEdges, &displayOutlines, SKdNode::SHARP_EDGES); } else { - runningShell.MakeEdgesInto(&displayEdges); - displayMesh.MakeCertainEdgesInto(&displayEdges, SKdNode::EMPHASIZED_EDGES); + displayMesh.MakeCertainEdgesAndOutlinesInto( + &displayEdges, &displayOutlines, SKdNode::EMPHASIZED_EDGES); } } } @@ -495,15 +500,23 @@ void Group::DrawDisplayItems(int t) { } if(SS.GW.showEdges) { + Vector projDir = SS.GW.projRight.Cross(SS.GW.projUp); + glDepthMask(GL_FALSE); if(SS.GW.showHdnLines) { ssglDepthRangeOffset(0); glDepthFunc(GL_GREATER); ssglDrawEdges(&displayEdges, false, { Style::HIDDEN_EDGE }); + ssglDrawOutlines(&displayOutlines, projDir, { Style::HIDDEN_EDGE }); glDepthFunc(GL_LEQUAL); } ssglDepthRangeOffset(2); ssglDrawEdges(&displayEdges, false, { Style::SOLID_EDGE }); + if(SS.GW.showOutlines) { + ssglDrawOutlines(&displayOutlines, projDir, { Style::OUTLINE }); + } else { + ssglDrawOutlines(&displayOutlines, projDir, { Style::SOLID_EDGE }); + } glDepthMask(GL_TRUE); } diff --git a/src/icons/outlines.png b/src/icons/outlines.png new file mode 100644 index 0000000..b1c95ab Binary files /dev/null and b/src/icons/outlines.png differ diff --git a/src/mesh.cpp b/src/mesh.cpp index b7f14d1..b3f4de8 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -91,9 +91,10 @@ void SMesh::MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d) { m.Clear(); } -void SMesh::MakeCertainEdgesInto(SEdgeList *sel, int type) { +void SMesh::MakeCertainEdgesAndOutlinesInto(SEdgeList *sel, SOutlineList *sol, int type) { SKdNode *root = SKdNode::From(this); root->MakeCertainEdgesInto(sel, type, false, NULL, NULL); + root->MakeOutlinesInto(sol); } //----------------------------------------------------------------------------- @@ -923,8 +924,8 @@ static bool CheckAndAddTrianglePair(std::set // * emphasized edges (i.e., edges where a triangle from one face joins // a triangle from a different face) //----------------------------------------------------------------------------- -void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how, - bool coplanarIsInter, bool *inter, bool *leaky) +void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how, bool coplanarIsInter, + bool *inter, bool *leaky, int auxA) { if(inter) *inter = false; if(leaky) *leaky = false; @@ -946,18 +947,18 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how, switch(how) { case NAKED_OR_SELF_INTER_EDGES: if(info.count != 1) { - sel->AddEdge(a, b); + sel->AddEdge(a, b, auxA); if(leaky) *leaky = true; } if(info.intersectsMesh) { - sel->AddEdge(a, b); + sel->AddEdge(a, b, auxA); if(inter) *inter = true; } break; case SELF_INTER_EDGES: if(info.intersectsMesh) { - sel->AddEdge(a, b); + sel->AddEdge(a, b, auxA); if(inter) *inter = true; } break; @@ -972,7 +973,7 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how, // 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, Style::SOLID_EDGE); + sel->AddEdge(a, b, auxA); } break; @@ -984,7 +985,7 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how, // 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); + sel->AddEdge(a, b, auxA); } break; @@ -1008,7 +1009,7 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how, break; // The two triangles that join at this edge meet at a sharp // angle. This implies they come from different faces. - sel->AddEdge(a, b); + sel->AddEdge(a, b, auxA); } } break; @@ -1021,3 +1022,52 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how, } } +void SKdNode::MakeOutlinesInto(SOutlineList *sol) +{ + std::vector tris; + ClearTags(); + ListTrianglesInto(&tris); + + std::set> edgeTris; + int cnt = 1234; + for(STriangle *tr : tris) { + for(int 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); + + SKdNode::EdgeOnInfo info = {}; + FindEdgeOn(a, b, cnt, /*coplanarIsInter=*/false, &info); + cnt++; + if(info.count != 1) continue; + if(CheckAndAddTrianglePair(&edgeTris, tr, info.tr)) + continue; + + sol->AddEdge(a, b, tr->Normal(), info.tr->Normal()); + } + } +} + +void SOutlineList::Clear() { + l.Clear(); +} + +void SOutlineList::AddEdge(Vector a, Vector b, Vector nl, Vector nr) { + SOutline so = {}; + so.a = a; + so.b = b; + so.nl = nl; + so.nr = nr; + l.Add(&so); +} + +void SOutlineList::FillOutlineTags(Vector projDir) { + for(SOutline *so = l.First(); so; so = l.NextAfter(so)) { + so->tag = ((so->nl.Dot(projDir) > 0.0) != (so->nr.Dot(projDir) > 0.0)); + } +} + +void SOutlineList::MakeFromCopyOf(SOutlineList *sol) { + for(SOutline *so = sol->l.First(); so; so = sol->l.NextAfter(so)) { + l.Add(so); + } +} diff --git a/src/polygon.h b/src/polygon.h index 4249e7a..7d2907b 100644 --- a/src/polygon.h +++ b/src/polygon.h @@ -13,6 +13,7 @@ class SPolygon; class SContour; class SMesh; class SBsp3; +class SOutlineList; class SEdge { public: @@ -249,7 +250,7 @@ public: void MakeFromAssemblyOf(SMesh *a, SMesh *b); void MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d); - void MakeCertainEdgesInto(SEdgeList *sel, int type); + void MakeCertainEdgesAndOutlinesInto(SEdgeList *sel, SOutlineList *sol, int type); bool IsEmpty(void); void RemapFaces(Group *g, int remap); @@ -267,6 +268,24 @@ public: static STriangleLl *Alloc(void); }; +class SOutline { +public: + int tag; + Vector a, b, nl, nr; +}; + +class SOutlineList { +public: + List l; + + void Clear(); + void AddEdge(Vector a, Vector b, Vector nl, Vector nr); + + void MakeFromCopyOf(SOutlineList *ol); + + void FillOutlineTags(Vector projDir); +}; + class SKdNode { public: struct EdgeOnInfo { @@ -304,7 +323,8 @@ public: SHARP_EDGES = 500, }; void MakeCertainEdgesInto(SEdgeList *sel, int how, bool coplanarIsInter, - bool *inter, bool *leaky); + bool *inter, bool *leaky, int auxA=0); + void MakeOutlinesInto(SOutlineList *sel); void OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt, bool removeHidden); void SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr, bool removeHidden); diff --git a/src/sketch.h b/src/sketch.h index e1c31aa..b34aeb5 100644 --- a/src/sketch.h +++ b/src/sketch.h @@ -176,6 +176,7 @@ public: bool displayDirty; SMesh displayMesh; SEdgeList displayEdges; + SOutlineList displayOutlines; enum { COMBINE_AS_UNION = 0, @@ -768,6 +769,7 @@ public: DRAW_ERROR = 12, DIM_SOLID = 13, HIDDEN_EDGE = 14, + OUTLINE = 15, FIRST_CUSTOM = 0x100 }; diff --git a/src/solvespace.h b/src/solvespace.h index 729c708..83939e3 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -21,6 +21,7 @@ #include #include #include +#include #ifdef HAVE_STDINT_H # include #endif @@ -346,6 +347,7 @@ void ssglFillMesh(bool useSpecColor, RgbaColor color, SMesh *m, uint32_t h, uint32_t s1, uint32_t s2); void ssglDebugPolygon(SPolygon *p); void ssglDrawEdges(SEdgeList *l, bool endpointsToo, hStyle hs); +void ssglDrawOutlines(SOutlineList *l, Vector projDir, hStyle hs); void ssglDebugMesh(SMesh *m); void ssglMarkPolygonNormal(SPolygon *p); typedef void ssglLineFn(void *data, Vector a, Vector b); diff --git a/src/style.cpp b/src/style.cpp index 24b1998..1fde1de 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -25,6 +25,7 @@ const Style::Default Style::Defaults[] = { { { DRAW_ERROR }, "DrawError", RGBf(1.0, 0.0, 0.0), 8.0, 0 }, { { DIM_SOLID }, "DimSolid", RGBf(0.1, 0.1, 0.1), 1.0, 0 }, { { HIDDEN_EDGE }, "HiddenEdge", RGBf(0.8, 0.8, 0.8), 2.0, 1 }, + { { OUTLINE }, "Outline", RGBf(0.8, 0.8, 0.8), 3.0, 5 }, { { 0 }, NULL, RGBf(0.0, 0.0, 0.0), 0.0, 0 } }; diff --git a/src/textwin.cpp b/src/textwin.cpp index f5d6b51..e685869 100644 --- a/src/textwin.cpp +++ b/src/textwin.cpp @@ -38,6 +38,7 @@ TextWindow::HideShowIcon TextWindow::hideShowIcons[] = { { &SPACER, 0, 0 }, { &(SS.GW.showShaded), Icon_shaded, "shaded view of solid model" }, { &(SS.GW.showEdges), Icon_edges, "edges of solid model" }, + { &(SS.GW.showOutlines), Icon_outlines, "outline of solid model" }, { &(SS.GW.showMesh), Icon_mesh, "triangle mesh of solid model" }, { &SPACER, 0, 0 }, { &(SS.GW.showHdnLines), Icon_hidden_lines, "hidden lines" }, diff --git a/src/ui.h b/src/ui.h index cdf2662..9762f31 100644 --- a/src/ui.h +++ b/src/ui.h @@ -704,6 +704,7 @@ public: bool showTextWindow; bool showShaded; bool showEdges; + bool showOutlines; bool showFaces; bool showMesh; bool showHdnLines; diff --git a/src/undoredo.cpp b/src/undoredo.cpp index 3bdd1a8..04b96ee 100644 --- a/src/undoredo.cpp +++ b/src/undoredo.cpp @@ -64,6 +64,7 @@ void SolveSpaceUI::PushFromCurrentOnto(UndoStack *uk) { dest.runningShell = {}; dest.displayMesh = {}; dest.displayEdges = {}; + dest.displayOutlines = {}; dest.remap = {}; src->remap.DeepCopyInto(&(dest.remap));