diff --git a/src/export.cpp b/src/export.cpp index 3739a6ef..db4791f2 100644 --- a/src/export.cpp +++ b/src/export.cpp @@ -143,7 +143,7 @@ public: bool DrawBeziers(const SBezierList &bl, hStroke hcs) override { ssassert(false, "Not implemented"); } - void DrawOutlines(const SOutlineList &ol, hStroke hcs) override { + void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) override { ssassert(false, "Not implemented"); } void DrawPoint(const Vector &o, double d, hFill hcf) override { @@ -209,11 +209,7 @@ void SolveSpaceUI::ExportViewOrWireframeTo(const std::string &filename, bool exp if(SS.GW.showEdges) { Group *g = SK.GetGroup(SS.GW.activeGroup); g->GenerateDisplayItems(); - SEdgeList *selr = &(g->displayEdges); - SEdge *se; - for(se = selr->l.First(); se; se = selr->l.NextAfter(se)) { - edges.AddEdge(se->a, se->b, Style::SOLID_EDGE); - } + g->displayOutlines.ListTaggedInto(&edges, Style::SOLID_EDGE); } if(SS.GW.showConstraints) { @@ -812,7 +808,7 @@ void SolveSpaceUI::ExportMeshTo(const std::string &filename) { ExportMeshAsObjTo(f, m); } else if(FilenameHasExtension(filename, ".js") || FilenameHasExtension(filename, ".html")) { - SEdgeList *e = &(SK.GetGroup(SS.GW.activeGroup)->displayEdges); + SOutlineList *e = &(SK.GetGroup(SS.GW.activeGroup)->displayOutlines); ExportMeshAsThreeJsTo(f, filename, m, e); } else { Error("Can't identify output file type from file extension of " @@ -899,11 +895,10 @@ void SolveSpaceUI::ExportMeshAsObjTo(FILE *f, SMesh *sm) { // Export the mesh as a JavaScript script, which is compatible with Three.js. //----------------------------------------------------------------------------- void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const std::string &filename, - SMesh *sm, SEdgeList *sel) + SMesh *sm, SOutlineList *sol) { SPointList spl = {}; STriangle *tr; - SEdge *e; Vector bndl, bndh; const char htmlbegin[] = R"( @@ -1055,14 +1050,15 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const std::string &filename, fputs(" ],\n" " edges: [\n", f); // Output edges. Assume user's model colors do not obscure white edges. - for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) { + for(const SOutline &so : sol->l) { + if(so.tag == 0) continue; fprintf(f, " [[%f, %f, %f], [%f, %f, %f]],\n", - e->a.x / SS.exportScale, - e->a.y / SS.exportScale, - e->a.z / SS.exportScale, - e->b.x / SS.exportScale, - e->b.y / SS.exportScale, - e->b.z / SS.exportScale); + so.a.x / SS.exportScale, + so.a.y / SS.exportScale, + so.a.z / SS.exportScale, + so.b.x / SS.exportScale, + so.b.y / SS.exportScale, + so.b.z / SS.exportScale); } fputs(" ]\n};\n", f); diff --git a/src/group.cpp b/src/group.cpp index ad07f264..f85d9818 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -28,7 +28,6 @@ void Group::Clear() { thisShell.Clear(); runningShell.Clear(); displayMesh.Clear(); - displayEdges.Clear(); displayOutlines.Clear(); impMesh.Clear(); impShell.Clear(); diff --git a/src/groupmesh.cpp b/src/groupmesh.cpp index f166733c..650a5560 100644 --- a/src/groupmesh.cpp +++ b/src/groupmesh.cpp @@ -377,14 +377,8 @@ void Group::GenerateDisplayItems() { displayMesh.Clear(); 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 { @@ -402,17 +396,14 @@ void Group::GenerateDisplayItems() { displayMesh.AddTriangle(&trn); } - displayEdges.Clear(); displayOutlines.Clear(); if(SS.GW.showEdges) { if(runningMesh.l.n > 0) { // Triangle mesh only; no shell or emphasized edges. - runningMesh.MakeCertainEdgesAndOutlinesInto( - &displayEdges, &displayOutlines, EdgeKind::EMPHASIZED); + runningMesh.MakeOutlinesInto(&displayOutlines, EdgeKind::EMPHASIZED); } else { - displayMesh.MakeCertainEdgesAndOutlinesInto( - &displayEdges, &displayOutlines, EdgeKind::SHARP); + displayMesh.MakeOutlinesInto(&displayOutlines, EdgeKind::SHARP); } } } @@ -536,13 +527,27 @@ void Group::Draw(Canvas *canvas) { DrawMesh(DrawMeshAs::DEFAULT, canvas); if(SS.GW.showEdges) { + if(SS.GW.showOutlines) { + Canvas::Stroke strokeOutline = {}; + strokeOutline.zIndex = 1; + strokeOutline.color = Style::Color(Style::OUTLINE); + strokeOutline.width = Style::Width(Style::OUTLINE); + Canvas::hStroke hcsOutline = canvas->GetStroke(strokeOutline); + + canvas->DrawOutlines(displayOutlines, hcsOutline, + Canvas::DrawOutlinesAs::CONTOUR_ONLY); + } + Canvas::Stroke strokeEdge = {}; strokeEdge.zIndex = 1; strokeEdge.color = Style::Color(Style::SOLID_EDGE); strokeEdge.width = Style::Width(Style::SOLID_EDGE); Canvas::hStroke hcsEdge = canvas->GetStroke(strokeEdge); - canvas->DrawEdges(displayEdges, hcsEdge); + canvas->DrawOutlines(displayOutlines, hcsEdge, + SS.GW.showOutlines + ? Canvas::DrawOutlinesAs::EMPHASIZED_WITHOUT_CONTOUR + : Canvas::DrawOutlinesAs::EMPHASIZED_AND_CONTOUR); if(SS.GW.showHdnLines) { Canvas::Stroke strokeHidden = strokeEdge; @@ -552,19 +557,8 @@ void Group::Draw(Canvas *canvas) { strokeHidden.stippleScale = Style::StippleScaleMm({ Style::HIDDEN_EDGE }); Canvas::hStroke hcsHidden = canvas->GetStroke(strokeHidden); - canvas->DrawEdges(displayEdges, hcsHidden); - canvas->DrawOutlines(displayOutlines, hcsHidden); - } - - if(SS.GW.showOutlines) { - Canvas::Stroke strokeOutline = strokeEdge; - strokeOutline.color = Style::Color(Style::OUTLINE); - strokeOutline.width = Style::Width(Style::OUTLINE); - Canvas::hStroke hcsOutline = canvas->GetStroke(strokeOutline); - - canvas->DrawOutlines(displayOutlines, hcsOutline); - } else { - canvas->DrawOutlines(displayOutlines, hcsEdge); + canvas->DrawOutlines(displayOutlines, hcsHidden, + Canvas::DrawOutlinesAs::EMPHASIZED_AND_CONTOUR); } } } diff --git a/src/mesh.cpp b/src/mesh.cpp index 65381860..39c4aaa1 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -91,10 +91,9 @@ void SMesh::MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d) { m.Clear(); } -void SMesh::MakeCertainEdgesAndOutlinesInto(SEdgeList *sel, SOutlineList *sol, EdgeKind type) { +void SMesh::MakeOutlinesInto(SOutlineList *sol, EdgeKind edgeKind) { SKdNode *root = SKdNode::From(this); - root->MakeCertainEdgesInto(sel, type, /*coplanarIsInter=*/false, NULL, NULL); - root->MakeOutlinesInto(sol); + root->MakeOutlinesInto(sol, edgeKind); } //----------------------------------------------------------------------------- @@ -1010,7 +1009,7 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, EdgeKind how, bool coplanarIs } } -void SKdNode::MakeOutlinesInto(SOutlineList *sol) const +void SKdNode::MakeOutlinesInto(SOutlineList *sol, EdgeKind edgeKind) const { std::vector tris; ClearTags(); @@ -1030,13 +1029,37 @@ void SKdNode::MakeOutlinesInto(SOutlineList *sol) const if(CheckAndAddTrianglePair(&edgeTris, tr, info.tr)) continue; + int tag = 0; + switch(edgeKind) { + case EdgeKind::EMPHASIZED: + if(tr->meta.face != info.tr->meta.face) { + tag = 1; + } + break; + + case EdgeKind::SHARP: { + Vector na0 = tr->normals[j].WithMagnitude(1.0); + Vector nb0 = tr->normals[(j + 1) % 3].WithMagnitude(1.0); + Vector na1 = info.tr->normals[info.ai].WithMagnitude(1.0); + Vector nb1 = info.tr->normals[info.bi].WithMagnitude(1.0); + if(!((na0.Equals(na1) && nb0.Equals(nb1)) || + (na0.Equals(nb1) && nb0.Equals(na1)))) { + tag = 1; + } + } + break; + + default: + ssassert(false, "Unexpected edge kind"); + } + Vector nl = tr->Normal().WithMagnitude(1.0); Vector nr = info.tr->Normal().WithMagnitude(1.0); // We don't add edges with the same left and right // normals because they can't produce outlines. - if(nl.Equals(nr)) continue; - sol->AddEdge(a, b, nl, nr); + if(tag == 0 && nl.Equals(nr)) continue; + sol->AddEdge(a, b, nl, nr, tag); } } } @@ -1052,15 +1075,23 @@ void SOutlineList::Clear() { l.Clear(); } -void SOutlineList::AddEdge(Vector a, Vector b, Vector nl, Vector nr) { +void SOutlineList::AddEdge(Vector a, Vector b, Vector nl, Vector nr, int tag) { SOutline so = {}; so.a = a; so.b = b; so.nl = nl; so.nr = nr; + so.tag = tag; l.Add(&so); } +void SOutlineList::ListTaggedInto(SEdgeList *el, int auxA, int auxB) { + for(const SOutline &so : l) { + if(so.tag == 0) continue; + el->AddEdge(so.a, so.b, auxA, auxB); + } +} + 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 29445504..9b58ff6c 100644 --- a/src/polygon.h +++ b/src/polygon.h @@ -269,7 +269,7 @@ public: void MakeFromAssemblyOf(SMesh *a, SMesh *b); void MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d); - void MakeCertainEdgesAndOutlinesInto(SEdgeList *sel, SOutlineList *sol, EdgeKind type); + void MakeOutlinesInto(SOutlineList *sol, EdgeKind type); bool IsEmpty() const; void RemapFaces(Group *g, int remap); @@ -300,7 +300,8 @@ public: List l; void Clear(); - void AddEdge(Vector a, Vector b, Vector nl, Vector nr); + void AddEdge(Vector a, Vector b, Vector nl, Vector nr, int tag = 0); + void ListTaggedInto(SEdgeList *el, int auxA = 0, int auxB = 0); void MakeFromCopyOf(SOutlineList *ol); }; @@ -336,7 +337,7 @@ public: void FindEdgeOn(Vector a, Vector b, int cnt, bool coplanarIsInter, EdgeOnInfo *info) const; void MakeCertainEdgesInto(SEdgeList *sel, EdgeKind how, bool coplanarIsInter, bool *inter, bool *leaky, int auxA = 0) const; - void MakeOutlinesInto(SOutlineList *sel) const; + void MakeOutlinesInto(SOutlineList *sel, EdgeKind tagKind) const; void OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt, bool removeHidden) const; void SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr, bool removeHidden) const; diff --git a/src/render/render.cpp b/src/render/render.cpp index 15c13ec0..e19baef8 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -297,7 +297,7 @@ void ObjectPicker::DrawEdges(const SEdgeList &el, hStroke hcs) { } } -void ObjectPicker::DrawOutlines(const SOutlineList &ol, hStroke hcs) { +void ObjectPicker::DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) { ssassert(false, "Not implemented"); } diff --git a/src/render/render.h b/src/render/render.h index 38e7d88c..0212dbd6 100644 --- a/src/render/render.h +++ b/src/render/render.h @@ -70,6 +70,17 @@ public: FRONT, // Always drawn above all other geometry }; + // The outlines are the collection of all edges that may be drawn. + // Outlines can be classified as emphasized or not; emphasized outlines indicate an abrupt + // change in the surface curvature. These are indicated by the SOutline tag. + // Outlines can also be classified as contour or not; contour outlines indicate the boundary + // of the filled mesh. Whether an outline is a part of contour or not depends on point of view. + enum class DrawOutlinesAs { + EMPHASIZED_AND_CONTOUR, // Both emphasized and contour outlines + EMPHASIZED_WITHOUT_CONTOUR, // Emphasized outlines except those also belonging to contour + CONTOUR_ONLY // Contour outlines only + }; + class Stroke { public: hStroke h; @@ -113,7 +124,7 @@ public: virtual void DrawLine(const Vector &a, const Vector &b, hStroke hcs) = 0; virtual void DrawEdges(const SEdgeList &el, hStroke hcs) = 0; virtual bool DrawBeziers(const SBezierList &bl, hStroke hcs) = 0; - virtual void DrawOutlines(const SOutlineList &ol, hStroke hcs) = 0; + virtual void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) = 0; virtual void DrawVectorText(const std::string &text, double height, const Vector &o, const Vector &u, const Vector &v, hStroke hcs) = 0; @@ -169,7 +180,7 @@ public: void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override; void DrawEdges(const SEdgeList &el, hStroke hcs) override; bool DrawBeziers(const SBezierList &bl, hStroke hcs) override { return false; } - void DrawOutlines(const SOutlineList &ol, hStroke hcs) override; + void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) override; void DrawVectorText(const std::string &text, double height, const Vector &o, const Vector &u, const Vector &v, hStroke hcs) override; @@ -216,7 +227,7 @@ public: void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override; void DrawEdges(const SEdgeList &el, hStroke hcs) override; bool DrawBeziers(const SBezierList &bl, hStroke hcs) override { return false; } - void DrawOutlines(const SOutlineList &ol, hStroke hcs) override; + void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) override; void DrawVectorText(const std::string &text, double height, const Vector &o, const Vector &u, const Vector &v, hStroke hcs) override; diff --git a/src/render/rendergl1.cpp b/src/render/rendergl1.cpp index 7cda977f..b08a2548 100644 --- a/src/render/rendergl1.cpp +++ b/src/render/rendergl1.cpp @@ -408,11 +408,32 @@ void OpenGl1Renderer::DrawEdges(const SEdgeList &el, hStroke hcs) { } } -void OpenGl1Renderer::DrawOutlines(const SOutlineList &ol, hStroke hcs) { +void OpenGl1Renderer::DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) { Vector projDir = camera.projRight.Cross(camera.projUp); - for(const SOutline *o = ol.l.First(); o; o = ol.l.NextAfter(o)) { - if(!o->IsVisible(projDir)) continue; - DoStippledLine(o->a, o->b, hcs); + switch(drawAs) { + case DrawOutlinesAs::EMPHASIZED_AND_CONTOUR: + for(const SOutline &o : ol.l) { + if(o.IsVisible(projDir) || o.tag != 0) { + DoStippledLine(o.a, o.b, hcs); + } + } + break; + + case DrawOutlinesAs::EMPHASIZED_WITHOUT_CONTOUR: + for(const SOutline &o : ol.l) { + if(!o.IsVisible(projDir) && o.tag != 0) { + DoStippledLine(o.a, o.b, hcs); + } + } + break; + + case DrawOutlinesAs::CONTOUR_ONLY: + for(const SOutline &o : ol.l) { + if(o.IsVisible(projDir)) { + DoStippledLine(o.a, o.b, hcs); + } + } + break; } } diff --git a/src/sketch.h b/src/sketch.h index c9ec9705..7423771a 100644 --- a/src/sketch.h +++ b/src/sketch.h @@ -192,7 +192,6 @@ public: bool displayDirty; SMesh displayMesh; - SEdgeList displayEdges; SOutlineList displayOutlines; enum class CombineAs : uint32_t { diff --git a/src/solvespace.h b/src/solvespace.h index 010b9610..59d60786 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -798,7 +798,7 @@ public: void ExportMeshAsStlTo(FILE *f, SMesh *sm); void ExportMeshAsObjTo(FILE *f, SMesh *sm); void ExportMeshAsThreeJsTo(FILE *f, const std::string &filename, - SMesh *sm, SEdgeList *sel); + SMesh *sm, SOutlineList *sol); void ExportViewOrWireframeTo(const std::string &filename, bool exportWireframe); void ExportSectionTo(const std::string &filename); void ExportWireframeCurves(SEdgeList *sel, SBezierList *sbl, diff --git a/src/undoredo.cpp b/src/undoredo.cpp index 3fe9c3bc..638100e9 100644 --- a/src/undoredo.cpp +++ b/src/undoredo.cpp @@ -63,7 +63,6 @@ void SolveSpaceUI::PushFromCurrentOnto(UndoStack *uk) { dest.thisShell = {}; dest.runningShell = {}; dest.displayMesh = {}; - dest.displayEdges = {}; dest.displayOutlines = {}; dest.remap = {};