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

Apply #659 to device-orientation-controls, integrating permission dialog into library #661

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion aframe/build/aframe-ar-new-location-only.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion aframe/build/aframe-ar-new-location-only.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion aframe/build/aframe-ar-nft.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion aframe/build/aframe-ar-nft.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion aframe/build/aframe-ar.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion aframe/build/aframe-ar.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion three.js/build/ar-threex-location-only.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion three.js/build/ar-threex-location-only.mjs

Large diffs are not rendered by default.

15 changes: 1 addition & 14 deletions three.js/examples/location-based/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,7 @@ function main() {

// Orientation controls only work on mobile device
if (isMobile()){
if (
window.DeviceOrientationEvent !== undefined &&
typeof window.DeviceOrientationEvent.requestPermission === "function"
) {
document.getElementById("startModal").style.display = "flex";
const onStartClick = () => {
orientationControls = new DeviceOrientationControls(camera);
document.getElementById("startModal").style.display = "none";
}
document.getElementById("startModal").addEventListener("click", onStartClick);
document.getElementById("startButton").addEventListener("click", onStartClick);
} else {
orientationControls = new DeviceOrientationControls(camera);
}
orientationControls = new DeviceOrientationControls(camera);
}

let fake = null;
Expand Down
255 changes: 212 additions & 43 deletions three.js/src/location-based/js/device-orientation-controls.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
// Modified version of THREE.DeviceOrientationControls from three.js
// will use the deviceorientationabsolute event if available
/**
* Class to handle device orientation.
* IMPORTANT - this code is a modified version the former official three.js
* DeviceOrientationControls class, which was formerly provided with the
* three.js repo
*
* Changes:
*
* - use "deviceorientationabsolute" rather than "deviceorientation"
* where available
*
* @author richt / http://richt.me
* @author WestLangley / http://github.com/WestLangley
*
* W3C Device Orientation control (http://w3c.github.io/deviceorientation/spec-source-orientation.html)
*/

import { Euler, EventDispatcher, MathUtils, Quaternion, Vector3 } from "three";

const isIOS =
navigator.userAgent.match(/iPhone|iPad|iPod/i) ||
(/Macintosh/i.test(navigator.userAgent) &&
navigator.maxTouchPoints != null &&
navigator.maxTouchPoints > 1); // for iPad Safari

const _zee = new Vector3(0, 0, 1);
const _euler = new Euler();
const _q0 = new Quaternion();
Expand All @@ -11,6 +31,11 @@ const _q1 = new Quaternion(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5)); // - PI/2 aro
const _changeEvent = { type: "change" };

class DeviceOrientationControls extends EventDispatcher {
/**
* Create an instance of DeviceOrientationControls.
* @param {Object} object - the object to attach the controls to
* (usually your Three.js camera)
*/
constructor(object) {
super();

Expand All @@ -30,10 +55,11 @@ class DeviceOrientationControls extends EventDispatcher {

this.enabled = true;

this.deviceOrientation = {};
this.deviceOrientation = null;
this.screenOrientation = 0;

this.alphaOffset = 0; // radians
this.initialOffset = null; // used in fix provided in issue #466 on main AR.js repo, iOS related

this.TWO_PI = 2 * Math.PI;
this.HALF_PI = 0.5 * Math.PI;
Expand All @@ -44,8 +70,25 @@ class DeviceOrientationControls extends EventDispatcher {

this.smoothingFactor = 1;

const onDeviceOrientationChangeEvent = function (event) {
scope.deviceOrientation = event;
const onDeviceOrientationChangeEvent = function ({
alpha,
beta,
gamma,
webkitCompassHeading,
}) {
if (isIOS) {
const ccwNorthHeading = 360 - webkitCompassHeading;
scope.alphaOffset = MathUtils.degToRad(ccwNorthHeading - alpha);
scope.deviceOrientation = { alpha, beta, gamma, webkitCompassHeading };
} else {
if (alpha < 0) alpha += 360;
scope.deviceOrientation = { alpha, beta, gamma };
}
window.dispatchEvent(
new CustomEvent("camera-rotation-change", {
detail: { cameraRotation: object.rotation },
}),
);
};

const onScreenOrientationChangeEvent = function () {
Expand All @@ -70,6 +113,10 @@ class DeviceOrientationControls extends EventDispatcher {
quaternion.multiply(_q0.setFromAxisAngle(_zee, -orient)); // adjust for screen orientation
};

/**
* Update the device orientation controls.
* Should be called from your three.js rendering/animation function.
*/
this.connect = function () {
onScreenOrientationChangeEvent(); // run once on load

Expand Down Expand Up @@ -123,9 +170,11 @@ class DeviceOrientationControls extends EventDispatcher {
);

scope.enabled = false;
scope.initialOffset = false;
scope.deviceOrientation = null;
};

this.update = function () {
this.update = function ({ theta = 0 } = { theta: 0 }) {
if (scope.enabled === false) return;

const device = scope.deviceOrientation;
Expand All @@ -143,45 +192,63 @@ class DeviceOrientationControls extends EventDispatcher {
? MathUtils.degToRad(scope.screenOrientation)
: 0; // O

if (this.smoothingFactor < 1) {
if (this.lastOrientation) {
const k = this.smoothingFactor;
alpha = this._getSmoothedAngle(
if (isIOS) {
const currentQuaternion = new Quaternion();
setObjectQuaternion(currentQuaternion, alpha, beta, gamma, orient);
// Extract the Euler angles from the quaternion and add the heading angle to the Y-axis rotation of the Euler angles
// (If we replace only the alpha value of the quaternion without using Euler angles, the camera will rotate unexpectedly. This is because a quaternion does not represent rotation values individually but rather through a combination of rotation axes and weights.)
const currentEuler = new Euler().setFromQuaternion(
currentQuaternion,
"YXZ",
);
// Replace the current alpha value of the Euler angles and reset the quaternion
currentEuler.y = MathUtils.degToRad(
360 - device.webkitCompassHeading,
);
currentQuaternion.setFromEuler(currentEuler);
scope.object.quaternion.copy(currentQuaternion);
} else {
if (this.smoothingFactor < 1) {
if (this.lastOrientation) {
const k = this.smoothingFactor;
alpha = this._getSmoothedAngle(
alpha,
this.lastOrientation.alpha,
k,
);
beta = this._getSmoothedAngle(
beta + Math.PI,
this.lastOrientation.beta,
k,
);
gamma = this._getSmoothedAngle(
gamma + this.HALF_PI,
this.lastOrientation.gamma,
k,
Math.PI,
);
} else {
beta += Math.PI;
gamma += this.HALF_PI;
}

this.lastOrientation = {
alpha,
this.lastOrientation.alpha,
k,
);
beta = this._getSmoothedAngle(
beta + Math.PI,
this.lastOrientation.beta,
k,
);
gamma = this._getSmoothedAngle(
gamma + this.HALF_PI,
this.lastOrientation.gamma,
k,
Math.PI,
);
} else {
beta += Math.PI;
gamma += this.HALF_PI;
beta,
gamma,
};
}

this.lastOrientation = {
alpha: alpha,
beta: beta,
gamma: gamma,
};
setObjectQuaternion(
scope.object.quaternion,
alpha + theta,
this.smoothingFactor < 1 ? beta - Math.PI : beta,
this.smoothingFactor < 1 ? gamma - this.HALF_PI : gamma,
orient,
);
}

setObjectQuaternion(
scope.object.quaternion,
alpha,
this.smoothingFactor < 1 ? beta - Math.PI : beta,
this.smoothingFactor < 1 ? gamma - this.HALF_PI : gamma,
orient,
);

// NB - NOT present in IOS fixed version issue #466
// Is it needed?
if (8 * (1 - lastQuaternion.dot(scope.object.quaternion)) > EPS) {
lastQuaternion.copy(scope.object.quaternion);
scope.dispatchEvent(_changeEvent);
Expand Down Expand Up @@ -218,12 +285,114 @@ class DeviceOrientationControls extends EventDispatcher {
return newangle;
};

// Provided in fix on issue #466 - iOS related
this.updateAlphaOffset = function () {
scope.initialOffset = false;
};

this.dispose = function () {
scope.disconnect();
};

this.connect();
// provided with fix on issue #466
this.getAlpha = function () {
const { deviceOrientation: device } = scope;
return device && device.alpha
? MathUtils.degToRad(device.alpha) + scope.alphaOffset
: 0;
};

// provided with fix on issue #466
this.getBeta = function () {
const { deviceOrientation: device } = scope;
return device && device.beta ? MathUtils.degToRad(device.beta) : 0;
};

// Provide gesture before initialising device orientation controls
// From PR #659 on the main AR.js repo
// Thanks to @ma2yama
if (
window.DeviceOrientationEvent !== undefined &&
typeof window.DeviceOrientationEvent.requestPermission === "function"
) {
this.initPermissionDialog();
} else {
this.connect();
}
}
}

// Provide gesture before initialising device orientation controls
// From PR #659 on the main AR.js repo
// Thanks to @ma2yama
initPermissionDialog() {
const startModal = document.createElement("div");
const innerDiv = document.createElement("div");
const msgDiv = document.createElement("div");
const btnDiv = document.createElement("div");
document.body.appendChild(startModal);
const startModalStyles = {
display: "flex",
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "100%",
zIndex: 1,
backgroundColor: "rgba(0,0,0,0.6)",
justifyContent: "center",
alignItems: "center",
};
const innerDivStyles = {
backgroundColor: "white",
padding: "6px",
borderRadius: "3px",
width: "18rem",
height: "12rem",
};
const msgDivStyles = {
width: "100%",
height: "70%",
display: "flex",
justifyContent: "center",
alignItems: "center",
};
const btnDivStyles = {
display: "inline-flex",
width: "100%",
height: "30%",
justifyContent: "center",
alignItems: "center",
};
for (let key in startModalStyles) {
startModal.style[key] = startModalStyles[key];
}
for (let key in innerDivStyles) {
innerDiv.style[key] = innerDivStyles[key];
}
for (let key in msgDivStyles) {
msgDiv.style[key] = msgDivStyles[key];
}
for (let key in btnDivStyles) {
btnDiv.style[key] = btnDivStyles[key];
}
startModal.appendChild(innerDiv);
innerDiv.appendChild(msgDiv);
innerDiv.appendChild(btnDiv);
msgDiv.innerHTML =
'<div style="font-size: 14pt; margin: 1rem;">This immersive website requires access to your device motion sensors.</div>';

const onStartClick = () => {
this.connect();
startModal.style.display = "none";
};
const btn = document.createElement("button");
btn.addEventListener("click", onStartClick);
btn.style.width = "50%";
btn.style.height = "80%";
btn.style.fontSize = "12pt";
btn.appendChild(document.createTextNode("OK"));
btnDiv.appendChild(btn);
document.body.appendChild(startModal);
}
}
export { DeviceOrientationControls };