UNPKG

@visactor/vrender-core

Version:

```typescript import { xxx } from '@visactor/vrender-core'; ```

192 lines (181 loc) 10.2 kB
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