@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
110 lines (99 loc) • 3.5 kB
text/typescript
import {BaseEvent, Euler, Quaternion, Vector3, Object3D} from 'three';
import {degToRad, radToDeg} from 'three/src/math/MathUtils';
export type ScreenOrientation = number;
export interface DeviceOrientationEventExtended extends DeviceOrientationEvent {
requestPermission?: () => Promise<string>;
webkitCompassHeading?: number;
}
export const CHANGE_EVENT: BaseEvent<'change'> = {type: 'change'};
export const EPS = 0.000001;
export const PI_DEG = radToDeg(Math.PI);
export const PI_DEG2 = 2 * radToDeg(Math.PI);
const AXIS_Z = new Vector3(0, 0, 1);
const euler = new Euler();
const q0 = new Quaternion();
const q1 = new Quaternion(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5)); // - PI/2 around the x-axis
const q2 = new Quaternion();
const ROTATION_ORDER_YXZ = 'YXZ';
function _setQuaternionFromAlphaBetaGamma(
alpha: number,
beta: number,
gamma: number,
orient: number,
target: Quaternion
): void {
euler.set(beta, alpha, -gamma, ROTATION_ORDER_YXZ); // 'ZXY' for the device, but 'YXZ' for us
target.setFromEuler(euler); // orient the device
target.multiply(q1); // camera looks out the back of the device, not the top
target.multiply(q0.setFromAxisAngle(AXIS_Z, -orient)); // adjust for screen orientation
}
const alphaOffset = 0;
export function setQuaternionFromEvent(
event: Partial<DeviceOrientationEvent>,
screenOrientation: ScreenOrientation,
target: Quaternion
): void {
const alpha = event.alpha ? degToRad(event.alpha) + alphaOffset : 0; // Z
const beta = event.beta ? degToRad(event.beta) : 0; // X'
const gamma = event.gamma ? degToRad(event.gamma) : 0; // Y''
const orient = screenOrientation ? degToRad(screenOrientation as number) : 0; // O
_setQuaternionFromAlphaBetaGamma(alpha, beta, gamma, orient, target);
}
export function quaternionYAngle(relativeQuaternion: Quaternion) {
euler.setFromQuaternion(relativeQuaternion);
return -radToDeg(euler.y);
}
export function yAngleFromEvent(event: Partial<DeviceOrientationEvent>, screenOrientation: ScreenOrientation): number {
setQuaternionFromEvent(event, screenOrientation, q2);
return quaternionYAngle(q2);
}
interface RotationHierachy {
ryOffset: Object3D;
ry: Object3D;
rx: Object3D;
rz: Object3D;
}
function _createObjects(): RotationHierachy {
const ryOffset = new Object3D();
const ry = new Object3D();
const rx = new Object3D();
const rz = new Object3D();
ryOffset.add(ry);
ry.add(rx);
rx.add(rz);
return {
ryOffset,
ry,
rx,
rz,
};
}
let _rotationHierarchy: RotationHierachy | undefined;
function _getRotationHierarchy() {
return _rotationHierarchy || (_rotationHierarchy = _createObjects());
}
export function blendQuaternionToAbsoluteYAngle(
relativeQuaternion: Quaternion,
yAngleOffset: number,
target: Quaternion
) {
euler.setFromQuaternion(relativeQuaternion);
const rotationHierarchy = _getRotationHierarchy();
rotationHierarchy.ryOffset.rotation.set(0, -degToRad(yAngleOffset), 0);
rotationHierarchy.ry.rotation.set(0, euler.y, 0);
rotationHierarchy.rx.rotation.set(euler.x, 0, 0);
rotationHierarchy.rz.rotation.set(0, 0, euler.z);
rotationHierarchy.rz.updateWorldMatrix(true, true);
target.setFromRotationMatrix(rotationHierarchy.rz.matrixWorld);
}
// export function ensureDeltaLessThan2PI(initValue: number, comparisonValue:number): number {
// const delta = initValue - comparisonValue;
// if (Math.abs(delta) > PI_DEG) {
// if (delta > 0) {
// return delta - PI_DEG2;
// } else {
// return delta + PI_DEG2;
// }
// }
// return delta;
// }