@visactor/vrender-core
Version:
## Description
375 lines (328 loc) • 16.6 kB
JavaScript
import { isPointInLine, pi, pi2 } from "@visactor/vutils";
import { enumCommandMap as CMD } from "../common/path-svg";
import { container } from "../container";
import { application } from "../application";
import { CanvasFactory, Context2dFactory } from "./constants";
export function wrapCanvas(params) {
return container.getNamed(CanvasFactory, application.global.env)(params);
}
export function wrapContext(canvas, dpr) {
return container.getNamed(Context2dFactory, application.global.env)(canvas, dpr);
}
const EPSILON_NUMERIC = 1e-4, THREE_SQRT = Math.sqrt(3), ONE_THIRD = 1 / 3;
function isAroundZero(val) {
return val > -EPSILON && val < EPSILON;
}
function isNotAroundZero(val) {
return val > EPSILON || val < -EPSILON;
}
export function vec2Equals(d1, d2) {
return Math.abs(d1[0] - d2[0]) + Math.abs(d1[1] - d2[1]) < 1e-10;
}
export function isNumber(data) {
return "number" == typeof data && Number.isFinite(data);
}
const _v0 = [ 0, 0 ], _v1 = [ 0, 0 ], _v2 = [ 0, 0 ];
function distanceSquare(v1, v2) {
return (v1[0] - v2[0]) * (v1[0] - v2[0]) + (v1[1] - v2[1]) * (v1[1] - v2[1]);
}
export function quadraticAt(p0, p1, p2, t) {
const onet = 1 - t;
return onet * (onet * p0 + 2 * t * p1) + t * t * p2;
}
export function cubicAt(p0, p1, p2, p3, t) {
const onet = 1 - t;
return onet * onet * (onet * p0 + 3 * t * p1) + t * t * (t * p3 + 3 * onet * p2);
}
export function quadraticRootAt(p0, p1, p2, val, roots) {
const a = p0 - 2 * p1 + p2, b = 2 * (p1 - p0), c = p0 - val;
let n = 0;
if (isAroundZero(a)) {
if (isNotAroundZero(b)) {
const t1 = -c / b;
t1 >= 0 && t1 <= 1 && (roots[n++] = t1);
}
} else {
const disc = b * b - 4 * a * c;
if (isAroundZero(disc)) {
const t1 = -b / (2 * a);
t1 >= 0 && t1 <= 1 && (roots[n++] = t1);
} else if (disc > 0) {
const discSqrt = Math.sqrt(disc), t1 = (-b + discSqrt) / (2 * a), t2 = (-b - discSqrt) / (2 * a);
t1 >= 0 && t1 <= 1 && (roots[n++] = t1), t2 >= 0 && t2 <= 1 && (roots[n++] = t2);
}
}
return n;
}
export function quadraticExtremum(p0, p1, p2) {
const divider = p0 + p2 - 2 * p1;
return 0 === divider ? .5 : (p0 - p1) / divider;
}
export function quadraticProjectPoint(x0, y0, x1, y1, x2, y2, x, y, out) {
let t = 0, interval = .005, d = 1 / 0;
_v0[0] = x, _v0[1] = y;
for (let _t = 0; _t < 1; _t += .05) {
_v1[0] = quadraticAt(x0, x1, x2, _t), _v1[1] = quadraticAt(y0, y1, y2, _t);
const d1 = distanceSquare(_v0, _v1);
d1 < d && (t = _t, d = d1);
}
d = 1 / 0;
for (let i = 0; i < 32 && !(interval < EPSILON_NUMERIC); i++) {
const prev = t - interval, next = t + interval;
_v1[0] = quadraticAt(x0, x1, x2, prev), _v1[1] = quadraticAt(y0, y1, y2, prev);
const d1 = distanceSquare(_v1, _v0);
if (prev >= 0 && d1 < d) t = prev, d = d1; else {
_v2[0] = quadraticAt(x0, x1, x2, next), _v2[1] = quadraticAt(y0, y1, y2, next);
const d2 = distanceSquare(_v2, _v0);
next <= 1 && d2 < d ? (t = next, d = d2) : interval *= .5;
}
}
return out && (out[0] = quadraticAt(x0, x1, x2, t), out[1] = quadraticAt(y0, y1, y2, t)),
Math.sqrt(d);
}
export function cubicProjectPoint(x0, y0, x1, y1, x2, y2, x3, y3, x, y, out) {
let prev, next, d1, d2, t = 0, interval = .005, d = 1 / 0;
_v0[0] = x, _v0[1] = y;
for (let _t = 0; _t < 1; _t += .05) _v1[0] = cubicAt(x0, x1, x2, x3, _t), _v1[1] = cubicAt(y0, y1, y2, y3, _t),
d1 = distanceSquare(_v0, _v1), d1 < d && (t = _t, d = d1);
d = 1 / 0;
for (let i = 0; i < 32 && !(interval < EPSILON_NUMERIC); i++) prev = t - interval,
next = t + interval, _v1[0] = cubicAt(x0, x1, x2, x3, prev), _v1[1] = cubicAt(y0, y1, y2, y3, prev),
d1 = distanceSquare(_v1, _v0), prev >= 0 && d1 < d ? (t = prev, d = d1) : (_v2[0] = cubicAt(x0, x1, x2, x3, next),
_v2[1] = cubicAt(y0, y1, y2, y3, next), d2 = distanceSquare(_v2, _v0), next <= 1 && d2 < d ? (t = next,
d = d2) : interval *= .5);
return out && (out[0] = cubicAt(x0, x1, x2, x3, t), out[1] = cubicAt(y0, y1, y2, y3, t)),
Math.sqrt(d);
}
export function normalizeRadian(angle) {
return (angle %= pi2) < 0 && (angle += pi2), angle;
}
export function containQuadStroke(x0, y0, x1, y1, x2, y2, lineWidth, x, y) {
if (0 === lineWidth) return !1;
const _l = lineWidth;
if (y > y0 + _l && y > y1 + _l && y > y2 + _l || y < y0 - _l && y < y1 - _l && y < y2 - _l || x > x0 + _l && x > x1 + _l && x > x2 + _l || x < x0 - _l && x < x1 - _l && x < x2 - _l) return !1;
return quadraticProjectPoint(x0, y0, x1, y1, x2, y2, x, y, null) <= _l / 2;
}
export function containCubicStroke(x0, y0, x1, y1, x2, y2, x3, y3, lineWidth, x, y) {
if (0 === lineWidth) return !1;
const _l = lineWidth;
if (y > y0 + _l && y > y1 + _l && y > y2 + _l && y > y3 + _l || y < y0 - _l && y < y1 - _l && y < y2 - _l && y < y3 - _l || x > x0 + _l && x > x1 + _l && x > x2 + _l && x > x3 + _l || x < x0 - _l && x < x1 - _l && x < x2 - _l && x < x3 - _l) return !1;
return cubicProjectPoint(x0, y0, x1, y1, x2, y2, x3, y3, x, y, null) <= _l / 2;
}
export function containArcStroke(cx, cy, r, startAngle, endAngle, anticlockwise, lineWidth, x, y) {
if (0 === lineWidth) return !1;
const _l = lineWidth;
x -= cx, y -= cy;
const d = Math.sqrt(x * x + y * y);
if (d - _l > r || d + _l < r) return !1;
if (Math.abs(startAngle - endAngle) % pi2 < 1e-4) return !0;
if (anticlockwise) {
const tmp = startAngle;
startAngle = normalizeRadian(endAngle), endAngle = normalizeRadian(tmp);
} else startAngle = normalizeRadian(startAngle), endAngle = normalizeRadian(endAngle);
startAngle > endAngle && (endAngle += pi2);
let angle = Math.atan2(y, x);
return angle < 0 && (angle += pi2), angle >= startAngle && angle <= endAngle || angle + pi2 >= startAngle && angle + pi2 <= endAngle;
}
export function containLineStroke(x0, y0, x1, y1, lineWidth, x, y) {
if (0 === lineWidth) return !1;
const _l = lineWidth, _halfL = lineWidth / 2;
let _a = 0, _b = x0;
if (y > y0 + _halfL && y > y1 + _halfL || y < y0 - _halfL && y < y1 - _halfL || x > x0 + _halfL && x > x1 + _halfL || x < x0 - _halfL && x < x1 - _halfL) return !1;
if (x0 === x1) return Math.abs(x - x0) <= _l / 2;
_a = (y0 - y1) / (x0 - x1), _b = (x0 * y1 - x1 * y0) / (x0 - x1);
const tmp = _a * x - y + _b;
return tmp * tmp / (_a * _a + 1) <= _l / 2 * _l / 2;
}
const globalPoint = {
x: 0,
y: 0
};
export function transformPoint(pos, ctx, out) {
const matrix = ctx.currentMatrix.getInverse();
return (out = out || globalPoint).x = pos.x * matrix.a + pos.y * matrix.c + matrix.e,
out.y = pos.x * matrix.b + pos.y * matrix.d + matrix.f, out;
}
const EPSILON = 1e-4;
export function cubicRootAt(p0, p1, p2, p3, val, roots) {
const a = p3 + 3 * (p1 - p2) - p0, b = 3 * (p2 - 2 * p1 + p0), c = 3 * (p1 - p0), d = p0 - val, A = b * b - 3 * a * c, B = b * c - 9 * a * d, C = c * c - 3 * b * d;
let n = 0;
if (isAroundZero(A) && isAroundZero(B)) if (isAroundZero(b)) roots[0] = 0; else {
const t1 = -c / b;
t1 >= 0 && t1 <= 1 && (roots[n++] = t1);
} else {
const disc = B * B - 4 * A * C;
if (isAroundZero(disc)) {
const K = B / A, t1 = -b / a + K, t2 = -K / 2;
t1 >= 0 && t1 <= 1 && (roots[n++] = t1), t2 >= 0 && t2 <= 1 && (roots[n++] = t2);
} else if (disc > 0) {
const discSqrt = Math.sqrt(disc);
let Y1 = A * b + 1.5 * a * (-B + discSqrt), Y2 = A * b + 1.5 * a * (-B - discSqrt);
Y1 = Y1 < 0 ? -Math.pow(-Y1, ONE_THIRD) : Math.pow(Y1, ONE_THIRD), Y2 = Y2 < 0 ? -Math.pow(-Y2, ONE_THIRD) : Math.pow(Y2, ONE_THIRD);
const t1 = (-b - (Y1 + Y2)) / (3 * a);
t1 >= 0 && t1 <= 1 && (roots[n++] = t1);
} else {
const T = (2 * A * b - 3 * a * B) / (2 * Math.sqrt(A * A * A)), theta = Math.acos(T) / 3, ASqrt = Math.sqrt(A), tmp = Math.cos(theta), t1 = (-b - 2 * ASqrt * tmp) / (3 * a), t2 = (-b + ASqrt * (tmp + THREE_SQRT * Math.sin(theta))) / (3 * a), t3 = (-b + ASqrt * (tmp - THREE_SQRT * Math.sin(theta))) / (3 * a);
t1 >= 0 && t1 <= 1 && (roots[n++] = t1), t2 >= 0 && t2 <= 1 && (roots[n++] = t2),
t3 >= 0 && t3 <= 1 && (roots[n++] = t3);
}
}
return n;
}
export function cubicExtrema(p0, p1, p2, p3, extrema) {
const b = 6 * p2 - 12 * p1 + 6 * p0, a = 9 * p1 + 3 * p3 - 3 * p0 - 9 * p2, c = 3 * p1 - 3 * p0;
let n = 0;
if (isAroundZero(a)) {
if (isNotAroundZero(b)) {
const t1 = -c / b;
t1 >= 0 && t1 <= 1 && (extrema[n++] = t1);
}
} else {
const disc = b * b - 4 * a * c;
if (isAroundZero(disc)) extrema[0] = -b / (2 * a); else if (disc > 0) {
const discSqrt = Math.sqrt(disc), t1 = (-b + discSqrt) / (2 * a), t2 = (-b - discSqrt) / (2 * a);
t1 >= 0 && t1 <= 1 && (extrema[n++] = t1), t2 >= 0 && t2 <= 1 && (extrema[n++] = t2);
}
}
return n;
}
function isAroundEqual(a, b) {
return Math.abs(a - b) < EPSILON;
}
const roots = [ -1, -1, -1 ], extrema = [ -1, -1 ];
function swapExtrema() {
const tmp = extrema[0];
extrema[0] = extrema[1], extrema[1] = tmp;
}
function windingCubic(x0, y0, x1, y1, x2, y2, x3, y3, x, y) {
if (y > y0 && y > y1 && y > y2 && y > y3 || y < y0 && y < y1 && y < y2 && y < y3) return 0;
const nRoots = cubicRootAt(y0, y1, y2, y3, y, roots);
if (0 === nRoots) return 0;
let w = 0, nExtrema = -1, y0_ = 0, y1_ = 0;
for (let i = 0; i < nRoots; i++) {
const t = roots[i], unit = 0 === t || 1 === t ? .5 : 1;
cubicAt(x0, x1, x2, x3, t) < x || (nExtrema < 0 && (nExtrema = cubicExtrema(y0, y1, y2, y3, extrema),
extrema[1] < extrema[0] && nExtrema > 1 && swapExtrema(), y0_ = cubicAt(y0, y1, y2, y3, extrema[0]),
nExtrema > 1 && (y1_ = cubicAt(y0, y1, y2, y3, extrema[1]))), 2 === nExtrema ? t < extrema[0] ? w += y0_ < y0 ? unit : -unit : t < extrema[1] ? w += y1_ < y0_ ? unit : -unit : w += y3 < y1_ ? unit : -unit : t < extrema[0] ? w += y0_ < y0 ? unit : -unit : w += y3 < y0_ ? unit : -unit);
}
return w;
}
function windingQuadratic(x0, y0, x1, y1, x2, y2, x, y) {
if (y > y0 && y > y1 && y > y2 || y < y0 && y < y1 && y < y2) return 0;
const nRoots = quadraticRootAt(y0, y1, y2, y, roots);
if (0 === nRoots) return 0;
const t = quadraticExtremum(y0, y1, y2);
if (t >= 0 && t <= 1) {
let w = 0;
const y_ = quadraticAt(y0, y1, y2, t);
for (let i = 0; i < nRoots; i++) {
const unit = 0 === roots[i] || 1 === roots[i] ? .5 : 1;
quadraticAt(x0, x1, x2, roots[i]) < x || (roots[i] < t ? w += y_ < y0 ? unit : -unit : w += y2 < y_ ? unit : -unit);
}
return w;
}
const unit = 0 === roots[0] || 1 === roots[0] ? .5 : 1;
return quadraticAt(x0, x1, x2, roots[0]) < x ? 0 : y2 < y0 ? unit : -unit;
}
function windingArc(cx, cy, r, startAngle, endAngle, anticlockwise, x, y) {
if ((y -= cy) > r || y < -r) return 0;
const tmp = Math.sqrt(r * r - y * y);
roots[0] = -tmp, roots[1] = tmp;
const dTheta = Math.abs(startAngle - endAngle);
if (dTheta < 1e-4) return 0;
if (dTheta >= pi2 - 1e-4) {
startAngle = 0, endAngle = pi2;
const dir = anticlockwise ? 1 : -1;
return x >= roots[0] + cx && x <= roots[1] + cx ? dir : 0;
}
if (startAngle > endAngle) {
const tmp = startAngle;
startAngle = endAngle, endAngle = tmp;
}
startAngle < 0 && (startAngle += pi2, endAngle += pi2);
let w = 0;
for (let i = 0; i < 2; i++) {
const x_ = roots[i];
if (x_ + cx > x) {
let angle = Math.atan2(y, x_), dir = anticlockwise ? 1 : -1;
angle < 0 && (angle = pi2 + angle), (angle >= startAngle && angle <= endAngle || angle + pi2 >= startAngle && angle + pi2 <= endAngle) && (angle > pi / 2 && angle < 1.5 * pi && (dir = -dir),
w += dir);
}
}
return w;
}
function modpi2(radian) {
return Math.round(radian / pi * 1e8) / 1e8 % 2 * pi;
}
function normalizeArcAngles(angles, anticlockwise) {
let newStartAngle = modpi2(angles[0]);
newStartAngle < 0 && (newStartAngle += pi2);
const delta = newStartAngle - angles[0];
let newEndAngle = angles[1];
newEndAngle += delta, !anticlockwise && newEndAngle - newStartAngle >= pi2 ? newEndAngle = newStartAngle + pi2 : anticlockwise && newStartAngle - newEndAngle >= pi2 ? newEndAngle = newStartAngle - pi2 : !anticlockwise && newStartAngle > newEndAngle ? newEndAngle = newStartAngle + (pi2 - modpi2(newStartAngle - newEndAngle)) : anticlockwise && newStartAngle < newEndAngle && (newEndAngle = newStartAngle - (pi2 - modpi2(newEndAngle - newStartAngle))),
angles[0] = newStartAngle, angles[1] = newEndAngle;
}
const tmpAngles = [ 0, 0 ];
function containPath(commands, lineWidth, isStroke, x, y) {
const data = commands, len = commands.length;
let x1, y1, w = 0, xi = 0, yi = 0, x0 = 0, y0 = 0;
for (let i = 0; i < len; i++) {
const command = data[i], isFirst = 0 === i;
command[0] === CMD.M && i > 1 && (isStroke || (w += isPointInLine(xi, yi, x0, y0, x, y))),
isFirst && (xi = command[1], yi = command[2], x0 = xi, y0 = yi);
const c0 = command[0], c1 = command[1], c2 = command[2], c3 = command[3], c4 = command[4], c5 = command[5], c6 = command[6];
let startAngle = c4, endAngle = c5;
tmpAngles[0] = startAngle, tmpAngles[1] = endAngle, normalizeArcAngles(tmpAngles, Boolean(command[6])),
startAngle = tmpAngles[0], endAngle = tmpAngles[1];
const theta = startAngle, dTheta = endAngle - startAngle, anticlockwise = !!(1 - (command[6] ? 0 : 1)), _x = (x - c1) * c3 / c3 + c1;
switch (c0) {
case CMD.M:
x0 = c1, y0 = c2, xi = x0, yi = y0;
break;
case CMD.L:
if (isStroke) {
if (containLineStroke(xi, yi, c1, c2, lineWidth, x, y)) return !0;
} else w += isPointInLine(xi, yi, c1, c2, x, y) || 0;
xi = c1, yi = c2;
break;
case CMD.C:
if (isStroke) {
if (containCubicStroke(xi, yi, c1, c2, c3, c4, c5, c6, lineWidth, x, y)) return !0;
} else w += windingCubic(xi, yi, c1, c2, c3, c4, c5, c6, x, y) || 0;
xi = c5, yi = c6;
break;
case CMD.Q:
if (isStroke) {
if (containQuadStroke(xi, yi, c1, c2, c3, c4, lineWidth, x, y)) return !0;
} else w += windingQuadratic(xi, yi, c1, c2, c3, c4, x, y) || 0;
xi = c3, yi = c4;
break;
case CMD.A:
if (x1 = Math.cos(theta) * c3 + c1, y1 = Math.sin(theta) * c3 + c2, isFirst ? (x0 = x1,
y0 = y1) : w += isPointInLine(xi, yi, x1, y1, x, y), isStroke) {
if (containArcStroke(c1, c2, c3, theta, theta + dTheta, anticlockwise, lineWidth, _x, y)) return !0;
} else w += windingArc(c1, c2, c3, theta, theta + dTheta, anticlockwise, _x, y);
xi = Math.cos(theta + dTheta) * c3 + c1, yi = Math.sin(theta + dTheta) * c3 + c2;
break;
case CMD.R:
if (x0 = xi = c1, y0 = yi = c2, x1 = x0 + c3, y1 = y0 + c4, isStroke) {
if (containLineStroke(x0, y0, x1, y0, lineWidth, x, y) || containLineStroke(x1, y0, x1, y1, lineWidth, x, y) || containLineStroke(x1, y1, x0, y1, lineWidth, x, y) || containLineStroke(x0, y1, x0, y0, lineWidth, x, y)) return !0;
} else w += isPointInLine(x1, y0, x1, y1, x, y), w += isPointInLine(x0, y1, x0, y0, x, y);
break;
case CMD.Z:
if (isStroke) {
if (containLineStroke(xi, yi, x0, y0, lineWidth, x, y)) return !0;
} else w += isPointInLine(xi, yi, x0, y0, x, y);
xi = x0, yi = y0;
}
}
return isStroke || isAroundEqual(yi, y0) || (w += isPointInLine(xi, yi, x0, y0, x, y) || 0),
0 !== w;
}
export function contain(commands, x, y) {
return containPath(commands, 0, !1, x, y);
}
export function containStroke(commands, lineWidth, x, y) {
return containPath(commands, lineWidth, !0, x, y);
}
//# sourceMappingURL=util.js.map