vega-scenegraph
Version:
Vega scenegraph and renderers.
149 lines (126 loc) • 3.47 kB
JavaScript
import {DegToRad, Epsilon, HalfPi, Tau} from '../util/constants';
const circleThreshold = Tau - 1e-8;
let bounds, lx, ly, rot, ma, mb, mc, md;
const add = (x, y) => bounds.add(x, y);
const addL = (x, y) => add(lx = x, ly = y);
const addX = x => add(x, bounds.y1);
const addY = y => add(bounds.x1, y);
const px = (x, y) => ma * x + mc * y;
const py = (x, y) => mb * x + md * y;
const addp = (x, y) => add(px(x, y), py(x, y));
const addpL = (x, y) => addL(px(x, y), py(x, y));
export default function(_, deg) {
bounds = _;
if (deg) {
rot = deg * DegToRad;
ma = md = Math.cos(rot);
mb = Math.sin(rot);
mc = -mb;
} else {
ma = md = 1;
rot = mb = mc = 0;
}
return context;
}
const context = {
beginPath() {},
closePath() {},
moveTo: addpL,
lineTo: addpL,
rect(x, y, w, h) {
if (rot) {
addp(x + w, y);
addp(x + w, y + h);
addp(x, y + h);
addpL(x, y);
} else {
add(x + w, y + h);
addL(x, y);
}
},
quadraticCurveTo(x1, y1, x2, y2) {
const px1 = px(x1, y1),
py1 = py(x1, y1),
px2 = px(x2, y2),
py2 = py(x2, y2);
quadExtrema(lx, px1, px2, addX);
quadExtrema(ly, py1, py2, addY);
addL(px2, py2);
},
bezierCurveTo(x1, y1, x2, y2, x3, y3) {
const px1 = px(x1, y1),
py1 = py(x1, y1),
px2 = px(x2, y2),
py2 = py(x2, y2),
px3 = px(x3, y3),
py3 = py(x3, y3);
cubicExtrema(lx, px1, px2, px3, addX);
cubicExtrema(ly, py1, py2, py3, addY);
addL(px3, py3);
},
arc(cx, cy, r, sa, ea, ccw) {
sa += rot;
ea += rot;
// store last point on path
lx = r * Math.cos(ea) + cx;
ly = r * Math.sin(ea) + cy;
if (Math.abs(ea - sa) > circleThreshold) {
// treat as full circle
add(cx - r, cy - r);
add(cx + r, cy + r);
} else {
const update = a => add(r * Math.cos(a) + cx, r * Math.sin(a) + cy);
let s, i;
// sample end points
update(sa);
update(ea);
// sample interior points aligned with 90 degrees
if (ea !== sa) {
sa = sa % Tau; if (sa < 0) sa += Tau;
ea = ea % Tau; if (ea < 0) ea += Tau;
if (ea < sa) {
ccw = !ccw; // flip direction
s = sa; sa = ea; ea = s; // swap end-points
}
if (ccw) {
ea -= Tau;
s = sa - (sa % HalfPi);
for (i=0; i<4 && s>ea; ++i, s-=HalfPi) update(s);
} else {
s = sa - (sa % HalfPi) + HalfPi;
for (i=0; i<4 && s<ea; ++i, s=s+HalfPi) update(s);
}
}
}
}
};
function quadExtrema(x0, x1, x2, cb) {
const t = (x0 - x1) / (x0 + x2 - 2 * x1);
if (0 < t && t < 1) cb(x0 + (x1 - x0) * t);
}
function cubicExtrema(x0, x1, x2, x3, cb) {
const a = x3 - x0 + 3 * x1 - 3 * x2,
b = x0 + x2 - 2 * x1,
c = x0 - x1;
let t0 = 0, t1 = 0, r;
// solve for parameter t
if (Math.abs(a) > Epsilon) {
// quadratic equation
r = b * b + c * a;
if (r >= 0) {
r = Math.sqrt(r);
t0 = (-b + r) / a;
t1 = (-b - r) / a;
}
} else {
// linear equation
t0 = 0.5 * c / b;
}
// calculate position
if (0 < t0 && t0 < 1) cb(cubic(t0, x0, x1, x2, x3));
if (0 < t1 && t1 < 1) cb(cubic(t1, x0, x1, x2, x3));
}
function cubic(t, x0, x1, x2, x3) {
const s = 1 - t, s2 = s * s, t2 = t * t;
return (s2 * s * x0) + (3 * s2 * t * x1) + (3 * s * t2 * x2) + (t2 * t * x3);
}