UNPKG

@visactor/vrender-core

Version:
389 lines (338 loc) 18.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: !0 }), exports.containStroke = exports.contain = exports.cubicExtrema = exports.cubicRootAt = exports.transformPoint = exports.containLineStroke = exports.containArcStroke = exports.containCubicStroke = exports.containQuadStroke = exports.normalizeRadian = exports.cubicProjectPoint = exports.quadraticProjectPoint = exports.quadraticExtremum = exports.quadraticRootAt = exports.cubicAt = exports.quadraticAt = exports.isNumber = exports.vec2Equals = exports.wrapContext = exports.wrapCanvas = void 0; const vutils_1 = require("@visactor/vutils"), path_svg_1 = require("../common/path-svg"), container_1 = require("../container"), application_1 = require("../application"), constants_1 = require("./constants"); function wrapCanvas(params) { return container_1.container.getNamed(constants_1.CanvasFactory, application_1.application.global.env)(params); } function wrapContext(canvas, dpr) { return container_1.container.getNamed(constants_1.Context2dFactory, application_1.application.global.env)(canvas, dpr); } exports.wrapCanvas = wrapCanvas, exports.wrapContext = wrapContext; 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; } function vec2Equals(d1, d2) { return Math.abs(d1[0] - d2[0]) + Math.abs(d1[1] - d2[1]) < 1e-10; } function isNumber(data) { return "number" == typeof data && Number.isFinite(data); } exports.vec2Equals = vec2Equals, exports.isNumber = isNumber; 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]); } function quadraticAt(p0, p1, p2, t) { const onet = 1 - t; return onet * (onet * p0 + 2 * t * p1) + t * t * p2; } 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); } 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; } function quadraticExtremum(p0, p1, p2) { const divider = p0 + p2 - 2 * p1; return 0 === divider ? .5 : (p0 - p1) / divider; } 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); } 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); } function normalizeRadian(angle) { return (angle %= vutils_1.pi2) < 0 && (angle += vutils_1.pi2), angle; } 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; } 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; } 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) % vutils_1.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 += vutils_1.pi2); let angle = Math.atan2(y, x); return angle < 0 && (angle += vutils_1.pi2), angle >= startAngle && angle <= endAngle || angle + vutils_1.pi2 >= startAngle && angle + vutils_1.pi2 <= endAngle; } 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; } exports.quadraticAt = quadraticAt, exports.cubicAt = cubicAt, exports.quadraticRootAt = quadraticRootAt, exports.quadraticExtremum = quadraticExtremum, exports.quadraticProjectPoint = quadraticProjectPoint, exports.cubicProjectPoint = cubicProjectPoint, exports.normalizeRadian = normalizeRadian, exports.containQuadStroke = containQuadStroke, exports.containCubicStroke = containCubicStroke, exports.containArcStroke = containArcStroke, exports.containLineStroke = containLineStroke; const globalPoint = { x: 0, y: 0 }; 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; } exports.transformPoint = transformPoint; const EPSILON = 1e-4; 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; } 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; } exports.cubicRootAt = cubicRootAt, exports.cubicExtrema = cubicExtrema; 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 >= vutils_1.pi2 - 1e-4) { startAngle = 0, endAngle = vutils_1.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 += vutils_1.pi2, endAngle += vutils_1.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 = vutils_1.pi2 + angle), (angle >= startAngle && angle <= endAngle || angle + vutils_1.pi2 >= startAngle && angle + vutils_1.pi2 <= endAngle) && (angle > vutils_1.pi / 2 && angle < 1.5 * vutils_1.pi && (dir = -dir), w += dir); } } return w; } function modpi2(radian) { return Math.round(radian / vutils_1.pi * 1e8) / 1e8 % 2 * vutils_1.pi; } function normalizeArcAngles(angles, anticlockwise) { let newStartAngle = modpi2(angles[0]); newStartAngle < 0 && (newStartAngle += vutils_1.pi2); const delta = newStartAngle - angles[0]; let newEndAngle = angles[1]; newEndAngle += delta, !anticlockwise && newEndAngle - newStartAngle >= vutils_1.pi2 ? newEndAngle = newStartAngle + vutils_1.pi2 : anticlockwise && newStartAngle - newEndAngle >= vutils_1.pi2 ? newEndAngle = newStartAngle - vutils_1.pi2 : !anticlockwise && newStartAngle > newEndAngle ? newEndAngle = newStartAngle + (vutils_1.pi2 - modpi2(newStartAngle - newEndAngle)) : anticlockwise && newStartAngle < newEndAngle && (newEndAngle = newStartAngle - (vutils_1.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] === path_svg_1.enumCommandMap.M && i > 1 && (isStroke || (w += (0, vutils_1.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 path_svg_1.enumCommandMap.M: x0 = c1, y0 = c2, xi = x0, yi = y0; break; case path_svg_1.enumCommandMap.L: if (isStroke) { if (containLineStroke(xi, yi, c1, c2, lineWidth, x, y)) return !0; } else w += (0, vutils_1.isPointInLine)(xi, yi, c1, c2, x, y) || 0; xi = c1, yi = c2; break; case path_svg_1.enumCommandMap.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 path_svg_1.enumCommandMap.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 path_svg_1.enumCommandMap.A: if (x1 = Math.cos(theta) * c3 + c1, y1 = Math.sin(theta) * c3 + c2, isFirst ? (x0 = x1, y0 = y1) : w += (0, vutils_1.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 path_svg_1.enumCommandMap.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 += (0, vutils_1.isPointInLine)(x1, y0, x1, y1, x, y), w += (0, vutils_1.isPointInLine)(x0, y1, x0, y0, x, y); break; case path_svg_1.enumCommandMap.Z: if (isStroke) { if (containLineStroke(xi, yi, x0, y0, lineWidth, x, y)) return !0; } else w += (0, vutils_1.isPointInLine)(xi, yi, x0, y0, x, y); xi = x0, yi = y0; } } return isStroke || isAroundEqual(yi, y0) || (w += (0, vutils_1.isPointInLine)(xi, yi, x0, y0, x, y) || 0), 0 !== w; } function contain(commands, x, y) { return containPath(commands, 0, !1, x, y); } function containStroke(commands, lineWidth, x, y) { return containPath(commands, lineWidth, !0, x, y); } exports.contain = contain, exports.containStroke = containStroke; //# sourceMappingURL=util.js.map