//----------------------------------------------------------------------------- // 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 &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 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 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 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> 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 a, std::pair 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(); } }