import * as matrix from './matrix'; 
 | 
import * as vector from './vector'; 
 | 
  
 | 
const mIdentity = matrix.identity; 
 | 
  
 | 
const EPSILON = 5e-5; 
 | 
  
 | 
function isNotAroundZero(val: number) { 
 | 
    return val > EPSILON || val < -EPSILON; 
 | 
} 
 | 
  
 | 
const scaleTmp: vector.VectorArray = []; 
 | 
const tmpTransform: matrix.MatrixArray = []; 
 | 
const originTransform = matrix.create(); 
 | 
const abs = Math.abs; 
 | 
  
 | 
class Transformable { 
 | 
  
 | 
    parent: Transformable 
 | 
  
 | 
    x: number 
 | 
    y: number 
 | 
  
 | 
    scaleX: number 
 | 
    scaleY: number 
 | 
  
 | 
    skewX: number 
 | 
    skewY: number 
 | 
  
 | 
    rotation: number 
 | 
  
 | 
    /** 
 | 
     * Will translated the element to the anchor position before applying other transforms. 
 | 
     */ 
 | 
    anchorX: number 
 | 
    anchorY: number 
 | 
    /** 
 | 
     * Origin of scale, rotation, skew 
 | 
     */ 
 | 
    originX: number 
 | 
    originY: number 
 | 
  
 | 
    /** 
 | 
     * Scale ratio 
 | 
     */ 
 | 
    globalScaleRatio: number 
 | 
  
 | 
    transform: matrix.MatrixArray 
 | 
    invTransform: matrix.MatrixArray 
 | 
  
 | 
    /** 
 | 
     * Get computed local transform 
 | 
     */ 
 | 
    getLocalTransform(m?: matrix.MatrixArray) { 
 | 
        return Transformable.getLocalTransform(this, m); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Set position from array 
 | 
     */ 
 | 
    setPosition(arr: number[]) { 
 | 
        this.x = arr[0]; 
 | 
        this.y = arr[1]; 
 | 
    } 
 | 
    /** 
 | 
     * Set scale from array 
 | 
     */ 
 | 
    setScale(arr: number[]) { 
 | 
        this.scaleX = arr[0]; 
 | 
        this.scaleY = arr[1]; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Set skew from array 
 | 
     */ 
 | 
    setSkew(arr: number[]) { 
 | 
        this.skewX = arr[0]; 
 | 
        this.skewY = arr[1]; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Set origin from array 
 | 
     */ 
 | 
    setOrigin(arr: number[]) { 
 | 
        this.originX = arr[0]; 
 | 
        this.originY = arr[1]; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * If needs to compute transform 
 | 
     */ 
 | 
    needLocalTransform(): boolean { 
 | 
        return isNotAroundZero(this.rotation) 
 | 
            || isNotAroundZero(this.x) 
 | 
            || isNotAroundZero(this.y) 
 | 
            || isNotAroundZero(this.scaleX - 1) 
 | 
            || isNotAroundZero(this.scaleY - 1) 
 | 
            || isNotAroundZero(this.skewX) 
 | 
            || isNotAroundZero(this.skewY); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Update global transform 
 | 
     */ 
 | 
    updateTransform() { 
 | 
        const parentTransform = this.parent && this.parent.transform; 
 | 
        const needLocalTransform = this.needLocalTransform(); 
 | 
  
 | 
        let m = this.transform; 
 | 
        if (!(needLocalTransform || parentTransform)) { 
 | 
            if (m) { 
 | 
                mIdentity(m); 
 | 
                // reset invTransform 
 | 
                this.invTransform = null; 
 | 
            } 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        m = m || matrix.create(); 
 | 
  
 | 
        if (needLocalTransform) { 
 | 
            this.getLocalTransform(m); 
 | 
        } 
 | 
        else { 
 | 
            mIdentity(m); 
 | 
        } 
 | 
  
 | 
        // 应用父节点变换 
 | 
        if (parentTransform) { 
 | 
            if (needLocalTransform) { 
 | 
                matrix.mul(m, parentTransform, m); 
 | 
            } 
 | 
            else { 
 | 
                matrix.copy(m, parentTransform); 
 | 
            } 
 | 
        } 
 | 
        // 保存这个变换矩阵 
 | 
        this.transform = m; 
 | 
  
 | 
        this._resolveGlobalScaleRatio(m); 
 | 
    } 
 | 
  
 | 
    private _resolveGlobalScaleRatio(m: matrix.MatrixArray) { 
 | 
        const globalScaleRatio = this.globalScaleRatio; 
 | 
        if (globalScaleRatio != null && globalScaleRatio !== 1) { 
 | 
            this.getGlobalScale(scaleTmp); 
 | 
            const relX = scaleTmp[0] < 0 ? -1 : 1; 
 | 
            const relY = scaleTmp[1] < 0 ? -1 : 1; 
 | 
            const sx = ((scaleTmp[0] - relX) * globalScaleRatio + relX) / scaleTmp[0] || 0; 
 | 
            const sy = ((scaleTmp[1] - relY) * globalScaleRatio + relY) / scaleTmp[1] || 0; 
 | 
  
 | 
            m[0] *= sx; 
 | 
            m[1] *= sx; 
 | 
            m[2] *= sy; 
 | 
            m[3] *= sy; 
 | 
        } 
 | 
  
 | 
        this.invTransform = this.invTransform || matrix.create(); 
 | 
        matrix.invert(this.invTransform, m); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Get computed global transform 
 | 
     * NOTE: this method will force update transform on all ancestors. 
 | 
     * Please be aware of the potential performance cost. 
 | 
     */ 
 | 
    getComputedTransform() { 
 | 
        let transformNode: Transformable = this; 
 | 
        const ancestors: Transformable[] = []; 
 | 
        while (transformNode) { 
 | 
            ancestors.push(transformNode); 
 | 
            transformNode = transformNode.parent; 
 | 
        } 
 | 
  
 | 
        // Update from topdown. 
 | 
        while (transformNode = ancestors.pop()) { 
 | 
            transformNode.updateTransform(); 
 | 
        } 
 | 
  
 | 
        return this.transform; 
 | 
    } 
 | 
  
 | 
    setLocalTransform(m: vector.VectorArray) { 
 | 
        if (!m) { 
 | 
            // TODO return or set identity? 
 | 
            return; 
 | 
        } 
 | 
        let sx = m[0] * m[0] + m[1] * m[1]; 
 | 
        let sy = m[2] * m[2] + m[3] * m[3]; 
 | 
  
 | 
        const rotation = Math.atan2(m[1], m[0]); 
 | 
  
 | 
        const shearX = Math.PI / 2 + rotation - Math.atan2(m[3], m[2]); 
 | 
        sy = Math.sqrt(sy) * Math.cos(shearX); 
 | 
        sx = Math.sqrt(sx); 
 | 
  
 | 
        this.skewX = shearX; 
 | 
        this.skewY = 0; 
 | 
        this.rotation = -rotation; 
 | 
  
 | 
        this.x = +m[4]; 
 | 
        this.y = +m[5]; 
 | 
        this.scaleX = sx; 
 | 
        this.scaleY = sy; 
 | 
  
 | 
        this.originX = 0; 
 | 
        this.originY = 0; 
 | 
    } 
 | 
    /** 
 | 
     * 分解`transform`矩阵到`position`, `rotation`, `scale` 
 | 
     */ 
 | 
    decomposeTransform() { 
 | 
        if (!this.transform) { 
 | 
            return; 
 | 
        } 
 | 
        const parent = this.parent; 
 | 
        let m = this.transform; 
 | 
        if (parent && parent.transform) { 
 | 
            // Get local transform and decompose them to position, scale, rotation 
 | 
            parent.invTransform = parent.invTransform || matrix.create(); 
 | 
            matrix.mul(tmpTransform, parent.invTransform, m); 
 | 
            m = tmpTransform; 
 | 
        } 
 | 
        const ox = this.originX; 
 | 
        const oy = this.originY; 
 | 
        if (ox || oy) { 
 | 
            originTransform[4] = ox; 
 | 
            originTransform[5] = oy; 
 | 
            matrix.mul(tmpTransform, m, originTransform); 
 | 
            tmpTransform[4] -= ox; 
 | 
            tmpTransform[5] -= oy; 
 | 
            m = tmpTransform; 
 | 
        } 
 | 
  
 | 
        this.setLocalTransform(m); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Get global scale 
 | 
     */ 
 | 
    getGlobalScale(out?: vector.VectorArray): vector.VectorArray { 
 | 
        const m = this.transform; 
 | 
        out = out || []; 
 | 
        if (!m) { 
 | 
            out[0] = 1; 
 | 
            out[1] = 1; 
 | 
            return out; 
 | 
        } 
 | 
        out[0] = Math.sqrt(m[0] * m[0] + m[1] * m[1]); 
 | 
        out[1] = Math.sqrt(m[2] * m[2] + m[3] * m[3]); 
 | 
        if (m[0] < 0) { 
 | 
            out[0] = -out[0]; 
 | 
        } 
 | 
        if (m[3] < 0) { 
 | 
            out[1] = -out[1]; 
 | 
        } 
 | 
        return out; 
 | 
    } 
 | 
    /** 
 | 
     * 变换坐标位置到 shape 的局部坐标空间 
 | 
     */ 
 | 
    transformCoordToLocal(x: number, y: number): number[] { 
 | 
        const v2 = [x, y]; 
 | 
        const invTransform = this.invTransform; 
 | 
        if (invTransform) { 
 | 
            vector.applyTransform(v2, v2, invTransform); 
 | 
        } 
 | 
        return v2; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * 变换局部坐标位置到全局坐标空间 
 | 
     */ 
 | 
    transformCoordToGlobal(x: number, y: number): number[] { 
 | 
        const v2 = [x, y]; 
 | 
        const transform = this.transform; 
 | 
        if (transform) { 
 | 
            vector.applyTransform(v2, v2, transform); 
 | 
        } 
 | 
        return v2; 
 | 
    } 
 | 
  
 | 
  
 | 
    getLineScale() { 
 | 
        const m = this.transform; 
 | 
        // Get the line scale. 
 | 
        // Determinant of `m` means how much the area is enlarged by the 
 | 
        // transformation. So its square root can be used as a scale factor 
 | 
        // for width. 
 | 
        return m && abs(m[0] - 1) > 1e-10 && abs(m[3] - 1) > 1e-10 
 | 
            ? Math.sqrt(abs(m[0] * m[3] - m[2] * m[1])) 
 | 
            : 1; 
 | 
    } 
 | 
  
 | 
    copyTransform(source: Transformable) { 
 | 
        copyTransform(this, source); 
 | 
    } 
 | 
  
 | 
  
 | 
    static getLocalTransform(target: Transformable, m?: matrix.MatrixArray): matrix.MatrixArray { 
 | 
        m = m || []; 
 | 
  
 | 
        const ox = target.originX || 0; 
 | 
        const oy = target.originY || 0; 
 | 
        const sx = target.scaleX; 
 | 
        const sy = target.scaleY; 
 | 
        const ax = target.anchorX; 
 | 
        const ay = target.anchorY; 
 | 
        const rotation = target.rotation || 0; 
 | 
        const x = target.x; 
 | 
        const y = target.y; 
 | 
        const skewX = target.skewX ? Math.tan(target.skewX) : 0; 
 | 
        // TODO: zrender use different hand in coordinate system and y axis is inversed. 
 | 
        const skewY = target.skewY ? Math.tan(-target.skewY) : 0; 
 | 
  
 | 
        // The order of transform (-anchor * -origin * scale * skew * rotate * origin * translate). 
 | 
        // We merge (-origin * scale * skew) into one. Also did identity in these operations. 
 | 
        // origin 
 | 
        if (ox || oy || ax || ay) { 
 | 
            const dx = ox + ax; 
 | 
            const dy = oy + ay; 
 | 
            m[4] = -dx * sx - skewX * dy * sy; 
 | 
            m[5] = -dy * sy - skewY * dx * sx; 
 | 
        } 
 | 
        else { 
 | 
            m[4] = m[5] = 0; 
 | 
        } 
 | 
        // scale 
 | 
        m[0] = sx; 
 | 
        m[3] = sy; 
 | 
        // skew 
 | 
        m[1] = skewY * sx; 
 | 
        m[2] = skewX * sy; 
 | 
  
 | 
        // Apply rotation 
 | 
        rotation && matrix.rotate(m, m, rotation); 
 | 
  
 | 
        // Translate back from origin and apply translation 
 | 
        m[4] += ox + x; 
 | 
        m[5] += oy + y; 
 | 
  
 | 
        return m; 
 | 
    } 
 | 
  
 | 
    private static initDefaultProps = (function () { 
 | 
        const proto = Transformable.prototype; 
 | 
        proto.scaleX = 
 | 
        proto.scaleY = 
 | 
        proto.globalScaleRatio = 1; 
 | 
        proto.x = 
 | 
        proto.y = 
 | 
        proto.originX = 
 | 
        proto.originY = 
 | 
        proto.skewX = 
 | 
        proto.skewY = 
 | 
        proto.rotation = 
 | 
        proto.anchorX = 
 | 
        proto.anchorY = 0; 
 | 
    })() 
 | 
}; 
 | 
  
 | 
export const TRANSFORMABLE_PROPS = [ 
 | 
    'x', 'y', 'originX', 'originY', 'anchorX', 'anchorY', 'rotation', 'scaleX', 'scaleY', 'skewX', 'skewY' 
 | 
] as const; 
 | 
  
 | 
export type TransformProp = (typeof TRANSFORMABLE_PROPS)[number] 
 | 
  
 | 
export function copyTransform( 
 | 
    target: Partial<Pick<Transformable, TransformProp>>, 
 | 
    source: Pick<Transformable, TransformProp> 
 | 
) { 
 | 
    for (let i = 0; i < TRANSFORMABLE_PROPS.length; i++) { 
 | 
        const propName = TRANSFORMABLE_PROPS[i]; 
 | 
        target[propName] = source[propName]; 
 | 
    } 
 | 
} 
 | 
  
 | 
export default Transformable; 
 |