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