webVRCamera.ts 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796
  1. Node.AddNodeConstructor("WebVRFreeCamera", (name, scene) => {
  2. return () => new WebVRFreeCamera(name, Vector3.Zero(), scene);
  3. });
  4. Node.AddNodeConstructor("WebVRGamepadCamera", (name, scene) => {
  5. return () => new WebVRFreeCamera(name, Vector3.Zero(), scene);
  6. });
  7. /**
  8. * This is a copy of VRPose. See https://developer.mozilla.org/en-US/docs/Web/API/VRPose
  9. * IMPORTANT!! The data is right-hand data.
  10. * @export
  11. * @interface DevicePose
  12. */
  13. export interface DevicePose {
  14. /**
  15. * The position of the device, values in array are [x,y,z].
  16. */
  17. readonly position: Nullable<Float32Array>;
  18. /**
  19. * The linearVelocity of the device, values in array are [x,y,z].
  20. */
  21. readonly linearVelocity: Nullable<Float32Array>;
  22. /**
  23. * The linearAcceleration of the device, values in array are [x,y,z].
  24. */
  25. readonly linearAcceleration: Nullable<Float32Array>;
  26. /**
  27. * The orientation of the device in a quaternion array, values in array are [x,y,z,w].
  28. */
  29. readonly orientation: Nullable<Float32Array>;
  30. /**
  31. * The angularVelocity of the device, values in array are [x,y,z].
  32. */
  33. readonly angularVelocity: Nullable<Float32Array>;
  34. /**
  35. * The angularAcceleration of the device, values in array are [x,y,z].
  36. */
  37. readonly angularAcceleration: Nullable<Float32Array>;
  38. }
  39. /**
  40. * Interface representing a pose controlled object in Babylon.
  41. * A pose controlled object has both regular pose values as well as pose values
  42. * from an external device such as a VR head mounted display
  43. */
  44. export interface PoseControlled {
  45. /**
  46. * The position of the object in babylon space.
  47. */
  48. position: Vector3;
  49. /**
  50. * The rotation quaternion of the object in babylon space.
  51. */
  52. rotationQuaternion: Quaternion;
  53. /**
  54. * The position of the device in babylon space.
  55. */
  56. devicePosition?: Vector3;
  57. /**
  58. * The rotation quaternion of the device in babylon space.
  59. */
  60. deviceRotationQuaternion: Quaternion;
  61. /**
  62. * The raw pose coming from the device.
  63. */
  64. rawPose: Nullable<DevicePose>;
  65. /**
  66. * The scale of the device to be used when translating from device space to babylon space.
  67. */
  68. deviceScaleFactor: number;
  69. /**
  70. * Updates the poseControlled values based on the input device pose.
  71. * @param poseData the pose data to update the object with
  72. */
  73. updateFromDevice(poseData: DevicePose): void;
  74. }
  75. /**
  76. * Set of options to customize the webVRCamera
  77. */
  78. export interface WebVROptions {
  79. /**
  80. * Sets if the webVR camera should be tracked to the vrDevice. (default: true)
  81. */
  82. trackPosition?: boolean;
  83. /**
  84. * Sets the scale of the vrDevice in babylon space. (default: 1)
  85. */
  86. positionScale?: number;
  87. /**
  88. * If there are more than one VRDisplays, this will choose the display matching this name. (default: pick first vrDisplay)
  89. */
  90. displayName?: string;
  91. /**
  92. * Should the native controller meshes be initialized. (default: true)
  93. */
  94. controllerMeshes?: boolean;
  95. /**
  96. * Creating a default HemiLight only on controllers. (default: true)
  97. */
  98. defaultLightingOnControllers?: boolean;
  99. /**
  100. * If you don't want to use the default VR button of the helper. (default: false)
  101. */
  102. useCustomVRButton?: boolean;
  103. /**
  104. * If you'd like to provide your own button to the VRHelper. (default: standard babylon vr button)
  105. */
  106. customVRButton?: HTMLButtonElement;
  107. /**
  108. * To change the length of the ray for gaze/controllers. Will be scaled by positionScale. (default: 100)
  109. */
  110. rayLength?: number;
  111. /**
  112. * To change the default offset from the ground to account for user's height in meters. Will be scaled by positionScale. (default: 1.7)
  113. */
  114. defaultHeight?: number;
  115. }
  116. /**
  117. * This represents a WebVR camera.
  118. * The WebVR camera is Babylon's simple interface to interaction with Windows Mixed Reality, HTC Vive and Oculus Rift.
  119. * @example http://doc.babylonjs.com/how_to/webvr_camera
  120. */
  121. export class WebVRFreeCamera extends FreeCamera implements PoseControlled {
  122. /**
  123. * @hidden
  124. * The vrDisplay tied to the camera. See https://developer.mozilla.org/en-US/docs/Web/API/VRDisplay
  125. */
  126. public _vrDevice: any = null;
  127. /**
  128. * The rawPose of the vrDevice.
  129. */
  130. public rawPose: Nullable<DevicePose> = null;
  131. private _onVREnabled: (success: boolean) => void;
  132. private _specsVersion: string = "1.1";
  133. private _attached: boolean = false;
  134. private _frameData: any;
  135. protected _descendants: Array<Node> = [];
  136. // Represents device position and rotation in room space. Should only be used to help calculate babylon space values
  137. private _deviceRoomPosition = Vector3.Zero();
  138. /** @hidden */
  139. public _deviceRoomRotationQuaternion = Quaternion.Identity();
  140. private _standingMatrix: Nullable<Matrix> = null;
  141. /**
  142. * Represents device position in babylon space.
  143. */
  144. public devicePosition = Vector3.Zero();
  145. /**
  146. * Represents device rotation in babylon space.
  147. */
  148. public deviceRotationQuaternion = Quaternion.Identity();
  149. /**
  150. * The scale of the device to be used when translating from device space to babylon space.
  151. */
  152. public deviceScaleFactor: number = 1;
  153. private _deviceToWorld = Matrix.Identity();
  154. private _worldToDevice = Matrix.Identity();
  155. /**
  156. * References to the webVR controllers for the vrDevice.
  157. */
  158. public controllers: Array<WebVRController> = [];
  159. /**
  160. * Emits an event when a controller is attached.
  161. */
  162. public onControllersAttachedObservable = new Observable<Array<WebVRController>>();
  163. /**
  164. * Emits an event when a controller's mesh has been loaded;
  165. */
  166. public onControllerMeshLoadedObservable = new Observable<WebVRController>();
  167. /**
  168. * Emits an event when the HMD's pose has been updated.
  169. */
  170. public onPoseUpdatedFromDeviceObservable = new Observable<any>();
  171. private _poseSet = false;
  172. /**
  173. * If the rig cameras be used as parent instead of this camera.
  174. */
  175. public rigParenting: boolean = true;
  176. private _lightOnControllers: HemisphericLight;
  177. private _defaultHeight?: number = undefined;
  178. /**
  179. * Instantiates a WebVRFreeCamera.
  180. * @param name The name of the WebVRFreeCamera
  181. * @param position The starting anchor position for the camera
  182. * @param scene The scene the camera belongs to
  183. * @param webVROptions a set of customizable options for the webVRCamera
  184. */
  185. constructor(name: string, position: Vector3, scene: Scene, private webVROptions: WebVROptions = {}) {
  186. super(name, position, scene);
  187. this._cache.position = Vector3.Zero();
  188. if (webVROptions.defaultHeight) {
  189. this._defaultHeight = webVROptions.defaultHeight;
  190. this.position.y = this._defaultHeight;
  191. }
  192. this.minZ = 0.1;
  193. //legacy support - the compensation boolean was removed.
  194. if (arguments.length === 5) {
  195. this.webVROptions = arguments[4];
  196. }
  197. // default webVR options
  198. if (this.webVROptions.trackPosition == undefined) {
  199. this.webVROptions.trackPosition = true;
  200. }
  201. if (this.webVROptions.controllerMeshes == undefined) {
  202. this.webVROptions.controllerMeshes = true;
  203. }
  204. if (this.webVROptions.defaultLightingOnControllers == undefined) {
  205. this.webVROptions.defaultLightingOnControllers = true;
  206. }
  207. this.rotationQuaternion = new Quaternion();
  208. if (this.webVROptions && this.webVROptions.positionScale) {
  209. this.deviceScaleFactor = this.webVROptions.positionScale;
  210. }
  211. //enable VR
  212. var engine = this.getEngine();
  213. this._onVREnabled = (success: boolean) => { if (success) { this.initControllers(); } };
  214. engine.onVRRequestPresentComplete.add(this._onVREnabled);
  215. engine.initWebVR().add((event: IDisplayChangedEventArgs) => {
  216. if (!event.vrDisplay || this._vrDevice === event.vrDisplay) {
  217. return;
  218. }
  219. this._vrDevice = event.vrDisplay;
  220. //reset the rig parameters.
  221. this.setCameraRigMode(Camera.RIG_MODE_WEBVR, { parentCamera: this, vrDisplay: this._vrDevice, frameData: this._frameData, specs: this._specsVersion });
  222. if (this._attached) {
  223. this.getEngine().enableVR();
  224. }
  225. });
  226. if (typeof (VRFrameData) !== "undefined") {
  227. this._frameData = new VRFrameData();
  228. }
  229. /**
  230. * The idea behind the following lines:
  231. * objects that have the camera as parent should actually have the rig cameras as a parent.
  232. * BUT, each of those cameras has a different view matrix, which means that if we set the parent to the first rig camera,
  233. * the second will not show it correctly.
  234. *
  235. * To solve this - each object that has the camera as parent will be added to a protected array.
  236. * When the rig camera renders, it will take this array and set all of those to be its children.
  237. * This way, the right camera will be used as a parent, and the mesh will be rendered correctly.
  238. * Amazing!
  239. */
  240. scene.onBeforeCameraRenderObservable.add((camera) => {
  241. if (camera.parent === this && this.rigParenting) {
  242. this._descendants = this.getDescendants(true, (n) => {
  243. // don't take the cameras or the controllers!
  244. let isController = this.controllers.some((controller) => { return controller._mesh === n; });
  245. let isRigCamera = this._rigCameras.indexOf(<Camera>n) !== -1;
  246. return !isController && !isRigCamera;
  247. });
  248. this._descendants.forEach((node) => {
  249. node.parent = camera;
  250. });
  251. }
  252. });
  253. scene.onAfterCameraRenderObservable.add((camera) => {
  254. if (camera.parent === this && this.rigParenting) {
  255. this._descendants.forEach((node) => {
  256. node.parent = this;
  257. });
  258. }
  259. });
  260. }
  261. /**
  262. * Gets the device distance from the ground in meters.
  263. * @returns the distance in meters from the vrDevice to ground in device space. If standing matrix is not supported for the vrDevice 0 is returned.
  264. */
  265. public deviceDistanceToRoomGround(): number {
  266. if (this._standingMatrix) {
  267. // Add standing matrix offset to get real offset from ground in room
  268. this._standingMatrix.getTranslationToRef(this._workingVector);
  269. return this._deviceRoomPosition.y + this._workingVector.y;
  270. }
  271. //If VRDisplay does not inform stage parameters and no default height is set we fallback to zero.
  272. return this._defaultHeight || 0;
  273. }
  274. /**
  275. * Enables the standing matrix when supported. This can be used to position the user's view the correct height from the ground.
  276. * @param callback will be called when the standing matrix is set. Callback parameter is if the standing matrix is supported.
  277. */
  278. public useStandingMatrix(callback = (bool: boolean) => { }) {
  279. // Use standing matrix if available
  280. this.getEngine().initWebVRAsync().then((result) => {
  281. if (!result.vrDisplay || !result.vrDisplay.stageParameters || !result.vrDisplay.stageParameters.sittingToStandingTransform || !this.webVROptions.trackPosition) {
  282. callback(false);
  283. } else {
  284. this._standingMatrix = new Matrix();
  285. Matrix.FromFloat32ArrayToRefScaled(result.vrDisplay.stageParameters.sittingToStandingTransform, 0, 1, this._standingMatrix);
  286. if (!this.getScene().useRightHandedSystem) {
  287. if (this._standingMatrix) {
  288. this._standingMatrix.toggleModelMatrixHandInPlace();
  289. }
  290. }
  291. callback(true);
  292. }
  293. });
  294. }
  295. /**
  296. * Enables the standing matrix when supported. This can be used to position the user's view the correct height from the ground.
  297. * @returns A promise with a boolean set to if the standing matrix is supported.
  298. */
  299. public useStandingMatrixAsync(): Promise<boolean> {
  300. return new Promise((res, rej) => {
  301. this.useStandingMatrix((supported) => {
  302. res(supported);
  303. });
  304. });
  305. }
  306. /**
  307. * Disposes the camera
  308. */
  309. public dispose(): void {
  310. this._detachIfAttached();
  311. this.getEngine().onVRRequestPresentComplete.removeCallback(this._onVREnabled);
  312. if (this._updateCacheWhenTrackingDisabledObserver) {
  313. this._scene.onBeforeRenderObservable.remove(this._updateCacheWhenTrackingDisabledObserver);
  314. }
  315. super.dispose();
  316. }
  317. /**
  318. * Gets a vrController by name.
  319. * @param name The name of the controller to retreive
  320. * @returns the controller matching the name specified or null if not found
  321. */
  322. public getControllerByName(name: string): Nullable<WebVRController> {
  323. for (var gp of this.controllers) {
  324. if (gp.hand === name) {
  325. return gp;
  326. }
  327. }
  328. return null;
  329. }
  330. private _leftController: Nullable<WebVRController>;
  331. /**
  332. * The controller corrisponding to the users left hand.
  333. */
  334. public get leftController(): Nullable<WebVRController> {
  335. if (!this._leftController) {
  336. this._leftController = this.getControllerByName("left");
  337. }
  338. return this._leftController;
  339. }
  340. private _rightController: Nullable<WebVRController>;
  341. /**
  342. * The controller corrisponding to the users right hand.
  343. */
  344. public get rightController(): Nullable<WebVRController> {
  345. if (!this._rightController) {
  346. this._rightController = this.getControllerByName("right");
  347. }
  348. return this._rightController;
  349. }
  350. /**
  351. * Casts a ray forward from the vrCamera's gaze.
  352. * @param length Length of the ray (default: 100)
  353. * @returns the ray corrisponding to the gaze
  354. */
  355. public getForwardRay(length = 100): Ray {
  356. if (this.leftCamera) {
  357. // Use left eye to avoid computation to compute center on every call
  358. return super.getForwardRay(length, this.leftCamera.getWorldMatrix(), this.leftCamera.globalPosition); // Need the actual rendered camera
  359. }
  360. else {
  361. return super.getForwardRay(length);
  362. }
  363. }
  364. /**
  365. * @hidden
  366. * Updates the camera based on device's frame data
  367. */
  368. public _checkInputs(): void {
  369. if (this._vrDevice && this._vrDevice.isPresenting) {
  370. this._vrDevice.getFrameData(this._frameData);
  371. this.updateFromDevice(this._frameData.pose);
  372. }
  373. super._checkInputs();
  374. }
  375. /**
  376. * Updates the poseControlled values based on the input device pose.
  377. * @param poseData Pose coming from the device
  378. */
  379. updateFromDevice(poseData: DevicePose) {
  380. if (poseData && poseData.orientation) {
  381. this.rawPose = poseData;
  382. this._deviceRoomRotationQuaternion.copyFromFloats(poseData.orientation[0], poseData.orientation[1], -poseData.orientation[2], -poseData.orientation[3]);
  383. if (this.getScene().useRightHandedSystem) {
  384. this._deviceRoomRotationQuaternion.z *= -1;
  385. this._deviceRoomRotationQuaternion.w *= -1;
  386. }
  387. if (this.webVROptions.trackPosition && this.rawPose.position) {
  388. this._deviceRoomPosition.copyFromFloats(this.rawPose.position[0], this.rawPose.position[1], -this.rawPose.position[2]);
  389. if (this.getScene().useRightHandedSystem) {
  390. this._deviceRoomPosition.z *= -1;
  391. }
  392. }
  393. this._poseSet = true;
  394. }
  395. }
  396. private _htmlElementAttached: Nullable<HTMLElement> = null;
  397. private _detachIfAttached = () => {
  398. var vrDisplay = this.getEngine().getVRDevice();
  399. if (vrDisplay && !vrDisplay.isPresenting && this._htmlElementAttached) {
  400. this.detachControl(this._htmlElementAttached);
  401. }
  402. }
  403. /**
  404. * WebVR's attach control will start broadcasting frames to the device.
  405. * Note that in certain browsers (chrome for example) this function must be called
  406. * within a user-interaction callback. Example:
  407. * <pre> scene.onPointerDown = function() { camera.attachControl(canvas); }</pre>
  408. *
  409. * @param element html element to attach the vrDevice to
  410. * @param noPreventDefault prevent the default html element operation when attaching the vrDevice
  411. */
  412. public attachControl(element: HTMLElement, noPreventDefault?: boolean): void {
  413. super.attachControl(element, noPreventDefault);
  414. this._attached = true;
  415. this._htmlElementAttached = element;
  416. noPreventDefault = Camera.ForceAttachControlToAlwaysPreventDefault ? false : noPreventDefault;
  417. if (this._vrDevice) {
  418. this.getEngine().enableVR();
  419. }
  420. window.addEventListener('vrdisplaypresentchange', this._detachIfAttached);
  421. }
  422. /**
  423. * Detaches the camera from the html element and disables VR
  424. *
  425. * @param element html element to detach from
  426. */
  427. public detachControl(element: HTMLElement): void {
  428. this.getScene().gamepadManager.onGamepadConnectedObservable.remove(this._onGamepadConnectedObserver);
  429. this.getScene().gamepadManager.onGamepadDisconnectedObservable.remove(this._onGamepadDisconnectedObserver);
  430. super.detachControl(element);
  431. this._attached = false;
  432. this.getEngine().disableVR();
  433. window.removeEventListener('vrdisplaypresentchange', this._detachIfAttached);
  434. }
  435. /**
  436. * @returns the name of this class
  437. */
  438. public getClassName(): string {
  439. return "WebVRFreeCamera";
  440. }
  441. /**
  442. * Calls resetPose on the vrDisplay
  443. * See: https://developer.mozilla.org/en-US/docs/Web/API/VRDisplay/resetPose
  444. */
  445. public resetToCurrentRotation() {
  446. //uses the vrDisplay's "resetPose()".
  447. //pitch and roll won't be affected.
  448. this._vrDevice.resetPose();
  449. }
  450. /**
  451. * @hidden
  452. * Updates the rig cameras (left and right eye)
  453. */
  454. public _updateRigCameras() {
  455. var camLeft = <TargetCamera>this._rigCameras[0];
  456. var camRight = <TargetCamera>this._rigCameras[1];
  457. camLeft.rotationQuaternion.copyFrom(this._deviceRoomRotationQuaternion);
  458. camRight.rotationQuaternion.copyFrom(this._deviceRoomRotationQuaternion);
  459. camLeft.position.copyFrom(this._deviceRoomPosition);
  460. camRight.position.copyFrom(this._deviceRoomPosition);
  461. }
  462. private _workingVector = Vector3.Zero();
  463. private _oneVector = Vector3.One();
  464. private _workingMatrix = Matrix.Identity();
  465. private updateCacheCalled: boolean;
  466. // Remove translation from 6dof headset if trackposition is set to false
  467. private _correctPositionIfNotTrackPosition(matrix: Matrix, isViewMatrix = false) {
  468. if (this.rawPose && this.rawPose.position && !this.webVROptions.trackPosition) {
  469. Matrix.TranslationToRef(this.rawPose.position[0], this.rawPose.position[1], -this.rawPose.position[2], this._tmpMatrix);
  470. if (!isViewMatrix) {
  471. this._tmpMatrix.invert();
  472. }
  473. this._tmpMatrix.multiplyToRef(matrix, matrix);
  474. }
  475. }
  476. /**
  477. * @hidden
  478. * Updates the cached values of the camera
  479. * @param ignoreParentClass ignores updating the parent class's cache (default: false)
  480. */
  481. public _updateCache(ignoreParentClass?: boolean): void {
  482. if (!this.rotationQuaternion.equals(this._cache.rotationQuaternion) || !this.position.equals(this._cache.position)) {
  483. // Update to ensure devicePosition is up to date with most recent _deviceRoomPosition
  484. if (!this.updateCacheCalled) {
  485. // make sure it is only called once per loop. this.update() might cause an infinite loop.
  486. this.updateCacheCalled = true;
  487. this.update();
  488. }
  489. // Set working vector to the device position in room space rotated by the new rotation
  490. this.rotationQuaternion.toRotationMatrix(this._workingMatrix);
  491. Vector3.TransformCoordinatesToRef(this._deviceRoomPosition, this._workingMatrix, this._workingVector);
  492. // Subtract this vector from the current device position in world to get the translation for the device world matrix
  493. this.devicePosition.subtractToRef(this._workingVector, this._workingVector);
  494. Matrix.ComposeToRef(this._oneVector, this.rotationQuaternion, this._workingVector, this._deviceToWorld);
  495. // Add translation from anchor position
  496. this._deviceToWorld.getTranslationToRef(this._workingVector);
  497. this._workingVector.addInPlace(this.position);
  498. this._workingVector.subtractInPlace(this._cache.position);
  499. this._deviceToWorld.setTranslation(this._workingVector);
  500. // Set an inverted matrix to be used when updating the camera
  501. this._deviceToWorld.invertToRef(this._worldToDevice);
  502. // Update the gamepad to ensure the mesh is updated on the same frame as camera
  503. this.controllers.forEach((controller) => {
  504. controller._deviceToWorld.copyFrom(this._deviceToWorld);
  505. this._correctPositionIfNotTrackPosition(controller._deviceToWorld);
  506. controller.update();
  507. });
  508. }
  509. if (!ignoreParentClass) {
  510. super._updateCache();
  511. }
  512. this.updateCacheCalled = false;
  513. }
  514. /**
  515. * @hidden
  516. * Get current device position in babylon world
  517. */
  518. public _computeDevicePosition() {
  519. Vector3.TransformCoordinatesToRef(this._deviceRoomPosition, this._deviceToWorld, this.devicePosition);
  520. }
  521. /**
  522. * Updates the current device position and rotation in the babylon world
  523. */
  524. public update() {
  525. this._computeDevicePosition();
  526. // Get current device rotation in babylon world
  527. Matrix.FromQuaternionToRef(this._deviceRoomRotationQuaternion, this._workingMatrix);
  528. this._workingMatrix.multiplyToRef(this._deviceToWorld, this._workingMatrix);
  529. Quaternion.FromRotationMatrixToRef(this._workingMatrix, this.deviceRotationQuaternion);
  530. if (this._poseSet) {
  531. this.onPoseUpdatedFromDeviceObservable.notifyObservers(null);
  532. }
  533. super.update();
  534. }
  535. /**
  536. * @hidden
  537. * Gets the view matrix of this camera (Always set to identity as left and right eye cameras contain the actual view matrix)
  538. * @returns an identity matrix
  539. */
  540. public _getViewMatrix(): Matrix {
  541. return Matrix.Identity();
  542. }
  543. private _tmpMatrix = new BABYLON.Matrix();
  544. /**
  545. * This function is called by the two RIG cameras.
  546. * 'this' is the left or right camera (and NOT (!!!) the WebVRFreeCamera instance)
  547. */
  548. protected _getWebVRViewMatrix(): Matrix {
  549. // Update the parent camera prior to using a child camera to avoid desynchronization
  550. let parentCamera: WebVRFreeCamera = this._cameraRigParams["parentCamera"];
  551. parentCamera._updateCache();
  552. //WebVR 1.1
  553. var viewArray = this._cameraRigParams["left"] ? this._cameraRigParams["frameData"].leftViewMatrix : this._cameraRigParams["frameData"].rightViewMatrix;
  554. Matrix.FromArrayToRef(viewArray, 0, this._webvrViewMatrix);
  555. if (!this.getScene().useRightHandedSystem) {
  556. this._webvrViewMatrix.toggleModelMatrixHandInPlace();
  557. }
  558. // update the camera rotation matrix
  559. this._webvrViewMatrix.getRotationMatrixToRef(this._cameraRotationMatrix);
  560. Vector3.TransformCoordinatesToRef(this._referencePoint, this._cameraRotationMatrix, this._transformedReferencePoint);
  561. // Computing target and final matrix
  562. this.position.addToRef(this._transformedReferencePoint, this._currentTarget);
  563. // should the view matrix be updated with scale and position offset?
  564. if (parentCamera.deviceScaleFactor !== 1) {
  565. this._webvrViewMatrix.invert();
  566. // scale the position, if set
  567. if (parentCamera.deviceScaleFactor) {
  568. this._webvrViewMatrix.multiplyAtIndex(12, parentCamera.deviceScaleFactor);
  569. this._webvrViewMatrix.multiplyAtIndex(13, parentCamera.deviceScaleFactor);
  570. this._webvrViewMatrix.multiplyAtIndex(14, parentCamera.deviceScaleFactor);
  571. }
  572. this._webvrViewMatrix.invert();
  573. }
  574. // Remove translation from 6dof headset if trackposition is set to false
  575. parentCamera._correctPositionIfNotTrackPosition(this._webvrViewMatrix, true);
  576. parentCamera._worldToDevice.multiplyToRef(this._webvrViewMatrix, this._webvrViewMatrix);
  577. // Compute global position
  578. this._workingMatrix = this._workingMatrix || Matrix.Identity();
  579. this._webvrViewMatrix.invertToRef(this._workingMatrix);
  580. this._workingMatrix.multiplyToRef(parentCamera.getWorldMatrix(), this._workingMatrix);
  581. this._workingMatrix.getTranslationToRef(this._globalPosition);
  582. this._markSyncedWithParent();
  583. return this._webvrViewMatrix;
  584. }
  585. protected _getWebVRProjectionMatrix(): Matrix {
  586. let parentCamera = <WebVRFreeCamera>this.parent;
  587. parentCamera._vrDevice.depthNear = parentCamera.minZ;
  588. parentCamera._vrDevice.depthFar = parentCamera.maxZ;
  589. var projectionArray = this._cameraRigParams["left"] ? this._cameraRigParams["frameData"].leftProjectionMatrix : this._cameraRigParams["frameData"].rightProjectionMatrix;
  590. Matrix.FromArrayToRef(projectionArray, 0, this._projectionMatrix);
  591. //babylon compatible matrix
  592. if (!this.getScene().useRightHandedSystem) {
  593. this._projectionMatrix.toggleProjectionMatrixHandInPlace();
  594. }
  595. return this._projectionMatrix;
  596. }
  597. private _onGamepadConnectedObserver: Nullable<Observer<Gamepad>>;
  598. private _onGamepadDisconnectedObserver: Nullable<Observer<Gamepad>>;
  599. private _updateCacheWhenTrackingDisabledObserver: Nullable<Observer<Scene>>;
  600. /**
  601. * Initializes the controllers and their meshes
  602. */
  603. public initControllers() {
  604. this.controllers = [];
  605. let manager = this.getScene().gamepadManager;
  606. this._onGamepadDisconnectedObserver = manager.onGamepadDisconnectedObservable.add((gamepad) => {
  607. if (gamepad.type === Gamepad.POSE_ENABLED) {
  608. let webVrController: WebVRController = <WebVRController>gamepad;
  609. if (webVrController.defaultModel) {
  610. webVrController.defaultModel.setEnabled(false);
  611. }
  612. if (webVrController.hand === "right") {
  613. this._rightController = null;
  614. }
  615. if (webVrController.hand === "left") {
  616. this._leftController = null;
  617. }
  618. const controllerIndex = this.controllers.indexOf(webVrController);
  619. if (controllerIndex !== -1) {
  620. this.controllers.splice(controllerIndex, 1);
  621. }
  622. }
  623. });
  624. this._onGamepadConnectedObserver = manager.onGamepadConnectedObservable.add((gamepad) => {
  625. if (gamepad.type === Gamepad.POSE_ENABLED) {
  626. let webVrController: WebVRController = <WebVRController>gamepad;
  627. if (!this.webVROptions.trackPosition) {
  628. webVrController._disableTrackPosition(new Vector3(webVrController.hand == "left" ? -0.15 : 0.15, -0.5, 0.25));
  629. // Cache must be updated before rendering controllers to avoid them being one frame behind
  630. if (!this._updateCacheWhenTrackingDisabledObserver) {
  631. this._updateCacheWhenTrackingDisabledObserver = this._scene.onBeforeRenderObservable.add(() => {
  632. this._updateCache();
  633. });
  634. }
  635. }
  636. webVrController.deviceScaleFactor = this.deviceScaleFactor;
  637. webVrController._deviceToWorld.copyFrom(this._deviceToWorld);
  638. this._correctPositionIfNotTrackPosition(webVrController._deviceToWorld);
  639. if (this.webVROptions.controllerMeshes) {
  640. if (webVrController.defaultModel) {
  641. webVrController.defaultModel.setEnabled(true);
  642. } else {
  643. // Load the meshes
  644. webVrController.initControllerMesh(this.getScene(), (loadedMesh) => {
  645. loadedMesh.scaling.scaleInPlace(this.deviceScaleFactor);
  646. this.onControllerMeshLoadedObservable.notifyObservers(webVrController);
  647. if (this.webVROptions.defaultLightingOnControllers) {
  648. if (!this._lightOnControllers) {
  649. this._lightOnControllers = new HemisphericLight("vrControllersLight", new Vector3(0, 1, 0), this.getScene());
  650. }
  651. let activateLightOnSubMeshes = function(mesh: AbstractMesh, light: HemisphericLight) {
  652. let children = mesh.getChildren();
  653. if (children && children.length !== 0) {
  654. children.forEach((mesh) => {
  655. light.includedOnlyMeshes.push(<AbstractMesh>mesh);
  656. activateLightOnSubMeshes(<AbstractMesh>mesh, light);
  657. });
  658. }
  659. };
  660. this._lightOnControllers.includedOnlyMeshes.push(loadedMesh);
  661. activateLightOnSubMeshes(loadedMesh, this._lightOnControllers);
  662. }
  663. });
  664. }
  665. }
  666. webVrController.attachToPoseControlledCamera(this);
  667. // since this is async - sanity check. Is the controller already stored?
  668. if (this.controllers.indexOf(webVrController) === -1) {
  669. //add to the controllers array
  670. this.controllers.push(webVrController);
  671. // Forced to add some control code for Vive as it doesn't always fill properly the "hand" property
  672. // Sometimes, both controllers are set correctly (left and right), sometimes none, sometimes only one of them...
  673. // So we're overriding setting left & right manually to be sure
  674. let firstViveWandDetected = false;
  675. for (let i = 0; i < this.controllers.length; i++) {
  676. if (this.controllers[i].controllerType === PoseEnabledControllerType.VIVE) {
  677. if (!firstViveWandDetected) {
  678. firstViveWandDetected = true;
  679. this.controllers[i].hand = "left";
  680. }
  681. else {
  682. this.controllers[i].hand = "right";
  683. }
  684. }
  685. }
  686. //did we find enough controllers? Great! let the developer know.
  687. if (this.controllers.length >= 2) {
  688. this.onControllersAttachedObservable.notifyObservers(this.controllers);
  689. }
  690. }
  691. }
  692. });
  693. }
  694. }