stereo.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. /**
  2. * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. *
  7. * Adapted from three.js, The MIT License, Copyright © 2010-2020 three.js authors
  8. */
  9. import { Mat4 } from '../../mol-math/linear-algebra';
  10. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  11. import { Camera, ICamera } from '../camera';
  12. import { Viewport } from './util';
  13. export const StereoCameraParams = {
  14. eyeSeparation: PD.Numeric(0.064, { min: 0.01, max: 0.5, step: 0.001 }),
  15. focus: PD.Numeric(10, { min: 1, max: 100, step: 0.01 }),
  16. };
  17. export const DefaultStereoCameraProps = PD.getDefaultValues(StereoCameraParams);
  18. export type StereoCameraProps = PD.Values<typeof StereoCameraParams>
  19. export { StereoCamera };
  20. class StereoCamera {
  21. readonly left: ICamera = new EyeCamera();
  22. readonly right: ICamera = new EyeCamera();
  23. get viewport() {
  24. return this.parent.viewport;
  25. }
  26. get viewOffset() {
  27. return this.parent.viewOffset;
  28. }
  29. private props: StereoCameraProps;
  30. constructor(private parent: Camera, props: Partial<StereoCameraProps> = {}) {
  31. this.props = { ...DefaultStereoCameraProps, ...props };
  32. }
  33. setProps(props: Partial<StereoCameraProps>) {
  34. Object.assign(this.props, props);
  35. }
  36. update() {
  37. this.parent.update();
  38. update(this.parent, this.props, this.left as EyeCamera, this.right as EyeCamera);
  39. }
  40. }
  41. namespace StereoCamera {
  42. export function is(camera: Camera | StereoCamera): camera is StereoCamera {
  43. return 'left' in camera && 'right' in camera;
  44. }
  45. }
  46. class EyeCamera implements ICamera {
  47. viewport = Viewport.create(0, 0, 0, 0);
  48. view = Mat4();
  49. projection = Mat4();
  50. projectionView = Mat4();
  51. inverseProjectionView = Mat4();
  52. state: Readonly<Camera.Snapshot> = Camera.createDefaultSnapshot();
  53. viewOffset: Readonly<Camera.ViewOffset> = Camera.ViewOffset();
  54. far: number = 0;
  55. near: number = 0;
  56. fogFar: number = 0;
  57. fogNear: number = 0;
  58. }
  59. const eyeLeft = Mat4.identity(), eyeRight = Mat4.identity();
  60. function update(camera: Camera, props: StereoCameraProps, left: EyeCamera, right: EyeCamera) {
  61. // Copy the states
  62. Viewport.copy(left.viewport, camera.viewport);
  63. Mat4.copy(left.view, camera.view);
  64. Mat4.copy(left.projection, camera.projection);
  65. Camera.copySnapshot(left.state, camera.state);
  66. Camera.copyViewOffset(left.viewOffset, camera.viewOffset);
  67. left.far = camera.far;
  68. left.near = camera.near;
  69. left.fogFar = camera.fogFar;
  70. left.fogNear = camera.fogNear;
  71. Viewport.copy(right.viewport, camera.viewport);
  72. Mat4.copy(right.view, camera.view);
  73. Mat4.copy(right.projection, camera.projection);
  74. Camera.copySnapshot(right.state, camera.state);
  75. Camera.copyViewOffset(right.viewOffset, camera.viewOffset);
  76. right.far = camera.far;
  77. right.near = camera.near;
  78. right.fogFar = camera.fogFar;
  79. right.fogNear = camera.fogNear;
  80. // update the view offsets
  81. const w = Math.floor(camera.viewport.width / 2);
  82. const aspect = w / camera.viewport.height;
  83. left.viewport.width = w;
  84. right.viewport.x += w;
  85. right.viewport.width -= w;
  86. // update the projection and view matrices
  87. const eyeSepHalf = props.eyeSeparation / 2;
  88. const eyeSepOnProjection = eyeSepHalf * camera.near / props.focus;
  89. const ymax = camera.near * Math.tan(camera.state.fov * 0.5);
  90. let xmin, xmax;
  91. // translate xOffset
  92. eyeLeft[12] = -eyeSepHalf;
  93. eyeRight[12] = eyeSepHalf;
  94. // for left eye
  95. xmin = -ymax * aspect + eyeSepOnProjection;
  96. xmax = ymax * aspect + eyeSepOnProjection;
  97. left.projection[0] = 2 * camera.near / (xmax - xmin);
  98. left.projection[8] = (xmax + xmin) / (xmax - xmin);
  99. Mat4.mul(left.view, left.view, eyeLeft);
  100. Mat4.mul(left.projectionView, left.projection, left.view);
  101. Mat4.invert(left.inverseProjectionView, left.projectionView);
  102. // for right eye
  103. xmin = -ymax * aspect - eyeSepOnProjection;
  104. xmax = ymax * aspect - eyeSepOnProjection;
  105. right.projection[0] = 2 * camera.near / (xmax - xmin);
  106. right.projection[8] = (xmax + xmin) / (xmax - xmin);
  107. Mat4.mul(right.view, right.view, eyeRight);
  108. Mat4.mul(right.projectionView, right.projection, right.view);
  109. Mat4.invert(right.inverseProjectionView, right.projectionView);
  110. }