webXRCamera.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import { Vector3, Matrix, Quaternion } from "../../Maths/math.vector";
  2. import { Scene } from "../../scene";
  3. import { Camera } from "../../Cameras/camera";
  4. import { FreeCamera } from "../../Cameras/freeCamera";
  5. import { TargetCamera } from "../../Cameras/targetCamera";
  6. import { WebXRSessionManager } from "./webXRSessionManager";
  7. import { Viewport } from '../../Maths/math.viewport';
  8. /**
  9. * WebXR Camera which holds the views for the xrSession
  10. * @see https://doc.babylonjs.com/how_to/webxr
  11. */
  12. export class WebXRCamera extends FreeCamera {
  13. /**
  14. * Is the camera in debug mode. Used when using an emulator
  15. */
  16. public debugMode = false;
  17. private _firstFrame = false;
  18. private _referencedPosition: Vector3 = new Vector3();
  19. private _referenceQuaternion: Quaternion = Quaternion.Identity();
  20. private _xrInvPositionCache: Vector3 = new Vector3();
  21. private _xrInvQuaternionCache = Quaternion.Identity();
  22. /**
  23. * Creates a new webXRCamera, this should only be set at the camera after it has been updated by the xrSessionManager
  24. * @param name the name of the camera
  25. * @param scene the scene to add the camera to
  26. */
  27. constructor(name: string, scene: Scene, private _xrSessionManager: WebXRSessionManager) {
  28. super(name, Vector3.Zero(), scene);
  29. // Initial camera configuration
  30. this.minZ = 0.1;
  31. this.rotationQuaternion = new Quaternion();
  32. this.cameraRigMode = Camera.RIG_MODE_CUSTOM;
  33. this.updateUpVectorFromRotation = true;
  34. this._updateNumberOfRigCameras(1);
  35. this._xrSessionManager.onXRSessionInit.add(() => {
  36. this._referencedPosition.copyFromFloats(0, 0, 0);
  37. this._referenceQuaternion.copyFromFloats(0, 0, 0, 1);
  38. // first frame - camera's y position should be 0 for the correct offset
  39. this._firstFrame = true;
  40. });
  41. // Check transformation changes on each frame. Callback is added to be first so that the transformation will be
  42. // applied to the rest of the elements using the referenceSpace object
  43. this._xrSessionManager.onXRFrameObservable.add((frame) => {
  44. if (!this._firstFrame) {
  45. this._updateReferenceSpace();
  46. }
  47. this._updateFromXRSession();
  48. }, undefined, true);
  49. }
  50. private _updateNumberOfRigCameras(viewCount = 1) {
  51. while (this.rigCameras.length < viewCount) {
  52. var newCamera = new TargetCamera("view: " + this.rigCameras.length, Vector3.Zero(), this.getScene());
  53. newCamera.minZ = 0.1;
  54. newCamera.rotationQuaternion = new Quaternion();
  55. newCamera.updateUpVectorFromRotation = true;
  56. this.rigCameras.push(newCamera);
  57. }
  58. while (this.rigCameras.length > viewCount) {
  59. var removedCamera = this.rigCameras.pop();
  60. if (removedCamera) {
  61. removedCamera.dispose();
  62. }
  63. }
  64. }
  65. /** @hidden */
  66. public _updateForDualEyeDebugging(/*pupilDistance = 0.01*/) {
  67. // Create initial camera rigs
  68. this._updateNumberOfRigCameras(2);
  69. this.rigCameras[0].viewport = new Viewport(0, 0, 0.5, 1.0);
  70. // this.rigCameras[0].position.x = -pupilDistance / 2;
  71. this.rigCameras[0].outputRenderTarget = null;
  72. this.rigCameras[1].viewport = new Viewport(0.5, 0, 0.5, 1.0);
  73. // this.rigCameras[1].position.x = pupilDistance / 2;
  74. this.rigCameras[1].outputRenderTarget = null;
  75. }
  76. private _updateReferenceSpace(): boolean {
  77. // were position & rotation updated OUTSIDE of the xr update loop
  78. if (!this.position.equals(this._referencedPosition) || !this.rotationQuaternion.equals(this._referenceQuaternion)) {
  79. this.position.subtractToRef(this._referencedPosition, this._referencedPosition);
  80. this._referenceQuaternion.conjugateInPlace();
  81. this._referenceQuaternion.multiplyToRef(this.rotationQuaternion, this._referenceQuaternion);
  82. this._updateReferenceSpaceOffset(this._referencedPosition, this._referenceQuaternion.normalize());
  83. return true;
  84. }
  85. return false;
  86. }
  87. private _updateReferenceSpaceOffset(positionOffset: Vector3, rotationOffset?: Quaternion, ignoreHeight: boolean = false) {
  88. if (!this._xrSessionManager.referenceSpace || !this._xrSessionManager.currentFrame) {
  89. return;
  90. }
  91. // Compute the origin offset based on player position/orientation.
  92. this._xrInvPositionCache.copyFrom(positionOffset);
  93. if (rotationOffset) {
  94. this._xrInvQuaternionCache.copyFrom(rotationOffset);
  95. } else {
  96. this._xrInvQuaternionCache.copyFromFloats(0, 0, 0, 1);
  97. }
  98. // right handed system
  99. if (!this._scene.useRightHandedSystem) {
  100. this._xrInvPositionCache.z *= -1;
  101. this._xrInvQuaternionCache.z *= -1;
  102. this._xrInvQuaternionCache.w *= -1;
  103. }
  104. this._xrInvPositionCache.negateInPlace();
  105. this._xrInvQuaternionCache.conjugateInPlace();
  106. // transform point according to rotation with pivot
  107. this._xrInvPositionCache.rotateByQuaternionToRef(this._xrInvQuaternionCache, this._xrInvPositionCache);
  108. if (ignoreHeight) {
  109. this._xrInvPositionCache.y = 0;
  110. }
  111. const transform = new XRRigidTransform(
  112. { ...this._xrInvPositionCache },
  113. { ...this._xrInvQuaternionCache });
  114. // Update offset reference to use a new originOffset with the teleported
  115. // player position and orientation.
  116. // This new offset needs to be applied to the base ref space.
  117. const referenceSpace = this._xrSessionManager.referenceSpace.getOffsetReferenceSpace(transform);
  118. const pose = this._xrSessionManager.currentFrame && this._xrSessionManager.currentFrame.getViewerPose(referenceSpace);
  119. if (pose) {
  120. const pos = new Vector3();
  121. pos.copyFrom(<any>(pose.transform.position));
  122. if (!this._scene.useRightHandedSystem) {
  123. pos.z *= -1;
  124. }
  125. this.position.subtractToRef(pos, pos);
  126. if (!this._scene.useRightHandedSystem) {
  127. pos.z *= -1;
  128. }
  129. pos.negateInPlace();
  130. const transform2 = new XRRigidTransform(
  131. { ...pos });
  132. // Update offset reference to use a new originOffset with the teleported
  133. // player position and orientation.
  134. // This new offset needs to be applied to the base ref space.
  135. this._xrSessionManager.referenceSpace = referenceSpace.getOffsetReferenceSpace(transform2);
  136. }
  137. }
  138. private _updateFromXRSession() {
  139. const pose = this._xrSessionManager.currentFrame && this._xrSessionManager.currentFrame.getViewerPose(this._xrSessionManager.referenceSpace);
  140. if (!pose) {
  141. return;
  142. }
  143. if (pose.transform) {
  144. this._referencedPosition.copyFrom(<any>(pose.transform.position));
  145. this._referenceQuaternion.copyFrom(<any>(pose.transform.orientation));
  146. if (!this._scene.useRightHandedSystem) {
  147. this._referencedPosition.z *= -1;
  148. this._referenceQuaternion.z *= -1;
  149. this._referenceQuaternion.w *= -1;
  150. }
  151. if (this._firstFrame) {
  152. this._firstFrame = false;
  153. // we have the XR reference, now use this to find the offset to get the camera to be
  154. // in the right position
  155. // set the height to correlate to the current height
  156. this.position.y += this._referencedPosition.y;
  157. // avoid using the head rotation on the first frame.
  158. this._referenceQuaternion.copyFromFloats(0, 0, 0, 1);
  159. // update the reference space so that the position will be correct
  160. return this.update();
  161. }
  162. this.rotationQuaternion.copyFrom(this._referenceQuaternion);
  163. this.position.copyFrom(this._referencedPosition);
  164. }
  165. // Update camera rigs
  166. if (this.rigCameras.length !== pose.views.length) {
  167. this._updateNumberOfRigCameras(pose.views.length);
  168. }
  169. pose.views.forEach((view: any, i: number) => {
  170. const currentRig = <TargetCamera>this.rigCameras[i];
  171. // update right and left, where applicable
  172. if (!currentRig.isLeftCamera && !currentRig.isRightCamera) {
  173. if (view.eye === 'right') {
  174. currentRig._isRightCamera = true;
  175. } else if (view.eye === 'left') {
  176. currentRig._isLeftCamera = true;
  177. }
  178. }
  179. // Update view/projection matrix
  180. if (view.transform.position) {
  181. currentRig.position.copyFrom(view.transform.position);
  182. currentRig.rotationQuaternion.copyFrom(view.transform.orientation);
  183. if (!this._scene.useRightHandedSystem) {
  184. currentRig.position.z *= -1;
  185. currentRig.rotationQuaternion.z *= -1;
  186. currentRig.rotationQuaternion.w *= -1;
  187. }
  188. } else {
  189. Matrix.FromFloat32ArrayToRefScaled(view.transform.matrix, 0, 1, currentRig._computedViewMatrix);
  190. if (!this._scene.useRightHandedSystem) {
  191. currentRig._computedViewMatrix.toggleModelMatrixHandInPlace();
  192. }
  193. }
  194. Matrix.FromFloat32ArrayToRefScaled(view.projectionMatrix, 0, 1, currentRig._projectionMatrix);
  195. if (!this._scene.useRightHandedSystem) {
  196. currentRig._projectionMatrix.toggleProjectionMatrixHandInPlace();
  197. }
  198. // Update viewport
  199. if (this._xrSessionManager.session.renderState.baseLayer) {
  200. var viewport = this._xrSessionManager.session.renderState.baseLayer.getViewport(view);
  201. var width = this._xrSessionManager.session.renderState.baseLayer.framebufferWidth;
  202. var height = this._xrSessionManager.session.renderState.baseLayer.framebufferHeight;
  203. currentRig.viewport.width = viewport.width / width;
  204. currentRig.viewport.height = viewport.height / height;
  205. currentRig.viewport.x = viewport.x / width;
  206. currentRig.viewport.y = viewport.y / height;
  207. }
  208. if (this.debugMode) {
  209. this._updateForDualEyeDebugging();
  210. }
  211. // Set cameras to render to the session's render target
  212. currentRig.outputRenderTarget = this._xrSessionManager.getRenderTargetTextureForEye(view.eye);
  213. });
  214. }
  215. }