//-----------------------------------------------------------------------------
// Backend-agnostic rendering interface, and various backends we use.
//
// Copyright 2016 whitequark
//-----------------------------------------------------------------------------
#include "solvespace.h"
namespace SolveSpace {
//-----------------------------------------------------------------------------
// Camera transformations.
//-----------------------------------------------------------------------------
Point2d Camera::ProjectPoint(Vector p) const {
Vector p3 = ProjectPoint3(p);
Point2d p2 = { p3.x, p3.y };
return p2;
}
Vector Camera::ProjectPoint3(Vector p) const {
double w;
Vector r = ProjectPoint4(p, &w);
return r.ScaledBy(scale/w);
}
Vector Camera::ProjectPoint4(Vector p, double *w) const {
p = p.Plus(offset);
Vector r;
r.x = p.Dot(projRight);
r.y = p.Dot(projUp);
r.z = p.Dot(projUp.Cross(projRight));
*w = 1 + r.z*tangent*scale;
return r;
}
Vector Camera::UnProjectPoint(Point2d p) const {
Vector orig = offset.ScaledBy(-1);
// Note that we're ignoring the effects of perspective. Since our returned
// point has the same component normal to the screen as the offset, it
// will have z = 0 after the rotation is applied, thus w = 1. So this is
// correct.
orig = orig.Plus(projRight.ScaledBy(p.x / scale)).Plus(
projUp. ScaledBy(p.y / scale));
return orig;
}
Vector Camera::UnProjectPoint3(Vector p) const {
p.z = p.z / (scale - p.z * tangent * scale);
double w = 1 + p.z * tangent * scale;
p.x *= w / scale;
p.y *= w / scale;
Vector orig = offset.ScaledBy(-1);
orig = orig.Plus(projRight.ScaledBy(p.x)).Plus(
projUp. ScaledBy(p.y).Plus(
projUp.Cross(projRight). ScaledBy(p.z)));
return orig;
}
Vector Camera::VectorFromProjs(Vector rightUpForward) const {
Vector n = projRight.Cross(projUp);
Vector r = (projRight.ScaledBy(rightUpForward.x));
r = r.Plus(projUp.ScaledBy(rightUpForward.y));
r = r.Plus(n.ScaledBy(rightUpForward.z));
return r;
}
Vector Camera::AlignToPixelGrid(Vector v) const {
if(!gridFit) return v;
v = ProjectPoint3(v);
v.x = floor(v.x) + 0.5;
v.y = floor(v.y) + 0.5;
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 };
projUp = { 0.0, 1.0, 0.0 };
scale = 1.0;
tangent = 0.0;
}
void Camera::NormalizeProjectionVectors() {
if(projRight.Magnitude() < LENGTH_EPS) {
projRight = Vector::From(1, 0, 0);
}
Vector norm = projRight.Cross(projUp);
// If projRight and projUp somehow ended up parallel, then pick an
// arbitrary projUp normal to projRight.
if(norm.Magnitude() < LENGTH_EPS) {
norm = projRight.Normal(0);
}
projUp = norm.Cross(projRight);
projUp = projUp.WithMagnitude(1);
projRight = projRight.WithMagnitude(1);
}
//-----------------------------------------------------------------------------
// Stroke and fill caching.
//-----------------------------------------------------------------------------
bool Canvas::Stroke::Equals(const Stroke &other) const {
return (layer == other.layer &&
zIndex == other.zIndex &&
color.Equals(other.color) &&
width == other.width &&
unit == other.unit &&
stipplePattern == other.stipplePattern &&
stippleScale == other.stippleScale);
}
double Canvas::Stroke::WidthMm(const Camera &camera) const {
switch(unit) {
case Canvas::Unit::MM:
return width;
case Canvas::Unit::PX:
return width / camera.scale;
default:
ssassert(false, "Unexpected unit");
}
}
double Canvas::Stroke::WidthPx(const Camera &camera) const {
switch(unit) {
case Canvas::Unit::MM:
return width * camera.scale;
case Canvas::Unit::PX:
return width;
default:
ssassert(false, "Unexpected unit");
}
}
double Canvas::Stroke::StippleScaleMm(const Camera &camera) const {
switch(unit) {
case Canvas::Unit::MM:
return stippleScale;
case Canvas::Unit::PX:
return stippleScale / camera.scale;
default:
ssassert(false, "Unexpected unit");
}
}
double Canvas::Stroke::StippleScalePx(const Camera &camera) const {
switch(unit) {
case Canvas::Unit::MM:
return stippleScale * camera.scale;
case Canvas::Unit::PX:
return stippleScale;
default:
ssassert(false, "Unexpected unit");
}
}
bool Canvas::Fill::Equals(const Fill &other) const {
return (layer == other.layer &&
zIndex == other.zIndex &&
color.Equals(other.color) &&
pattern == other.pattern &&
texture == other.texture);
}
void Canvas::Clear() {
strokes.Clear();
fills.Clear();
}
Canvas::hStroke Canvas::GetStroke(const Stroke &stroke) {
for(const Stroke &s : strokes) {
if(s.Equals(stroke)) return s.h;
}
Stroke strokeCopy = stroke;
return strokes.AddAndAssignId(&strokeCopy);
}
Canvas::hFill Canvas::GetFill(const Fill &fill) {
for(const Fill &f : fills) {
if(f.Equals(fill)) return f.h;
}
Fill fillCopy = fill;
return fills.AddAndAssignId(&fillCopy);
}
BitmapFont *Canvas::GetBitmapFont() {
if(bitmapFont.IsEmpty()) {
bitmapFont = BitmapFont::Create();
}
return &bitmapFont;
}
std::shared_ptr