Skip to content

Commit 49512db

Browse files
committed
convert library to TS, refactor Transform2d and Transform3d to functional components with shared hooks
1 parent 298f54a commit 49512db

8 files changed

+595
-0
lines changed

src/Transform2d.tsx

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import PropTypes from 'prop-types';
2+
import { mat2d, vec2 } from 'gl-matrix';
3+
4+
import { vec2Obj, vec2GlMatrix, mat2dGlMatrix } from './constants';
5+
import { setVec2FromProp } from './utils';
6+
import { useFactoryRef } from './useFactoryRef';
7+
import { useRender } from './useRender';
8+
import type {
9+
MultiplicationOrder,
10+
TransformChildren,
11+
Vec2Object,
12+
} from './types';
13+
14+
const propTypes = {
15+
parentMatrixWorld: mat2dGlMatrix,
16+
multiplicationOrder: PropTypes.oneOf(['PRE', 'POST']),
17+
translate: PropTypes.oneOfType([vec2GlMatrix, vec2Obj]),
18+
scale: PropTypes.oneOfType([vec2GlMatrix, vec2Obj, PropTypes.number]),
19+
rotate: PropTypes.number,
20+
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
21+
};
22+
23+
export type Transform2dProps = {
24+
children: TransformChildren<mat2d>;
25+
parentMatrixWorld?: mat2d;
26+
multiplicationOrder?: MultiplicationOrder;
27+
translate?: vec2 | Vec2Object;
28+
scale?: vec2 | Vec2Object | number;
29+
rotate?: number;
30+
};
31+
32+
export type Apply2dTransformsParams = {
33+
parentMatrixWorld: mat2d;
34+
matrix: mat2d;
35+
matrixWorld: mat2d;
36+
vTranslation: vec2;
37+
vScale: vec2;
38+
multiplicationOrder: MultiplicationOrder;
39+
translate?: vec2 | Vec2Object;
40+
scale?: vec2 | Vec2Object | number;
41+
rotate?: number;
42+
};
43+
44+
// TODO: somehow implement safe memoization with some sort of equality check?
45+
// considering mutable objects
46+
export function apply2dTransforms({
47+
parentMatrixWorld,
48+
matrix,
49+
matrixWorld,
50+
vTranslation,
51+
vScale,
52+
multiplicationOrder,
53+
translate,
54+
scale,
55+
rotate,
56+
}: Apply2dTransformsParams) {
57+
const theta = typeof rotate === 'number' ? rotate : 0;
58+
59+
mat2d.identity(matrix);
60+
61+
setVec2FromProp(vTranslation, translate);
62+
setVec2FromProp(vScale, scale, 1);
63+
64+
// T * R * S
65+
mat2d.translate(matrix, matrix, vTranslation);
66+
mat2d.rotate(matrix, matrix, theta);
67+
mat2d.scale(matrix, matrix, vScale);
68+
69+
if (multiplicationOrder === 'PRE') {
70+
mat2d.multiply(matrixWorld, matrix, parentMatrixWorld);
71+
} else {
72+
mat2d.multiply(matrixWorld, parentMatrixWorld, matrix);
73+
}
74+
}
75+
76+
const Transform2d = ({
77+
children,
78+
parentMatrixWorld,
79+
translate,
80+
scale,
81+
rotate,
82+
multiplicationOrder = 'POST',
83+
}: Transform2dProps) => {
84+
const safeParentMatrixWorld = useFactoryRef<mat2d>(
85+
() => parentMatrixWorld || mat2d.create(),
86+
);
87+
const matrix = useFactoryRef<mat2d>(() => mat2d.create());
88+
const matrixWorld = useFactoryRef<mat2d>(() => mat2d.create());
89+
const vTranslation = useFactoryRef<vec2>(() => vec2.create());
90+
const vScale = useFactoryRef<vec2>(() => vec2.create());
91+
92+
apply2dTransforms({
93+
parentMatrixWorld: safeParentMatrixWorld.current,
94+
matrix: matrix.current,
95+
matrixWorld: matrixWorld.current,
96+
vTranslation: vTranslation.current,
97+
vScale: vScale.current,
98+
multiplicationOrder,
99+
translate,
100+
scale,
101+
rotate,
102+
});
103+
104+
const render = useRender<mat2d>({
105+
cssMatrixPrefix: 'matrix',
106+
matrixWorld,
107+
multiplicationOrder,
108+
});
109+
110+
return render(children);
111+
};
112+
113+
Transform2d.propTypes = propTypes;
114+
115+
export default Transform2d;

src/Transform3d.tsx

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import PropTypes from 'prop-types';
2+
import { mat4, vec3 } from 'gl-matrix';
3+
4+
import { vec3Obj, vec3GlMatrix, mat4GlMatrix } from './constants';
5+
import { setVec3FromProp } from './utils';
6+
import { useFactoryRef } from './useFactoryRef';
7+
import { useRender } from './useRender';
8+
import type {
9+
MultiplicationOrder,
10+
TransformChildren,
11+
Vec3Object,
12+
} from './types';
13+
14+
const propTypes = {
15+
parentMatrixWorld: mat4GlMatrix,
16+
multiplicationOrder: PropTypes.oneOf(['PRE', 'POST']),
17+
translate: PropTypes.oneOfType([vec3GlMatrix, vec3Obj]),
18+
scale: PropTypes.oneOfType([vec3GlMatrix, vec3Obj, PropTypes.number]),
19+
rotate: PropTypes.number,
20+
rotateAxis: PropTypes.oneOfType([vec3GlMatrix, vec3Obj]),
21+
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
22+
};
23+
24+
export type Transform3dProps = {
25+
children: TransformChildren<mat4>;
26+
parentMatrixWorld?: mat4;
27+
multiplicationOrder?: MultiplicationOrder;
28+
translate?: vec3 | Vec3Object;
29+
scale?: vec3 | Vec3Object | number;
30+
rotate?: number;
31+
rotateAxis?: vec3 | Vec3Object;
32+
};
33+
34+
export type Apply3dTransformsParams = {
35+
parentMatrixWorld: mat4;
36+
matrix: mat4;
37+
matrixWorld: mat4;
38+
vTranslation: vec3;
39+
vScale: vec3;
40+
vRotationAxis: vec3;
41+
multiplicationOrder: MultiplicationOrder;
42+
translate?: vec3 | Vec3Object;
43+
scale?: vec3 | Vec3Object | number;
44+
rotate?: number;
45+
rotateAxis?: vec3 | Vec3Object;
46+
};
47+
48+
// TODO: somehow implement safe memoization with some sort of equality check?
49+
// considering mutable objects
50+
export function apply3dTransforms({
51+
parentMatrixWorld,
52+
matrix,
53+
matrixWorld,
54+
vTranslation,
55+
vScale,
56+
vRotationAxis,
57+
multiplicationOrder,
58+
translate,
59+
scale,
60+
rotate,
61+
rotateAxis,
62+
}: Apply3dTransformsParams) {
63+
const theta = typeof rotate === 'number' ? rotate : 0;
64+
65+
mat4.identity(matrix);
66+
67+
setVec3FromProp(vTranslation, translate);
68+
setVec3FromProp(vScale, scale, 1, 1, 1);
69+
setVec3FromProp(vRotationAxis, rotateAxis, 0, 0, 1);
70+
71+
// T * R * S
72+
mat4.translate(matrix, matrix, vTranslation);
73+
mat4.rotate(matrix, matrix, theta, vRotationAxis);
74+
mat4.scale(matrix, matrix, vScale);
75+
76+
if (multiplicationOrder === 'PRE') {
77+
mat4.multiply(matrixWorld, matrix, parentMatrixWorld);
78+
} else {
79+
mat4.multiply(matrixWorld, parentMatrixWorld, matrix);
80+
}
81+
}
82+
83+
const Transform3d = ({
84+
children,
85+
parentMatrixWorld,
86+
translate,
87+
scale,
88+
rotate,
89+
rotateAxis,
90+
multiplicationOrder = 'POST',
91+
}: Transform3dProps) => {
92+
const safeParentMatrixWorld = useFactoryRef<mat4>(
93+
() => parentMatrixWorld || mat4.create(),
94+
);
95+
const matrix = useFactoryRef<mat4>(() => mat4.create());
96+
const matrixWorld = useFactoryRef<mat4>(() => mat4.create());
97+
const vTranslation = useFactoryRef<vec3>(() => vec3.create());
98+
const vScale = useFactoryRef<vec3>(() => vec3.create());
99+
const vRotationAxis = useFactoryRef<vec3>(() => vec3.create());
100+
101+
apply3dTransforms({
102+
parentMatrixWorld: safeParentMatrixWorld.current,
103+
matrix: matrix.current,
104+
matrixWorld: matrixWorld.current,
105+
vTranslation: vTranslation.current,
106+
vScale: vScale.current,
107+
vRotationAxis: vRotationAxis.current,
108+
multiplicationOrder,
109+
translate,
110+
scale,
111+
rotate,
112+
rotateAxis,
113+
});
114+
115+
const render = useRender<mat4>({
116+
cssMatrixPrefix: 'matrix3d',
117+
matrixWorld,
118+
multiplicationOrder,
119+
});
120+
121+
return render(children);
122+
};
123+
124+
Transform3d.propTypes = propTypes;
125+
126+
export default Transform3d;

src/constants.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import {
2+
isVec2,
3+
isVec3,
4+
isMat2d,
5+
isMat4,
6+
isVec2Object,
7+
isVec3Object,
8+
} from './utils';
9+
10+
type PropTypes = { [key: string]: any };
11+
12+
export const vec2Obj = <P extends PropTypes>(
13+
props: P,
14+
propName: string & keyof P,
15+
componentName: string,
16+
) => {
17+
const passes = isVec2Object(props[propName]);
18+
return passes
19+
? null
20+
: new Error(`${propName} in ${componentName} is not a vec2 shape`);
21+
};
22+
23+
export const vec3Obj = <P extends PropTypes>(
24+
props: P,
25+
propName: string & keyof P,
26+
componentName: string,
27+
) => {
28+
const passes = isVec3Object(props[propName]);
29+
return passes
30+
? null
31+
: new Error(`${propName} in ${componentName} is not a vec3 shape`);
32+
};
33+
34+
export const vec2GlMatrix = <P extends PropTypes>(
35+
props: P,
36+
propName: string & keyof P,
37+
componentName: string,
38+
) => {
39+
const passes = isVec2(props[propName]);
40+
return passes
41+
? null
42+
: new Error(`${propName} in ${componentName} is not a gl-matrix vec2`);
43+
};
44+
45+
export const vec3GlMatrix = <P extends PropTypes>(
46+
props: P,
47+
propName: string & keyof P,
48+
componentName: string,
49+
) => {
50+
const passes = isVec3(props[propName]);
51+
return passes
52+
? null
53+
: new Error(`${propName} in ${componentName} is not a gl-matrix vec3`);
54+
};
55+
56+
export const mat2dGlMatrix = <P extends PropTypes>(
57+
props: P,
58+
propName: string & keyof P,
59+
componentName: string,
60+
) => {
61+
const passes = isMat2d(props[propName]);
62+
return passes
63+
? null
64+
: new Error(`${propName} in ${componentName} is not a gl-matrix mat2d`);
65+
};
66+
67+
export const mat4GlMatrix = <P extends PropTypes>(
68+
props: P,
69+
propName: string & keyof P,
70+
componentName: string,
71+
) => {
72+
const passes = isMat4(props[propName]);
73+
return passes
74+
? null
75+
: new Error(`${propName} in ${componentName} is not a gl-matrix mat4`);
76+
};

src/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export {
2+
vec2Obj,
3+
vec3Obj,
4+
vec2GlMatrix,
5+
vec3GlMatrix,
6+
mat2dGlMatrix,
7+
mat4GlMatrix,
8+
} from './constants';
9+
export { default as Transform2d } from './Transform2d';
10+
export { default as Transform3d } from './Transform3d';

src/types.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import type React from 'react';
2+
import type { vec2, vec3 } from 'gl-matrix';
3+
4+
export type GLMatrixType = Array<number> | Float32Array;
5+
export type Vec2Object = {
6+
x?: number;
7+
y?: number;
8+
};
9+
export type Vec3Object = Vec2Object & {
10+
z?: number;
11+
};
12+
export type Vec2SettableProperty = number | vec2 | Vec2Object;
13+
export type Vec3SettableProperty = number | vec3 | Vec3Object;
14+
15+
/**
16+
* POST is the default, more natural mathematically and (arguably) more useful, it behaves in the same order as when
17+
* applying multiple transforms on the css `transform` property:
18+
*
19+
* <Transform2d translate={{x:100, y:100}} multiplicationOrder={Transform2d.MULTIPLICATION_ORDER.POST}>
20+
* <Transform2d rotate={0.7854}>
21+
* <Transform2d scale={2}>
22+
* <div></div>
23+
* </Transform2d>
24+
* </Transform>
25+
* </Transform2d>
26+
*
27+
* Is the same as applying a style of `transform: translate(100px,100px) rotate(0.7845rad) scale(2)` to the inner div
28+
*
29+
* PRE mimics how CSS transforms work between nested DOM elements
30+
*
31+
* <Transform2d translate={{x:100, y:100}} multiplicationOrder={Transform2d.MULTIPLICATION_ORDER.PRE}>
32+
* <Transform2d rotate={0.7854}>
33+
* <Transform2d scale={2}>
34+
* <div></div>
35+
* </Transform2d>
36+
* </Transform>
37+
* </Transform2d>
38+
*
39+
* Is basically the same (assuming all the nested divs are positioned absolutely in the same place) as:
40+
*
41+
* <div style="transform: translate(100px, 100px)>
42+
* <div style="transform: rotate(0.7854rad)">
43+
* <div style="transform: scale(2)">
44+
* <div></div>
45+
* </div>
46+
* </div>
47+
* </div>
48+
*/
49+
export type MultiplicationOrder = 'PRE' | 'POST';
50+
51+
export type CSSMatrixPrefix = 'matrix' | 'matrix3d';
52+
53+
export type TransformChildProps<Matrix extends GLMatrixType> = {
54+
parentMatrixWorld?: Matrix;
55+
multiplicationOrder?: MultiplicationOrder;
56+
style?: React.CSSProperties;
57+
};
58+
59+
export type TransformChildFunction<Matrix extends GLMatrixType> = (
60+
params: TransformChildProps<Matrix>,
61+
) => JSX.Element;
62+
63+
export type TransformChildren<Matrix extends GLMatrixType> =
64+
| React.ReactElement
65+
| React.ReactElement[]
66+
| TransformChildFunction<Matrix>;

0 commit comments

Comments
 (0)