vrExperienceHelper.js 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306
  1. import { Logger } from "../../Misc/logger.js";
  2. import { Observable } from "../../Misc/observable.js";
  3. import { FreeCamera } from "../../Cameras/freeCamera.js";
  4. import { TargetCamera } from "../../Cameras/targetCamera.js";
  5. import { DeviceOrientationCamera } from "../../Cameras/deviceOrientationCamera.js";
  6. import { VRDeviceOrientationFreeCamera } from "../../Cameras/VR/vrDeviceOrientationFreeCamera.js";
  7. import { PointerEventTypes } from "../../Events/pointerEvents.js";
  8. import { Quaternion, Matrix, Vector3 } from "../../Maths/math.vector.js";
  9. import { Color3, Color4 } from "../../Maths/math.color.js";
  10. import { Gamepad } from "../../Gamepads/gamepad.js";
  11. import { Xbox360Button } from "../../Gamepads/xboxGamepad.js";
  12. import { Ray } from "../../Culling/ray.js";
  13. import { ImageProcessingConfiguration } from "../../Materials/imageProcessingConfiguration.js";
  14. import { StandardMaterial } from "../../Materials/standardMaterial.js";
  15. import { DynamicTexture } from "../../Materials/Textures/dynamicTexture.js";
  16. import { SineEase, EasingFunction, CircleEase } from "../../Animations/easing.js";
  17. import { Animation } from "../../Animations/animation.js";
  18. import "../../Gamepads/gamepadSceneComponent.js";
  19. import "../../Animations/animatable.js";
  20. import { WebXRSessionManager } from "../../XR/webXRSessionManager.js";
  21. import { WebXRState } from "../../XR/webXRTypes.js";
  22. import { CreateGround } from "../../Meshes/Builders/groundBuilder.js";
  23. import { CreateTorus } from "../../Meshes/Builders/torusBuilder.js";
  24. class VRExperienceHelperGazer {
  25. constructor(scene, gazeTrackerToClone = null) {
  26. this.scene = scene;
  27. /** @internal */
  28. this._pointerDownOnMeshAsked = false;
  29. /** @internal */
  30. this._isActionableMesh = false;
  31. /** @internal */
  32. this._teleportationRequestInitiated = false;
  33. /** @internal */
  34. this._teleportationBackRequestInitiated = false;
  35. /** @internal */
  36. this._rotationRightAsked = false;
  37. /** @internal */
  38. this._rotationLeftAsked = false;
  39. /** @internal */
  40. this._dpadPressed = true;
  41. /** @internal */
  42. this._activePointer = false;
  43. this._id = VRExperienceHelperGazer._IdCounter++;
  44. // Gaze tracker
  45. if (!gazeTrackerToClone) {
  46. this._gazeTracker = CreateTorus("gazeTracker", {
  47. diameter: 0.0035,
  48. thickness: 0.0025,
  49. tessellation: 20,
  50. updatable: false,
  51. }, scene);
  52. this._gazeTracker.bakeCurrentTransformIntoVertices();
  53. this._gazeTracker.isPickable = false;
  54. this._gazeTracker.isVisible = false;
  55. const targetMat = new StandardMaterial("targetMat", scene);
  56. targetMat.specularColor = Color3.Black();
  57. targetMat.emissiveColor = new Color3(0.7, 0.7, 0.7);
  58. targetMat.backFaceCulling = false;
  59. this._gazeTracker.material = targetMat;
  60. }
  61. else {
  62. this._gazeTracker = gazeTrackerToClone.clone("gazeTracker");
  63. }
  64. }
  65. /**
  66. * @internal
  67. */
  68. _getForwardRay(length) {
  69. return new Ray(Vector3.Zero(), new Vector3(0, 0, length));
  70. }
  71. /** @internal */
  72. _selectionPointerDown() {
  73. this._pointerDownOnMeshAsked = true;
  74. if (this._currentHit) {
  75. this.scene.simulatePointerDown(this._currentHit, { pointerId: this._id });
  76. }
  77. }
  78. /** @internal */
  79. _selectionPointerUp() {
  80. if (this._currentHit) {
  81. this.scene.simulatePointerUp(this._currentHit, { pointerId: this._id });
  82. }
  83. this._pointerDownOnMeshAsked = false;
  84. }
  85. /** @internal */
  86. _activatePointer() {
  87. this._activePointer = true;
  88. }
  89. /** @internal */
  90. _deactivatePointer() {
  91. this._activePointer = false;
  92. }
  93. /**
  94. * @internal
  95. */
  96. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  97. _updatePointerDistance(distance = 100) { }
  98. dispose() {
  99. this._interactionsEnabled = false;
  100. this._teleportationEnabled = false;
  101. if (this._gazeTracker) {
  102. this._gazeTracker.dispose();
  103. }
  104. }
  105. }
  106. VRExperienceHelperGazer._IdCounter = 0;
  107. class VRExperienceHelperCameraGazer extends VRExperienceHelperGazer {
  108. constructor(_getCamera, scene) {
  109. super(scene);
  110. this._getCamera = _getCamera;
  111. }
  112. _getForwardRay(length) {
  113. const camera = this._getCamera();
  114. if (camera) {
  115. return camera.getForwardRay(length);
  116. }
  117. else {
  118. return new Ray(Vector3.Zero(), Vector3.Forward());
  119. }
  120. }
  121. }
  122. /**
  123. * Event containing information after VR has been entered
  124. */
  125. export class OnAfterEnteringVRObservableEvent {
  126. }
  127. /**
  128. * Helps to quickly add VR support to an existing scene.
  129. * See https://doc.babylonjs.com/features/featuresDeepDive/cameras/webVRHelper
  130. * @deprecated Use WebXR instead!
  131. */
  132. export class VRExperienceHelper {
  133. /** Return this.onEnteringVRObservable
  134. * Note: This one is for backward compatibility. Please use onEnteringVRObservable directly
  135. */
  136. get onEnteringVR() {
  137. return this.onEnteringVRObservable;
  138. }
  139. /** Return this.onExitingVRObservable
  140. * Note: This one is for backward compatibility. Please use onExitingVRObservable directly
  141. */
  142. get onExitingVR() {
  143. return this.onExitingVRObservable;
  144. }
  145. /**
  146. * The mesh used to display where the user is going to teleport.
  147. */
  148. get teleportationTarget() {
  149. return this._teleportationTarget;
  150. }
  151. /**
  152. * Sets the mesh to be used to display where the user is going to teleport.
  153. */
  154. set teleportationTarget(value) {
  155. if (value) {
  156. value.name = "teleportationTarget";
  157. this._isDefaultTeleportationTarget = false;
  158. this._teleportationTarget = value;
  159. }
  160. }
  161. /**
  162. * The mesh used to display where the user is selecting, this mesh will be cloned and set as the gazeTracker for the left and right controller
  163. * when set bakeCurrentTransformIntoVertices will be called on the mesh.
  164. * See https://doc.babylonjs.com/features/featuresDeepDive/mesh/transforms/center_origin/bakingTransforms
  165. */
  166. get gazeTrackerMesh() {
  167. return this._cameraGazer._gazeTracker;
  168. }
  169. set gazeTrackerMesh(value) {
  170. if (value) {
  171. // Dispose of existing meshes
  172. if (this._cameraGazer._gazeTracker) {
  173. this._cameraGazer._gazeTracker.dispose();
  174. }
  175. // Set and create gaze trackers on head and controllers
  176. this._cameraGazer._gazeTracker = value;
  177. this._cameraGazer._gazeTracker.bakeCurrentTransformIntoVertices();
  178. this._cameraGazer._gazeTracker.isPickable = false;
  179. this._cameraGazer._gazeTracker.isVisible = false;
  180. this._cameraGazer._gazeTracker.name = "gazeTracker";
  181. }
  182. }
  183. /**
  184. * If the ray of the gaze should be displayed.
  185. */
  186. get displayGaze() {
  187. return this._displayGaze;
  188. }
  189. /**
  190. * Sets if the ray of the gaze should be displayed.
  191. */
  192. set displayGaze(value) {
  193. this._displayGaze = value;
  194. if (!value) {
  195. this._cameraGazer._gazeTracker.isVisible = false;
  196. }
  197. }
  198. /**
  199. * If the ray of the LaserPointer should be displayed.
  200. */
  201. get displayLaserPointer() {
  202. return this._displayLaserPointer;
  203. }
  204. /**
  205. * Sets if the ray of the LaserPointer should be displayed.
  206. */
  207. set displayLaserPointer(value) {
  208. this._displayLaserPointer = value;
  209. }
  210. /**
  211. * The deviceOrientationCamera used as the camera when not in VR.
  212. */
  213. get deviceOrientationCamera() {
  214. return this._deviceOrientationCamera;
  215. }
  216. /**
  217. * Based on the current WebVR support, returns the current VR camera used.
  218. */
  219. get currentVRCamera() {
  220. return this._scene.activeCamera;
  221. }
  222. /**
  223. * The deviceOrientationCamera that is used as a fallback when vr device is not connected.
  224. */
  225. get vrDeviceOrientationCamera() {
  226. return this._vrDeviceOrientationCamera;
  227. }
  228. /**
  229. * The html button that is used to trigger entering into VR.
  230. */
  231. get vrButton() {
  232. return this._btnVR;
  233. }
  234. get _teleportationRequestInitiated() {
  235. return this._cameraGazer._teleportationRequestInitiated;
  236. }
  237. /**
  238. * Instantiates a VRExperienceHelper.
  239. * Helps to quickly add VR support to an existing scene.
  240. * @param scene The scene the VRExperienceHelper belongs to.
  241. * @param webVROptions Options to modify the vr experience helper's behavior.
  242. */
  243. constructor(scene,
  244. /** Options to modify the vr experience helper's behavior. */
  245. webVROptions = {}) {
  246. this.webVROptions = webVROptions;
  247. // Are we presenting in the fullscreen fallback?
  248. this._fullscreenVRpresenting = false;
  249. /**
  250. * Gets or sets a boolean indicating that gaze can be enabled even if pointer lock is not engage (useful on iOS where fullscreen mode and pointer lock are not supported)
  251. */
  252. this.enableGazeEvenWhenNoPointerLock = false;
  253. /**
  254. * Gets or sets a boolean indicating that the VREXperienceHelper will exit VR if double tap is detected
  255. */
  256. this.exitVROnDoubleTap = true;
  257. /**
  258. * Observable raised right before entering VR.
  259. */
  260. this.onEnteringVRObservable = new Observable();
  261. /**
  262. * Observable raised when entering VR has completed.
  263. */
  264. this.onAfterEnteringVRObservable = new Observable();
  265. /**
  266. * Observable raised when exiting VR.
  267. */
  268. this.onExitingVRObservable = new Observable();
  269. this._useCustomVRButton = false;
  270. this._teleportActive = false;
  271. this._floorMeshesCollection = [];
  272. this._teleportationMode = VRExperienceHelper.TELEPORTATIONMODE_CONSTANTTIME;
  273. this._teleportationTime = 122;
  274. this._teleportationSpeed = 20;
  275. this._rotationAllowed = true;
  276. this._teleportBackwardsVector = new Vector3(0, -1, -1);
  277. this._isDefaultTeleportationTarget = true;
  278. this._teleportationFillColor = "#444444";
  279. this._teleportationBorderColor = "#FFFFFF";
  280. this._rotationAngle = 0;
  281. this._haloCenter = new Vector3(0, 0, 0);
  282. this._padSensibilityUp = 0.65;
  283. this._padSensibilityDown = 0.35;
  284. this._pickedLaserColor = new Color3(0.2, 0.2, 1);
  285. this._pickedGazeColor = new Color3(0, 0, 1);
  286. /**
  287. * Observable raised when a new mesh is selected based on meshSelectionPredicate
  288. */
  289. this.onNewMeshSelected = new Observable();
  290. /**
  291. * Observable raised when a new mesh is picked based on meshSelectionPredicate
  292. */
  293. this.onNewMeshPicked = new Observable();
  294. /**
  295. * Observable raised before camera teleportation
  296. */
  297. this.onBeforeCameraTeleport = new Observable();
  298. /**
  299. * Observable raised after camera teleportation
  300. */
  301. this.onAfterCameraTeleport = new Observable();
  302. /**
  303. * Observable raised when current selected mesh gets unselected
  304. */
  305. this.onSelectedMeshUnselected = new Observable();
  306. /**
  307. * Set teleportation enabled. If set to false camera teleportation will be disabled but camera rotation will be kept.
  308. */
  309. this.teleportationEnabled = true;
  310. this._teleportationInitialized = false;
  311. this._interactionsEnabled = false;
  312. this._displayGaze = true;
  313. this._displayLaserPointer = true;
  314. /**
  315. * If the gaze trackers scale should be updated to be constant size when pointing at near/far meshes
  316. */
  317. this.updateGazeTrackerScale = true;
  318. /**
  319. * If the gaze trackers color should be updated when selecting meshes
  320. */
  321. this.updateGazeTrackerColor = true;
  322. /**
  323. * If the controller laser color should be updated when selecting meshes
  324. */
  325. this.updateControllerLaserColor = true;
  326. /**
  327. * Defines whether or not Pointer lock should be requested when switching to
  328. * full screen.
  329. */
  330. this.requestPointerLockOnFullScreen = true;
  331. /**
  332. * Was the XR test done already. If this is true AND this.xr exists, xr is initialized.
  333. * If this is true and no this.xr, xr exists but is not supported, using WebVR.
  334. */
  335. this.xrTestDone = false;
  336. this._onResize = () => {
  337. this._moveButtonToBottomRight();
  338. };
  339. this._onFullscreenChange = () => {
  340. this._fullscreenVRpresenting = !!document.fullscreenElement;
  341. if (!this._fullscreenVRpresenting && this._inputElement) {
  342. this.exitVR();
  343. if (!this._useCustomVRButton && this._btnVR) {
  344. this._btnVR.style.top = this._inputElement.offsetTop + this._inputElement.offsetHeight - 70 + "px";
  345. this._btnVR.style.left = this._inputElement.offsetLeft + this._inputElement.offsetWidth - 100 + "px";
  346. // make sure the button is visible after setting its position
  347. this._updateButtonVisibility();
  348. }
  349. }
  350. };
  351. this._cachedAngularSensibility = { angularSensibilityX: null, angularSensibilityY: null, angularSensibility: null };
  352. this._beforeRender = () => {
  353. if (this._scene.getEngine().isPointerLock || this.enableGazeEvenWhenNoPointerLock) {
  354. // no-op
  355. }
  356. else {
  357. this._cameraGazer._gazeTracker.isVisible = false;
  358. }
  359. };
  360. this._onNewGamepadConnected = (gamepad) => {
  361. if (gamepad.type !== Gamepad.POSE_ENABLED) {
  362. if (gamepad.leftStick) {
  363. gamepad.onleftstickchanged((stickValues) => {
  364. if (this._teleportationInitialized && this.teleportationEnabled) {
  365. // Listening to classic/xbox gamepad only if no VR controller is active
  366. this._checkTeleportWithRay(stickValues, this._cameraGazer);
  367. this._checkTeleportBackwards(stickValues, this._cameraGazer);
  368. }
  369. });
  370. }
  371. if (gamepad.rightStick) {
  372. gamepad.onrightstickchanged((stickValues) => {
  373. if (this._teleportationInitialized) {
  374. this._checkRotate(stickValues, this._cameraGazer);
  375. }
  376. });
  377. }
  378. if (gamepad.type === Gamepad.XBOX) {
  379. gamepad.onbuttondown((buttonPressed) => {
  380. if (this._interactionsEnabled && buttonPressed === Xbox360Button.A) {
  381. this._cameraGazer._selectionPointerDown();
  382. }
  383. });
  384. gamepad.onbuttonup((buttonPressed) => {
  385. if (this._interactionsEnabled && buttonPressed === Xbox360Button.A) {
  386. this._cameraGazer._selectionPointerUp();
  387. }
  388. });
  389. }
  390. }
  391. };
  392. this._workingVector = Vector3.Zero();
  393. this._workingQuaternion = Quaternion.Identity();
  394. this._workingMatrix = Matrix.Identity();
  395. Logger.Warn("WebVR is deprecated. Please avoid using this experience helper and use the WebXR experience helper instead");
  396. this._scene = scene;
  397. this._inputElement = scene.getEngine().getInputElement();
  398. // check for VR support:
  399. const vrSupported = "getVRDisplays" in navigator;
  400. // no VR support? force XR but only when it is not set because web vr can work without the getVRDisplays
  401. if (!vrSupported && webVROptions.useXR === undefined) {
  402. webVROptions.useXR = true;
  403. }
  404. // Parse options
  405. if (webVROptions.createFallbackVRDeviceOrientationFreeCamera === undefined) {
  406. webVROptions.createFallbackVRDeviceOrientationFreeCamera = true;
  407. }
  408. if (webVROptions.createDeviceOrientationCamera === undefined) {
  409. webVROptions.createDeviceOrientationCamera = true;
  410. }
  411. if (webVROptions.laserToggle === undefined) {
  412. webVROptions.laserToggle = true;
  413. }
  414. this._hasEnteredVR = false;
  415. // Set position
  416. if (this._scene.activeCamera) {
  417. this._position = this._scene.activeCamera.position.clone();
  418. }
  419. else {
  420. this._position = new Vector3(0, this._defaultHeight, 0);
  421. }
  422. // Set non-vr camera
  423. if (webVROptions.createDeviceOrientationCamera || !this._scene.activeCamera) {
  424. this._deviceOrientationCamera = new DeviceOrientationCamera("deviceOrientationVRHelper", this._position.clone(), scene);
  425. // Copy data from existing camera
  426. if (this._scene.activeCamera) {
  427. this._deviceOrientationCamera.minZ = this._scene.activeCamera.minZ;
  428. this._deviceOrientationCamera.maxZ = this._scene.activeCamera.maxZ;
  429. // Set rotation from previous camera
  430. if (this._scene.activeCamera instanceof TargetCamera && this._scene.activeCamera.rotation) {
  431. const targetCamera = this._scene.activeCamera;
  432. if (targetCamera.rotationQuaternion) {
  433. this._deviceOrientationCamera.rotationQuaternion.copyFrom(targetCamera.rotationQuaternion);
  434. }
  435. else {
  436. this._deviceOrientationCamera.rotationQuaternion.copyFrom(Quaternion.RotationYawPitchRoll(targetCamera.rotation.y, targetCamera.rotation.x, targetCamera.rotation.z));
  437. }
  438. this._deviceOrientationCamera.rotation = targetCamera.rotation.clone();
  439. }
  440. }
  441. this._scene.activeCamera = this._deviceOrientationCamera;
  442. if (this._inputElement) {
  443. this._scene.activeCamera.attachControl();
  444. }
  445. }
  446. else {
  447. this._existingCamera = this._scene.activeCamera;
  448. }
  449. if (this.webVROptions.useXR && navigator.xr) {
  450. // force-check XR session support
  451. WebXRSessionManager.IsSessionSupportedAsync("immersive-vr").then((supported) => {
  452. if (supported) {
  453. Logger.Log("Using WebXR. It is recommended to use the WebXRDefaultExperience directly");
  454. // it is possible to use XR, let's do it!
  455. scene
  456. .createDefaultXRExperienceAsync({
  457. floorMeshes: webVROptions.floorMeshes || [],
  458. })
  459. .then((xr) => {
  460. this.xr = xr;
  461. // connect observables
  462. this.xrTestDone = true;
  463. this._cameraGazer = new VRExperienceHelperCameraGazer(() => {
  464. return this.xr.baseExperience.camera;
  465. }, scene);
  466. this.xr.baseExperience.onStateChangedObservable.add((state) => {
  467. // support for entering / exiting
  468. switch (state) {
  469. case WebXRState.ENTERING_XR:
  470. this.onEnteringVRObservable.notifyObservers(this);
  471. if (!this._interactionsEnabled) {
  472. this.xr.pointerSelection.detach();
  473. }
  474. this.xr.pointerSelection.displayLaserPointer = this._displayLaserPointer;
  475. break;
  476. case WebXRState.EXITING_XR:
  477. this.onExitingVRObservable.notifyObservers(this);
  478. // resize to update width and height when exiting vr exits fullscreen
  479. this._scene.getEngine().resize();
  480. break;
  481. case WebXRState.IN_XR:
  482. this._hasEnteredVR = true;
  483. break;
  484. case WebXRState.NOT_IN_XR:
  485. this._hasEnteredVR = false;
  486. break;
  487. }
  488. });
  489. });
  490. }
  491. else {
  492. // XR not supported (thou exists), continue WebVR init
  493. this._completeVRInit(scene, webVROptions);
  494. }
  495. });
  496. }
  497. else {
  498. // no XR, continue init synchronous
  499. this._completeVRInit(scene, webVROptions);
  500. }
  501. }
  502. _completeVRInit(scene, webVROptions) {
  503. this.xrTestDone = true;
  504. // Create VR cameras
  505. if (webVROptions.createFallbackVRDeviceOrientationFreeCamera) {
  506. this._vrDeviceOrientationCamera = new VRDeviceOrientationFreeCamera("VRDeviceOrientationVRHelper", this._position, this._scene, true, webVROptions.vrDeviceOrientationCameraMetrics);
  507. this._vrDeviceOrientationCamera.angularSensibility = Number.MAX_VALUE;
  508. }
  509. this._cameraGazer = new VRExperienceHelperCameraGazer(() => {
  510. return this.currentVRCamera;
  511. }, scene);
  512. // Create default button
  513. if (!this._useCustomVRButton) {
  514. this._btnVR = document.createElement("BUTTON");
  515. this._btnVR.className = "babylonVRicon";
  516. this._btnVR.id = "babylonVRiconbtn";
  517. this._btnVR.title = "Click to switch to VR";
  518. const url = !window.SVGSVGElement
  519. ? "https://cdn.babylonjs.com/Assets/vrButton.png"
  520. : "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";
  521. let css = ".babylonVRicon { position: absolute; right: 20px; height: 50px; width: 80px; background-color: rgba(51,51,51,0.7); background-image: url(" +
  522. url +
  523. "); 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) }";
  524. css += ".babylonVRicon.vrdisplaypresenting { display: none; }";
  525. // TODO: Add user feedback so that they know what state the VRDisplay is in (disconnected, connected, entering-VR)
  526. // css += ".babylonVRicon.vrdisplaysupported { }";
  527. // css += ".babylonVRicon.vrdisplayready { }";
  528. // css += ".babylonVRicon.vrdisplayrequesting { }";
  529. const style = document.createElement("style");
  530. style.appendChild(document.createTextNode(css));
  531. document.getElementsByTagName("head")[0].appendChild(style);
  532. this._moveButtonToBottomRight();
  533. }
  534. // VR button click event
  535. if (this._btnVR) {
  536. this._btnVR.addEventListener("click", () => {
  537. if (!this.isInVRMode) {
  538. this.enterVR();
  539. }
  540. });
  541. }
  542. // Window events
  543. const hostWindow = this._scene.getEngine().getHostWindow();
  544. if (!hostWindow) {
  545. return;
  546. }
  547. hostWindow.addEventListener("resize", this._onResize);
  548. document.addEventListener("fullscreenchange", this._onFullscreenChange, false);
  549. // Display vr button when headset is connected
  550. if (webVROptions.createFallbackVRDeviceOrientationFreeCamera) {
  551. this._displayVRButton();
  552. }
  553. // Exiting VR mode using 'ESC' key on desktop
  554. this._onKeyDown = (event) => {
  555. if (event.keyCode === 27 && this.isInVRMode) {
  556. this.exitVR();
  557. }
  558. };
  559. document.addEventListener("keydown", this._onKeyDown);
  560. // Exiting VR mode double tapping the touch screen
  561. this._scene.onPrePointerObservable.add(() => {
  562. if (this._hasEnteredVR && this.exitVROnDoubleTap) {
  563. this.exitVR();
  564. if (this._fullscreenVRpresenting) {
  565. this._scene.getEngine().exitFullscreen();
  566. }
  567. }
  568. }, PointerEventTypes.POINTERDOUBLETAP, false);
  569. scene.onDisposeObservable.add(() => {
  570. this.dispose();
  571. });
  572. this._updateButtonVisibility();
  573. //create easing functions
  574. this._circleEase = new CircleEase();
  575. this._circleEase.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT);
  576. this._teleportationEasing = this._circleEase;
  577. // Allow clicking in the vrDeviceOrientationCamera
  578. scene.onPointerObservable.add((e) => {
  579. if (this._interactionsEnabled) {
  580. if (scene.activeCamera === this.vrDeviceOrientationCamera && e.event.pointerType === "mouse") {
  581. if (e.type === PointerEventTypes.POINTERDOWN) {
  582. this._cameraGazer._selectionPointerDown();
  583. }
  584. else if (e.type === PointerEventTypes.POINTERUP) {
  585. this._cameraGazer._selectionPointerUp();
  586. }
  587. }
  588. }
  589. });
  590. if (this.webVROptions.floorMeshes) {
  591. this.enableTeleportation({ floorMeshes: this.webVROptions.floorMeshes });
  592. }
  593. }
  594. /**
  595. * Gets a value indicating if we are currently in VR mode.
  596. */
  597. get isInVRMode() {
  598. return (this.xr && this.webVROptions.useXR && this.xr.baseExperience.state === WebXRState.IN_XR) || this._fullscreenVRpresenting;
  599. }
  600. _moveButtonToBottomRight() {
  601. if (this._inputElement && !this._useCustomVRButton && this._btnVR) {
  602. const rect = this._inputElement.getBoundingClientRect();
  603. this._btnVR.style.top = rect.top + rect.height - 70 + "px";
  604. this._btnVR.style.left = rect.left + rect.width - 100 + "px";
  605. }
  606. }
  607. _displayVRButton() {
  608. if (!this._useCustomVRButton && !this._btnVRDisplayed && this._btnVR) {
  609. document.body.appendChild(this._btnVR);
  610. this._btnVRDisplayed = true;
  611. }
  612. }
  613. _updateButtonVisibility() {
  614. if (!this._btnVR || this._useCustomVRButton) {
  615. return;
  616. }
  617. this._btnVR.className = "babylonVRicon";
  618. if (this.isInVRMode) {
  619. this._btnVR.className += " vrdisplaypresenting";
  620. }
  621. }
  622. /**
  623. * Attempt to enter VR. If a headset is connected and ready, will request present on that.
  624. * Otherwise, will use the fullscreen API.
  625. */
  626. enterVR() {
  627. if (this.xr) {
  628. this.xr.baseExperience.enterXRAsync("immersive-vr", "local-floor", this.xr.renderTarget);
  629. return;
  630. }
  631. if (this.onEnteringVRObservable) {
  632. try {
  633. this.onEnteringVRObservable.notifyObservers(this);
  634. }
  635. catch (err) {
  636. Logger.Warn("Error in your custom logic onEnteringVR: " + err);
  637. }
  638. }
  639. if (this._scene.activeCamera) {
  640. this._position = this._scene.activeCamera.position.clone();
  641. if (this.vrDeviceOrientationCamera) {
  642. this.vrDeviceOrientationCamera.rotation = Quaternion.FromRotationMatrix(this._scene.activeCamera.getWorldMatrix().getRotationMatrix()).toEulerAngles();
  643. this.vrDeviceOrientationCamera.angularSensibility = 2000;
  644. }
  645. // make sure that we return to the last active camera
  646. this._existingCamera = this._scene.activeCamera;
  647. // Remove and cache angular sensability to avoid camera rotation when in VR
  648. if (this._existingCamera.angularSensibilityX) {
  649. this._cachedAngularSensibility.angularSensibilityX = this._existingCamera.angularSensibilityX;
  650. this._existingCamera.angularSensibilityX = Number.MAX_VALUE;
  651. }
  652. if (this._existingCamera.angularSensibilityY) {
  653. this._cachedAngularSensibility.angularSensibilityY = this._existingCamera.angularSensibilityY;
  654. this._existingCamera.angularSensibilityY = Number.MAX_VALUE;
  655. }
  656. if (this._existingCamera.angularSensibility) {
  657. this._cachedAngularSensibility.angularSensibility = this._existingCamera.angularSensibility;
  658. this._existingCamera.angularSensibility = Number.MAX_VALUE;
  659. }
  660. }
  661. // If WebVR is supported and a headset is connected
  662. if (this._vrDeviceOrientationCamera) {
  663. this._vrDeviceOrientationCamera.position = this._position;
  664. if (this._scene.activeCamera) {
  665. this._vrDeviceOrientationCamera.minZ = this._scene.activeCamera.minZ;
  666. }
  667. this._scene.activeCamera = this._vrDeviceOrientationCamera;
  668. this._scene.getEngine().enterFullscreen(this.requestPointerLockOnFullScreen);
  669. this._updateButtonVisibility();
  670. this._vrDeviceOrientationCamera.onViewMatrixChangedObservable.addOnce(() => {
  671. this.onAfterEnteringVRObservable.notifyObservers({ success: true });
  672. });
  673. }
  674. if (this._scene.activeCamera && this._inputElement) {
  675. this._scene.activeCamera.attachControl();
  676. }
  677. if (this._interactionsEnabled) {
  678. this._scene.registerBeforeRender(this._beforeRender);
  679. }
  680. this._hasEnteredVR = true;
  681. }
  682. /**
  683. * Attempt to exit VR, or fullscreen.
  684. */
  685. exitVR() {
  686. if (this.xr) {
  687. this.xr.baseExperience.exitXRAsync();
  688. return;
  689. }
  690. if (this._hasEnteredVR) {
  691. if (this.onExitingVRObservable) {
  692. try {
  693. this.onExitingVRObservable.notifyObservers(this);
  694. }
  695. catch (err) {
  696. Logger.Warn("Error in your custom logic onExitingVR: " + err);
  697. }
  698. }
  699. if (this._scene.activeCamera) {
  700. this._position = this._scene.activeCamera.position.clone();
  701. }
  702. if (this.vrDeviceOrientationCamera) {
  703. this.vrDeviceOrientationCamera.angularSensibility = Number.MAX_VALUE;
  704. }
  705. if (this._deviceOrientationCamera) {
  706. this._deviceOrientationCamera.position = this._position;
  707. this._scene.activeCamera = this._deviceOrientationCamera;
  708. // Restore angular sensibility
  709. if (this._cachedAngularSensibility.angularSensibilityX) {
  710. this._deviceOrientationCamera.angularSensibilityX = this._cachedAngularSensibility.angularSensibilityX;
  711. this._cachedAngularSensibility.angularSensibilityX = null;
  712. }
  713. if (this._cachedAngularSensibility.angularSensibilityY) {
  714. this._deviceOrientationCamera.angularSensibilityY = this._cachedAngularSensibility.angularSensibilityY;
  715. this._cachedAngularSensibility.angularSensibilityY = null;
  716. }
  717. if (this._cachedAngularSensibility.angularSensibility) {
  718. this._deviceOrientationCamera.angularSensibility = this._cachedAngularSensibility.angularSensibility;
  719. this._cachedAngularSensibility.angularSensibility = null;
  720. }
  721. }
  722. else if (this._existingCamera) {
  723. this._existingCamera.position = this._position;
  724. this._scene.activeCamera = this._existingCamera;
  725. if (this._inputElement) {
  726. this._scene.activeCamera.attachControl();
  727. }
  728. // Restore angular sensibility
  729. if (this._cachedAngularSensibility.angularSensibilityX) {
  730. this._existingCamera.angularSensibilityX = this._cachedAngularSensibility.angularSensibilityX;
  731. this._cachedAngularSensibility.angularSensibilityX = null;
  732. }
  733. if (this._cachedAngularSensibility.angularSensibilityY) {
  734. this._existingCamera.angularSensibilityY = this._cachedAngularSensibility.angularSensibilityY;
  735. this._cachedAngularSensibility.angularSensibilityY = null;
  736. }
  737. if (this._cachedAngularSensibility.angularSensibility) {
  738. this._existingCamera.angularSensibility = this._cachedAngularSensibility.angularSensibility;
  739. this._cachedAngularSensibility.angularSensibility = null;
  740. }
  741. }
  742. this._updateButtonVisibility();
  743. if (this._interactionsEnabled) {
  744. this._scene.unregisterBeforeRender(this._beforeRender);
  745. this._cameraGazer._gazeTracker.isVisible = false;
  746. }
  747. // resize to update width and height when exiting vr exits fullscreen
  748. this._scene.getEngine().resize();
  749. this._hasEnteredVR = false;
  750. }
  751. }
  752. /**
  753. * The position of the vr experience helper.
  754. */
  755. get position() {
  756. return this._position;
  757. }
  758. /**
  759. * Sets the position of the vr experience helper.
  760. */
  761. set position(value) {
  762. this._position = value;
  763. if (this._scene.activeCamera) {
  764. this._scene.activeCamera.position = value;
  765. }
  766. }
  767. /**
  768. * Enables controllers and user interactions such as selecting and object or clicking on an object.
  769. */
  770. enableInteractions() {
  771. if (!this._interactionsEnabled) {
  772. // in XR it is enabled by default, but just to make sure, re-attach
  773. if (this.xr) {
  774. if (this.xr.baseExperience.state === WebXRState.IN_XR) {
  775. this.xr.pointerSelection.attach();
  776. }
  777. return;
  778. }
  779. this.raySelectionPredicate = (mesh) => {
  780. return mesh.isVisible && (mesh.isPickable || mesh.name === this._floorMeshName);
  781. };
  782. this.meshSelectionPredicate = () => {
  783. return true;
  784. };
  785. this._raySelectionPredicate = (mesh) => {
  786. if (this._isTeleportationFloor(mesh) ||
  787. (mesh.name.indexOf("gazeTracker") === -1 && mesh.name.indexOf("teleportationTarget") === -1 && mesh.name.indexOf("torusTeleportation") === -1)) {
  788. return this.raySelectionPredicate(mesh);
  789. }
  790. return false;
  791. };
  792. this._interactionsEnabled = true;
  793. }
  794. }
  795. _isTeleportationFloor(mesh) {
  796. for (let i = 0; i < this._floorMeshesCollection.length; i++) {
  797. if (this._floorMeshesCollection[i].id === mesh.id) {
  798. return true;
  799. }
  800. }
  801. if (this._floorMeshName && mesh.name === this._floorMeshName) {
  802. return true;
  803. }
  804. return false;
  805. }
  806. /**
  807. * Adds a floor mesh to be used for teleportation.
  808. * @param floorMesh the mesh to be used for teleportation.
  809. */
  810. addFloorMesh(floorMesh) {
  811. if (!this._floorMeshesCollection) {
  812. return;
  813. }
  814. if (this._floorMeshesCollection.indexOf(floorMesh) > -1) {
  815. return;
  816. }
  817. this._floorMeshesCollection.push(floorMesh);
  818. }
  819. /**
  820. * Removes a floor mesh from being used for teleportation.
  821. * @param floorMesh the mesh to be removed.
  822. */
  823. removeFloorMesh(floorMesh) {
  824. if (!this._floorMeshesCollection) {
  825. return;
  826. }
  827. const meshIndex = this._floorMeshesCollection.indexOf(floorMesh);
  828. if (meshIndex !== -1) {
  829. this._floorMeshesCollection.splice(meshIndex, 1);
  830. }
  831. }
  832. /**
  833. * Enables interactions and teleportation using the VR controllers and gaze.
  834. * @param vrTeleportationOptions options to modify teleportation behavior.
  835. */
  836. enableTeleportation(vrTeleportationOptions = {}) {
  837. if (!this._teleportationInitialized) {
  838. this.enableInteractions();
  839. if (this.webVROptions.useXR && (vrTeleportationOptions.floorMeshes || vrTeleportationOptions.floorMeshName)) {
  840. const floorMeshes = vrTeleportationOptions.floorMeshes || [];
  841. if (!floorMeshes.length) {
  842. const floorMesh = this._scene.getMeshByName(vrTeleportationOptions.floorMeshName);
  843. if (floorMesh) {
  844. floorMeshes.push(floorMesh);
  845. }
  846. }
  847. if (this.xr) {
  848. floorMeshes.forEach((mesh) => {
  849. this.xr.teleportation.addFloorMesh(mesh);
  850. });
  851. if (!this.xr.teleportation.attached) {
  852. this.xr.teleportation.attach();
  853. }
  854. return;
  855. }
  856. else if (!this.xrTestDone) {
  857. const waitForXr = () => {
  858. if (this.xrTestDone) {
  859. this._scene.unregisterBeforeRender(waitForXr);
  860. if (this.xr) {
  861. if (!this.xr.teleportation.attached) {
  862. this.xr.teleportation.attach();
  863. }
  864. }
  865. else {
  866. this.enableTeleportation(vrTeleportationOptions);
  867. }
  868. }
  869. };
  870. this._scene.registerBeforeRender(waitForXr);
  871. return;
  872. }
  873. }
  874. if (vrTeleportationOptions.floorMeshName) {
  875. this._floorMeshName = vrTeleportationOptions.floorMeshName;
  876. }
  877. if (vrTeleportationOptions.floorMeshes) {
  878. this._floorMeshesCollection = vrTeleportationOptions.floorMeshes;
  879. }
  880. if (vrTeleportationOptions.teleportationMode) {
  881. this._teleportationMode = vrTeleportationOptions.teleportationMode;
  882. }
  883. if (vrTeleportationOptions.teleportationTime && vrTeleportationOptions.teleportationTime > 0) {
  884. this._teleportationTime = vrTeleportationOptions.teleportationTime;
  885. }
  886. if (vrTeleportationOptions.teleportationSpeed && vrTeleportationOptions.teleportationSpeed > 0) {
  887. this._teleportationSpeed = vrTeleportationOptions.teleportationSpeed;
  888. }
  889. if (vrTeleportationOptions.easingFunction !== undefined) {
  890. this._teleportationEasing = vrTeleportationOptions.easingFunction;
  891. }
  892. // Creates an image processing post process for the vignette not relying
  893. // on the main scene configuration for image processing to reduce setup and spaces
  894. // (gamma/linear) conflicts.
  895. const imageProcessingConfiguration = new ImageProcessingConfiguration();
  896. imageProcessingConfiguration.vignetteColor = new Color4(0, 0, 0, 0);
  897. imageProcessingConfiguration.vignetteEnabled = true;
  898. this._teleportationInitialized = true;
  899. if (this._isDefaultTeleportationTarget) {
  900. this._createTeleportationCircles();
  901. }
  902. }
  903. }
  904. _checkTeleportWithRay(stateObject, gazer) {
  905. // Dont teleport if another gaze already requested teleportation
  906. if (this._teleportationRequestInitiated && !gazer._teleportationRequestInitiated) {
  907. return;
  908. }
  909. if (!gazer._teleportationRequestInitiated) {
  910. if (stateObject.y < -this._padSensibilityUp && gazer._dpadPressed) {
  911. gazer._activatePointer();
  912. gazer._teleportationRequestInitiated = true;
  913. }
  914. }
  915. else {
  916. // Listening to the proper controller values changes to confirm teleportation
  917. if (Math.sqrt(stateObject.y * stateObject.y + stateObject.x * stateObject.x) < this._padSensibilityDown) {
  918. if (this._teleportActive) {
  919. this.teleportCamera(this._haloCenter);
  920. }
  921. gazer._teleportationRequestInitiated = false;
  922. }
  923. }
  924. }
  925. _checkRotate(stateObject, gazer) {
  926. // Only rotate when user is not currently selecting a teleportation location
  927. if (gazer._teleportationRequestInitiated) {
  928. return;
  929. }
  930. if (!gazer._rotationLeftAsked) {
  931. if (stateObject.x < -this._padSensibilityUp && gazer._dpadPressed) {
  932. gazer._rotationLeftAsked = true;
  933. if (this._rotationAllowed) {
  934. this._rotateCamera(false);
  935. }
  936. }
  937. }
  938. else {
  939. if (stateObject.x > -this._padSensibilityDown) {
  940. gazer._rotationLeftAsked = false;
  941. }
  942. }
  943. if (!gazer._rotationRightAsked) {
  944. if (stateObject.x > this._padSensibilityUp && gazer._dpadPressed) {
  945. gazer._rotationRightAsked = true;
  946. if (this._rotationAllowed) {
  947. this._rotateCamera(true);
  948. }
  949. }
  950. }
  951. else {
  952. if (stateObject.x < this._padSensibilityDown) {
  953. gazer._rotationRightAsked = false;
  954. }
  955. }
  956. }
  957. _checkTeleportBackwards(stateObject, gazer) {
  958. // Only teleport backwards when user is not currently selecting a teleportation location
  959. if (gazer._teleportationRequestInitiated) {
  960. return;
  961. }
  962. // Teleport backwards
  963. if (stateObject.y > this._padSensibilityUp && gazer._dpadPressed) {
  964. if (!gazer._teleportationBackRequestInitiated) {
  965. if (!this.currentVRCamera) {
  966. return;
  967. }
  968. // Get rotation and position of the current camera
  969. const rotation = Quaternion.FromRotationMatrix(this.currentVRCamera.getWorldMatrix().getRotationMatrix());
  970. const position = this.currentVRCamera.position;
  971. // Get matrix with only the y rotation of the device rotation
  972. rotation.toEulerAnglesToRef(this._workingVector);
  973. this._workingVector.z = 0;
  974. this._workingVector.x = 0;
  975. Quaternion.RotationYawPitchRollToRef(this._workingVector.y, this._workingVector.x, this._workingVector.z, this._workingQuaternion);
  976. this._workingQuaternion.toRotationMatrix(this._workingMatrix);
  977. // Rotate backwards ray by device rotation to cast at the ground behind the user
  978. Vector3.TransformCoordinatesToRef(this._teleportBackwardsVector, this._workingMatrix, this._workingVector);
  979. // Teleport if ray hit the ground and is not to far away eg. backwards off a cliff
  980. const ray = new Ray(position, this._workingVector);
  981. const hit = this._scene.pickWithRay(ray, this._raySelectionPredicate);
  982. if (hit && hit.pickedPoint && hit.pickedMesh && this._isTeleportationFloor(hit.pickedMesh) && hit.distance < 5) {
  983. this.teleportCamera(hit.pickedPoint);
  984. }
  985. gazer._teleportationBackRequestInitiated = true;
  986. }
  987. }
  988. else {
  989. gazer._teleportationBackRequestInitiated = false;
  990. }
  991. }
  992. _createTeleportationCircles() {
  993. this._teleportationTarget = CreateGround("teleportationTarget", { width: 2, height: 2, subdivisions: 2 }, this._scene);
  994. this._teleportationTarget.isPickable = false;
  995. const length = 512;
  996. const dynamicTexture = new DynamicTexture("DynamicTexture", length, this._scene, true);
  997. dynamicTexture.hasAlpha = true;
  998. const context = dynamicTexture.getContext();
  999. const centerX = length / 2;
  1000. const centerY = length / 2;
  1001. const radius = 200;
  1002. context.beginPath();
  1003. context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
  1004. context.fillStyle = this._teleportationFillColor;
  1005. context.fill();
  1006. context.lineWidth = 10;
  1007. context.strokeStyle = this._teleportationBorderColor;
  1008. context.stroke();
  1009. context.closePath();
  1010. dynamicTexture.update();
  1011. const teleportationCircleMaterial = new StandardMaterial("TextPlaneMaterial", this._scene);
  1012. teleportationCircleMaterial.diffuseTexture = dynamicTexture;
  1013. this._teleportationTarget.material = teleportationCircleMaterial;
  1014. const torus = CreateTorus("torusTeleportation", {
  1015. diameter: 0.75,
  1016. thickness: 0.1,
  1017. tessellation: 25,
  1018. updatable: false,
  1019. }, this._scene);
  1020. torus.isPickable = false;
  1021. torus.parent = this._teleportationTarget;
  1022. const animationInnerCircle = new Animation("animationInnerCircle", "position.y", 30, Animation.ANIMATIONTYPE_FLOAT, Animation.ANIMATIONLOOPMODE_CYCLE);
  1023. const keys = [];
  1024. keys.push({
  1025. frame: 0,
  1026. value: 0,
  1027. });
  1028. keys.push({
  1029. frame: 30,
  1030. value: 0.4,
  1031. });
  1032. keys.push({
  1033. frame: 60,
  1034. value: 0,
  1035. });
  1036. animationInnerCircle.setKeys(keys);
  1037. const easingFunction = new SineEase();
  1038. easingFunction.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT);
  1039. animationInnerCircle.setEasingFunction(easingFunction);
  1040. torus.animations = [];
  1041. torus.animations.push(animationInnerCircle);
  1042. this._scene.beginAnimation(torus, 0, 60, true);
  1043. this._hideTeleportationTarget();
  1044. }
  1045. _hideTeleportationTarget() {
  1046. this._teleportActive = false;
  1047. if (this._teleportationInitialized) {
  1048. this._teleportationTarget.isVisible = false;
  1049. if (this._isDefaultTeleportationTarget) {
  1050. this._teleportationTarget.getChildren()[0].isVisible = false;
  1051. }
  1052. }
  1053. }
  1054. _rotateCamera(right) {
  1055. if (!(this.currentVRCamera instanceof FreeCamera)) {
  1056. return;
  1057. }
  1058. if (right) {
  1059. this._rotationAngle++;
  1060. }
  1061. else {
  1062. this._rotationAngle--;
  1063. }
  1064. this.currentVRCamera.animations = [];
  1065. const target = Quaternion.FromRotationMatrix(Matrix.RotationY((Math.PI / 4) * this._rotationAngle));
  1066. const animationRotation = new Animation("animationRotation", "rotationQuaternion", 90, Animation.ANIMATIONTYPE_QUATERNION, Animation.ANIMATIONLOOPMODE_CONSTANT);
  1067. const animationRotationKeys = [];
  1068. animationRotationKeys.push({
  1069. frame: 0,
  1070. value: this.currentVRCamera.rotationQuaternion,
  1071. });
  1072. animationRotationKeys.push({
  1073. frame: 6,
  1074. value: target,
  1075. });
  1076. animationRotation.setKeys(animationRotationKeys);
  1077. animationRotation.setEasingFunction(this._circleEase);
  1078. this.currentVRCamera.animations.push(animationRotation);
  1079. this._postProcessMove.animations = [];
  1080. const animationPP = new Animation("animationPP", "vignetteWeight", 90, Animation.ANIMATIONTYPE_FLOAT, Animation.ANIMATIONLOOPMODE_CONSTANT);
  1081. const vignetteWeightKeys = [];
  1082. vignetteWeightKeys.push({
  1083. frame: 0,
  1084. value: 0,
  1085. });
  1086. vignetteWeightKeys.push({
  1087. frame: 3,
  1088. value: 4,
  1089. });
  1090. vignetteWeightKeys.push({
  1091. frame: 6,
  1092. value: 0,
  1093. });
  1094. animationPP.setKeys(vignetteWeightKeys);
  1095. animationPP.setEasingFunction(this._circleEase);
  1096. this._postProcessMove.animations.push(animationPP);
  1097. const animationPP2 = new Animation("animationPP2", "vignetteStretch", 90, Animation.ANIMATIONTYPE_FLOAT, Animation.ANIMATIONLOOPMODE_CONSTANT);
  1098. const vignetteStretchKeys = [];
  1099. vignetteStretchKeys.push({
  1100. frame: 0,
  1101. value: 0,
  1102. });
  1103. vignetteStretchKeys.push({
  1104. frame: 3,
  1105. value: 10,
  1106. });
  1107. vignetteStretchKeys.push({
  1108. frame: 6,
  1109. value: 0,
  1110. });
  1111. animationPP2.setKeys(vignetteStretchKeys);
  1112. animationPP2.setEasingFunction(this._circleEase);
  1113. this._postProcessMove.animations.push(animationPP2);
  1114. this._postProcessMove.imageProcessingConfiguration.vignetteWeight = 0;
  1115. this._postProcessMove.imageProcessingConfiguration.vignetteStretch = 0;
  1116. this._postProcessMove.samples = 4;
  1117. this._scene.beginAnimation(this.currentVRCamera, 0, 6, false, 1);
  1118. }
  1119. /**
  1120. * Teleports the users feet to the desired location
  1121. * @param location The location where the user's feet should be placed
  1122. */
  1123. teleportCamera(location) {
  1124. if (!(this.currentVRCamera instanceof FreeCamera)) {
  1125. return;
  1126. }
  1127. // Teleport the hmd to where the user is looking by moving the anchor to where they are looking minus the
  1128. // offset of the headset from the anchor.
  1129. this._workingVector.copyFrom(location);
  1130. // Add height to account for user's height offset
  1131. if (this.isInVRMode) {
  1132. // no-op
  1133. }
  1134. else {
  1135. this._workingVector.y += this._defaultHeight;
  1136. }
  1137. this.onBeforeCameraTeleport.notifyObservers(this._workingVector);
  1138. // Animations FPS
  1139. const FPS = 90;
  1140. let speedRatio, lastFrame;
  1141. if (this._teleportationMode == VRExperienceHelper.TELEPORTATIONMODE_CONSTANTSPEED) {
  1142. lastFrame = FPS;
  1143. const dist = Vector3.Distance(this.currentVRCamera.position, this._workingVector);
  1144. speedRatio = this._teleportationSpeed / dist;
  1145. }
  1146. else {
  1147. // teleportationMode is TELEPORTATIONMODE_CONSTANTTIME
  1148. lastFrame = Math.round((this._teleportationTime * FPS) / 1000);
  1149. speedRatio = 1;
  1150. }
  1151. // Create animation from the camera's position to the new location
  1152. this.currentVRCamera.animations = [];
  1153. const animationCameraTeleportation = new Animation("animationCameraTeleportation", "position", FPS, Animation.ANIMATIONTYPE_VECTOR3, Animation.ANIMATIONLOOPMODE_CONSTANT);
  1154. const animationCameraTeleportationKeys = [
  1155. {
  1156. frame: 0,
  1157. value: this.currentVRCamera.position,
  1158. },
  1159. {
  1160. frame: lastFrame,
  1161. value: this._workingVector,
  1162. },
  1163. ];
  1164. animationCameraTeleportation.setKeys(animationCameraTeleportationKeys);
  1165. animationCameraTeleportation.setEasingFunction(this._teleportationEasing);
  1166. this.currentVRCamera.animations.push(animationCameraTeleportation);
  1167. this._postProcessMove.animations = [];
  1168. // Calculate the mid frame for vignette animations
  1169. const midFrame = Math.round(lastFrame / 2);
  1170. const animationPP = new Animation("animationPP", "vignetteWeight", FPS, Animation.ANIMATIONTYPE_FLOAT, Animation.ANIMATIONLOOPMODE_CONSTANT);
  1171. const vignetteWeightKeys = [];
  1172. vignetteWeightKeys.push({
  1173. frame: 0,
  1174. value: 0,
  1175. });
  1176. vignetteWeightKeys.push({
  1177. frame: midFrame,
  1178. value: 8,
  1179. });
  1180. vignetteWeightKeys.push({
  1181. frame: lastFrame,
  1182. value: 0,
  1183. });
  1184. animationPP.setKeys(vignetteWeightKeys);
  1185. this._postProcessMove.animations.push(animationPP);
  1186. const animationPP2 = new Animation("animationPP2", "vignetteStretch", FPS, Animation.ANIMATIONTYPE_FLOAT, Animation.ANIMATIONLOOPMODE_CONSTANT);
  1187. const vignetteStretchKeys = [];
  1188. vignetteStretchKeys.push({
  1189. frame: 0,
  1190. value: 0,
  1191. });
  1192. vignetteStretchKeys.push({
  1193. frame: midFrame,
  1194. value: 10,
  1195. });
  1196. vignetteStretchKeys.push({
  1197. frame: lastFrame,
  1198. value: 0,
  1199. });
  1200. animationPP2.setKeys(vignetteStretchKeys);
  1201. this._postProcessMove.animations.push(animationPP2);
  1202. this._postProcessMove.imageProcessingConfiguration.vignetteWeight = 0;
  1203. this._postProcessMove.imageProcessingConfiguration.vignetteStretch = 0;
  1204. this._scene.beginAnimation(this.currentVRCamera, 0, lastFrame, false, speedRatio, () => {
  1205. this.onAfterCameraTeleport.notifyObservers(this._workingVector);
  1206. });
  1207. this._hideTeleportationTarget();
  1208. }
  1209. /**
  1210. * Permanently set new colors for the laser pointer
  1211. * @param color the new laser color
  1212. * @param pickedColor the new laser color when picked mesh detected
  1213. */
  1214. setLaserColor(color, pickedColor = this._pickedLaserColor) {
  1215. this._pickedLaserColor = pickedColor;
  1216. }
  1217. /**
  1218. * Set lighting enabled / disabled on the laser pointer of both controllers
  1219. * @param _enabled should the lighting be enabled on the laser pointer
  1220. */
  1221. setLaserLightingState(_enabled = true) {
  1222. // no-op
  1223. }
  1224. /**
  1225. * Permanently set new colors for the gaze pointer
  1226. * @param color the new gaze color
  1227. * @param pickedColor the new gaze color when picked mesh detected
  1228. */
  1229. setGazeColor(color, pickedColor = this._pickedGazeColor) {
  1230. this._pickedGazeColor = pickedColor;
  1231. }
  1232. /**
  1233. * Sets the color of the laser ray from the vr controllers.
  1234. * @param _color new color for the ray.
  1235. */
  1236. changeLaserColor(_color) {
  1237. if (!this.updateControllerLaserColor) {
  1238. return;
  1239. }
  1240. }
  1241. /**
  1242. * Sets the color of the ray from the vr headsets gaze.
  1243. * @param color new color for the ray.
  1244. */
  1245. changeGazeColor(color) {
  1246. if (!this.updateGazeTrackerColor) {
  1247. return;
  1248. }
  1249. if (!this._cameraGazer._gazeTracker.material) {
  1250. return;
  1251. }
  1252. this._cameraGazer._gazeTracker.material.emissiveColor = color;
  1253. }
  1254. /**
  1255. * Exits VR and disposes of the vr experience helper
  1256. */
  1257. dispose() {
  1258. if (this.isInVRMode) {
  1259. this.exitVR();
  1260. }
  1261. if (this._postProcessMove) {
  1262. this._postProcessMove.dispose();
  1263. }
  1264. if (this._vrDeviceOrientationCamera) {
  1265. this._vrDeviceOrientationCamera.dispose();
  1266. }
  1267. if (!this._useCustomVRButton && this._btnVR && this._btnVR.parentNode) {
  1268. document.body.removeChild(this._btnVR);
  1269. }
  1270. if (this._deviceOrientationCamera && this._scene.activeCamera != this._deviceOrientationCamera) {
  1271. this._deviceOrientationCamera.dispose();
  1272. }
  1273. if (this._cameraGazer) {
  1274. this._cameraGazer.dispose();
  1275. }
  1276. if (this._teleportationTarget) {
  1277. this._teleportationTarget.dispose();
  1278. }
  1279. if (this.xr) {
  1280. this.xr.dispose();
  1281. }
  1282. this._floorMeshesCollection.length = 0;
  1283. document.removeEventListener("keydown", this._onKeyDown);
  1284. window.removeEventListener("vrdisplaypresentchange", this._onVrDisplayPresentChangeBind);
  1285. window.removeEventListener("resize", this._onResize);
  1286. document.removeEventListener("fullscreenchange", this._onFullscreenChange);
  1287. this._scene.gamepadManager.onGamepadConnectedObservable.removeCallback(this._onNewGamepadConnected);
  1288. this._scene.unregisterBeforeRender(this._beforeRender);
  1289. }
  1290. /**
  1291. * Gets the name of the VRExperienceHelper class
  1292. * @returns "VRExperienceHelper"
  1293. */
  1294. getClassName() {
  1295. return "VRExperienceHelper";
  1296. }
  1297. }
  1298. /**
  1299. * Time Constant Teleportation Mode
  1300. */
  1301. VRExperienceHelper.TELEPORTATIONMODE_CONSTANTTIME = 0;
  1302. /**
  1303. * Speed Constant Teleportation Mode
  1304. */
  1305. VRExperienceHelper.TELEPORTATIONMODE_CONSTANTSPEED = 1;
  1306. //# sourceMappingURL=vrExperienceHelper.js.map