Allow rendering solid outlines using a distinct style.

A new button is added, "Show/hide outline of solid model".

When the outline is hidden, it is rendered using the "solid edge"
style. When the outline is shown, it is rendered using the "outline"
style.

In SolveSpace's true WYSIWYG tradition, the 2d view export follows
the rendered view exactly.

Moreover, shell edges are not rendered anymore, since there is not
much need in them anymore and not drawing them lessens the overlap
between various kinds of lines, which already includes entities,
solid edges and outlines.
This commit is contained in:
EvilSpirit 2016-03-14 22:14:24 +06:00 committed by whitequark
parent d1a2eb6d18
commit 24fc65a71c
14 changed files with 128 additions and 18 deletions

View File

@ -328,7 +328,8 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s
// to back-facing. // to back-facing.
if(SS.GW.showEdges) { if(SS.GW.showEdges) {
root->MakeCertainEdgesInto(sel, SKdNode::TURNING_EDGES, root->MakeCertainEdgesInto(sel, SKdNode::TURNING_EDGES,
false, NULL, NULL); /*coplanarIsInter=*/false, NULL, NULL,
GW.showOutlines ? Style::OUTLINE : Style::SOLID_EDGE);
} }
root->ClearTags(); root->ClearTags();

View File

@ -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) { 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); glBegin(GL_LINES);
ssglVertex3v(a); ssglVertex3v(a);
ssglVertex3v(b); ssglVertex3v(b);
@ -213,7 +213,7 @@ void ssglLine(const Vector &a, const Vector &b, double pixelWidth, bool maybeFat
void ssglPoint(Vector p, double pixelSize) void ssglPoint(Vector p, double pixelSize)
{ {
if(/*!maybeFat || */pixelSize < 3.0) { if(/*!maybeFat || */pixelSize <= 3.0) {
glBegin(GL_LINES); glBegin(GL_LINES);
Vector u = SS.GW.projRight.WithMagnitude(pixelSize / SS.GW.scale / 2.0); Vector u = SS.GW.projRight.WithMagnitude(pixelSize / SS.GW.scale / 2.0);
ssglVertex3v(p.Minus(u)); 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) void ssglDebugMesh(SMesh *m)
{ {
int i; int i;

View File

@ -233,6 +233,7 @@ void GraphicsWindow::Init(void) {
showShaded = true; showShaded = true;
showEdges = true; showEdges = true;
showMesh = false; showMesh = false;
showOutlines = false;
showTextWindow = true; showTextWindow = true;
ShowTextWindow(showTextWindow); ShowTextWindow(showTextWindow);

View File

@ -29,6 +29,7 @@ void Group::Clear(void) {
runningShell.Clear(); runningShell.Clear();
displayMesh.Clear(); displayMesh.Clear();
displayEdges.Clear(); displayEdges.Clear();
displayOutlines.Clear();
impMesh.Clear(); impMesh.Clear();
impShell.Clear(); impShell.Clear();
impEntity.Clear(); impEntity.Clear();

View File

@ -378,12 +378,14 @@ void Group::GenerateDisplayItems(void) {
displayMesh.MakeFromCopyOf(&(pg->displayMesh)); displayMesh.MakeFromCopyOf(&(pg->displayMesh));
displayEdges.Clear(); displayEdges.Clear();
displayOutlines.Clear();
if(SS.GW.showEdges) { if(SS.GW.showEdges) {
SEdge *se; SEdge *se;
SEdgeList *src = &(pg->displayEdges); SEdgeList *src = &(pg->displayEdges);
for(se = src->l.First(); se; se = src->l.NextAfter(se)) { for(se = src->l.First(); se; se = src->l.NextAfter(se)) {
displayEdges.l.Add(se); displayEdges.l.Add(se);
} }
displayOutlines.MakeFromCopyOf(&pg->displayOutlines);
} }
} else { } else {
// We do contribute new solid model, so we have to triangulate the // We do contribute new solid model, so we have to triangulate the
@ -401,17 +403,20 @@ void Group::GenerateDisplayItems(void) {
} }
displayEdges.Clear(); displayEdges.Clear();
displayOutlines.Clear();
if(SS.GW.showEdges) { if(SS.GW.showEdges) {
if(runningMesh.l.n > 0) { if(runningMesh.l.n > 0) {
// Triangle mesh only; no shell or emphasized edges. // Triangle mesh only; no shell or emphasized edges.
runningMesh.MakeCertainEdgesInto(&displayEdges, SKdNode::EMPHASIZED_EDGES); runningMesh.MakeCertainEdgesAndOutlinesInto(
&displayEdges, &displayOutlines, SKdNode::EMPHASIZED_EDGES);
} else { } else {
if(SS.exportMode) { if(SS.exportMode) {
displayMesh.MakeCertainEdgesInto(&displayEdges, SKdNode::SHARP_EDGES); displayMesh.MakeCertainEdgesAndOutlinesInto(
&displayEdges, &displayOutlines, SKdNode::SHARP_EDGES);
} else { } else {
runningShell.MakeEdgesInto(&displayEdges); displayMesh.MakeCertainEdgesAndOutlinesInto(
displayMesh.MakeCertainEdgesInto(&displayEdges, SKdNode::EMPHASIZED_EDGES); &displayEdges, &displayOutlines, SKdNode::EMPHASIZED_EDGES);
} }
} }
} }
@ -495,15 +500,23 @@ void Group::DrawDisplayItems(int t) {
} }
if(SS.GW.showEdges) { if(SS.GW.showEdges) {
Vector projDir = SS.GW.projRight.Cross(SS.GW.projUp);
glDepthMask(GL_FALSE); glDepthMask(GL_FALSE);
if(SS.GW.showHdnLines) { if(SS.GW.showHdnLines) {
ssglDepthRangeOffset(0); ssglDepthRangeOffset(0);
glDepthFunc(GL_GREATER); glDepthFunc(GL_GREATER);
ssglDrawEdges(&displayEdges, false, { Style::HIDDEN_EDGE }); ssglDrawEdges(&displayEdges, false, { Style::HIDDEN_EDGE });
ssglDrawOutlines(&displayOutlines, projDir, { Style::HIDDEN_EDGE });
glDepthFunc(GL_LEQUAL); glDepthFunc(GL_LEQUAL);
} }
ssglDepthRangeOffset(2); ssglDepthRangeOffset(2);
ssglDrawEdges(&displayEdges, false, { Style::SOLID_EDGE }); 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); glDepthMask(GL_TRUE);
} }

BIN
src/icons/outlines.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

View File

@ -91,9 +91,10 @@ void SMesh::MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d) {
m.Clear(); m.Clear();
} }
void SMesh::MakeCertainEdgesInto(SEdgeList *sel, int type) { void SMesh::MakeCertainEdgesAndOutlinesInto(SEdgeList *sel, SOutlineList *sol, int type) {
SKdNode *root = SKdNode::From(this); SKdNode *root = SKdNode::From(this);
root->MakeCertainEdgesInto(sel, type, false, NULL, NULL); root->MakeCertainEdgesInto(sel, type, false, NULL, NULL);
root->MakeOutlinesInto(sol);
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -923,8 +924,8 @@ static bool CheckAndAddTrianglePair(std::set<std::pair<STriangle *, STriangle *>
// * emphasized edges (i.e., edges where a triangle from one face joins // * emphasized edges (i.e., edges where a triangle from one face joins
// a triangle from a different face) // a triangle from a different face)
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how, void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how, bool coplanarIsInter,
bool coplanarIsInter, bool *inter, bool *leaky) bool *inter, bool *leaky, int auxA)
{ {
if(inter) *inter = false; if(inter) *inter = false;
if(leaky) *leaky = false; if(leaky) *leaky = false;
@ -946,18 +947,18 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how,
switch(how) { switch(how) {
case NAKED_OR_SELF_INTER_EDGES: case NAKED_OR_SELF_INTER_EDGES:
if(info.count != 1) { if(info.count != 1) {
sel->AddEdge(a, b); sel->AddEdge(a, b, auxA);
if(leaky) *leaky = true; if(leaky) *leaky = true;
} }
if(info.intersectsMesh) { if(info.intersectsMesh) {
sel->AddEdge(a, b); sel->AddEdge(a, b, auxA);
if(inter) *inter = true; if(inter) *inter = true;
} }
break; break;
case SELF_INTER_EDGES: case SELF_INTER_EDGES:
if(info.intersectsMesh) { if(info.intersectsMesh) {
sel->AddEdge(a, b); sel->AddEdge(a, b, auxA);
if(inter) *inter = true; if(inter) *inter = true;
} }
break; break;
@ -972,7 +973,7 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how,
// This triangle is back-facing (or on edge), and // This triangle is back-facing (or on edge), and
// this edge has exactly one mate, and that mate is // this edge has exactly one mate, and that mate is
// front-facing. So this is a turning edge. // front-facing. So this is a turning edge.
sel->AddEdge(a, b, Style::SOLID_EDGE); sel->AddEdge(a, b, auxA);
} }
break; break;
@ -984,7 +985,7 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how,
// different faces; either really different faces, // different faces; either really different faces,
// or one is from a face and the other is zero (i.e., // or one is from a face and the other is zero (i.e.,
// not from a face). // not from a face).
sel->AddEdge(a, b); sel->AddEdge(a, b, auxA);
} }
break; break;
@ -1008,7 +1009,7 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how,
break; break;
// The two triangles that join at this edge meet at a sharp // The two triangles that join at this edge meet at a sharp
// angle. This implies they come from different faces. // angle. This implies they come from different faces.
sel->AddEdge(a, b); sel->AddEdge(a, b, auxA);
} }
} }
break; break;
@ -1021,3 +1022,52 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how,
} }
} }
void SKdNode::MakeOutlinesInto(SOutlineList *sol)
{
std::vector<STriangle *> tris;
ClearTags();
ListTrianglesInto(&tris);
std::set<std::pair<STriangle *, STriangle *>> 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);
}
}

View File

@ -13,6 +13,7 @@ class SPolygon;
class SContour; class SContour;
class SMesh; class SMesh;
class SBsp3; class SBsp3;
class SOutlineList;
class SEdge { class SEdge {
public: public:
@ -249,7 +250,7 @@ public:
void MakeFromAssemblyOf(SMesh *a, SMesh *b); void MakeFromAssemblyOf(SMesh *a, SMesh *b);
void MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d); 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); bool IsEmpty(void);
void RemapFaces(Group *g, int remap); void RemapFaces(Group *g, int remap);
@ -267,6 +268,24 @@ public:
static STriangleLl *Alloc(void); static STriangleLl *Alloc(void);
}; };
class SOutline {
public:
int tag;
Vector a, b, nl, nr;
};
class SOutlineList {
public:
List<SOutline> l;
void Clear();
void AddEdge(Vector a, Vector b, Vector nl, Vector nr);
void MakeFromCopyOf(SOutlineList *ol);
void FillOutlineTags(Vector projDir);
};
class SKdNode { class SKdNode {
public: public:
struct EdgeOnInfo { struct EdgeOnInfo {
@ -304,7 +323,8 @@ public:
SHARP_EDGES = 500, SHARP_EDGES = 500,
}; };
void MakeCertainEdgesInto(SEdgeList *sel, int how, bool coplanarIsInter, 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 OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt, bool removeHidden);
void SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr, bool removeHidden); void SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr, bool removeHidden);

View File

@ -176,6 +176,7 @@ public:
bool displayDirty; bool displayDirty;
SMesh displayMesh; SMesh displayMesh;
SEdgeList displayEdges; SEdgeList displayEdges;
SOutlineList displayOutlines;
enum { enum {
COMBINE_AS_UNION = 0, COMBINE_AS_UNION = 0,
@ -768,6 +769,7 @@ public:
DRAW_ERROR = 12, DRAW_ERROR = 12,
DIM_SOLID = 13, DIM_SOLID = 13,
HIDDEN_EDGE = 14, HIDDEN_EDGE = 14,
OUTLINE = 15,
FIRST_CUSTOM = 0x100 FIRST_CUSTOM = 0x100
}; };

View File

@ -21,6 +21,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <map> #include <map>
#include <set>
#ifdef HAVE_STDINT_H #ifdef HAVE_STDINT_H
# include <stdint.h> # include <stdint.h>
#endif #endif
@ -346,6 +347,7 @@ void ssglFillMesh(bool useSpecColor, RgbaColor color,
SMesh *m, uint32_t h, uint32_t s1, uint32_t s2); SMesh *m, uint32_t h, uint32_t s1, uint32_t s2);
void ssglDebugPolygon(SPolygon *p); void ssglDebugPolygon(SPolygon *p);
void ssglDrawEdges(SEdgeList *l, bool endpointsToo, hStyle hs); void ssglDrawEdges(SEdgeList *l, bool endpointsToo, hStyle hs);
void ssglDrawOutlines(SOutlineList *l, Vector projDir, hStyle hs);
void ssglDebugMesh(SMesh *m); void ssglDebugMesh(SMesh *m);
void ssglMarkPolygonNormal(SPolygon *p); void ssglMarkPolygonNormal(SPolygon *p);
typedef void ssglLineFn(void *data, Vector a, Vector b); typedef void ssglLineFn(void *data, Vector a, Vector b);

View File

@ -25,6 +25,7 @@ const Style::Default Style::Defaults[] = {
{ { DRAW_ERROR }, "DrawError", RGBf(1.0, 0.0, 0.0), 8.0, 0 }, { { 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 }, { { 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 }, { { 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 } { { 0 }, NULL, RGBf(0.0, 0.0, 0.0), 0.0, 0 }
}; };

View File

@ -38,6 +38,7 @@ TextWindow::HideShowIcon TextWindow::hideShowIcons[] = {
{ &SPACER, 0, 0 }, { &SPACER, 0, 0 },
{ &(SS.GW.showShaded), Icon_shaded, "shaded view of solid model" }, { &(SS.GW.showShaded), Icon_shaded, "shaded view of solid model" },
{ &(SS.GW.showEdges), Icon_edges, "edges 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" }, { &(SS.GW.showMesh), Icon_mesh, "triangle mesh of solid model" },
{ &SPACER, 0, 0 }, { &SPACER, 0, 0 },
{ &(SS.GW.showHdnLines), Icon_hidden_lines, "hidden lines" }, { &(SS.GW.showHdnLines), Icon_hidden_lines, "hidden lines" },

View File

@ -704,6 +704,7 @@ public:
bool showTextWindow; bool showTextWindow;
bool showShaded; bool showShaded;
bool showEdges; bool showEdges;
bool showOutlines;
bool showFaces; bool showFaces;
bool showMesh; bool showMesh;
bool showHdnLines; bool showHdnLines;

View File

@ -64,6 +64,7 @@ void SolveSpaceUI::PushFromCurrentOnto(UndoStack *uk) {
dest.runningShell = {}; dest.runningShell = {};
dest.displayMesh = {}; dest.displayMesh = {};
dest.displayEdges = {}; dest.displayEdges = {};
dest.displayOutlines = {};
dest.remap = {}; dest.remap = {};
src->remap.DeepCopyInto(&(dest.remap)); src->remap.DeepCopyInto(&(dest.remap));