babylon.vrExperienceHelper.ts 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213
  1. module BABYLON {
  2. export interface VRTeleportationOptions {
  3. floorMeshName?: string; // If you'd like to provide a mesh acting as the floor
  4. floorMeshes?: Mesh[];
  5. }
  6. export class VRExperienceHelper {
  7. private _scene: BABYLON.Scene;
  8. private _position: Vector3;
  9. private _btnVR: HTMLButtonElement;
  10. // Can the system support WebVR, even if a headset isn't plugged in?
  11. private _webVRsupported = false;
  12. // If WebVR is supported, is a headset plugged in and are we ready to present?
  13. private _webVRready = false;
  14. // Are we waiting for the requestPresent callback to complete?
  15. private _webVRrequesting = false;
  16. // Are we presenting to the headset right now?
  17. private _webVRpresenting = false;
  18. // Are we presenting in the fullscreen fallback?
  19. private _fullscreenVRpresenting = false;
  20. private _canvas: Nullable<HTMLCanvasElement>;
  21. private _webVRCamera: WebVRFreeCamera;
  22. private _vrDeviceOrientationCamera: VRDeviceOrientationFreeCamera;
  23. private _deviceOrientationCamera: DeviceOrientationCamera;
  24. private _onKeyDown: (event: KeyboardEvent) => void;
  25. private _onVrDisplayPresentChange: any;
  26. private _onVRDisplayChanged: (eventArgs:IDisplayChangedEventArgs) => void;
  27. private _onVRRequestPresentStart: () => void;
  28. private _onVRRequestPresentComplete: (success: boolean) => void;
  29. public onEnteringVR = new Observable();
  30. public onExitingVR = new Observable();
  31. public onControllerMeshLoaded = new Observable<WebVRController>();
  32. private _rayLength: number;
  33. private _useCustomVRButton: boolean = false;
  34. private _teleportationRequested: boolean = false;
  35. private _teleportationEnabledOnLeftController: boolean = false;
  36. private _teleportationEnabledOnRightController: boolean = false;
  37. private _interactionsEnabledOnLeftController: boolean = false;
  38. private _interactionsEnabledOnRightController: boolean = false;
  39. private _leftControllerReady: boolean = false;
  40. private _rightControllerReady: boolean = false;
  41. private _floorMeshName: string;
  42. private _floorMeshesCollection: Mesh[] = [];
  43. private _teleportationAllowed: boolean = false;
  44. private _rotationAllowed: boolean = true;
  45. private _teleportationRequestInitiated = false;
  46. private _rotationRightAsked = false;
  47. private _rotationLeftAsked = false;
  48. private _teleportationCircle: Mesh;
  49. private _postProcessMove: ImageProcessingPostProcess;
  50. private _passProcessMove: PassPostProcess;
  51. private _teleportationFillColor: string = "#444444";
  52. private _teleportationBorderColor: string = "#FFFFFF";
  53. private _rotationAngle: number = 0;
  54. private _haloCenter = new Vector3(0, 0, 0);
  55. private _gazeTracker: BABYLON.Mesh;
  56. private _padSensibilityUp = 0.65;
  57. private _padSensibilityDown = 0.35;
  58. private _leftLaserPointer: Nullable<Mesh>;
  59. private _rightLaserPointer: Nullable<Mesh>;
  60. private _currentMeshSelected: Nullable<AbstractMesh>;
  61. public onNewMeshSelected = new Observable<AbstractMesh>();
  62. private _circleEase:CircleEase;
  63. private _raySelectionPredicate: (mesh: AbstractMesh) => boolean;
  64. /**
  65. * To be optionaly changed by user to define custom ray selection
  66. */
  67. public raySelectionPredicate: (mesh: AbstractMesh) => boolean;
  68. /**
  69. * To be optionaly changed by user to define custom selection logic (after ray selection)
  70. */
  71. public meshSelectionPredicate: (mesh: AbstractMesh) => boolean;
  72. private _currentHit: Nullable<PickingInfo>;
  73. private _pointerDownOnMeshAsked = false;
  74. private _isActionableMesh = false;
  75. private _defaultHeight:number;
  76. private _teleportationEnabled = false;
  77. private _interactionsEnabled = false;
  78. private _interactionsRequested = false;
  79. private _displayGaze = true;
  80. private _displayLaserPointer = true;
  81. public get displayGaze(): boolean {
  82. return this._displayGaze;
  83. }
  84. public set displayGaze(value: boolean) {
  85. this._displayGaze = value;
  86. if (!value) {
  87. this._gazeTracker.isVisible = false;
  88. }
  89. }
  90. public get displayLaserPointer(): boolean {
  91. return this._displayLaserPointer;
  92. }
  93. public set displayLaserPointer(value: boolean) {
  94. this._displayLaserPointer = value;
  95. if (!value) {
  96. if (this._rightLaserPointer) {
  97. this._rightLaserPointer.isVisible = false;
  98. }
  99. if (this._leftLaserPointer) {
  100. this._leftLaserPointer.isVisible = false;
  101. }
  102. }
  103. }
  104. public get deviceOrientationCamera(): DeviceOrientationCamera {
  105. return this._deviceOrientationCamera;
  106. }
  107. // Based on the current WebVR support, returns the current VR camera used
  108. public get currentVRCamera(): FreeCamera {
  109. if (this._webVRready) {
  110. return this._webVRCamera;
  111. }
  112. else {
  113. return this._vrDeviceOrientationCamera;
  114. }
  115. }
  116. public get webVRCamera(): WebVRFreeCamera {
  117. return this._webVRCamera;
  118. }
  119. public get vrDeviceOrientationCamera(): VRDeviceOrientationFreeCamera {
  120. return this._vrDeviceOrientationCamera;
  121. }
  122. constructor(scene: Scene, public webVROptions: WebVROptions = {}) {
  123. this._scene = scene;
  124. this._defaultHeight = webVROptions.defaultHeight || 1.7;
  125. if (!this._scene.activeCamera || isNaN(this._scene.activeCamera.position.x)) {
  126. this._position = new BABYLON.Vector3(0, this._defaultHeight, 0);
  127. this._deviceOrientationCamera = new BABYLON.DeviceOrientationCamera("deviceOrientationVRHelper", this._position.clone(), scene);
  128. }
  129. else {
  130. this._position = this._scene.activeCamera.position.clone();
  131. this._deviceOrientationCamera = new BABYLON.DeviceOrientationCamera("deviceOrientationVRHelper", this._position.clone(), scene);
  132. this._deviceOrientationCamera.minZ = this._scene.activeCamera.minZ;
  133. this._deviceOrientationCamera.maxZ = this._scene.activeCamera.maxZ;
  134. // Set rotation from previous camera
  135. if(this._scene.activeCamera instanceof TargetCamera && this._scene.activeCamera.rotation){
  136. var targetCamera = this._scene.activeCamera;
  137. if(targetCamera.rotationQuaternion){
  138. this._deviceOrientationCamera.rotationQuaternion.copyFrom(targetCamera.rotationQuaternion);
  139. }else{
  140. this._deviceOrientationCamera.rotationQuaternion.copyFrom(Quaternion.RotationYawPitchRoll(targetCamera.rotation.y, targetCamera.rotation.x, targetCamera.rotation.z));
  141. }
  142. this._deviceOrientationCamera.rotation = targetCamera.rotation.clone();
  143. }
  144. }
  145. this._scene.activeCamera = this._deviceOrientationCamera;
  146. this._canvas = scene.getEngine().getRenderingCanvas();
  147. if (this._canvas) {
  148. this._scene.activeCamera.attachControl(this._canvas);
  149. }
  150. if (webVROptions) {
  151. if (webVROptions.useCustomVRButton) {
  152. this._useCustomVRButton = true;
  153. if (webVROptions.customVRButton) {
  154. this._btnVR = webVROptions.customVRButton;
  155. }
  156. }
  157. if (webVROptions.rayLength) {
  158. this._rayLength = webVROptions.rayLength
  159. }
  160. }
  161. if (!this._useCustomVRButton) {
  162. this._btnVR = <HTMLButtonElement>document.createElement("BUTTON");
  163. this._btnVR.className = "babylonVRicon";
  164. this._btnVR.id = "babylonVRiconbtn";
  165. this._btnVR.title = "Click to switch to VR";
  166. var css = ".babylonVRicon { position: absolute; right: 20px; height: 50px; width: 80px; background-color: rgba(51,51,51,0.7); background-image: url(data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%222048%22%20height%3D%221152%22%20viewBox%3D%220%200%202048%201152%22%20version%3D%221.1%22%3E%3Cpath%20transform%3D%22rotate%28180%201024%2C576.0000000000001%29%22%20d%3D%22m1109%2C896q17%2C0%2030%2C-12t13%2C-30t-12.5%2C-30.5t-30.5%2C-12.5l-170%2C0q-18%2C0%20-30.5%2C12.5t-12.5%2C30.5t13%2C30t30%2C12l170%2C0zm-85%2C256q59%2C0%20132.5%2C-1.5t154.5%2C-5.5t164.5%2C-11.5t163%2C-20t150%2C-30t124.5%2C-41.5q23%2C-11%2042%2C-24t38%2C-30q27%2C-25%2041%2C-61.5t14%2C-72.5l0%2C-257q0%2C-123%20-47%2C-232t-128%2C-190t-190%2C-128t-232%2C-47l-81%2C0q-37%2C0%20-68.5%2C14t-60.5%2C34.5t-55.5%2C45t-53%2C45t-53%2C34.5t-55.5%2C14t-55.5%2C-14t-53%2C-34.5t-53%2C-45t-55.5%2C-45t-60.5%2C-34.5t-68.5%2C-14l-81%2C0q-123%2C0%20-232%2C47t-190%2C128t-128%2C190t-47%2C232l0%2C257q0%2C68%2038%2C115t97%2C73q54%2C24%20124.5%2C41.5t150%2C30t163%2C20t164.5%2C11.5t154.5%2C5.5t132.5%2C1.5zm939%2C-298q0%2C39%20-24.5%2C67t-58.5%2C42q-54%2C23%20-122%2C39.5t-143.5%2C28t-155.5%2C19t-157%2C11t-148.5%2C5t-129.5%2C1.5q-59%2C0%20-130%2C-1.5t-148%2C-5t-157%2C-11t-155.5%2C-19t-143.5%2C-28t-122%2C-39.5q-34%2C-14%20-58.5%2C-42t-24.5%2C-67l0%2C-257q0%2C-106%2040.5%2C-199t110%2C-162.5t162.5%2C-109.5t199%2C-40l81%2C0q27%2C0%2052%2C14t50%2C34.5t51%2C44.5t55.5%2C44.5t63.5%2C34.5t74%2C14t74%2C-14t63.5%2C-34.5t55.5%2C-44.5t51%2C-44.5t50%2C-34.5t52%2C-14l14%2C0q37%2C0%2070%2C0.5t64.5%2C4.5t63.5%2C12t68%2C23q71%2C30%20128.5%2C78.5t98.5%2C110t63.5%2C133.5t22.5%2C149l0%2C257z%22%20fill%3D%22white%22%20/%3E%3C/svg%3E%0A); background-size: 80%; background-repeat:no-repeat; background-position: center; border: none; outline: none; transition: transform 0.125s ease-out } .babylonVRicon:hover { transform: scale(1.05) } .babylonVRicon:active {background-color: rgba(51,51,51,1) } .babylonVRicon:focus {background-color: rgba(51,51,51,1) }";
  167. css += ".babylonVRicon.vrdisplaypresenting { display: none; }";
  168. // TODO: Add user feedback so that they know what state the VRDisplay is in (disconnected, connected, entering-VR)
  169. // css += ".babylonVRicon.vrdisplaysupported { }";
  170. // css += ".babylonVRicon.vrdisplayready { }";
  171. // css += ".babylonVRicon.vrdisplayrequesting { }";
  172. var style = document.createElement('style');
  173. style.appendChild(document.createTextNode(css));
  174. document.getElementsByTagName('head')[0].appendChild(style);
  175. }
  176. if (this._canvas) {
  177. if (!this._useCustomVRButton) {
  178. this._btnVR.style.top = this._canvas.offsetTop + this._canvas.offsetHeight - 70 + "px";
  179. this._btnVR.style.left = this._canvas.offsetLeft + this._canvas.offsetWidth - 100 + "px";
  180. }
  181. if (this._btnVR) {
  182. this._btnVR.addEventListener("click", () => {
  183. this.enterVR();
  184. });
  185. }
  186. window.addEventListener("resize", () => {
  187. if (this._canvas && !this._useCustomVRButton) {
  188. this._btnVR.style.top = this._canvas.offsetTop + this._canvas.offsetHeight - 70 + "px";
  189. this._btnVR.style.left = this._canvas.offsetLeft + this._canvas.offsetWidth - 100 + "px";
  190. }
  191. if (this._fullscreenVRpresenting && this._webVRready) {
  192. this.exitVR();
  193. }
  194. });
  195. }
  196. document.addEventListener("fullscreenchange", () => { this._onFullscreenChange() }, false);
  197. document.addEventListener("mozfullscreenchange", () => { this._onFullscreenChange() }, false);
  198. document.addEventListener("webkitfullscreenchange", () => { this._onFullscreenChange() }, false);
  199. document.addEventListener("msfullscreenchange", () => { this._onFullscreenChange() }, false);
  200. if (!this._useCustomVRButton) {
  201. document.body.appendChild(this._btnVR);
  202. }
  203. // Exiting VR mode using 'ESC' key on desktop
  204. this._onKeyDown = (event: KeyboardEvent) => {
  205. if (event.keyCode === 27 && this.isInVRMode()) {
  206. this.exitVR();
  207. }
  208. };
  209. document.addEventListener("keydown", this._onKeyDown);
  210. // Exiting VR mode double tapping the touch screen
  211. this._scene.onPrePointerObservable.add( (pointerInfo, eventState) => {
  212. if (this.isInVRMode()) {
  213. this.exitVR();
  214. if (this._fullscreenVRpresenting) {
  215. this._scene.getEngine().switchFullscreen(true);
  216. }
  217. }
  218. }, BABYLON.PointerEventTypes.POINTERDOUBLETAP, false);
  219. // Listen for WebVR display changes
  220. this._onVRDisplayChanged = (eventArgs:IDisplayChangedEventArgs) => this.onVRDisplayChanged(eventArgs);
  221. this._onVrDisplayPresentChange = () => this.onVrDisplayPresentChange();
  222. this._onVRRequestPresentStart = () => {
  223. this._webVRrequesting = true;
  224. this.updateButtonVisibility();
  225. }
  226. this._onVRRequestPresentComplete = (success: boolean) => {
  227. this._webVRrequesting = false;
  228. this.updateButtonVisibility();
  229. };
  230. scene.getEngine().onVRDisplayChangedObservable.add(this._onVRDisplayChanged);
  231. scene.getEngine().onVRRequestPresentStart.add(this._onVRRequestPresentStart);
  232. scene.getEngine().onVRRequestPresentComplete.add(this._onVRRequestPresentComplete);
  233. window.addEventListener('vrdisplaypresentchange', this._onVrDisplayPresentChange);
  234. // Create the cameras
  235. this._vrDeviceOrientationCamera = new BABYLON.VRDeviceOrientationFreeCamera("VRDeviceOrientationVRHelper", this._position, this._scene);
  236. this._webVRCamera = new BABYLON.WebVRFreeCamera("WebVRHelper", this._position, this._scene, webVROptions);
  237. this._webVRCamera.onControllerMeshLoadedObservable.add((webVRController) => this._onDefaultMeshLoaded(webVRController));
  238. this._scene.gamepadManager.onGamepadConnectedObservable.add((pad) => this._onNewGamepadConnected(pad));
  239. this.updateButtonVisibility();
  240. //create easing functions
  241. this._circleEase = new BABYLON.CircleEase();
  242. this._circleEase.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);
  243. }
  244. // Raised when one of the controller has loaded successfully its associated default mesh
  245. private _onDefaultMeshLoaded(webVRController: WebVRController) {
  246. if (webVRController.hand === "left") {
  247. this._leftControllerReady = true;
  248. if (this._interactionsRequested && !this._interactionsEnabledOnLeftController) {
  249. this._enableInteractionOnController(webVRController);
  250. }
  251. if (this._teleportationRequested && !this._teleportationEnabledOnLeftController) {
  252. this._enableTeleportationOnController(webVRController);
  253. }
  254. }
  255. if (webVRController.hand === "right") {
  256. this._rightControllerReady = true;
  257. if (this._interactionsRequested && !this._interactionsEnabledOnRightController) {
  258. this._enableInteractionOnController(webVRController);
  259. }
  260. if (this._teleportationRequested && !this._teleportationEnabledOnRightController) {
  261. this._enableTeleportationOnController(webVRController);
  262. }
  263. }
  264. try {
  265. this.onControllerMeshLoaded.notifyObservers(webVRController);
  266. }
  267. catch (err) {
  268. Tools.Warn("Error in your custom logic onControllerMeshLoaded: " + err);
  269. }
  270. }
  271. private _onFullscreenChange() {
  272. if (document.fullscreen !== undefined) {
  273. this._fullscreenVRpresenting = document.fullscreen;
  274. } else if (document.mozFullScreen !== undefined) {
  275. this._fullscreenVRpresenting = document.mozFullScreen;
  276. } else if (document.webkitIsFullScreen !== undefined) {
  277. this._fullscreenVRpresenting = document.webkitIsFullScreen;
  278. } else if (document.msIsFullScreen !== undefined) {
  279. this._fullscreenVRpresenting = document.msIsFullScreen;
  280. }
  281. if (!this._fullscreenVRpresenting && this._canvas) {
  282. this.exitVR();
  283. if (!this._useCustomVRButton) {
  284. this._btnVR.style.top = this._canvas.offsetTop + this._canvas.offsetHeight - 70 + "px";
  285. this._btnVR.style.left = this._canvas.offsetLeft + this._canvas.offsetWidth - 100 + "px";
  286. }
  287. }
  288. }
  289. private isInVRMode() {
  290. return this._webVRpresenting || this._fullscreenVRpresenting;
  291. }
  292. private onVrDisplayPresentChange() {
  293. var vrDisplay = this._scene.getEngine().getVRDevice();
  294. if (vrDisplay) {
  295. var wasPresenting = this._webVRpresenting;
  296. // A VR display is connected
  297. this._webVRpresenting = vrDisplay.isPresenting;
  298. if (wasPresenting && !this._webVRpresenting)
  299. this.exitVR();
  300. } else {
  301. Tools.Warn('Detected VRDisplayPresentChange on an unknown VRDisplay. Did you can enterVR on the vrExperienceHelper?');
  302. }
  303. this.updateButtonVisibility();
  304. }
  305. private onVRDisplayChanged(eventArgs:IDisplayChangedEventArgs) {
  306. this._webVRsupported = eventArgs.vrSupported;
  307. this._webVRready = !!eventArgs.vrDisplay;
  308. this._webVRpresenting = eventArgs.vrDisplay && eventArgs.vrDisplay.isPresenting;
  309. this.updateButtonVisibility();
  310. }
  311. private updateButtonVisibility() {
  312. if (!this._btnVR) {
  313. return;
  314. }
  315. this._btnVR.className = "babylonVRicon";
  316. if (this.isInVRMode()) {
  317. this._btnVR.className += " vrdisplaypresenting";
  318. } else {
  319. if (this._webVRready) this._btnVR.className += " vrdisplayready";
  320. if (this._webVRsupported) this._btnVR.className += " vrdisplaysupported";
  321. if (this._webVRrequesting) this._btnVR.className += " vrdisplayrequesting";
  322. }
  323. }
  324. /**
  325. * Attempt to enter VR. If a headset is connected and ready, will request present on that.
  326. * Otherwise, will use the fullscreen API.
  327. */
  328. public enterVR() {
  329. if (this._scene.activeCamera) {
  330. this._position = this._scene.activeCamera.position.clone();
  331. }
  332. if (this.onEnteringVR) {
  333. try {
  334. this.onEnteringVR.notifyObservers({});
  335. }
  336. catch (err) {
  337. Tools.Warn("Error in your custom logic onEnteringVR: " + err);
  338. }
  339. }
  340. if (this._webVRrequesting)
  341. return;
  342. // If WebVR is supported and a headset is connected
  343. if (this._webVRready) {
  344. if (!this._webVRpresenting) {
  345. this._webVRCamera.position = this._position;
  346. this._scene.activeCamera = this._webVRCamera;
  347. }
  348. }
  349. else {
  350. this._vrDeviceOrientationCamera.position = this._position;
  351. this._scene.activeCamera = this._vrDeviceOrientationCamera;
  352. this._scene.getEngine().switchFullscreen(true);
  353. this.updateButtonVisibility();
  354. }
  355. if (this._scene.activeCamera && this._canvas) {
  356. this._scene.activeCamera.attachControl(this._canvas);
  357. }
  358. }
  359. /**
  360. * Attempt to exit VR, or fullscreen.
  361. */
  362. public exitVR() {
  363. if (this.onExitingVR) {
  364. try {
  365. this.onExitingVR.notifyObservers({});
  366. }
  367. catch (err) {
  368. Tools.Warn("Error in your custom logic onExitingVR: " + err);
  369. }
  370. }
  371. if (this._webVRpresenting) {
  372. this._scene.getEngine().disableVR();
  373. }
  374. if (this._scene.activeCamera) {
  375. this._position = this._scene.activeCamera.position.clone();
  376. }
  377. this._deviceOrientationCamera.position = this._position;
  378. this._scene.activeCamera = this._deviceOrientationCamera;
  379. if (this._canvas) {
  380. this._scene.activeCamera.attachControl(this._canvas);
  381. }
  382. this.updateButtonVisibility();
  383. }
  384. public get position(): Vector3 {
  385. return this._position;
  386. }
  387. public set position(value: Vector3) {
  388. this._position = value;
  389. if (this._scene.activeCamera) {
  390. this._scene.activeCamera.position = value;
  391. }
  392. }
  393. public enableInteractions() {
  394. if (!this._interactionsEnabled) {
  395. this._interactionsRequested = true;
  396. if (this._leftControllerReady && this._webVRCamera.leftController) {
  397. this._enableInteractionOnController(this._webVRCamera.leftController)
  398. }
  399. if (this._rightControllerReady && this._webVRCamera.rightController) {
  400. this._enableInteractionOnController(this._webVRCamera.rightController)
  401. }
  402. this._createGazeTracker();
  403. this.raySelectionPredicate = (mesh) => {
  404. return true;
  405. }
  406. this.meshSelectionPredicate = (mesh) => {
  407. return true;
  408. }
  409. this._raySelectionPredicate = (mesh) => {
  410. if (this._isTeleportationFloor(mesh) || (mesh.isVisible && mesh.name.indexOf("gazeTracker") === -1
  411. && mesh.name.indexOf("teleportationCircle") === -1
  412. && mesh.name.indexOf("torusTeleportation") === -1
  413. && mesh.name.indexOf("laserPointer") === -1)) {
  414. return this.raySelectionPredicate(mesh);
  415. }
  416. return false;
  417. }
  418. this._scene.registerBeforeRender(() => {
  419. this._castRayAndSelectObject();
  420. });
  421. this._interactionsEnabled = true;
  422. }
  423. }
  424. private _isTeleportationFloor(mesh: AbstractMesh): boolean {
  425. for (var i=0; i<this._floorMeshesCollection.length; i++) {
  426. if (this._floorMeshesCollection[i].id === mesh.id) {
  427. return true;
  428. }
  429. }
  430. if (this._floorMeshName && mesh.name.indexOf(this._floorMeshName) !== -1) {
  431. return true;
  432. }
  433. return false;
  434. }
  435. public enableTeleportation(vrTeleportationOptions: VRTeleportationOptions = {}) {
  436. if (!this._teleportationEnabled) {
  437. this._teleportationRequested = true;
  438. this.enableInteractions();
  439. if (vrTeleportationOptions) {
  440. if (vrTeleportationOptions.floorMeshName) {
  441. this._floorMeshName = vrTeleportationOptions.floorMeshName;
  442. }
  443. if (vrTeleportationOptions.floorMeshes) {
  444. this._floorMeshesCollection = vrTeleportationOptions.floorMeshes;
  445. }
  446. }
  447. if (this._leftControllerReady && this._webVRCamera.leftController) {
  448. this._enableTeleportationOnController(this._webVRCamera.leftController)
  449. }
  450. if (this._rightControllerReady && this._webVRCamera.rightController) {
  451. this._enableTeleportationOnController(this._webVRCamera.rightController)
  452. }
  453. // Creates an image processing post process for the vignette not relying
  454. // on the main scene configuration for image processing to reduce setup and spaces
  455. // (gamma/linear) conflicts.
  456. const imageProcessingConfiguration = new ImageProcessingConfiguration();
  457. imageProcessingConfiguration.vignetteColor = new BABYLON.Color4(0, 0, 0, 0);
  458. imageProcessingConfiguration.vignetteEnabled = true;
  459. this._postProcessMove = new BABYLON.ImageProcessingPostProcess("postProcessMove",
  460. 1.0,
  461. this._webVRCamera,
  462. undefined,
  463. undefined,
  464. undefined,
  465. undefined,
  466. imageProcessingConfiguration);
  467. this._webVRCamera.detachPostProcess(this._postProcessMove)
  468. this._passProcessMove = new BABYLON.PassPostProcess("pass", 1.0, this._webVRCamera);
  469. this._teleportationEnabled = true;
  470. this._createTeleportationCircles();
  471. }
  472. }
  473. private _onNewGamepadConnected(gamepad: Gamepad) {
  474. if (gamepad.type !== BABYLON.Gamepad.POSE_ENABLED) {
  475. if (gamepad.leftStick) {
  476. gamepad.onleftstickchanged((stickValues) => {
  477. if (this._teleportationEnabled) {
  478. // Listening to classic/xbox gamepad only if no VR controller is active
  479. if ((!this._leftLaserPointer && !this._rightLaserPointer) ||
  480. ((this._leftLaserPointer && !this._leftLaserPointer.isVisible) &&
  481. (this._rightLaserPointer && !this._rightLaserPointer.isVisible))) {
  482. if (!this._teleportationRequestInitiated) {
  483. if (stickValues.y < -this._padSensibilityUp) {
  484. this._teleportationRequestInitiated = true;
  485. }
  486. }
  487. else {
  488. if (stickValues.y > -this._padSensibilityDown) {
  489. if (this._teleportationAllowed) {
  490. this._teleportCamera();
  491. }
  492. this._teleportationRequestInitiated = false;
  493. }
  494. }
  495. }
  496. }
  497. });
  498. }
  499. if (gamepad.rightStick) {
  500. gamepad.onrightstickchanged((stickValues) => {
  501. if (this._teleportationEnabled) {
  502. if (!this._rotationLeftAsked) {
  503. if (stickValues.x < -this._padSensibilityUp) {
  504. this._rotationLeftAsked = true;
  505. if (this._rotationAllowed) {
  506. this._rotateCamera(false);
  507. }
  508. }
  509. }
  510. else {
  511. if (stickValues.x > -this._padSensibilityDown) {
  512. this._rotationLeftAsked = false;
  513. }
  514. }
  515. if (!this._rotationRightAsked) {
  516. if (stickValues.x > this._padSensibilityUp) {
  517. this._rotationRightAsked = true;
  518. if (this._rotationAllowed) {
  519. this._rotateCamera(true);
  520. }
  521. }
  522. }
  523. else {
  524. if (stickValues.x < this._padSensibilityDown) {
  525. this._rotationRightAsked = false;
  526. }
  527. }
  528. }
  529. });
  530. }
  531. if (gamepad.type === BABYLON.Gamepad.XBOX) {
  532. (<Xbox360Pad>gamepad).onbuttondown((buttonPressed: Xbox360Button) => {
  533. if (this._interactionsEnabled && buttonPressed === Xbox360Button.A) {
  534. this._pointerDownOnMeshAsked = true;
  535. if (this._currentMeshSelected && this._currentHit) {
  536. this._scene.simulatePointerDown(this._currentHit);
  537. }
  538. }
  539. });
  540. (<Xbox360Pad>gamepad).onbuttonup((buttonPressed: Xbox360Button) => {
  541. if (this._interactionsEnabled && buttonPressed === Xbox360Button.A) {
  542. if (this._currentMeshSelected && this._currentHit) {
  543. this._scene.simulatePointerUp(this._currentHit);
  544. }
  545. this._pointerDownOnMeshAsked = false;
  546. }
  547. });
  548. }
  549. }
  550. }
  551. private _enableInteractionOnController(webVRController: WebVRController) {
  552. var controllerMesh = webVRController.mesh;
  553. if (controllerMesh) {
  554. var childMeshes = controllerMesh.getChildMeshes();
  555. for (var i = 0; i < childMeshes.length; i++) {
  556. if (childMeshes[i].name === "POINTING_POSE") {
  557. controllerMesh = childMeshes[i];
  558. break;
  559. }
  560. }
  561. var laserPointer = BABYLON.Mesh.CreateCylinder("laserPointer", 1, 0.004, 0.0002, 20, 1, this._scene, false);
  562. var laserPointerMaterial = new BABYLON.StandardMaterial("laserPointerMat", this._scene);
  563. laserPointerMaterial.emissiveColor = new BABYLON.Color3(0.7, 0.7, 0.7);
  564. laserPointerMaterial.alpha = 0.6;
  565. laserPointer.material = laserPointerMaterial;
  566. laserPointer.rotation.x = Math.PI / 2;
  567. laserPointer.parent = controllerMesh;
  568. laserPointer.position.z = -0.5;
  569. laserPointer.position.y = 0;
  570. laserPointer.isVisible = false;
  571. if (webVRController.hand === "left") {
  572. this._leftLaserPointer = laserPointer;
  573. this._interactionsEnabledOnLeftController = true;
  574. }
  575. else {
  576. this._rightLaserPointer = laserPointer;
  577. this._interactionsEnabledOnRightController = true;
  578. }
  579. webVRController.onMainButtonStateChangedObservable.add((stateObject) => {
  580. // Enabling / disabling laserPointer
  581. if (this._displayLaserPointer && stateObject.value === 1) {
  582. laserPointer.isVisible = !laserPointer.isVisible;
  583. // Laser pointer can only be active on left or right, not both at the same time
  584. if (webVRController.hand === "left" && this._rightLaserPointer) {
  585. this._rightLaserPointer.isVisible = false;
  586. }
  587. else if (this._leftLaserPointer) {
  588. this._leftLaserPointer.isVisible = false;
  589. }
  590. }
  591. });
  592. webVRController.onTriggerStateChangedObservable.add((stateObject) => {
  593. if (!this._pointerDownOnMeshAsked) {
  594. if (stateObject.value > this._padSensibilityUp) {
  595. this._pointerDownOnMeshAsked = true;
  596. if (this._currentMeshSelected && this._currentHit) {
  597. this._scene.simulatePointerDown(this._currentHit);
  598. }
  599. }
  600. }
  601. else if (stateObject.value < this._padSensibilityDown) {
  602. if (this._currentMeshSelected && this._currentHit) {
  603. this._scene.simulatePointerUp(this._currentHit);
  604. }
  605. this._pointerDownOnMeshAsked = false;
  606. }
  607. });
  608. }
  609. }
  610. private _enableTeleportationOnController(webVRController: WebVRController) {
  611. var controllerMesh = webVRController.mesh;
  612. if (controllerMesh) {
  613. if (webVRController.hand === "left") {
  614. if (!this._interactionsEnabledOnLeftController) {
  615. this._enableInteractionOnController(webVRController);
  616. }
  617. this._teleportationEnabledOnLeftController = true;
  618. }
  619. else {
  620. if (!this._interactionsEnabledOnRightController) {
  621. this._enableInteractionOnController(webVRController);
  622. }
  623. this._teleportationEnabledOnRightController = true;
  624. }
  625. webVRController.onPadValuesChangedObservable.add((stateObject) => {
  626. if (!this._teleportationRequestInitiated) {
  627. if (stateObject.y < -this._padSensibilityUp) {
  628. // If laser pointer wasn't enabled yet
  629. if (this._displayLaserPointer && webVRController.hand === "left" && this._leftLaserPointer) {
  630. this._leftLaserPointer.isVisible = true;
  631. if (this._rightLaserPointer) {
  632. this._rightLaserPointer.isVisible = false;
  633. }
  634. }
  635. else if (this._displayLaserPointer && this._rightLaserPointer) {
  636. this._rightLaserPointer.isVisible = true;
  637. if (this._leftLaserPointer) {
  638. this._leftLaserPointer.isVisible = false;
  639. }
  640. }
  641. this._teleportationRequestInitiated = true;
  642. }
  643. }
  644. else {
  645. // Listening to the proper controller values changes to confirm teleportation
  646. if ((webVRController.hand === "left" && this._leftLaserPointer && this._leftLaserPointer.isVisible)
  647. || (webVRController.hand === "right" && this._rightLaserPointer && this._rightLaserPointer.isVisible)) {
  648. if (stateObject.y > -this._padSensibilityDown) {
  649. if (this._teleportationAllowed) {
  650. this._teleportationAllowed = false;
  651. this._teleportCamera();
  652. }
  653. this._teleportationRequestInitiated = false;
  654. }
  655. }
  656. }
  657. if (!this._rotationLeftAsked) {
  658. if (stateObject.x < -this._padSensibilityUp) {
  659. this._rotationLeftAsked = true;
  660. if (this._rotationAllowed) {
  661. this._rotateCamera(false);
  662. }
  663. }
  664. }
  665. else {
  666. if (stateObject.x > -this._padSensibilityDown) {
  667. this._rotationLeftAsked = false;
  668. }
  669. }
  670. if (!this._rotationRightAsked) {
  671. if (stateObject.x > this._padSensibilityUp) {
  672. this._rotationRightAsked = true;
  673. if (this._rotationAllowed) {
  674. this._rotateCamera(true);
  675. }
  676. }
  677. }
  678. else {
  679. if (stateObject.x < this._padSensibilityDown) {
  680. this._rotationRightAsked = false;
  681. }
  682. }
  683. });
  684. }
  685. }
  686. // Gaze support used to point to teleport or to interact with an object
  687. private _createGazeTracker() {
  688. this._gazeTracker = BABYLON.Mesh.CreateTorus("gazeTracker", 0.0035, 0.0025, 20, this._scene, false);
  689. this._gazeTracker.bakeCurrentTransformIntoVertices();
  690. this._gazeTracker.isPickable = false;
  691. this._gazeTracker.isVisible = false;
  692. var targetMat = new BABYLON.StandardMaterial("targetMat", this._scene);
  693. targetMat.specularColor = BABYLON.Color3.Black();
  694. targetMat.emissiveColor = new BABYLON.Color3(0.7, 0.7, 0.7)
  695. targetMat.backFaceCulling = false;
  696. this._gazeTracker.material = targetMat;
  697. }
  698. private _createTeleportationCircles() {
  699. this._teleportationCircle = BABYLON.Mesh.CreateGround("teleportationCircle", 2, 2, 2, this._scene);
  700. this._teleportationCircle.isPickable = false;
  701. var length = 512;
  702. var dynamicTexture = new BABYLON.DynamicTexture("DynamicTexture", length, this._scene, true);
  703. dynamicTexture.hasAlpha = true;
  704. var context = dynamicTexture.getContext();
  705. var centerX = length / 2;
  706. var centerY = length / 2;
  707. var radius = 200;
  708. context.beginPath();
  709. context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
  710. context.fillStyle = this._teleportationFillColor;
  711. context.fill();
  712. context.lineWidth = 10;
  713. context.strokeStyle = this._teleportationBorderColor;
  714. context.stroke();
  715. context.closePath();
  716. dynamicTexture.update();
  717. var teleportationCircleMaterial = new BABYLON.StandardMaterial("TextPlaneMaterial", this._scene);
  718. teleportationCircleMaterial.diffuseTexture = dynamicTexture;
  719. this._teleportationCircle.material = teleportationCircleMaterial;
  720. var torus = BABYLON.Mesh.CreateTorus("torusTeleportation", 0.75, 0.1, 25, this._scene, false);
  721. torus.isPickable = false;
  722. torus.parent = this._teleportationCircle;
  723. var animationInnerCircle = new BABYLON.Animation("animationInnerCircle", "position.y", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
  724. var keys = [];
  725. keys.push({
  726. frame: 0,
  727. value: 0
  728. });
  729. keys.push({
  730. frame: 30,
  731. value: 0.4
  732. });
  733. keys.push({
  734. frame: 60,
  735. value: 0
  736. });
  737. animationInnerCircle.setKeys(keys);
  738. var easingFunction = new BABYLON.SineEase();
  739. easingFunction.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);
  740. animationInnerCircle.setEasingFunction(easingFunction);
  741. torus.animations = [];
  742. torus.animations.push(animationInnerCircle);
  743. this._scene.beginAnimation(torus, 0, 60, true);
  744. this._hideTeleportationCircle();
  745. }
  746. private _displayTeleportationCircle() {
  747. if (this._teleportationEnabled) {
  748. this._teleportationCircle.isVisible = true;
  749. (<Mesh>this._teleportationCircle.getChildren()[0]).isVisible = true;
  750. }
  751. }
  752. private _hideTeleportationCircle() {
  753. if (this._teleportationEnabled) {
  754. this._teleportationCircle.isVisible = false;
  755. (<Mesh>this._teleportationCircle.getChildren()[0]).isVisible = false;
  756. }
  757. }
  758. private _rotateCamera(right: boolean) {
  759. if (right) {
  760. this._rotationAngle++;
  761. }
  762. else {
  763. this._rotationAngle--;
  764. }
  765. this.currentVRCamera.animations = [];
  766. var target = BABYLON.Quaternion.FromRotationMatrix(BABYLON.Matrix.RotationY(Math.PI / 4 * this._rotationAngle));
  767. var animationRotation = new BABYLON.Animation("animationRotation", "rotationQuaternion", 90, BABYLON.Animation.ANIMATIONTYPE_QUATERNION,
  768. BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT);
  769. var animationRotationKeys = [];
  770. animationRotationKeys.push({
  771. frame: 0,
  772. value: this.currentVRCamera.rotationQuaternion
  773. });
  774. animationRotationKeys.push({
  775. frame: 6,
  776. value: target
  777. });
  778. animationRotation.setKeys(animationRotationKeys);
  779. animationRotation.setEasingFunction(this._circleEase);
  780. this.currentVRCamera.animations.push(animationRotation);
  781. this._postProcessMove.animations = [];
  782. var animationPP = new BABYLON.Animation("animationPP", "vignetteWeight", 90, BABYLON.Animation.ANIMATIONTYPE_FLOAT,
  783. BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT);
  784. var vignetteWeightKeys = [];
  785. vignetteWeightKeys.push({
  786. frame: 0,
  787. value: 0
  788. });
  789. vignetteWeightKeys.push({
  790. frame: 3,
  791. value: 4
  792. });
  793. vignetteWeightKeys.push({
  794. frame: 6,
  795. value: 0
  796. });
  797. animationPP.setKeys(vignetteWeightKeys);
  798. animationPP.setEasingFunction(this._circleEase);
  799. this._postProcessMove.animations.push(animationPP);
  800. var animationPP2 = new BABYLON.Animation("animationPP2", "vignetteStretch", 90, BABYLON.Animation.ANIMATIONTYPE_FLOAT,
  801. BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT);
  802. var vignetteStretchKeys = [];
  803. vignetteStretchKeys.push({
  804. frame: 0,
  805. value: 0
  806. });
  807. vignetteStretchKeys.push({
  808. frame: 3,
  809. value: 10
  810. });
  811. vignetteStretchKeys.push({
  812. frame: 6,
  813. value: 0
  814. });
  815. animationPP2.setKeys(vignetteStretchKeys);
  816. animationPP2.setEasingFunction(this._circleEase);
  817. this._postProcessMove.animations.push(animationPP2);
  818. this._postProcessMove.imageProcessingConfiguration.vignetteWeight = 0;
  819. this._postProcessMove.imageProcessingConfiguration.vignetteStretch = 0;
  820. this._webVRCamera.attachPostProcess(this._postProcessMove)
  821. this._scene.beginAnimation(this._postProcessMove, 0, 6, false, 1, () => {
  822. this._webVRCamera.detachPostProcess(this._postProcessMove)
  823. });
  824. this._scene.beginAnimation(this.currentVRCamera, 0, 6, false, 1);
  825. }
  826. private _moveTeleportationSelectorTo(hit: PickingInfo) {
  827. if (hit.pickedPoint) {
  828. this._teleportationAllowed = true;
  829. if (this._teleportationRequestInitiated) {
  830. this._displayTeleportationCircle();
  831. }
  832. else {
  833. this._hideTeleportationCircle();
  834. }
  835. this._haloCenter.copyFrom(hit.pickedPoint);
  836. this._teleportationCircle.position.copyFrom(hit.pickedPoint);
  837. var pickNormal = hit.getNormal();
  838. if (pickNormal) {
  839. var axis1 = BABYLON.Vector3.Cross(BABYLON.Axis.Y, pickNormal);
  840. var axis2 = BABYLON.Vector3.Cross(pickNormal, axis1);
  841. BABYLON.Vector3.RotationFromAxisToRef(axis2, pickNormal, axis1, this._teleportationCircle.rotation);
  842. }
  843. this._teleportationCircle.position.y += 0.1;
  844. }
  845. }
  846. private _workingVector = Vector3.Zero();
  847. private _teleportCamera() {
  848. // Teleport the hmd to where the user is looking by moving the anchor to where they are looking minus the
  849. // offset of the headset from the anchor. Then add the helper's position to account for user's height offset
  850. this.webVRCamera.leftCamera!.globalPosition.subtractToRef(this.webVRCamera.position, this._workingVector);
  851. this._haloCenter.subtractToRef(this._workingVector, this._workingVector);
  852. this._workingVector.y += this._defaultHeight;
  853. // Create animation from the camera's position to the new location
  854. this.currentVRCamera.animations = [];
  855. var animationCameraTeleportation = new BABYLON.Animation("animationCameraTeleportation", "position", 90, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT);
  856. var animationCameraTeleportationKeys = [{
  857. frame: 0,
  858. value: this.currentVRCamera.position
  859. },
  860. {
  861. frame: 11,
  862. value: this._workingVector
  863. }
  864. ];
  865. animationCameraTeleportation.setKeys(animationCameraTeleportationKeys);
  866. animationCameraTeleportation.setEasingFunction(this._circleEase);
  867. this.currentVRCamera.animations.push(animationCameraTeleportation);
  868. this._postProcessMove.animations = [];
  869. var animationPP = new BABYLON.Animation("animationPP", "vignetteWeight", 90, BABYLON.Animation.ANIMATIONTYPE_FLOAT,
  870. BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT);
  871. var vignetteWeightKeys = [];
  872. vignetteWeightKeys.push({
  873. frame: 0,
  874. value: 0
  875. });
  876. vignetteWeightKeys.push({
  877. frame: 5,
  878. value: 8
  879. });
  880. vignetteWeightKeys.push({
  881. frame: 11,
  882. value: 0
  883. });
  884. animationPP.setKeys(vignetteWeightKeys);
  885. this._postProcessMove.animations.push(animationPP);
  886. var animationPP2 = new BABYLON.Animation("animationPP2", "vignetteStretch", 90, BABYLON.Animation.ANIMATIONTYPE_FLOAT,
  887. BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT);
  888. var vignetteStretchKeys = [];
  889. vignetteStretchKeys.push({
  890. frame: 0,
  891. value: 0
  892. });
  893. vignetteStretchKeys.push({
  894. frame: 5,
  895. value: 10
  896. });
  897. vignetteStretchKeys.push({
  898. frame: 11,
  899. value: 0
  900. });
  901. animationPP2.setKeys(vignetteStretchKeys);
  902. this._postProcessMove.animations.push(animationPP2);
  903. this._postProcessMove.imageProcessingConfiguration.vignetteWeight = 8;
  904. this._postProcessMove.imageProcessingConfiguration.vignetteStretch = 10;
  905. this._webVRCamera.attachPostProcess(this._postProcessMove)
  906. this._scene.beginAnimation(this._postProcessMove, 0, 11, false, 1, () => {
  907. this._webVRCamera.detachPostProcess(this._postProcessMove)
  908. });
  909. this._scene.beginAnimation(this.currentVRCamera, 0, 11, false, 1);
  910. }
  911. private _castRayAndSelectObject () {
  912. var ray;
  913. if ((!(<WebVRFreeCamera>this.currentVRCamera).rightController && !(<WebVRFreeCamera>this.currentVRCamera).leftController) ||
  914. (this._leftLaserPointer && !this._leftLaserPointer.isVisible && !this._rightLaserPointer) ||
  915. (this._rightLaserPointer && !this._rightLaserPointer.isVisible && !this._leftLaserPointer) ||
  916. (this._rightLaserPointer && this._leftLaserPointer && !this._rightLaserPointer.isVisible && !this._leftLaserPointer.isVisible)) {
  917. ray = this.currentVRCamera.getForwardRay(this._rayLength);
  918. } else {
  919. if (this._leftLaserPointer && this._leftLaserPointer.isVisible) {
  920. ray = (<any>this.currentVRCamera).leftController.getForwardRay(this._rayLength);
  921. }
  922. else {
  923. ray = (<any>this.currentVRCamera).rightController.getForwardRay(this._rayLength);
  924. }
  925. }
  926. var hit = this._scene.pickWithRay(ray, this._raySelectionPredicate);
  927. // Moving the gazeTracker on the mesh face targetted
  928. if (hit && hit.pickedPoint) {
  929. if (this._displayGaze) {
  930. let multiplier = 1;
  931. this._gazeTracker.isVisible = true;
  932. if (this._isActionableMesh) {
  933. multiplier = 3;
  934. }
  935. this._gazeTracker.scaling.x = hit.distance * multiplier;
  936. this._gazeTracker.scaling.y = hit.distance * multiplier;
  937. this._gazeTracker.scaling.z = hit.distance * multiplier;
  938. var pickNormal = hit.getNormal();
  939. // To avoid z-fighting
  940. let deltaFighting = 0.002;
  941. if (pickNormal) {
  942. var axis1 = BABYLON.Vector3.Cross(BABYLON.Axis.Y, pickNormal);
  943. var axis2 = BABYLON.Vector3.Cross(pickNormal, axis1);
  944. BABYLON.Vector3.RotationFromAxisToRef(axis2, pickNormal, axis1, this._gazeTracker.rotation);
  945. }
  946. this._gazeTracker.position.copyFrom(hit.pickedPoint);
  947. if (this._gazeTracker.position.x < 0) {
  948. this._gazeTracker.position.x += deltaFighting;
  949. }
  950. else {
  951. this._gazeTracker.position.x -= deltaFighting;
  952. }
  953. if (this._gazeTracker.position.y < 0) {
  954. this._gazeTracker.position.y += deltaFighting;
  955. }
  956. else {
  957. this._gazeTracker.position.y -= deltaFighting;
  958. }
  959. if (this._gazeTracker.position.z < 0) {
  960. this._gazeTracker.position.z += deltaFighting;
  961. }
  962. else {
  963. this._gazeTracker.position.z -= deltaFighting;
  964. }
  965. }
  966. // Changing the size of the laser pointer based on the distance from the targetted point
  967. if (this._rightLaserPointer && this._rightLaserPointer.isVisible) {
  968. this._rightLaserPointer.scaling.y = hit.distance;
  969. this._rightLaserPointer.position.z = -hit.distance / 2;
  970. }
  971. if (this._leftLaserPointer && this._leftLaserPointer.isVisible) {
  972. this._leftLaserPointer.scaling.y = hit.distance;
  973. this._leftLaserPointer.position.z = -hit.distance / 2;
  974. }
  975. }
  976. else {
  977. this._gazeTracker.isVisible = false;
  978. }
  979. if (hit && hit.pickedMesh) {
  980. this._currentHit = hit;
  981. if (this._pointerDownOnMeshAsked) {
  982. this._scene.simulatePointerMove(this._currentHit);
  983. }
  984. // The object selected is the floor, we're in a teleportation scenario
  985. if (this._teleportationEnabled && this._isTeleportationFloor(hit.pickedMesh) && hit.pickedPoint) {
  986. // Moving the teleportation area to this targetted point
  987. this._moveTeleportationSelectorTo(hit);
  988. return;
  989. }
  990. // If not, we're in a selection scenario
  991. this._hideTeleportationCircle();
  992. this._teleportationAllowed = false;
  993. if (hit.pickedMesh !== this._currentMeshSelected) {
  994. if (this.meshSelectionPredicate(hit.pickedMesh)) {
  995. this._currentMeshSelected = hit.pickedMesh;
  996. if (hit.pickedMesh.isPickable && hit.pickedMesh.actionManager) {
  997. this.changeGazeColor(new BABYLON.Color3(0,0,1));
  998. this.changeLaserColor(new BABYLON.Color3(0.2,0.2,1));
  999. this._isActionableMesh = true;
  1000. }
  1001. else {
  1002. this.changeGazeColor(new BABYLON.Color3(0.7,0.7,0.7));
  1003. this.changeLaserColor(new BABYLON.Color3(0.7,0.7,0.7));
  1004. this._isActionableMesh = false;
  1005. }
  1006. try {
  1007. this.onNewMeshSelected.notifyObservers(this._currentMeshSelected);
  1008. }
  1009. catch (err) {
  1010. Tools.Warn("Error in your custom logic onNewMeshSelected: " + err);
  1011. }
  1012. }
  1013. else {
  1014. this._currentMeshSelected = null;
  1015. this.changeGazeColor(new BABYLON.Color3(0.7,0.7,0.7));
  1016. this.changeLaserColor(new BABYLON.Color3(0.7,0.7,0.7));
  1017. }
  1018. }
  1019. }
  1020. else {
  1021. this._currentHit = null;
  1022. this._currentMeshSelected = null;
  1023. this._teleportationAllowed = false;
  1024. this._hideTeleportationCircle();
  1025. this.changeGazeColor(new BABYLON.Color3(0.7,0.7,0.7));
  1026. this.changeLaserColor(new BABYLON.Color3(0.7,0.7,0.7));
  1027. }
  1028. }
  1029. public changeLaserColor(color: Color3) {
  1030. if (this._leftLaserPointer && this._leftLaserPointer.material) {
  1031. (<StandardMaterial>this._leftLaserPointer.material).emissiveColor = color;
  1032. }
  1033. if (this._rightLaserPointer && this._rightLaserPointer.material) {
  1034. (<StandardMaterial>this._rightLaserPointer.material).emissiveColor = color;
  1035. }
  1036. }
  1037. public changeGazeColor(color: Color3) {
  1038. if (this._gazeTracker.material) {
  1039. (<StandardMaterial>this._gazeTracker.material).emissiveColor = color;
  1040. }
  1041. }
  1042. public dispose() {
  1043. if (this.isInVRMode()) {
  1044. this.exitVR();
  1045. }
  1046. this._deviceOrientationCamera.dispose();
  1047. if (this._passProcessMove) {
  1048. this._passProcessMove.dispose();
  1049. }
  1050. if (this._postProcessMove) {
  1051. this._postProcessMove.dispose();
  1052. }
  1053. if (this._webVRCamera) {
  1054. this._webVRCamera.dispose();
  1055. }
  1056. if (this._vrDeviceOrientationCamera) {
  1057. this._vrDeviceOrientationCamera.dispose();
  1058. }
  1059. if (!this._useCustomVRButton) {
  1060. document.body.removeChild(this._btnVR);
  1061. }
  1062. document.removeEventListener("keydown", this._onKeyDown);
  1063. window.removeEventListener('vrdisplaypresentchange', this._onVrDisplayPresentChange);
  1064. }
  1065. public getClassName(): string {
  1066. return "VRExperienceHelper";
  1067. }
  1068. }
  1069. }