//-----------------------------------------------------------------------------
// 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, Canvas::hStroke hcs) {
Stroke *stroke = strokes.FindById(hcs);
Fill fill = {};
fill.layer = stroke->layer;
fill.zIndex = stroke->zIndex;
fill.color = stroke->color;
hFill hcf = GetFill(fill);
Vector u = camera.projRight.ScaledBy(stroke->width/2.0/camera.scale),
v = camera.projUp.ScaledBy(stroke->width/2.0/camera.scale);
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) {
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);
}
}
void SurfaceRenderer::DrawFaces(const SMesh &m, const std::vector