babylon.webVRCamera.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. declare var HMDVRDevice: any;
  2. declare var VRDisplay: any;
  3. declare var VRFrameData: any;
  4. module BABYLON {
  5. /**
  6. * This is a copy of VRPose.
  7. * IMPORTANT!! The data is right-hand data.
  8. * @export
  9. * @interface DevicePose
  10. */
  11. export interface DevicePose {
  12. readonly position?: Float32Array;
  13. readonly linearVelocity?: Float32Array;
  14. readonly linearAcceleration?: Float32Array;
  15. readonly orientation?: Float32Array;
  16. readonly angularVelocity?: Float32Array;
  17. readonly angularAcceleration?: Float32Array;
  18. }
  19. export interface PoseControlled {
  20. position: Vector3;
  21. rotationQuaternion: Quaternion;
  22. devicePosition?: Vector3;
  23. deviceRotationQuaternion: Quaternion;
  24. rawPose: Nullable<DevicePose>;
  25. deviceScaleFactor: number;
  26. updateFromDevice(poseData: DevicePose): void;
  27. }
  28. export interface WebVROptions {
  29. trackPosition?: boolean; //for the sake of your users - set it to true.
  30. positionScale?: number;
  31. displayName?: string; //if there are more than one VRDisplays.
  32. controllerMeshes?: boolean; // should the native controller meshes be initialized
  33. defaultLightingOnControllers?: boolean; // creating a default HemiLight only on controllers
  34. useCustomVRButton?: boolean; // if you don't want to use the default VR button of the helper
  35. customVRButton?: HTMLButtonElement; //if you'd like to provide your own button to the VRHelper
  36. rayLength?: number; // to change the length of the ray for gaze/controllers.
  37. defaultHeight?: number; // to change the default offset from the ground to account for user's height
  38. }
  39. export class WebVRFreeCamera extends FreeCamera implements PoseControlled {
  40. public _vrDevice: any = null;
  41. public rawPose: Nullable<DevicePose> = null;
  42. private _onVREnabled: (success: boolean) => void;
  43. private _specsVersion: string = "1.1";
  44. private _attached: boolean = false;
  45. private _frameData: any;
  46. protected _descendants: Array<Node> = [];
  47. // Represents device position and rotation in room space. Should only be used to help calculate babylon space values
  48. private _deviceRoomPosition = Vector3.Zero();
  49. private _deviceRoomRotationQuaternion = Quaternion.Identity();
  50. // Represents device position and rotation in babylon space
  51. public devicePosition = Vector3.Zero();
  52. public deviceRotationQuaternion = Quaternion.Identity();
  53. public deviceScaleFactor: number = 1;
  54. private _deviceToWorld = Matrix.Identity();
  55. private _worldToDevice = Matrix.Identity();
  56. public controllers: Array<WebVRController> = [];
  57. public onControllersAttachedObservable = new Observable<Array<WebVRController>>();
  58. public onControllerMeshLoadedObservable = new Observable<WebVRController>();
  59. public rigParenting: boolean = true; // should the rig cameras be used as parent instead of this camera.
  60. private _lightOnControllers: BABYLON.HemisphericLight;
  61. constructor(name: string, position: Vector3, scene: Scene, private webVROptions: WebVROptions = {}) {
  62. super(name, position, scene);
  63. this._cache.position = Vector3.Zero();
  64. if(webVROptions.defaultHeight){
  65. this.position.y = webVROptions.defaultHeight;
  66. }
  67. this.minZ = 0.1;
  68. //legacy support - the compensation boolean was removed.
  69. if (arguments.length === 5) {
  70. this.webVROptions = arguments[4];
  71. }
  72. // default webVR options
  73. if (this.webVROptions.trackPosition == undefined) {
  74. this.webVROptions.trackPosition = true;
  75. }
  76. if (this.webVROptions.controllerMeshes == undefined) {
  77. this.webVROptions.controllerMeshes = true;
  78. }
  79. if (this.webVROptions.defaultLightingOnControllers == undefined) {
  80. this.webVROptions.defaultLightingOnControllers = true;
  81. }
  82. this.rotationQuaternion = new Quaternion();
  83. if (this.webVROptions && this.webVROptions.positionScale) {
  84. this.deviceScaleFactor = this.webVROptions.positionScale;
  85. }
  86. //enable VR
  87. var engine = this.getEngine();
  88. this._onVREnabled = (success: boolean) => { if (success) { this.initControllers(); } };
  89. engine.onVRRequestPresentComplete.add(this._onVREnabled);
  90. engine.initWebVR().add((event: IDisplayChangedEventArgs) => {
  91. if (!event.vrDisplay || this._vrDevice === event.vrDisplay) {
  92. return;
  93. }
  94. this._vrDevice = event.vrDisplay;
  95. //reset the rig parameters.
  96. this.setCameraRigMode(Camera.RIG_MODE_WEBVR, { parentCamera: this, vrDisplay: this._vrDevice, frameData: this._frameData, specs: this._specsVersion });
  97. if (this._attached) {
  98. this.getEngine().enableVR();
  99. }
  100. });
  101. if (typeof(VRFrameData) !== "undefined")
  102. this._frameData = new VRFrameData();
  103. /**
  104. * The idea behind the following lines:
  105. * objects that have the camera as parent should actually have the rig cameras as a parent.
  106. * BUT, each of those cameras has a different view matrix, which means that if we set the parent to the first rig camera,
  107. * the second will not show it correctly.
  108. *
  109. * To solve this - each object that has the camera as parent will be added to a protected array.
  110. * When the rig camera renders, it will take this array and set all of those to be its children.
  111. * This way, the right camera will be used as a parent, and the mesh will be rendered correctly.
  112. * Amazing!
  113. */
  114. scene.onBeforeCameraRenderObservable.add((camera) => {
  115. if (camera.parent === this && this.rigParenting) {
  116. this._descendants = this.getDescendants(true, (n) => {
  117. // don't take the cameras or the controllers!
  118. let isController = this.controllers.some(controller => { return controller._mesh === n });
  119. let isRigCamera = this._rigCameras.indexOf(<Camera>n) !== -1
  120. return !isController && !isRigCamera;
  121. });
  122. this._descendants.forEach(node => {
  123. node.parent = camera;
  124. });
  125. }
  126. });
  127. scene.onAfterCameraRenderObservable.add((camera) => {
  128. if (camera.parent === this && this.rigParenting) {
  129. this._descendants.forEach(node => {
  130. node.parent = this;
  131. });
  132. }
  133. });
  134. }
  135. public dispose(): void {
  136. this.getEngine().onVRRequestPresentComplete.removeCallback(this._onVREnabled);
  137. super.dispose();
  138. }
  139. public getControllerByName(name: string): Nullable<WebVRController> {
  140. for (var gp of this.controllers) {
  141. if (gp.hand === name) {
  142. return gp;
  143. }
  144. }
  145. return null;
  146. }
  147. private _leftController: Nullable<WebVRController>;
  148. public get leftController(): Nullable<WebVRController> {
  149. if (!this._leftController) {
  150. this._leftController = this.getControllerByName("left");
  151. }
  152. return this._leftController;
  153. };
  154. private _rightController: Nullable<WebVRController>;
  155. public get rightController(): Nullable<WebVRController> {
  156. if (!this._rightController) {
  157. this._rightController = this.getControllerByName("right");
  158. }
  159. return this._rightController;
  160. };
  161. public getForwardRay(length = 100): Ray {
  162. if (this.leftCamera) {
  163. // Use left eye to avoid computation to compute center on every call
  164. return super.getForwardRay(length, this.leftCamera.getWorldMatrix(), this.leftCamera.globalPosition); // Need the actual rendered camera
  165. }
  166. else {
  167. return super.getForwardRay(length);
  168. }
  169. }
  170. public _checkInputs(): void {
  171. if (this._vrDevice && this._vrDevice.isPresenting) {
  172. this._vrDevice.getFrameData(this._frameData);
  173. this.updateFromDevice(this._frameData.pose);
  174. }
  175. super._checkInputs();
  176. }
  177. updateFromDevice(poseData: DevicePose) {
  178. if (poseData && poseData.orientation) {
  179. this.rawPose = poseData;
  180. this._deviceRoomRotationQuaternion.copyFromFloats(poseData.orientation[0], poseData.orientation[1], -poseData.orientation[2], -poseData.orientation[3]);
  181. if (this.getScene().useRightHandedSystem) {
  182. this._deviceRoomRotationQuaternion.z *= -1;
  183. this._deviceRoomRotationQuaternion.w *= -1;
  184. }
  185. if (this.webVROptions.trackPosition && this.rawPose.position) {
  186. this._deviceRoomPosition.copyFromFloats(this.rawPose.position[0], this.rawPose.position[1], -this.rawPose.position[2]);
  187. if (this.getScene().useRightHandedSystem) {
  188. this._deviceRoomPosition.z *= -1;
  189. }
  190. }
  191. }
  192. }
  193. /**
  194. * WebVR's attach control will start broadcasting frames to the device.
  195. * Note that in certain browsers (chrome for example) this function must be called
  196. * within a user-interaction callback. Example:
  197. * <pre> scene.onPointerDown = function() { camera.attachControl(canvas); }</pre>
  198. *
  199. * @param {HTMLElement} element
  200. * @param {boolean} [noPreventDefault]
  201. *
  202. * @memberOf WebVRFreeCamera
  203. */
  204. public attachControl(element: HTMLElement, noPreventDefault?: boolean): void {
  205. super.attachControl(element, noPreventDefault);
  206. this._attached = true;
  207. noPreventDefault = Camera.ForceAttachControlToAlwaysPreventDefault ? false : noPreventDefault;
  208. if (this._vrDevice) {
  209. this.getEngine().enableVR();
  210. }
  211. }
  212. public detachControl(element: HTMLElement): void {
  213. this.getScene().gamepadManager.onGamepadConnectedObservable.remove(this._onGamepadConnectedObserver);
  214. this.getScene().gamepadManager.onGamepadDisconnectedObservable.remove(this._onGamepadDisconnectedObserver);
  215. super.detachControl(element);
  216. this._attached = false;
  217. this.getEngine().disableVR();
  218. }
  219. public getClassName(): string {
  220. return "WebVRFreeCamera";
  221. }
  222. public resetToCurrentRotation() {
  223. //uses the vrDisplay's "resetPose()".
  224. //pitch and roll won't be affected.
  225. this._vrDevice.resetPose();
  226. }
  227. public _updateRigCameras() {
  228. var camLeft = <TargetCamera>this._rigCameras[0];
  229. var camRight = <TargetCamera>this._rigCameras[1];
  230. camLeft.rotationQuaternion.copyFrom(this._deviceRoomRotationQuaternion);
  231. camRight.rotationQuaternion.copyFrom(this._deviceRoomRotationQuaternion);
  232. camLeft.position.copyFrom(this._deviceRoomPosition);
  233. camRight.position.copyFrom(this._deviceRoomPosition);
  234. }
  235. private _workingVector = Vector3.Zero();
  236. private _oneVector = Vector3.One();
  237. private _workingMatrix = Matrix.Identity();
  238. public _updateCache(ignoreParentClass?: boolean): void {
  239. if(!this.rotationQuaternion.equals(this._cache.rotationQuaternion) || !this.position.equals(this._cache.position)){
  240. // Set working vector to the device position in room space rotated by the new rotation
  241. this.rotationQuaternion.toRotationMatrix(this._workingMatrix);
  242. Vector3.TransformCoordinatesToRef(this._deviceRoomPosition, this._workingMatrix, this._workingVector);
  243. // Subtract this vector from the current device position in world to get the translation for the device world matrix
  244. this.devicePosition.subtractToRef(this._workingVector, this._workingVector)
  245. Matrix.ComposeToRef(this._oneVector, this.rotationQuaternion, this._workingVector, this._deviceToWorld);
  246. // Add translation from anchor position
  247. this._deviceToWorld.getTranslationToRef(this._workingVector)
  248. this._workingVector.addInPlace(this.position);
  249. this._workingVector.subtractInPlace(this._cache.position)
  250. this._deviceToWorld.setTranslation(this._workingVector)
  251. // Set an inverted matrix to be used when updating the camera
  252. this._deviceToWorld.invertToRef(this._worldToDevice)
  253. // Update the gamepad to ensure the mesh is updated on the same frame as camera
  254. this.controllers.forEach((controller)=>{
  255. controller._deviceToWorld = this._deviceToWorld;
  256. controller.update();
  257. })
  258. this.update();
  259. }
  260. if (!ignoreParentClass) {
  261. super._updateCache();
  262. }
  263. }
  264. public update() {
  265. // Get current device position in babylon world
  266. Vector3.TransformCoordinatesToRef(this._deviceRoomPosition, this._deviceToWorld, this.devicePosition);
  267. // Get current device rotation in babylon world
  268. Matrix.FromQuaternionToRef(this._deviceRoomRotationQuaternion, this._workingMatrix);
  269. this._deviceToWorld.multiplyToRef(this._workingMatrix, this._workingMatrix);
  270. Quaternion.FromRotationMatrixToRef(this._workingMatrix, this.deviceRotationQuaternion);
  271. super.update();
  272. }
  273. public _getViewMatrix(): Matrix {
  274. return Matrix.Identity();
  275. }
  276. /**
  277. * This function is called by the two RIG cameras.
  278. * 'this' is the left or right camera (and NOT (!!!) the WebVRFreeCamera instance)
  279. */
  280. protected _getWebVRViewMatrix(): Matrix {
  281. //WebVR 1.1
  282. var viewArray = this._cameraRigParams["left"] ? this._cameraRigParams["frameData"].leftViewMatrix : this._cameraRigParams["frameData"].rightViewMatrix;
  283. Matrix.FromArrayToRef(viewArray, 0, this._webvrViewMatrix);
  284. if (!this.getScene().useRightHandedSystem) {
  285. [2, 6, 8, 9, 14].forEach((num) => {
  286. this._webvrViewMatrix.m[num] *= -1;
  287. });
  288. }
  289. // update the camera rotation matrix
  290. this._webvrViewMatrix.getRotationMatrixToRef(this._cameraRotationMatrix);
  291. Vector3.TransformCoordinatesToRef(this._referencePoint, this._cameraRotationMatrix, this._transformedReferencePoint);
  292. // Computing target and final matrix
  293. this.position.addToRef(this._transformedReferencePoint, this._currentTarget);
  294. let parentCamera: WebVRFreeCamera = this._cameraRigParams["parentCamera"];
  295. // should the view matrix be updated with scale and position offset?
  296. if (parentCamera.deviceScaleFactor !== 1) {
  297. this._webvrViewMatrix.invert();
  298. // scale the position, if set
  299. if (parentCamera.deviceScaleFactor) {
  300. this._webvrViewMatrix.m[12] *= parentCamera.deviceScaleFactor;
  301. this._webvrViewMatrix.m[13] *= parentCamera.deviceScaleFactor;
  302. this._webvrViewMatrix.m[14] *= parentCamera.deviceScaleFactor;
  303. }
  304. this._webvrViewMatrix.invert();
  305. }
  306. parentCamera._worldToDevice.multiplyToRef(this._webvrViewMatrix, this._webvrViewMatrix);
  307. return this._webvrViewMatrix;
  308. }
  309. protected _getWebVRProjectionMatrix(): Matrix {
  310. let parentCamera = <WebVRFreeCamera>this.parent;
  311. parentCamera._vrDevice.depthNear = parentCamera.minZ;
  312. parentCamera._vrDevice.depthFar = parentCamera.maxZ;
  313. var projectionArray = this._cameraRigParams["left"] ? this._cameraRigParams["frameData"].leftProjectionMatrix : this._cameraRigParams["frameData"].rightProjectionMatrix;
  314. Matrix.FromArrayToRef(projectionArray, 0, this._projectionMatrix);
  315. //babylon compatible matrix
  316. if (!this.getScene().useRightHandedSystem) {
  317. [8, 9, 10, 11].forEach((num) => {
  318. this._projectionMatrix.m[num] *= -1;
  319. });
  320. }
  321. return this._projectionMatrix;
  322. }
  323. private _onGamepadConnectedObserver: Nullable<Observer<Gamepad>>;
  324. private _onGamepadDisconnectedObserver: Nullable<Observer<Gamepad>>;
  325. public initControllers() {
  326. this.controllers = [];
  327. let manager = this.getScene().gamepadManager;
  328. this._onGamepadDisconnectedObserver = manager.onGamepadDisconnectedObservable.add((gamepad) => {
  329. if (gamepad.type === BABYLON.Gamepad.POSE_ENABLED) {
  330. let webVrController: WebVRController = <WebVRController>gamepad;
  331. if (webVrController.defaultModel) {
  332. webVrController.defaultModel.setEnabled(false);
  333. }
  334. if(webVrController.hand === "right"){
  335. this._rightController = null;
  336. }
  337. if(webVrController.hand === "left"){
  338. this._rightController = null;
  339. }
  340. const controllerIndex = this.controllers.indexOf(webVrController);
  341. if (controllerIndex !== -1) {
  342. this.controllers.splice(controllerIndex, 1);
  343. }
  344. }
  345. });
  346. this._onGamepadConnectedObserver = manager.onGamepadConnectedObservable.add((gamepad) => {
  347. if (gamepad.type === BABYLON.Gamepad.POSE_ENABLED) {
  348. let webVrController: WebVRController = <WebVRController>gamepad;
  349. webVrController._deviceToWorld = this._deviceToWorld;
  350. if (this.webVROptions.controllerMeshes) {
  351. if (webVrController.defaultModel) {
  352. webVrController.defaultModel.setEnabled(true);
  353. } else {
  354. // Load the meshes
  355. webVrController.initControllerMesh(this.getScene(), (loadedMesh) => {
  356. this.onControllerMeshLoadedObservable.notifyObservers(webVrController);
  357. if (this.webVROptions.defaultLightingOnControllers) {
  358. if (!this._lightOnControllers) {
  359. this._lightOnControllers = new BABYLON.HemisphericLight("vrControllersLight", new BABYLON.Vector3(0, 1, 0), this.getScene());
  360. }
  361. let activateLightOnSubMeshes = function (mesh: AbstractMesh, light: HemisphericLight) {
  362. let children = mesh.getChildren();
  363. if (children.length !== 0) {
  364. children.forEach((mesh) => {
  365. light.includedOnlyMeshes.push(<AbstractMesh>mesh);
  366. activateLightOnSubMeshes(<AbstractMesh>mesh, light);
  367. });
  368. }
  369. }
  370. this._lightOnControllers.includedOnlyMeshes.push(loadedMesh);
  371. activateLightOnSubMeshes(loadedMesh, this._lightOnControllers);
  372. }
  373. });
  374. }
  375. }
  376. webVrController.attachToPoseControlledCamera(this);
  377. // since this is async - sanity check. Is the controller already stored?
  378. if (this.controllers.indexOf(webVrController) === -1) {
  379. //add to the controllers array
  380. this.controllers.push(webVrController);
  381. //did we find enough controllers? Great! let the developer know.
  382. if (this.controllers.length >= 2) {
  383. // Forced to add some control code for Vive as it doesn't always fill properly the "hand" property
  384. // Sometimes, both controllers are set correctly (left and right), sometimes none, sometimes only one of them...
  385. // So we're overriding setting left & right manually to be sure
  386. let firstViveWandDetected = false;
  387. for (let i = 0; i < this.controllers.length; i++) {
  388. if (this.controllers[i].controllerType === PoseEnabledControllerType.VIVE) {
  389. if (!firstViveWandDetected) {
  390. firstViveWandDetected = true;
  391. this.controllers[i].hand = "left";
  392. }
  393. else {
  394. this.controllers[i].hand = "right";
  395. }
  396. }
  397. }
  398. this.onControllersAttachedObservable.notifyObservers(this.controllers);
  399. }
  400. }
  401. }
  402. });
  403. }
  404. }
  405. }