Skip to content

Commit 31ba4b8

Browse files
committed
Add complex transformations example
1 parent 7c34268 commit 31ba4b8

1 file changed

Lines changed: 241 additions & 18 deletions

File tree

  • apps/common-app/src/legacy/v2_api/transformations

apps/common-app/src/legacy/v2_api/transformations/index.tsx

Lines changed: 241 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,259 @@ import Animated, {
55
useSharedValue,
66
} from 'react-native-reanimated';
77
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
8+
import { useState } from 'react';
89

910
// @ts-ignore it's an image
1011
import SIGNET from '../../../ListWithHeader/signet.png';
1112

13+
function identity4() {
14+
'worklet';
15+
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
16+
}
17+
18+
function multiply4(a: number[], b: number[]) {
19+
'worklet';
20+
return [
21+
a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12],
22+
a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13],
23+
a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14],
24+
a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15],
25+
a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12],
26+
a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13],
27+
a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14],
28+
a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15],
29+
a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12],
30+
a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13],
31+
a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14],
32+
a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15],
33+
a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12],
34+
a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13],
35+
a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14],
36+
a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15],
37+
];
38+
}
39+
40+
function scale4(sx: number, sy: number, sz: number) {
41+
'worklet';
42+
return [sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1];
43+
}
44+
45+
function translate4(tx: number, ty: number, tz: number) {
46+
'worklet';
47+
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tx, ty, tz, 1];
48+
}
49+
50+
function rotate4(rad: number, x: number, y: number, z: number) {
51+
'worklet';
52+
const len = Math.hypot(x, y, z);
53+
const c = Math.cos(rad);
54+
const s = Math.sin(rad);
55+
const t = 1 - c;
56+
x /= len;
57+
y /= len;
58+
z /= len;
59+
return [
60+
t * x * x + c,
61+
t * x * y - s * z,
62+
t * x * z + s * y,
63+
0,
64+
t * x * y + s * z,
65+
t * y * y + c,
66+
t * y * z - s * x,
67+
0,
68+
t * x * z - s * y,
69+
t * y * z + s * x,
70+
t * z * z + c,
71+
0,
72+
0,
73+
0,
74+
0,
75+
1,
76+
];
77+
}
78+
79+
function invert2(m: number[]) {
80+
'worklet';
81+
const a = m[0];
82+
const b = m[1];
83+
const c = m[2];
84+
const d = m[3];
85+
const det = a * d - b * c;
86+
87+
return [d / det, -b / det, -c / det, a / det];
88+
}
89+
90+
function toTransformedCoords(
91+
point: { x: number; y: number },
92+
matrix: number[]
93+
) {
94+
'worklet';
95+
const m2 = [matrix[0], matrix[1], matrix[4], matrix[5]];
96+
const inv = invert2(m2);
97+
const x = point.x;
98+
const y = point.y;
99+
const newX = inv[0] * x + inv[2] * y;
100+
const newY = inv[1] * x + inv[3] * y;
101+
102+
return { x: newX, y: newY };
103+
}
104+
105+
function createMatrix(
106+
translation: { x: number; y: number },
107+
scale: number,
108+
rotation: number,
109+
origin: { x: number; y: number }
110+
) {
111+
'worklet';
112+
let matrix = identity4();
113+
114+
if (scale !== 1) {
115+
matrix = multiply4(matrix, translate4(origin.x, origin.y, 0));
116+
matrix = multiply4(matrix, scale4(scale, scale, 1));
117+
matrix = multiply4(matrix, translate4(-origin.x, -origin.y, 0));
118+
}
119+
if (rotation !== 0) {
120+
matrix = multiply4(matrix, translate4(origin.x, origin.y, 0));
121+
matrix = multiply4(matrix, rotate4(-rotation, 0, 0, 1));
122+
matrix = multiply4(matrix, translate4(-origin.x, -origin.y, 0));
123+
}
124+
125+
if (translation.x !== 0 || translation.y !== 0) {
126+
matrix = multiply4(matrix, translate4(translation.x, translation.y, 0));
127+
}
128+
129+
return matrix;
130+
}
131+
132+
function applyTransformations(
133+
translation: { x: number; y: number },
134+
scale: number,
135+
rotation: number,
136+
origin: { x: number; y: number },
137+
matrix: number[]
138+
) {
139+
'worklet';
140+
const translationInViewCoords = toTransformedCoords(translation, matrix);
141+
const transform = createMatrix(
142+
translationInViewCoords,
143+
scale,
144+
rotation,
145+
origin
146+
);
147+
return multiply4(transform, matrix);
148+
}
149+
12150
function Photo() {
13-
const translationX = useSharedValue(0);
14-
const translationY = useSharedValue(0);
151+
const [size, setSize] = useState({ width: 0, height: 0 });
152+
const translation = useSharedValue({ x: 0, y: 0 });
153+
const origin = useSharedValue({ x: 0, y: 0 });
15154
const scale = useSharedValue(1);
16155
const rotation = useSharedValue(0);
156+
const isRotating = useSharedValue(false);
157+
const isScaling = useSharedValue(false);
158+
159+
const transform = useSharedValue(identity4());
17160

18161
const style = useAnimatedStyle(() => {
162+
const matrix = applyTransformations(
163+
translation.value,
164+
scale.value,
165+
rotation.value,
166+
origin.value,
167+
transform.value
168+
);
169+
19170
return {
20171
transform: [
21-
{ translateX: translationX.value },
22-
{ translateY: translationY.value },
23-
{ scale: scale.value },
24-
{ rotateZ: `${rotation.value}rad` },
172+
{ translateX: matrix[12] },
173+
{ translateY: matrix[13] },
174+
{ scale: Math.hypot(matrix[0], matrix[1]) },
175+
{ rotateZ: `${Math.atan2(matrix[1], matrix[0])}rad` },
25176
],
26177
};
27178
});
28179

29-
const rotationGesture = Gesture.Rotation().onChange((e) => {
30-
'worklet';
31-
rotation.value += e.rotationChange;
32-
});
180+
const rotationGesture = Gesture.Rotation()
181+
.onStart((e) => {
182+
if (!isRotating.value && !isScaling.value) {
183+
origin.value = {
184+
x: -(e.anchorX - size.width / 2),
185+
y: -(e.anchorY - size.height / 2),
186+
};
187+
}
188+
isRotating.value = true;
189+
})
190+
.onChange((e) => {
191+
'worklet';
192+
rotation.value += e.rotationChange;
193+
})
194+
.onEnd(() => {
195+
'worklet';
196+
transform.value = applyTransformations(
197+
translation.value,
198+
scale.value,
199+
rotation.value,
200+
origin.value,
201+
transform.value
202+
);
33203

34-
const scaleGesture = Gesture.Pinch().onChange((e) => {
35-
'worklet';
36-
scale.value *= e.scaleChange;
37-
});
204+
rotation.value = 0;
205+
translation.value = { x: 0, y: 0 };
206+
scale.value = 1;
207+
isRotating.value = false;
208+
});
209+
210+
const scaleGesture = Gesture.Pinch()
211+
.onStart((e) => {
212+
if (!isRotating.value && !isScaling.value) {
213+
origin.value = {
214+
x: -(e.focalX - size.width / 2),
215+
y: -(e.focalY - size.height / 2),
216+
};
217+
}
218+
isScaling.value = true;
219+
})
220+
.onChange((e) => {
221+
'worklet';
222+
scale.value *= e.scaleChange;
223+
})
224+
.onEnd(() => {
225+
'worklet';
226+
transform.value = applyTransformations(
227+
translation.value,
228+
scale.value,
229+
rotation.value,
230+
origin.value,
231+
transform.value
232+
);
233+
rotation.value = 0;
234+
translation.value = { x: 0, y: 0 };
235+
scale.value = 1;
236+
isScaling.value = false;
237+
});
38238

39239
const panGesture = Gesture.Pan()
40240
.averageTouches(true)
41241
.onChange((e) => {
42242
'worklet';
43-
translationX.value += e.changeX;
44-
translationY.value += e.changeY;
243+
translation.value = {
244+
x: translation.value.x + e.changeX,
245+
y: translation.value.y + e.changeY,
246+
};
247+
})
248+
.onEnd(() => {
249+
'worklet';
250+
transform.value = applyTransformations(
251+
translation.value,
252+
scale.value,
253+
rotation.value,
254+
origin.value,
255+
transform.value
256+
);
257+
258+
rotation.value = 0;
259+
translation.value = { x: 0, y: 0 };
260+
scale.value = 1;
45261
});
46262

47263
const doubleTapGesture = Gesture.Tap()
@@ -62,7 +278,14 @@ function Photo() {
62278

63279
return (
64280
<GestureDetector gesture={gesture}>
65-
<Animated.View style={[styles.container, style]}>
281+
<Animated.View
282+
onLayout={({ nativeEvent }) => {
283+
setSize({
284+
width: nativeEvent.layout.width,
285+
height: nativeEvent.layout.height,
286+
});
287+
}}
288+
style={[styles.container, style]}>
66289
<Image source={SIGNET} style={styles.image} resizeMode="contain" />
67290
</Animated.View>
68291
</GestureDetector>
@@ -80,8 +303,8 @@ export default function Example() {
80303
const styles = StyleSheet.create({
81304
home: {
82305
flex: 1,
83-
alignItems: 'center',
84306
justifyContent: 'center',
307+
alignItems: 'center',
85308
},
86309
container: {
87310
width: 240,

0 commit comments

Comments
 (0)