Skip to content

Commit 38c05f2

Browse files
committed
move scene UI code (vrmode/stats) to components
1 parent c423d69 commit 38c05f2

File tree

5 files changed

+373
-266
lines changed

5 files changed

+373
-266
lines changed

‎src/components/scene/fog.js‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
var register = require('../../core/component').registerComponent;
2+
var THREE = require('../../../lib/three');
3+
var debug = require('../../utils/debug');
4+
5+
var warn = debug('components:fog:warn');
6+
7+
/**
8+
* Fog component.
9+
* Applies only to the scene entity.
10+
*/
11+
module.exports.Component = register('fog', {
12+
schema: {
13+
color: { default: '#000' },
14+
density: { default: 0.00025 },
15+
far: { default: 1000, min: 0 },
16+
near: { default: 1, min: 0 },
17+
type: { default: 'linear', oneOf: ['linear', 'exponential'] }
18+
},
19+
20+
update: function () {
21+
var data = this.data;
22+
var el = this.el;
23+
var fog = this.el.object3D.fog;
24+
25+
if (!el.isScene) {
26+
warn('Fog component can only be applied to <a-scene>');
27+
return;
28+
}
29+
30+
// (Re)create fog if fog doesn't exist or fog type changed.
31+
if (!fog || data.type !== fog.name) {
32+
el.object3D.fog = getFog(data);
33+
el.updateMaterials();
34+
return;
35+
}
36+
37+
// Fog data changed. Update fog.
38+
Object.keys(this.schema).forEach(function (key) {
39+
var value = data[key];
40+
if (key === 'color') { value = new THREE.Color(value); }
41+
fog[key] = value;
42+
});
43+
},
44+
45+
/**
46+
* Remove fog on remove (callback).
47+
*/
48+
remove: function () {
49+
var fog = this.el.object3D.fog;
50+
if (fog) {
51+
fog.density = 0;
52+
fog.far = 0;
53+
fog.near = 0;
54+
}
55+
}
56+
});
57+
58+
/**
59+
* Creates a fog object. Sets fog.name to be able to detect fog type changes.
60+
*
61+
* @param {object} data - Fog data.
62+
* @returns {object} fog
63+
*/
64+
function getFog (data) {
65+
var fog;
66+
if (data.type === 'exponential') {
67+
fog = new THREE.FogExp2(data.color, data.density);
68+
} else {
69+
fog = new THREE.Fog(data.color, data.near, data.far);
70+
}
71+
fog.name = data.type;
72+
return fog;
73+
}

‎src/components/scene/stats.js‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
var registerComponent = require('../../core/component').registerComponent;
2+
var RStats = require('../../../lib/vendor/rStats');
3+
4+
var HIDDEN_CLASS = 'a-hidden';
5+
6+
/**
7+
* Stats appended to document.body by RStats.
8+
*/
9+
module.exports.Component = registerComponent('stats', {
10+
init: function () {
11+
var scene = this.el;
12+
13+
this.stats = createStats();
14+
this.statsEl = document.querySelector('.rs-base');
15+
16+
this.hideBound = this.hide.bind(this);
17+
this.showBound = this.show.bind(this);
18+
19+
scene.addEventListener('vrmode-enter', this.hideBound);
20+
scene.addEventListener('vrmode-exit', this.showBound);
21+
scene.addBehavior({
22+
update: this.tick.bind(this)
23+
});
24+
},
25+
26+
remove: function () {
27+
this.el.removeEventListener('vrmode-enter', this.hideBound);
28+
this.el.removeEventListener('vrmode-exit', this.showBound);
29+
this.statsEl.parentNode.removeChild(this.statsEl);
30+
},
31+
32+
tick: function () {
33+
var stats = this.stats;
34+
stats('rAF').tick();
35+
stats('FPS').frame();
36+
stats().update();
37+
},
38+
39+
hide: function () {
40+
this.statsEl.classList.add(HIDDEN_CLASS);
41+
},
42+
43+
show: function () {
44+
this.statsEl.classList.remove(HIDDEN_CLASS);
45+
}
46+
});
47+
48+
function createStats () {
49+
return new RStats({
50+
CSSPath: '../../../style/',
51+
values: {
52+
fps: { caption: 'fps', below: 30 }
53+
},
54+
groups: [
55+
{ caption: 'Framerate', values: [ 'fps', 'raf' ] }
56+
]
57+
});
58+
}

‎src/components/scene/vr-mode-ui.js‎

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
var registerComponent = require('../../core/component').registerComponent;
2+
var THREE = require('../../../lib/three');
3+
var utils = require('../../utils/');
4+
5+
var dummyDolly = new THREE.Object3D();
6+
var controls = new THREE.VRControls(dummyDolly);
7+
8+
var ENTER_VR_CLASS = 'a-enter-vr';
9+
var ENTER_VR_NO_HEADSET = 'data-a-enter-vr-no-headset';
10+
var ENTER_VR_NO_WEBVR = 'data-a-enter-vr-no-webvr';
11+
var ENTER_VR_BTN_CLASS = 'a-enter-vr-button';
12+
var ENTER_VR_MODAL_CLASS = 'a-enter-vr-modal';
13+
var HIDDEN_CLASS = 'a-hidden';
14+
var ORIENTATION_MODAL_CLASS = 'a-orientation-modal';
15+
16+
module.exports.Component = registerComponent('vr-mode-ui', {
17+
dependencies: ['vr-mode'],
18+
19+
schema: {
20+
enabled: { default: true }
21+
},
22+
23+
init: function () {
24+
var isIOS = utils.isIOS();
25+
var self = this;
26+
var scene = this.el;
27+
var vrMode = scene.components['vr-mode'];
28+
29+
this.enterVRBound = vrMode.enterVR.bind(vrMode);
30+
this.exitVRBound = vrMode.exitVR.bind(vrMode);
31+
this.insideLoader = false;
32+
this.enterVREl = null;
33+
this.orientationModalEl = null;
34+
35+
// Hide/show VR UI when entering/exiting VR mode.
36+
scene.addEventListener('vrmode-enter', this.hide.bind(this));
37+
scene.addEventListener('vrmode-exit', this.show.bind(this));
38+
39+
window.addEventListener('message', function (event) {
40+
if (event.data.type === 'loaderReady') {
41+
self.insideLoader = true;
42+
self.remove();
43+
}
44+
});
45+
46+
// Orientational modal toggling on iOS.
47+
window.addEventListener('orientationchange', function () {
48+
if (!isIOS) { return; }
49+
if (!self.orientationModalEl) { return; }
50+
51+
if (!utils.isLandscape() && scene.is('vrmode')) {
52+
// Show if in VR-mode on portrait.
53+
self.orientationModalEl.classList.remove(HIDDEN_CLASS);
54+
} else {
55+
self.orientationModalEl.classList.add(HIDDEN_CLASS);
56+
}
57+
});
58+
},
59+
60+
update: function () {
61+
var scene = this.el;
62+
63+
if (!this.data.enabled || this.insideLoader) { return this.remove(); }
64+
if (this.enterVREl || this.orientationModalEl) { return; }
65+
66+
// Add UI if enabled and not already present.
67+
this.enterVREl = createEnterVR(this.enterVRBound, scene.isMobile);
68+
this.el.appendChild(this.enterVREl);
69+
70+
this.orientationModalEl = createOrientationModal(this.exitVRBound);
71+
this.el.appendChild(this.orientationModalEl);
72+
},
73+
74+
remove: function () {
75+
[this.enterVREl, this.orientationModalEl].forEach(function (uiElement) {
76+
if (uiElement) {
77+
uiElement.parentNode.removeChild(uiElement);
78+
}
79+
});
80+
},
81+
82+
hide: function () {
83+
this.enterVREl.classList.add(HIDDEN_CLASS);
84+
},
85+
86+
show: function () {
87+
this.enterVREl.classList.remove(HIDDEN_CLASS);
88+
}
89+
});
90+
91+
/**
92+
* Creates Enter VR flow (button and compatibility modal).
93+
*
94+
* Creates a button that when clicked will enter into stereo-rendering mode for VR.
95+
*
96+
* For compatibility:
97+
* - Mobile always has compatibility via polyfill.
98+
* - If desktop browser does not have WebVR excluding polyfill, disable button, show modal.
99+
* - If desktop browser has WebVR excluding polyfill but not headset connected,
100+
* don't disable button, but show modal.
101+
* - If desktop browser has WebVR excluding polyfill and has headset connected, then
102+
* then no modal.
103+
*
104+
* Structure: <div><modal/><button></div>
105+
*
106+
* @returns {Element} Wrapper <div>.
107+
*/
108+
function createEnterVR (enterVRHandler, isMobile) {
109+
var compatModal;
110+
var compatModalLink;
111+
var compatModalText;
112+
// window.hasNativeVRSupport is set in src/aframe-core.js.
113+
var hasWebVR = isMobile || window.hasNonPolyfillWebVRSupport;
114+
var orientation;
115+
var vrButton;
116+
var wrapper;
117+
118+
// Create elements.
119+
wrapper = document.createElement('div');
120+
wrapper.classList.add(ENTER_VR_CLASS);
121+
compatModal = document.createElement('div');
122+
compatModal.className = ENTER_VR_MODAL_CLASS;
123+
compatModalText = document.createElement('p');
124+
compatModalLink = document.createElement('a');
125+
compatModalLink.setAttribute('href', 'http://mozvr.com/#start');
126+
compatModalLink.setAttribute('target', '_blank');
127+
compatModalLink.innerHTML = 'Learn more.';
128+
vrButton = document.createElement('button');
129+
vrButton.className = ENTER_VR_BTN_CLASS;
130+
131+
// Insert elements.
132+
if (compatModal) {
133+
compatModal.appendChild(compatModalText);
134+
compatModal.appendChild(compatModalLink);
135+
wrapper.appendChild(compatModal);
136+
}
137+
wrapper.appendChild(vrButton);
138+
139+
if (!checkHeadsetConnected() && !isMobile) {
140+
compatModalText.innerHTML = 'Your browser supports WebVR. To enter VR, connect a headset, or use a mobile phone.';
141+
wrapper.setAttribute(ENTER_VR_NO_HEADSET, '');
142+
}
143+
144+
// Handle enter VR flows.
145+
if (!hasWebVR) {
146+
compatModalText.innerHTML = 'Your browser does not support WebVR. To enter VR, use a VR-compatible browser or a mobile phone.';
147+
wrapper.setAttribute(ENTER_VR_NO_WEBVR, '');
148+
} else {
149+
vrButton.addEventListener('click', enterVRHandler);
150+
}
151+
return wrapper;
152+
153+
/**
154+
* Check for headset connection by looking at orientation {0 0 0}.
155+
*/
156+
function checkHeadsetConnected () {
157+
controls.update();
158+
orientation = dummyDolly.quaternion;
159+
if (orientation._x !== 0 || orientation._y !== 0 || orientation._z !== 0) {
160+
return true;
161+
}
162+
}
163+
}
164+
165+
function createOrientationModal (exitVRHandler) {
166+
var modal = document.createElement('div');
167+
modal.className = ORIENTATION_MODAL_CLASS;
168+
modal.classList.add(HIDDEN_CLASS);
169+
170+
var exit = document.createElement('button');
171+
exit.innerHTML = 'Exit VR';
172+
exit.addEventListener('click', exitVRHandler);
173+
modal.appendChild(exit);
174+
175+
return modal;
176+
}

‎src/components/scene/vr-mode.js‎

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
var registerComponent = require('../../core/component').registerComponent;
2+
var getUrlParameter = require('../../utils/').getUrlParameter;
3+
var THREE = require('../../../lib/three');
4+
5+
module.exports.Component = registerComponent('vr-mode', {
6+
init: function () {
7+
var enterVR = this.enterVR;
8+
var scene = this.el;
9+
10+
this.stereoRenderer = new THREE.VREffect(scene.renderer);
11+
12+
// Keyboard shortcut.
13+
window.addEventListener('keyup', function (event) {
14+
if (event.keyCode === 70) { // f.
15+
enterVR();
16+
}
17+
}, false);
18+
19+
// Check for ?vr parameter.
20+
if (getUrlParameter('mode') === 'vr') {
21+
enterVR();
22+
}
23+
},
24+
25+
enterVR: function () {
26+
var scene = this.el;
27+
scene.addState('vrmode');
28+
29+
scene.emit('vrmode-enter', {
30+
target: scene
31+
});
32+
33+
this.setStereoRenderer();
34+
scene.setFullscreen();
35+
},
36+
37+
exitVR: function () {
38+
var scene = this.el;
39+
scene.removeState('vrmode');
40+
41+
scene.emit('vrmode-exit', {
42+
target: scene
43+
});
44+
45+
scene.setMonoRenderer();
46+
},
47+
48+
/**
49+
* Sets renderer to stereo (two eyes) and resizes canvas.
50+
*/
51+
setStereoRenderer: function () {
52+
var scene = this.el;
53+
scene.renderer = this.stereoRenderer;
54+
// TODO: canvas component.
55+
scene.resizeCanvas();
56+
}
57+
});

0 commit comments

Comments
 (0)