UNPKG

@leafer/math

Version:
367 lines (281 loc) 10.4 kB
import { IMatrixData, IPointData, ILayoutData, IMatrixWithLayoutData, IMatrixWithOptionScaleData, IScaleData, IMatrixWithScaleData } from '@leafer/interface' import { isUndefined } from '@leafer/data' import { MathHelper, OneRadian, PI_2, getBoundsData, getMatrixData } from './MathHelper' const { sin, cos, acos, sqrt } = Math const { float } = MathHelper const tempPoint = {} as IPointData function getWorld(): IMatrixWithLayoutData { return { ...getMatrixData(), ...getBoundsData(), scaleX: 1, scaleY: 1, rotation: 0, skewX: 0, skewY: 0 } } export const MatrixHelper = { defaultMatrix: getMatrixData(), defaultWorld: getWorld(), tempMatrix: {} as IMatrixData, set(t: IMatrixData, a = 1, b = 0, c = 0, d = 1, e = 0, f = 0): void { t.a = a t.b = b t.c = c t.d = d t.e = e t.f = f }, get: getMatrixData, getWorld, copy(t: IMatrixData, matrix: IMatrixData): void { t.a = matrix.a t.b = matrix.b t.c = matrix.c t.d = matrix.d t.e = matrix.e t.f = matrix.f }, translate(t: IMatrixData, x: number, y: number): void { t.e += x t.f += y }, translateInner(t: IMatrixData, x: number, y: number, hasOrigin?: boolean): void { t.e += t.a * x + t.c * y t.f += t.b * x + t.d * y if (hasOrigin) t.e -= x, t.f -= y }, scale(t: IMatrixData, scaleX: number, scaleY: number = scaleX): void { t.a *= scaleX t.b *= scaleX t.c *= scaleY t.d *= scaleY }, scaleOfOuter(t: IMatrixData, origin: IPointData, scaleX: number, scaleY?: number): void { M.toInnerPoint(t, origin, tempPoint) M.scaleOfInner(t, tempPoint, scaleX, scaleY) }, scaleOfInner(t: IMatrixData, origin: IPointData, scaleX: number, scaleY: number = scaleX): void { M.translateInner(t, origin.x, origin.y) M.scale(t, scaleX, scaleY) M.translateInner(t, -origin.x, -origin.y) }, rotate(t: IMatrixData, rotation: number): void { const { a, b, c, d } = t rotation *= OneRadian const cosR = cos(rotation) const sinR = sin(rotation) t.a = a * cosR - b * sinR t.b = a * sinR + b * cosR t.c = c * cosR - d * sinR t.d = c * sinR + d * cosR }, rotateOfOuter(t: IMatrixData, origin: IPointData, rotation: number): void { M.toInnerPoint(t, origin, tempPoint) M.rotateOfInner(t, tempPoint, rotation) }, rotateOfInner(t: IMatrixData, origin: IPointData, rotation: number): void { M.translateInner(t, origin.x, origin.y) M.rotate(t, rotation) M.translateInner(t, -origin.x, -origin.y) }, skew(t: IMatrixData, skewX: number, skewY?: number): void { const { a, b, c, d } = t if (skewY) { skewY *= OneRadian t.a = a + c * skewY t.b = b + d * skewY } if (skewX) { skewX *= OneRadian t.c = c + a * skewX t.d = d + b * skewX } }, skewOfOuter(t: IMatrixData, origin: IPointData, skewX: number, skewY?: number): void { M.toInnerPoint(t, origin, tempPoint) M.skewOfInner(t, tempPoint, skewX, skewY) }, skewOfInner(t: IMatrixData, origin: IPointData, skewX: number, skewY: number = 0): void { M.translateInner(t, origin.x, origin.y) M.skew(t, skewX, skewY) M.translateInner(t, -origin.x, -origin.y) }, multiply(t: IMatrixData, child: IMatrixData): void { const { a, b, c, d, e, f } = t t.a = child.a * a + child.b * c t.b = child.a * b + child.b * d t.c = child.c * a + child.d * c t.d = child.c * b + child.d * d t.e = child.e * a + child.f * c + e t.f = child.e * b + child.f * d + f }, multiplyParent(t: IMatrixWithOptionScaleData, parent: IMatrixWithOptionScaleData, to?: IMatrixWithOptionScaleData, abcdChanged?: boolean | number, childScaleData?: IScaleData): void { // = transform const { e, f } = t to || (to = t) if (isUndefined(abcdChanged)) abcdChanged = t.a !== 1 || t.b || t.c || t.d !== 1 if (abcdChanged) { const { a, b, c, d } = t to.a = a * parent.a + b * parent.c to.b = a * parent.b + b * parent.d to.c = c * parent.a + d * parent.c to.d = c * parent.b + d * parent.d if (childScaleData) { to.scaleX = parent.scaleX * childScaleData.scaleX to.scaleY = parent.scaleY * childScaleData.scaleY } } else { to.a = parent.a to.b = parent.b to.c = parent.c to.d = parent.d if (childScaleData) { to.scaleX = parent.scaleX to.scaleY = parent.scaleY } } to.e = e * parent.a + f * parent.c + parent.e to.f = e * parent.b + f * parent.d + parent.f }, divide(t: IMatrixData, child: IMatrixData): void { M.multiply(t, M.tempInvert(child)) }, divideParent(t: IMatrixData, parent: IMatrixData): void { M.multiplyParent(t, M.tempInvert(parent)) }, tempInvert(t: IMatrixData): IMatrixData { const { tempMatrix } = M M.copy(tempMatrix, t) M.invert(tempMatrix) return tempMatrix }, invert(t: IMatrixData): void { const { a, b, c, d, e, f } = t if (!b && !c) { if (a === 1 && d === 1) { t.e = -e t.f = -f } else { const s = 1 / (a * d) t.a = d * s t.d = a * s t.e = -e * d * s t.f = -f * a * s } } else { const s = 1 / (a * d - b * c) t.a = d * s t.b = -b * s t.c = -c * s t.d = a * s t.e = -(e * d - f * c) * s t.f = -(f * a - e * b) * s } }, toOuterPoint(t: IMatrixData, inner: IPointData, to?: IPointData, distance?: boolean): void { const { x, y } = inner // outer to || (to = inner) to.x = x * t.a + y * t.c to.y = x * t.b + y * t.d if (!distance) { to.x += t.e to.y += t.f } }, toInnerPoint(t: IMatrixData, outer: IPointData, to?: IPointData, distance?: boolean): void { const { a, b, c, d } = t const s = 1 / (a * d - b * c) const { x, y } = outer // inner to || (to = outer) to.x = (x * d - y * c) * s to.y = (y * a - x * b) * s if (!distance) { const { e, f } = t to.x -= (e * d - f * c) * s to.y -= (f * a - e * b) * s } }, setLayout(t: IMatrixData, layout: ILayoutData, origin?: IPointData, around?: IPointData, bcChanged?: boolean | number): void { const { x, y, scaleX, scaleY } = layout if (isUndefined(bcChanged)) bcChanged = layout.rotation || layout.skewX || layout.skewY if (bcChanged) { const { rotation, skewX, skewY } = layout const r = rotation * OneRadian const cosR = cos(r) const sinR = sin(r) if (skewX || skewY) { // rotate -> skew -> scale const sx = skewX * OneRadian const sy = skewY * OneRadian t.a = (cosR + sy * -sinR) * scaleX t.b = (sinR + sy * cosR) * scaleX t.c = (-sinR + sx * cosR) * scaleY t.d = (cosR + sx * sinR) * scaleY } else { // rotate -> scale t.a = cosR * scaleX t.b = sinR * scaleX t.c = -sinR * scaleY t.d = cosR * scaleY } } else { t.a = scaleX t.b = 0 t.c = 0 t.d = scaleY } // translate t.e = x t.f = y if (origin = origin || around) M.translateInner(t, -origin.x, -origin.y, !around) }, getLayout(t: IMatrixData, origin?: IPointData, around?: IPointData, firstSkewY?: boolean): ILayoutData { const { a, b, c, d, e, f } = t let x = e, y = f, scaleX: number, scaleY: number, rotation: number, skewX: number, skewY: number if (b || c) { const s = a * d - b * c if (c && !firstSkewY) { scaleX = sqrt(a * a + b * b) scaleY = s / scaleX const cosR = a / scaleX rotation = b > 0 ? acos(cosR) : -acos(cosR) } else { scaleY = sqrt(c * c + d * d) scaleX = s / scaleY const cosR = c / scaleY rotation = PI_2 - (d > 0 ? acos(-cosR) : -acos(cosR)) } const cosR = float(cos(rotation)) // when -90 / 90 is 0 const sinR = sin(rotation) scaleX = float(scaleX), scaleY = float(scaleY) skewX = cosR ? float((c / scaleY + sinR) / cosR / OneRadian, 9) : 0 skewY = cosR ? float((b / scaleX - sinR) / cosR / OneRadian, 9) : 0 rotation = float(rotation / OneRadian) } else { scaleX = a scaleY = d rotation = skewX = skewY = 0 } if (origin = around || origin) { x += origin.x * a + origin.y * c y += origin.x * b + origin.y * d if (!around) x -= origin.x, y -= origin.y } return { x, y, scaleX, scaleY, rotation, skewX, skewY } }, withScale(t: IMatrixData, scaleX?: number, scaleY = scaleX): IMatrixWithScaleData { const world = t as unknown as IMatrixWithScaleData if (!scaleX || !scaleY) { const { a, b, c, d } = t if (b || c) { scaleX = sqrt(a * a + b * b) scaleY = (a * d - b * c) / scaleX } else { scaleX = a scaleY = d } } world.scaleX = scaleX world.scaleY = scaleY return world }, reset(t: IMatrixData): void { M.set(t) } } const M = MatrixHelper