import PathProxy from '../core/PathProxy'; 
 | 
import { cubicSubdivide } from '../core/curve'; 
 | 
import Path from '../graphic/Path'; 
 | 
import Element, { ElementAnimateConfig } from '../Element'; 
 | 
import { defaults, map } from '../core/util'; 
 | 
import { lerp } from '../core/vector'; 
 | 
import Group, { GroupLike } from '../graphic/Group'; 
 | 
import { clonePath } from './path'; 
 | 
import { MatrixArray } from '../core/matrix'; 
 | 
import Transformable from '../core/Transformable'; 
 | 
import { ZRenderType } from '../zrender'; 
 | 
import { split } from './dividePath'; 
 | 
import { pathToBezierCurves } from './convertPath'; 
 | 
  
 | 
function alignSubpath(subpath1: number[], subpath2: number[]): [number[], number[]] { 
 | 
    const len1 = subpath1.length; 
 | 
    const len2 = subpath2.length; 
 | 
    if (len1 === len2) { 
 | 
        return [subpath1, subpath2]; 
 | 
    } 
 | 
    const tmpSegX: number[] = []; 
 | 
    const tmpSegY: number[] = []; 
 | 
  
 | 
    const shorterPath = len1 < len2 ? subpath1 : subpath2; 
 | 
    const shorterLen = Math.min(len1, len2); 
 | 
    // Should divide excatly 
 | 
    const diff = Math.abs(len2 - len1) / 6; 
 | 
    const shorterBezierCount = (shorterLen - 2) / 6; 
 | 
    // Add `diff` number of beziers 
 | 
    const eachCurveSubDivCount = Math.ceil(diff / shorterBezierCount) + 1; 
 | 
  
 | 
    const newSubpath = [shorterPath[0], shorterPath[1]]; 
 | 
    let remained = diff; 
 | 
  
 | 
    for (let i = 2; i < shorterLen;) { 
 | 
        let x0 = shorterPath[i - 2]; 
 | 
        let y0 = shorterPath[i - 1]; 
 | 
        let x1 = shorterPath[i++]; 
 | 
        let y1 = shorterPath[i++]; 
 | 
        let x2 = shorterPath[i++]; 
 | 
        let y2 = shorterPath[i++]; 
 | 
        let x3 = shorterPath[i++]; 
 | 
        let y3 = shorterPath[i++]; 
 | 
  
 | 
        if (remained <= 0) { 
 | 
            newSubpath.push(x1, y1, x2, y2, x3, y3); 
 | 
            continue; 
 | 
        } 
 | 
  
 | 
        let actualSubDivCount = Math.min(remained, eachCurveSubDivCount - 1) + 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); 
 | 
  
 | 
            // tmpSegX[3] === tmpSegX[4] 
 | 
            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]; 
 | 
            // The last point (x3, y3) is still the same. 
 | 
        } 
 | 
        remained -= actualSubDivCount - 1; 
 | 
    } 
 | 
  
 | 
    return shorterPath === subpath1 ? [newSubpath, subpath2] : [subpath1, newSubpath]; 
 | 
} 
 | 
  
 | 
function createSubpath(lastSubpathSubpath: number[], otherSubpath: number[]) { 
 | 
    const len = lastSubpathSubpath.length; 
 | 
    const lastX = lastSubpathSubpath[len - 2]; 
 | 
    const lastY = lastSubpathSubpath[len - 1]; 
 | 
  
 | 
    const newSubpath: number[] = []; 
 | 
    for (let i = 0; i < otherSubpath.length;) { 
 | 
        newSubpath[i++] = lastX; 
 | 
        newSubpath[i++] = lastY; 
 | 
    } 
 | 
    return newSubpath; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Make two bezier arrays aligns on structure. To have better animation. 
 | 
 * 
 | 
 * It will: 
 | 
 * Make two bezier arrays have same number of subpaths. 
 | 
 * Make each subpath has equal number of bezier curves. 
 | 
 * 
 | 
 * array is the convert result of pathToBezierCurves. 
 | 
 */ 
 | 
export function alignBezierCurves(array1: number[][], array2: number[][]) { 
 | 
  
 | 
    let lastSubpath1; 
 | 
    let lastSubpath2; 
 | 
  
 | 
    let newArray1 = []; 
 | 
    let newArray2 = []; 
 | 
  
 | 
    for (let i = 0; i < Math.max(array1.length, array2.length); i++) { 
 | 
        const subpath1 = array1[i]; 
 | 
        const subpath2 = array2[i]; 
 | 
  
 | 
        let newSubpath1; 
 | 
        let newSubpath2; 
 | 
  
 | 
        if (!subpath1) { 
 | 
            newSubpath1 = createSubpath(lastSubpath1 || subpath2, subpath2); 
 | 
            newSubpath2 = subpath2; 
 | 
        } 
 | 
        else if (!subpath2) { 
 | 
            newSubpath2 = createSubpath(lastSubpath2 || subpath1, subpath1); 
 | 
            newSubpath1 = subpath1; 
 | 
        } 
 | 
        else { 
 | 
            [newSubpath1, newSubpath2] = alignSubpath(subpath1, subpath2); 
 | 
            lastSubpath1 = newSubpath1; 
 | 
            lastSubpath2 = newSubpath2; 
 | 
        } 
 | 
  
 | 
        newArray1.push(newSubpath1); 
 | 
        newArray2.push(newSubpath2); 
 | 
    } 
 | 
  
 | 
    return [newArray1, newArray2]; 
 | 
} 
 | 
  
 | 
interface MorphingPath extends Path { 
 | 
    __morphT: number; 
 | 
} 
 | 
  
 | 
export interface CombineMorphingPath extends Path { 
 | 
    childrenRef(): (CombineMorphingPath | Path)[] 
 | 
    __isCombineMorphing: boolean; 
 | 
} 
 | 
  
 | 
export function centroid(array: number[]) { 
 | 
    // https://en.wikipedia.org/wiki/Centroid#Of_a_polygon 
 | 
    let signedArea = 0; 
 | 
    let cx = 0; 
 | 
    let cy = 0; 
 | 
    const len = array.length; 
 | 
    // Polygon should been closed. 
 | 
    for (let i = 0, j = len - 2; i < len; j = i, i += 2) { 
 | 
        const x0 = array[j]; 
 | 
        const y0 = array[j + 1]; 
 | 
        const x1 = array[i]; 
 | 
        const y1 = array[i + 1]; 
 | 
        const a = x0 * y1 - x1 * y0; 
 | 
        signedArea += a; 
 | 
        cx += (x0 + x1) * a; 
 | 
        cy += (y0 + y1) * a; 
 | 
    } 
 | 
  
 | 
    if (signedArea === 0) { 
 | 
        return [array[0] || 0, array[1] || 0]; 
 | 
    } 
 | 
  
 | 
    return [cx / signedArea / 3, cy / signedArea / 3, signedArea]; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Offset the points to find the nearest morphing distance. 
 | 
 * Return beziers count needs to be offset. 
 | 
 */ 
 | 
function findBestRingOffset( 
 | 
    fromSubBeziers: number[], 
 | 
    toSubBeziers: number[], 
 | 
    fromCp: number[], 
 | 
    toCp: number[] 
 | 
) { 
 | 
    const bezierCount = (fromSubBeziers.length - 2) / 6; 
 | 
    let bestScore = Infinity; 
 | 
    let bestOffset = 0; 
 | 
  
 | 
    const len = fromSubBeziers.length; 
 | 
    const len2 = len - 2; 
 | 
    for (let offset = 0; offset < bezierCount; offset++) { 
 | 
        const cursorOffset = offset * 6; 
 | 
        let score = 0; 
 | 
  
 | 
        for (let k = 0; k < len; k += 2) { 
 | 
            let idx = k === 0 ? cursorOffset : ((cursorOffset + k - 2) % len2 + 2); 
 | 
  
 | 
            const x0 = fromSubBeziers[idx] - fromCp[0]; 
 | 
            const y0 = fromSubBeziers[idx + 1] - fromCp[1]; 
 | 
            const x1 = toSubBeziers[k] - toCp[0]; 
 | 
            const y1 = toSubBeziers[k + 1] - toCp[1]; 
 | 
  
 | 
            const dx = x1 - x0; 
 | 
            const dy = y1 - y0; 
 | 
            score += dx * dx + dy * dy; 
 | 
        } 
 | 
        if (score < bestScore) { 
 | 
            bestScore = score; 
 | 
            bestOffset = offset; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    return bestOffset; 
 | 
} 
 | 
  
 | 
function reverse(array: number[]) { 
 | 
    const newArr: number[] = []; 
 | 
    const 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; 
 | 
} 
 | 
  
 | 
type MorphingData = { 
 | 
    from: number[]; 
 | 
    to: number[]; 
 | 
    fromCp: number[]; 
 | 
    toCp: number[]; 
 | 
    rotation: number; 
 | 
}[]; 
 | 
  
 | 
/** 
 | 
 * If we interpolating between two bezier curve arrays. 
 | 
 * It will have many broken effects during the transition. 
 | 
 * So we try to apply an extra rotation which can make each bezier curve morph as small as possible. 
 | 
 */ 
 | 
function findBestMorphingRotation( 
 | 
    fromArr: number[][], 
 | 
    toArr: number[][], 
 | 
    searchAngleIteration: number, 
 | 
    searchAngleRange: number 
 | 
): MorphingData { 
 | 
    const result = []; 
 | 
  
 | 
    let fromNeedsReverse: boolean; 
 | 
  
 | 
    for (let i = 0; i < fromArr.length; i++) { 
 | 
        let fromSubpathBezier = fromArr[i]; 
 | 
        const toSubpathBezier = toArr[i]; 
 | 
  
 | 
        const fromCp = centroid(fromSubpathBezier); 
 | 
        const toCp = centroid(toSubpathBezier); 
 | 
  
 | 
        if (fromNeedsReverse == null) { 
 | 
            // Reverse from array if two have different directions. 
 | 
            // Determine the clockwise based on the first subpath. 
 | 
            // Reverse all subpaths or not. Avoid winding rule changed. 
 | 
            fromNeedsReverse = fromCp[2] < 0 !== toCp[2] < 0; 
 | 
        } 
 | 
  
 | 
        const newFromSubpathBezier: number[] = []; 
 | 
        const newToSubpathBezier: number[] = []; 
 | 
        let bestAngle = 0; 
 | 
        let bestScore = Infinity; 
 | 
        let tmpArr: number[] = []; 
 | 
  
 | 
        const len = fromSubpathBezier.length; 
 | 
        if (fromNeedsReverse) { 
 | 
            // Make sure clockwise 
 | 
            fromSubpathBezier = reverse(fromSubpathBezier); 
 | 
        } 
 | 
        const offset = findBestRingOffset(fromSubpathBezier, toSubpathBezier, fromCp, toCp) * 6; 
 | 
  
 | 
        const len2 = len - 2; 
 | 
        for (let k = 0; k < len2; k += 2) { 
 | 
            // Not include the start point. 
 | 
            const idx = (offset + k) % len2 + 2; 
 | 
            newFromSubpathBezier[k + 2] = fromSubpathBezier[idx] - fromCp[0]; 
 | 
            newFromSubpathBezier[k + 3] = fromSubpathBezier[idx + 1] - fromCp[1]; 
 | 
        } 
 | 
        newFromSubpathBezier[0] = fromSubpathBezier[offset] - fromCp[0]; 
 | 
        newFromSubpathBezier[1] = fromSubpathBezier[offset + 1] - fromCp[1]; 
 | 
  
 | 
        if (searchAngleIteration > 0) { 
 | 
            const step = searchAngleRange / searchAngleIteration; 
 | 
            for (let angle = -searchAngleRange / 2; angle <= searchAngleRange / 2; angle += step) { 
 | 
                const sa = Math.sin(angle); 
 | 
                const ca = Math.cos(angle); 
 | 
                let score = 0; 
 | 
  
 | 
                for (let k = 0; k < fromSubpathBezier.length; k += 2) { 
 | 
                    const x0 = newFromSubpathBezier[k]; 
 | 
                    const y0 = newFromSubpathBezier[k + 1]; 
 | 
                    const x1 = toSubpathBezier[k] - toCp[0]; 
 | 
                    const y1 = toSubpathBezier[k + 1] - toCp[1]; 
 | 
  
 | 
                    // Apply rotation on the target point. 
 | 
                    const newX1 = x1 * ca - y1 * sa; 
 | 
                    const newY1 = x1 * sa + y1 * ca; 
 | 
  
 | 
                    tmpArr[k] = newX1; 
 | 
                    tmpArr[k + 1] = newY1; 
 | 
  
 | 
                    const dx = newX1 - x0; 
 | 
                    const dy = newY1 - y0; 
 | 
  
 | 
                    // Use dot product to have min direction change. 
 | 
                    // const d = Math.sqrt(x0 * x0 + y0 * y0); 
 | 
                    // score += x0 * dx / d + y0 * dy / d; 
 | 
                    score += dx * dx + dy * dy; 
 | 
                } 
 | 
  
 | 
                if (score < bestScore) { 
 | 
                    bestScore = score; 
 | 
                    bestAngle = angle; 
 | 
                    // Copy. 
 | 
                    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, 
 | 
            toCp, 
 | 
            rotation: -bestAngle 
 | 
        }); 
 | 
    } 
 | 
    return result; 
 | 
} 
 | 
  
 | 
export function isCombineMorphing(path: Element): path is CombineMorphingPath { 
 | 
    return (path as CombineMorphingPath).__isCombineMorphing; 
 | 
} 
 | 
  
 | 
export function isMorphing(el: Element) { 
 | 
    return (el as MorphingPath).__morphT >= 0; 
 | 
} 
 | 
  
 | 
const SAVED_METHOD_PREFIX = '__mOriginal_'; 
 | 
function saveAndModifyMethod<T extends object, M extends keyof T>( 
 | 
    obj: T, 
 | 
    methodName: M, 
 | 
    modifiers: { replace?: T[M], after?: T[M], before?: T[M] } 
 | 
) { 
 | 
    const savedMethodName = SAVED_METHOD_PREFIX + methodName; 
 | 
    const originalMethod = (obj as any)[savedMethodName] || obj[methodName]; 
 | 
    if (!(obj as any)[savedMethodName]) { 
 | 
        (obj as any)[savedMethodName] = obj[methodName]; 
 | 
    } 
 | 
    const replace = modifiers.replace; 
 | 
    const after = modifiers.after; 
 | 
    const before = modifiers.before; 
 | 
  
 | 
    (obj as any)[methodName] = function () { 
 | 
        const args = arguments; 
 | 
        let res; 
 | 
        before && (before as unknown as Function).apply(this, args); 
 | 
        // Still call the original method if not replacement. 
 | 
        if (replace) { 
 | 
            res = (replace as unknown as Function).apply(this, args); 
 | 
        } 
 | 
        else { 
 | 
            res = originalMethod.apply(this, args); 
 | 
        } 
 | 
        after && (after as unknown as Function).apply(this, args); 
 | 
        return res; 
 | 
    }; 
 | 
} 
 | 
function restoreMethod<T extends object>( 
 | 
    obj: T, 
 | 
    methodName: keyof T 
 | 
) { 
 | 
    const savedMethodName = SAVED_METHOD_PREFIX + methodName; 
 | 
    if ((obj as any)[savedMethodName]) { 
 | 
        obj[methodName] = (obj as any)[savedMethodName]; 
 | 
        (obj as any)[savedMethodName] = null; 
 | 
    } 
 | 
} 
 | 
  
 | 
function applyTransformOnBeziers(bezierCurves: number[][], mm: MatrixArray) { 
 | 
    for (let i = 0; i < bezierCurves.length; i++) { 
 | 
        const subBeziers = bezierCurves[i]; 
 | 
        for (let k = 0; k < subBeziers.length;) { 
 | 
            const x = subBeziers[k]; 
 | 
            const y = subBeziers[k + 1]; 
 | 
  
 | 
            subBeziers[k++] = mm[0] * x + mm[2] * y + mm[4]; 
 | 
            subBeziers[k++] = mm[1] * x + mm[3] * y + mm[5]; 
 | 
        } 
 | 
    } 
 | 
} 
 | 
  
 | 
function prepareMorphPath( 
 | 
    fromPath: Path, 
 | 
    toPath: Path 
 | 
) { 
 | 
    const fromPathProxy = fromPath.getUpdatedPathProxy(); 
 | 
    const toPathProxy = toPath.getUpdatedPathProxy(); 
 | 
  
 | 
    const [fromBezierCurves, toBezierCurves] = 
 | 
        alignBezierCurves(pathToBezierCurves(fromPathProxy), pathToBezierCurves(toPathProxy)); 
 | 
  
 | 
    const fromPathTransform = fromPath.getComputedTransform(); 
 | 
    const toPathTransform = toPath.getComputedTransform(); 
 | 
    function updateIdentityTransform(this: Transformable) { 
 | 
        this.transform = null; 
 | 
    } 
 | 
    fromPathTransform && applyTransformOnBeziers(fromBezierCurves, fromPathTransform); 
 | 
    toPathTransform && applyTransformOnBeziers(toBezierCurves, toPathTransform); 
 | 
    // Just ignore transform 
 | 
    saveAndModifyMethod(toPath, 'updateTransform', { replace: updateIdentityTransform }); 
 | 
    toPath.transform = null; 
 | 
  
 | 
    const morphingData = findBestMorphingRotation(fromBezierCurves, toBezierCurves, 10, Math.PI); 
 | 
  
 | 
    const tmpArr: number[] = []; 
 | 
  
 | 
    saveAndModifyMethod(toPath, 'buildPath', { replace(path: PathProxy) { 
 | 
        const t = (toPath as MorphingPath).__morphT; 
 | 
        const onet = 1 - t; 
 | 
  
 | 
        const newCp: number[] = []; 
 | 
  
 | 
        for (let i = 0; i < morphingData.length; i++) { 
 | 
            const item = morphingData[i]; 
 | 
            const from = item.from; 
 | 
            const to = item.to; 
 | 
            const angle = item.rotation * t; 
 | 
            const fromCp = item.fromCp; 
 | 
            const toCp = item.toCp; 
 | 
            const sa = Math.sin(angle); 
 | 
            const ca = Math.cos(angle); 
 | 
  
 | 
            lerp(newCp, fromCp, toCp, t); 
 | 
  
 | 
            for (let m = 0; m < from.length; m += 2) { 
 | 
                const x0 = from[m]; 
 | 
                const y0 = from[m + 1]; 
 | 
                const x1 = to[m]; 
 | 
                const y1 = to[m + 1]; 
 | 
  
 | 
                const x = x0 * onet + x1 * t; 
 | 
                const y = y0 * onet + y1 * t; 
 | 
  
 | 
                tmpArr[m] = (x * ca - y * sa) + newCp[0]; 
 | 
                tmpArr[m + 1] = (x * sa + y * ca) + newCp[1]; 
 | 
            } 
 | 
  
 | 
            let x0 = tmpArr[0]; 
 | 
            let y0 = tmpArr[1]; 
 | 
  
 | 
            path.moveTo(x0, y0); 
 | 
  
 | 
            for (let m = 2; m < from.length;) { 
 | 
                const x1 = tmpArr[m++]; 
 | 
                const y1 = tmpArr[m++]; 
 | 
                const x2 = tmpArr[m++]; 
 | 
                const y2 = tmpArr[m++]; 
 | 
                const x3 = tmpArr[m++]; 
 | 
                const y3 = tmpArr[m++]; 
 | 
  
 | 
                // Is a line. 
 | 
                if (x0 === x1 && y0 === y1 && x2 === x3 && y2 === y3) { 
 | 
                    path.lineTo(x3, y3); 
 | 
                } 
 | 
                else { 
 | 
                    path.bezierCurveTo(x1, y1, x2, y2, x3, y3); 
 | 
                } 
 | 
                x0 = x3; 
 | 
                y0 = y3; 
 | 
            } 
 | 
        } 
 | 
    } }); 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Morphing from old path to new path. 
 | 
 */ 
 | 
export function morphPath( 
 | 
    fromPath: Path, 
 | 
    toPath: Path, 
 | 
    animationOpts: ElementAnimateConfig 
 | 
): Path { 
 | 
    if (!fromPath || !toPath) { 
 | 
        return toPath; 
 | 
    } 
 | 
  
 | 
    const oldDone = animationOpts.done; 
 | 
    // const oldAborted = animationOpts.aborted; 
 | 
    const oldDuring = animationOpts.during; 
 | 
  
 | 
    prepareMorphPath(fromPath, toPath); 
 | 
  
 | 
    (toPath as MorphingPath).__morphT = 0; 
 | 
  
 | 
    function restoreToPath() { 
 | 
        restoreMethod(toPath, 'buildPath'); 
 | 
        restoreMethod(toPath, 'updateTransform'); 
 | 
        // Mark as not in morphing 
 | 
        (toPath as MorphingPath).__morphT = -1; 
 | 
        // Cleanup. 
 | 
        toPath.createPathProxy(); 
 | 
        toPath.dirtyShape(); 
 | 
    } 
 | 
  
 | 
    toPath.animateTo({ 
 | 
        __morphT: 1 
 | 
    } as any, defaults({ 
 | 
        during(p) { 
 | 
            toPath.dirtyShape(); 
 | 
            oldDuring && oldDuring(p); 
 | 
        }, 
 | 
        done() { 
 | 
            restoreToPath(); 
 | 
            oldDone && oldDone(); 
 | 
        } 
 | 
        // NOTE: Don't do restore if aborted. 
 | 
        // Because all status was just set when animation started. 
 | 
        // aborted() { 
 | 
        //     oldAborted && oldAborted(); 
 | 
        // } 
 | 
    } as ElementAnimateConfig, animationOpts)); 
 | 
  
 | 
    return toPath; 
 | 
} 
 | 
  
 | 
// https://github.com/mapbox/earcut/blob/master/src/earcut.js#L437 
 | 
// https://jsfiddle.net/pissang/2jk7x145/ 
 | 
// function zOrder(x: number, y: number, minX: number, minY: number, maxX: number, maxY: number) { 
 | 
//     // Normalize coords to 0 - 1 
 | 
//     // The transformed into non-negative 15-bit integer range 
 | 
//     x = (maxX === minX) ? 0 : Math.round(32767 * (x - minX) / (maxX - minX)); 
 | 
//     y = (maxY === minY) ? 0 : Math.round(32767 * (y - minY) / (maxY - minY)); 
 | 
  
 | 
//     x = (x | (x << 8)) & 0x00FF00FF; 
 | 
//     x = (x | (x << 4)) & 0x0F0F0F0F; 
 | 
//     x = (x | (x << 2)) & 0x33333333; 
 | 
//     x = (x | (x << 1)) & 0x55555555; 
 | 
  
 | 
//     y = (y | (y << 8)) & 0x00FF00FF; 
 | 
//     y = (y | (y << 4)) & 0x0F0F0F0F; 
 | 
//     y = (y | (y << 2)) & 0x33333333; 
 | 
//     y = (y | (y << 1)) & 0x55555555; 
 | 
  
 | 
//     return x | (y << 1); 
 | 
// } 
 | 
  
 | 
// https://github.com/w8r/hilbert/blob/master/hilbert.js#L30 
 | 
// https://jsfiddle.net/pissang/xdnbzg6v/ 
 | 
function hilbert(x: number, y: number, minX: number, minY: number, maxX: number, maxY: number) { 
 | 
    const bits = 16; 
 | 
    x = (maxX === minX) ? 0 : Math.round(32767 * (x - minX) / (maxX - minX)); 
 | 
    y = (maxY === minY) ? 0 : Math.round(32767 * (y - minY) / (maxY - minY)); 
 | 
  
 | 
    let d = 0; 
 | 
    let tmp: number; 
 | 
    for (let s = (1 << bits) / 2; s > 0; s /= 2) { 
 | 
        let rx = 0; 
 | 
        let ry = 0; 
 | 
  
 | 
        if ((x & s) > 0) { 
 | 
            rx = 1; 
 | 
        } 
 | 
        if ((y & s) > 0) { 
 | 
            ry = 1; 
 | 
        } 
 | 
  
 | 
        d += s * s * ((3 * rx) ^ ry); 
 | 
  
 | 
        if (ry === 0) { 
 | 
            if (rx === 1) { 
 | 
                x = s - 1 - x; 
 | 
                y = s - 1 - y; 
 | 
            } 
 | 
            tmp = x; 
 | 
            x = y; 
 | 
            y = tmp; 
 | 
        } 
 | 
    } 
 | 
    return d; 
 | 
} 
 | 
  
 | 
// Sort paths on hilbert. Not using z-order because it may still have large cross. 
 | 
// So the left most source can animate to the left most target, not right most target. 
 | 
// Hope in this way. We can make sure each element is animated to the proper target. Not the farthest. 
 | 
function sortPaths(pathList: Path[]): Path[] { 
 | 
    let xMin = Infinity; 
 | 
    let yMin = Infinity; 
 | 
    let xMax = -Infinity; 
 | 
    let yMax = -Infinity; 
 | 
    const cps = map(pathList, path => { 
 | 
        const rect = path.getBoundingRect(); 
 | 
        const m = path.getComputedTransform(); 
 | 
        const x = rect.x + rect.width / 2 + (m ? m[4] : 0); 
 | 
        const y = rect.y + rect.height / 2 + (m ? m[5] : 0); 
 | 
        xMin = Math.min(x, xMin); 
 | 
        yMin = Math.min(y, yMin); 
 | 
        xMax = Math.max(x, xMax); 
 | 
        yMax = Math.max(y, yMax); 
 | 
        return [x, y]; 
 | 
    }); 
 | 
  
 | 
    const items = map(cps, (cp, idx) => { 
 | 
        return { 
 | 
            cp, 
 | 
            z: hilbert(cp[0], cp[1], xMin, yMin, xMax, yMax), 
 | 
            path: pathList[idx] 
 | 
        }; 
 | 
    }); 
 | 
  
 | 
    return items.sort((a, b) => a.z - b.z).map(item => item.path); 
 | 
} 
 | 
  
 | 
export interface DividePathParams { 
 | 
    path: Path, 
 | 
    count: number 
 | 
}; 
 | 
export interface DividePath { 
 | 
    (params: DividePathParams): Path[] 
 | 
} 
 | 
  
 | 
export interface IndividualDelay { 
 | 
    (index: number, count: number, fromPath: Path, toPath: Path): number 
 | 
} 
 | 
  
 | 
function defaultDividePath(param: DividePathParams) { 
 | 
    return split(param.path, param.count); 
 | 
} 
 | 
export interface CombineConfig extends ElementAnimateConfig { 
 | 
    /** 
 | 
     * Transform of returned will be ignored. 
 | 
     */ 
 | 
    dividePath?: DividePath 
 | 
    /** 
 | 
     * delay of each individual. 
 | 
     * Because individual are sorted on z-order. The index is also sorted top-left / right-down. 
 | 
     */ 
 | 
    individualDelay?: IndividualDelay 
 | 
    /** 
 | 
     * If sort splitted paths so the movement between them can be more natural 
 | 
     */ 
 | 
    // sort?: boolean 
 | 
} 
 | 
  
 | 
function createEmptyReturn() { 
 | 
    return { 
 | 
        fromIndividuals: [] as Path[], 
 | 
        toIndividuals: [] as Path[], 
 | 
        count: 0 
 | 
    }; 
 | 
} 
 | 
/** 
 | 
 * Make combine morphing from many paths to one. 
 | 
 * Will return a group to replace the original path. 
 | 
 */ 
 | 
export function combineMorph( 
 | 
    fromList: (CombineMorphingPath | Path)[], 
 | 
    toPath: Path, 
 | 
    animationOpts: CombineConfig 
 | 
) { 
 | 
    let fromPathList: Path[] = []; 
 | 
  
 | 
    function addFromPath(fromList: Element[]) { 
 | 
        for (let i = 0; i < fromList.length; i++) { 
 | 
            const from = fromList[i]; 
 | 
            if (isCombineMorphing(from)) { 
 | 
                addFromPath((from as GroupLike).childrenRef()); 
 | 
            } 
 | 
            else if (from instanceof Path) { 
 | 
                fromPathList.push(from); 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
    addFromPath(fromList); 
 | 
  
 | 
    const separateCount = fromPathList.length; 
 | 
  
 | 
    // fromPathList.length is 0. 
 | 
    if (!separateCount) { 
 | 
        return createEmptyReturn(); 
 | 
    } 
 | 
  
 | 
    const dividePath = animationOpts.dividePath || defaultDividePath; 
 | 
  
 | 
    let toSubPathList = dividePath({ 
 | 
        path: toPath, count: separateCount 
 | 
    }); 
 | 
    if (toSubPathList.length !== separateCount) { 
 | 
        console.error('Invalid morphing: unmatched splitted path'); 
 | 
        return createEmptyReturn(); 
 | 
    } 
 | 
  
 | 
    fromPathList = sortPaths(fromPathList); 
 | 
    toSubPathList = sortPaths(toSubPathList); 
 | 
  
 | 
    const oldDone = animationOpts.done; 
 | 
    // const oldAborted = animationOpts.aborted; 
 | 
    const oldDuring = animationOpts.during; 
 | 
    const individualDelay = animationOpts.individualDelay; 
 | 
  
 | 
    const identityTransform = new Transformable(); 
 | 
  
 | 
    for (let i = 0; i < separateCount; i++) { 
 | 
        const from = fromPathList[i]; 
 | 
        const to = toSubPathList[i]; 
 | 
  
 | 
        to.parent = toPath as unknown as Group; 
 | 
  
 | 
        // Ignore transform in each subpath. 
 | 
        to.copyTransform(identityTransform); 
 | 
  
 | 
        // Will do morphPath for each individual if individualDelay is set. 
 | 
        if (!individualDelay) { 
 | 
            prepareMorphPath(from, to); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    (toPath as CombineMorphingPath).__isCombineMorphing = true; 
 | 
    (toPath as CombineMorphingPath).childrenRef = function () { 
 | 
        return toSubPathList; 
 | 
    }; 
 | 
  
 | 
    function addToSubPathListToZr(zr: ZRenderType) { 
 | 
        for (let i = 0; i < toSubPathList.length; i++) { 
 | 
            toSubPathList[i].addSelfToZr(zr); 
 | 
        } 
 | 
    } 
 | 
    saveAndModifyMethod(toPath, 'addSelfToZr', { 
 | 
        after(zr) { 
 | 
            addToSubPathListToZr(zr); 
 | 
        } 
 | 
    }); 
 | 
    saveAndModifyMethod(toPath, 'removeSelfFromZr', { 
 | 
        after(zr) { 
 | 
            for (let i = 0; i < toSubPathList.length; i++) { 
 | 
                toSubPathList[i].removeSelfFromZr(zr); 
 | 
            } 
 | 
        } 
 | 
    }); 
 | 
  
 | 
    function restoreToPath() { 
 | 
        (toPath as CombineMorphingPath).__isCombineMorphing = false; 
 | 
        // Mark as not in morphing 
 | 
        (toPath as MorphingPath).__morphT = -1; 
 | 
        (toPath as CombineMorphingPath).childrenRef = null; 
 | 
  
 | 
        restoreMethod(toPath, 'addSelfToZr'); 
 | 
        restoreMethod(toPath, 'removeSelfFromZr'); 
 | 
    } 
 | 
  
 | 
    const toLen = toSubPathList.length; 
 | 
  
 | 
    if (individualDelay) { 
 | 
        let animating = toLen; 
 | 
        const eachDone = () => { 
 | 
            animating--; 
 | 
            if (animating === 0) { 
 | 
                restoreToPath(); 
 | 
                oldDone && oldDone(); 
 | 
            } 
 | 
        }; 
 | 
        // Animate each element individually. 
 | 
        for (let i = 0; i < toLen; i++) { 
 | 
            // TODO only call during once? 
 | 
            const indivdualAnimationOpts = individualDelay ? defaults({ 
 | 
                delay: (animationOpts.delay || 0) + individualDelay(i, toLen, fromPathList[i], toSubPathList[i]), 
 | 
                done: eachDone 
 | 
            } as ElementAnimateConfig, animationOpts) : animationOpts; 
 | 
            morphPath(fromPathList[i], toSubPathList[i], indivdualAnimationOpts); 
 | 
        } 
 | 
    } 
 | 
    else { 
 | 
        (toPath as MorphingPath).__morphT = 0; 
 | 
        toPath.animateTo({ 
 | 
            __morphT: 1 
 | 
        } as any, defaults({ 
 | 
            during(p) { 
 | 
                for (let i = 0; i < toLen; i++) { 
 | 
                    const child = toSubPathList[i] as MorphingPath; 
 | 
                    child.__morphT = (toPath as MorphingPath).__morphT; 
 | 
                    child.dirtyShape(); 
 | 
                } 
 | 
                oldDuring && oldDuring(p); 
 | 
            }, 
 | 
            done() { 
 | 
                restoreToPath(); 
 | 
                for (let i = 0; i < fromList.length; i++) { 
 | 
                    restoreMethod(fromList[i], 'updateTransform'); 
 | 
                } 
 | 
                oldDone && oldDone(); 
 | 
            } 
 | 
        } as ElementAnimateConfig, animationOpts)); 
 | 
    } 
 | 
  
 | 
    if (toPath.__zr) { 
 | 
        addToSubPathListToZr(toPath.__zr); 
 | 
    } 
 | 
  
 | 
    return { 
 | 
        fromIndividuals: fromPathList, 
 | 
        toIndividuals: toSubPathList, 
 | 
        count: toLen 
 | 
    }; 
 | 
} 
 | 
export interface SeparateConfig extends ElementAnimateConfig { 
 | 
    dividePath?: DividePath 
 | 
    individualDelay?: IndividualDelay 
 | 
    /** 
 | 
     * If sort splitted paths so the movement between them can be more natural 
 | 
     */ 
 | 
    // sort?: boolean 
 | 
    // // If the from path of separate animation is doing combine animation. 
 | 
    // // And the paths number is not same with toPathList. We need to do enter/leave animation 
 | 
    // // on the missing/spare paths. 
 | 
    // enter?: (el: Path) => void 
 | 
    // leave?: (el: Path) => void 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Make separate morphing from one path to many paths. 
 | 
 * Make the MorphingKind of `toPath` become `'ONE_ONE'`. 
 | 
 */ 
 | 
export function separateMorph( 
 | 
    fromPath: Path, 
 | 
    toPathList: Path[], 
 | 
    animationOpts: SeparateConfig 
 | 
) { 
 | 
    const toLen = toPathList.length; 
 | 
    let fromPathList: Path[] = []; 
 | 
  
 | 
    const dividePath = animationOpts.dividePath || defaultDividePath; 
 | 
  
 | 
    function addFromPath(fromList: Element[]) { 
 | 
        for (let i = 0; i < fromList.length; i++) { 
 | 
            const from = fromList[i]; 
 | 
            if (isCombineMorphing(from)) { 
 | 
                addFromPath((from as GroupLike).childrenRef()); 
 | 
            } 
 | 
            else if (from instanceof Path) { 
 | 
                fromPathList.push(from); 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
    // This case most happen when a combining path is called to reverse the animation 
 | 
    // to its original separated state. 
 | 
    if (isCombineMorphing(fromPath)) { 
 | 
        addFromPath(fromPath.childrenRef()); 
 | 
  
 | 
        const fromLen = fromPathList.length; 
 | 
        if (fromLen < toLen) { 
 | 
            let k = 0; 
 | 
            for (let i = fromLen; i < toLen; i++) { 
 | 
                // Create a clone 
 | 
                fromPathList.push(clonePath(fromPathList[k++ % fromLen])); 
 | 
            } 
 | 
        } 
 | 
        // Else simply remove if fromLen > toLen 
 | 
        fromPathList.length = toLen; 
 | 
    } 
 | 
    else { 
 | 
        fromPathList = dividePath({ path: fromPath, count: toLen }); 
 | 
        const fromPathTransform = fromPath.getComputedTransform(); 
 | 
        for (let i = 0; i < fromPathList.length; i++) { 
 | 
            // Force use transform of source path. 
 | 
            fromPathList[i].setLocalTransform(fromPathTransform); 
 | 
        } 
 | 
        if (fromPathList.length !== toLen) { 
 | 
            console.error('Invalid morphing: unmatched splitted path'); 
 | 
            return createEmptyReturn(); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    fromPathList = sortPaths(fromPathList); 
 | 
    toPathList = sortPaths(toPathList); 
 | 
  
 | 
    const individualDelay = animationOpts.individualDelay; 
 | 
    for (let i = 0; i < toLen; i++) { 
 | 
        const indivdualAnimationOpts = individualDelay ? defaults({ 
 | 
            delay: (animationOpts.delay || 0) + individualDelay(i, toLen, fromPathList[i], toPathList[i]) 
 | 
        } as ElementAnimateConfig, animationOpts) : animationOpts; 
 | 
        morphPath(fromPathList[i], toPathList[i], indivdualAnimationOpts); 
 | 
    } 
 | 
  
 | 
    return { 
 | 
        fromIndividuals: fromPathList, 
 | 
        toIndividuals: toPathList, 
 | 
        count: toPathList.length 
 | 
    }; 
 | 
} 
 | 
  
 | 
export { split as defaultDividePath }; 
 |