UNPKG

react-native-redash

Version:
337 lines (311 loc) 7.31 kB
/* eslint-disable prefer-destructuring */ type Vec2 = readonly [number, number]; type Vec3 = readonly [number, number, number]; type Vec4 = readonly [number, number, number, number]; export type Matrix4 = readonly [ number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, ]; type Transform3dName = | "translateX" | "translateY" | "translateZ" | "translate" | "scale" | "scaleX" | "scaleY" | "skewX" | "skewY" | "rotateZ" | "rotate" | "perspective" | "rotateX" | "rotateY" | "rotateZ" | "matrix"; type Transformations = { [Name in Transform3dName]: Name extends "matrix" ? Matrix4 : Name extends "translate" ? Vec3 | Vec2 : number; }; type Transform3d = | Pick<Transformations, "translateX"> | Pick<Transformations, "translateY"> | Pick<Transformations, "translateZ"> | Pick<Transformations, "translate"> | Pick<Transformations, "scale"> | Pick<Transformations, "scaleX"> | Pick<Transformations, "scaleY"> | Pick<Transformations, "skewX"> | Pick<Transformations, "skewY"> | Pick<Transformations, "perspective"> | Pick<Transformations, "rotateX"> | Pick<Transformations, "rotateY"> | Pick<Transformations, "rotateZ"> | Pick<Transformations, "rotate"> | Pick<Transformations, "matrix">; export type Transforms3d = Transform3d[]; const exhaustiveCheck = (a: never): never => { "worklet"; throw new Error(`Unexhaustive handling for ${a}`); }; /** * @worklet */ export const identity4: Matrix4 = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, ]; /** * @worklet */ export const translate = (x: number, y: number, z: number): Matrix4 => { "worklet"; return [1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1]; }; /** * @worklet */ const scale = (sx: number, sy: number, sz: number): Matrix4 => { "worklet"; return [sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1]; }; /** * @worklet */ export const skewX = (s: number): Matrix4 => { "worklet"; return [1, 0, 0, 0, Math.tan(s), 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; }; /** * @worklet */ export const skewY = (s: number): Matrix4 => { "worklet"; return [1, Math.tan(s), 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; }; /** * @worklet */ export const perspective = (p: number): Matrix4 => { "worklet"; return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -1 / p, 1]; }; const normalizeVec = (vec: Vec3): Vec3 => { "worklet"; const x = vec[0]; const y = vec[1]; const z = vec[2]; const length = Math.sqrt(x * x + y * y + z * z); // Check for zero length to avoid division by zero if (length === 0) { return [0, 0, 0]; } return [x / length, y / length, z / length]; }; const rotatedUnitSinCos = ( axisVec: Vec3, sinAngle: number, cosAngle: number ): Matrix4 => { "worklet"; const x = axisVec[0]; const y = axisVec[1]; const z = axisVec[2]; const c = cosAngle; const s = sinAngle; const t = 1 - c; return [ t * x * x + c, t * x * y - s * z, t * x * z + s * y, 0, t * x * y + s * z, t * y * y + c, t * y * z - s * x, 0, t * x * z - s * y, t * y * z + s * x, t * z * z + c, 0, 0, 0, 0, 1, ]; }; /** * @worklet */ export const rotate = (axis: Vec3, value: number) => { "worklet"; return rotatedUnitSinCos( normalizeVec(axis), Math.sin(value), Math.cos(value) ); }; /** * @worklet */ export const matrixVecMul4 = (m: Matrix4, v: Vec4): Vec4 => { "worklet"; return [ m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3] * v[3], m[4] * v[0] + m[5] * v[1] + m[6] * v[2] + m[7] * v[3], m[8] * v[0] + m[9] * v[1] + m[10] * v[2] + m[11] * v[3], m[12] * v[0] + m[13] * v[1] + m[14] * v[2] + m[15] * v[3], ]; }; /** * @worklet */ export const mapPoint3d = (m: Matrix4, v: Vec3) => { "worklet"; const r = matrixVecMul4(m, [v[0], v[1], v[2], 1]); return [r[0] / r[3], r[1] / r[3], r[2] / r[3]] as const; }; /** * @worklet */ export const multiply4 = (a: Matrix4, b: Matrix4): Matrix4 => { "worklet"; const result = new Array(16).fill(0); for (let i = 0; i < 4; i++) { for (let j = 0; j < 4; j++) { result[i * 4 + j] = a[i * 4] * b[j] + a[i * 4 + 1] * b[j + 4] + a[i * 4 + 2] * b[j + 8] + a[i * 4 + 3] * b[j + 12]; } } return result as unknown as Matrix4; }; /** * @worklet */ export const toMatrix3 = (m: Matrix4) => { "worklet"; return [m[0], m[1], m[2], m[4], m[5], m[6], m[3], m[7], 1]; }; /** * @worklet */ export const processTransform3d = (transforms: Transforms3d) => { "worklet"; return transforms.reduce((acc, val) => { const key = Object.keys(val)[0] as Transform3dName; const transform = val as Pick<Transformations, typeof key>; if (key === "translateX") { const value = transform[key]; return multiply4(acc, translate(value, 0, 0)); } if (key === "translate") { const translateValue = transform[key]; const x = translateValue[0]; const y = translateValue[1]; const z = translateValue[2] ?? 0; return multiply4(acc, translate(x, y, z)); } if (key === "translateY") { const value = transform[key]; return multiply4(acc, translate(0, value, 0)); } if (key === "translateZ") { const value = transform[key]; return multiply4(acc, translate(0, 0, value)); } if (key === "scale") { const value = transform[key]; return multiply4(acc, scale(value, value, 1)); } if (key === "scaleX") { const value = transform[key]; return multiply4(acc, scale(value, 1, 1)); } if (key === "scaleY") { const value = transform[key]; return multiply4(acc, scale(1, value, 1)); } if (key === "skewX") { const value = transform[key]; return multiply4(acc, skewX(value)); } if (key === "skewY") { const value = transform[key]; return multiply4(acc, skewY(value)); } if (key === "rotateX") { const value = transform[key]; return multiply4(acc, rotate([1, 0, 0], value)); } if (key === "rotateY") { const value = transform[key]; return multiply4(acc, rotate([0, 1, 0], value)); } if (key === "perspective") { const value = transform[key]; return multiply4(acc, perspective(value)); } if (key === "rotate" || key === "rotateZ") { const value = transform[key]; return multiply4(acc, rotate([0, 0, 1], value)); } if (key === "matrix") { const value = transform[key]; return multiply4(acc, value); } return exhaustiveCheck(key); }, identity4); }; /** * @worklet */ export const concat4 = (m: Matrix4, transform: Transforms3d) => { "worklet"; return multiply4(m, processTransform3d(transform)); }; /** * @worklet */ export const rotateX = (value: number) => { "worklet"; return rotate([1, 0, 0], value); }; /** * @worklet */ export const rotateY = (value: number) => { "worklet"; return rotate([0, 1, 0], value); }; /** * @worklet */ export const rotateZ = (value: number) => { "worklet"; return rotate([0, 0, 1], value); }; export const Matrix4 = { translate, scale, rotateX, rotateY, rotateZ, };