@visactor/vrender-core
Version:
```typescript import { xxx } from '@visactor/vrender-core'; ```
192 lines (181 loc) • 10.2 kB
JavaScript
import { CustomPath2D } from "./custom-path2d";
export 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;
}
export 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;
}
export 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;
}
export 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;
}
export 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 ];
}
export function pathToBezierCurves(path) {
const tempPath = new CustomPath2D, svgPathString = path.toString();
if (!svgPathString) return [];
tempPath.fromString(svgPathString);
const curves = tempPath.tryBuildCurves();
if (!curves || 0 === curves.length) return [];
const bezierSubpaths = [];
let currentSubpath = null;
currentSubpath = [];
let firstX = 0, firstY = 0, lastX = 0, lastY = 0, isSubpathStart = !0, isPathClosed = !1;
for (let i = 0; i < curves.length; i++) {
const curve = curves[i];
if (isSubpathStart && (firstX = curve.p0.x, firstY = curve.p0.y, lastX = firstX,
lastY = firstY, currentSubpath = [ firstX, firstY ], bezierSubpaths.push(currentSubpath),
isSubpathStart = !1), curve.p1 && curve.p2 && curve.p3) currentSubpath.push(curve.p1.x, curve.p1.y, curve.p2.x, curve.p2.y, curve.p3.x, curve.p3.y),
lastX = curve.p3.x, lastY = curve.p3.y; else if (curve.p1 && curve.p2) {
const x1 = curve.p1.x, y1 = curve.p1.y, x2 = curve.p2.x, y2 = curve.p2.y;
currentSubpath.push(lastX + 2 / 3 * (x1 - lastX), lastY + 2 / 3 * (y1 - lastY), x2 + 2 / 3 * (x1 - x2), y2 + 2 / 3 * (y1 - y2), x2, y2),
lastX = x2, lastY = y2;
} else if (curve.p1) {
const endX = curve.p1.x, endY = curve.p1.y;
Math.abs(lastX - endX) < 1e-10 && Math.abs(lastY - endY) < 1e-10 || currentSubpath.push(lastX, lastY, endX, endY, endX, endY),
lastX = endX, lastY = endY;
}
if (i === curves.length - 1 && Math.abs(lastX - firstX) < 1e-10 && Math.abs(lastY - firstY) < 1e-10 && (isPathClosed = !0),
i < curves.length - 1) {
const nextCurve = curves[i + 1];
(Math.abs(lastX - nextCurve.p0.x) > 1e-10 || Math.abs(lastY - nextCurve.p0.y) > 1e-10) && (isSubpathStart = !0);
}
}
const validSubpaths = bezierSubpaths.filter((subpath => subpath.length > 2));
return 1 === validSubpaths.length ? [ validSubpaths[0] ] : validSubpaths;
}
export 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;
}
}
}
export function bezierCurversToPath(bezierCurves) {
const path = new 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;
}
//# sourceMappingURL=morphing-utils.js.map