Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add useAnimatedSensor for Web #3955

Merged
merged 8 commits into from Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/reanimated2/NativeReanimated/NativeReanimated.ts
@@ -1,5 +1,10 @@
import { NativeModules } from 'react-native';
import { ShareableRef, ShareableSyncDataHolderRef } from '../commonTypes';
import {
ShareableRef,
ShareableSyncDataHolderRef,
Value3D,
ValueRotation,
} from '../commonTypes';
import { LayoutAnimationFunction } from '../layoutReanimation';
import { version as jsVersion } from '../../../package.json';

Expand Down Expand Up @@ -88,7 +93,7 @@ export class NativeReanimated {
registerSensor<T>(
sensorType: number,
interval: number,
handler: ShareableRef<T>
handler: ShareableRef<T> | ((data: Value3D | ValueRotation) => void)
) {
return this.InnerNativeModule.registerSensor(sensorType, interval, handler);
}
Expand Down
8 changes: 8 additions & 0 deletions src/reanimated2/commonTypes.ts
Expand Up @@ -145,6 +145,14 @@ export interface Animation<T extends AnimationObject> extends AnimationObject {
) => void;
}

export enum SensorType {
ACCELEROMETER = 1,
GYROSCOPE = 2,
GRAVITY = 3,
MAGNETIC_FIELD = 4,
ROTATION = 5,
}

export interface NumericAnimation {
current?: number;
}
Expand Down
15 changes: 15 additions & 0 deletions src/reanimated2/hook/.expo/README.md
@@ -0,0 +1,15 @@
> Why do I have a folder named ".expo" in my project?
The ".expo" folder is created when an Expo project is started using "expo start" command.

> What do the files contain?
- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds.
- "packager-info.json": contains port numbers and process PIDs that are used to serve the application to the mobile device/simulator.
- "settings.json": contains the server configuration that is used to serve the application manifest.

> Should I commit the ".expo" folder?
No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
mstach60161 marked this conversation as resolved.
Show resolved Hide resolved

Upon project creation, the ".expo" folder is already added to your ".gitignore" file.
8 changes: 8 additions & 0 deletions src/reanimated2/hook/.expo/settings.json
@@ -0,0 +1,8 @@
{
"hostType": "lan",
"lanType": "ip",
"dev": true,
"minify": false,
"urlRandomness": null,
"https": false
}
2 changes: 1 addition & 1 deletion src/reanimated2/hook/index.ts
Expand Up @@ -20,7 +20,7 @@ export { useAnimatedScrollHandler } from './useAnimatedScrollHandler';
export type { ScrollHandler, ScrollHandlers } from './useAnimatedScrollHandler';
export { useDerivedValue } from './useDerivedValue';
export type { DerivedValue } from './useDerivedValue';
export { useAnimatedSensor, SensorType } from './useAnimatedSensor';
export { useAnimatedSensor } from './useAnimatedSensor';
export { useFrameCallback } from './useFrameCallback';
export type { FrameCallback } from './useFrameCallback';
export { useAnimatedKeyboard } from './useAnimatedKeyboard';
Expand Down
15 changes: 6 additions & 9 deletions src/reanimated2/hook/useAnimatedSensor.ts
@@ -1,14 +1,11 @@
import { useEffect, useRef } from 'react';
import { makeMutable, registerSensor, unregisterSensor } from '../core';
import { SharedValue, Value3D, ValueRotation } from '../commonTypes';

export enum SensorType {
ACCELEROMETER = 1,
GYROSCOPE = 2,
GRAVITY = 3,
MAGNETIC_FIELD = 4,
ROTATION = 5,
}
import {
SensorType,
SharedValue,
Value3D,
ValueRotation,
} from '../commonTypes';

export type SensorConfig = {
interval: number | 'auto';
Expand Down
90 changes: 84 additions & 6 deletions src/reanimated2/js-reanimated/JSReanimated.ts
@@ -1,8 +1,17 @@
import { NativeReanimated } from '../NativeReanimated/NativeReanimated';
import { ShareableRef } from '../commonTypes';
import {
SensorType,
ShareableRef,
Value3D,
ValueRotation,
} from '../commonTypes';
import { isJest } from '../PlatformChecker';
import { WebSensor } from './WebSensor';

export default class JSReanimated extends NativeReanimated {
nextSensorId = 0;
sensors = new Map<number, WebSensor>();

constructor() {
super(false);
if (isJest()) {
Expand Down Expand Up @@ -52,13 +61,48 @@ export default class JSReanimated extends NativeReanimated {
);
}

registerSensor(): number {
console.warn('[Reanimated] useAnimatedSensor is not available on web yet.');
return -1;
registerSensor(
sensorType: SensorType,
interval: number,
eventHandler: (data: Value3D | ValueRotation) => void
): number {
if (!(this.getSensorName(sensorType) in window)) {
return -1;
}

const sensor: WebSensor = this.initializeSensor(sensorType, interval);
sensor.addEventListener('reading', () => {
if (sensorType === SensorType.ROTATION) {
const [qw, qx, qy, qz] = sensor.quaternion;

// reference: https://stackoverflow.com/questions/5782658/extracting-yaw-from-a-quaternion
const yaw = Math.atan2(
2.0 * (qy * qz + qw * qx),
qw * qw - qx * qx - qy * qy + qz * qz
);
const pitch = Math.sin(-2.0 * (qx * qz - qw * qy));
const roll = Math.atan2(
2.0 * (qx * qy + qw * qz),
qw * qw + qx * qx - qy * qy - qz * qz
);
eventHandler({ qw, qx, qy, qz, yaw, pitch, roll });
} else {
const { x, y, z } = sensor;
eventHandler({ x, y, z });
}
});
Comment on lines +74 to +93
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this way we can get rid off unnecessary if condition inside of callback:

let callback;
if (sensorType === SensorType.ROTATION) {
  callback = () => {
    const [qw, qx, qy, qz] = sensor.quaternion;

    // reference: https://stackoverflow.com/questions/5782658/extracting-yaw-from-a-quaternion
    const yaw = Math.atan2(
      2.0 * (qy * qz + qw * qx),
      qw * qw - qx * qx - qy * qy + qz * qz
    );
    const pitch = Math.sin(-2.0 * (qx * qz - qw * qy));
    const roll = Math.atan2(
      2.0 * (qx * qy + qw * qz),
      qw * qw + qx * qx - qy * qy - qz * qz
    );
    eventHandler({ qw, qx, qy, qz, yaw, pitch, roll });
  };
} else {
  callback = () => {
    const { x, y, z } = sensor;
    eventHandler({ x, y, z });
  };
}
sensor.addEventListener('reading', callback);

sensor.start();

this.sensors.set(this.nextSensorId, sensor);
return this.nextSensorId++;
}

unregisterSensor(): void {
// noop
unregisterSensor(id: number): void {
const sensor: WebSensor | undefined = this.sensors.get(id);
if (sensor !== undefined) {
sensor.stop();
this.sensors.delete(id);
}
}

subscribeForKeyboardEvents(_: ShareableRef<number>): number {
Expand All @@ -71,4 +115,38 @@ export default class JSReanimated extends NativeReanimated {
unsubscribeFromKeyboardEvents(_: number): void {
// noop
}

initializeSensor(sensorType: SensorType, interval: number): WebSensor {
const config =
interval === -1
? { referenceFrame: 'device' }
: { frequency: 1 / interval };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be{ frequency: 1000 / interval }, because the interval contains milliseconds.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if interval is equal 0 ?

switch (sensorType) {
case SensorType.ACCELEROMETER:
return new window.Accelerometer(config);
case SensorType.GYROSCOPE:
return new window.Gyroscope(config);
case SensorType.GRAVITY:
return new window.GravitySensor(config);
case SensorType.MAGNETIC_FIELD:
return new window.Magnetometer(config);
case SensorType.ROTATION:
return new window.AbsoluteOrientationSensor(config);
}
}

getSensorName(sensorType: SensorType): string {
switch (sensorType) {
case SensorType.ACCELEROMETER:
return 'Accelerometer';
case SensorType.GRAVITY:
return 'GravitySensor';
case SensorType.GYROSCOPE:
return 'Gyroscope';
case SensorType.MAGNETIC_FIELD:
return 'Magnetometer';
case SensorType.ROTATION:
return 'AbsoluteOrientationSensor';
}
}
}
34 changes: 34 additions & 0 deletions src/reanimated2/js-reanimated/WebSensor.ts
@@ -0,0 +1,34 @@
export declare class WebSensor {
start: () => void;
stop: () => void;
addEventListener: (eventType: string, eventHandler: () => void) => void;
quaternion: [number, number, number, number];
x: number;
y: number;
z: number;
}

type configOptions =
| {
referenceFrame: string;
frequency?: undefined;
}
| {
frequency: number;
referenceFrame?: undefined;
};

interface Constructable<T> {
new (config: configOptions): T;
}

declare global {
interface Window {
Accelerometer: Constructable<WebSensor>;
GravitySensor: Constructable<WebSensor>;
Gyroscope: Constructable<WebSensor>;
Magnetometer: Constructable<WebSensor>;
AbsoluteOrientationSensor: Constructable<WebSensor>;
Sensor: Constructable<WebSensor>;
}
}