mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
406729873b
It encapsulates and simplifies the use of requestVideoFrameCallback and cancelVideoFrameCallback --------- Co-authored-by: Wojciech Tyczyński <tykus160@gmail.com>
739 lines
21 KiB
JavaScript
739 lines
21 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
|
|
goog.provide('shaka.ui.VRWebgl');
|
|
|
|
goog.require('goog.asserts');
|
|
goog.require('shaka.log');
|
|
goog.require('shaka.Player');
|
|
goog.require('shaka.ui.Matrix4x4');
|
|
goog.require('shaka.ui.MatrixQuaternion');
|
|
goog.require('shaka.ui.VRUtils');
|
|
goog.require('shaka.util.EventManager');
|
|
goog.require('shaka.util.IReleasable');
|
|
goog.require('shaka.util.MediaReadyState');
|
|
goog.require('shaka.util.Timer');
|
|
goog.require('shaka.util.VideoFrameCallbackHandler');
|
|
|
|
|
|
/**
|
|
* @implements {shaka.util.IReleasable}
|
|
*/
|
|
shaka.ui.VRWebgl = class {
|
|
/**
|
|
* @param {!HTMLMediaElement} video
|
|
* @param {!shaka.Player} player
|
|
* @param {!HTMLCanvasElement} canvas
|
|
* @param {WebGLRenderingContext} gl
|
|
* @param {string} projectionMode
|
|
*/
|
|
constructor(video, player, canvas, gl, projectionMode) {
|
|
/** @private {!HTMLVideoElement} */
|
|
this.video_ = /** @type {!HTMLVideoElement} */ (video);
|
|
|
|
/** @private {shaka.Player} */
|
|
this.player_ = player;
|
|
|
|
/** @private {HTMLCanvasElement} */
|
|
this.canvas_ = canvas;
|
|
|
|
/** @private {WebGLRenderingContext} */
|
|
this.gl_ = gl;
|
|
|
|
/** @private {shaka.util.EventManager} */
|
|
this.eventManager_ = new shaka.util.EventManager();
|
|
|
|
/** @private {!Float32Array} */
|
|
this.originalQuaternion_ = shaka.ui.MatrixQuaternion.create();
|
|
|
|
/** @private {!Float32Array} */
|
|
this.currentQuaternion_ = shaka.ui.MatrixQuaternion.create();
|
|
|
|
/** @private {?WebGLProgram} */
|
|
this.shaderProgram_ = null;
|
|
|
|
/** @private {?WebGLBuffer} */
|
|
this.verticesBuffer_ = null;
|
|
|
|
/** @private {?WebGLBuffer} */
|
|
this.verticesTextureCoordBuffer_ = null;
|
|
|
|
/** @private {?WebGLBuffer} */
|
|
this.verticesIndexBuffer_ = null;
|
|
|
|
/** @private {!Float32Array} */
|
|
this.viewMatrix_ = shaka.ui.Matrix4x4.create();
|
|
|
|
/** @private {!Float32Array} */
|
|
this.projectionMatrix_ = shaka.ui.Matrix4x4.create();
|
|
|
|
/** @private {!Float32Array} */
|
|
this.viewProjectionMatrix_ = shaka.ui.Matrix4x4.create();
|
|
|
|
/** @private {!Float32Array} */
|
|
this.identityMatrix_ = shaka.ui.Matrix4x4.create();
|
|
|
|
/** @private {?Float32Array} */
|
|
this.diff_ = null;
|
|
|
|
/** @private {boolean} */
|
|
this.stereoscopicMode_ = false;
|
|
|
|
/** @private {?shaka.util.Timer} */
|
|
this.activeTimer_ = null;
|
|
|
|
/** @private {?shaka.util.Timer} */
|
|
this.resetTimer_ = null;
|
|
|
|
/** @private {number} */
|
|
this.previousCanvasWidth_ = 0;
|
|
|
|
/** @private {number} */
|
|
this.previousCanvasHeight_ = 0;
|
|
|
|
/**
|
|
* @private {?{vertices: !Array<number>, textureCoords: !Array<number>,
|
|
* indices: !Array<number>}}
|
|
*/
|
|
this.geometry_ = null;
|
|
|
|
/** @private {?number} */
|
|
this.vertexPositionAttribute_ = null;
|
|
|
|
/** @private {?number} */
|
|
this.textureCoordAttribute_ = null;
|
|
|
|
/** @private {?WebGLTexture} */
|
|
this.texture_ = null;
|
|
|
|
/** @private {number} */
|
|
this.positionX_ = 0;
|
|
|
|
/** @private {number} */
|
|
this.positionY_ = 0;
|
|
|
|
/** @private {number} */
|
|
this.fieldOfView_ = 75;
|
|
|
|
/** @private {number} */
|
|
this.cont_ = 0;
|
|
|
|
/** @private {string} */
|
|
this.projectionMode_ = projectionMode;
|
|
|
|
/** @private {?shaka.util.VideoFrameCallbackHandler} */
|
|
this.videoFrameCallbackHandler_ =
|
|
new shaka.util.VideoFrameCallbackHandler(this.video_);
|
|
|
|
this.init_();
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
release() {
|
|
this.videoFrameCallbackHandler_?.release();
|
|
this.videoFrameCallbackHandler_ = null;
|
|
|
|
this.eventManager_?.release();
|
|
this.eventManager_ = null;
|
|
|
|
this.activeTimer_?.stop();
|
|
this.activeTimer_ = null;
|
|
|
|
this.resetTimer_?.stop();
|
|
this.resetTimer_ = null;
|
|
}
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
getProjectionMode() {
|
|
return this.projectionMode_;
|
|
}
|
|
|
|
/**
|
|
* @param {!Float32Array} quat
|
|
* @return {{pitch: number, yaw: number, roll: number}} as radians
|
|
* @private
|
|
*/
|
|
toEulerAngles_(quat) {
|
|
const angles = {
|
|
pitch: 0,
|
|
yaw: 0,
|
|
roll: 0,
|
|
};
|
|
const x = quat[0];
|
|
const y = quat[1];
|
|
const z = quat[2];
|
|
const w = quat[3];
|
|
const x2 = x * x;
|
|
const y2 = y * y;
|
|
const z2 = z * z;
|
|
const w2 = w * w;
|
|
const unit = x2 + y2 + z2 + w2;
|
|
const test = x * w - y * z;
|
|
if (test > 0.499995 * unit) {
|
|
// singularity at the north pole
|
|
angles.pitch = Math.PI / 2;
|
|
angles.yaw = 2 * Math.atan2(y, x);
|
|
angles.roll = 0;
|
|
} else if (test < -0.499995 * unit) {
|
|
// singularity at the south pole
|
|
angles.pitch = -Math.PI / 2;
|
|
angles.yaw = 2 * Math.atan2(y, x);
|
|
angles.roll = 0;
|
|
} else {
|
|
angles.pitch = Math.asin(2 * (x * z - w * y));
|
|
angles.yaw = Math.atan2(2 * (x * w + y * z), 1 - 2 * (z2 + w2));
|
|
angles.roll = Math.atan2(2 * (x * y + z * w), 1 - 2 * (y2 + z2));
|
|
}
|
|
return angles;
|
|
}
|
|
|
|
/**
|
|
* Toggle stereoscopic mode
|
|
*/
|
|
toggleStereoscopicMode() {
|
|
this.stereoscopicMode_ = !this.stereoscopicMode_;
|
|
if (!this.stereoscopicMode_) {
|
|
this.gl_.viewport(0, 0, this.canvas_.width, this.canvas_.height);
|
|
}
|
|
this.renderGL_(false);
|
|
}
|
|
|
|
/**
|
|
* Returns true if stereoscopic mode is enabled.
|
|
*
|
|
* @return {boolean}
|
|
*/
|
|
isStereoscopicModeEnabled() {
|
|
return this.stereoscopicMode_;
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
init_() {
|
|
this.initMatrices_();
|
|
this.initGL_();
|
|
this.initGLShaders_();
|
|
this.initGLBuffers_();
|
|
this.initGLTexture_();
|
|
|
|
const setupListeners = () => {
|
|
if (this.video_.readyState >= HTMLMediaElement.HAVE_ENOUGH_DATA) {
|
|
this.renderGL_();
|
|
}
|
|
|
|
const usingFrames = this.videoFrameCallbackHandler_.start(() => {
|
|
this.renderGL_();
|
|
});
|
|
if (!usingFrames) {
|
|
let frameRate;
|
|
this.eventManager_.listen(this.video_, 'canplaythrough', () => {
|
|
this.renderGL_();
|
|
});
|
|
this.eventManager_.listen(this.video_, 'playing', () => {
|
|
if (this.activeTimer_) {
|
|
this.activeTimer_.stop();
|
|
}
|
|
if (!frameRate) {
|
|
const variants = this.player_.getVariantTracks();
|
|
for (const variant of variants) {
|
|
const variantFrameRate = variant.frameRate;
|
|
if (variantFrameRate &&
|
|
(!frameRate || frameRate < variantFrameRate)) {
|
|
frameRate = variantFrameRate;
|
|
}
|
|
}
|
|
}
|
|
if (!frameRate) {
|
|
frameRate = 60;
|
|
}
|
|
this.renderGL_();
|
|
this.activeTimer_ = new shaka.util.Timer(() => {
|
|
this.renderGL_();
|
|
}).tickNow().tickEvery(1 / frameRate);
|
|
});
|
|
this.eventManager_.listen(this.video_, 'pause', () => {
|
|
if (this.activeTimer_) {
|
|
this.activeTimer_.stop();
|
|
}
|
|
this.activeTimer_ = null;
|
|
this.renderGL_();
|
|
});
|
|
this.eventManager_.listen(this.video_, 'seeked', () => {
|
|
this.renderGL_();
|
|
});
|
|
|
|
this.eventManager_.listen(document, 'visibilitychange', () => {
|
|
this.renderGL_();
|
|
});
|
|
}
|
|
};
|
|
|
|
shaka.util.MediaReadyState.waitForReadyState(this.video_,
|
|
HTMLMediaElement.HAVE_CURRENT_DATA,
|
|
this.eventManager_,
|
|
setupListeners);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
initMatrices_() {
|
|
shaka.ui.Matrix4x4.lookAt(
|
|
this.viewMatrix_, [0, 0, 0], [1, 0, 0], [0, 1, 0]);
|
|
shaka.ui.Matrix4x4.getRotation(
|
|
this.originalQuaternion_, this.viewMatrix_);
|
|
shaka.ui.Matrix4x4.scale(
|
|
this.identityMatrix_, this.identityMatrix_, [4.0, 4.0, 4.0]);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
initGL_() {
|
|
this.updateViewPort_();
|
|
this.gl_.viewport(
|
|
0, 0, this.gl_.drawingBufferWidth, this.gl_.drawingBufferHeight);
|
|
this.gl_.clearColor(0.0, 0.0, 0.0, 1.0);
|
|
this.gl_.enable(this.gl_.CULL_FACE);
|
|
this.gl_.cullFace(this.gl_.FRONT);
|
|
// Clear the context with the newly set color. This is
|
|
// the function call that actually does the drawing.
|
|
this.gl_.clear(this.gl_.COLOR_BUFFER_BIT);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
initGLShaders_() {
|
|
const vertexShader = this.getGLShader_(this.gl_.VERTEX_SHADER);
|
|
const fragmentShader = this.getGLShader_(this.gl_.FRAGMENT_SHADER);
|
|
|
|
// Create program
|
|
this.shaderProgram_ = this.gl_.createProgram();
|
|
this.gl_.attachShader(this.shaderProgram_, vertexShader);
|
|
this.gl_.attachShader(this.shaderProgram_, fragmentShader);
|
|
this.gl_.linkProgram(this.shaderProgram_);
|
|
|
|
// If creating the shader program failed, alert
|
|
if (!this.gl_.getProgramParameter(
|
|
this.shaderProgram_, this.gl_.LINK_STATUS)) {
|
|
shaka.log.error('Unable to initialize the shader program: ',
|
|
this.gl_.getProgramInfoLog(this.shaderProgram_));
|
|
}
|
|
|
|
// Bind data
|
|
if (this.projectionMode_ == 'cubemap') {
|
|
this.vertexPositionAttribute_ = this.gl_.getAttribLocation(
|
|
this.shaderProgram_, 'aVertexPosition');
|
|
this.textureCoordAttribute_ = this.gl_.getAttribLocation(
|
|
this.shaderProgram_, 'aTextureCoord');
|
|
} else {
|
|
this.vertexPositionAttribute_ = this.gl_.getAttribLocation(
|
|
this.shaderProgram_, 'a_vPosition');
|
|
this.gl_.enableVertexAttribArray(this.vertexPositionAttribute_);
|
|
this.textureCoordAttribute_ = this.gl_.getAttribLocation(
|
|
this.shaderProgram_, 'a_TexCoordinate');
|
|
this.gl_.enableVertexAttribArray(this.textureCoordAttribute_);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read and generate WebGL shader
|
|
*
|
|
* @param {number} glType Type of shader requested.
|
|
* @return {?WebGLShader}
|
|
* @private
|
|
*/
|
|
getGLShader_(glType) {
|
|
let source;
|
|
|
|
switch (glType) {
|
|
case this.gl_.VERTEX_SHADER:
|
|
if (this.projectionMode_ == 'cubemap') {
|
|
source = shaka.ui.VRUtils.VERTEX_CUBE_SHADER;
|
|
} else {
|
|
source = shaka.ui.VRUtils.VERTEX_SPHERE_SHADER;
|
|
}
|
|
break;
|
|
case this.gl_.FRAGMENT_SHADER:
|
|
if (this.projectionMode_ == 'cubemap') {
|
|
source = shaka.ui.VRUtils.FRAGMENT_CUBE_SHADER;
|
|
} else {
|
|
source = shaka.ui.VRUtils.FRAGMENT_SPHERE_SHADER;
|
|
}
|
|
break;
|
|
default:
|
|
return null;
|
|
}
|
|
|
|
const shader = this.gl_.createShader(glType);
|
|
|
|
this.gl_.shaderSource(shader, source);
|
|
|
|
this.gl_.compileShader(shader);
|
|
|
|
if (!this.gl_.getShaderParameter(shader, this.gl_.COMPILE_STATUS)) {
|
|
shaka.log.warning('Error in ' + glType + ' shader: ' +
|
|
this.gl_.getShaderInfoLog(shader));
|
|
}
|
|
|
|
goog.asserts.assert(shader, 'Should have a shader!');
|
|
|
|
return shader;
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
initGLBuffers_() {
|
|
if (this.projectionMode_ == 'cubemap') {
|
|
this.geometry_ = shaka.ui.VRUtils.generateCube();
|
|
} else if (this.projectionMode_ == 'halfequirectangular') {
|
|
this.geometry_ = shaka.ui.VRUtils.generateSphere(100, true);
|
|
} else {
|
|
this.geometry_ = shaka.ui.VRUtils.generateSphere(100);
|
|
}
|
|
this.verticesBuffer_ = this.gl_.createBuffer();
|
|
this.gl_.bindBuffer(this.gl_.ARRAY_BUFFER, this.verticesBuffer_);
|
|
this.gl_.bufferData(this.gl_.ARRAY_BUFFER,
|
|
new Float32Array(this.geometry_.vertices), this.gl_.STATIC_DRAW);
|
|
this.verticesTextureCoordBuffer_ = this.gl_.createBuffer();
|
|
this.gl_.bindBuffer(
|
|
this.gl_.ARRAY_BUFFER, this.verticesTextureCoordBuffer_);
|
|
this.gl_.bufferData(this.gl_.ARRAY_BUFFER,
|
|
new Float32Array(this.geometry_.textureCoords), this.gl_.STATIC_DRAW);
|
|
this.verticesIndexBuffer_ = this.gl_.createBuffer();
|
|
this.gl_.bindBuffer(
|
|
this.gl_.ELEMENT_ARRAY_BUFFER, this.verticesIndexBuffer_);
|
|
this.gl_.bufferData(this.gl_.ELEMENT_ARRAY_BUFFER,
|
|
new Uint16Array(this.geometry_.indices), this.gl_.STATIC_DRAW);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
initGLTexture_() {
|
|
this.texture_ = this.gl_.createTexture();
|
|
this.gl_.bindTexture(this.gl_.TEXTURE_2D, this.texture_);
|
|
|
|
this.gl_.texParameteri(this.gl_.TEXTURE_2D,
|
|
this.gl_.TEXTURE_WRAP_S, this.gl_.CLAMP_TO_EDGE);
|
|
this.gl_.texParameteri(this.gl_.TEXTURE_2D,
|
|
this.gl_.TEXTURE_WRAP_T, this.gl_.CLAMP_TO_EDGE);
|
|
this.gl_.texParameteri(this.gl_.TEXTURE_2D,
|
|
this.gl_.TEXTURE_MIN_FILTER, this.gl_.NEAREST);
|
|
this.gl_.texParameteri(this.gl_.TEXTURE_2D,
|
|
this.gl_.TEXTURE_MAG_FILTER, this.gl_.NEAREST);
|
|
}
|
|
|
|
/**
|
|
* @param {boolean=} textureUpdate
|
|
* @private
|
|
*/
|
|
renderGL_(textureUpdate = true) {
|
|
const loadMode = this.player_.getLoadMode();
|
|
const isMSE = loadMode == shaka.Player.LoadMode.MEDIA_SOURCE;
|
|
if (!this.video_ || this.video_.readyState < 2 ||
|
|
(!isMSE && this.video_.playbackRate == 0)) {
|
|
return;
|
|
}
|
|
shaka.ui.Matrix4x4.perspective(this.projectionMatrix_,
|
|
this.fieldOfView_ * Math.PI / 180, 5 / 3.2, 0.1, 100.0);
|
|
|
|
if (this.projectionMode_ == 'cubemap') {
|
|
shaka.ui.Matrix4x4.perspective(this.projectionMatrix_,
|
|
this.fieldOfView_ * Math.PI / 180, 5 / 2, 0.1, 100.0);
|
|
} else {
|
|
shaka.ui.Matrix4x4.perspective(this.projectionMatrix_,
|
|
this.fieldOfView_ * Math.PI / 180, 5 / 3.2, 0.1, 100.0);
|
|
}
|
|
|
|
this.gl_.useProgram(this.shaderProgram_);
|
|
|
|
this.gl_.clear(this.gl_.COLOR_BUFFER_BIT);
|
|
this.updateViewPort_();
|
|
|
|
if (textureUpdate) {
|
|
this.gl_.activeTexture(this.gl_.TEXTURE0);
|
|
this.gl_.bindTexture(this.gl_.TEXTURE_2D, this.texture_);
|
|
this.gl_.pixelStorei(this.gl_.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
|
|
this.gl_.texImage2D(this.gl_.TEXTURE_2D, 0, this.gl_.RGBA,
|
|
this.gl_.RGBA, this.gl_.UNSIGNED_BYTE, this.video_);
|
|
}
|
|
|
|
// Update matrix
|
|
if (this.projectionMode_ == 'equirectangular' ||
|
|
this.projectionMode_ == 'halfequirectangular') {
|
|
shaka.ui.Matrix4x4.multiply(this.viewProjectionMatrix_,
|
|
this.viewMatrix_, this.identityMatrix_);
|
|
shaka.ui.Matrix4x4.multiply(this.viewProjectionMatrix_,
|
|
this.projectionMatrix_, this.viewProjectionMatrix_);
|
|
}
|
|
|
|
// Plumbing
|
|
// Vertices
|
|
this.gl_.bindBuffer(this.gl_.ARRAY_BUFFER, this.verticesBuffer_);
|
|
goog.asserts.assert(this.vertexPositionAttribute_ != null,
|
|
'Should have a texture attribute!');
|
|
this.gl_.vertexAttribPointer(
|
|
this.vertexPositionAttribute_, 3, this.gl_.FLOAT, false, 0, 0);
|
|
this.gl_.enableVertexAttribArray(this.vertexPositionAttribute_);
|
|
|
|
// UVs
|
|
this.gl_.bindBuffer(
|
|
this.gl_.ARRAY_BUFFER, this.verticesTextureCoordBuffer_);
|
|
goog.asserts.assert(this.textureCoordAttribute_ != null,
|
|
'Should have a texture attribute!');
|
|
this.gl_.vertexAttribPointer(
|
|
this.textureCoordAttribute_, 2, this.gl_.FLOAT, false, 0, 0);
|
|
this.gl_.enableVertexAttribArray(this.textureCoordAttribute_);
|
|
|
|
this.gl_.bindBuffer(
|
|
this.gl_.ELEMENT_ARRAY_BUFFER, this.verticesIndexBuffer_);
|
|
|
|
this.setMatrixUniforms_();
|
|
|
|
this.gl_.uniform1i(
|
|
this.gl_.getUniformLocation(this.shaderProgram_, 'uSampler'), 0);
|
|
|
|
if (this.stereoscopicMode_) {
|
|
this.gl_.viewport(0, 0, this.canvas_.width / 2, this.canvas_.height);
|
|
}
|
|
|
|
// Draw
|
|
this.gl_.drawElements(this.gl_.TRIANGLES,
|
|
this.geometry_.indices.length, this.gl_.UNSIGNED_SHORT, 0);
|
|
|
|
if (this.stereoscopicMode_) {
|
|
this.gl_.viewport(this.canvas_.width / 2, 0,
|
|
this.canvas_.width / 2, this.canvas_.height);
|
|
this.gl_.drawElements(this.gl_.TRIANGLES,
|
|
this.geometry_.indices.length, this.gl_.UNSIGNED_SHORT, 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
setMatrixUniforms_() {
|
|
if (this.projectionMode_ == 'cubemap') {
|
|
this.gl_.uniformMatrix4fv(
|
|
this.gl_.getUniformLocation(this.shaderProgram_, 'uProjectionMatrix'),
|
|
false, this.projectionMatrix_);
|
|
this.gl_.uniformMatrix4fv(
|
|
this.gl_.getUniformLocation(this.shaderProgram_, 'uModelViewMatrix'),
|
|
false, this.viewProjectionMatrix_);
|
|
} else {
|
|
this.gl_.uniformMatrix4fv(
|
|
this.gl_.getUniformLocation(this.shaderProgram_, 'u_VPMatrix'),
|
|
false, this.viewProjectionMatrix_);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
updateViewPort_() {
|
|
let currentWidth = this.video_.videoWidth;
|
|
if (!currentWidth) {
|
|
currentWidth = this.canvas_.scrollWidth;
|
|
}
|
|
let currentHeight = this.video_.videoHeight;
|
|
if (!currentHeight) {
|
|
currentHeight = this.canvas_.scrollHeight;
|
|
}
|
|
|
|
if (this.previousCanvasWidth_ !== currentWidth ||
|
|
this.previousCanvasHeight_ !== currentHeight) {
|
|
this.canvas_.width = currentWidth;
|
|
this.canvas_.height = currentHeight;
|
|
|
|
this.previousCanvasWidth_ = currentWidth;
|
|
this.previousCanvasHeight_ = currentHeight;
|
|
|
|
const ratio = currentWidth / currentHeight;
|
|
|
|
this.projectionMatrix_ = shaka.ui.Matrix4x4.frustum(
|
|
this.projectionMatrix_, -ratio, ratio, -1, 1, 0, 1);
|
|
|
|
this.gl_.viewport(0, 0, currentWidth, currentHeight);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rotate the view matrix global
|
|
*
|
|
* @param {!number} yaw Yaw.
|
|
* @param {!number} pitch Pitch.
|
|
* @param {!number} roll Roll.
|
|
*/
|
|
rotateViewGlobal(yaw, pitch, roll) {
|
|
let yawBoundary = Infinity;
|
|
let pitchBoundary = 90.0 * Math.PI / 180;
|
|
|
|
if (this.projectionMode_ == 'halfequirectangular') {
|
|
yawBoundary = 90.0 * Math.PI / 180;
|
|
pitchBoundary /= 2;
|
|
}
|
|
|
|
let matrix;
|
|
if (this.projectionMode_ == 'cubemap') {
|
|
matrix = this.viewProjectionMatrix_;
|
|
} else {
|
|
matrix = this.viewMatrix_;
|
|
}
|
|
|
|
// Variable to limit the movement
|
|
this.positionX_ += yaw;
|
|
this.positionY_ += pitch;
|
|
|
|
if (this.positionX_ < yawBoundary &&
|
|
this.positionX_ > -yawBoundary) {
|
|
// Rotate global axis
|
|
shaka.ui.Matrix4x4.rotateY(matrix, matrix, yaw);
|
|
} else {
|
|
this.positionX_ -= yaw;
|
|
}
|
|
|
|
if (this.positionY_ < pitchBoundary &&
|
|
this.positionY_ > -pitchBoundary) {
|
|
const out = shaka.ui.Matrix4x4.create();
|
|
shaka.ui.Matrix4x4.rotateX(out, shaka.ui.Matrix4x4.create(), -1 * pitch);
|
|
// Rotate local axis
|
|
shaka.ui.Matrix4x4.multiply(matrix, out, matrix);
|
|
} else {
|
|
// Doing this we restart the value to the previous position,
|
|
// to not maintain a value over 90º or under -90º.
|
|
this.positionY_ -= pitch;
|
|
}
|
|
|
|
const out2 = shaka.ui.Matrix4x4.create();
|
|
shaka.ui.Matrix4x4.rotateZ(out2, shaka.ui.Matrix4x4.create(), roll);
|
|
|
|
// Rotate local axis
|
|
shaka.ui.Matrix4x4.multiply(matrix, out2, matrix);
|
|
|
|
this.renderGL_(false);
|
|
}
|
|
|
|
/**
|
|
* @param {number} amount
|
|
*/
|
|
zoom(amount) {
|
|
const zoomMin = 20;
|
|
const zoomMax = 100;
|
|
amount /= 50;
|
|
if (this.fieldOfView_ >= zoomMin && this.fieldOfView_ <= zoomMax) {
|
|
this.fieldOfView_ += amount;
|
|
}
|
|
if (this.fieldOfView_ < zoomMin) {
|
|
this.fieldOfView_ = zoomMin;
|
|
} else if (this.fieldOfView_ > zoomMax) {
|
|
this.fieldOfView_ = zoomMax;
|
|
}
|
|
this.renderGL_(false);
|
|
}
|
|
|
|
/**
|
|
* @return {number}
|
|
*/
|
|
getFieldOfView() {
|
|
return this.fieldOfView_;
|
|
}
|
|
|
|
/**
|
|
* @param {number} fieldOfView
|
|
*/
|
|
setFieldOfView(fieldOfView) {
|
|
this.fieldOfView_ = fieldOfView;
|
|
this.renderGL_(false);
|
|
}
|
|
|
|
/**
|
|
* @return {number}
|
|
*/
|
|
getNorth() {
|
|
shaka.ui.Matrix4x4.getRotation(this.currentQuaternion_, this.viewMatrix_);
|
|
|
|
const angles = this.toEulerAngles_(this.currentQuaternion_);
|
|
|
|
const normalizedDir = {
|
|
x: Math.cos(angles.yaw) * Math.cos(angles.pitch),
|
|
y: Math.sin(angles.yaw) * Math.cos(angles.pitch),
|
|
z: Math.sin(angles.pitch),
|
|
};
|
|
|
|
const northYaw = Math.acos(normalizedDir.x);
|
|
|
|
return ((northYaw * 180) / Math.PI);
|
|
}
|
|
|
|
/**
|
|
* @param {boolean=} firstTime
|
|
*/
|
|
reset(firstTime = true) {
|
|
const steps = 20;
|
|
|
|
if (firstTime) {
|
|
shaka.ui.Matrix4x4.getRotation(
|
|
this.currentQuaternion_, this.viewMatrix_);
|
|
this.cont_ = 0;
|
|
this.diff_ = shaka.ui.MatrixQuaternion.create();
|
|
this.diff_[0] =
|
|
(this.currentQuaternion_[0] - this.originalQuaternion_[0]) / steps;
|
|
this.diff_[1] =
|
|
(this.currentQuaternion_[1] - this.originalQuaternion_[1]) / steps;
|
|
this.diff_[2] =
|
|
(this.currentQuaternion_[2] - this.originalQuaternion_[2]) / steps;
|
|
this.diff_[3] =
|
|
(this.currentQuaternion_[3] - this.originalQuaternion_[3]) / steps;
|
|
}
|
|
|
|
this.currentQuaternion_[0] -= this.diff_[0];
|
|
this.currentQuaternion_[1] -= this.diff_[1];
|
|
this.currentQuaternion_[2] -= this.diff_[2];
|
|
this.currentQuaternion_[3] -= this.diff_[3];
|
|
|
|
// Set the view to the original matrix
|
|
const out = shaka.ui.Matrix4x4.create();
|
|
|
|
shaka.ui.MatrixQuaternion.normalize(
|
|
this.currentQuaternion_, this.currentQuaternion_);
|
|
|
|
shaka.ui.Matrix4x4.fromQuat(out, this.currentQuaternion_);
|
|
|
|
this.viewMatrix_ = out;
|
|
|
|
if (this.resetTimer_) {
|
|
this.resetTimer_.stop();
|
|
this.resetTimer_ = null;
|
|
}
|
|
if (this.cont_ < steps) {
|
|
this.resetTimer_ = new shaka.util.Timer(() => {
|
|
this.reset(false);
|
|
this.positionX_ = 0;
|
|
this.positionY_ = 0;
|
|
this.cont_++;
|
|
this.renderGL_(false);
|
|
}).tickAfter(shaka.ui.VRWebgl.ANIMATION_DURATION_ / steps);
|
|
} else {
|
|
shaka.ui.Matrix4x4.fromQuat(out, this.originalQuaternion_);
|
|
this.viewMatrix_ = out;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @const {number}
|
|
*/
|
|
shaka.ui.VRWebgl.ANIMATION_DURATION_ = 0.5;
|