UNPKG

5.22 kBPlain TextView Raw
1'use strict';
2import { useEffect, useMemo, useRef } from 'react';
3import { initializeSensor, registerSensor, unregisterSensor } from '../core';
4import type {
5 SensorConfig,
6 AnimatedSensor,
7 Value3D,
8 ValueRotation,
9} from '../commonTypes';
10import {
11 SensorType,
12 IOSReferenceFrame,
13 InterfaceOrientation,
14} from '../commonTypes';
15import { callMicrotasks } from '../threads';
16
17// euler angles are in order ZXY, z = yaw, x = pitch, y = roll
18// https://github.com/mrdoob/three.js/blob/dev/src/math/Quaternion.js#L237
19function eulerToQuaternion(pitch: number, roll: number, yaw: number) {
20 'worklet';
21 const c1 = Math.cos(pitch / 2);
22 const s1 = Math.sin(pitch / 2);
23 const c2 = Math.cos(roll / 2);
24 const s2 = Math.sin(roll / 2);
25 const c3 = Math.cos(yaw / 2);
26 const s3 = Math.sin(yaw / 2);
27
28 return [
29 s1 * c2 * c3 - c1 * s2 * s3,
30 c1 * s2 * c3 + s1 * c2 * s3,
31 c1 * c2 * s3 + s1 * s2 * c3,
32 c1 * c2 * c3 - s1 * s2 * s3,
33 ];
34}
35
36function adjustRotationToInterfaceOrientation(data: ValueRotation) {
37 'worklet';
38 const { interfaceOrientation, pitch, roll, yaw } = data;
39 if (interfaceOrientation === InterfaceOrientation.ROTATION_90) {
40 data.pitch = roll;
41 data.roll = -pitch;
42 data.yaw = yaw - Math.PI / 2;
43 } else if (interfaceOrientation === InterfaceOrientation.ROTATION_270) {
44 data.pitch = -roll;
45 data.roll = pitch;
46 data.yaw = yaw + Math.PI / 2;
47 } else if (interfaceOrientation === InterfaceOrientation.ROTATION_180) {
48 data.pitch *= -1;
49 data.roll *= -1;
50 data.yaw *= -1;
51 }
52
53 const q = eulerToQuaternion(data.pitch, data.roll, data.yaw);
54 data.qx = q[0];
55 data.qy = q[1];
56 data.qz = q[2];
57 data.qw = q[3];
58 return data;
59}
60
61function adjustVectorToInterfaceOrientation(data: Value3D) {
62 'worklet';
63 const { interfaceOrientation, x, y } = data;
64 if (interfaceOrientation === InterfaceOrientation.ROTATION_90) {
65 data.x = -y;
66 data.y = x;
67 } else if (interfaceOrientation === InterfaceOrientation.ROTATION_270) {
68 data.x = y;
69 data.y = -x;
70 } else if (interfaceOrientation === InterfaceOrientation.ROTATION_180) {
71 data.x *= -1;
72 data.y *= -1;
73 }
74 return data;
75}
76
77/**
78 * Lets you create animations based on data from the device's sensors.
79 *
80 * @param sensorType - Type of the sensor to use. Configured with {@link SensorType} enum.
81 * @param config - The sensor configuration - {@link SensorConfig}.
82 * @returns An object containing the sensor measurements [shared value](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary#shared-value) and a function to unregister the sensor
83 * @see https://docs.swmansion.com/react-native-reanimated/docs/device/useAnimatedSensor
84 */
85export function useAnimatedSensor(
86 sensorType: SensorType.ROTATION,
87 userConfig?: Partial<SensorConfig>
88): AnimatedSensor<ValueRotation>;
89export function useAnimatedSensor(
90 sensorType: Exclude<SensorType, SensorType.ROTATION>,
91 userConfig?: Partial<SensorConfig>
92): AnimatedSensor<Value3D>;
93export function useAnimatedSensor(
94 sensorType: SensorType,
95 userConfig?: Partial<SensorConfig>
96): AnimatedSensor<ValueRotation> | AnimatedSensor<Value3D> {
97 const userConfigRef = useRef(userConfig);
98
99 const hasConfigChanged =
100 userConfigRef.current?.adjustToInterfaceOrientation !==
101 userConfig?.adjustToInterfaceOrientation ||
102 userConfigRef.current?.interval !== userConfig?.interval ||
103 userConfigRef.current?.iosReferenceFrame !== userConfig?.iosReferenceFrame;
104
105 if (hasConfigChanged) {
106 userConfigRef.current = { ...userConfig };
107 }
108
109 const config: SensorConfig = useMemo(
110 () => ({
111 interval: 'auto',
112 adjustToInterfaceOrientation: true,
113 iosReferenceFrame: IOSReferenceFrame.Auto,
114 ...userConfigRef.current,
115 }),
116 [userConfigRef.current]
117 );
118
119 const ref = useRef<AnimatedSensor<Value3D | ValueRotation>>({
120 sensor: initializeSensor(sensorType, config),
121 unregister: () => {
122 // NOOP
123 },
124 isAvailable: false,
125 config,
126 });
127
128 useEffect(() => {
129 ref.current = {
130 sensor: initializeSensor(sensorType, config),
131 unregister: () => {
132 // NOOP
133 },
134 isAvailable: false,
135 config,
136 };
137
138 const sensorData = ref.current.sensor;
139 const adjustToInterfaceOrientation =
140 ref.current.config.adjustToInterfaceOrientation;
141
142 const id = registerSensor(sensorType, config, (data) => {
143 'worklet';
144 if (adjustToInterfaceOrientation) {
145 if (sensorType === SensorType.ROTATION) {
146 data = adjustRotationToInterfaceOrientation(data as ValueRotation);
147 } else {
148 data = adjustVectorToInterfaceOrientation(data as Value3D);
149 }
150 }
151 sensorData.value = data;
152 callMicrotasks();
153 });
154
155 if (id !== -1) {
156 // if sensor is available
157 ref.current.unregister = () => unregisterSensor(id);
158 ref.current.isAvailable = true;
159 } else {
160 // if sensor is unavailable
161 ref.current.unregister = () => {
162 // NOOP
163 };
164 ref.current.isAvailable = false;
165 }
166
167 return () => {
168 ref.current.unregister();
169 };
170 }, [sensorType, config]);
171
172 return ref.current as AnimatedSensor<ValueRotation> | AnimatedSensor<Value3D>;
173}