solvespace/src/render/render2d.cpp
whitequark 4f49a8a9d4 Eliminate several memory leaks.
All leaks found with valgrind while running the test suite.
This commit also clears the Cairo cache to improve SNR of Valgrind
output.
2016-08-01 05:39:18 +00:00

416 lines
14 KiB
C++

//-----------------------------------------------------------------------------
// 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);
}
mp.Clear();
}
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() {
Canvas::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();
}
}