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.
This commit is contained in:
parent
8af3a933cf
commit
8960ee365a
@ -158,6 +158,7 @@ set(solvespace_SOURCES
|
|||||||
view.cpp
|
view.cpp
|
||||||
render/render.cpp
|
render/render.cpp
|
||||||
render/rendergl1.cpp
|
render/rendergl1.cpp
|
||||||
|
render/render2d.cpp
|
||||||
srf/boolean.cpp
|
srf/boolean.cpp
|
||||||
srf/curve.cpp
|
srf/curve.cpp
|
||||||
srf/merge.cpp
|
srf/merge.cpp
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
SBsp2 *SBsp2::Alloc() { return (SBsp2 *)AllocTemporary(sizeof(SBsp2)); }
|
SBsp2 *SBsp2::Alloc() { return (SBsp2 *)AllocTemporary(sizeof(SBsp2)); }
|
||||||
SBsp3 *SBsp3::Alloc() { return (SBsp3 *)AllocTemporary(sizeof(SBsp3)); }
|
SBsp3 *SBsp3::Alloc() { return (SBsp3 *)AllocTemporary(sizeof(SBsp3)); }
|
||||||
|
|
||||||
SBsp3 *SBsp3::FromMesh(SMesh *m) {
|
SBsp3 *SBsp3::FromMesh(const SMesh *m) {
|
||||||
SBsp3 *bsp3 = NULL;
|
SBsp3 *bsp3 = NULL;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
@ -407,7 +407,17 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s
|
|||||||
SEdgeList edges = {};
|
SEdgeList edges = {};
|
||||||
// Split the original edge against the mesh
|
// Split the original edge against the mesh
|
||||||
edges.AddEdge(se->a, se->b, se->auxA);
|
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
|
// the occlusion test splits unnecessarily; so fix those
|
||||||
edges.MergeCollinearSegments(se->a, se->b);
|
edges.MergeCollinearSegments(se->a, se->b);
|
||||||
cnt++;
|
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
|
// 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
|
// them for occlusion. sel is both our input and our output. tag indicates
|
||||||
// and our output.
|
// 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 = {};
|
SEdgeList seln = {};
|
||||||
|
|
||||||
Vector tn = tr->Normal().WithMagnitude(1);
|
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(tn.z > LENGTH_EPS) {
|
||||||
// If the edge crosses our triangle's plane, then split into above
|
// If the edge crosses our triangle's plane, then split into above
|
||||||
// and below parts. Note that we must preserve auxA, which contains
|
// 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;
|
SEdge *se;
|
||||||
for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
|
for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
|
||||||
double da = (se->a).Dot(tn) - td,
|
double da = (se->a).Dot(tn) - td,
|
||||||
@ -672,12 +673,12 @@ void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr, bool remo
|
|||||||
Vector m = Vector::AtIntersectionOfPlaneAndLine(
|
Vector m = Vector::AtIntersectionOfPlaneAndLine(
|
||||||
tn, td,
|
tn, td,
|
||||||
se->a, se->b, NULL);
|
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;
|
se->b = m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(se = seln.l.First(); se; se = seln.l.NextAfter(se)) {
|
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();
|
seln.Clear();
|
||||||
|
|
||||||
@ -723,48 +724,49 @@ void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr, bool remo
|
|||||||
double dab = (db - da);
|
double dab = (db - da);
|
||||||
Vector spl = ((se->a).ScaledBy( db/dab)).Plus(
|
Vector spl = ((se->a).ScaledBy( db/dab)).Plus(
|
||||||
(se->b).ScaledBy(-da/dab));
|
(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;
|
se->b = spl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(se = seln.l.First(); se; se = seln.l.NextAfter(se)) {
|
for(se = seln.l.First(); se; se = seln.l.NextAfter(se)) {
|
||||||
// The split pieces are all behind the triangle, since only
|
// The split pieces are all behind the triangle, since only
|
||||||
// edges behind the triangle got split. So their auxB is 0.
|
// 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();
|
seln.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
|
for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
|
||||||
|
bool occluded;
|
||||||
if(se->auxB) {
|
if(se->auxB) {
|
||||||
// Lies above or on the triangle plane, so triangle doesn't
|
// Lies above or on the triangle plane, so triangle doesn't
|
||||||
// occlude it.
|
// occlude it.
|
||||||
se->tag = 0;
|
occluded = false;
|
||||||
} else {
|
} else {
|
||||||
// Test the segment to see if it lies outside the triangle
|
// Test the segment to see if it lies outside the triangle
|
||||||
// (i.e., outside wrt at least one edge), and keep it only
|
// (i.e., outside wrt at least one edge), and keep it only
|
||||||
// then.
|
// then.
|
||||||
Point2d pt = ((se->a).Plus(se->b).ScaledBy(0.5)).ProjectXy();
|
Point2d pt = ((se->a).Plus(se->b).ScaledBy(0.5)).ProjectXy();
|
||||||
se->tag = 1;
|
occluded = true;
|
||||||
for(i = 0; i < 3; i++) {
|
for(i = 0; i < 3; i++) {
|
||||||
// If the test point lies on the boundary of our triangle,
|
// If the test point lies on the boundary of our triangle,
|
||||||
// then we still discard the edge.
|
// 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
|
// 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) {
|
if(gt && lt) {
|
||||||
double ac = (orig.a).Element(which),
|
double ac = (orig.a).Element(which),
|
||||||
bc = (orig.b).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 ||
|
bc < c + KDTREE_EPS ||
|
||||||
which == 2)
|
which == 2)
|
||||||
{
|
{
|
||||||
lt->OcclusionTestLine(orig, sel, cnt, removeHidden);
|
lt->OcclusionTestLine(orig, sel, cnt);
|
||||||
}
|
}
|
||||||
if(ac > c - KDTREE_EPS ||
|
if(ac > c - KDTREE_EPS ||
|
||||||
bc > c - KDTREE_EPS ||
|
bc > c - KDTREE_EPS ||
|
||||||
which == 2)
|
which == 2)
|
||||||
{
|
{
|
||||||
gt->OcclusionTestLine(orig, sel, cnt, removeHidden);
|
gt->OcclusionTestLine(orig, sel, cnt);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
STriangleLl *ll;
|
STriangleLl *ll;
|
||||||
@ -789,7 +791,7 @@ void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt, bool remove
|
|||||||
|
|
||||||
if(tr->tag == cnt) continue;
|
if(tr->tag == cnt) continue;
|
||||||
|
|
||||||
SplitLinesAgainstTriangle(sel, tr, removeHidden);
|
SplitLinesAgainstTriangle(sel, tr);
|
||||||
tr->tag = cnt;
|
tr->tag = cnt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,8 +156,9 @@ void SEdgeList::Clear() {
|
|||||||
l.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 = {};
|
SEdge e = {};
|
||||||
|
e.tag = tag;
|
||||||
e.a = a;
|
e.a = a;
|
||||||
e.b = b;
|
e.b = b;
|
||||||
e.auxA = auxA;
|
e.auxA = auxA;
|
||||||
|
@ -50,7 +50,7 @@ public:
|
|||||||
List<SEdge> l;
|
List<SEdge> l;
|
||||||
|
|
||||||
void Clear();
|
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 AssemblePolygon(SPolygon *dest, SEdge *errorAt, bool keepDir=false) const;
|
||||||
bool AssembleContour(Vector first, Vector last, SContour *dest,
|
bool AssembleContour(Vector first, Vector last, SContour *dest,
|
||||||
SEdge *errorAt, bool keepDir) const;
|
SEdge *errorAt, bool keepDir) const;
|
||||||
@ -226,7 +226,7 @@ public:
|
|||||||
SBsp2 *edges;
|
SBsp2 *edges;
|
||||||
|
|
||||||
static SBsp3 *Alloc();
|
static SBsp3 *Alloc();
|
||||||
static SBsp3 *FromMesh(SMesh *m);
|
static SBsp3 *FromMesh(const SMesh *m);
|
||||||
|
|
||||||
Vector IntersectionWith(Vector a, Vector b) const;
|
Vector IntersectionWith(Vector a, Vector b) const;
|
||||||
|
|
||||||
@ -342,8 +342,8 @@ public:
|
|||||||
bool *inter, bool *leaky, int auxA = 0) const;
|
bool *inter, bool *leaky, int auxA = 0) const;
|
||||||
void MakeOutlinesInto(SOutlineList *sel, EdgeKind tagKind) const;
|
void MakeOutlinesInto(SOutlineList *sel, EdgeKind tagKind) const;
|
||||||
|
|
||||||
void OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt, bool removeHidden) const;
|
void OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt) const;
|
||||||
void SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr, bool removeHidden) const;
|
void SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr) const;
|
||||||
|
|
||||||
void SnapToMesh(SMesh *m);
|
void SnapToMesh(SMesh *m);
|
||||||
void SnapToVertex(Vector v, SMesh *extras);
|
void SnapToVertex(Vector v, SMesh *extras);
|
||||||
|
@ -78,6 +78,27 @@ Vector Camera::AlignToPixelGrid(Vector v) const {
|
|||||||
return UnProjectPoint3(v);
|
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() {
|
void Camera::LoadIdentity() {
|
||||||
offset = { 0.0, 0.0, 0.0 };
|
offset = { 0.0, 0.0, 0.0 };
|
||||||
projRight = { 1.0, 0.0, 0.0 };
|
projRight = { 1.0, 0.0, 0.0 };
|
||||||
|
@ -7,6 +7,10 @@
|
|||||||
#ifndef SOLVESPACE_RENDER_H
|
#ifndef SOLVESPACE_RENDER_H
|
||||||
#define SOLVESPACE_RENDER_H
|
#define SOLVESPACE_RENDER_H
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Interfaces and utilities common for all renderers.
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
enum class StipplePattern : uint32_t;
|
enum class StipplePattern : uint32_t;
|
||||||
|
|
||||||
// A mapping from 3d sketch coordinates to 2d screen coordinates, using
|
// A mapping from 3d sketch coordinates to 2d screen coordinates, using
|
||||||
@ -31,6 +35,8 @@ public:
|
|||||||
Vector VectorFromProjs(Vector rightUpForward) const;
|
Vector VectorFromProjs(Vector rightUpForward) const;
|
||||||
Vector AlignToPixelGrid(Vector v) const;
|
Vector AlignToPixelGrid(Vector v) const;
|
||||||
|
|
||||||
|
SBezier ProjectBezier(SBezier b) const;
|
||||||
|
|
||||||
void LoadIdentity();
|
void LoadIdentity();
|
||||||
void NormalizeProjectionVectors();
|
void NormalizeProjectionVectors();
|
||||||
};
|
};
|
||||||
@ -204,6 +210,70 @@ public:
|
|||||||
bool Pick(std::function<void()> drawFn);
|
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.
|
// An offscreen renderer based on OpenGL framebuffers.
|
||||||
class GlOffscreen {
|
class GlOffscreen {
|
||||||
public:
|
public:
|
||||||
|
411
src/render/render2d.cpp
Normal file
411
src/render/render2d.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -43,6 +43,8 @@ enum class StipplePattern : uint32_t {
|
|||||||
LAST = ZIGZAG
|
LAST = ZIGZAG
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::vector<double> StipplePatternDashes(StipplePattern pattern, double scale);
|
||||||
|
|
||||||
enum class Command : uint32_t;
|
enum class Command : uint32_t;
|
||||||
|
|
||||||
// All of the hWhatever handles are a 32-bit ID, that is used to represent
|
// 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
|
// 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 Group;
|
||||||
class SSurface;
|
class SSurface;
|
||||||
#include "dsc.h"
|
#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 {
|
Vector SSurface::PointAt(Point2d puv) const {
|
||||||
return PointAt(puv.x, puv.y);
|
return PointAt(puv.x, puv.y);
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
double Bernstein(int k, int deg, double t);
|
double Bernstein(int k, int deg, double t);
|
||||||
double BernsteinDerivative(int k, int deg, double t);
|
double BernsteinDerivative(int k, int deg, double t);
|
||||||
|
|
||||||
|
class SBezierList;
|
||||||
class SSurface;
|
class SSurface;
|
||||||
class SCurvePt;
|
class SCurvePt;
|
||||||
|
|
||||||
@ -95,6 +96,7 @@ public:
|
|||||||
void MakePwlInto(List<Vector> *l, double chordTol=0) const;
|
void MakePwlInto(List<Vector> *l, double chordTol=0) const;
|
||||||
void MakePwlWorker(List<Vector> *l, double ta, double tb, double chordTol) 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 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 AllIntersectionsWith(const SBezier *sbb, SPointList *spl) const;
|
||||||
void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax) 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.x <= (maxp.x + r) &&
|
||||||
p.y <= (maxp.y + 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
Block a user