Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/core/utils.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,21 @@ diff({a: 1, b: 2, c: 3}, {b: 2, c: 4})
### `AFRAME.utils.extendDeep(target, source, [source, ...])`

[Deep Assign](https://www.npmjs.com/package/deep-assign)

## `AFRAME.utils.device`

### `AFRAME.utils.device.checkHasPositionalTracking()`

Checks if there is positional tracking available. Returns a `boolean`.

### `AFRAME.utils.device.checkHeadsetConnected()`

Checks if a VR headset is connected by looking for orientation data. Returns a `boolean`.

### `AFRAME.utils.device.isGearVR()`

Checks if device is Gear VR. Returns a `boolean`.

### `AFRAME.utils.device.isMobile()`

Checks if device is a smartphone. Returns a `boolean`.
22 changes: 10 additions & 12 deletions src/components/camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ var THREE = require('../lib/three');
var utils = require('../utils/');
var bind = utils.bind;

var checkHeadsetConnected = utils.checkHeadsetConnected;
var checkHasPositionalTracking = utils.device.checkHasPositionalTracking;

/**
* Camera component.
Expand Down Expand Up @@ -121,20 +121,18 @@ module.exports.Component = registerComponent('camera', {
/**
* Remove the height offset (called when entering VR) since WebVR API gives absolute
* position.
* Does not apply for mobile.
*/
removeHeightOffset: function () {
var currentPosition;
var el = this.el;
var headsetConnected;
var sceneEl = el.sceneEl;
var hasPositionalTracking;
var userHeightOffset = this.data.userHeight;

// If there's not a headset connected we keep the offset.
// Remove the offset if there is positional tracking when entering VR.
// Necessary for fullscreen mode with no headset.
// Checking this.headsetConnected to make the value injectable for unit tests.
headsetConnected = this.headsetConnected || checkHeadsetConnected();
if (sceneEl.isMobile || !userHeightOffset || !headsetConnected) { return; }
// Checking this.hasPositionalTracking to make the value injectable for unit tests.
hasPositionalTracking = this.hasPositionalTracking || checkHasPositionalTracking();
if (!userHeightOffset || !hasPositionalTracking) { return; }

currentPosition = el.getAttribute('position') || {x: 0, y: 0, z: 0};
el.setAttribute('position', {
Expand All @@ -149,9 +147,9 @@ module.exports.Component = registerComponent('camera', {
*/
saveCameraPose: function () {
var el = this.el;
var headsetConnected = this.headsetConnected || checkHeadsetConnected();
var hasPositionalTracking = this.hasPositionalTracking || checkHasPositionalTracking();

if (this.savedPose || !headsetConnected) { return; }
if (this.savedPose || !hasPositionalTracking) { return; }

this.savedPose = {
position: el.getAttribute('position'),
Expand All @@ -165,9 +163,9 @@ module.exports.Component = registerComponent('camera', {
restoreCameraPose: function () {
var el = this.el;
var savedPose = this.savedPose;
var headsetConnected = this.headsetConnected || checkHeadsetConnected();
var hasPositionalTracking = this.hasPositionalTracking || checkHasPositionalTracking();

if (!savedPose || !headsetConnected) { return; }
if (!savedPose || !hasPositionalTracking) { return; }

// Reset camera orientation.
el.setAttribute('position', savedPose.position);
Expand Down
2 changes: 1 addition & 1 deletion src/components/cursor.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ module.exports.Component = registerComponent('cursor', {
dependencies: ['raycaster'],

schema: {
fuse: {default: utils.isMobile()},
fuse: {default: utils.device.isMobile()},
fuseTimeout: {default: 1500, min: 0}
},

Expand Down
2 changes: 1 addition & 1 deletion src/components/look-controls.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
var registerComponent = require('../core/component').registerComponent;
var THREE = require('../lib/three');
var isMobile = require('../utils/').isMobile();
var isMobile = require('../utils/').device.isMobile();
var bind = require('../utils/bind');

// To avoid recalculation at every mouse movement tick
Expand Down
2 changes: 1 addition & 1 deletion src/components/scene/vr-mode-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ module.exports.Component = registerComponent('vr-mode-ui', {
var sceneEl = this.el;
var orientationModalEl = this.orientationModalEl;
if (!orientationModalEl || !sceneEl.isMobile) { return; }
if (!utils.isLandscape() && sceneEl.is('vr-mode')) {
if (!utils.device.isLandscape() && sceneEl.is('vr-mode')) {
// Show if in VR mode on portrait.
orientationModalEl.classList.remove(HIDDEN_CLASS);
} else {
Expand Down
6 changes: 3 additions & 3 deletions src/core/scene/a-scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ var ANode = require('../a-node');
var initPostMessageAPI = require('./postMessage');

var bind = utils.bind;
var checkHeadsetConnected = utils.checkHeadsetConnected;
var checkHeadsetConnected = utils.device.checkHeadsetConnected;
var isIOS = utils.device.isIOS();
var isMobile = utils.device.isMobile();
var registerElement = re.registerElement;
var isIOS = utils.isIOS();
var isMobile = utils.isMobile();
var warn = utils.debug('core:a-scene:warn');

/**
Expand Down
16 changes: 0 additions & 16 deletions src/utils/checkHeadsetConnected.js

This file was deleted.

76 changes: 76 additions & 0 deletions src/utils/device.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
var THREE = require('../lib/three');
var dolly = new THREE.Object3D();
var controls = new THREE.VRControls(dolly);

/**
* Determine if a headset is connected by checking if the
* orientation is available
*/
function checkHeadsetConnected () {
var orientation;
controls.update();
orientation = dolly.quaternion;
if (orientation._x !== 0 || orientation._y !== 0 || orientation._z !== 0) {
return true;
}
return false;
}
module.exports.checkHeadsetConnected = checkHeadsetConnected;

/**
* Check for positional tracking.
*/
function checkHasPositionalTracking () {
var position = new THREE.Vector3();
return (function () {
if (isMobile() || isGearVR()) { return false; }
controls.update();
dolly.updateMatrix();
position.setFromMatrixPosition(dolly.matrix);
if (position.x !== 0 || position.y !== 0 || position.z !== 0) {
return true;
}
return false;
})();
}
module.exports.checkHasPositionalTracking = checkHasPositionalTracking;

/**
* Checks if browser is mobile.
* @return {Boolean} True if mobile browser detected.
*/
var isMobile = (function () {
var _isMobile = false;
(function (a) {
if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) {
_isMobile = true;
}
if (isIOS()) {
_isMobile = true;
}
if (isGearVR()) {
_isMobile = false;
}
})(navigator.userAgent || navigator.vendor || window.opera);

return function () { return _isMobile; };
})();
module.exports.isMobile = isMobile;

function isIOS () {
return /iPad|iPhone|iPod/.test(navigator.platform);
}
module.exports.isIOS = isIOS;

function isGearVR () {
return /SamsungBrowser.+Mobile VR/i.test(navigator.userAgent);
}
module.exports.isGearVR = isGearVR;

/**
* Checks mobile device orientation.
* @return {Boolean} True if landscape orientation.
*/
module.exports.isLandscape = function () {
return window.orientation === 90 || window.orientation === -90;
};
38 changes: 1 addition & 37 deletions src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ var objectAssign = require('object-assign');

module.exports.bind = require('./bind');
module.exports.coordinates = require('./coordinates');
module.exports.checkHeadsetConnected = require('./checkHeadsetConnected');
module.exports.debug = require('./debug');
module.exports.device = require('./device');
module.exports.entity = require('./entity');
module.exports.forceCanvasResizeSafariMobile = require('./forceCanvasResizeSafariMobile');
module.exports.material = require('./material');
Expand Down Expand Up @@ -89,42 +89,6 @@ module.exports.diff = function (a, b) {
return diff;
};

/**
* Checks if browser is mobile.
* @return {Boolean} True if mobile browser detected.
*/
module.exports.isMobile = function () {
var check = false;
(function (a) {
if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) {
check = true;
}
if (isIOS()) {
check = true;
}
if (isGearVR()) {
check = false;
}
})(navigator.userAgent || navigator.vendor || window.opera);
return check;
};

var isIOS = module.exports.isIOS = function () {
return /iPad|iPhone|iPod/.test(navigator.platform);
};

var isGearVR = module.exports.isGearVR = function () {
return /SamsungBrowser.+Mobile VR/i.test(navigator.userAgent);
};

/**
* Checks mobile device orientation.
* @return {Boolean} True if landscape orientation.
*/
module.exports.isLandscape = function () {
return window.orientation === 90 || window.orientation === -90;
};

/**
* Returns whether we should capture this keyboard event for keyboard shortcuts.
* @param {Event} event Event object.
Expand Down
Empty file added src/utils/mobile.js
Empty file.
37 changes: 10 additions & 27 deletions tests/components/camera.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,19 @@ suite('camera', function () {
});

suite('saveCameraPose', function () {
test('saves camera pose when entering VR with headset', function () {
test('saves camera pose when entering VR w/ positional tracking', function () {
var cameraEl = this.el;
var sceneEl = cameraEl.sceneEl;
cameraEl.components.camera.headsetConnected = true;
cameraEl.components.camera.hasPositionalTracking = true;
sceneEl.emit('enter-vr');
assert.shallowDeepEqual(cameraEl.components.camera.savedPose.position,
{x: 0, y: 1.6, z: 0});
});

test('does not save camera pose when entering VR without headset', function () {
test('does not save camera pose when entering VR w/o positional tracking', function () {
var cameraEl = this.el;
var sceneEl = cameraEl.sceneEl;
cameraEl.components.camera.headsetConnected = false;
cameraEl.components.camera.hasPositionalTracking = false;
sceneEl.emit('enter-vr');
assert.notOk(cameraEl.components.camera.savedPose);
});
Expand All @@ -70,39 +70,22 @@ suite('camera', function () {
var cameraEl = this.el;
assert.shallowDeepEqual(cameraEl.getAttribute('position'), {x: 0, y: 1.6, z: 0});
});

test('adds userHeight offset on mobile', function () {
var cameraEl = this.el;
var sceneEl = cameraEl.sceneEl;
sceneEl.isMobile = true;
assert.shallowDeepEqual(cameraEl.getAttribute('position'), {x: 0, y: 1.6, z: 0});
});
});

suite('removeCameraPose (enter VR)', function () {
test('removes the default offset with headset', function () {
test('removes the default offset w/ positional tracking', function () {
var cameraEl = this.el;
var sceneEl = cameraEl.sceneEl;
cameraEl.components.camera.headsetConnected = true;
cameraEl.components.camera.hasPositionalTracking = true;
assert.shallowDeepEqual(cameraEl.getAttribute('position'), {x: 0, y: 1.6, z: 0});
sceneEl.emit('enter-vr');
assert.shallowDeepEqual(cameraEl.getAttribute('position'), {x: 0, y: 0, z: 0});
});

test('does not remove the default offset without headset', function () {
var cameraEl = this.el;
var sceneEl = cameraEl.sceneEl;
cameraEl.components.camera.headsetConnected = false;
assert.shallowDeepEqual(cameraEl.getAttribute('position'), {x: 0, y: 1.6, z: 0});
sceneEl.emit('enter-vr');
assert.shallowDeepEqual(cameraEl.getAttribute('position'), {x: 0, y: 1.6, z: 0});
});

test('does not remove the default offset on mobile', function () {
test('does not remove the default offset w/o positional tracking', function () {
var cameraEl = this.el;
var sceneEl = cameraEl.sceneEl;
sceneEl.isMobile = true;
cameraEl.components.camera.headsetConnected = true;
cameraEl.components.camera.hasPositionalTracking = false;
assert.shallowDeepEqual(cameraEl.getAttribute('position'), {x: 0, y: 1.6, z: 0});
sceneEl.emit('enter-vr');
assert.shallowDeepEqual(cameraEl.getAttribute('position'), {x: 0, y: 1.6, z: 0});
Expand All @@ -113,7 +96,7 @@ suite('camera', function () {
test('restores camera pose with headset', function () {
var cameraEl = this.el;
var sceneEl = cameraEl.sceneEl;
cameraEl.components.camera.headsetConnected = true;
cameraEl.components.camera.hasPositionalTracking = true;
sceneEl.emit('enter-vr');
cameraEl.setAttribute('position', {x: 6, y: 6, z: 6});
sceneEl.emit('exit-vr');
Expand All @@ -123,7 +106,7 @@ suite('camera', function () {
test('does not restore camera pose without headset', function () {
var sceneEl = this.el.sceneEl;
var cameraEl = this.el;
cameraEl.components.camera.headsetConnected = false;
cameraEl.components.camera.hasPositionalTracking = false;
sceneEl.emit('enter-vr');
cameraEl.setAttribute('position', {x: 6, y: 6, z: 6});
sceneEl.emit('exit-vr');
Expand Down