webXRCamera.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. import { Vector3, Matrix, Quaternion, TmpVectors } from "../Maths/math.vector.js";
  2. import { Camera } from "../Cameras/camera.js";
  3. import { FreeCamera } from "../Cameras/freeCamera.js";
  4. import { TargetCamera } from "../Cameras/targetCamera.js";
  5. import { Viewport } from "../Maths/math.viewport.js";
  6. import { Observable } from "../Misc/observable.js";
  7. import { WebXRTrackingState } from "./webXRTypes.js";
  8. /**
  9. * WebXR Camera which holds the views for the xrSession
  10. * @see https://doc.babylonjs.com/features/featuresDeepDive/webXR/webXRCamera
  11. */
  12. export class WebXRCamera extends FreeCamera {
  13. /**
  14. * Creates a new webXRCamera, this should only be set at the camera after it has been updated by the xrSessionManager
  15. * @param name the name of the camera
  16. * @param scene the scene to add the camera to
  17. * @param _xrSessionManager a constructed xr session manager
  18. */
  19. constructor(name, scene, _xrSessionManager) {
  20. super(name, Vector3.Zero(), scene);
  21. this._xrSessionManager = _xrSessionManager;
  22. this._firstFrame = false;
  23. this._referenceQuaternion = Quaternion.Identity();
  24. this._referencedPosition = new Vector3();
  25. this._trackingState = WebXRTrackingState.NOT_TRACKING;
  26. /**
  27. * This will be triggered after the first XR Frame initialized the camera,
  28. * including the right number of views and their rendering parameters
  29. */
  30. this.onXRCameraInitializedObservable = new Observable();
  31. /**
  32. * Observable raised before camera teleportation
  33. * @deprecated use onBeforeCameraTeleport of the teleportation feature instead
  34. */
  35. this.onBeforeCameraTeleport = new Observable();
  36. /**
  37. * Observable raised after camera teleportation
  38. * @deprecated use onAfterCameraTeleport of the teleportation feature instead
  39. */
  40. this.onAfterCameraTeleport = new Observable();
  41. /**
  42. * Notifies when the camera's tracking state has changed.
  43. * Notice - will also be triggered when tracking has started (at the beginning of the session)
  44. */
  45. this.onTrackingStateChanged = new Observable();
  46. /**
  47. * Should position compensation execute on first frame.
  48. * This is used when copying the position from a native (non XR) camera
  49. */
  50. this.compensateOnFirstFrame = true;
  51. this._rotate180 = new Quaternion(0, 1, 0, 0);
  52. // Initial camera configuration
  53. this.minZ = 0.1;
  54. this.rotationQuaternion = new Quaternion();
  55. this.cameraRigMode = Camera.RIG_MODE_CUSTOM;
  56. this.updateUpVectorFromRotation = true;
  57. this._updateNumberOfRigCameras(1);
  58. // freeze projection matrix, which will be copied later
  59. this.freezeProjectionMatrix();
  60. this._deferOnly = true;
  61. this._xrSessionManager.onXRSessionInit.add(() => {
  62. this._referencedPosition.copyFromFloats(0, 0, 0);
  63. this._referenceQuaternion.copyFromFloats(0, 0, 0, 1);
  64. // first frame - camera's y position should be 0 for the correct offset
  65. this._firstFrame = this.compensateOnFirstFrame;
  66. this._xrSessionManager.onWorldScaleFactorChangedObservable.add(() => {
  67. // only run if in session
  68. if (!this._xrSessionManager.currentFrame) {
  69. return;
  70. }
  71. this._updateDepthNearFar();
  72. });
  73. });
  74. // Check transformation changes on each frame. Callback is added to be first so that the transformation will be
  75. // applied to the rest of the elements using the referenceSpace object
  76. this._xrSessionManager.onXRFrameObservable.add(() => {
  77. if (this._firstFrame) {
  78. this._updateFromXRSession();
  79. }
  80. if (this.onXRCameraInitializedObservable.hasObservers()) {
  81. this.onXRCameraInitializedObservable.notifyObservers(this);
  82. this.onXRCameraInitializedObservable.clear();
  83. }
  84. if (this._deferredUpdated) {
  85. this.position.copyFrom(this._deferredPositionUpdate);
  86. this.rotationQuaternion.copyFrom(this._deferredRotationQuaternionUpdate);
  87. }
  88. this._updateReferenceSpace();
  89. this._updateFromXRSession();
  90. }, undefined, true);
  91. }
  92. /**
  93. * Get the current XR tracking state of the camera
  94. */
  95. get trackingState() {
  96. return this._trackingState;
  97. }
  98. _setTrackingState(newState) {
  99. if (this._trackingState !== newState) {
  100. this._trackingState = newState;
  101. this.onTrackingStateChanged.notifyObservers(newState);
  102. }
  103. }
  104. /**
  105. * Return the user's height, unrelated to the current ground.
  106. * This will be the y position of this camera, when ground level is 0.
  107. *
  108. * Note - this value is multiplied by the worldScalingFactor (if set), so it will be in the same units as the scene.
  109. */
  110. get realWorldHeight() {
  111. const basePose = this._xrSessionManager.currentFrame && this._xrSessionManager.currentFrame.getViewerPose(this._xrSessionManager.baseReferenceSpace);
  112. if (basePose && basePose.transform) {
  113. return basePose.transform.position.y * this._xrSessionManager.worldScalingFactor;
  114. }
  115. else {
  116. return 0;
  117. }
  118. }
  119. /** @internal */
  120. _updateForDualEyeDebugging( /*pupilDistance = 0.01*/) {
  121. // Create initial camera rigs
  122. this._updateNumberOfRigCameras(2);
  123. this.rigCameras[0].viewport = new Viewport(0, 0, 0.5, 1.0);
  124. // this.rigCameras[0].position.x = -pupilDistance / 2;
  125. this.rigCameras[0].outputRenderTarget = null;
  126. this.rigCameras[1].viewport = new Viewport(0.5, 0, 0.5, 1.0);
  127. // this.rigCameras[1].position.x = pupilDistance / 2;
  128. this.rigCameras[1].outputRenderTarget = null;
  129. }
  130. /**
  131. * Sets this camera's transformation based on a non-vr camera
  132. * @param otherCamera the non-vr camera to copy the transformation from
  133. * @param resetToBaseReferenceSpace should XR reset to the base reference space
  134. */
  135. setTransformationFromNonVRCamera(otherCamera = this.getScene().activeCamera, resetToBaseReferenceSpace = true) {
  136. if (!otherCamera || otherCamera === this) {
  137. return;
  138. }
  139. const mat = otherCamera.computeWorldMatrix();
  140. mat.decompose(undefined, this.rotationQuaternion, this.position);
  141. // set the ground level
  142. this.position.y = 0;
  143. Quaternion.FromEulerAnglesToRef(0, this.rotationQuaternion.toEulerAngles().y, 0, this.rotationQuaternion);
  144. this._firstFrame = true;
  145. if (resetToBaseReferenceSpace) {
  146. this._xrSessionManager.resetReferenceSpace();
  147. }
  148. }
  149. /**
  150. * Gets the current instance class name ("WebXRCamera").
  151. * @returns the class name
  152. */
  153. getClassName() {
  154. return "WebXRCamera";
  155. }
  156. /**
  157. * Set the target for the camera to look at.
  158. * Note that this only rotates around the Y axis, as opposed to the default behavior of other cameras
  159. * @param target the target to set the camera to look at
  160. */
  161. setTarget(target) {
  162. // only rotate around the y axis!
  163. const tmpVector = TmpVectors.Vector3[1];
  164. target.subtractToRef(this.position, tmpVector);
  165. tmpVector.y = 0;
  166. tmpVector.normalize();
  167. const yRotation = Math.atan2(tmpVector.x, tmpVector.z);
  168. this.rotationQuaternion.toEulerAnglesToRef(tmpVector);
  169. Quaternion.FromEulerAnglesToRef(tmpVector.x, yRotation, tmpVector.z, this.rotationQuaternion);
  170. }
  171. dispose() {
  172. super.dispose();
  173. this._lastXRViewerPose = undefined;
  174. }
  175. _updateDepthNearFar() {
  176. const far = (this.maxZ || 10000) * this._xrSessionManager.worldScalingFactor;
  177. const xrRenderState = {
  178. // if maxZ is 0 it should be "Infinity", but it doesn't work with the WebXR API. Setting to a large number.
  179. depthFar: far,
  180. depthNear: this.minZ,
  181. };
  182. this._xrSessionManager.updateRenderState(xrRenderState);
  183. this._cache.minZ = this.minZ;
  184. this._cache.maxZ = far;
  185. }
  186. _updateFromXRSession() {
  187. const pose = this._xrSessionManager.currentFrame && this._xrSessionManager.currentFrame.getViewerPose(this._xrSessionManager.referenceSpace);
  188. this._lastXRViewerPose = pose || undefined;
  189. if (!pose) {
  190. this._setTrackingState(WebXRTrackingState.NOT_TRACKING);
  191. return;
  192. }
  193. // Set the tracking state. if it didn't change it is a no-op
  194. const trackingState = pose.emulatedPosition ? WebXRTrackingState.TRACKING_LOST : WebXRTrackingState.TRACKING;
  195. this._setTrackingState(trackingState);
  196. // check min/max Z and update if not the same as in cache
  197. if (this.minZ !== this._cache.minZ || this.maxZ !== this._cache.maxZ) {
  198. this._updateDepthNearFar();
  199. }
  200. if (pose.transform) {
  201. const orientation = pose.transform.orientation;
  202. if (pose.transform.orientation.x === undefined) {
  203. // Babylon native polyfill can return an undefined orientation value
  204. // When not initialized
  205. return;
  206. }
  207. const pos = pose.transform.position;
  208. this._referencedPosition.set(pos.x, pos.y, pos.z).scaleInPlace(this._xrSessionManager.worldScalingFactor);
  209. this._referenceQuaternion.set(orientation.x, orientation.y, orientation.z, orientation.w);
  210. if (!this._scene.useRightHandedSystem) {
  211. this._referencedPosition.z *= -1;
  212. this._referenceQuaternion.z *= -1;
  213. this._referenceQuaternion.w *= -1;
  214. }
  215. if (this._firstFrame) {
  216. this._firstFrame = false;
  217. // we have the XR reference, now use this to find the offset to get the camera to be
  218. // in the right position
  219. // set the height to correlate to the current height
  220. this.position.y += this._referencedPosition.y;
  221. // avoid using the head rotation on the first frame.
  222. this._referenceQuaternion.copyFromFloats(0, 0, 0, 1);
  223. }
  224. else {
  225. // update position and rotation as reference
  226. this.rotationQuaternion.copyFrom(this._referenceQuaternion);
  227. this.position.copyFrom(this._referencedPosition);
  228. }
  229. }
  230. // Update camera rigs
  231. if (this.rigCameras.length !== pose.views.length) {
  232. this._updateNumberOfRigCameras(pose.views.length);
  233. }
  234. pose.views.forEach((view, i) => {
  235. const currentRig = this.rigCameras[i];
  236. // update right and left, where applicable
  237. if (!currentRig.isLeftCamera && !currentRig.isRightCamera) {
  238. if (view.eye === "right") {
  239. currentRig._isRightCamera = true;
  240. }
  241. else if (view.eye === "left") {
  242. currentRig._isLeftCamera = true;
  243. }
  244. }
  245. // add any custom render targets to this camera, if available in the scene
  246. const customRenderTargets = this.getScene().customRenderTargets;
  247. // use a for loop
  248. for (let i = 0; i < customRenderTargets.length; i++) {
  249. const rt = customRenderTargets[i];
  250. // make sure we don't add the same render target twice
  251. if (currentRig.customRenderTargets.indexOf(rt) === -1) {
  252. currentRig.customRenderTargets.push(rt);
  253. }
  254. }
  255. // Update view/projection matrix
  256. const pos = view.transform.position;
  257. const orientation = view.transform.orientation;
  258. currentRig.parent = this.parent;
  259. currentRig.position.set(pos.x, pos.y, pos.z).scaleInPlace(this._xrSessionManager.worldScalingFactor);
  260. currentRig.rotationQuaternion.set(orientation.x, orientation.y, orientation.z, orientation.w);
  261. if (!this._scene.useRightHandedSystem) {
  262. currentRig.position.z *= -1;
  263. currentRig.rotationQuaternion.z *= -1;
  264. currentRig.rotationQuaternion.w *= -1;
  265. }
  266. else {
  267. currentRig.rotationQuaternion.multiplyInPlace(this._rotate180);
  268. }
  269. Matrix.FromFloat32ArrayToRefScaled(view.projectionMatrix, 0, 1, currentRig._projectionMatrix);
  270. if (!this._scene.useRightHandedSystem) {
  271. currentRig._projectionMatrix.toggleProjectionMatrixHandInPlace();
  272. }
  273. // fov
  274. const fov = Math.atan2(1, view.projectionMatrix[5]) * 2;
  275. currentRig.fov = fov;
  276. // first camera?
  277. if (i === 0) {
  278. this.fov = fov;
  279. this._projectionMatrix.copyFrom(currentRig._projectionMatrix);
  280. }
  281. const renderTargetTexture = this._xrSessionManager.getRenderTargetTextureForView(view);
  282. this._renderingMultiview = renderTargetTexture?._texture?.isMultiview || false;
  283. if (this._renderingMultiview) {
  284. // For multiview, the render target texture is the same per-view (just the slice index is different),
  285. // so we only need to set the output render target once for the rig parent.
  286. if (i == 0) {
  287. this._xrSessionManager.trySetViewportForView(this.viewport, view);
  288. this.outputRenderTarget = renderTargetTexture;
  289. }
  290. }
  291. else {
  292. // Update viewport
  293. this._xrSessionManager.trySetViewportForView(currentRig.viewport, view);
  294. // Set cameras to render to the session's render target
  295. currentRig.outputRenderTarget = renderTargetTexture || this._xrSessionManager.getRenderTargetTextureForView(view);
  296. }
  297. // Replicate parent rig camera behavior
  298. currentRig.layerMask = this.layerMask;
  299. });
  300. }
  301. _updateNumberOfRigCameras(viewCount = 1) {
  302. while (this.rigCameras.length < viewCount) {
  303. const newCamera = new TargetCamera("XR-RigCamera: " + this.rigCameras.length, Vector3.Zero(), this.getScene());
  304. newCamera.minZ = 0.1;
  305. newCamera.rotationQuaternion = new Quaternion();
  306. newCamera.updateUpVectorFromRotation = true;
  307. newCamera.isRigCamera = true;
  308. newCamera.rigParent = this;
  309. // do not compute projection matrix, provided by XR
  310. newCamera.freezeProjectionMatrix();
  311. this.rigCameras.push(newCamera);
  312. }
  313. while (this.rigCameras.length > viewCount) {
  314. const removedCamera = this.rigCameras.pop();
  315. if (removedCamera) {
  316. removedCamera.dispose();
  317. }
  318. }
  319. }
  320. _updateReferenceSpace() {
  321. // were position & rotation updated OUTSIDE of the xr update loop
  322. if (!this.position.equals(this._referencedPosition) || !this.rotationQuaternion.equals(this._referenceQuaternion)) {
  323. const referencedMat = TmpVectors.Matrix[0];
  324. const poseMat = TmpVectors.Matrix[1];
  325. const transformMat = TmpVectors.Matrix[2];
  326. Matrix.ComposeToRef(WebXRCamera._ScaleReadOnly, this._referenceQuaternion, this._referencedPosition, referencedMat);
  327. Matrix.ComposeToRef(WebXRCamera._ScaleReadOnly, this.rotationQuaternion, this.position, poseMat);
  328. referencedMat.invert().multiplyToRef(poseMat, transformMat);
  329. transformMat.invert();
  330. if (!this._scene.useRightHandedSystem) {
  331. transformMat.toggleModelMatrixHandInPlace();
  332. }
  333. transformMat.decompose(undefined, this._referenceQuaternion, this._referencedPosition);
  334. const transform = new XRRigidTransform({
  335. x: this._referencedPosition.x / this._xrSessionManager.worldScalingFactor,
  336. y: this._referencedPosition.y / this._xrSessionManager.worldScalingFactor,
  337. z: this._referencedPosition.z / this._xrSessionManager.worldScalingFactor,
  338. }, {
  339. x: this._referenceQuaternion.x,
  340. y: this._referenceQuaternion.y,
  341. z: this._referenceQuaternion.z,
  342. w: this._referenceQuaternion.w,
  343. });
  344. this._xrSessionManager.referenceSpace = this._xrSessionManager.referenceSpace.getOffsetReferenceSpace(transform);
  345. }
  346. }
  347. }
  348. WebXRCamera._ScaleReadOnly = Vector3.One();
  349. //# sourceMappingURL=webXRCamera.js.map