@visactor/vrender-core
Version:
## Description
295 lines (271 loc) • 15.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: !0
}), exports.bezierCurversToPath = exports.applyTransformOnBezierCurves = exports.pathToBezierCurves = exports.alignBezierCurves = exports.findBestMorphingRotation = exports.centroidOfSubpath = exports.alignSubpath = exports.cubicSubdivide = void 0;
const vutils_1 = require("@visactor/vutils"), custom_path2d_1 = require("./custom-path2d"), path_svg_1 = require("./path-svg"), arc_1 = require("./shape/arc");
function cubicSubdivide(p0, p1, p2, p3, t, out) {
const p01 = (p1 - p0) * t + p0, p12 = (p2 - p1) * t + p1, p23 = (p3 - p2) * t + p2, p012 = (p12 - p01) * t + p01, p123 = (p23 - p12) * t + p12, p0123 = (p123 - p012) * t + p012;
out[0] = p0, out[1] = p01, out[2] = p012, out[3] = p0123, out[4] = p0123, out[5] = p123,
out[6] = p23, out[7] = p3;
}
function alignSubpath(subpath1, subpath2) {
const len1 = subpath1.length, len2 = subpath2.length;
if (len1 === len2) return [ subpath1, subpath2 ];
const tmpSegX = [], tmpSegY = [], shorterPath = len1 < len2 ? subpath1 : subpath2, shorterLen = Math.min(len1, len2), diff = Math.abs(len2 - len1) / 6, shorterBezierCount = (shorterLen - 2) / 6, eachCurveSubDivCount = Math.ceil(diff / shorterBezierCount), newSubpath = [ shorterPath[0], shorterPath[1] ];
let remained = diff;
for (let i = 2; i < shorterLen; i += 6) {
let x0 = shorterPath[i - 2], y0 = shorterPath[i - 1], x1 = shorterPath[i], y1 = shorterPath[i + 1], x2 = shorterPath[i + 2], y2 = shorterPath[i + 3];
const x3 = shorterPath[i + 4], y3 = shorterPath[i + 5];
if (remained <= 0) {
newSubpath.push(x1, y1, x2, y2, x3, y3);
continue;
}
const actualSubDivCount = Math.min(remained, eachCurveSubDivCount) + 1;
for (let k = 1; k <= actualSubDivCount; k++) {
const p = k / actualSubDivCount;
cubicSubdivide(x0, x1, x2, x3, p, tmpSegX), cubicSubdivide(y0, y1, y2, y3, p, tmpSegY),
x0 = tmpSegX[3], y0 = tmpSegY[3], newSubpath.push(tmpSegX[1], tmpSegY[1], tmpSegX[2], tmpSegY[2], x0, y0),
x1 = tmpSegX[5], y1 = tmpSegY[5], x2 = tmpSegX[6], y2 = tmpSegY[6];
}
remained -= actualSubDivCount - 1;
}
return shorterPath === subpath1 ? [ newSubpath, subpath2 ] : [ subpath1, newSubpath ];
}
function createSubpath(lastSubpath, otherSubpath) {
const prevSubPath = lastSubpath || otherSubpath, len = prevSubPath.length, lastX = prevSubPath[len - 2], lastY = prevSubPath[len - 1], newSubpath = [];
for (let i = 0; i < otherSubpath.length; i += 2) newSubpath[i] = lastX, newSubpath[i + 1] = lastY;
return newSubpath;
}
function reverseSubpath(array) {
const newArr = [], len = array.length;
for (let i = 0; i < len; i += 2) newArr[i] = array[len - i - 2], newArr[i + 1] = array[len - i - 1];
return newArr;
}
function centroidOfSubpath(array) {
let signedArea = 0, cx = 0, cy = 0;
const len = array.length;
for (let i = 0, j = len - 2; i < len; j = i, i += 2) {
const x0 = array[j], y0 = array[j + 1], x1 = array[i], y1 = array[i + 1], a = x0 * y1 - x1 * y0;
signedArea += a, cx += (x0 + x1) * a, cy += (y0 + y1) * a;
}
return 0 === signedArea ? [ array[0] || 0, array[1] || 0, 0 ] : [ cx / signedArea / 3, cy / signedArea / 3, signedArea ];
}
function findBestRotationOffset(fromSubBeziers, toSubBeziers, fromCp, toCp) {
const bezierCount = (fromSubBeziers.length - 2) / 6;
let bestScore = 1 / 0, bestOffset = 0;
const len = fromSubBeziers.length, len2 = len - 2;
for (let offset = 0; offset < bezierCount; offset++) {
const cursorOffset = 6 * offset;
let score = 0;
for (let k = 0; k < len; k += 2) {
const idx = 0 === k ? cursorOffset : (cursorOffset + k - 2) % len2 + 2, x0 = fromSubBeziers[idx] - fromCp[0], y0 = fromSubBeziers[idx + 1] - fromCp[1], dx = toSubBeziers[k] - toCp[0] - x0, dy = toSubBeziers[k + 1] - toCp[1] - y0;
score += dx * dx + dy * dy;
}
score < bestScore && (bestScore = score, bestOffset = offset);
}
return bestOffset;
}
function findBestMorphingRotation(fromArr, toArr, searchAngleIteration, searchAngleRange) {
const result = [];
let fromNeedsReverse;
for (let i = 0; i < fromArr.length; i++) {
let fromSubpathBezier = fromArr[i];
const toSubpathBezier = toArr[i], fromCp = centroidOfSubpath(fromSubpathBezier), toCp = centroidOfSubpath(toSubpathBezier);
null == fromNeedsReverse && (fromNeedsReverse = fromCp[2] < 0 != toCp[2] < 0);
const newFromSubpathBezier = [], newToSubpathBezier = [];
let bestAngle = 0, bestScore = 1 / 0;
const tmpArr = [], len = fromSubpathBezier.length;
fromNeedsReverse && (fromSubpathBezier = reverseSubpath(fromSubpathBezier));
const offset = 6 * findBestRotationOffset(fromSubpathBezier, toSubpathBezier, fromCp, toCp), len2 = len - 2;
for (let k = 0; k < len2; k += 2) {
const idx = (offset + k) % len2 + 2;
newFromSubpathBezier[k + 2] = fromSubpathBezier[idx] - fromCp[0], newFromSubpathBezier[k + 3] = fromSubpathBezier[idx + 1] - fromCp[1];
}
if (newFromSubpathBezier[0] = fromSubpathBezier[offset] - fromCp[0], newFromSubpathBezier[1] = fromSubpathBezier[offset + 1] - fromCp[1],
searchAngleIteration > 0) {
const step = searchAngleRange / searchAngleIteration;
for (let angle = -searchAngleRange / 2; angle <= searchAngleRange / 2; angle += step) {
const sa = Math.sin(angle), ca = Math.cos(angle);
let score = 0;
for (let k = 0; k < fromSubpathBezier.length; k += 2) {
const x0 = newFromSubpathBezier[k], y0 = newFromSubpathBezier[k + 1], x1 = toSubpathBezier[k] - toCp[0], y1 = toSubpathBezier[k + 1] - toCp[1], newX1 = x1 * ca - y1 * sa, newY1 = x1 * sa + y1 * ca;
tmpArr[k] = newX1, tmpArr[k + 1] = newY1;
const dx = newX1 - x0, dy = newY1 - y0;
score += dx * dx + dy * dy;
}
if (score < bestScore) {
bestScore = score, bestAngle = angle;
for (let m = 0; m < tmpArr.length; m++) newToSubpathBezier[m] = tmpArr[m];
}
}
} else for (let i = 0; i < len; i += 2) newToSubpathBezier[i] = toSubpathBezier[i] - toCp[0],
newToSubpathBezier[i + 1] = toSubpathBezier[i + 1] - toCp[1];
result.push({
from: newFromSubpathBezier,
to: newToSubpathBezier,
fromCp: fromCp,
toCp: toCp,
rotation: -bestAngle
});
}
return result;
}
function alignBezierCurves(array1, array2) {
let lastSubpath1, lastSubpath2;
const newArray1 = [], newArray2 = [];
for (let i = 0; i < Math.max(array1.length, array2.length); i++) {
const subpath1 = array1[i], subpath2 = array2[i];
let newSubpath1, newSubpath2;
subpath1 ? subpath2 ? ([newSubpath1, newSubpath2] = alignSubpath(subpath1, subpath2),
lastSubpath1 = newSubpath1, lastSubpath2 = newSubpath2) : (newSubpath2 = createSubpath(lastSubpath2, subpath1),
newSubpath1 = subpath1) : (newSubpath1 = createSubpath(lastSubpath1, subpath2),
newSubpath2 = subpath2), newArray1.push(newSubpath1), newArray2.push(newSubpath2);
}
return [ newArray1, newArray2 ];
}
exports.cubicSubdivide = cubicSubdivide, exports.alignSubpath = alignSubpath, exports.centroidOfSubpath = centroidOfSubpath,
exports.findBestMorphingRotation = findBestMorphingRotation, exports.alignBezierCurves = alignBezierCurves;
const addLineToBezierPath = (bezierPath, x0, y0, x1, y1) => {
(0, vutils_1.isNumberClose)(x0, x1) && (0, vutils_1.isNumberClose)(y0, y1) || bezierPath.push(x0, y0, x1, y1, x1, y1);
};
function pathToBezierCurves(path) {
const commandList = path.commandList, bezierArrayGroups = [];
let currentSubpath, xi = 0, yi = 0, x0 = 0, y0 = 0;
const createNewSubpath = (x, y) => {
currentSubpath && currentSubpath.length > 2 && bezierArrayGroups.push(currentSubpath),
currentSubpath = [ x, y ];
};
let x1, y1, x2, y2;
for (let i = 0, len = commandList.length; i < len; i++) {
const cmd = commandList[i], isFirst = 0 === i;
switch (isFirst && (x0 = xi = cmd[1], y0 = yi = cmd[2], [ path_svg_1.enumCommandMap.L, path_svg_1.enumCommandMap.C, path_svg_1.enumCommandMap.Q ].includes(cmd[0]) && (currentSubpath = [ x0, y0 ])),
cmd[0]) {
case path_svg_1.enumCommandMap.M:
xi = x0 = cmd[1], yi = y0 = cmd[2], createNewSubpath(x0, y0);
break;
case path_svg_1.enumCommandMap.L:
x1 = cmd[1], y1 = cmd[2], addLineToBezierPath(currentSubpath, xi, yi, x1, y1), xi = x1,
yi = y1;
break;
case path_svg_1.enumCommandMap.C:
currentSubpath.push(cmd[1], cmd[2], cmd[3], cmd[4], xi = cmd[5], yi = cmd[6]);
break;
case path_svg_1.enumCommandMap.Q:
x1 = cmd[1], y1 = cmd[2], x2 = cmd[3], y2 = cmd[4], currentSubpath.push(xi + 2 / 3 * (x1 - xi), yi + 2 / 3 * (y1 - yi), x2 + 2 / 3 * (x1 - x2), y2 + 2 / 3 * (y1 - y2), x2, y2),
xi = x2, yi = y2;
break;
case path_svg_1.enumCommandMap.A:
{
const cx = cmd[1], cy = cmd[2], rx = cmd[3], ry = rx, startAngle = cmd[4], endAngle = cmd[5], counterClockwise = !!cmd[6];
x1 = Math.cos(startAngle) * rx + cx, y1 = Math.sin(startAngle) * rx + cy, isFirst ? (x0 = x1,
y0 = y1, createNewSubpath(x0, y0)) : addLineToBezierPath(currentSubpath, xi, yi, x1, y1),
xi = Math.cos(endAngle) * rx + cx, yi = Math.sin(endAngle) * rx + cy;
const step = (counterClockwise ? -1 : 1) * Math.PI / 2;
for (let angle = startAngle; counterClockwise ? angle > endAngle : angle < endAngle; angle += step) {
const nextAngle = counterClockwise ? Math.max(angle + step, endAngle) : Math.min(angle + step, endAngle);
(0, arc_1.addArcToBezierPath)(currentSubpath, angle, nextAngle, cx, cy, rx, ry);
}
break;
}
case path_svg_1.enumCommandMap.E:
{
const cx = cmd[1], cy = cmd[2], rx = cmd[3], ry = cmd[4], rotate = cmd[5], startAngle = cmd[6], endAngle = cmd[7] + startAngle, anticlockwise = !!cmd[8], hasRotate = !(0,
vutils_1.isNumberClose)(rotate, 0), rc = Math.cos(rotate), rs = Math.sin(rotate);
let xTemp = Math.cos(startAngle) * rx, yTemp = Math.sin(startAngle) * ry;
hasRotate ? (x1 = xTemp * rc - yTemp * rs + cx, y1 = xTemp * rs + yTemp * rc + cy) : (x1 = xTemp + cx,
y1 = yTemp + cy), isFirst ? (x0 = x1, y0 = y1, createNewSubpath(x0, y0)) : addLineToBezierPath(currentSubpath, xi, yi, x1, y1),
xTemp = Math.cos(endAngle) * rx, yTemp = Math.sin(endAngle) * ry, hasRotate ? (xi = xTemp * rc - yTemp * rs + cx,
yi = xTemp * rs + yTemp * rc + cy) : (xi = xTemp + cx, yi = yTemp + cy);
const step = (anticlockwise ? -1 : 1) * Math.PI / 2;
for (let angle = startAngle; anticlockwise ? angle > endAngle : angle < endAngle; angle += step) {
const nextAngle = anticlockwise ? Math.max(angle + step, endAngle) : Math.min(angle + step, endAngle);
if ((0, arc_1.addArcToBezierPath)(currentSubpath, angle, nextAngle, cx, cy, rx, ry),
hasRotate) {
const curLen = currentSubpath.length;
for (let j = curLen - 6; j <= curLen - 1; j += 2) xTemp = currentSubpath[j], yTemp = currentSubpath[j + 1],
currentSubpath[j] = (xTemp - cx) * rc - (yTemp - cy) * rs + cx, currentSubpath[j + 1] = (xTemp - cx) * rs + (yTemp - cy) * rc + cy;
}
}
break;
}
case path_svg_1.enumCommandMap.R:
x0 = xi = cmd[1], y0 = yi = cmd[2], x1 = x0 + cmd[3], y1 = y0 + cmd[4], createNewSubpath(x1, y0),
addLineToBezierPath(currentSubpath, x1, y0, x1, y1), addLineToBezierPath(currentSubpath, x1, y1, x0, y1),
addLineToBezierPath(currentSubpath, x0, y1, x0, y0), addLineToBezierPath(currentSubpath, x0, y0, x1, y0);
break;
case path_svg_1.enumCommandMap.AT:
{
const tx1 = cmd[1], ty1 = cmd[2], tx2 = cmd[3], ty2 = cmd[4], r = cmd[5], dis1 = vutils_1.PointService.distancePP({
x: xi,
y: yi
}, {
x: tx1,
y: ty1
}), dis2 = vutils_1.PointService.distancePP({
x: tx2,
y: ty2
}, {
x: tx1,
y: ty1
}), theta = ((xi - tx1) * (tx2 - tx1) + (yi - ty1) * (ty2 - ty1)) / (dis1 * dis2), dis = r / Math.sin(theta / 2), midX = (xi + tx2 - 2 * tx1) / 2, midY = (yi + ty2 - 2 * ty1) / 2, midLen = vutils_1.PointService.distancePP({
x: midX,
y: midY
}, {
x: 0,
y: 0
}), cx = tx1 + dis * midX / midLen, cy = tx2 + dis * midY / midLen, disP = Math.sqrt(dis * dis - r * r);
x0 = tx1 + disP * (xi - tx1) / dis1, y0 = ty1 + disP * (yi - ty1) / dis1, addLineToBezierPath(currentSubpath, xi, yi, x0, y0),
xi = tx1 + disP * (tx2 - tx1) / dis2, yi = ty1 + disP * (ty2 - ty1) / dis2;
const startAngle = (0, vutils_1.getAngleByPoint)({
x: cx,
y: cy
}, {
x: x0,
y: y0
}), endAngle = (0, vutils_1.getAngleByPoint)({
x: cx,
y: cy
}, {
x: xi,
y: yi
});
(0, arc_1.addArcToBezierPath)(currentSubpath, startAngle, endAngle, cx, cy, r, r);
break;
}
case path_svg_1.enumCommandMap.Z:
currentSubpath && addLineToBezierPath(currentSubpath, xi, yi, x0, y0), xi = x0,
yi = y0;
}
}
return currentSubpath && currentSubpath.length > 2 && bezierArrayGroups.push(currentSubpath),
bezierArrayGroups;
}
function applyTransformOnBezierCurves(bezierCurves, martrix) {
for (let i = 0; i < bezierCurves.length; i++) {
const subPath = bezierCurves[i];
for (let k = 0; k < subPath.length; k += 2) {
const x = subPath[k], y = subPath[k + 1], res = {
x: x,
y: y
};
martrix.transformPoint({
x: x,
y: y
}, res), subPath[k] = res.x, subPath[k + 1] = res.y;
}
}
}
function bezierCurversToPath(bezierCurves) {
const path = new custom_path2d_1.CustomPath2D;
for (let i = 0; i < bezierCurves.length; i++) {
const subPath = bezierCurves[i];
if (subPath.length > 2) {
path.moveTo(subPath[0], subPath[1]);
for (let k = 2; k < subPath.length; k += 6) path.bezierCurveTo(subPath[k], subPath[k + 1], subPath[k + 2], subPath[k + 3], subPath[k + 4], subPath[k + 5]);
}
}
return path;
}
exports.pathToBezierCurves = pathToBezierCurves, exports.applyTransformOnBezierCurves = applyTransformOnBezierCurves,
exports.bezierCurversToPath = bezierCurversToPath;
//# sourceMappingURL=morphing-utils.js.map