babylon.vrExperienceHelper.ts 64 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418
  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 interface VRExperienceHelperOptions extends WebVROptions {
  7. createDeviceOrientationCamera?: boolean; // Create a DeviceOrientationCamera to be used as your out of vr camera.
  8. createFallbackVRDeviceOrientationFreeCamera?: boolean; // Create a VRDeviceOrientationFreeCamera to be used for VR when no external HMD is found
  9. }
  10. export class VRExperienceHelper {
  11. private _scene: BABYLON.Scene;
  12. private _position: Vector3;
  13. private _btnVR: HTMLButtonElement;
  14. private _btnVRDisplayed: boolean;
  15. // Can the system support WebVR, even if a headset isn't plugged in?
  16. private _webVRsupported = false;
  17. // If WebVR is supported, is a headset plugged in and are we ready to present?
  18. private _webVRready = false;
  19. // Are we waiting for the requestPresent callback to complete?
  20. private _webVRrequesting = false;
  21. // Are we presenting to the headset right now?
  22. private _webVRpresenting = false;
  23. // Are we presenting in the fullscreen fallback?
  24. private _fullscreenVRpresenting = false;
  25. private _canvas: Nullable<HTMLCanvasElement>;
  26. private _webVRCamera: WebVRFreeCamera;
  27. private _vrDeviceOrientationCamera: Nullable<VRDeviceOrientationFreeCamera>;
  28. private _deviceOrientationCamera: Nullable<DeviceOrientationCamera>;
  29. private _existingCamera: Camera;
  30. private _onKeyDown: (event: KeyboardEvent) => void;
  31. private _onVrDisplayPresentChange: any;
  32. private _onVRDisplayChanged: (eventArgs: IDisplayChangedEventArgs) => void;
  33. private _onVRRequestPresentStart: () => void;
  34. private _onVRRequestPresentComplete: (success: boolean) => void;
  35. public onEnteringVR = new Observable<VRExperienceHelper>();
  36. public onExitingVR = new Observable<VRExperienceHelper>();
  37. public onControllerMeshLoaded = new Observable<WebVRController>();
  38. private _rayLength: number;
  39. private _useCustomVRButton: boolean = false;
  40. private _teleportationRequested: boolean = false;
  41. private _teleportationEnabledOnLeftController: boolean = false;
  42. private _teleportationEnabledOnRightController: boolean = false;
  43. private _interactionsEnabledOnLeftController: boolean = false;
  44. private _interactionsEnabledOnRightController: boolean = false;
  45. private _leftControllerReady: boolean = false;
  46. private _rightControllerReady: boolean = false;
  47. private _floorMeshName: string;
  48. private _floorMeshesCollection: Mesh[] = [];
  49. private _teleportationAllowed: boolean = false;
  50. private _rotationAllowed: boolean = true;
  51. private _teleportationRequestInitiated = false;
  52. private _teleportationBackRequestInitiated = false;
  53. private teleportBackwardsVector = new Vector3(0, -1, -1);
  54. private _rotationRightAsked = false;
  55. private _rotationLeftAsked = false;
  56. private _teleportationTarget: Mesh;
  57. private _isDefaultTeleportationTarget = true;
  58. private _postProcessMove: ImageProcessingPostProcess;
  59. private _passProcessMove: PassPostProcess;
  60. private _teleportationFillColor: string = "#444444";
  61. private _teleportationBorderColor: string = "#FFFFFF";
  62. private _rotationAngle: number = 0;
  63. private _haloCenter = new Vector3(0, 0, 0);
  64. private _gazeTracker: BABYLON.Mesh;
  65. private _padSensibilityUp = 0.65;
  66. private _padSensibilityDown = 0.35;
  67. private _leftLaserPointer: Nullable<Mesh>;
  68. private _rightLaserPointer: Nullable<Mesh>;
  69. private _currentMeshSelected: Nullable<AbstractMesh>;
  70. public onNewMeshSelected = new Observable<AbstractMesh>();
  71. private _circleEase: CircleEase;
  72. private _raySelectionPredicate: (mesh: AbstractMesh) => boolean;
  73. /**
  74. * To be optionaly changed by user to define custom ray selection
  75. */
  76. public raySelectionPredicate: (mesh: AbstractMesh) => boolean;
  77. /**
  78. * To be optionaly changed by user to define custom selection logic (after ray selection)
  79. */
  80. public meshSelectionPredicate: (mesh: AbstractMesh) => boolean;
  81. private _currentHit: Nullable<PickingInfo>;
  82. private _pointerDownOnMeshAsked = false;
  83. private _isActionableMesh = false;
  84. private _defaultHeight: number;
  85. private _teleportationEnabled = false;
  86. private _interactionsEnabled = false;
  87. private _interactionsRequested = false;
  88. private _displayGaze = true;
  89. private _displayLaserPointer = true;
  90. public get teleportationTarget(): Mesh {
  91. return this._teleportationTarget;
  92. }
  93. public set teleportationTarget(value: Mesh) {
  94. if (value) {
  95. value.name = "teleportationTarget";
  96. this._isDefaultTeleportationTarget = false;
  97. this._teleportationTarget = value;
  98. }
  99. }
  100. public get displayGaze(): boolean {
  101. return this._displayGaze;
  102. }
  103. public set displayGaze(value: boolean) {
  104. this._displayGaze = value;
  105. if (!value) {
  106. this._gazeTracker.isVisible = false;
  107. }
  108. }
  109. public get displayLaserPointer(): boolean {
  110. return this._displayLaserPointer;
  111. }
  112. public set displayLaserPointer(value: boolean) {
  113. this._displayLaserPointer = value;
  114. if (!value) {
  115. if (this._rightLaserPointer) {
  116. this._rightLaserPointer.isVisible = false;
  117. }
  118. if (this._leftLaserPointer) {
  119. this._leftLaserPointer.isVisible = false;
  120. }
  121. }
  122. else {
  123. if(this._rightLaserPointer) {
  124. this._rightLaserPointer.isVisible = true;
  125. }
  126. else if (this._leftLaserPointer) {
  127. this._leftLaserPointer.isVisible = true;
  128. }
  129. }
  130. }
  131. public get deviceOrientationCamera(): Nullable<DeviceOrientationCamera> {
  132. return this._deviceOrientationCamera;
  133. }
  134. // Based on the current WebVR support, returns the current VR camera used
  135. public get currentVRCamera(): Nullable<Camera> {
  136. if (this._webVRready) {
  137. return this._webVRCamera;
  138. }
  139. else {
  140. return this._scene.activeCamera;
  141. }
  142. }
  143. public get webVRCamera(): WebVRFreeCamera {
  144. return this._webVRCamera;
  145. }
  146. public get vrDeviceOrientationCamera(): Nullable<VRDeviceOrientationFreeCamera> {
  147. return this._vrDeviceOrientationCamera;
  148. }
  149. constructor(scene: Scene, public webVROptions: VRExperienceHelperOptions = {}) {
  150. this._scene = scene;
  151. this._canvas = scene.getEngine().getRenderingCanvas();
  152. // Parse options
  153. if (webVROptions.createFallbackVRDeviceOrientationFreeCamera === undefined) {
  154. webVROptions.createFallbackVRDeviceOrientationFreeCamera = true;
  155. }
  156. if (webVROptions.createDeviceOrientationCamera === undefined) {
  157. webVROptions.createDeviceOrientationCamera = true;
  158. }
  159. if (webVROptions.defaultHeight === undefined) {
  160. webVROptions.defaultHeight = 1.7;
  161. }
  162. if (webVROptions.useCustomVRButton) {
  163. this._useCustomVRButton = true;
  164. if (webVROptions.customVRButton) {
  165. this._btnVR = webVROptions.customVRButton;
  166. }
  167. }
  168. if (webVROptions.rayLength) {
  169. this._rayLength = webVROptions.rayLength
  170. }
  171. this._defaultHeight = webVROptions.defaultHeight;
  172. // Set position
  173. if(this._scene.activeCamera){
  174. this._position = this._scene.activeCamera.position.clone();
  175. }else{
  176. this._position = new BABYLON.Vector3(0, this._defaultHeight, 0);
  177. }
  178. // Set non-vr camera
  179. if(webVROptions.createDeviceOrientationCamera || !this._scene.activeCamera){
  180. this._deviceOrientationCamera = new BABYLON.DeviceOrientationCamera("deviceOrientationVRHelper", this._position.clone(), scene);
  181. // Copy data from existing camera
  182. if(this._scene.activeCamera){
  183. this._deviceOrientationCamera.minZ = this._scene.activeCamera.minZ;
  184. this._deviceOrientationCamera.maxZ = this._scene.activeCamera.maxZ;
  185. // Set rotation from previous camera
  186. if (this._scene.activeCamera instanceof TargetCamera && this._scene.activeCamera.rotation) {
  187. var targetCamera = this._scene.activeCamera;
  188. if (targetCamera.rotationQuaternion) {
  189. this._deviceOrientationCamera.rotationQuaternion.copyFrom(targetCamera.rotationQuaternion);
  190. } else {
  191. this._deviceOrientationCamera.rotationQuaternion.copyFrom(Quaternion.RotationYawPitchRoll(targetCamera.rotation.y, targetCamera.rotation.x, targetCamera.rotation.z));
  192. }
  193. this._deviceOrientationCamera.rotation = targetCamera.rotation.clone();
  194. }
  195. }
  196. this._scene.activeCamera = this._deviceOrientationCamera;
  197. if (this._canvas) {
  198. this._scene.activeCamera.attachControl(this._canvas);
  199. }
  200. }else{
  201. this._existingCamera = this._scene.activeCamera;
  202. }
  203. // Create VR cameras
  204. if (webVROptions.createFallbackVRDeviceOrientationFreeCamera) {
  205. this._vrDeviceOrientationCamera = new BABYLON.VRDeviceOrientationFreeCamera("VRDeviceOrientationVRHelper", this._position, this._scene);
  206. }
  207. this._webVRCamera = new BABYLON.WebVRFreeCamera("WebVRHelper", this._position, this._scene, webVROptions);
  208. this._webVRCamera.useStandingMatrix()
  209. // Create default button
  210. if (!this._useCustomVRButton) {
  211. this._btnVR = <HTMLButtonElement>document.createElement("BUTTON");
  212. this._btnVR.className = "babylonVRicon";
  213. this._btnVR.id = "babylonVRiconbtn";
  214. this._btnVR.title = "Click to switch to VR";
  215. 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) }";
  216. css += ".babylonVRicon.vrdisplaypresenting { display: none; }";
  217. // TODO: Add user feedback so that they know what state the VRDisplay is in (disconnected, connected, entering-VR)
  218. // css += ".babylonVRicon.vrdisplaysupported { }";
  219. // css += ".babylonVRicon.vrdisplayready { }";
  220. // css += ".babylonVRicon.vrdisplayrequesting { }";
  221. var style = document.createElement('style');
  222. style.appendChild(document.createTextNode(css));
  223. document.getElementsByTagName('head')[0].appendChild(style);
  224. this.moveButtonToBottomRight();
  225. }
  226. // VR button click event
  227. if (this._btnVR) {
  228. this._btnVR.addEventListener("click", () => {
  229. if (!this.isInVRMode) {
  230. this.enterVR();
  231. } else {
  232. this.exitVR();
  233. }
  234. });
  235. }
  236. // Window events
  237. window.addEventListener("resize", this._onResize);
  238. document.addEventListener("fullscreenchange", this._onFullscreenChange, false);
  239. document.addEventListener("mozfullscreenchange", this._onFullscreenChange, false);
  240. document.addEventListener("webkitfullscreenchange", this._onFullscreenChange, false);
  241. document.addEventListener("msfullscreenchange", this._onFullscreenChange, false);
  242. // Display vr button when headset is connected
  243. if (webVROptions.createFallbackVRDeviceOrientationFreeCamera) {
  244. this.displayVRButton();
  245. }else{
  246. this._scene.getEngine().onVRDisplayChangedObservable.add((e) => {
  247. if (e.vrDisplay) {
  248. this.displayVRButton();
  249. }
  250. })
  251. }
  252. // Exiting VR mode using 'ESC' key on desktop
  253. this._onKeyDown = (event: KeyboardEvent) => {
  254. if (event.keyCode === 27 && this.isInVRMode) {
  255. this.exitVR();
  256. }
  257. };
  258. document.addEventListener("keydown", this._onKeyDown);
  259. // Exiting VR mode double tapping the touch screen
  260. this._scene.onPrePointerObservable.add((pointerInfo, eventState) => {
  261. if (this.isInVRMode) {
  262. this.exitVR();
  263. if (this._fullscreenVRpresenting) {
  264. this._scene.getEngine().switchFullscreen(true);
  265. }
  266. }
  267. }, BABYLON.PointerEventTypes.POINTERDOUBLETAP, false);
  268. // Listen for WebVR display changes
  269. this._onVRDisplayChanged = (eventArgs: IDisplayChangedEventArgs) => this.onVRDisplayChanged(eventArgs);
  270. this._onVrDisplayPresentChange = () => this.onVrDisplayPresentChange();
  271. this._onVRRequestPresentStart = () => {
  272. this._webVRrequesting = true;
  273. this.updateButtonVisibility();
  274. }
  275. this._onVRRequestPresentComplete = (success: boolean) => {
  276. this._webVRrequesting = false;
  277. this.updateButtonVisibility();
  278. };
  279. scene.getEngine().onVRDisplayChangedObservable.add(this._onVRDisplayChanged);
  280. scene.getEngine().onVRRequestPresentStart.add(this._onVRRequestPresentStart);
  281. scene.getEngine().onVRRequestPresentComplete.add(this._onVRRequestPresentComplete);
  282. window.addEventListener('vrdisplaypresentchange', this._onVrDisplayPresentChange);
  283. scene.onDisposeObservable.add(()=>{
  284. this.dispose();
  285. })
  286. // Gamepad connection events
  287. this._webVRCamera.onControllerMeshLoadedObservable.add((webVRController) => this._onDefaultMeshLoaded(webVRController));
  288. this._scene.gamepadManager.onGamepadConnectedObservable.add(this._onNewGamepadConnected);
  289. this._scene.gamepadManager.onGamepadDisconnectedObservable.add(this._onNewGamepadDisconnected);
  290. this.updateButtonVisibility();
  291. //create easing functions
  292. this._circleEase = new BABYLON.CircleEase();
  293. this._circleEase.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);
  294. }
  295. // Raised when one of the controller has loaded successfully its associated default mesh
  296. private _onDefaultMeshLoaded(webVRController: WebVRController) {
  297. if (webVRController.hand === "left") {
  298. this._leftControllerReady = true;
  299. if (this._interactionsRequested && !this._interactionsEnabledOnLeftController) {
  300. this._enableInteractionOnController(webVRController);
  301. }
  302. if (this._teleportationRequested && !this._teleportationEnabledOnLeftController) {
  303. this._enableTeleportationOnController(webVRController);
  304. }
  305. }
  306. if (webVRController.hand === "right") {
  307. this._rightControllerReady = true;
  308. if (this._interactionsRequested && !this._interactionsEnabledOnRightController) {
  309. this._enableInteractionOnController(webVRController);
  310. }
  311. if (this._teleportationRequested && !this._teleportationEnabledOnRightController) {
  312. this._enableTeleportationOnController(webVRController);
  313. }
  314. }
  315. try {
  316. this.onControllerMeshLoaded.notifyObservers(webVRController);
  317. }
  318. catch (err) {
  319. Tools.Warn("Error in your custom logic onControllerMeshLoaded: " + err);
  320. }
  321. }
  322. private _onResize = ()=>{
  323. this.moveButtonToBottomRight();
  324. if (this._fullscreenVRpresenting && this._webVRready) {
  325. this.exitVR();
  326. }
  327. }
  328. private _onFullscreenChange = ()=>{
  329. if (document.fullscreen !== undefined) {
  330. this._fullscreenVRpresenting = document.fullscreen;
  331. } else if (document.mozFullScreen !== undefined) {
  332. this._fullscreenVRpresenting = document.mozFullScreen;
  333. } else if (document.webkitIsFullScreen !== undefined) {
  334. this._fullscreenVRpresenting = document.webkitIsFullScreen;
  335. } else if (document.msIsFullScreen !== undefined) {
  336. this._fullscreenVRpresenting = document.msIsFullScreen;
  337. }
  338. if (!this._fullscreenVRpresenting && this._canvas) {
  339. this.exitVR();
  340. if (!this._useCustomVRButton) {
  341. this._btnVR.style.top = this._canvas.offsetTop + this._canvas.offsetHeight - 70 + "px";
  342. this._btnVR.style.left = this._canvas.offsetLeft + this._canvas.offsetWidth - 100 + "px";
  343. }
  344. }
  345. }
  346. /**
  347. * Gets a value indicating if we are currently in VR mode.
  348. */
  349. public get isInVRMode(): boolean {
  350. return this._webVRpresenting || this._fullscreenVRpresenting;
  351. }
  352. private onVrDisplayPresentChange() {
  353. var vrDisplay = this._scene.getEngine().getVRDevice();
  354. if (vrDisplay) {
  355. var wasPresenting = this._webVRpresenting;
  356. // A VR display is connected
  357. this._webVRpresenting = vrDisplay.isPresenting;
  358. if (wasPresenting && !this._webVRpresenting)
  359. this.exitVR();
  360. } else {
  361. Tools.Warn('Detected VRDisplayPresentChange on an unknown VRDisplay. Did you can enterVR on the vrExperienceHelper?');
  362. }
  363. this.updateButtonVisibility();
  364. }
  365. private onVRDisplayChanged(eventArgs: IDisplayChangedEventArgs) {
  366. this._webVRsupported = eventArgs.vrSupported;
  367. this._webVRready = !!eventArgs.vrDisplay;
  368. this._webVRpresenting = eventArgs.vrDisplay && eventArgs.vrDisplay.isPresenting;
  369. this.updateButtonVisibility();
  370. }
  371. private moveButtonToBottomRight(){
  372. if (this._canvas && !this._useCustomVRButton) {
  373. this._btnVR.style.top = this._canvas.offsetTop + this._canvas.offsetHeight - 70 + "px";
  374. this._btnVR.style.left = this._canvas.offsetLeft + this._canvas.offsetWidth - 100 + "px";
  375. }
  376. }
  377. private displayVRButton(){
  378. if (!this._useCustomVRButton && !this._btnVRDisplayed) {
  379. document.body.appendChild(this._btnVR);
  380. this._btnVRDisplayed = true;
  381. }
  382. }
  383. private updateButtonVisibility() {
  384. if (!this._btnVR || this._useCustomVRButton) {
  385. return;
  386. }
  387. this._btnVR.className = "babylonVRicon";
  388. if (this.isInVRMode) {
  389. this._btnVR.className += " vrdisplaypresenting";
  390. } else {
  391. if (this._webVRready) this._btnVR.className += " vrdisplayready";
  392. if (this._webVRsupported) this._btnVR.className += " vrdisplaysupported";
  393. if (this._webVRrequesting) this._btnVR.className += " vrdisplayrequesting";
  394. }
  395. }
  396. /**
  397. * Attempt to enter VR. If a headset is connected and ready, will request present on that.
  398. * Otherwise, will use the fullscreen API.
  399. */
  400. public enterVR() {
  401. if (this.onEnteringVR) {
  402. try {
  403. this.onEnteringVR.notifyObservers(this);
  404. }
  405. catch (err) {
  406. Tools.Warn("Error in your custom logic onEnteringVR: " + err);
  407. }
  408. }
  409. if (this._scene.activeCamera) {
  410. this._position = this._scene.activeCamera.position.clone();
  411. }
  412. if (this._webVRrequesting)
  413. return;
  414. // If WebVR is supported and a headset is connected
  415. if (this._webVRready) {
  416. if (!this._webVRpresenting) {
  417. this._webVRCamera.position = this._position;
  418. this._scene.activeCamera = this._webVRCamera;
  419. }
  420. }
  421. else if (this._vrDeviceOrientationCamera) {
  422. this._vrDeviceOrientationCamera.position = this._position;
  423. this._scene.activeCamera = this._vrDeviceOrientationCamera;
  424. this._scene.getEngine().switchFullscreen(true);
  425. this.updateButtonVisibility();
  426. }
  427. if (this._scene.activeCamera && this._canvas) {
  428. this._scene.activeCamera.attachControl(this._canvas);
  429. }
  430. }
  431. /**
  432. * Attempt to exit VR, or fullscreen.
  433. */
  434. public exitVR() {
  435. if (this.onExitingVR) {
  436. try {
  437. this.onExitingVR.notifyObservers(this);
  438. }
  439. catch (err) {
  440. Tools.Warn("Error in your custom logic onExitingVR: " + err);
  441. }
  442. }
  443. if (this._webVRpresenting) {
  444. this._scene.getEngine().disableVR();
  445. }
  446. if (this._scene.activeCamera) {
  447. this._position = this._scene.activeCamera.position.clone();
  448. }
  449. if (this._deviceOrientationCamera) {
  450. this._deviceOrientationCamera.position = this._position;
  451. this._scene.activeCamera = this._deviceOrientationCamera;
  452. if (this._canvas) {
  453. this._scene.activeCamera.attachControl(this._canvas);
  454. }
  455. } else if (this._existingCamera) {
  456. this._existingCamera.position = this._position;
  457. this._scene.activeCamera = this._existingCamera;
  458. }
  459. this.updateButtonVisibility();
  460. }
  461. public get position(): Vector3 {
  462. return this._position;
  463. }
  464. public set position(value: Vector3) {
  465. this._position = value;
  466. if (this._scene.activeCamera) {
  467. this._scene.activeCamera.position = value;
  468. }
  469. }
  470. public enableInteractions() {
  471. if (!this._interactionsEnabled) {
  472. this._interactionsRequested = true;
  473. if (this._leftControllerReady && this._webVRCamera.leftController) {
  474. this._enableInteractionOnController(this._webVRCamera.leftController)
  475. }
  476. if (this._rightControllerReady && this._webVRCamera.rightController) {
  477. this._enableInteractionOnController(this._webVRCamera.rightController)
  478. }
  479. this._createGazeTracker();
  480. this.raySelectionPredicate = (mesh) => {
  481. return true;
  482. }
  483. this.meshSelectionPredicate = (mesh) => {
  484. return true;
  485. }
  486. this._raySelectionPredicate = (mesh) => {
  487. if (this._isTeleportationFloor(mesh) || (mesh.isVisible && mesh.name.indexOf("gazeTracker") === -1
  488. && mesh.name.indexOf("teleportationTarget") === -1
  489. && mesh.name.indexOf("torusTeleportation") === -1
  490. && mesh.name.indexOf("laserPointer") === -1)) {
  491. return this.raySelectionPredicate(mesh);
  492. }
  493. return false;
  494. }
  495. this._scene.registerBeforeRender(this.beforeRender);
  496. this._interactionsEnabled = true;
  497. }
  498. }
  499. private beforeRender = () => {
  500. this._castRayAndSelectObject();
  501. }
  502. private _isTeleportationFloor(mesh: AbstractMesh): boolean {
  503. for (var i = 0; i < this._floorMeshesCollection.length; i++) {
  504. if (this._floorMeshesCollection[i].id === mesh.id) {
  505. return true;
  506. }
  507. }
  508. if (this._floorMeshName && mesh.name === this._floorMeshName) {
  509. return true;
  510. }
  511. return false;
  512. }
  513. public addFloorMesh(floorMesh: Mesh): void {
  514. if (!this._floorMeshesCollection) {
  515. return;
  516. }
  517. if (this._floorMeshesCollection.indexOf(floorMesh) > -1) {
  518. return;
  519. }
  520. this._floorMeshesCollection.push(floorMesh);
  521. }
  522. public removeFloorMesh(floorMesh: Mesh): void {
  523. if (!this._floorMeshesCollection) {
  524. return
  525. }
  526. const meshIndex = this._floorMeshesCollection.indexOf(floorMesh);
  527. if (meshIndex !== -1) {
  528. this._floorMeshesCollection.splice(meshIndex, 1);
  529. }
  530. }
  531. public enableTeleportation(vrTeleportationOptions: VRTeleportationOptions = {}) {
  532. if (!this._teleportationEnabled) {
  533. this._teleportationRequested = true;
  534. this.enableInteractions();
  535. if (vrTeleportationOptions) {
  536. if (vrTeleportationOptions.floorMeshName) {
  537. this._floorMeshName = vrTeleportationOptions.floorMeshName;
  538. }
  539. if (vrTeleportationOptions.floorMeshes) {
  540. this._floorMeshesCollection = vrTeleportationOptions.floorMeshes;
  541. }
  542. }
  543. if (this._leftControllerReady && this._webVRCamera.leftController) {
  544. this._enableTeleportationOnController(this._webVRCamera.leftController)
  545. }
  546. if (this._rightControllerReady && this._webVRCamera.rightController) {
  547. this._enableTeleportationOnController(this._webVRCamera.rightController)
  548. }
  549. // Creates an image processing post process for the vignette not relying
  550. // on the main scene configuration for image processing to reduce setup and spaces
  551. // (gamma/linear) conflicts.
  552. const imageProcessingConfiguration = new ImageProcessingConfiguration();
  553. imageProcessingConfiguration.vignetteColor = new BABYLON.Color4(0, 0, 0, 0);
  554. imageProcessingConfiguration.vignetteEnabled = true;
  555. this._postProcessMove = new BABYLON.ImageProcessingPostProcess("postProcessMove",
  556. 1.0,
  557. this._webVRCamera,
  558. undefined,
  559. undefined,
  560. undefined,
  561. undefined,
  562. imageProcessingConfiguration);
  563. this._webVRCamera.detachPostProcess(this._postProcessMove)
  564. this._passProcessMove = new BABYLON.PassPostProcess("pass", 1.0, this._webVRCamera);
  565. this._teleportationEnabled = true;
  566. if (this._isDefaultTeleportationTarget) {
  567. this._createTeleportationCircles();
  568. }
  569. }
  570. }
  571. private _onNewGamepadConnected = (gamepad: Gamepad) => {
  572. if (gamepad.type !== BABYLON.Gamepad.POSE_ENABLED) {
  573. if (gamepad.leftStick) {
  574. gamepad.onleftstickchanged((stickValues) => {
  575. if (this._teleportationEnabled) {
  576. // Listening to classic/xbox gamepad only if no VR controller is active
  577. if ((!this._leftLaserPointer && !this._rightLaserPointer) ||
  578. ((this._leftLaserPointer && !this._leftLaserPointer.isVisible) &&
  579. (this._rightLaserPointer && !this._rightLaserPointer.isVisible))) {
  580. this._checkTeleportWithRay(stickValues);
  581. this._checkTeleportBackwards(stickValues);
  582. }
  583. }
  584. });
  585. }
  586. if (gamepad.rightStick) {
  587. gamepad.onrightstickchanged((stickValues) => {
  588. if (this._teleportationEnabled) {
  589. this._checkRotate(stickValues);
  590. }
  591. });
  592. }
  593. if (gamepad.type === BABYLON.Gamepad.XBOX) {
  594. (<Xbox360Pad>gamepad).onbuttondown((buttonPressed: Xbox360Button) => {
  595. if (this._interactionsEnabled && buttonPressed === Xbox360Button.A) {
  596. this._selectionPointerDown();
  597. }
  598. });
  599. (<Xbox360Pad>gamepad).onbuttonup((buttonPressed: Xbox360Button) => {
  600. if (this._interactionsEnabled && buttonPressed === Xbox360Button.A) {
  601. this._selectionPointerUp();
  602. }
  603. });
  604. }
  605. }
  606. }
  607. private _onNewGamepadDisconnected = (gamepad: Gamepad) => {
  608. if (gamepad instanceof WebVRController) {
  609. if (gamepad.hand === "left") {
  610. this._interactionsEnabledOnLeftController = false;
  611. this._teleportationEnabledOnLeftController = false;
  612. this._leftControllerReady = false;
  613. if (this._leftLaserPointer) {
  614. this._leftLaserPointer.dispose();
  615. }
  616. }
  617. if (gamepad.hand === "right") {
  618. this._interactionsEnabledOnRightController = false;
  619. this._teleportationEnabledOnRightController = false;
  620. this._rightControllerReady = false;
  621. if (this._rightLaserPointer) {
  622. this._rightLaserPointer.dispose();
  623. }
  624. }
  625. }
  626. }
  627. private _enableInteractionOnController(webVRController: WebVRController) {
  628. var controllerMesh = webVRController.mesh;
  629. if (controllerMesh) {
  630. var makeNotPick = (root:AbstractMesh)=>{
  631. root.name += " laserPointer";
  632. root.getChildMeshes().forEach((c)=>{
  633. makeNotPick(c);
  634. });
  635. }
  636. makeNotPick(controllerMesh);
  637. var childMeshes = controllerMesh.getChildMeshes();
  638. for (var i = 0; i < childMeshes.length; i++) {
  639. if (childMeshes[i].name && childMeshes[i].name.indexOf("POINTING_POSE") >= 0) {
  640. controllerMesh = childMeshes[i];
  641. break;
  642. }
  643. }
  644. var laserPointer = BABYLON.Mesh.CreateCylinder("laserPointer", 1, 0.004, 0.0002, 20, 1, this._scene, false);
  645. var laserPointerMaterial = new BABYLON.StandardMaterial("laserPointerMat", this._scene);
  646. laserPointerMaterial.emissiveColor = new BABYLON.Color3(0.7, 0.7, 0.7);
  647. laserPointerMaterial.alpha = 0.6;
  648. laserPointer.material = laserPointerMaterial;
  649. laserPointer.rotation.x = Math.PI / 2;
  650. laserPointer.parent = controllerMesh;
  651. laserPointer.position.z = -0.5;
  652. laserPointer.isVisible = false;
  653. if (webVRController.hand === "left") {
  654. this._leftLaserPointer = laserPointer;
  655. this._interactionsEnabledOnLeftController = true;
  656. if (!this._rightLaserPointer) {
  657. this._leftLaserPointer.isVisible = true;
  658. }
  659. }
  660. else {
  661. this._rightLaserPointer = laserPointer;
  662. this._interactionsEnabledOnRightController = true;
  663. if (!this._leftLaserPointer) {
  664. this._rightLaserPointer.isVisible = true;
  665. }
  666. }
  667. webVRController.onMainButtonStateChangedObservable.add((stateObject) => {
  668. // Enabling / disabling laserPointer
  669. if (this._displayLaserPointer && stateObject.value === 1) {
  670. laserPointer.isVisible = !laserPointer.isVisible;
  671. // Laser pointer can only be active on left or right, not both at the same time
  672. if (webVRController.hand === "left" && this._rightLaserPointer) {
  673. this._rightLaserPointer.isVisible = false;
  674. }
  675. else if (this._leftLaserPointer) {
  676. this._leftLaserPointer.isVisible = false;
  677. }
  678. }
  679. });
  680. webVRController.onTriggerStateChangedObservable.add((stateObject) => {
  681. if (!this._pointerDownOnMeshAsked) {
  682. if (stateObject.value > this._padSensibilityUp) {
  683. this._selectionPointerDown();
  684. }
  685. } else if (stateObject.value < this._padSensibilityDown) {
  686. this._selectionPointerUp();
  687. }
  688. });
  689. }
  690. }
  691. private _checkTeleportWithRay(stateObject: StickValues, webVRController:Nullable<WebVRController> = null){
  692. if (!this._teleportationRequestInitiated) {
  693. if (stateObject.y < -this._padSensibilityUp) {
  694. if(webVRController){
  695. // If laser pointer wasn't enabled yet
  696. if (this._displayLaserPointer && webVRController.hand === "left" && this._leftLaserPointer) {
  697. this._leftLaserPointer.isVisible = true;
  698. if (this._rightLaserPointer) {
  699. this._rightLaserPointer.isVisible = false;
  700. }
  701. } else if (this._displayLaserPointer && this._rightLaserPointer) {
  702. this._rightLaserPointer.isVisible = true;
  703. if (this._leftLaserPointer) {
  704. this._leftLaserPointer.isVisible = false;
  705. }
  706. }
  707. }
  708. this._teleportationRequestInitiated = true;
  709. }
  710. } else {
  711. // Listening to the proper controller values changes to confirm teleportation
  712. if (webVRController == null
  713. ||(webVRController.hand === "left" && this._leftLaserPointer && this._leftLaserPointer.isVisible)
  714. || (webVRController.hand === "right" && this._rightLaserPointer && this._rightLaserPointer.isVisible)) {
  715. if (stateObject.y > -this._padSensibilityDown) {
  716. if (this._teleportationAllowed) {
  717. this._teleportationAllowed = false;
  718. this._teleportCamera();
  719. }
  720. this._teleportationRequestInitiated = false;
  721. }
  722. }
  723. }
  724. }
  725. private _selectionPointerDown(){
  726. this._pointerDownOnMeshAsked = true;
  727. if (this._currentMeshSelected && this._currentHit) {
  728. this._scene.simulatePointerDown(this._currentHit);
  729. }
  730. }
  731. private _selectionPointerUp(){
  732. if (this._currentMeshSelected && this._currentHit) {
  733. this._scene.simulatePointerUp(this._currentHit);
  734. }
  735. this._pointerDownOnMeshAsked = false;
  736. }
  737. private _checkRotate(stateObject: StickValues){
  738. if (!this._rotationLeftAsked) {
  739. if (stateObject.x < -this._padSensibilityUp) {
  740. this._rotationLeftAsked = true;
  741. if (this._rotationAllowed) {
  742. this._rotateCamera(false);
  743. }
  744. }
  745. } else {
  746. if (stateObject.x > -this._padSensibilityDown) {
  747. this._rotationLeftAsked = false;
  748. }
  749. }
  750. if (!this._rotationRightAsked) {
  751. if (stateObject.x > this._padSensibilityUp) {
  752. this._rotationRightAsked = true;
  753. if (this._rotationAllowed) {
  754. this._rotateCamera(true);
  755. }
  756. }
  757. } else {
  758. if (stateObject.x < this._padSensibilityDown) {
  759. this._rotationRightAsked = false;
  760. }
  761. }
  762. }
  763. private _checkTeleportBackwards(stateObject: StickValues){
  764. // Teleport backwards
  765. if(stateObject.y > this._padSensibilityUp) {
  766. if(!this._teleportationBackRequestInitiated){
  767. if(!this.currentVRCamera){
  768. return;
  769. }
  770. // Get rotation and position of the current camera
  771. var rotation = Quaternion.FromRotationMatrix(this.currentVRCamera.getWorldMatrix().getRotationMatrix());
  772. var position = this.currentVRCamera.position;
  773. // If the camera has device position, use that instead
  774. if((<WebVRFreeCamera>this.currentVRCamera).devicePosition && (<WebVRFreeCamera>this.currentVRCamera).deviceRotationQuaternion){
  775. rotation = (<WebVRFreeCamera>this.currentVRCamera).deviceRotationQuaternion;
  776. position = (<WebVRFreeCamera>this.currentVRCamera).devicePosition;
  777. }
  778. // Get matrix with only the y rotation of the device rotation
  779. rotation.toEulerAnglesToRef(this._workingVector);
  780. this._workingVector.z = 0;
  781. this._workingVector.x = 0;
  782. Quaternion.RotationYawPitchRollToRef(this._workingVector.y, this._workingVector.x, this._workingVector.z, this._workingQuaternion);
  783. this._workingQuaternion.toRotationMatrix(this._workingMatrix);
  784. // Rotate backwards ray by device rotation to cast at the ground behind the user
  785. Vector3.TransformCoordinatesToRef(this.teleportBackwardsVector, this._workingMatrix, this._workingVector);
  786. // Teleport if ray hit the ground and is not to far away eg. backwards off a cliff
  787. var ray = new BABYLON.Ray(position, this._workingVector);
  788. var hit = this._scene.pickWithRay(ray, this._raySelectionPredicate);
  789. if(hit && hit.pickedPoint && hit.pickedMesh &&this._isTeleportationFloor(hit.pickedMesh) && hit.distance < 5){
  790. this._teleportCamera(hit.pickedPoint);
  791. }
  792. this._teleportationBackRequestInitiated = true;
  793. }
  794. }else{
  795. this._teleportationBackRequestInitiated = false;
  796. }
  797. }
  798. private _enableTeleportationOnController(webVRController: WebVRController) {
  799. var controllerMesh = webVRController.mesh;
  800. if (controllerMesh) {
  801. if (webVRController.hand === "left") {
  802. if (!this._interactionsEnabledOnLeftController) {
  803. this._enableInteractionOnController(webVRController);
  804. }
  805. this._teleportationEnabledOnLeftController = true;
  806. }
  807. else {
  808. if (!this._interactionsEnabledOnRightController) {
  809. this._enableInteractionOnController(webVRController);
  810. }
  811. this._teleportationEnabledOnRightController = true;
  812. }
  813. webVRController.onPadValuesChangedObservable.add((stateObject) => {
  814. this._checkTeleportBackwards(stateObject);
  815. this._checkTeleportWithRay(stateObject, webVRController);
  816. this._checkRotate(stateObject)
  817. });
  818. }
  819. }
  820. // Gaze support used to point to teleport or to interact with an object
  821. private _createGazeTracker() {
  822. this._gazeTracker = BABYLON.Mesh.CreateTorus("gazeTracker", 0.0035, 0.0025, 20, this._scene, false);
  823. this._gazeTracker.bakeCurrentTransformIntoVertices();
  824. this._gazeTracker.isPickable = false;
  825. this._gazeTracker.isVisible = false;
  826. var targetMat = new BABYLON.StandardMaterial("targetMat", this._scene);
  827. targetMat.specularColor = BABYLON.Color3.Black();
  828. targetMat.emissiveColor = new BABYLON.Color3(0.7, 0.7, 0.7)
  829. targetMat.backFaceCulling = false;
  830. this._gazeTracker.material = targetMat;
  831. }
  832. private _createTeleportationCircles() {
  833. this._teleportationTarget = BABYLON.Mesh.CreateGround("teleportationTarget", 2, 2, 2, this._scene);
  834. this._teleportationTarget.isPickable = false;
  835. var length = 512;
  836. var dynamicTexture = new BABYLON.DynamicTexture("DynamicTexture", length, this._scene, true);
  837. dynamicTexture.hasAlpha = true;
  838. var context = dynamicTexture.getContext();
  839. var centerX = length / 2;
  840. var centerY = length / 2;
  841. var radius = 200;
  842. context.beginPath();
  843. context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
  844. context.fillStyle = this._teleportationFillColor;
  845. context.fill();
  846. context.lineWidth = 10;
  847. context.strokeStyle = this._teleportationBorderColor;
  848. context.stroke();
  849. context.closePath();
  850. dynamicTexture.update();
  851. var teleportationCircleMaterial = new BABYLON.StandardMaterial("TextPlaneMaterial", this._scene);
  852. teleportationCircleMaterial.diffuseTexture = dynamicTexture;
  853. this._teleportationTarget.material = teleportationCircleMaterial;
  854. var torus = BABYLON.Mesh.CreateTorus("torusTeleportation", 0.75, 0.1, 25, this._scene, false);
  855. torus.isPickable = false;
  856. torus.parent = this._teleportationTarget;
  857. var animationInnerCircle = new BABYLON.Animation("animationInnerCircle", "position.y", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
  858. var keys = [];
  859. keys.push({
  860. frame: 0,
  861. value: 0
  862. });
  863. keys.push({
  864. frame: 30,
  865. value: 0.4
  866. });
  867. keys.push({
  868. frame: 60,
  869. value: 0
  870. });
  871. animationInnerCircle.setKeys(keys);
  872. var easingFunction = new BABYLON.SineEase();
  873. easingFunction.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);
  874. animationInnerCircle.setEasingFunction(easingFunction);
  875. torus.animations = [];
  876. torus.animations.push(animationInnerCircle);
  877. this._scene.beginAnimation(torus, 0, 60, true);
  878. this._hideTeleportationTarget();
  879. }
  880. private _displayTeleportationTarget() {
  881. if (this._teleportationEnabled) {
  882. this._teleportationTarget.isVisible = true;
  883. if (this._isDefaultTeleportationTarget) {
  884. (<Mesh>this._teleportationTarget.getChildren()[0]).isVisible = true;
  885. }
  886. }
  887. }
  888. private _hideTeleportationTarget() {
  889. if (this._teleportationEnabled) {
  890. this._teleportationTarget.isVisible = false;
  891. if (this._isDefaultTeleportationTarget) {
  892. (<Mesh>this._teleportationTarget.getChildren()[0]).isVisible = false;
  893. }
  894. }
  895. }
  896. private _rotateCamera(right: boolean) {
  897. if (!(this.currentVRCamera instanceof FreeCamera)) {
  898. return;
  899. }
  900. if (right) {
  901. this._rotationAngle++;
  902. }
  903. else {
  904. this._rotationAngle--;
  905. }
  906. this.currentVRCamera.animations = [];
  907. var target = BABYLON.Quaternion.FromRotationMatrix(BABYLON.Matrix.RotationY(Math.PI / 4 * this._rotationAngle));
  908. var animationRotation = new BABYLON.Animation("animationRotation", "rotationQuaternion", 90, BABYLON.Animation.ANIMATIONTYPE_QUATERNION,
  909. BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT);
  910. var animationRotationKeys = [];
  911. animationRotationKeys.push({
  912. frame: 0,
  913. value: this.currentVRCamera.rotationQuaternion
  914. });
  915. animationRotationKeys.push({
  916. frame: 6,
  917. value: target
  918. });
  919. animationRotation.setKeys(animationRotationKeys);
  920. animationRotation.setEasingFunction(this._circleEase);
  921. this.currentVRCamera.animations.push(animationRotation);
  922. this._postProcessMove.animations = [];
  923. var animationPP = new BABYLON.Animation("animationPP", "vignetteWeight", 90, BABYLON.Animation.ANIMATIONTYPE_FLOAT,
  924. BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT);
  925. var vignetteWeightKeys = [];
  926. vignetteWeightKeys.push({
  927. frame: 0,
  928. value: 0
  929. });
  930. vignetteWeightKeys.push({
  931. frame: 3,
  932. value: 4
  933. });
  934. vignetteWeightKeys.push({
  935. frame: 6,
  936. value: 0
  937. });
  938. animationPP.setKeys(vignetteWeightKeys);
  939. animationPP.setEasingFunction(this._circleEase);
  940. this._postProcessMove.animations.push(animationPP);
  941. var animationPP2 = new BABYLON.Animation("animationPP2", "vignetteStretch", 90, BABYLON.Animation.ANIMATIONTYPE_FLOAT,
  942. BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT);
  943. var vignetteStretchKeys = [];
  944. vignetteStretchKeys.push({
  945. frame: 0,
  946. value: 0
  947. });
  948. vignetteStretchKeys.push({
  949. frame: 3,
  950. value: 10
  951. });
  952. vignetteStretchKeys.push({
  953. frame: 6,
  954. value: 0
  955. });
  956. animationPP2.setKeys(vignetteStretchKeys);
  957. animationPP2.setEasingFunction(this._circleEase);
  958. this._postProcessMove.animations.push(animationPP2);
  959. this._postProcessMove.imageProcessingConfiguration.vignetteWeight = 0;
  960. this._postProcessMove.imageProcessingConfiguration.vignetteStretch = 0;
  961. this._webVRCamera.attachPostProcess(this._postProcessMove)
  962. this._scene.beginAnimation(this._postProcessMove, 0, 6, false, 1, () => {
  963. this._webVRCamera.detachPostProcess(this._postProcessMove)
  964. });
  965. this._scene.beginAnimation(this.currentVRCamera, 0, 6, false, 1);
  966. }
  967. private _moveTeleportationSelectorTo(hit: PickingInfo) {
  968. if (hit.pickedPoint) {
  969. this._teleportationAllowed = true;
  970. if (this._teleportationRequestInitiated) {
  971. this._displayTeleportationTarget();
  972. }
  973. else {
  974. this._hideTeleportationTarget();
  975. }
  976. this._haloCenter.copyFrom(hit.pickedPoint);
  977. this._teleportationTarget.position.copyFrom(hit.pickedPoint);
  978. var pickNormal = hit.getNormal(true, false);
  979. if (pickNormal) {
  980. var axis1 = BABYLON.Vector3.Cross(BABYLON.Axis.Y, pickNormal);
  981. var axis2 = BABYLON.Vector3.Cross(pickNormal, axis1);
  982. BABYLON.Vector3.RotationFromAxisToRef(axis2, pickNormal, axis1, this._teleportationTarget.rotation);
  983. }
  984. this._teleportationTarget.position.y += 0.1;
  985. }
  986. }
  987. private _workingVector = Vector3.Zero();
  988. private _workingQuaternion = Quaternion.Identity();
  989. private _workingMatrix = Matrix.Identity();
  990. private _teleportCamera(location:Nullable<Vector3> = null) {
  991. if (!(this.currentVRCamera instanceof FreeCamera)) {
  992. return;
  993. }
  994. if(!location){
  995. location = this._haloCenter;
  996. }
  997. // Teleport the hmd to where the user is looking by moving the anchor to where they are looking minus the
  998. // offset of the headset from the anchor.
  999. if (this.webVRCamera.leftCamera) {
  1000. this._workingVector.copyFrom(this.webVRCamera.leftCamera.globalPosition);
  1001. this._workingVector.subtractInPlace(this.webVRCamera.position);
  1002. location.subtractToRef(this._workingVector, this._workingVector);
  1003. } else {
  1004. this._workingVector.copyFrom(location);
  1005. }
  1006. // Add height to account for user's height offset
  1007. if(this.isInVRMode){
  1008. this._workingVector.y += this.webVRCamera.deviceDistanceToRoomGround();
  1009. }else{
  1010. this._workingVector.y += this._defaultHeight;
  1011. }
  1012. // Create animation from the camera's position to the new location
  1013. this.currentVRCamera.animations = [];
  1014. var animationCameraTeleportation = new BABYLON.Animation("animationCameraTeleportation", "position", 90, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT);
  1015. var animationCameraTeleportationKeys = [{
  1016. frame: 0,
  1017. value: this.currentVRCamera.position
  1018. },
  1019. {
  1020. frame: 11,
  1021. value: this._workingVector
  1022. }
  1023. ];
  1024. animationCameraTeleportation.setKeys(animationCameraTeleportationKeys);
  1025. animationCameraTeleportation.setEasingFunction(this._circleEase);
  1026. this.currentVRCamera.animations.push(animationCameraTeleportation);
  1027. this._postProcessMove.animations = [];
  1028. var animationPP = new BABYLON.Animation("animationPP", "vignetteWeight", 90, BABYLON.Animation.ANIMATIONTYPE_FLOAT,
  1029. BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT);
  1030. var vignetteWeightKeys = [];
  1031. vignetteWeightKeys.push({
  1032. frame: 0,
  1033. value: 0
  1034. });
  1035. vignetteWeightKeys.push({
  1036. frame: 5,
  1037. value: 8
  1038. });
  1039. vignetteWeightKeys.push({
  1040. frame: 11,
  1041. value: 0
  1042. });
  1043. animationPP.setKeys(vignetteWeightKeys);
  1044. this._postProcessMove.animations.push(animationPP);
  1045. var animationPP2 = new BABYLON.Animation("animationPP2", "vignetteStretch", 90, BABYLON.Animation.ANIMATIONTYPE_FLOAT,
  1046. BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT);
  1047. var vignetteStretchKeys = [];
  1048. vignetteStretchKeys.push({
  1049. frame: 0,
  1050. value: 0
  1051. });
  1052. vignetteStretchKeys.push({
  1053. frame: 5,
  1054. value: 10
  1055. });
  1056. vignetteStretchKeys.push({
  1057. frame: 11,
  1058. value: 0
  1059. });
  1060. animationPP2.setKeys(vignetteStretchKeys);
  1061. this._postProcessMove.animations.push(animationPP2);
  1062. this._postProcessMove.imageProcessingConfiguration.vignetteWeight = 0;
  1063. this._postProcessMove.imageProcessingConfiguration.vignetteStretch = 0;
  1064. this._webVRCamera.attachPostProcess(this._postProcessMove)
  1065. this._scene.beginAnimation(this._postProcessMove, 0, 11, false, 1, () => {
  1066. this._webVRCamera.detachPostProcess(this._postProcessMove)
  1067. });
  1068. this._scene.beginAnimation(this.currentVRCamera, 0, 11, false, 1);
  1069. }
  1070. private _castRayAndSelectObject() {
  1071. if (!(this.currentVRCamera instanceof FreeCamera)) {
  1072. return;
  1073. }
  1074. var ray:Ray;
  1075. if (this._leftLaserPointer && this._leftLaserPointer.isVisible && (<any>this.currentVRCamera).leftController) {
  1076. ray = (<any>this.currentVRCamera).leftController.getForwardRay(this._rayLength);
  1077. }
  1078. else if (this._rightLaserPointer && this._rightLaserPointer.isVisible && (<any>this.currentVRCamera).rightController) {
  1079. ray = (<any>this.currentVRCamera).rightController.getForwardRay(this._rayLength);
  1080. } else {
  1081. ray = this.currentVRCamera.getForwardRay(this._rayLength);
  1082. }
  1083. var hit = this._scene.pickWithRay(ray, this._raySelectionPredicate);
  1084. // Moving the gazeTracker on the mesh face targetted
  1085. if (hit && hit.pickedPoint) {
  1086. if (this._displayGaze) {
  1087. let multiplier = 1;
  1088. this._gazeTracker.isVisible = true;
  1089. if (this._isActionableMesh) {
  1090. multiplier = 3;
  1091. }
  1092. this._gazeTracker.scaling.x = hit.distance * multiplier;
  1093. this._gazeTracker.scaling.y = hit.distance * multiplier;
  1094. this._gazeTracker.scaling.z = hit.distance * multiplier;
  1095. var pickNormal = hit.getNormal();
  1096. // To avoid z-fighting
  1097. let deltaFighting = 0.002;
  1098. if (pickNormal) {
  1099. var axis1 = BABYLON.Vector3.Cross(BABYLON.Axis.Y, pickNormal);
  1100. var axis2 = BABYLON.Vector3.Cross(pickNormal, axis1);
  1101. BABYLON.Vector3.RotationFromAxisToRef(axis2, pickNormal, axis1, this._gazeTracker.rotation);
  1102. }
  1103. this._gazeTracker.position.copyFrom(hit.pickedPoint);
  1104. if (this._gazeTracker.position.x < 0) {
  1105. this._gazeTracker.position.x += deltaFighting;
  1106. }
  1107. else {
  1108. this._gazeTracker.position.x -= deltaFighting;
  1109. }
  1110. if (this._gazeTracker.position.y < 0) {
  1111. this._gazeTracker.position.y += deltaFighting;
  1112. }
  1113. else {
  1114. this._gazeTracker.position.y -= deltaFighting;
  1115. }
  1116. if (this._gazeTracker.position.z < 0) {
  1117. this._gazeTracker.position.z += deltaFighting;
  1118. }
  1119. else {
  1120. this._gazeTracker.position.z -= deltaFighting;
  1121. }
  1122. }
  1123. // Changing the size of the laser pointer based on the distance from the targetted point
  1124. if (this._rightLaserPointer && this._rightLaserPointer.isVisible) {
  1125. this._rightLaserPointer.scaling.y = hit.distance;
  1126. this._rightLaserPointer.position.z = -hit.distance / 2;
  1127. }
  1128. if (this._leftLaserPointer && this._leftLaserPointer.isVisible) {
  1129. this._leftLaserPointer.scaling.y = hit.distance;
  1130. this._leftLaserPointer.position.z = -hit.distance / 2;
  1131. }
  1132. }
  1133. else {
  1134. this._gazeTracker.isVisible = false;
  1135. }
  1136. if (hit && hit.pickedMesh) {
  1137. this._currentHit = hit;
  1138. if (this._pointerDownOnMeshAsked) {
  1139. this._scene.simulatePointerMove(this._currentHit);
  1140. }
  1141. // The object selected is the floor, we're in a teleportation scenario
  1142. if (this._teleportationEnabled && this._isTeleportationFloor(hit.pickedMesh) && hit.pickedPoint) {
  1143. // Moving the teleportation area to this targetted point
  1144. this._moveTeleportationSelectorTo(hit);
  1145. return;
  1146. }
  1147. // If not, we're in a selection scenario
  1148. this._hideTeleportationTarget();
  1149. this._teleportationAllowed = false;
  1150. if (hit.pickedMesh !== this._currentMeshSelected) {
  1151. if (this.meshSelectionPredicate(hit.pickedMesh)) {
  1152. this._currentMeshSelected = hit.pickedMesh;
  1153. if (hit.pickedMesh.isPickable && hit.pickedMesh.actionManager) {
  1154. this.changeGazeColor(new BABYLON.Color3(0, 0, 1));
  1155. this.changeLaserColor(new BABYLON.Color3(0.2, 0.2, 1));
  1156. this._isActionableMesh = true;
  1157. }
  1158. else {
  1159. this.changeGazeColor(new BABYLON.Color3(0.7, 0.7, 0.7));
  1160. this.changeLaserColor(new BABYLON.Color3(0.7, 0.7, 0.7));
  1161. this._isActionableMesh = false;
  1162. }
  1163. try {
  1164. this.onNewMeshSelected.notifyObservers(this._currentMeshSelected);
  1165. }
  1166. catch (err) {
  1167. Tools.Warn("Error in your custom logic onNewMeshSelected: " + err);
  1168. }
  1169. }
  1170. else {
  1171. this._currentMeshSelected = null;
  1172. this.changeGazeColor(new BABYLON.Color3(0.7, 0.7, 0.7));
  1173. this.changeLaserColor(new BABYLON.Color3(0.7, 0.7, 0.7));
  1174. }
  1175. }
  1176. }
  1177. else {
  1178. this._currentHit = null;
  1179. this._currentMeshSelected = null;
  1180. this._teleportationAllowed = false;
  1181. this._hideTeleportationTarget();
  1182. this.changeGazeColor(new BABYLON.Color3(0.7, 0.7, 0.7));
  1183. this.changeLaserColor(new BABYLON.Color3(0.7, 0.7, 0.7));
  1184. }
  1185. }
  1186. public changeLaserColor(color: Color3) {
  1187. if (this._leftLaserPointer && this._leftLaserPointer.material) {
  1188. (<StandardMaterial>this._leftLaserPointer.material).emissiveColor = color;
  1189. }
  1190. if (this._rightLaserPointer && this._rightLaserPointer.material) {
  1191. (<StandardMaterial>this._rightLaserPointer.material).emissiveColor = color;
  1192. }
  1193. }
  1194. public changeGazeColor(color: Color3) {
  1195. if (this._gazeTracker.material) {
  1196. (<StandardMaterial>this._gazeTracker.material).emissiveColor = color;
  1197. }
  1198. }
  1199. public dispose() {
  1200. if (this.isInVRMode) {
  1201. this.exitVR();
  1202. }
  1203. if (this._passProcessMove) {
  1204. this._passProcessMove.dispose();
  1205. }
  1206. if (this._postProcessMove) {
  1207. this._postProcessMove.dispose();
  1208. }
  1209. if (this._webVRCamera) {
  1210. this._webVRCamera.dispose();
  1211. }
  1212. if (this._vrDeviceOrientationCamera) {
  1213. this._vrDeviceOrientationCamera.dispose();
  1214. }
  1215. if (!this._useCustomVRButton && this._btnVR.parentNode) {
  1216. document.body.removeChild(this._btnVR);
  1217. }
  1218. if (this._deviceOrientationCamera && (this._scene.activeCamera != this._deviceOrientationCamera)) {
  1219. this._deviceOrientationCamera.dispose();
  1220. }
  1221. this._gazeTracker.dispose();
  1222. this._teleportationTarget.dispose();
  1223. this._floorMeshesCollection = [];
  1224. document.removeEventListener("keydown", this._onKeyDown);
  1225. window.removeEventListener('vrdisplaypresentchange', this._onVrDisplayPresentChange);
  1226. window.removeEventListener("resize", this._onResize);
  1227. document.removeEventListener("fullscreenchange", this._onFullscreenChange);
  1228. document.removeEventListener("mozfullscreenchange", this._onFullscreenChange);
  1229. document.removeEventListener("webkitfullscreenchange", this._onFullscreenChange);
  1230. document.removeEventListener("msfullscreenchange", this._onFullscreenChange);
  1231. this._scene.getEngine().onVRDisplayChangedObservable.removeCallback(this._onVRDisplayChanged);
  1232. this._scene.getEngine().onVRRequestPresentStart.removeCallback(this._onVRRequestPresentStart);
  1233. this._scene.getEngine().onVRRequestPresentComplete.removeCallback(this._onVRRequestPresentComplete);
  1234. window.removeEventListener('vrdisplaypresentchange', this._onVrDisplayPresentChange);
  1235. this._scene.gamepadManager.onGamepadConnectedObservable.removeCallback(this._onNewGamepadConnected);
  1236. this._scene.gamepadManager.onGamepadDisconnectedObservable.removeCallback(this._onNewGamepadDisconnected);
  1237. this._scene.unregisterBeforeRender(this.beforeRender);
  1238. }
  1239. public getClassName(): string {
  1240. return "VRExperienceHelper";
  1241. }
  1242. }
  1243. }