aframe
Version:
A web framework for building virtual reality experiences.
272 lines (231 loc) • 8.58 kB
JavaScript
import * as constants from '../constants/index.js';
import { registerSystem } from '../core/system.js';
var DEFAULT_CAMERA_ATTR = 'data-aframe-default-camera';
/**
* Camera system. Manages which camera is active among multiple cameras in scene.
*
* @member {object} activeCameraEl - Active camera entity.
*/
export var System = registerSystem('camera', {
init: function () {
this.activeCameraEl = null;
this.render = this.render.bind(this);
this.unwrapRender = this.unwrapRender.bind(this);
this.wrapRender = this.wrapRender.bind(this);
this.initialCameraFound = false;
this.numUserCameras = 0;
this.numUserCamerasChecked = 0;
this.setupInitialCamera();
},
/**
* Setup initial camera, either searching for camera or
* creating a default camera if user has not added one during the initial scene traversal.
* We want sceneEl.camera to be ready, set, and initialized before the rest of the scene
* loads.
*
* Default camera offset height is at average eye level (~1.6m).
*/
setupInitialCamera: function () {
var cameraEls;
var i;
var sceneEl = this.sceneEl;
var self = this;
// Camera already defined or the one defined it is an spectator one.
if (sceneEl.camera && !sceneEl.camera.el.getAttribute('camera').spectator) {
sceneEl.emit('cameraready', {cameraEl: sceneEl.camera.el});
return;
}
// Search for initial user-defined camera.
cameraEls = sceneEl.querySelectorAll('a-camera, :not(a-mixin)[camera]');
// No user cameras, create default one.
if (!cameraEls.length) {
this.createDefaultCamera();
return;
}
this.numUserCameras = cameraEls.length;
for (i = 0; i < cameraEls.length; i++) {
cameraEls[i].addEventListener('object3dset', function (evt) {
if (evt.detail.type !== 'camera') { return; }
self.checkUserCamera(this);
});
// Load camera and wait for camera to initialize.
if (cameraEls[i].isANode) {
cameraEls[i].load();
} else {
cameraEls[i].addEventListener('nodeready', function () {
this.load();
});
}
}
},
/**
* Check if a user-defined camera entity is appropriate to be initial camera.
* (active + non-spectator).
*
* Keep track of the number of cameras we checked and whether we found one.
*/
checkUserCamera: function (cameraEl) {
var cameraData;
var sceneEl = this.el.sceneEl;
this.numUserCamerasChecked++;
// Already found one.
if (this.initialCameraFound) { return; }
// Check if camera is appropriate for being the initial camera.
cameraData = cameraEl.getAttribute('camera');
if (!cameraData.active || cameraData.spectator) {
// No user cameras eligible, create default camera.
if (this.numUserCamerasChecked === this.numUserCameras) {
this.createDefaultCamera();
}
return;
}
this.initialCameraFound = true;
sceneEl.camera = cameraEl.getObject3D('camera');
sceneEl.emit('cameraready', {cameraEl: cameraEl});
},
createDefaultCamera: function () {
var defaultCameraEl;
var sceneEl = this.sceneEl;
// Set up default camera.
defaultCameraEl = document.createElement('a-entity');
defaultCameraEl.setAttribute('camera', {active: true});
defaultCameraEl.setAttribute('position', {
x: 0,
y: constants.DEFAULT_CAMERA_HEIGHT,
z: 0
});
defaultCameraEl.setAttribute('wasd-controls', '');
defaultCameraEl.setAttribute('look-controls', '');
defaultCameraEl.setAttribute(constants.AFRAME_INJECTED, '');
defaultCameraEl.addEventListener('object3dset', function (evt) {
if (evt.detail.type !== 'camera') { return; }
sceneEl.camera = evt.detail.object;
sceneEl.emit('cameraready', {cameraEl: defaultCameraEl});
});
sceneEl.appendChild(defaultCameraEl);
},
/**
* Set a different active camera.
* When we choose a (sort of) random scene camera as the replacement, set its `active` to
* true. The camera component will call `setActiveCamera` and handle passing the torch to
* the new camera.
*/
disableActiveCamera: function () {
var cameraEls;
var newActiveCameraEl;
cameraEls = this.sceneEl.querySelectorAll(':not(a-mixin)[camera]');
newActiveCameraEl = cameraEls[cameraEls.length - 1];
newActiveCameraEl.setAttribute('camera', 'active', true);
},
/**
* Set active camera to be used by renderer.
* Removes the default camera (if present).
* Disables all other cameras in the scene.
*
* @param {Element} newCameraEl - Entity with camera component.
*/
setActiveCamera: function (newCameraEl) {
var cameraEl;
var cameraEls;
var i;
var newCamera;
var previousCamera = this.activeCameraEl;
var sceneEl = this.sceneEl;
// Same camera.
newCamera = newCameraEl.getObject3D('camera');
if (!newCamera || newCameraEl === this.activeCameraEl) { return; }
// Grab the default camera.
var defaultCameraWrapper = sceneEl.querySelector('[' + DEFAULT_CAMERA_ATTR + ']');
var defaultCameraEl = defaultCameraWrapper &&
defaultCameraWrapper.querySelector(':not(a-mixin)[camera]');
// Remove default camera if new camera is not the default camera.
if (newCameraEl !== defaultCameraEl) { removeDefaultCamera(sceneEl); }
// Make new camera active.
this.activeCameraEl = newCameraEl;
this.activeCameraEl.play();
sceneEl.camera = newCamera;
// Disable current camera
if (previousCamera) {
previousCamera.setAttribute('camera', 'active', false);
}
// Disable other cameras in the scene
cameraEls = sceneEl.querySelectorAll(':not(a-mixin)[camera]');
for (i = 0; i < cameraEls.length; i++) {
cameraEl = cameraEls[i];
if (!cameraEl.isEntity || newCameraEl === cameraEl) { continue; }
cameraEl.setAttribute('camera', 'active', false);
cameraEl.pause();
}
sceneEl.emit('camera-set-active', {cameraEl: newCameraEl});
},
/**
* Set spectator camera to render the scene on a 2D display.
*
* @param {Element} newCameraEl - Entity with camera component.
*/
setSpectatorCamera: function (newCameraEl) {
var newCamera;
var previousCamera = this.spectatorCameraEl;
var sceneEl = this.sceneEl;
var spectatorCameraEl;
// Same camera.
newCamera = newCameraEl.getObject3D('camera');
if (!newCamera || newCameraEl === this.spectatorCameraEl) { return; }
// Disable current camera
if (previousCamera) {
previousCamera.setAttribute('camera', 'spectator', false);
}
spectatorCameraEl = this.spectatorCameraEl = newCameraEl;
sceneEl.addEventListener('enter-vr', this.wrapRender);
sceneEl.addEventListener('exit-vr', this.unwrapRender);
spectatorCameraEl.setAttribute('camera', 'active', false);
spectatorCameraEl.play();
sceneEl.emit('camera-set-spectator', {cameraEl: newCameraEl});
},
/**
* Disables current spectator camera.
*/
disableSpectatorCamera: function () {
this.spectatorCameraEl = undefined;
},
/**
* Wrap the render method of the renderer to render
* the spectator camera.
*/
wrapRender: function () {
if (!this.spectatorCameraEl || this.originalRender) { return; }
this.originalRender = this.sceneEl.renderer.render;
this.sceneEl.renderer.render = this.render;
},
unwrapRender: function () {
if (!this.originalRender) { return; }
this.sceneEl.renderer.render = this.originalRender;
this.originalRender = undefined;
},
render: function (scene, camera) {
var isVREnabled;
var sceneEl = this.sceneEl;
var spectatorCamera;
isVREnabled = sceneEl.renderer.xr.enabled;
this.originalRender.call(sceneEl.renderer, scene, camera);
if (!this.spectatorCameraEl || sceneEl.isMobile || !isVREnabled) { return; }
spectatorCamera = this.spectatorCameraEl.components.camera.camera;
sceneEl.renderer.xr.enabled = false;
this.originalRender.call(sceneEl.renderer, scene, spectatorCamera);
sceneEl.renderer.xr.enabled = isVREnabled;
}
});
/**
* Remove injected default camera from scene, if present.
*
* @param {Element} sceneEl
*/
function removeDefaultCamera (sceneEl) {
var defaultCamera;
var camera = sceneEl.camera;
if (!camera) { return; }
// Remove default camera if present.
defaultCamera = sceneEl.querySelector('[' + DEFAULT_CAMERA_ATTR + ']');
if (!defaultCamera) { return; }
sceneEl.removeChild(defaultCamera);
}