whs.js/modules/VRKit.module.js
Alexander Buzin 3903dab29f 2.1.9
2017-12-05 23:54:46 +02:00

882 lines
22 KiB
JavaScript

/* Built for whs v2.1.9 */
import { ControlsModule } from 'whs';
import { Matrix4, PerspectiveCamera, REVISION, Vector3 } from 'three';
/**
* @author dmarcos / https://github.com/dmarcos
* @author mrdoob / http://mrdoob.com
*
* WebVR Spec: http://mozvr.github.io/webvr-spec/webvr.html
*
* Firefox: http://mozvr.com/downloads/
* Chromium: https://webvr.info/get-chrome
*
*/
var VREffect = function VREffect(renderer, onError) {
var vrDisplay, vrDisplays;
var eyeTranslationL = new Vector3();
var eyeTranslationR = new Vector3();
var renderRectL, renderRectR;
var frameData = null;
if ('VRFrameData' in window) {
frameData = new VRFrameData();
}
function gotVRDisplays(displays) {
vrDisplays = displays;
if (displays.length > 0) {
vrDisplay = displays[0];
} else {
if (onError) onError('HMD not available');
}
}
if (navigator.getVRDisplays) {
navigator.getVRDisplays().then(gotVRDisplays).catch(function () {
console.warn('THREE.VREffect: Unable to get VR Displays');
});
}
//
this.isPresenting = false;
this.scale = 1;
var scope = this;
var rendererSize = renderer.getSize();
var rendererUpdateStyle = false;
var rendererPixelRatio = renderer.getPixelRatio();
this.getVRDisplay = function () {
return vrDisplay;
};
this.setVRDisplay = function (value) {
vrDisplay = value;
};
this.getVRDisplays = function () {
console.warn('THREE.VREffect: getVRDisplays() is being deprecated.');
return vrDisplays;
};
this.setSize = function (width, height, updateStyle) {
rendererSize = { width: width, height: height };
rendererUpdateStyle = updateStyle;
if (scope.isPresenting) {
var eyeParamsL = vrDisplay.getEyeParameters('left');
renderer.setPixelRatio(1);
renderer.setSize(eyeParamsL.renderWidth * 2, eyeParamsL.renderHeight, false);
} else {
renderer.setPixelRatio(rendererPixelRatio);
renderer.setSize(width, height, updateStyle);
}
};
// fullscreen
var canvas = renderer.domElement;
var requestFullscreen;
var exitFullscreen;
var fullscreenElement;
var defaultLeftBounds = [0.0, 0.0, 0.5, 1.0];
var defaultRightBounds = [0.5, 0.0, 0.5, 1.0];
function onVRDisplayPresentChange() {
var wasPresenting = scope.isPresenting;
scope.isPresenting = vrDisplay !== undefined && vrDisplay.isPresenting;
if (scope.isPresenting) {
var eyeParamsL = vrDisplay.getEyeParameters('left');
var eyeWidth = eyeParamsL.renderWidth;
var eyeHeight = eyeParamsL.renderHeight;
if (!wasPresenting) {
rendererPixelRatio = renderer.getPixelRatio();
rendererSize = renderer.getSize();
renderer.setPixelRatio(1);
renderer.setSize(eyeWidth * 2, eyeHeight, false);
}
} else if (wasPresenting) {
renderer.setPixelRatio(rendererPixelRatio);
renderer.setSize(rendererSize.width, rendererSize.height, rendererUpdateStyle);
}
}
window.addEventListener('vrdisplaypresentchange', onVRDisplayPresentChange, false);
this.setFullScreen = function (boolean) {
return new Promise(function (resolve, reject) {
if (vrDisplay === undefined) {
reject(new Error('No VR hardware found.'));
return;
}
if (scope.isPresenting === boolean) {
resolve();
return;
}
if (boolean) {
resolve(vrDisplay.requestPresent([{ source: canvas }]));
} else {
resolve(vrDisplay.exitPresent());
}
});
};
this.requestPresent = function () {
return this.setFullScreen(true);
};
this.exitPresent = function () {
return this.setFullScreen(false);
};
this.requestAnimationFrame = function (f) {
if (vrDisplay !== undefined) {
return vrDisplay.requestAnimationFrame(f);
} else {
return window.requestAnimationFrame(f);
}
};
this.cancelAnimationFrame = function (h) {
if (vrDisplay !== undefined) {
vrDisplay.cancelAnimationFrame(h);
} else {
window.cancelAnimationFrame(h);
}
};
this.submitFrame = function () {
if (vrDisplay !== undefined && scope.isPresenting) {
vrDisplay.submitFrame();
}
};
this.autoSubmitFrame = true;
// render
var cameraL = new PerspectiveCamera();
cameraL.layers.enable(1);
var cameraR = new PerspectiveCamera();
cameraR.layers.enable(2);
this.render = function (scene, camera, renderTarget, forceClear) {
if (vrDisplay && scope.isPresenting) {
var autoUpdate = scene.autoUpdate;
if (autoUpdate) {
scene.updateMatrixWorld();
scene.autoUpdate = false;
}
var eyeParamsL = vrDisplay.getEyeParameters('left');
var eyeParamsR = vrDisplay.getEyeParameters('right');
eyeTranslationL.fromArray(eyeParamsL.offset);
eyeTranslationR.fromArray(eyeParamsR.offset);
if (Array.isArray(scene)) {
console.warn('THREE.VREffect.render() no longer supports arrays. Use object.layers instead.');
scene = scene[0];
}
// When rendering we don't care what the recommended size is, only what the actual size
// of the backbuffer is.
var size = renderer.getSize();
var layers = vrDisplay.getLayers();
var leftBounds;
var rightBounds;
if (layers.length) {
var layer = layers[0];
leftBounds = layer.leftBounds !== null && layer.leftBounds.length === 4 ? layer.leftBounds : defaultLeftBounds;
rightBounds = layer.rightBounds !== null && layer.rightBounds.length === 4 ? layer.rightBounds : defaultRightBounds;
} else {
leftBounds = defaultLeftBounds;
rightBounds = defaultRightBounds;
}
renderRectL = {
x: Math.round(size.width * leftBounds[0]),
y: Math.round(size.height * leftBounds[1]),
width: Math.round(size.width * leftBounds[2]),
height: Math.round(size.height * leftBounds[3])
};
renderRectR = {
x: Math.round(size.width * rightBounds[0]),
y: Math.round(size.height * rightBounds[1]),
width: Math.round(size.width * rightBounds[2]),
height: Math.round(size.height * rightBounds[3])
};
if (renderTarget) {
renderer.setRenderTarget(renderTarget);
renderTarget.scissorTest = true;
} else {
renderer.setRenderTarget(null);
renderer.setScissorTest(true);
}
if (renderer.autoClear || forceClear) renderer.clear();
if (camera.parent === null) camera.updateMatrixWorld();
camera.matrixWorld.decompose(cameraL.position, cameraL.quaternion, cameraL.scale);
camera.matrixWorld.decompose(cameraR.position, cameraR.quaternion, cameraR.scale);
var scale = this.scale;
cameraL.translateOnAxis(eyeTranslationL, scale);
cameraR.translateOnAxis(eyeTranslationR, scale);
if (vrDisplay.getFrameData) {
vrDisplay.depthNear = camera.near;
vrDisplay.depthFar = camera.far;
vrDisplay.getFrameData(frameData);
cameraL.projectionMatrix.elements = frameData.leftProjectionMatrix;
cameraR.projectionMatrix.elements = frameData.rightProjectionMatrix;
} else {
cameraL.projectionMatrix = fovToProjection(eyeParamsL.fieldOfView, true, camera.near, camera.far);
cameraR.projectionMatrix = fovToProjection(eyeParamsR.fieldOfView, true, camera.near, camera.far);
}
// render left eye
if (renderTarget) {
renderTarget.viewport.set(renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height);
renderTarget.scissor.set(renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height);
} else {
renderer.setViewport(renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height);
renderer.setScissor(renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height);
}
renderer.render(scene, cameraL, renderTarget, forceClear);
// render right eye
if (renderTarget) {
renderTarget.viewport.set(renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height);
renderTarget.scissor.set(renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height);
} else {
renderer.setViewport(renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height);
renderer.setScissor(renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height);
}
renderer.render(scene, cameraR, renderTarget, forceClear);
if (renderTarget) {
renderTarget.viewport.set(0, 0, size.width, size.height);
renderTarget.scissor.set(0, 0, size.width, size.height);
renderTarget.scissorTest = false;
renderer.setRenderTarget(null);
} else {
renderer.setViewport(0, 0, size.width, size.height);
renderer.setScissorTest(false);
}
if (autoUpdate) {
scene.autoUpdate = true;
}
if (scope.autoSubmitFrame) {
scope.submitFrame();
}
return;
}
// Regular render mode if not HMD
renderer.render(scene, camera, renderTarget, forceClear);
};
this.dispose = function () {
window.removeEventListener('vrdisplaypresentchange', onVRDisplayPresentChange, false);
};
//
function fovToNDCScaleOffset(fov) {
var pxscale = 2.0 / (fov.leftTan + fov.rightTan);
var pxoffset = (fov.leftTan - fov.rightTan) * pxscale * 0.5;
var pyscale = 2.0 / (fov.upTan + fov.downTan);
var pyoffset = (fov.upTan - fov.downTan) * pyscale * 0.5;
return { scale: [pxscale, pyscale], offset: [pxoffset, pyoffset] };
}
function fovPortToProjection(fov, rightHanded, zNear, zFar) {
rightHanded = rightHanded === undefined ? true : rightHanded;
zNear = zNear === undefined ? 0.01 : zNear;
zFar = zFar === undefined ? 10000.0 : zFar;
var handednessScale = rightHanded ? -1.0 : 1.0;
// start with an identity matrix
var mobj = new Matrix4();
var m = mobj.elements;
// and with scale/offset info for normalized device coords
var scaleAndOffset = fovToNDCScaleOffset(fov);
// X result, map clip edges to [-w,+w]
m[0 * 4 + 0] = scaleAndOffset.scale[0];
m[0 * 4 + 1] = 0.0;
m[0 * 4 + 2] = scaleAndOffset.offset[0] * handednessScale;
m[0 * 4 + 3] = 0.0;
// Y result, map clip edges to [-w,+w]
// Y offset is negated because this proj matrix transforms from world coords with Y=up,
// but the NDC scaling has Y=down (thanks D3D?)
m[1 * 4 + 0] = 0.0;
m[1 * 4 + 1] = scaleAndOffset.scale[1];
m[1 * 4 + 2] = -scaleAndOffset.offset[1] * handednessScale;
m[1 * 4 + 3] = 0.0;
// Z result (up to the app)
m[2 * 4 + 0] = 0.0;
m[2 * 4 + 1] = 0.0;
m[2 * 4 + 2] = zFar / (zNear - zFar) * -handednessScale;
m[2 * 4 + 3] = zFar * zNear / (zNear - zFar);
// W result (= Z in)
m[3 * 4 + 0] = 0.0;
m[3 * 4 + 1] = 0.0;
m[3 * 4 + 2] = handednessScale;
m[3 * 4 + 3] = 0.0;
mobj.transpose();
return mobj;
}
function fovToProjection(fov, rightHanded, zNear, zFar) {
var DEG2RAD = Math.PI / 180.0;
var fovPort = {
upTan: Math.tan(fov.upDegrees * DEG2RAD),
downTan: Math.tan(fov.downDegrees * DEG2RAD),
leftTan: Math.tan(fov.leftDegrees * DEG2RAD),
rightTan: Math.tan(fov.rightDegrees * DEG2RAD)
};
return fovPortToProjection(fovPort, rightHanded, zNear, zFar);
}
};
/**
* @author dmarcos / https://github.com/dmarcos
* @author mrdoob / http://mrdoob.com
* @author halvves / https://github.com/halvves (i only es6 moduled it)
*/
class VRControls$1 {
constructor(camera, onError) {
this.camera = camera;
this.vrDisplay;
this.vrDisplays;
this.standingMatrix = new Matrix4();
this.frameData = null;
if ('VRFrameData' in window) {
this.frameData = new VRFrameData();
}
if (navigator.getVRDisplays) {
navigator
.getVRDisplays()
.then((displays) => {
this.vrDisplays = displays;
if (displays.length > 0) {
this.vrDisplay = displays[0];
} else {
if (onError) onError('VR input not available.');
}
})
.catch(() => {
console.warn('VRControls: Unable to get VR Displays');
});
}
// the Rift SDK returns the position in meters
// this scale factor allows the user to define how meters
// are converted to scene units.
this.scale = 1;
// If true will use "standing space" coordinate system where y=0 is the
// floor and x=0, z=0 is the center of the room.
this.standing = false;
// Distance from the users eyes to the floor in meters. Used when
// standing=true but the VRDisplay doesn't provide stageParameters.
this.userHeight = 1.6;
}
getVRDisplay() {
return this.vrDisplay;
};
setVRDisplay(value) {
this.vrDisplay = value;
};
getVRDisplays() {
console.warn('VRControls: getVRDisplays() is being deprecated.');
return this.vrDisplays;
};
getStandingMatrix() {
return this.standingMatrix;
};
update() {
const camera = this.camera;
if (this.vrDisplay) {
let pose;
if (this.vrDisplay.getFrameData) {
this.vrDisplay.getFrameData(this.frameData);
pose = this.frameData.pose;
} else if (this.vrDisplay.getPose) {
pose = this.vrDisplay.getPose();
}
if (pose.orientation !== null) {
camera.quaternion.fromArray(pose.orientation);
}
if (pose.position !== null) {
camera.position.fromArray(pose.position);
} else {
camera.position.set(0, 0, 0);
}
if (this.standing) {
if (this.vrDisplay.stageParameters) {
camera.updateMatrix();
this.standingMatrix.fromArray(this.vrDisplay.stageParameters.sittingToStandingTransform);
camera.applyMatrix(this.standingMatrix);
} else {
camera.position.setY(camera.position.y + this.userHeight);
}
}
camera.position.multiplyScalar(this.scale);
}
};
dispose() {
this.vrDisplay = null;
};
}
/**
* @author mrdoob / http://mrdoob.com
* @author Mugen87 / https://github.com/Mugen87
*
* Based on @tojiro's vr-samples-utils.js
*/
var WEBVR = {
isAvailable: function isAvailable() {
console.warn('WEBVR: isAvailable() is being deprecated. Use .checkAvailability() instead.');
return navigator.getVRDisplays !== undefined;
},
checkAvailability: function checkAvailability() {
return new Promise(function (resolve, reject) {
if (navigator.getVRDisplays !== undefined) {
navigator.getVRDisplays().then(function (displays) {
if (displays.length === 0) {
reject('WebVR supported, but no VRDisplays found.');
} else {
resolve();
}
});
} else {
reject('Your browser does not support WebVR. See <a href="https://webvr.info">webvr.info</a> for assistance.');
}
});
},
getVRDisplay: function getVRDisplay(onDisplay) {
if ('getVRDisplays' in navigator) {
navigator.getVRDisplays().then(function (displays) {
onDisplay(displays[0]);
});
}
},
getMessage: function getMessage() {
console.warn('WEBVR: getMessage() is being deprecated. Use .getMessageContainer( message ) instead.');
var message;
if (navigator.getVRDisplays) {
navigator.getVRDisplays().then(function (displays) {
if (displays.length === 0) message = 'WebVR supported, but no VRDisplays found.';
});
} else {
message = 'Your browser does not support WebVR. See <a href="http://webvr.info">webvr.info</a> for assistance.';
}
if (message !== undefined) {
var container = document.createElement('div');
container.style.position = 'absolute';
container.style.left = '0';
container.style.top = '0';
container.style.right = '0';
container.style.zIndex = '999';
container.align = 'center';
var error = document.createElement('div');
error.style.fontFamily = 'sans-serif';
error.style.fontSize = '16px';
error.style.fontStyle = 'normal';
error.style.lineHeight = '26px';
error.style.backgroundColor = '#fff';
error.style.color = '#000';
error.style.padding = '10px 20px';
error.style.margin = '50px';
error.style.display = 'inline-block';
error.innerHTML = message;
container.appendChild(error);
return container;
}
},
getMessageContainer: function getMessageContainer(message) {
var container = document.createElement('div');
container.style.position = 'absolute';
container.style.left = '0';
container.style.top = '0';
container.style.right = '0';
container.style.zIndex = '999';
container.align = 'center';
var error = document.createElement('div');
error.style.fontFamily = 'sans-serif';
error.style.fontSize = '16px';
error.style.fontStyle = 'normal';
error.style.lineHeight = '26px';
error.style.backgroundColor = '#fff';
error.style.color = '#000';
error.style.padding = '10px 20px';
error.style.margin = '50px';
error.style.display = 'inline-block';
error.innerHTML = message;
container.appendChild(error);
return container;
},
getButton: function getButton(display, canvas) {
if ('VREffect' in THREE && display instanceof THREE.VREffect) {
console.error('WebVR.getButton() now expects a VRDisplay.');
return document.createElement('button');
}
var button = document.createElement('button');
button.style.position = 'absolute';
button.style.left = 'calc(50% - 50px)';
button.style.bottom = '20px';
button.style.width = '100px';
button.style.border = '0';
button.style.padding = '8px';
button.style.cursor = 'pointer';
button.style.backgroundColor = '#000';
button.style.color = '#fff';
button.style.fontFamily = 'sans-serif';
button.style.fontSize = '13px';
button.style.fontStyle = 'normal';
button.style.textAlign = 'center';
button.style.zIndex = '999';
if (display) {
button.textContent = 'ENTER VR';
button.onclick = function () {
display.isPresenting ? display.exitPresent() : display.requestPresent([{ source: canvas }]);
};
window.addEventListener('vrdisplaypresentchange', function () {
button.textContent = display.isPresenting ? 'EXIT VR' : 'ENTER VR';
}, false);
} else {
button.textContent = 'NO VR DISPLAY';
}
return button;
}
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};
var possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
var VRModule = function () {
function VRModule() {
var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
classCallCheck(this, VRModule);
this.params = Object.assign(params, {
message: true,
button: true
});
this.scene = null;
this.camera = null;
this.effect = null;
}
createClass(VRModule, [{
key: 'manager',
value: function manager(_manager) {
var _this = this;
_manager.define('vr');
if (REVISION > 86) console.warn('Please use VRModule2 for Three.js ^0.87.0 (r87)');
var rendering = _manager.use('rendering');
var renderer = _manager.get('renderer');
var resize = _manager.use('resize');
this.effect = new VREffect(renderer);
this.scene = _manager.get('scene');
this.camera = _manager.get('camera');
rendering.effect(this.effect);
// TODO: Fix resize.
resize.addCallback(function (width, height) {
_this.effect.setSize(+width, +height);
});
// WEBVR
var _params = this.params,
message = _params.message,
button = _params.button;
if (message) WEBVR.checkAvailability().catch(function (message) {
document.body.appendChild(WEBVR.getMessageContainer(message));
});
if (button) WEBVR.getVRDisplay(function (display) {
var vrbtn = WEBVR.getButton(display, renderer.domElement);
vrbtn.className = 'vr-btn';
document.body.appendChild(vrbtn);
});
}
}]);
return VRModule;
}();
var VR2Module = function () {
function VR2Module() {
classCallCheck(this, VR2Module);
this.display = new Promise(function (resolve) {
return WEBVR.getVRDisplay(function (display) {
return resolve(display);
});
});
}
createClass(VR2Module, [{
key: 'manager',
value: function manager(_manager2) {
_manager2.define('vr');
var renderer = _manager2.get('renderer');
renderer.vr.enabled = true;
console.log(REVISION);
console.log(1);
this.display.then(function (display) {
renderer.vr.setDevice(display);
var vrbtn = WEBVR.getButton(display, renderer.domElement);
vrbtn.className = 'vr-btn';
document.body.appendChild(vrbtn);
});
}
}]);
return VR2Module;
}();
var VRControls = function (_ControlsModule) {
inherits(VRControls, _ControlsModule);
function VRControls(_ref) {
var object = _ref.object,
onError = _ref.onError,
intensity = _ref.intensity;
classCallCheck(this, VRControls);
var controls = new VRControls$1(object.native, onError);
controls.standing = true;
controls.scale = intensity;
return possibleConstructorReturn(this, (VRControls.__proto__ || Object.getPrototypeOf(VRControls)).call(this, { controls: controls }));
}
return VRControls;
}(ControlsModule);
export { WEBVR, VRModule, VR2Module, VRControls };
//# sourceMappingURL=VRKit.module.js.map