react-native-redash
Version:
Utility library for React Native Reanimated
384 lines (349 loc) • 8.21 kB
text/typescript
/* eslint-disable prefer-destructuring */
export type Vec2 = readonly [number, number];
type Vec3 = readonly [number, number, number];
export type Matrix3 = readonly [
number,
number,
number,
number,
number,
number,
number,
number,
number,
];
export interface TransformProp {
transform: Transforms2d;
}
type Transformations = {
translateX: number;
translateY: number;
scale: number;
skewX: string;
skewY: string;
scaleX: number;
scaleY: number;
rotateZ: string;
rotate: string;
};
export type Transforms2d = (
| Pick<Transformations, "translateX">
| Pick<Transformations, "translateY">
| Pick<Transformations, "scale">
| Pick<Transformations, "scaleX">
| Pick<Transformations, "scaleY">
| Pick<Transformations, "skewX">
| Pick<Transformations, "skewY">
| Pick<Transformations, "rotate">
| Pick<Transformations, "rotateZ">
)[];
/**
* @worklet
*/
export const parseAngle = (angle: string) => {
"worklet";
if (angle.endsWith("deg")) {
return parseFloat(angle) * (Math.PI / 180);
}
return parseFloat(angle);
};
/**
* @worklet
*/
export const isTranslateX = (
transform: Transforms2d[0]
): transform is Pick<Transformations, "translateX"> => {
"worklet";
return Object.keys(transform).indexOf("translateX") !== -1;
};
/**
* @worklet
*/
export const isTranslateY = (
transform: Transforms2d[0]
): transform is Pick<Transformations, "translateY"> => {
"worklet";
return Object.keys(transform).indexOf("translateY") !== -1;
};
/**
* @worklet
*/
export const isScale = (
transform: Transforms2d[0]
): transform is Pick<Transformations, "scale"> => {
"worklet";
return Object.keys(transform).indexOf("scale") !== -1;
};
/**
* @worklet
*/
export const isScaleX = (
transform: Transforms2d[0]
): transform is Pick<Transformations, "scaleX"> => {
"worklet";
return Object.keys(transform).indexOf("scaleX") !== -1;
};
/**
* @worklet
*/
export const isScaleY = (
transform: Transforms2d[0]
): transform is Pick<Transformations, "scaleY"> => {
"worklet";
return Object.keys(transform).indexOf("scaleY") !== -1;
};
/**
* @worklet
*/
export const isSkewX = (
transform: Transforms2d[0]
): transform is Pick<Transformations, "skewX"> => {
"worklet";
return Object.keys(transform).indexOf("skewX") !== -1;
};
/**
* @worklet
*/
export const isSkewY = (
transform: Transforms2d[0]
): transform is Pick<Transformations, "skewY"> => {
"worklet";
return Object.keys(transform).indexOf("skewY") !== -1;
};
/**
* @worklet
*/
export const isRotate = (
transform: Transforms2d[0]
): transform is Pick<Transformations, "rotate"> => {
"worklet";
return Object.keys(transform).indexOf("rotate") !== -1;
};
/**
* @worklet
*/
export const isRotateZ = (
transform: Transforms2d[0]
): transform is Pick<Transformations, "rotateZ"> => {
"worklet";
return Object.keys(transform).indexOf("rotateZ") !== -1;
};
/**
* @worklet
*/
const exhaustiveCheck = (a: never): never => {
"worklet";
throw new Error(`Unexhaustive handling for ${a}`);
};
export const identity3: Matrix3 = [1, 0, 0, 0, 1, 0, 0, 0, 1];
/**
* @worklet
*/
const translateXMatrix = (x: number): Matrix3 => {
"worklet";
return [1, 0, x, 0, 1, 0, 0, 0, 1];
};
/**
* @worklet
*/
const translateYMatrix = (y: number): Matrix3 => {
"worklet";
return [1, 0, 0, 0, 1, y, 0, 0, 1];
};
/**
* @worklet
*/
const scaleMatrix = (s: number): Matrix3 => {
"worklet";
return [s, 0, 0, 0, s, 0, 0, 0, 1];
};
/**
* @worklet
*/
const scaleXMatrix = (s: number): Matrix3 => {
"worklet";
return [s, 0, 0, 0, 1, 0, 0, 0, 1];
};
/**
* @worklet
*/
const scaleYMatrix = (s: number): Matrix3 => {
"worklet";
return [1, 0, 0, 0, s, 0, 0, 0, 1];
};
/**
* @worklet
*/
const skewXMatrix = (s: number): Matrix3 => {
"worklet";
return [1, Math.tan(s), 0, 0, 1, 0, 0, 0, 1];
};
/**
* @worklet
*/
const skewYMatrix = (s: number): Matrix3 => {
"worklet";
return [1, 0, 0, Math.tan(s), 1, 0, 0, 0, 1];
};
/**
* @worklet
*/
const rotateZMatrix = (r: number): Matrix3 => {
"worklet";
return [
Math.cos(r),
-1 * Math.sin(r),
0,
Math.sin(r),
Math.cos(r),
0,
0,
0,
1,
];
};
/**
* @worklet
*/
export const dot3 = (row: Vec3, col: Vec3) => {
"worklet";
return row[0] * col[0] + row[1] * col[1] + row[2] * col[2];
};
/**
* @worklet
*/
export const matrixVecMul3 = (m: Matrix3, v: Vec3) => {
"worklet";
return [
dot3([m[0], m[1], m[2]], v),
dot3([m[3], m[4], m[5]], v),
dot3([m[6], m[7], m[8]], v),
] as const;
};
/**
* @worklet
*/
export const mapPoint = (m: Matrix3, v: Vec2) => {
"worklet";
const r = matrixVecMul3(m, [v[0], v[1], 1]);
return [r[0] / r[2], r[1] / r[2]] as const;
};
/**
* @worklet
*/
export const multiply3 = (m1: Matrix3, m2: Matrix3) => {
"worklet";
const row0 = [m1[0], m1[1], m1[2]] as const;
const row1 = [m1[3], m1[4], m1[5]] as const;
const row2 = [m1[6], m1[7], m1[8]] as const;
const col0 = [m2[0], m2[3 + 0], m2[6 + 0]] as const;
const col1 = [m2[1], m2[3 + 1], m2[6 + 1]] as const;
const col2 = [m2[2], m2[3 + 2], m2[6 + 2]] as const;
return [
dot3(row0, col0),
dot3(row0, col1),
dot3(row0, col2),
dot3(row1, col0),
dot3(row1, col1),
dot3(row1, col2),
dot3(row2, col0),
dot3(row2, col1),
dot3(row2, col2),
] as const;
};
/**
* @worklet
*/
const serializeToSVGMatrix = (m: Matrix3) => {
"worklet";
return `matrix(${m[0]}, ${m[3 + 0]}, ${m[1]}, ${m[3 + 1]}, ${m[2]}, ${
m[3 + 2]
})`;
};
/**
* @worklet
*/
export const svgMatrix = (transforms: Transforms2d) => {
"worklet";
return serializeToSVGMatrix(processTransform2d(transforms));
};
/**
* @worklet
*/
export const processTransform2d = (transforms: Transforms2d) => {
"worklet";
return transforms.reduce((acc, transform) => {
if (isTranslateX(transform)) {
return multiply3(acc, translateXMatrix(transform.translateX));
}
if (isTranslateY(transform)) {
return multiply3(acc, translateYMatrix(transform.translateY));
}
if (isScale(transform)) {
return multiply3(acc, scaleMatrix(transform.scale));
}
if (isScaleX(transform)) {
return multiply3(acc, scaleXMatrix(transform.scaleX));
}
if (isScaleY(transform)) {
return multiply3(acc, scaleYMatrix(transform.scaleY));
}
if (isSkewX(transform)) {
return multiply3(acc, skewXMatrix(parseAngle(transform.skewX)));
}
if (isSkewY(transform)) {
return multiply3(acc, skewYMatrix(parseAngle(transform.skewY)));
}
if (isRotate(transform)) {
return multiply3(acc, rotateZMatrix(parseAngle(transform.rotate)));
}
if (isRotateZ(transform)) {
return multiply3(acc, rotateZMatrix(parseAngle(transform.rotateZ)));
}
return exhaustiveCheck(transform);
}, identity3);
};
/**
* @worklet
*/
const isMatrix3 = (arg: Matrix3 | Transforms2d): arg is Matrix3 => {
"worklet";
return arg.length === 9 && arg[0] instanceof Array;
};
// https://math.stackexchange.com/questions/13150/extracting-rotation-scale-values-from-2d-transformation-matrix
// https://math.stackexchange.com/questions/296794/finding-the-transform-matrix-from-4-projected-points-with-javascript
// https://franklinta.com/2014/09/08/computing-css-matrix3d-transforms/
// http://jsfiddle.net/dFrHS/1/
/**
* @worklet
*/
export const decompose2d = (arg: Matrix3 | Transforms2d) => {
"worklet";
const m = isMatrix3(arg) ? arg : processTransform2d(arg);
const a = m[0];
const b = m[3 + 0];
const c = m[1];
const d = m[3 + 1];
const translateX = m[2];
const translateY = m[3 + 2];
const E = (a + d) / 2;
const F = (a - d) / 2;
const G = (c + b) / 2;
const H = (c - b) / 2;
const Q = Math.sqrt(Math.pow(E, 2) + Math.pow(H, 2));
const R = Math.sqrt(Math.pow(F, 2) + Math.pow(G, 2));
const scaleX = Q + R;
const scaleY = Q - R;
const a1 = Math.atan2(G, F);
const a2 = Math.atan2(H, E);
const theta = (a2 - a1) / 2;
const phi = (a2 + a1) / 2;
return [
{ translateX },
{ translateY },
{ rotateZ: -1 * theta },
{ scaleX },
{ scaleY },
{ rotateZ: -1 * phi },
] as const;
};