Home Reference Source

viewer/camera.js

  1. import * as mat4 from "./glmatrix/mat4.js";
  2. import * as mat3 from "./glmatrix/mat3.js";
  3. import * as vec3 from "./glmatrix/vec3.js";
  4. import * as vec4 from "./glmatrix/vec4.js";
  5.  
  6. import {AnimatedVec3} from "./animatedvec3.js";
  7. import {Perspective} from "./perspective.js";
  8. import {Orthographic} from "./orthographic.js";
  9.  
  10. /**
  11. A **Camera** defines viewing and projection transforms for its Viewer.
  12. */
  13. export class Camera {
  14.  
  15. constructor(viewer) {
  16. this.viewer = viewer;
  17.  
  18. this.perspective = new Perspective(viewer);
  19.  
  20. this.orthographic = new Orthographic(viewer);
  21.  
  22. this._projection = this.perspective; // Currently active projection
  23. this._viewMatrix = mat4.create();
  24. this._viewProjMatrix = mat4.create();
  25. this._viewMatrixInverted = mat4.create();
  26. this._viewProjMatrixInverted = mat4.create();
  27.  
  28. this._viewNormalMatrix = mat3.create();
  29.  
  30. this._eye = new AnimatedVec3(0.0, 0.0, -10.0); // World-space eye position
  31. this._target = new AnimatedVec3(0.0, 0.0, 0.0); // World-space point-of-interest
  32. this._up = vec3.fromValues(0.0, 1.0, 0.0); // Camera's "up" vector, always orthogonal to eye->target
  33. this._center = vec3.copy(vec3.create(), this._target.get());
  34. this._negatedCenter = vec3.create();
  35. vec3.negate(this._negatedCenter, this._center);
  36.  
  37. this._worldAxis = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
  38. this._worldUp = vec3.fromValues(0.0, 1.0, 0.0); // Direction of "up" in World-space
  39. this._worldRight = vec3.fromValues(1, 0, 0); // Direction of "right" in World-space
  40. this._worldForward = vec3.fromValues(0, 0, -1); // Direction of "forward" in World-space
  41.  
  42. this._gimbalLock = true; // When true, orbiting world-space "up", else orbiting camera's local "up"
  43. this._constrainPitch = true; // When true, will prevent camera} from being rotated upside-down
  44.  
  45. this._dirty = true; // Lazy-builds view matrix
  46. this._locked = false;
  47.  
  48. this._modelBounds = null;
  49.  
  50. this.tempMat4 = mat4.create();
  51. this.tempMat3b = mat3.create();
  52. this.tempVec3 = vec3.create();
  53. this.tempVec3b = vec3.create();
  54. this.tempVec3c = vec3.create();
  55. this.tempVec3d = vec3.create();
  56. this.tempVec3e = vec3.create();
  57. this.tempVecBuild = vec3.create();
  58.  
  59. this.tmp_modelBounds = vec3.create();
  60.  
  61. this.yawMatrix = mat4.create();
  62. // Until there is a proper event handler mechanism, just do it manually.
  63. this.listeners = [];
  64. this.lowVolumeListeners = [];
  65.  
  66. this._orbitting = false;
  67.  
  68. this._tmp_interpolate_current_dir = vec3.create();
  69. this._tmp_interpolate_new_dir = vec3.create();
  70. this._tmp_interpolate_a = vec3.create();
  71. this._tmp_interpolate_b = vec3.create();
  72. this._tmp_interpolate_c = vec3.create();
  73. this._tmp_interpolate_d = vec4.create();
  74. this._tmp_interpolate_e = mat4.create();
  75. this._tmp_interpolate_f = vec3.create();
  76. this._tmp_eye = vec3.create();
  77. this._tmp_target = vec3.create();
  78. }
  79.  
  80. lock() {
  81. this._locked = true;
  82. }
  83.  
  84. unlock() {
  85. this._locked = false;
  86. this._build();
  87. }
  88.  
  89. _setDirty() {
  90. this._dirty = true;
  91. this.viewer.dirty = 2;
  92. }
  93.  
  94. setModelBounds(bounds) {
  95. this._modelBounds = [];
  96.  
  97. this.perspective.setModelBounds(vec3.clone(bounds));
  98. this.orthographic.setModelBounds(vec3.clone(bounds));
  99. // Store aabb calculated from points
  100. let a = vec3.fromValues(+Infinity, +Infinity, +Infinity);
  101. let b = vec3.fromValues(-Infinity, -Infinity, -Infinity);
  102.  
  103. let zero_one = [0,1];
  104.  
  105. for (let i of zero_one) {
  106. for (let j of zero_one) {
  107. for (let k of zero_one) {
  108. let v = vec3.fromValues(bounds[3*i+0], bounds[3*j+1], bounds[3*k+2]);
  109. this._modelBounds.push(v);
  110.  
  111. for (let l = 0; l < 3; ++l) {
  112. if (v[l] < a[l]) {
  113. a[l] = v[l];
  114. }
  115. if (v[l] > b[l]) {
  116. b[l] = v[l];
  117. }
  118. }
  119. }
  120. }
  121. }
  122.  
  123. vec3.add(a, a, b);
  124. vec3.scale(a, a, 0.5);
  125.  
  126. this._center.set(a);
  127. vec3.negate(this._negatedCenter, this._center);
  128. this._dirty = true;
  129. }
  130.  
  131. forceBuild() {
  132. let eye = this._eye.get();
  133. let target = this._target.get();
  134.  
  135. vec3.set(this._up, 0, 0, 1);
  136. vec3.subtract(this.tempVecBuild, target, eye);
  137. vec3.normalize(this.tempVecBuild, this.tempVecBuild);
  138. vec3.cross(this._up, this.tempVecBuild, this._up);
  139. vec3.cross(this._up, this._up, this.tempVecBuild);
  140. if (vec3.equals(this._up, vec3.fromValues(0, 0, 0))) {
  141. // Not good, choose something
  142. vec3.set(this._up, 0, 1, 0);
  143. }
  144.  
  145. mat4.lookAt(this._viewMatrix, eye, target, this._up);
  146. mat3.fromMat4(this.tempMat3b, this._viewMatrix);
  147. mat3.invert(this.tempMat3b, this.tempMat3b);
  148. mat3.transpose(this._viewNormalMatrix, this.tempMat3b);
  149. let [near, far] = [+Infinity, -Infinity];
  150.  
  151. if (!this.viewer.geospatialMode && this._modelBounds) {
  152. for (var v of this._modelBounds) {
  153. vec3.transformMat4(this.tmp_modelBounds, v, this._viewMatrix);
  154. let z = -this.tmp_modelBounds[2];
  155. if (z < near) {
  156. near = z;
  157. }
  158. if (z > far) {
  159. far = z;
  160. }
  161. }
  162.  
  163. if (near < 1.e-3) {
  164. near = far / 1000.;
  165. }
  166. } else {
  167. [near, far] = [+1000, +200000. + vec3.length(this.eye)];
  168. }
  169.  
  170. this.perspective.near = near - 1e-2;
  171. this.perspective.far = far + 1e-2;
  172. this.orthographic.near = near - 1e-2;
  173. this.orthographic.far = far + 1e-2;
  174.  
  175. mat4.invert(this._viewMatrixInverted, this._viewMatrix);
  176. mat4.multiply(this._viewProjMatrix, this.projMatrix, this._viewMatrix);
  177. mat4.invert(this._viewProjMatrixInverted, this._viewProjMatrix);
  178.  
  179. this._dirty = false;
  180. for (var listener of this.listeners) {
  181. listener();
  182. }
  183. }
  184. _build() {
  185. if (this._dirty && !this._locked && this._modelBounds) {
  186. this.forceBuild();
  187. }
  188. }
  189.  
  190. /**
  191. Gets the current viewing transform matrix.
  192.  
  193. @return {Float32Array} 4x4 column-order matrix as an array of 16 contiguous floats.
  194. */
  195. get viewMatrix() {
  196. if (this._dirty) {
  197. this._build();
  198. }
  199. return this._viewMatrix;
  200. }
  201.  
  202. /**
  203. Gets the current view projection matrix.
  204.  
  205. @return {Float32Array} 4x4 column-order matrix as an array of 16 contiguous floats.
  206. */
  207. get viewProjMatrix() {
  208. if (this._dirty) {
  209. this._build();
  210. }
  211. return this._viewProjMatrix;
  212. }
  213.  
  214. /**
  215. Gets the current inverted view projection matrix.
  216.  
  217. @return {Float32Array} 4x4 column-order matrix as an array of 16 contiguous floats.
  218. */
  219. get viewProjMatrixInverted() {
  220. if (this._dirty) {
  221. this._build();
  222. }
  223. return this._viewProjMatrixInverted;
  224. }
  225.  
  226. get viewMatrixInverted() {
  227. if (this._dirty) {
  228. this._build();
  229. }
  230. return this._viewMatrixInverted;
  231. }
  232.  
  233. /**
  234. Gets the current viewing transform matrix for normals.
  235.  
  236. This is the transposed inverse of the view matrix.
  237.  
  238. @return {Float32Array} 4x4 column-order matrix as an array of 16 contiguous floats.
  239. */
  240. get viewNormalMatrix() {
  241. if (this._dirty) {
  242. this._build();
  243. }
  244. return this._viewNormalMatrix;
  245. }
  246.  
  247. /**
  248. Gets the current projection transform matrix.
  249.  
  250. @return {Float32Array} 4x4 column-order matrix as an array of 16 contiguous floats.
  251. */
  252. get projMatrix() {
  253. return this._projection.projMatrix;
  254. }
  255.  
  256. /**
  257. Selects the current projection type.
  258.  
  259. @param {String} projectionType Accepted values are "persp" or "ortho".
  260. */
  261. set projectionType(projectionType) {
  262. if (projectionType.toLowerCase().startsWith("persp")) {
  263. this._projection = this.perspective;
  264. } else if (projectionType.toLowerCase().startsWith("ortho")) {
  265. this._projection = this.orthographic;
  266. } else {
  267. console.error("Unsupported projectionType: " + projectionType);
  268. }
  269. this.viewer.dirty = 2;
  270. }
  271.  
  272. /**
  273. Gets the current projection type.
  274.  
  275. @return {String} projectionType "persp" or "ortho".
  276. */
  277. get projectionType() {
  278. return this._projection.constructor.name.substr(0,5).toLowerCase();
  279. }
  280.  
  281. /**
  282. Gets the component that represents the current projection type.
  283.  
  284. @return {Perspective|Orthographic}
  285. */
  286. get projection() {
  287. return this._projection;
  288. }
  289.  
  290. /**
  291. Sets the position of the camera.
  292. @param {Float32Array} eye 3D position of the camera in World space.
  293. */
  294. set eye(eye) {
  295. if (!vec3.equals(this._eye.get(), eye)) {
  296. this._eye.get().set(eye);
  297. this._setDirty();
  298. for (var listener of this.lowVolumeListeners) {
  299. listener();
  300. }
  301. }
  302. }
  303.  
  304. /**
  305. Gets the position of the camera.
  306. @return {Float32Array} 3D position of the camera in World space.
  307. */
  308. get eye() {
  309. return this._eye.get();
  310. }
  311.  
  312. /**
  313. Sets the point the camera is looking at.
  314. @param {Float32Array} target 3D position of the point of interest in World space.
  315. */
  316. set target(target) {
  317. if (!vec3.equals(this._target.get(), target)) {
  318. this._target.get().set(target);
  319. this._setDirty();
  320. for (var listener of this.lowVolumeListeners) {
  321. listener();
  322. }
  323. }
  324. }
  325.  
  326. /**
  327. Gets the point tha camera is looking at.
  328. @return {Float32Array} 3D position of the point of interest in World space.
  329. */
  330. get target() {
  331. return this._target.get();
  332. }
  333.  
  334. set center(v) {
  335. if (!vec3.equals(this._center, v)) {
  336. this._center.set(v);
  337. vec3.negate(this._negatedCenter, this._center);
  338. this.listeners.forEach((fn) => { fn(); });
  339. }
  340. }
  341.  
  342. get center() {
  343. return this._center;
  344. }
  345.  
  346. /**
  347. Sets the camera's "up" direction.
  348. @param {Float32Array} up 3D vector indicating the camera's "up" direction in World-space.
  349. */
  350. set up(up) {
  351. this._up.set(up || [0.0, 1.0, 0.0]);
  352. this._setDirty();
  353. }
  354.  
  355. /**
  356. Gets the camera's "up" direction.
  357. @return {Float32Array} 3D vector indicating the camera's "up" direction in World-space.
  358. */
  359. get up() {
  360. return this._up;
  361. }
  362.  
  363. /**
  364. Sets whether camera rotation is gimbal locked.
  365.  
  366. When true, yaw rotation will always pivot about the World-space "up" axis.
  367.  
  368. @param {Boolean} gimbalLock Whether or not to enable gimbal locking.
  369. */
  370. set gimbalLock(gimbalLock) {
  371. this._gimbalLock = gimbalLock;
  372. }
  373.  
  374. /**
  375. Sets whether camera rotation is gimbal locked.
  376.  
  377. When true, yaw rotation will always pivot about the World-space "up" axis.
  378.  
  379. @return {Boolean} True if gimbal locking is enabled.
  380. */
  381. get gimbalLock() {
  382. return this._gimbalLock;
  383. }
  384.  
  385. /**
  386. Sets whether its currently possible to pitch the camera to look at the model upside-down.
  387.  
  388. When this is true, camera will ignore attempts to orbit (camera or model) about the horizontal axis
  389. that would result in the model being viewed upside-down.
  390.  
  391. @param {Boolean} constrainPitch Whether or not to activate the constraint.
  392. */
  393. set constrainPitch(constrainPitch) {
  394. this._constrainPitch = constrainPitch;
  395. }
  396.  
  397. /**
  398. Gets whether its currently possible to pitch the camera to look at the model upside-down.
  399.  
  400. @return {Boolean}
  401. */
  402. get constrainPitch() {
  403. return this._constrainPitch;
  404. }
  405.  
  406. /**
  407. Indicates the up, right and forward axis of the World coordinate system.
  408.  
  409. This is used for deriving rotation axis for yaw orbiting, and for moving camera to axis-aligned positions.
  410.  
  411. Has format: ````[rightX, rightY, rightZ, upX, upY, upZ, forwardX, forwardY, forwardZ]````
  412.  
  413. @type {Float32Array}
  414. */
  415. set worldAxis(worldAxis) {
  416. this._worldAxis.set(worldAxis || [1, 0, 0, 0, 1, 0, 0, 0, 1]);
  417. this._worldRight[0] = this._worldAxis[0];
  418. this._worldRight[1] = this._worldAxis[1];
  419. this._worldRight[2] = this._worldAxis[2];
  420. this._worldUp[0] = this._worldAxis[3];
  421. this._worldUp[1] = this._worldAxis[4];
  422. this._worldUp[2] = this._worldAxis[5];
  423. this._worldForward[0] = this._worldAxis[6];
  424. this._worldForward[1] = this._worldAxis[7];
  425. this._worldForward[2] = this._worldAxis[8];
  426. this._setDirty();
  427. }
  428.  
  429. /**
  430. Indicates the up, right and forward axis of the World coordinate system.
  431.  
  432. This is used for deriving rotation axis for yaw orbiting, and for moving camera to axis-aligned positions.
  433.  
  434. Has format: ````[rightX, rightY, rightZ, upX, upY, upZ, forwardX, forwardY, forwardZ]````
  435.  
  436. @type {Float32Array}
  437. */
  438. get worldAxis() {
  439. return this._worldAxis;
  440. }
  441.  
  442. /**
  443. Direction of World-space "up".
  444.  
  445. @type Float32Array
  446. */
  447. get worldUp() {
  448. return this._worldUp;
  449. }
  450.  
  451. /**
  452. Direction of World-space "right".
  453.  
  454. @type Float32Array
  455. */
  456. get worldRight() {
  457. return this._worldRight;
  458. }
  459.  
  460. /**
  461. Direction of World-space "forwards".
  462.  
  463. @type Float32Array
  464. */
  465. get worldForward() {
  466. return this._worldForward;
  467. }
  468.  
  469. set orbitting(orbitting) {
  470. if (this._orbitting != orbitting) {
  471. for (var listener of this.lowVolumeListeners) {
  472. listener();
  473. }
  474. }
  475. this._orbitting = orbitting;
  476. }
  477.  
  478. get orbitting() {
  479. return this._orbitting;
  480. }
  481. /**
  482. Rotates the eye position about the target position, pivoting around the up vector.
  483.  
  484. @param {Number} degrees Angle of rotation in degrees
  485. */
  486. orbitYaw(degrees) {
  487.  
  488. let eye = this._eye.get();
  489. let target = this._target.get();
  490.  
  491. // @todo, these functions are not efficient nor numerically stable, but simple to understand.
  492. mat4.identity(this.yawMatrix);
  493. mat4.translate(this.yawMatrix, this.yawMatrix, this._center);
  494. mat4.rotate(this.yawMatrix, this.yawMatrix, degrees * 0.0174532925 * 2, this._worldUp);
  495. mat4.translate(this.yawMatrix, this.yawMatrix, this._negatedCenter);
  496. vec3.transformMat4(eye, eye, this.yawMatrix);
  497. vec3.transformMat4(target, target, this.yawMatrix);
  498.  
  499. this._setDirty();
  500. return;
  501. }
  502.  
  503. /**
  504. Rotates the eye position about the target position, pivoting around the right axis (orthogonal to up vector and eye->target vector).
  505.  
  506. @param {Number} degrees Angle of rotation in degrees
  507. */
  508. orbitPitch(degrees) { // Rotate (pitch) 'eye' and 'up' about 'target', pivoting around vector ortho to (target->eye) and camera 'up'
  509. let currentPitch = Math.acos(this._viewMatrix[10]);
  510. let adjustment = - degrees * 0.0174532925 * 2;
  511. if (currentPitch + adjustment < 0.01) {
  512. adjustment = 0.01 - currentPitch;
  513. }
  514. if (currentPitch + adjustment > Math.PI - 0.01) {
  515. adjustment = Math.PI - 0.01 - currentPitch;
  516. }
  517.  
  518. if (Math.abs(adjustment) < 1.e-5) {
  519. return;
  520. }
  521.  
  522. let eye = this._eye.get();
  523. let target = this._target.get();
  524.  
  525. var T1 = mat4.fromTranslation(mat4.create(), this._center);
  526. var R = mat4.fromRotation(mat4.create(), adjustment, this._viewMatrixInverted);
  527. var T2 = mat4.fromTranslation(mat4.create(), vec3.negate(vec3.create(), this._center));
  528.  
  529. vec3.transformMat4(eye, eye, T2);
  530. vec3.transformMat4(eye, eye, R);
  531. vec3.transformMat4(eye, eye, T1);
  532.  
  533. vec3.transformMat4(target, target, T2);
  534. vec3.transformMat4(target, target, R);
  535. vec3.transformMat4(target, target, T1);
  536.  
  537. this._setDirty();
  538. return;
  539. }
  540.  
  541. /**
  542. Rotates the target position about the eye, pivoting around the right axis (orthogonal to up vector and eye->target vector).
  543.  
  544. @param {Number} degrees Angle of rotation in degrees
  545. */
  546. pitch(degrees) { // Rotate (pitch) 'eye' and 'up' about 'target', pivoting around horizontal vector ortho to (target->eye) and camera 'up'
  547. let eye = this._eye.get();
  548. let target = this._target.get();
  549.  
  550. var eyeToTarget = vec3.subtract(this.tempVec3, target, eye);
  551. var a = vec3.normalize(this.tempVec3c, eyeToTarget);
  552. var b = vec3.normalize(this.tempVec3d, this._up);
  553. var axis = vec3.cross(this.tempVec3b, a, b); // Pivot vector is orthogonal to target->eye
  554. mat4.fromRotation(this.tempMat4, degrees * 0.0174532925, axis);
  555. vec3.transformMat4(eyeToTarget, eyeToTarget, this.tempMat4); // Rotate vector
  556. var newUp = vec3.transformMat4(this.tempVec3d, this._up, this.tempMat4); // Rotate 'up' vector
  557. if (this._constrainPitch) {
  558. var angle = vec3.dot(newUp, this._worldUp) / 0.0174532925; // Don't allow 'up' to go up[side-down with respect to World 'up'
  559. if (angle < 1) {
  560. return;
  561. }
  562. }
  563. this._up.set(newUp);
  564. vec3.add(target, eye, eyeToTarget); // Derive 'target'} from eye and vector
  565. this._setDirty();
  566. }
  567.  
  568. /**
  569. Pans the camera along the camera's local X, Y and Z axis.
  570.  
  571. @param {Array} pan The pan vector
  572. */
  573. pan(pan) { // Translate 'eye' and 'target' along local camera axis
  574. let eye = this._eye.get();
  575. let target = this._target.get();
  576.  
  577. var eyeToTarget = vec3.subtract(this.tempVec3, eye, target);
  578. var vec = [0, 0, 0];
  579. if (pan[0] !== 0) {
  580. let a = vec3.normalize(this.tempVec3b, eyeToTarget); // Get vector orthogonal to 'up' and eye->target
  581. let b = vec3.normalize(this.tempVec3c, this._up);
  582. let v = vec3.cross(this.tempVec3d, a, b);
  583. vec3.scale(v, v, pan[0]);
  584. vec[0] += v[0];
  585. vec[1] += v[1];
  586. vec[2] += v[2];
  587. }
  588. if (pan[1] !== 0) {
  589. let v = vec3.scale(this.tempVec3, vec3.normalize(this.tempVec3b, this._up), pan[1]);
  590. vec[0] += v[0];
  591. vec[1] += v[1];
  592. vec[2] += v[2];
  593. }
  594. if (pan[2] !== 0) {
  595. let v = vec3.scale(this.tempVec3, vec3.normalize(this.tempVec3b, eyeToTarget), pan[2]);
  596. vec[0] += v[0];
  597. vec[1] += v[1];
  598. vec[2] += v[2];
  599. }
  600. vec3.add(eye, eye, vec);
  601. this.target = vec3.add(target, target, vec);
  602. this._setDirty();
  603. }
  604.  
  605. /**
  606. Moves the camera along a ray through unprojected mouse coordinates
  607.  
  608. @param {Number} delta Zoom increment
  609. @param canvasPos Mouse position relative to canvas to determine ray along which to move
  610. */
  611. zoom(delta, canvasPos) { // Translate 'eye' by given increment on (eye->target) vector
  612. // @todo: also not efficient
  613.  
  614. let eye = this._eye.get();
  615. let target = this._target.get();
  616.  
  617. this.orthographic.zoom(delta);
  618. let [x,y] = canvasPos;
  619. vec3.set(this.tempVec3, x / this.viewer.width * 2 - 1, - y / this.viewer.height * 2 + 1, 1.);
  620. vec3.transformMat4(this.tempVec3, this.tempVec3, this.projection.projMatrixInverted);
  621. vec3.transformMat4(this.tempVec3, this.tempVec3, this.viewMatrixInverted);
  622. vec3.subtract(this.tempVec3, this.tempVec3, eye);
  623. vec3.normalize(this.tempVec3, this.tempVec3);
  624. vec3.scale(this.tempVec3, this.tempVec3, -delta);
  625.  
  626. vec3.add(eye, eye, this.tempVec3);
  627. vec3.add(target, target, this.tempVec3);
  628.  
  629. this._setDirty();
  630.  
  631. this.updateLowVolumeListeners();
  632. }
  633. updateLowVolumeListeners() {
  634. for (var listener of this.lowVolumeListeners) {
  635. listener();
  636. }
  637. }
  638.  
  639. calcViewFit(aabb, fitFOV, eye, target) {
  640. aabb = aabb || this.viewer.modelBounds;
  641. fitFOV = fitFOV || this.perspective.fov;
  642. var eyeToTarget = vec3.normalize(this.tempVec3b, vec3.subtract(this.tempVec3, eye, target));
  643. var diagonal = Math.sqrt(
  644. Math.pow(aabb[3] - aabb[0], 2) +
  645. Math.pow(aabb[4] - aabb[1], 2) +
  646. Math.pow(aabb[5] - aabb[2], 2));
  647. var center = [
  648. (aabb[3] + aabb[0]) / 2,
  649. (aabb[4] + aabb[1]) / 2,
  650. (aabb[5] + aabb[2]) / 2
  651. ];
  652. target.set(center);
  653. var sca = Math.abs(diagonal / Math.tan(fitFOV * 0.0174532925));
  654. eye[0] = target[0] + (eyeToTarget[0] * sca);
  655. eye[1] = target[1] + (eyeToTarget[1] * sca);
  656. eye[2] = target[2] + (eyeToTarget[2] * sca);
  657.  
  658. this._setDirty();
  659. }
  660.  
  661. interpolateView(newEye, newTarget) {
  662. this._eye.deanimate();
  663. this._target.deanimate();
  664.  
  665. vec3.sub(this._tmp_interpolate_current_dir, this._target.get(), this._eye.get());
  666. vec3.normalize(this._tmp_interpolate_current_dir, this._tmp_interpolate_current_dir);
  667.  
  668. vec3.sub(this._tmp_interpolate_new_dir, newTarget, newEye);
  669. vec3.normalize(this._tmp_interpolate_new_dir, this._tmp_interpolate_new_dir);
  670.  
  671. let d = vec3.dot(this._tmp_interpolate_current_dir, this._tmp_interpolate_new_dir);
  672.  
  673. if (d > 0.5) {
  674. // More or less pointing in the same direction
  675.  
  676. this._eye.b.set(newEye);
  677. this._target.b.set(newTarget);
  678. this._eye.animate(1000);
  679. this._target.animate(1000);
  680. } else {
  681. // Add an additional intermediate interpolation point
  682. // Add a point on the bisectrice of d1 d2
  683.  
  684. let an = Math.acos(d);
  685. let d1 = vec3.subtract(this._tmp_interpolate_a, newEye, newTarget);
  686. let d2 = vec3.subtract(this._tmp_interpolate_b, this.eye, newTarget);
  687. let l1 = vec3.len(d1);
  688. let l2 = vec3.len(d2);
  689. vec3.normalize(d1, d1);
  690. vec3.normalize(d2, d2);
  691.  
  692. let d3;
  693. if (d < -0.99) {
  694. // parallel view vecs, choose arbitrary axis
  695. let temp;
  696. if (Math.abs(d1[1]) > 0.99) {
  697. temp = vec3.fromValues(1,0,0);
  698. } else {
  699. temp = vec3.fromValues(0,1,0);
  700. }
  701. d3 = vec3.cross(this._tmp_interpolate_c, d1, temp);
  702. } else {
  703. d3 = vec3.cross(this._tmp_interpolate_c, d1, d2);
  704. }
  705. vec3.normalize(d3, d3);
  706. let rot = mat4.fromRotation(this._tmp_interpolate_e, an / 2., d3);
  707. let intermediate = this._tmp_interpolate_d;
  708. vec3.copy(intermediate, d1);
  709. vec4.transformMat4(intermediate, intermediate, rot);
  710. vec3.normalize(intermediate, intermediate);
  711. vec3.scale(intermediate, intermediate, (l1 + l2) / 2.);
  712. vec3.add(intermediate, intermediate, newTarget);
  713. let intermediate3 = intermediate.subarray(0,3);
  714. this._eye.b.set(intermediate3);
  715. this._target.b.set(vec3.lerp(this._tmp_interpolate_f, this.target, newTarget, 0.5));
  716. this._eye.c.set(newEye);
  717. this._target.c.set(newTarget);
  718. this._eye.animate(500, 500);
  719. this._target.animate(500, 500);
  720. }
  721. }
  722.  
  723. /**
  724. Jumps the camera to look at the given axis-aligned World-space bounding box.
  725.  
  726. @param {Float32Array} aabb The axis-aligned World-space bounding box (AABB).
  727. @param {Number} fitFOV Field-of-view occupied by the AABB when the camera has fitted it to view.
  728. */
  729. viewFit(params) {
  730. let eye, target, eye2, target2;
  731. if (params.animate) {
  732. eye = this._eye.get();
  733. target = this._target.get();
  734.  
  735. eye2 = this._tmp_eye;
  736. target2 = this._tmp_target;
  737. } else {
  738. eye2 = eye = this._eye.get();
  739. target2 = target = this._target.get();
  740. }
  741.  
  742. if (params.viewDirection) {
  743. eye = params.viewDirection;
  744. }
  745.  
  746. const aabb = params.aabb || this.viewer.modelBounds;
  747. const fitFOV = this.perspective.fov;
  748. var eyeToTarget = vec3.normalize(this.tempVec3b, vec3.subtract(this.tempVec3, eye, target));
  749. var diagonal = Math.sqrt(
  750. Math.pow(aabb[3] - aabb[0], 2) +
  751. Math.pow(aabb[4] - aabb[1], 2) +
  752. Math.pow(aabb[5] - aabb[2], 2));
  753. var center = [
  754. (aabb[3] + aabb[0]) / 2,
  755. (aabb[4] + aabb[1]) / 2,
  756. (aabb[5] + aabb[2]) / 2
  757. ];
  758. target2.set(center);
  759. var sca = Math.abs(diagonal / Math.tan(fitFOV * 0.0174532925));
  760. eye2[0] = target2[0] + (eyeToTarget[0] * sca);
  761. eye2[1] = target2[1] + (eyeToTarget[1] * sca);
  762. eye2[2] = target2[2] + (eyeToTarget[2] * sca);
  763.  
  764. if (params.animate) {
  765. this.interpolateView(this._tmp_eye, this._tmp_target);
  766. }
  767.  
  768. this._setDirty();
  769. }
  770.  
  771. restore(params) {
  772. if (params.type) {
  773. this.projectionType = params.type;
  774. }
  775. if (this._projection instanceof Perspective && params.fovy) {
  776. this._projection.fov = params.fovy;
  777. }
  778. ["eye", "target", "up"].forEach((k) => {
  779. if (params[k]) {
  780. let fn_set = Object.getOwnPropertyDescriptor(this, k).set;
  781. let fn_get = Object.getOwnPropertyDescriptor(this, k).get;
  782. let fn_update;
  783. if (params[k] instanceof AnimatedVec3) {
  784. fn_update = (t, v) => { fn_get(t).set(v); };
  785. } else {
  786. fn_update = fn_set;
  787. }
  788. fn_update(this, params[k]);
  789. }
  790. });
  791. }
  792. }