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
parent
8af3a933cf
commit
8960ee365a
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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++;
|
||||
|
|
42
src/mesh.cpp
42
src/mesh.cpp
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
36
src/util.cpp
36
src/util.cpp
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue