Created
May 12, 2019 12:13
-
-
Save jonastreub/c38c146aae5a249207b9de9c33228206 to your computer and use it in GitHub Desktop.
A hook making it fun to work with the gyroscope
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useState, useEffect } from "react" | |
interface WorldOrientation { | |
/** A number representing the motion of the device around the z axis, expressed in degrees with values ranging from 0 to 360. */ | |
alpha: number | |
/** A number representing the motion of the device around the x axis, expressed in degrees with values ranging from -180 to 180. This represents a front to back motion of the device. */ | |
beta: number | |
/** A number representing the motion of the device around the y axis, expressed in degrees with values ranging from -90 to 90. This represents a left to right motion of the device. */ | |
gamma: number | |
} | |
interface DeviceOrientation { | |
/** A number representing the horizontal heading, expressed in degrees with values ranging from 0 to 360. */ | |
heading: number | |
/** A number representing the vertical elevation, expressed in values ranging from 90 degrees up to -90 degrees down. */ | |
elevation: number | |
/** A number representing tilt, expressed in values ranging from -180 degrees counter clockwise to 180 degrees clockwise. */ | |
tilt: number | |
} | |
interface GyroData extends WorldOrientation, DeviceOrientation { | |
/** A boolean representing whether the device supports gyro events. */ | |
supportsGyro: boolean | |
} | |
/** A hook making it fun to work with the gyroscope. | |
* @returns {@link GyroData} | |
*/ | |
export function useGyro(): GyroData { | |
const [data, setData] = useState(initialData) | |
useEffect(() => { | |
if (!data.supportsGyro) return | |
function onOrientationChange(event: DeviceOrientationEvent) { | |
const { alpha, beta, gamma } = event | |
// Math only works for non zero values | |
if (alpha === 0 || beta === 0 || gamma === 0) return | |
const { heading, elevation, tilt } = worldToDeviceOrientation(event) | |
setData(previousData => { | |
return { | |
...previousData, | |
alpha, | |
beta, | |
gamma, | |
heading, | |
elevation, | |
tilt, | |
} | |
}) | |
} | |
window.addEventListener("deviceorientation", onOrientationChange) | |
return () => { | |
window.removeEventListener("deviceorientation", onOrientationChange) | |
} | |
}, []) | |
return data | |
} | |
const initialData: GyroData = { | |
heading: 0, | |
elevation: 0, | |
tilt: 0, | |
alpha: 0, | |
beta: 0, | |
gamma: 0, | |
supportsGyro: hasDeviceOrientationEvent(), | |
} | |
function worldToDeviceOrientation( | |
worldOrientation: WorldOrientation | |
): DeviceOrientation { | |
const { alpha, beta, gamma } = worldOrientation | |
const alphaRad = degToRad(alpha) | |
const betaRad = degToRad(beta) | |
const gammaRad = degToRad(gamma) | |
// Calculate equation components | |
const cA = Math.cos(alphaRad) | |
const sA = Math.sin(alphaRad) | |
const cB = Math.cos(betaRad) | |
const sB = Math.sin(betaRad) | |
const cG = Math.cos(gammaRad) | |
const sG = Math.sin(gammaRad) | |
// x unitvector | |
const xrA = -sA * sB * sG + cA * cG | |
const xrB = cA * sB * sG + sA * cG | |
const xrC = cB * sG | |
// y unitvector | |
const yrA = -sA * cB | |
const yrB = cA * cB | |
const yrC = -sB | |
// -z unitvector | |
const zrA = -sA * sB * cG - cA * sG | |
const zrB = cA * sB * cG - sA * sG | |
const zrC = cB * cG | |
// Calculate heading | |
let heading = Math.atan(zrA / zrB) | |
// Convert from half unit circle to whole unit circle | |
if (zrB < 0) { | |
heading += Math.PI | |
} else if (zrA < 0) { | |
heading += 2 * Math.PI | |
} | |
// Calculate elevation | |
const elevation = Math.PI / 2 - Math.acos(-zrC) | |
// Calculate tilt | |
const cH = Math.sqrt(1 - zrC * zrC) | |
const tilt = Math.acos(-xrC / cH) * Math.sign(yrC) | |
return { | |
heading: radToDeg(heading), | |
elevation: radToDeg(elevation), | |
tilt: radToDeg(tilt), | |
} | |
} | |
function degToRad(deg) { | |
return (deg * Math.PI) / 180 | |
} | |
function radToDeg(rad) { | |
return (rad * 180) / Math.PI | |
} | |
function hasDeviceOrientationEvent() { | |
return !!window["DeviceOrientationEvent"] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment