Unify displayEdges and displayOutlines.

This has the following benefits:
  * Less geometry to generate; we can do both in one pass;
  * Less geometry to draw;
  * Eliminate overdraw of outlines on top of emphasized edges;
  * In future, being able to seamlessly stitch stippled lines.

The contour edges are now also drawn before emphasized edges;
this makes intersections of contour and emphasized edges look better
as the thinner emphasized edge doesn't clobber the depth buffer.
pull/33/head
EvilSpirit 2016-06-26 12:39:27 +06:00 committed by whitequark
parent e7c8c1c8f2
commit 7f411d1593
11 changed files with 114 additions and 63 deletions

View File

@ -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"(
<!DOCTYPE html>
@ -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);

View File

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

View File

@ -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);
}
}
}

View File

@ -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<STriangle *> 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);

View File

@ -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<SOutline> 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;

View File

@ -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");
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -192,7 +192,6 @@ public:
bool displayDirty;
SMesh displayMesh;
SEdgeList displayEdges;
SOutlineList displayOutlines;
enum class CombineAs : uint32_t {

View File

@ -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,

View File

@ -63,7 +63,6 @@ void SolveSpaceUI::PushFromCurrentOnto(UndoStack *uk) {
dest.thisShell = {};
dest.runningShell = {};
dest.displayMesh = {};
dest.displayEdges = {};
dest.displayOutlines = {};
dest.remap = {};