Add a new renderer that prepares geometry for 2d backends.

SurfaceRenderer is a new renderer implementing the Canvas interface
running entirely on the CPU; it projects strokes and triangles
in the exact same way as OpenGL would, and it can be used for
rendering into raster or vector 2d surfaces.
pull/33/head
whitequark 2016-07-21 20:27:53 +00:00
parent 8af3a933cf
commit 8960ee365a
14 changed files with 623 additions and 27 deletions

View File

@ -158,6 +158,7 @@ set(solvespace_SOURCES
view.cpp
render/render.cpp
render/rendergl1.cpp
render/render2d.cpp
srf/boolean.cpp
srf/curve.cpp
srf/merge.cpp

View File

@ -11,7 +11,7 @@
SBsp2 *SBsp2::Alloc() { return (SBsp2 *)AllocTemporary(sizeof(SBsp2)); }
SBsp3 *SBsp3::Alloc() { return (SBsp3 *)AllocTemporary(sizeof(SBsp3)); }
SBsp3 *SBsp3::FromMesh(SMesh *m) {
SBsp3 *SBsp3::FromMesh(const SMesh *m) {
SBsp3 *bsp3 = NULL;
int i;

View File

@ -407,7 +407,17 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s
SEdgeList edges = {};
// Split the original edge against the mesh
edges.AddEdge(se->a, se->b, se->auxA);
root->OcclusionTestLine(*se, &edges, cnt, /*removeHidden=*/!SS.GW.showHdnLines);
root->OcclusionTestLine(*se, &edges, cnt);
if(SS.GW.showHdnLines) {
for(SEdge &se : edges.l) {
if(se.tag == 1) {
se.auxA = Style::HIDDEN_EDGE;
}
}
} else {
edges.l.RemoveTagged();
}
// the occlusion test splits unnecessarily; so fix those
edges.MergeCollinearSegments(se->a, se->b);
cnt++;

View File

@ -648,10 +648,10 @@ void SKdNode::SnapToMesh(SMesh *m) {
//-----------------------------------------------------------------------------
// For all the edges in sel, split them against the given triangle, and test
// them for occlusion. Keep only the visible segments. sel is both our input
// and our output.
// them for occlusion. sel is both our input and our output. tag indicates
// whether an edge is occluded.
//-----------------------------------------------------------------------------
void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr, bool removeHidden) const {
void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr) const {
SEdgeList seln = {};
Vector tn = tr->Normal().WithMagnitude(1);
@ -661,7 +661,8 @@ void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr, bool remo
if(tn.z > LENGTH_EPS) {
// If the edge crosses our triangle's plane, then split into above
// and below parts. Note that we must preserve auxA, which contains
// the style associated with this line.
// the style associated with this line, as well as the tag, which
// contains the occlusion status.
SEdge *se;
for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
double da = (se->a).Dot(tn) - td,
@ -672,12 +673,12 @@ void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr, bool remo
Vector m = Vector::AtIntersectionOfPlaneAndLine(
tn, td,
se->a, se->b, NULL);
seln.AddEdge(m, se->b, se->auxA);
seln.AddEdge(m, se->b, se->auxA, 0, se->tag);
se->b = m;
}
}
for(se = seln.l.First(); se; se = seln.l.NextAfter(se)) {
sel->AddEdge(se->a, se->b, se->auxA);
sel->AddEdge(se->a, se->b, se->auxA, 0, se->tag);
}
seln.Clear();
@ -723,48 +724,49 @@ void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr, bool remo
double dab = (db - da);
Vector spl = ((se->a).ScaledBy( db/dab)).Plus(
(se->b).ScaledBy(-da/dab));
seln.AddEdge(spl, se->b, se->auxA);
seln.AddEdge(spl, se->b, se->auxA, 0, se->tag);
se->b = spl;
}
}
for(se = seln.l.First(); se; se = seln.l.NextAfter(se)) {
// The split pieces are all behind the triangle, since only
// edges behind the triangle got split. So their auxB is 0.
sel->AddEdge(se->a, se->b, se->auxA, 0);
sel->AddEdge(se->a, se->b, se->auxA, 0, se->tag);
}
seln.Clear();
}
for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
bool occluded;
if(se->auxB) {
// Lies above or on the triangle plane, so triangle doesn't
// occlude it.
se->tag = 0;
occluded = false;
} else {
// Test the segment to see if it lies outside the triangle
// (i.e., outside wrt at least one edge), and keep it only
// then.
Point2d pt = ((se->a).Plus(se->b).ScaledBy(0.5)).ProjectXy();
se->tag = 1;
occluded = true;
for(i = 0; i < 3; i++) {
// If the test point lies on the boundary of our triangle,
// then we still discard the edge.
if(n[i].Dot(pt) - d[i] > LENGTH_EPS) se->tag = 0;
if(n[i].Dot(pt) - d[i] > LENGTH_EPS) occluded = false;
}
}
if(!removeHidden && se->tag == 1)
se->auxA = Style::HIDDEN_EDGE;
if(occluded) {
se->tag = 1;
}
}
if(removeHidden)
sel->l.RemoveTagged();
}
}
//-----------------------------------------------------------------------------
// Given an edge orig, occlusion test it against our mesh. We output an edge
// list in sel, containing the visible portions of that edge.
// list in sel, where only invisible portions of the edge are tagged.
//-----------------------------------------------------------------------------
void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt, bool removeHidden) const {
void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt) const {
if(gt && lt) {
double ac = (orig.a).Element(which),
bc = (orig.b).Element(which);
@ -774,13 +776,13 @@ void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt, bool remove
bc < c + KDTREE_EPS ||
which == 2)
{
lt->OcclusionTestLine(orig, sel, cnt, removeHidden);
lt->OcclusionTestLine(orig, sel, cnt);
}
if(ac > c - KDTREE_EPS ||
bc > c - KDTREE_EPS ||
which == 2)
{
gt->OcclusionTestLine(orig, sel, cnt, removeHidden);
gt->OcclusionTestLine(orig, sel, cnt);
}
} else {
STriangleLl *ll;
@ -789,7 +791,7 @@ void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt, bool remove
if(tr->tag == cnt) continue;
SplitLinesAgainstTriangle(sel, tr, removeHidden);
SplitLinesAgainstTriangle(sel, tr);
tr->tag = cnt;
}
}

View File

@ -156,8 +156,9 @@ void SEdgeList::Clear() {
l.Clear();
}
void SEdgeList::AddEdge(Vector a, Vector b, int auxA, int auxB) {
void SEdgeList::AddEdge(Vector a, Vector b, int auxA, int auxB, int tag) {
SEdge e = {};
e.tag = tag;
e.a = a;
e.b = b;
e.auxA = auxA;

View File

@ -50,7 +50,7 @@ public:
List<SEdge> l;
void Clear();
void AddEdge(Vector a, Vector b, int auxA=0, int auxB=0);
void AddEdge(Vector a, Vector b, int auxA=0, int auxB=0, int tag=0);
bool AssemblePolygon(SPolygon *dest, SEdge *errorAt, bool keepDir=false) const;
bool AssembleContour(Vector first, Vector last, SContour *dest,
SEdge *errorAt, bool keepDir) const;
@ -226,7 +226,7 @@ public:
SBsp2 *edges;
static SBsp3 *Alloc();
static SBsp3 *FromMesh(SMesh *m);
static SBsp3 *FromMesh(const SMesh *m);
Vector IntersectionWith(Vector a, Vector b) const;
@ -342,8 +342,8 @@ public:
bool *inter, bool *leaky, int auxA = 0) 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;
void OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt) const;
void SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr) const;
void SnapToMesh(SMesh *m);
void SnapToVertex(Vector v, SMesh *extras);

View File

@ -78,6 +78,27 @@ Vector Camera::AlignToPixelGrid(Vector v) const {
return UnProjectPoint3(v);
}
SBezier Camera::ProjectBezier(SBezier b) const {
Quaternion q = Quaternion::From(projRight, projUp);
q = q.Inverse();
// we want Q*(p - o) = Q*p - Q*o
b = b.TransformedBy(q.Rotate(offset).ScaledBy(scale), q, scale);
for(int i = 0; i <= b.deg; i++) {
Vector4 ct = Vector4::From(b.weight[i], b.ctrl[i]);
// so the desired curve, before perspective, is
// (x/w, y/w, z/w)
// and after perspective is
// ((x/w)/(1 - (z/w)*tangent, ...
// = (x/(w - z*tangent), ...
// so we want to let w' = w - z*tangent
ct.w = ct.w - ct.z*tangent;
b.ctrl[i] = ct.PerspectiveProject();
b.weight[i] = ct.w;
}
return b;
}
void Camera::LoadIdentity() {
offset = { 0.0, 0.0, 0.0 };
projRight = { 1.0, 0.0, 0.0 };

View File

@ -7,6 +7,10 @@
#ifndef SOLVESPACE_RENDER_H
#define SOLVESPACE_RENDER_H
//-----------------------------------------------------------------------------
// Interfaces and utilities common for all renderers.
//-----------------------------------------------------------------------------
enum class StipplePattern : uint32_t;
// A mapping from 3d sketch coordinates to 2d screen coordinates, using
@ -31,6 +35,8 @@ public:
Vector VectorFromProjs(Vector rightUpForward) const;
Vector AlignToPixelGrid(Vector v) const;
SBezier ProjectBezier(SBezier b) const;
void LoadIdentity();
void NormalizeProjectionVectors();
};
@ -204,6 +210,70 @@ public:
bool Pick(std::function<void()> drawFn);
};
// A canvas that renders onto a 2d surface, performing z-index sorting, occlusion testing, etc,
// on the CPU.
class SurfaceRenderer : public Canvas {
public:
Camera camera;
Lighting lighting;
// Chord tolerance, for converting beziers to pwl.
double chordTolerance;
// Render lists.
handle_map<hStroke, SEdgeList> edges;
handle_map<hStroke, SBezierList> beziers;
SMesh mesh;
// State.
BBox bbox;
SurfaceRenderer() : camera(), lighting(), chordTolerance(), mesh(), bbox() {}
virtual void Clear();
// Canvas interface.
const Camera &GetCamera() const override { return camera; }
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;
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;
void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
hFill hcf) override;
void DrawPoint(const Vector &o, double s, hFill hcf) override;
void DrawPolygon(const SPolygon &p, hFill hcf) override;
void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack, hStroke hcsTriangles) override;
void DrawFaces(const SMesh &m, const std::vector<uint32_t> &faces, hFill hcf) override;
void DrawPixmap(std::shared_ptr<const Pixmap> pm,
const Vector &o, const Vector &u, const Vector &v,
const Point2d &ta, const Point2d &tb, hFill hcf) override;
void InvalidatePixmap(std::shared_ptr<const Pixmap> pm) override;
// Geometry manipulation.
void CalculateBBox();
void ConvertBeziersToEdges();
void CullOccludedStrokes();
// Renderer operations.
void OutputInPaintOrder();
virtual bool CanOutputCurves() const = 0;
virtual bool CanOutputTriangles() const = 0;
virtual void OutputStart() = 0;
virtual void OutputBezier(const SBezier &b, hStroke hcs) = 0;
virtual void OutputTriangle(const STriangle &tr) = 0;
virtual void OutputEnd() = 0;
void OutputBezierAsNonrationalCubic(const SBezier &b, hStroke hcs);
};
//-----------------------------------------------------------------------------
// 3d renderers.
//-----------------------------------------------------------------------------
// An offscreen renderer based on OpenGL framebuffers.
class GlOffscreen {
public:

411
src/render/render2d.cpp Normal file
View File

@ -0,0 +1,411 @@
//-----------------------------------------------------------------------------
// Rendering projections to 2d surfaces: z-sorting, occlusion testing, etc.
//
// Copyright 2016 whitequark
//-----------------------------------------------------------------------------
#include "solvespace.h"
namespace SolveSpace {
// FIXME: The export coordinate system has a different handedness than display
// coordinate system; lighting and occlusion calculations are right-handed.
static Vector ProjectPoint3RH(const Camera &camera, Vector p) {
p = p.Plus(camera.offset);
Vector r;
r.x = p.Dot(camera.projRight);
r.y = p.Dot(camera.projUp);
r.z = p.Dot(camera.projRight.Cross(camera.projUp));
double w = 1 + r.z*camera.tangent*camera.scale;
return r.ScaledBy(camera.scale/w);
}
//-----------------------------------------------------------------------------
// Accumulation of geometry
//-----------------------------------------------------------------------------
void SurfaceRenderer::DrawLine(const Vector &a, const Vector &b, hStroke hcs) {
edges[hcs].AddEdge(ProjectPoint3RH(camera, a),
ProjectPoint3RH(camera, b));
}
void SurfaceRenderer::DrawEdges(const SEdgeList &el, hStroke hcs) {
for(const SEdge &e : el.l) {
edges[hcs].AddEdge(ProjectPoint3RH(camera, e.a),
ProjectPoint3RH(camera, e.b));
}
}
bool SurfaceRenderer::DrawBeziers(const SBezierList &bl, hStroke hcs) {
if(!CanOutputCurves())
return false;
for(const SBezier &b : bl.l) {
SBezier pb = camera.ProjectBezier(b);
beziers[hcs].l.Add(&pb);
}
return true;
}
void SurfaceRenderer::DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) {
Vector projDir = camera.projRight.Cross(camera.projUp);
for(const SOutline &o : ol.l) {
if(drawAs == DrawOutlinesAs::EMPHASIZED_AND_CONTOUR &&
!(o.IsVisible(projDir) || o.tag != 0))
continue;
if(drawAs == DrawOutlinesAs::EMPHASIZED_WITHOUT_CONTOUR &&
!(!o.IsVisible(projDir) && o.tag != 0))
continue;
if(drawAs == DrawOutlinesAs::CONTOUR_ONLY &&
!(o.IsVisible(projDir)))
continue;
edges[hcs].AddEdge(ProjectPoint3RH(camera, o.a),
ProjectPoint3RH(camera, o.b));
}
}
void SurfaceRenderer::DrawVectorText(const std::string &text, double height,
const Vector &o, const Vector &u, const Vector &v,
hStroke hcs) {
auto traceEdge = [&](Vector a, Vector b) {
edges[hcs].AddEdge(ProjectPoint3RH(camera, a),
ProjectPoint3RH(camera, b));
};
VectorFont::Builtin()->Trace(height, o, u, v, text, traceEdge, camera);
}
void SurfaceRenderer::DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
hFill hcf) {
Fill *fill = fills.FindById(hcf);
ssassert(fill->layer == Layer::NORMAL ||
fill->layer == Layer::DEPTH_ONLY ||
fill->layer == Layer::FRONT ||
fill->layer == Layer::BACK, "Unexpected mesh layer");
Vector zOffset = {};
if(fill->layer == Layer::BACK) {
zOffset.z -= 1e6;
} else if(fill->layer == Layer::FRONT) {
zOffset.z += 1e6;
}
zOffset.z += camera.scale * fill->zIndex;
STriMeta meta = {};
if(fill->layer != Layer::DEPTH_ONLY) {
meta.color = fill->color;
}
Vector ta = ProjectPoint3RH(camera, a).Plus(zOffset),
tb = ProjectPoint3RH(camera, b).Plus(zOffset),
tc = ProjectPoint3RH(camera, c).Plus(zOffset),
td = ProjectPoint3RH(camera, d).Plus(zOffset);
mesh.AddTriangle(meta, tc, tb, ta);
mesh.AddTriangle(meta, ta, td, tc);
}
void SurfaceRenderer::DrawPoint(const Vector &o, double s, hFill hcf) {
Vector u = camera.projRight.ScaledBy(1 / camera.scale * s),
v = camera.projUp.ScaledBy(1 / camera.scale * s);
DrawQuad(o.Minus(u).Minus(v), o.Minus(u).Plus(v),
o.Plus(u).Plus(v), o.Plus(u).Minus(v), hcf);
}
void SurfaceRenderer::DrawPolygon(const SPolygon &p, hFill hcf) {
SMesh m = {};
p.TriangulateInto(&m);
DrawMesh(m, hcf, {}, {});
m.Clear();
}
void SurfaceRenderer::DrawMesh(const SMesh &m,
hFill hcfFront, hFill hcfBack, hStroke hcsTriangles) {
Fill *fill = fills.FindById(hcfFront);
ssassert(fill->layer == Layer::NORMAL ||
fill->layer == Layer::DEPTH_ONLY, "Unexpected mesh layer");
Vector l0 = (lighting.lightDirection[0]).WithMagnitude(1),
l1 = (lighting.lightDirection[1]).WithMagnitude(1);
for(STriangle tr : m.l) {
tr.a = ProjectPoint3RH(camera, tr.a);
tr.b = ProjectPoint3RH(camera, tr.b);
tr.c = ProjectPoint3RH(camera, tr.c);
if(CanOutputTriangles() && fill->layer == Layer::NORMAL) {
if(fill->color.IsEmpty()) {
// Compute lighting, since we're going to draw the shaded triangles.
Vector n = tr.Normal().WithMagnitude(1);
double intensity = lighting.ambientIntensity +
max(0.0, (lighting.lightIntensity[0])*(n.Dot(l0))) +
max(0.0, (lighting.lightIntensity[1])*(n.Dot(l1)));
double r = min(1.0, tr.meta.color.redF() * intensity),
g = min(1.0, tr.meta.color.greenF() * intensity),
b = min(1.0, tr.meta.color.blueF() * intensity);
tr.meta.color = RGBf(r, g, b);
} else {
// We're going to draw this triangle, but it's not shaded.
tr.meta.color = fill->color;
}
} else {
// This triangle is just for occlusion testing.
tr.meta.color = {};
}
mesh.AddTriangle(&tr);
}
if(hcsTriangles.v != 0) {
for(const STriangle &tr : m.l) {
edges[hcsTriangles].AddEdge(ProjectPoint3RH(camera, tr.a),
ProjectPoint3RH(camera, tr.b));
edges[hcsTriangles].AddEdge(ProjectPoint3RH(camera, tr.b),
ProjectPoint3RH(camera, tr.c));
edges[hcsTriangles].AddEdge(ProjectPoint3RH(camera, tr.c),
ProjectPoint3RH(camera, tr.a));
}
}
}
void SurfaceRenderer::DrawFaces(const SMesh &m, const std::vector<uint32_t> &faces, hFill hcf) {
Fill *fill = fills.FindById(hcf);
ssassert(fill->layer == Layer::NORMAL ||
fill->layer == Layer::DEPTH_ONLY, "Unexpected mesh layer");
Vector zOffset = {};
zOffset.z += camera.scale * fill->zIndex;
size_t facesSize = faces.size();
for(STriangle tr : m.l) {
uint32_t face = tr.meta.face;
for(size_t j = 0; j < facesSize; j++) {
if(faces[j] != face) continue;
if(!fill->color.IsEmpty()) {
tr.meta.color = fill->color;
}
mesh.AddTriangle(tr.meta,
ProjectPoint3RH(camera, tr.a).Plus(zOffset),
ProjectPoint3RH(camera, tr.b).Plus(zOffset),
ProjectPoint3RH(camera, tr.c).Plus(zOffset));
break;
}
}
}
void SurfaceRenderer::DrawPixmap(std::shared_ptr<const Pixmap> pm,
const Vector &o, const Vector &u, const Vector &v,
const Point2d &ta, const Point2d &tb, hFill hcf) {
ssassert(false, "Not implemented");
}
void SurfaceRenderer::InvalidatePixmap(std::shared_ptr<const Pixmap> pm) {
ssassert(false, "Not implemented");
}
//-----------------------------------------------------------------------------
// Processing of geometry
//-----------------------------------------------------------------------------
void SurfaceRenderer::CalculateBBox() {
bbox.minp = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE);
bbox.maxp = Vector::From(VERY_NEGATIVE, VERY_NEGATIVE, VERY_NEGATIVE);
for(auto &it : edges) {
SEdgeList &el = it.second;
for(SEdge &e : el.l) {
bbox.Include(e.a);
bbox.Include(e.b);
}
}
for(auto &it : beziers) {
SBezierList &bl = it.second;
for(SBezier &b : bl.l) {
for(int i = 0; i <= b.deg; i++) {
bbox.Include(b.ctrl[i]);
}
}
}
for(STriangle &tr : mesh.l) {
for(int i = 0; i < 3; i++) {
bbox.Include(tr.vertices[i]);
}
}
}
void SurfaceRenderer::ConvertBeziersToEdges() {
for(auto &it : beziers) {
hStroke hcs = it.first;
SBezierList &bl = it.second;
SEdgeList &el = edges[hcs];
for(const SBezier &b : bl.l) {
if(b.deg == 1) {
el.AddEdge(b.ctrl[0], b.ctrl[1]);
} else {
List<Vector> lv = {};
b.MakePwlInto(&lv, chordTolerance);
for(int i = 1; i < lv.n; i++) {
el.AddEdge(lv.elem[i-1], lv.elem[i]);
}
lv.Clear();
}
}
bl.l.Clear();
}
beziers.clear();
}
void SurfaceRenderer::CullOccludedStrokes() {
// Perform occlusion testing, if necessary.
if(mesh.l.n == 0) return;
// We can't perform hidden line removal on exact curves.
ConvertBeziersToEdges();
// Remove hidden lines (on NORMAL layers), or remove visible lines (on OCCLUDED layers).
SKdNode *root = SKdNode::From(&mesh);
root->ClearTags();
int cnt = 1234;
for(auto &eit : edges) {
hStroke hcs = eit.first;
SEdgeList &el = eit.second;
Stroke *stroke = strokes.FindById(hcs);
if(stroke->layer != Layer::NORMAL &&
stroke->layer != Layer::OCCLUDED) continue;
SEdgeList nel = {};
for(const SEdge &e : el.l) {
SEdgeList oel = {};
oel.AddEdge(e.a, e.b);
root->OcclusionTestLine(e, &oel, cnt);
if(stroke->layer == Layer::OCCLUDED) {
for(SEdge &oe : oel.l) {
oe.tag = !oe.tag;
}
}
oel.l.RemoveTagged();
oel.MergeCollinearSegments(e.a, e.b);
for(const SEdge &oe : oel.l) {
nel.AddEdge(oe.a, oe.b);
}
oel.Clear();
cnt++;
}
el.l.Clear();
el.l = nel.l;
}
}
void SurfaceRenderer::OutputInPaintOrder() {
// Sort our strokes in paint order.
std::vector<std::pair<Layer, int>> paintOrder;
paintOrder.emplace_back(Layer::NORMAL, 0); // mesh
for(const Stroke &cs : strokes) {
paintOrder.emplace_back(cs.layer, cs.zIndex);
}
const Layer stackup[] = {
Layer::BACK, Layer::NORMAL, Layer::DEPTH_ONLY, Layer::OCCLUDED, Layer::FRONT
};
std::sort(paintOrder.begin(), paintOrder.end(),
[&](std::pair<Layer, int> a, std::pair<Layer, int> b) {
Layer aLayer = a.first,
bLayer = b.first;
int aZIndex = a.second,
bZIndex = b.second;
int aLayerIndex =
std::find(std::begin(stackup), std::end(stackup), aLayer) - std::begin(stackup);
int bLayerIndex =
std::find(std::begin(stackup), std::end(stackup), bLayer) - std::begin(stackup);
if(aLayerIndex == bLayerIndex) {
return aZIndex < bZIndex;
} else {
return aLayerIndex < bLayerIndex;
}
});
auto last = std::unique(paintOrder.begin(), paintOrder.end());
paintOrder.erase(last, paintOrder.end());
// Output geometry in paint order.
OutputStart();
for(auto &it : paintOrder) {
Layer layer = it.first;
int zIndex = it.second;
if(layer == Layer::NORMAL && zIndex == 0) {
SMesh mp = {};
SBsp3 *bsp = SBsp3::FromMesh(&mesh);
if(bsp) bsp->GenerateInPaintOrder(&mp);
for(const STriangle &tr : mp.l) {
// Cull back-facing and invisible triangles.
if(tr.Normal().z < 0) continue;
if(tr.meta.color.IsEmpty()) continue;
OutputTriangle(tr);
}
}
for(auto eit : edges) {
hStroke hcs = eit.first;
const SEdgeList &el = eit.second;
Stroke *stroke = strokes.FindById(hcs);
if(stroke->layer != layer || stroke->zIndex != zIndex) continue;
for(const SEdge &e : el.l) {
OutputBezier(SBezier::From(e.a, e.b), hcs);
}
}
for(auto &bit : beziers) {
hStroke hcs = bit.first;
const SBezierList &bl = bit.second;
Stroke *stroke = strokes.FindById(hcs);
if(stroke->layer != layer || stroke->zIndex != zIndex) continue;
for(const SBezier &b : bl.l) {
OutputBezier(b, hcs);
}
}
}
OutputEnd();
}
void SurfaceRenderer::Clear() {
for(auto &eit : edges) {
SEdgeList &el = eit.second;
el.l.Clear();
}
edges.clear();
for(auto &bit : beziers) {
SBezierList &bl = bit.second;
bl.l.Clear();
}
beziers.clear();
mesh.Clear();
}
void SurfaceRenderer::OutputBezierAsNonrationalCubic(const SBezier &b, hStroke hcs) {
// Arbitrary choice of tolerance; make it a little finer than pwl tolerance since
// it should be easier to achieve that with the smooth curves.
SBezierList bl;
b.MakeNonrationalCubicInto(&bl, chordTolerance / 2);
for(const SBezier &cb : bl.l) {
OutputBezier(cb, hcs);
}
bl.Clear();
}
}

View File

@ -43,6 +43,8 @@ enum class StipplePattern : uint32_t {
LAST = ZIGZAG
};
std::vector<double> StipplePatternDashes(StipplePattern pattern, double scale);
enum class Command : uint32_t;
// All of the hWhatever handles are a 32-bit ID, that is used to represent

View File

@ -293,6 +293,14 @@ void vl(); // debug function to validate heaps
// End of platform-specific functions
//================
template<class T>
struct CompareHandle {
bool operator()(T lhs, T rhs) const { return lhs.v < rhs.v; }
};
template<class Key, class T>
using handle_map = std::map<Key, T, CompareHandle<Key>>;
class Group;
class SSurface;
#include "dsc.h"

View File

@ -322,6 +322,38 @@ void SBezier::MakePwlInitialWorker(List<Vector> *l, double ta, double tb, double
}
}
void SBezier::MakeNonrationalCubicInto(SBezierList *bl, double tolerance, int depth) const {
Vector t0 = TangentAt(0), t1 = TangentAt(1);
// The curve is correct, and the first derivatives are correct, at the
// endpoints.
SBezier bnr = SBezier::From(
Start(),
Start().Plus(t0.ScaledBy(1.0/3)),
Finish().Minus(t1.ScaledBy(1.0/3)),
Finish());
bool closeEnough = true;
int i;
for(i = 1; i <= 3; i++) {
double t = i/4.0;
Vector p0 = PointAt(t),
pn = bnr.PointAt(t);
double d = (p0.Minus(pn)).Magnitude();
if(d > tolerance) {
closeEnough = false;
}
}
if(closeEnough || depth > 3) {
bl->l.Add(this);
} else {
SBezier bef, aft;
SplitAt(0.5, &bef, &aft);
bef.MakeNonrationalCubicInto(bl, tolerance, depth+1);
aft.MakeNonrationalCubicInto(bl, tolerance, depth+1);
}
}
Vector SSurface::PointAt(Point2d puv) const {
return PointAt(puv.x, puv.y);
}

View File

@ -14,6 +14,7 @@
double Bernstein(int k, int deg, double t);
double BernsteinDerivative(int k, int deg, double t);
class SBezierList;
class SSurface;
class SCurvePt;
@ -95,6 +96,7 @@ public:
void MakePwlInto(List<Vector> *l, double chordTol=0) const;
void MakePwlWorker(List<Vector> *l, double ta, double tb, double chordTol) const;
void MakePwlInitialWorker(List<Vector> *l, double ta, double tb, double chordTol) const;
void MakeNonrationalCubicInto(SBezierList *bl, double tolerance, int depth = 0) const;
void AllIntersectionsWith(const SBezier *sbb, SPointList *spl) const;
void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax) const;

View File

@ -1085,3 +1085,39 @@ bool BBox::Contains(const Point2d &p, double r) const {
p.x <= (maxp.x + r) &&
p.y <= (maxp.y + r);
}
std::vector<double> SolveSpace::StipplePatternDashes(StipplePattern pattern, double scale) {
// Inkscape ignores all elements that are exactly zero instead of drawing
// them as dots.
double zero = 1e-6;
std::vector<double> result;
switch(pattern) {
case StipplePattern::CONTINUOUS:
break;
case StipplePattern::SHORT_DASH:
result = { scale, scale * 2.0 };
break;
case StipplePattern::DASH:
result = { scale, scale };
break;
case StipplePattern::DASH_DOT:
result = { scale, scale * 0.5, zero, scale * 0.5 };
break;
case StipplePattern::DASH_DOT_DOT:
result = { scale, scale * 0.5, zero, scale * 0.5, scale * 0.5, zero };
break;
case StipplePattern::DOT:
result = { zero, scale * 0.5 };
break;
case StipplePattern::LONG_DASH:
result = { scale * 2.0, scale * 0.5 };
break;
case StipplePattern::FREEHAND:
case StipplePattern::ZIGZAG:
ssassert(false, "Freehand and zigzag export not implemented");
}
return result;
}