react-native-redash
Version:
Utility library for React Native Reanimated
337 lines (311 loc) • 7.31 kB
text/typescript
/* 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,
};