Skip to content

Commit

Permalink
feat: add DeviceOrientationController
Browse files Browse the repository at this point in the history
  • Loading branch information
bhouston committed Jul 21, 2020
1 parent 47b8dcd commit 1649801
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 5 deletions.
29 changes: 24 additions & 5 deletions src/examples/passes/equirectangular/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { DeviceOrientationController } from "../../../lib/controllers/DeviceOrientation";
import { passGeometry } from "../../../lib/geometry/primitives/passGeometry";
import { ShaderMaterial } from "../../../lib/materials/ShaderMaterial";
import { Euler } from "../../../lib/math/Euler";
import { Matrix4 } from "../../../lib/math/Matrix4";
import {
makeMatrix4Inverse,
makeMatrix4PerspectiveFov,
makeMatrix4RotationFromEuler,
makeMatrix4RotationFromQuaternion,
} from "../../../lib/math/Matrix4.Functions";
import { Quaternion } from "../../../lib/math/Quaternion";
import { makeBufferGeometryFromGeometry } from "../../../lib/renderers/webgl/buffers/BufferGeometry";
import { DepthTestFunc, DepthTestState } from "../../../lib/renderers/webgl/DepthTestState";
import { makeProgramFromShaderMaterial } from "../../../lib/renderers/webgl/programs/Program";
Expand Down Expand Up @@ -47,13 +48,31 @@ async function init(): Promise<null> {
const bufferGeometry = makeBufferGeometryFromGeometry(context, geometry);
const depthTestState = new DepthTestState(true, DepthTestFunc.Less);

const deviceOrientation = new Quaternion();

let deviceOrientationController: DeviceOrientationController | undefined = undefined;

const body = document.getElementsByTagName("body")[0];
body.addEventListener(
"click",
() => {
if (deviceOrientationController === undefined) {
deviceOrientationController = new DeviceOrientationController((orientation) => {
deviceOrientation.copy(orientation);
});
}
},
false,
);

function animate(): void {
requestAnimationFrame(animate);

if (deviceOrientationController !== undefined) {
deviceOrientationController.update();
}
const now = Date.now();
passUniforms.viewToWorld = makeMatrix4Inverse(
makeMatrix4RotationFromEuler(new Euler(Math.sin(now * 0.0003), now * 0.0004, 0)),
);
passUniforms.viewToWorld = makeMatrix4RotationFromQuaternion(deviceOrientation);
passUniforms.equirectangularMap = Math.floor(now / 5000) % 2 === 0 ? garageMap : debugMap;

canvasFramebuffer.renderBufferGeometry(passProgram, passUniforms, bufferGeometry, depthTestState);
Expand Down
17 changes: 17 additions & 0 deletions src/lib/controllers/Controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { generateUUID } from "../core/generateUuid";
import { IDisposable } from "../core/types";

export class Controller implements IDisposable {
disposed = false;
enabled = true;
name = "";
readonly uuid: string = generateUUID();

constructor() {}

dispose(): void {
if (!this.disposed) {
this.disposed = true;
}
}
}
75 changes: 75 additions & 0 deletions src/lib/controllers/DeviceOrientation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Euler, EulerOrder } from "../math/Euler";
import { Quaternion } from "../math/Quaternion";
import { makeQuaternionFromAxisAngle, makeQuaternionFromEuler } from "../math/Quaternion.Functions";
import { degToRad } from "../math/Utilities";
import { Vector3 } from "../math/Vector3";
import { Controller } from "./Controller";

export class DeviceOrientationController extends Controller {
deviceOrientation = new Euler(0, 0, 0, EulerOrder.YXZ);
screenOrientation = 0;
onDispose: (() => void) | undefined = undefined;

constructor(public fnCallback: (orientation: Quaternion) => void) {
super();

// this is required because when invoked on an event, "this" is set to "window"
const onDeviceOrientation = (event: DeviceOrientationEvent): void => {
this.deviceOrientation.set(degToRad(event.beta ?? 0), degToRad(event.alpha ?? 0), degToRad(event.gamma ?? 0));
};
const onOrientationChange = (): void => {
this.screenOrientation = -degToRad(window.orientation as number);
};

if (
// iOS 13+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
window.DeviceOrientationEvent !== undefined &&
typeof window.DeviceOrientationEvent.requestPermission === "function"
) {
// eslint-disable-next-line @typescript-eslint/no-this-alias, cflint/no-this-assignment
window.DeviceOrientationEvent.requestPermission()
.then((response) => {
if (response === "granted") {
window.addEventListener("orientationchange", onOrientationChange, false);
window.addEventListener("deviceorientation", onDeviceOrientation, false);
}
})
.catch(() => {
throw new Error("DeviceOrientation API not available.");
});
} else {
window.addEventListener("orientationchange", onOrientationChange, false);
window.addEventListener("deviceorientation", onDeviceOrientation, false);
}

this.onDispose = (): void => {
window.removeEventListener("orientationchange", onOrientationChange, false);
window.removeEventListener("deviceorientation", onDeviceOrientation, false);
};
}

update(): void {
if (!this.enabled) {
return;
}

const zAxis = new Vector3(0, 0, 1);
const q1 = new Quaternion(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5)); // - PI/2 around the x-axis

const result = makeQuaternionFromEuler(this.deviceOrientation);
result.multiply(q1); // camera looks out the back of the device, not the top
result.multiply(makeQuaternionFromAxisAngle(zAxis, this.screenOrientation)); // adjust for screen orientation

this.fnCallback(result);
}

dispose(): void {
if (!this.disposed) {
if (this.onDispose !== undefined) {
this.onDispose();
}
super.dispose();
}
}
}
1 change: 1 addition & 0 deletions threeify.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"Clearcoat",
"Cloneable",
"Equirectangular",
"HDRs",
"Hashable",
"Metalness",
"Mipmaps",
Expand Down

0 comments on commit 1649801

Please sign in to comment.