Source: ui/vr_webgl.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.ui.VRWebgl');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.log');
  9. goog.require('shaka.Player');
  10. goog.require('shaka.ui.Matrix4x4');
  11. goog.require('shaka.ui.MatrixQuaternion');
  12. goog.require('shaka.ui.VRUtils');
  13. goog.require('shaka.util.EventManager');
  14. goog.require('shaka.util.IReleasable');
  15. goog.require('shaka.util.MediaReadyState');
  16. goog.require('shaka.util.Timer');
  17. /**
  18. * @implements {shaka.util.IReleasable}
  19. */
  20. shaka.ui.VRWebgl = class {
  21. /**
  22. * @param {!HTMLMediaElement} video
  23. * @param {!shaka.Player} player
  24. * @param {!HTMLCanvasElement} canvas
  25. * @param {WebGLRenderingContext} gl
  26. * @param {string} projectionMode
  27. */
  28. constructor(video, player, canvas, gl, projectionMode) {
  29. /** @private {!HTMLVideoElement} */
  30. this.video_ = /** @type {!HTMLVideoElement} */ (video);
  31. /** @private {shaka.Player} */
  32. this.player_ = player;
  33. /** @private {HTMLCanvasElement} */
  34. this.canvas_ = canvas;
  35. /** @private {WebGLRenderingContext} */
  36. this.gl_ = gl;
  37. /** @private {shaka.util.EventManager} */
  38. this.eventManager_ = new shaka.util.EventManager();
  39. /** @private {!Float32Array} */
  40. this.originalQuaternion_ = shaka.ui.MatrixQuaternion.create();
  41. /** @private {!Float32Array} */
  42. this.currentQuaternion_ = shaka.ui.MatrixQuaternion.create();
  43. /** @private {?WebGLProgram} */
  44. this.shaderProgram_ = null;
  45. /** @private {?WebGLBuffer} */
  46. this.verticesBuffer_ = null;
  47. /** @private {?WebGLBuffer} */
  48. this.verticesTextureCoordBuffer_ = null;
  49. /** @private {?WebGLBuffer} */
  50. this.verticesIndexBuffer_ = null;
  51. /** @private {!Float32Array} */
  52. this.viewMatrix_ = shaka.ui.Matrix4x4.create();
  53. /** @private {!Float32Array} */
  54. this.projectionMatrix_ = shaka.ui.Matrix4x4.create();
  55. /** @private {!Float32Array} */
  56. this.viewProjectionMatrix_ = shaka.ui.Matrix4x4.create();
  57. /** @private {!Float32Array} */
  58. this.identityMatrix_ = shaka.ui.Matrix4x4.create();
  59. /** @private {?Float32Array} */
  60. this.diff_ = null;
  61. /** @private {boolean} */
  62. this.stereoscopicMode_ = false;
  63. /** @private {?shaka.util.Timer} */
  64. this.activeTimer_ = null;
  65. /** @private {?shaka.util.Timer} */
  66. this.resetTimer_ = null;
  67. /** @private {number} */
  68. this.previousCanvasWidth_ = 0;
  69. /** @private {number} */
  70. this.previousCanvasHeight_ = 0;
  71. /**
  72. * @private {?{vertices: !Array<number>, textureCoords: !Array<number>,
  73. * indices: !Array<number>}}
  74. */
  75. this.geometry_ = null;
  76. /** @private {?number} */
  77. this.vertexPositionAttribute_ = null;
  78. /** @private {?number} */
  79. this.textureCoordAttribute_ = null;
  80. /** @private {?WebGLTexture} */
  81. this.texture_ = null;
  82. /** @private {number} */
  83. this.positionX_ = 0;
  84. /** @private {number} */
  85. this.positionY_ = 0;
  86. /** @private {number} */
  87. this.fieldOfView_ = 75;
  88. /** @private {number} */
  89. this.cont_ = 0;
  90. /** @private {string} */
  91. this.projectionMode_ = projectionMode;
  92. /** @private {number} */
  93. this.videoCallbackId_ = -1;
  94. this.init_();
  95. }
  96. /**
  97. * @override
  98. */
  99. release() {
  100. if (this.videoCallbackId_ != -1) {
  101. this.video_.cancelVideoFrameCallback(this.videoCallbackId_);
  102. this.videoCallbackId_ = -1;
  103. }
  104. if (this.eventManager_) {
  105. this.eventManager_.release();
  106. this.eventManager_ = null;
  107. }
  108. if (this.activeTimer_) {
  109. this.activeTimer_.stop();
  110. this.activeTimer_ = null;
  111. }
  112. if (this.resetTimer_) {
  113. this.resetTimer_.stop();
  114. this.resetTimer_ = null;
  115. }
  116. }
  117. /**
  118. * @return {string}
  119. */
  120. getProjectionMode() {
  121. return this.projectionMode_;
  122. }
  123. /**
  124. * @param {!Float32Array} quat
  125. * @return {{pitch: number, yaw: number, roll: number}} as radians
  126. * @private
  127. */
  128. toEulerAngles_(quat) {
  129. const angles = {
  130. pitch: 0,
  131. yaw: 0,
  132. roll: 0,
  133. };
  134. const x = quat[0];
  135. const y = quat[1];
  136. const z = quat[2];
  137. const w = quat[3];
  138. const x2 = x * x;
  139. const y2 = y * y;
  140. const z2 = z * z;
  141. const w2 = w * w;
  142. const unit = x2 + y2 + z2 + w2;
  143. const test = x * w - y * z;
  144. if (test > 0.499995 * unit) {
  145. // singularity at the north pole
  146. angles.pitch = Math.PI / 2;
  147. angles.yaw = 2 * Math.atan2(y, x);
  148. angles.roll = 0;
  149. } else if (test < -0.499995 * unit) {
  150. // singularity at the south pole
  151. angles.pitch = -Math.PI / 2;
  152. angles.yaw = 2 * Math.atan2(y, x);
  153. angles.roll = 0;
  154. } else {
  155. angles.pitch = Math.asin(2 * (x * z - w * y));
  156. angles.yaw = Math.atan2(2 * (x * w + y * z), 1 - 2 * (z2 + w2));
  157. angles.roll = Math.atan2(2 * (x * y + z * w), 1 - 2 * (y2 + z2));
  158. }
  159. return angles;
  160. }
  161. /**
  162. * Toggle stereoscopic mode
  163. */
  164. toggleStereoscopicMode() {
  165. this.stereoscopicMode_ = !this.stereoscopicMode_;
  166. if (!this.stereoscopicMode_) {
  167. this.gl_.viewport(0, 0, this.canvas_.width, this.canvas_.height);
  168. }
  169. this.renderGL_(false);
  170. }
  171. /**
  172. * Returns true if stereoscopic mode is enabled.
  173. *
  174. * @return {boolean}
  175. */
  176. isStereoscopicModeEnabled() {
  177. return this.stereoscopicMode_;
  178. }
  179. /**
  180. * @private
  181. */
  182. init_() {
  183. this.initMatrices_();
  184. this.initGL_();
  185. this.initGLShaders_();
  186. this.initGLBuffers_();
  187. this.initGLTexture_();
  188. const setupListeners = () => {
  189. if (this.video_.readyState >= HTMLMediaElement.HAVE_ENOUGH_DATA) {
  190. this.renderGL_();
  191. }
  192. if ('requestVideoFrameCallback' in this.video_) {
  193. const videoFrameCallback = (now, metadata) => {
  194. if (this.videoCallbackId_ == -1) {
  195. return;
  196. }
  197. this.renderGL_();
  198. // It is necessary to check this again because this callback can be
  199. // executed in another thread by the browser and we have to be sure
  200. // again here that we have not cancelled it in the middle of an
  201. // execution.
  202. if (this.videoCallbackId_ == -1) {
  203. return;
  204. }
  205. this.videoCallbackId_ =
  206. this.video_.requestVideoFrameCallback(videoFrameCallback);
  207. };
  208. this.videoCallbackId_ =
  209. this.video_.requestVideoFrameCallback(videoFrameCallback);
  210. } else {
  211. let frameRate;
  212. this.eventManager_.listen(this.video_, 'canplaythrough', () => {
  213. this.renderGL_();
  214. });
  215. this.eventManager_.listen(this.video_, 'playing', () => {
  216. if (this.activeTimer_) {
  217. this.activeTimer_.stop();
  218. }
  219. if (!frameRate) {
  220. const variants = this.player_.getVariantTracks();
  221. for (const variant of variants) {
  222. const variantFrameRate = variant.frameRate;
  223. if (variantFrameRate &&
  224. (!frameRate || frameRate < variantFrameRate)) {
  225. frameRate = variantFrameRate;
  226. }
  227. }
  228. }
  229. if (!frameRate) {
  230. frameRate = 60;
  231. }
  232. this.renderGL_();
  233. this.activeTimer_ = new shaka.util.Timer(() => {
  234. this.renderGL_();
  235. }).tickNow().tickEvery(1 / frameRate);
  236. });
  237. this.eventManager_.listen(this.video_, 'pause', () => {
  238. if (this.activeTimer_) {
  239. this.activeTimer_.stop();
  240. }
  241. this.activeTimer_ = null;
  242. this.renderGL_();
  243. });
  244. this.eventManager_.listen(this.video_, 'seeked', () => {
  245. this.renderGL_();
  246. });
  247. this.eventManager_.listen(document, 'visibilitychange', () => {
  248. this.renderGL_();
  249. });
  250. }
  251. };
  252. shaka.util.MediaReadyState.waitForReadyState(this.video_,
  253. HTMLMediaElement.HAVE_CURRENT_DATA,
  254. this.eventManager_,
  255. setupListeners);
  256. }
  257. /**
  258. * @private
  259. */
  260. initMatrices_() {
  261. shaka.ui.Matrix4x4.lookAt(
  262. this.viewMatrix_, [0, 0, 0], [1, 0, 0], [0, 1, 0]);
  263. shaka.ui.Matrix4x4.getRotation(
  264. this.originalQuaternion_, this.viewMatrix_);
  265. shaka.ui.Matrix4x4.scale(
  266. this.identityMatrix_, this.identityMatrix_, [4.0, 4.0, 4.0]);
  267. }
  268. /**
  269. * @private
  270. */
  271. initGL_() {
  272. this.updateViewPort_();
  273. this.gl_.viewport(
  274. 0, 0, this.gl_.drawingBufferWidth, this.gl_.drawingBufferHeight);
  275. this.gl_.clearColor(0.0, 0.0, 0.0, 1.0);
  276. this.gl_.enable(this.gl_.CULL_FACE);
  277. this.gl_.cullFace(this.gl_.FRONT);
  278. // Clear the context with the newly set color. This is
  279. // the function call that actually does the drawing.
  280. this.gl_.clear(this.gl_.COLOR_BUFFER_BIT);
  281. }
  282. /**
  283. * @private
  284. */
  285. initGLShaders_() {
  286. const vertexShader = this.getGLShader_(this.gl_.VERTEX_SHADER);
  287. const fragmentShader = this.getGLShader_(this.gl_.FRAGMENT_SHADER);
  288. // Create program
  289. this.shaderProgram_ = this.gl_.createProgram();
  290. this.gl_.attachShader(this.shaderProgram_, vertexShader);
  291. this.gl_.attachShader(this.shaderProgram_, fragmentShader);
  292. this.gl_.linkProgram(this.shaderProgram_);
  293. // If creating the shader program failed, alert
  294. if (!this.gl_.getProgramParameter(
  295. this.shaderProgram_, this.gl_.LINK_STATUS)) {
  296. shaka.log.error('Unable to initialize the shader program: ',
  297. this.gl_.getProgramInfoLog(this.shaderProgram_));
  298. }
  299. // Bind data
  300. if (this.projectionMode_ == 'cubemap') {
  301. this.vertexPositionAttribute_ = this.gl_.getAttribLocation(
  302. this.shaderProgram_, 'aVertexPosition');
  303. this.textureCoordAttribute_ = this.gl_.getAttribLocation(
  304. this.shaderProgram_, 'aTextureCoord');
  305. } else {
  306. this.vertexPositionAttribute_ = this.gl_.getAttribLocation(
  307. this.shaderProgram_, 'a_vPosition');
  308. this.gl_.enableVertexAttribArray(this.vertexPositionAttribute_);
  309. this.textureCoordAttribute_ = this.gl_.getAttribLocation(
  310. this.shaderProgram_, 'a_TexCoordinate');
  311. this.gl_.enableVertexAttribArray(this.textureCoordAttribute_);
  312. }
  313. }
  314. /**
  315. * Read and generate WebGL shader
  316. *
  317. * @param {number} glType Type of shader requested.
  318. * @return {?WebGLShader}
  319. * @private
  320. */
  321. getGLShader_(glType) {
  322. let source;
  323. switch (glType) {
  324. case this.gl_.VERTEX_SHADER:
  325. if (this.projectionMode_ == 'cubemap') {
  326. source = shaka.ui.VRUtils.VERTEX_CUBE_SHADER;
  327. } else {
  328. source = shaka.ui.VRUtils.VERTEX_SPHERE_SHADER;
  329. }
  330. break;
  331. case this.gl_.FRAGMENT_SHADER:
  332. if (this.projectionMode_ == 'cubemap') {
  333. source = shaka.ui.VRUtils.FRAGMENT_CUBE_SHADER;
  334. } else {
  335. source = shaka.ui.VRUtils.FRAGMENT_SPHERE_SHADER;
  336. }
  337. break;
  338. default:
  339. return null;
  340. }
  341. const shader = this.gl_.createShader(glType);
  342. this.gl_.shaderSource(shader, source);
  343. this.gl_.compileShader(shader);
  344. if (!this.gl_.getShaderParameter(shader, this.gl_.COMPILE_STATUS)) {
  345. shaka.log.warning('Error in ' + glType + ' shader: ' +
  346. this.gl_.getShaderInfoLog(shader));
  347. }
  348. goog.asserts.assert(shader, 'Should have a shader!');
  349. return shader;
  350. }
  351. /**
  352. * @private
  353. */
  354. initGLBuffers_() {
  355. if (this.projectionMode_ == 'cubemap') {
  356. this.geometry_ = shaka.ui.VRUtils.generateCube();
  357. } else if (this.projectionMode_ == 'halfequirectangular') {
  358. this.geometry_ = shaka.ui.VRUtils.generateSphere(100, true);
  359. } else {
  360. this.geometry_ = shaka.ui.VRUtils.generateSphere(100);
  361. }
  362. this.verticesBuffer_ = this.gl_.createBuffer();
  363. this.gl_.bindBuffer(this.gl_.ARRAY_BUFFER, this.verticesBuffer_);
  364. this.gl_.bufferData(this.gl_.ARRAY_BUFFER,
  365. new Float32Array(this.geometry_.vertices), this.gl_.STATIC_DRAW);
  366. this.verticesTextureCoordBuffer_ = this.gl_.createBuffer();
  367. this.gl_.bindBuffer(
  368. this.gl_.ARRAY_BUFFER, this.verticesTextureCoordBuffer_);
  369. this.gl_.bufferData(this.gl_.ARRAY_BUFFER,
  370. new Float32Array(this.geometry_.textureCoords), this.gl_.STATIC_DRAW);
  371. this.verticesIndexBuffer_ = this.gl_.createBuffer();
  372. this.gl_.bindBuffer(
  373. this.gl_.ELEMENT_ARRAY_BUFFER, this.verticesIndexBuffer_);
  374. this.gl_.bufferData(this.gl_.ELEMENT_ARRAY_BUFFER,
  375. new Uint16Array(this.geometry_.indices), this.gl_.STATIC_DRAW);
  376. }
  377. /**
  378. * @private
  379. */
  380. initGLTexture_() {
  381. this.texture_ = this.gl_.createTexture();
  382. this.gl_.bindTexture(this.gl_.TEXTURE_2D, this.texture_);
  383. this.gl_.texParameteri(this.gl_.TEXTURE_2D,
  384. this.gl_.TEXTURE_WRAP_S, this.gl_.CLAMP_TO_EDGE);
  385. this.gl_.texParameteri(this.gl_.TEXTURE_2D,
  386. this.gl_.TEXTURE_WRAP_T, this.gl_.CLAMP_TO_EDGE);
  387. this.gl_.texParameteri(this.gl_.TEXTURE_2D,
  388. this.gl_.TEXTURE_MIN_FILTER, this.gl_.NEAREST);
  389. this.gl_.texParameteri(this.gl_.TEXTURE_2D,
  390. this.gl_.TEXTURE_MAG_FILTER, this.gl_.NEAREST);
  391. }
  392. /**
  393. * @param {boolean=} textureUpdate
  394. * @private
  395. */
  396. renderGL_(textureUpdate = true) {
  397. const loadMode = this.player_.getLoadMode();
  398. const isMSE = loadMode == shaka.Player.LoadMode.MEDIA_SOURCE;
  399. if (!this.video_ || this.video_.readyState < 2 ||
  400. (!isMSE && this.video_.playbackRate == 0)) {
  401. return;
  402. }
  403. shaka.ui.Matrix4x4.perspective(this.projectionMatrix_,
  404. this.fieldOfView_ * Math.PI / 180, 5 / 3.2, 0.1, 100.0);
  405. if (this.projectionMode_ == 'cubemap') {
  406. shaka.ui.Matrix4x4.perspective(this.projectionMatrix_,
  407. this.fieldOfView_ * Math.PI / 180, 5 / 2, 0.1, 100.0);
  408. } else {
  409. shaka.ui.Matrix4x4.perspective(this.projectionMatrix_,
  410. this.fieldOfView_ * Math.PI / 180, 5 / 3.2, 0.1, 100.0);
  411. }
  412. this.gl_.useProgram(this.shaderProgram_);
  413. this.gl_.clear(this.gl_.COLOR_BUFFER_BIT);
  414. this.updateViewPort_();
  415. if (textureUpdate) {
  416. this.gl_.activeTexture(this.gl_.TEXTURE0);
  417. this.gl_.bindTexture(this.gl_.TEXTURE_2D, this.texture_);
  418. this.gl_.pixelStorei(this.gl_.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
  419. this.gl_.texImage2D(this.gl_.TEXTURE_2D, 0, this.gl_.RGBA,
  420. this.gl_.RGBA, this.gl_.UNSIGNED_BYTE, this.video_);
  421. }
  422. // Update matrix
  423. if (this.projectionMode_ == 'equirectangular' ||
  424. this.projectionMode_ == 'halfequirectangular') {
  425. shaka.ui.Matrix4x4.multiply(this.viewProjectionMatrix_,
  426. this.viewMatrix_, this.identityMatrix_);
  427. shaka.ui.Matrix4x4.multiply(this.viewProjectionMatrix_,
  428. this.projectionMatrix_, this.viewProjectionMatrix_);
  429. }
  430. // Plumbing
  431. // Vertices
  432. this.gl_.bindBuffer(this.gl_.ARRAY_BUFFER, this.verticesBuffer_);
  433. goog.asserts.assert(this.vertexPositionAttribute_ != null,
  434. 'Should have a texture attribute!');
  435. this.gl_.vertexAttribPointer(
  436. this.vertexPositionAttribute_, 3, this.gl_.FLOAT, false, 0, 0);
  437. this.gl_.enableVertexAttribArray(this.vertexPositionAttribute_);
  438. // UVs
  439. this.gl_.bindBuffer(
  440. this.gl_.ARRAY_BUFFER, this.verticesTextureCoordBuffer_);
  441. goog.asserts.assert(this.textureCoordAttribute_ != null,
  442. 'Should have a texture attribute!');
  443. this.gl_.vertexAttribPointer(
  444. this.textureCoordAttribute_, 2, this.gl_.FLOAT, false, 0, 0);
  445. this.gl_.enableVertexAttribArray(this.textureCoordAttribute_);
  446. this.gl_.bindBuffer(
  447. this.gl_.ELEMENT_ARRAY_BUFFER, this.verticesIndexBuffer_);
  448. this.setMatrixUniforms_();
  449. this.gl_.uniform1i(
  450. this.gl_.getUniformLocation(this.shaderProgram_, 'uSampler'), 0);
  451. if (this.stereoscopicMode_) {
  452. this.gl_.viewport(0, 0, this.canvas_.width / 2, this.canvas_.height);
  453. }
  454. // Draw
  455. this.gl_.drawElements(this.gl_.TRIANGLES,
  456. this.geometry_.indices.length, this.gl_.UNSIGNED_SHORT, 0);
  457. if (this.stereoscopicMode_) {
  458. this.gl_.viewport(this.canvas_.width / 2, 0,
  459. this.canvas_.width / 2, this.canvas_.height);
  460. this.gl_.drawElements(this.gl_.TRIANGLES,
  461. this.geometry_.indices.length, this.gl_.UNSIGNED_SHORT, 0);
  462. }
  463. }
  464. /**
  465. * @private
  466. */
  467. setMatrixUniforms_() {
  468. if (this.projectionMode_ == 'cubemap') {
  469. this.gl_.uniformMatrix4fv(
  470. this.gl_.getUniformLocation(this.shaderProgram_, 'uProjectionMatrix'),
  471. false, this.projectionMatrix_);
  472. this.gl_.uniformMatrix4fv(
  473. this.gl_.getUniformLocation(this.shaderProgram_, 'uModelViewMatrix'),
  474. false, this.viewProjectionMatrix_);
  475. } else {
  476. this.gl_.uniformMatrix4fv(
  477. this.gl_.getUniformLocation(this.shaderProgram_, 'u_VPMatrix'),
  478. false, this.viewProjectionMatrix_);
  479. }
  480. }
  481. /**
  482. * @private
  483. */
  484. updateViewPort_() {
  485. let currentWidth = this.video_.videoWidth;
  486. if (!currentWidth) {
  487. currentWidth = this.canvas_.scrollWidth;
  488. }
  489. let currentHeight = this.video_.videoHeight;
  490. if (!currentHeight) {
  491. currentHeight = this.canvas_.scrollHeight;
  492. }
  493. if (this.previousCanvasWidth_ !== currentWidth ||
  494. this.previousCanvasHeight_ !== currentHeight) {
  495. this.canvas_.width = currentWidth;
  496. this.canvas_.height = currentHeight;
  497. this.previousCanvasWidth_ = currentWidth;
  498. this.previousCanvasHeight_ = currentHeight;
  499. const ratio = currentWidth / currentHeight;
  500. this.projectionMatrix_ = shaka.ui.Matrix4x4.frustum(
  501. this.projectionMatrix_, -ratio, ratio, -1, 1, 0, 1);
  502. this.gl_.viewport(0, 0, currentWidth, currentHeight);
  503. }
  504. }
  505. /**
  506. * Rotate the view matrix global
  507. *
  508. * @param {!number} yaw Yaw.
  509. * @param {!number} pitch Pitch.
  510. * @param {!number} roll Roll.
  511. */
  512. rotateViewGlobal(yaw, pitch, roll) {
  513. let yawBoundary = Infinity;
  514. let pitchBoundary = 90.0 * Math.PI / 180;
  515. if (this.projectionMode_ == 'halfequirectangular') {
  516. yawBoundary = 90.0 * Math.PI / 180;
  517. pitchBoundary /= 2;
  518. }
  519. let matrix;
  520. if (this.projectionMode_ == 'cubemap') {
  521. matrix = this.viewProjectionMatrix_;
  522. } else {
  523. matrix = this.viewMatrix_;
  524. }
  525. // Variable to limit the movement
  526. this.positionX_ += yaw;
  527. this.positionY_ += pitch;
  528. if (this.positionX_ < yawBoundary &&
  529. this.positionX_ > -yawBoundary) {
  530. // Rotate global axis
  531. shaka.ui.Matrix4x4.rotateY(matrix, matrix, yaw);
  532. } else {
  533. this.positionX_ -= yaw;
  534. }
  535. if (this.positionY_ < pitchBoundary &&
  536. this.positionY_ > -pitchBoundary) {
  537. const out = shaka.ui.Matrix4x4.create();
  538. shaka.ui.Matrix4x4.rotateX(out, shaka.ui.Matrix4x4.create(), -1 * pitch);
  539. // Rotate local axis
  540. shaka.ui.Matrix4x4.multiply(matrix, out, matrix);
  541. } else {
  542. // Doing this we restart the value to the previous position,
  543. // to not maintain a value over 90º or under -90º.
  544. this.positionY_ -= pitch;
  545. }
  546. const out2 = shaka.ui.Matrix4x4.create();
  547. shaka.ui.Matrix4x4.rotateZ(out2, shaka.ui.Matrix4x4.create(), roll);
  548. // Rotate local axis
  549. shaka.ui.Matrix4x4.multiply(matrix, out2, matrix);
  550. this.renderGL_(false);
  551. }
  552. /**
  553. * @param {number} amount
  554. */
  555. zoom(amount) {
  556. const zoomMin = 20;
  557. const zoomMax = 100;
  558. amount /= 50;
  559. if (this.fieldOfView_ >= zoomMin && this.fieldOfView_ <= zoomMax) {
  560. this.fieldOfView_ += amount;
  561. }
  562. if (this.fieldOfView_ < zoomMin) {
  563. this.fieldOfView_ = zoomMin;
  564. } else if (this.fieldOfView_ > zoomMax) {
  565. this.fieldOfView_ = zoomMax;
  566. }
  567. this.renderGL_(false);
  568. }
  569. /**
  570. * @return {number}
  571. */
  572. getFieldOfView() {
  573. return this.fieldOfView_;
  574. }
  575. /**
  576. * @param {number} fieldOfView
  577. */
  578. setFieldOfView(fieldOfView) {
  579. this.fieldOfView_ = fieldOfView;
  580. this.renderGL_(false);
  581. }
  582. /**
  583. * @return {number}
  584. */
  585. getNorth() {
  586. shaka.ui.Matrix4x4.getRotation(this.currentQuaternion_, this.viewMatrix_);
  587. const angles = this.toEulerAngles_(this.currentQuaternion_);
  588. const normalizedDir = {
  589. x: Math.cos(angles.yaw) * Math.cos(angles.pitch),
  590. y: Math.sin(angles.yaw) * Math.cos(angles.pitch),
  591. z: Math.sin(angles.pitch),
  592. };
  593. const northYaw = Math.acos(normalizedDir.x);
  594. return ((northYaw * 180) / Math.PI);
  595. }
  596. /**
  597. * @param {boolean=} firstTime
  598. */
  599. reset(firstTime = true) {
  600. const steps = 20;
  601. if (firstTime) {
  602. shaka.ui.Matrix4x4.getRotation(
  603. this.currentQuaternion_, this.viewMatrix_);
  604. this.cont_ = 0;
  605. this.diff_ = shaka.ui.MatrixQuaternion.create();
  606. this.diff_[0] =
  607. (this.currentQuaternion_[0] - this.originalQuaternion_[0]) / steps;
  608. this.diff_[1] =
  609. (this.currentQuaternion_[1] - this.originalQuaternion_[1]) / steps;
  610. this.diff_[2] =
  611. (this.currentQuaternion_[2] - this.originalQuaternion_[2]) / steps;
  612. this.diff_[3] =
  613. (this.currentQuaternion_[3] - this.originalQuaternion_[3]) / steps;
  614. }
  615. this.currentQuaternion_[0] -= this.diff_[0];
  616. this.currentQuaternion_[1] -= this.diff_[1];
  617. this.currentQuaternion_[2] -= this.diff_[2];
  618. this.currentQuaternion_[3] -= this.diff_[3];
  619. // Set the view to the original matrix
  620. const out = shaka.ui.Matrix4x4.create();
  621. shaka.ui.MatrixQuaternion.normalize(
  622. this.currentQuaternion_, this.currentQuaternion_);
  623. shaka.ui.Matrix4x4.fromQuat(out, this.currentQuaternion_);
  624. this.viewMatrix_ = out;
  625. if (this.resetTimer_) {
  626. this.resetTimer_.stop();
  627. this.resetTimer_ = null;
  628. }
  629. if (this.cont_ < steps) {
  630. this.resetTimer_ = new shaka.util.Timer(() => {
  631. this.reset(false);
  632. this.positionX_ = 0;
  633. this.positionY_ = 0;
  634. this.cont_++;
  635. this.renderGL_(false);
  636. }).tickAfter(shaka.ui.VRWebgl.ANIMATION_DURATION_ / steps);
  637. } else {
  638. shaka.ui.Matrix4x4.fromQuat(out, this.originalQuaternion_);
  639. this.viewMatrix_ = out;
  640. }
  641. }
  642. };
  643. /**
  644. * @const {number}
  645. */
  646. shaka.ui.VRWebgl.ANIMATION_DURATION_ = 0.5;