vrExperienceHelper.ts 97 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371
  1. import { Logger } from "../../Misc/logger";
  2. import { Observer, Observable } from "../../Misc/observable";
  3. import { Nullable } from "../../types";
  4. import { Camera } from "../../Cameras/camera";
  5. import { FreeCamera } from "../../Cameras/freeCamera";
  6. import { TargetCamera } from "../../Cameras/targetCamera";
  7. import { DeviceOrientationCamera } from "../../Cameras/deviceOrientationCamera";
  8. import { VRDeviceOrientationFreeCamera } from "../../Cameras/VR/vrDeviceOrientationFreeCamera";
  9. import { WebVROptions, WebVRFreeCamera } from "../../Cameras/VR/webVRCamera";
  10. import { PointerEventTypes } from "../../Events/pointerEvents";
  11. import { Scene, IDisposable } from "../../scene";
  12. import { Quaternion, Matrix, Vector3 } from "../../Maths/math.vector";
  13. import { Color3, Color4 } from '../../Maths/math.color';
  14. import { Gamepad, StickValues } from "../../Gamepads/gamepad";
  15. import { PoseEnabledController, PoseEnabledControllerType } from "../../Gamepads/Controllers/poseEnabledController";
  16. import { WebVRController } from "../../Gamepads/Controllers/webVRController";
  17. import { Xbox360Pad, Xbox360Button } from "../../Gamepads/xboxGamepad";
  18. import { IDisplayChangedEventArgs } from "../../Engines/engine";
  19. import { AbstractMesh } from "../../Meshes/abstractMesh";
  20. import { TransformNode } from "../../Meshes/transformNode";
  21. import { Mesh } from "../../Meshes/mesh";
  22. import { PickingInfo } from "../../Collisions/pickingInfo";
  23. import { Ray } from "../../Culling/ray";
  24. import { ImageProcessingConfiguration } from "../../Materials/imageProcessingConfiguration";
  25. import { StandardMaterial } from "../../Materials/standardMaterial";
  26. import { DynamicTexture } from "../../Materials/Textures/dynamicTexture";
  27. import { ImageProcessingPostProcess } from "../../PostProcesses/imageProcessingPostProcess";
  28. import { SineEase, EasingFunction, CircleEase } from "../../Animations/easing";
  29. import { Animation } from "../../Animations/animation";
  30. import { VRCameraMetrics } from '../../Cameras/VR/vrCameraMetrics';
  31. import "../../Meshes/Builders/groundBuilder";
  32. import "../../Meshes/Builders/torusBuilder";
  33. import "../../Meshes/Builders/cylinderBuilder";
  34. import "../../Gamepads/gamepadSceneComponent";
  35. import "../../Animations/animatable";
  36. import { Axis } from '../../Maths/math.axis';
  37. import { WebXRSessionManager } from '../XR/webXRSessionManager';
  38. import { WebXRDefaultExperience } from '../XR/webXRDefaultExperience';
  39. import { WebXRState } from '../XR/webXRTypes';
  40. /**
  41. * Options to modify the vr teleportation behavior.
  42. */
  43. export interface VRTeleportationOptions {
  44. /**
  45. * The name of the mesh which should be used as the teleportation floor. (default: null)
  46. */
  47. floorMeshName?: string;
  48. /**
  49. * A list of meshes to be used as the teleportation floor. (default: empty)
  50. */
  51. floorMeshes?: Mesh[];
  52. /**
  53. * The teleportation mode. (default: TELEPORTATIONMODE_CONSTANTTIME)
  54. */
  55. teleportationMode?: number;
  56. /**
  57. * The duration of the animation in ms, apply when animationMode is TELEPORTATIONMODE_CONSTANTTIME. (default 122ms)
  58. */
  59. teleportationTime?: number;
  60. /**
  61. * The speed of the animation in distance/sec, apply when animationMode is TELEPORTATIONMODE_CONSTANTSPEED. (default 20 units / sec)
  62. */
  63. teleportationSpeed?: number;
  64. /**
  65. * The easing function used in the animation or null for Linear. (default CircleEase)
  66. */
  67. easingFunction?: EasingFunction;
  68. }
  69. /**
  70. * Options to modify the vr experience helper's behavior.
  71. */
  72. export interface VRExperienceHelperOptions extends WebVROptions {
  73. /**
  74. * Create a DeviceOrientationCamera to be used as your out of vr camera. (default: true)
  75. */
  76. createDeviceOrientationCamera?: boolean;
  77. /**
  78. * Create a VRDeviceOrientationFreeCamera to be used for VR when no external HMD is found. (default: true)
  79. */
  80. createFallbackVRDeviceOrientationFreeCamera?: boolean;
  81. /**
  82. * Uses the main button on the controller to toggle the laser casted. (default: true)
  83. */
  84. laserToggle?: boolean;
  85. /**
  86. * A list of meshes to be used as the teleportation floor. If specified, teleportation will be enabled (default: undefined)
  87. */
  88. floorMeshes?: Mesh[];
  89. /**
  90. * Distortion metrics for the fallback vrDeviceOrientationCamera (default: VRCameraMetrics.Default)
  91. */
  92. vrDeviceOrientationCameraMetrics?: VRCameraMetrics;
  93. /**
  94. * Defines if WebXR should be used instead of WebVR (if available)
  95. */
  96. useXR?: boolean;
  97. }
  98. class VRExperienceHelperGazer implements IDisposable {
  99. /** @hidden */
  100. public _gazeTracker: Mesh;
  101. /** @hidden */
  102. public _currentMeshSelected: Nullable<AbstractMesh>;
  103. /** @hidden */
  104. public _currentHit: Nullable<PickingInfo>;
  105. public static _idCounter = 0;
  106. /** @hidden */
  107. public _id: number;
  108. /** @hidden */
  109. public _pointerDownOnMeshAsked: boolean = false;
  110. /** @hidden */
  111. public _isActionableMesh: boolean = false;
  112. /** @hidden */
  113. public _interactionsEnabled: boolean;
  114. /** @hidden */
  115. public _teleportationEnabled: boolean;
  116. /** @hidden */
  117. public _teleportationRequestInitiated = false;
  118. /** @hidden */
  119. public _teleportationBackRequestInitiated = false;
  120. /** @hidden */
  121. public _rotationRightAsked = false;
  122. /** @hidden */
  123. public _rotationLeftAsked = false;
  124. /** @hidden */
  125. public _dpadPressed = true;
  126. /** @hidden */
  127. public _activePointer = false;
  128. constructor(public scene: Scene, gazeTrackerToClone: Nullable<Mesh> = null) {
  129. this._id = VRExperienceHelperGazer._idCounter++;
  130. // Gaze tracker
  131. if (!gazeTrackerToClone) {
  132. this._gazeTracker = Mesh.CreateTorus("gazeTracker", 0.0035, 0.0025, 20, scene, false);
  133. this._gazeTracker.bakeCurrentTransformIntoVertices();
  134. this._gazeTracker.isPickable = false;
  135. this._gazeTracker.isVisible = false;
  136. var targetMat = new StandardMaterial("targetMat", scene);
  137. targetMat.specularColor = Color3.Black();
  138. targetMat.emissiveColor = new Color3(0.7, 0.7, 0.7);
  139. targetMat.backFaceCulling = false;
  140. this._gazeTracker.material = targetMat;
  141. } else {
  142. this._gazeTracker = gazeTrackerToClone.clone("gazeTracker") as Mesh;
  143. }
  144. }
  145. /** @hidden */
  146. public _getForwardRay(length: number): Ray {
  147. return new Ray(Vector3.Zero(), new Vector3(0, 0, length));
  148. }
  149. /** @hidden */
  150. public _selectionPointerDown() {
  151. this._pointerDownOnMeshAsked = true;
  152. if (this._currentHit) {
  153. this.scene.simulatePointerDown(this._currentHit, { pointerId: this._id });
  154. }
  155. }
  156. /** @hidden */
  157. public _selectionPointerUp() {
  158. if (this._currentHit) {
  159. this.scene.simulatePointerUp(this._currentHit, { pointerId: this._id });
  160. }
  161. this._pointerDownOnMeshAsked = false;
  162. }
  163. /** @hidden */
  164. public _activatePointer() {
  165. this._activePointer = true;
  166. }
  167. /** @hidden */
  168. public _deactivatePointer() {
  169. this._activePointer = false;
  170. }
  171. /** @hidden */
  172. public _updatePointerDistance(distance: number = 100) {
  173. }
  174. public dispose() {
  175. this._interactionsEnabled = false;
  176. this._teleportationEnabled = false;
  177. if (this._gazeTracker) {
  178. this._gazeTracker.dispose();
  179. }
  180. }
  181. }
  182. class VRExperienceHelperControllerGazer extends VRExperienceHelperGazer {
  183. private _laserPointer: Mesh;
  184. private _meshAttachedObserver: Nullable<Observer<AbstractMesh>>;
  185. constructor(public webVRController: WebVRController, scene: Scene, gazeTrackerToClone: Mesh) {
  186. super(scene, gazeTrackerToClone);
  187. // Laser pointer
  188. this._laserPointer = Mesh.CreateCylinder("laserPointer", 1, 0.004, 0.0002, 20, 1, scene, false);
  189. var laserPointerMaterial = new StandardMaterial("laserPointerMat", scene);
  190. laserPointerMaterial.emissiveColor = new Color3(0.7, 0.7, 0.7);
  191. laserPointerMaterial.alpha = 0.6;
  192. this._laserPointer.material = laserPointerMaterial;
  193. this._laserPointer.rotation.x = Math.PI / 2;
  194. this._laserPointer.position.z = -0.5;
  195. this._laserPointer.isVisible = false;
  196. this._laserPointer.isPickable = false;
  197. if (!webVRController.mesh) {
  198. // Create an empty mesh that is used prior to loading the high quality model
  199. var preloadMesh = new Mesh("preloadControllerMesh", scene);
  200. var preloadPointerPose = new Mesh(PoseEnabledController.POINTING_POSE, scene);
  201. preloadPointerPose.rotation.x = -0.7;
  202. preloadMesh.addChild(preloadPointerPose);
  203. webVRController.attachToMesh(preloadMesh);
  204. }
  205. this._setLaserPointerParent(webVRController.mesh!);
  206. this._meshAttachedObserver = webVRController._meshAttachedObservable.add((mesh) => {
  207. this._setLaserPointerParent(mesh);
  208. });
  209. }
  210. _getForwardRay(length: number): Ray {
  211. return this.webVRController.getForwardRay(length);
  212. }
  213. /** @hidden */
  214. public _activatePointer() {
  215. super._activatePointer();
  216. this._laserPointer.isVisible = true;
  217. }
  218. /** @hidden */
  219. public _deactivatePointer() {
  220. super._deactivatePointer();
  221. this._laserPointer.isVisible = false;
  222. }
  223. /** @hidden */
  224. public _setLaserPointerColor(color: Color3) {
  225. (<StandardMaterial>this._laserPointer.material).emissiveColor = color;
  226. }
  227. /** @hidden */
  228. public _setLaserPointerLightingDisabled(disabled: boolean) {
  229. (<StandardMaterial>this._laserPointer.material).disableLighting = disabled;
  230. }
  231. /** @hidden */
  232. public _setLaserPointerParent(mesh: AbstractMesh) {
  233. var makeNotPick = (root: AbstractMesh) => {
  234. root.isPickable = false;
  235. root.getChildMeshes().forEach((c) => {
  236. makeNotPick(c);
  237. });
  238. };
  239. makeNotPick(mesh);
  240. var meshChildren = mesh.getChildren(undefined, false);
  241. let laserParent: TransformNode = mesh;
  242. this.webVRController._pointingPoseNode = null;
  243. for (var i = 0; i < meshChildren.length; i++) {
  244. if (meshChildren[i].name && meshChildren[i].name.indexOf(PoseEnabledController.POINTING_POSE) >= 0) {
  245. laserParent = <TransformNode>meshChildren[i];
  246. this.webVRController._pointingPoseNode = laserParent;
  247. break;
  248. }
  249. }
  250. this._laserPointer.parent = laserParent;
  251. }
  252. public _updatePointerDistance(distance: number = 100) {
  253. this._laserPointer.scaling.y = distance;
  254. this._laserPointer.position.z = -distance / 2;
  255. }
  256. dispose() {
  257. super.dispose();
  258. this._laserPointer.dispose();
  259. if (this._meshAttachedObserver) {
  260. this.webVRController._meshAttachedObservable.remove(this._meshAttachedObserver);
  261. }
  262. }
  263. }
  264. class VRExperienceHelperCameraGazer extends VRExperienceHelperGazer {
  265. constructor(private getCamera: () => Nullable<Camera>, scene: Scene) {
  266. super(scene);
  267. }
  268. _getForwardRay(length: number): Ray {
  269. var camera = this.getCamera();
  270. if (camera) {
  271. return camera.getForwardRay(length);
  272. } else {
  273. return new Ray(Vector3.Zero(), Vector3.Forward());
  274. }
  275. }
  276. }
  277. /**
  278. * Event containing information after VR has been entered
  279. */
  280. export class OnAfterEnteringVRObservableEvent {
  281. /**
  282. * If entering vr was successful
  283. */
  284. public success: boolean;
  285. }
  286. /**
  287. * Helps to quickly add VR support to an existing scene.
  288. * See http://doc.babylonjs.com/how_to/webvr_helper
  289. */
  290. export class VRExperienceHelper {
  291. private _scene: Scene;
  292. private _position: Vector3;
  293. private _btnVR: Nullable<HTMLButtonElement>;
  294. private _btnVRDisplayed: boolean;
  295. // Can the system support WebVR, even if a headset isn't plugged in?
  296. private _webVRsupported = false;
  297. // If WebVR is supported, is a headset plugged in and are we ready to present?
  298. private _webVRready = false;
  299. // Are we waiting for the requestPresent callback to complete?
  300. private _webVRrequesting = false;
  301. // Are we presenting to the headset right now? (this is the vrDevice state)
  302. private _webVRpresenting = false;
  303. // Have we entered VR? (this is the VRExperienceHelper state)
  304. private _hasEnteredVR: boolean;
  305. // Are we presenting in the fullscreen fallback?
  306. private _fullscreenVRpresenting = false;
  307. private _inputElement: Nullable<HTMLElement>;
  308. private _webVRCamera: WebVRFreeCamera;
  309. private _vrDeviceOrientationCamera: Nullable<VRDeviceOrientationFreeCamera>;
  310. private _deviceOrientationCamera: Nullable<DeviceOrientationCamera>;
  311. private _existingCamera: Camera;
  312. private _onKeyDown: (event: KeyboardEvent) => void;
  313. private _onVrDisplayPresentChange: any;
  314. private _onVRDisplayChanged: (eventArgs: IDisplayChangedEventArgs) => void;
  315. private _onVRRequestPresentStart: () => void;
  316. private _onVRRequestPresentComplete: (success: boolean) => void;
  317. /**
  318. * 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)
  319. */
  320. public enableGazeEvenWhenNoPointerLock = false;
  321. /**
  322. * Gets or sets a boolean indicating that the VREXperienceHelper will exit VR if double tap is detected
  323. */
  324. public exitVROnDoubleTap = true;
  325. /**
  326. * Observable raised right before entering VR.
  327. */
  328. public onEnteringVRObservable = new Observable<VRExperienceHelper>();
  329. /**
  330. * Observable raised when entering VR has completed.
  331. */
  332. public onAfterEnteringVRObservable = new Observable<OnAfterEnteringVRObservableEvent>();
  333. /**
  334. * Observable raised when exiting VR.
  335. */
  336. public onExitingVRObservable = new Observable<VRExperienceHelper>();
  337. /**
  338. * Observable raised when controller mesh is loaded.
  339. */
  340. public onControllerMeshLoadedObservable = new Observable<WebVRController>();
  341. /** Return this.onEnteringVRObservable
  342. * Note: This one is for backward compatibility. Please use onEnteringVRObservable directly
  343. */
  344. public get onEnteringVR(): Observable<VRExperienceHelper> {
  345. return this.onEnteringVRObservable;
  346. }
  347. /** Return this.onExitingVRObservable
  348. * Note: This one is for backward compatibility. Please use onExitingVRObservable directly
  349. */
  350. public get onExitingVR(): Observable<VRExperienceHelper> {
  351. return this.onExitingVRObservable;
  352. }
  353. /** Return this.onControllerMeshLoadedObservable
  354. * Note: This one is for backward compatibility. Please use onControllerMeshLoadedObservable directly
  355. */
  356. public get onControllerMeshLoaded(): Observable<WebVRController> {
  357. return this.onControllerMeshLoadedObservable;
  358. }
  359. private _rayLength: number;
  360. private _useCustomVRButton: boolean = false;
  361. private _teleportationRequested: boolean = false;
  362. private _teleportActive = false;
  363. private _floorMeshName: string;
  364. private _floorMeshesCollection: Mesh[] = [];
  365. private _teleportationMode: number = VRExperienceHelper.TELEPORTATIONMODE_CONSTANTTIME;
  366. private _teleportationTime: number = 122;
  367. private _teleportationSpeed: number = 20;
  368. private _teleportationEasing: EasingFunction;
  369. private _rotationAllowed: boolean = true;
  370. private _teleportBackwardsVector = new Vector3(0, -1, -1);
  371. private _teleportationTarget: Mesh;
  372. private _isDefaultTeleportationTarget = true;
  373. private _postProcessMove: ImageProcessingPostProcess;
  374. private _teleportationFillColor: string = "#444444";
  375. private _teleportationBorderColor: string = "#FFFFFF";
  376. private _rotationAngle: number = 0;
  377. private _haloCenter = new Vector3(0, 0, 0);
  378. private _cameraGazer: VRExperienceHelperCameraGazer;
  379. private _padSensibilityUp = 0.65;
  380. private _padSensibilityDown = 0.35;
  381. private _leftController: Nullable<VRExperienceHelperControllerGazer> = null;
  382. private _rightController: Nullable<VRExperienceHelperControllerGazer> = null;
  383. private _gazeColor: Color3 = new Color3(0.7, 0.7, 0.7);
  384. private _laserColor: Color3 = new Color3(0.7, 0.7, 0.7);
  385. private _pickedLaserColor: Color3 = new Color3(0.2, 0.2, 1);
  386. private _pickedGazeColor: Color3 = new Color3(0, 0, 1);
  387. /**
  388. * Observable raised when a new mesh is selected based on meshSelectionPredicate
  389. */
  390. public onNewMeshSelected = new Observable<AbstractMesh>();
  391. /**
  392. * Observable raised when a new mesh is selected based on meshSelectionPredicate.
  393. * This observable will provide the mesh and the controller used to select the mesh
  394. */
  395. public onMeshSelectedWithController = new Observable<{ mesh: AbstractMesh, controller: WebVRController }>();
  396. /**
  397. * Observable raised when a new mesh is picked based on meshSelectionPredicate
  398. */
  399. public onNewMeshPicked = new Observable<PickingInfo>();
  400. private _circleEase: CircleEase;
  401. /**
  402. * Observable raised before camera teleportation
  403. */
  404. public onBeforeCameraTeleport = new Observable<Vector3>();
  405. /**
  406. * Observable raised after camera teleportation
  407. */
  408. public onAfterCameraTeleport = new Observable<Vector3>();
  409. /**
  410. * Observable raised when current selected mesh gets unselected
  411. */
  412. public onSelectedMeshUnselected = new Observable<AbstractMesh>();
  413. private _raySelectionPredicate: (mesh: AbstractMesh) => boolean;
  414. /**
  415. * To be optionaly changed by user to define custom ray selection
  416. */
  417. public raySelectionPredicate: (mesh: AbstractMesh) => boolean;
  418. /**
  419. * To be optionaly changed by user to define custom selection logic (after ray selection)
  420. */
  421. public meshSelectionPredicate: (mesh: AbstractMesh) => boolean;
  422. /**
  423. * Set teleportation enabled. If set to false camera teleportation will be disabled but camera rotation will be kept.
  424. */
  425. public teleportationEnabled: boolean = true;
  426. private _defaultHeight: number;
  427. private _teleportationInitialized = false;
  428. private _interactionsEnabled = false;
  429. private _interactionsRequested = false;
  430. private _displayGaze = true;
  431. private _displayLaserPointer = true;
  432. /**
  433. * The mesh used to display where the user is going to teleport.
  434. */
  435. public get teleportationTarget(): Mesh {
  436. return this._teleportationTarget;
  437. }
  438. /**
  439. * Sets the mesh to be used to display where the user is going to teleport.
  440. */
  441. public set teleportationTarget(value: Mesh) {
  442. if (value) {
  443. value.name = "teleportationTarget";
  444. this._isDefaultTeleportationTarget = false;
  445. this._teleportationTarget = value;
  446. }
  447. }
  448. /**
  449. * 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
  450. * when set bakeCurrentTransformIntoVertices will be called on the mesh.
  451. * See http://doc.babylonjs.com/resources/baking_transformations
  452. */
  453. public get gazeTrackerMesh(): Mesh {
  454. return this._cameraGazer._gazeTracker;
  455. }
  456. public set gazeTrackerMesh(value: Mesh) {
  457. if (value) {
  458. // Dispose of existing meshes
  459. if (this._cameraGazer._gazeTracker) {
  460. this._cameraGazer._gazeTracker.dispose();
  461. }
  462. if (this._leftController && this._leftController._gazeTracker) {
  463. this._leftController._gazeTracker.dispose();
  464. }
  465. if (this._rightController && this._rightController._gazeTracker) {
  466. this._rightController._gazeTracker.dispose();
  467. }
  468. // Set and create gaze trackers on head and controllers
  469. this._cameraGazer._gazeTracker = value;
  470. this._cameraGazer._gazeTracker.bakeCurrentTransformIntoVertices();
  471. this._cameraGazer._gazeTracker.isPickable = false;
  472. this._cameraGazer._gazeTracker.isVisible = false;
  473. this._cameraGazer._gazeTracker.name = "gazeTracker";
  474. if (this._leftController) {
  475. this._leftController._gazeTracker = this._cameraGazer._gazeTracker.clone("gazeTracker") as Mesh;
  476. }
  477. if (this._rightController) {
  478. this._rightController._gazeTracker = this._cameraGazer._gazeTracker.clone("gazeTracker") as Mesh;
  479. }
  480. }
  481. }
  482. /**
  483. * If the gaze trackers scale should be updated to be constant size when pointing at near/far meshes
  484. */
  485. public updateGazeTrackerScale = true;
  486. /**
  487. * If the gaze trackers color should be updated when selecting meshes
  488. */
  489. public updateGazeTrackerColor = true;
  490. /**
  491. * If the controller laser color should be updated when selecting meshes
  492. */
  493. public updateControllerLaserColor = true;
  494. /**
  495. * The gaze tracking mesh corresponding to the left controller
  496. */
  497. public get leftControllerGazeTrackerMesh(): Nullable<Mesh> {
  498. if (this._leftController) {
  499. return this._leftController._gazeTracker;
  500. }
  501. return null;
  502. }
  503. /**
  504. * The gaze tracking mesh corresponding to the right controller
  505. */
  506. public get rightControllerGazeTrackerMesh(): Nullable<Mesh> {
  507. if (this._rightController) {
  508. return this._rightController._gazeTracker;
  509. }
  510. return null;
  511. }
  512. /**
  513. * If the ray of the gaze should be displayed.
  514. */
  515. public get displayGaze(): boolean {
  516. return this._displayGaze;
  517. }
  518. /**
  519. * Sets if the ray of the gaze should be displayed.
  520. */
  521. public set displayGaze(value: boolean) {
  522. this._displayGaze = value;
  523. if (!value) {
  524. this._cameraGazer._gazeTracker.isVisible = false;
  525. if (this._leftController) {
  526. this._leftController._gazeTracker.isVisible = false;
  527. }
  528. if (this._rightController) {
  529. this._rightController._gazeTracker.isVisible = false;
  530. }
  531. }
  532. }
  533. /**
  534. * If the ray of the LaserPointer should be displayed.
  535. */
  536. public get displayLaserPointer(): boolean {
  537. return this._displayLaserPointer;
  538. }
  539. /**
  540. * Sets if the ray of the LaserPointer should be displayed.
  541. */
  542. public set displayLaserPointer(value: boolean) {
  543. this._displayLaserPointer = value;
  544. if (!value) {
  545. if (this._rightController) {
  546. this._rightController._deactivatePointer();
  547. this._rightController._gazeTracker.isVisible = false;
  548. }
  549. if (this._leftController) {
  550. this._leftController._deactivatePointer();
  551. this._leftController._gazeTracker.isVisible = false;
  552. }
  553. }
  554. else {
  555. if (this._rightController) {
  556. this._rightController._activatePointer();
  557. }
  558. if (this._leftController) {
  559. this._leftController._activatePointer();
  560. }
  561. }
  562. }
  563. /**
  564. * The deviceOrientationCamera used as the camera when not in VR.
  565. */
  566. public get deviceOrientationCamera(): Nullable<DeviceOrientationCamera> {
  567. return this._deviceOrientationCamera;
  568. }
  569. /**
  570. * Based on the current WebVR support, returns the current VR camera used.
  571. */
  572. public get currentVRCamera(): Nullable<Camera> {
  573. if (this._webVRready) {
  574. return this._webVRCamera;
  575. }
  576. else {
  577. return this._scene.activeCamera;
  578. }
  579. }
  580. /**
  581. * The webVRCamera which is used when in VR.
  582. */
  583. public get webVRCamera(): WebVRFreeCamera {
  584. return this._webVRCamera;
  585. }
  586. /**
  587. * The deviceOrientationCamera that is used as a fallback when vr device is not connected.
  588. */
  589. public get vrDeviceOrientationCamera(): Nullable<VRDeviceOrientationFreeCamera> {
  590. return this._vrDeviceOrientationCamera;
  591. }
  592. /**
  593. * The html button that is used to trigger entering into VR.
  594. */
  595. public get vrButton(): Nullable<HTMLButtonElement> {
  596. return this._btnVR;
  597. }
  598. private get _teleportationRequestInitiated(): boolean {
  599. var result = this._cameraGazer._teleportationRequestInitiated
  600. || (this._leftController !== null && this._leftController._teleportationRequestInitiated)
  601. || (this._rightController !== null && this._rightController._teleportationRequestInitiated);
  602. return result;
  603. }
  604. /**
  605. * Defines wether or not Pointer lock should be requested when switching to
  606. * full screen.
  607. */
  608. public requestPointerLockOnFullScreen = true;
  609. // XR
  610. /**
  611. * If asking to force XR, this will be populated with the default xr experience
  612. */
  613. public xr: WebXRDefaultExperience;
  614. /**
  615. * Was the XR test done already. If this is true AND this.xr exists, xr is initialized.
  616. * If this is true and no this.xr, xr exists but is not supported, using WebVR.
  617. */
  618. public xrTestDone: boolean = false;
  619. /**
  620. * Instantiates a VRExperienceHelper.
  621. * Helps to quickly add VR support to an existing scene.
  622. * @param scene The scene the VRExperienceHelper belongs to.
  623. * @param webVROptions Options to modify the vr experience helper's behavior.
  624. */
  625. constructor(scene: Scene,
  626. /** Options to modify the vr experience helper's behavior. */
  627. public webVROptions: VRExperienceHelperOptions = {}) {
  628. this._scene = scene;
  629. this._inputElement = scene.getEngine().getInputElement();
  630. // check for VR support:
  631. const vrSupported = 'getVRDisplays' in navigator;
  632. // no VR support? force XR
  633. if (!vrSupported) {
  634. webVROptions.useXR = true;
  635. }
  636. // Parse options
  637. if (webVROptions.createFallbackVRDeviceOrientationFreeCamera === undefined) {
  638. webVROptions.createFallbackVRDeviceOrientationFreeCamera = true;
  639. }
  640. if (webVROptions.createDeviceOrientationCamera === undefined) {
  641. webVROptions.createDeviceOrientationCamera = true;
  642. }
  643. if (webVROptions.laserToggle === undefined) {
  644. webVROptions.laserToggle = true;
  645. }
  646. if (webVROptions.defaultHeight === undefined) {
  647. webVROptions.defaultHeight = 1.7;
  648. }
  649. if (webVROptions.useCustomVRButton) {
  650. this._useCustomVRButton = true;
  651. if (webVROptions.customVRButton) {
  652. this._btnVR = webVROptions.customVRButton;
  653. }
  654. }
  655. if (webVROptions.rayLength) {
  656. this._rayLength = webVROptions.rayLength;
  657. }
  658. this._defaultHeight = webVROptions.defaultHeight;
  659. if (webVROptions.positionScale) {
  660. this._rayLength *= webVROptions.positionScale;
  661. this._defaultHeight *= webVROptions.positionScale;
  662. }
  663. this._hasEnteredVR = false;
  664. // Set position
  665. if (this._scene.activeCamera) {
  666. this._position = this._scene.activeCamera.position.clone();
  667. } else {
  668. this._position = new Vector3(0, this._defaultHeight, 0);
  669. }
  670. // Set non-vr camera
  671. if (webVROptions.createDeviceOrientationCamera || !this._scene.activeCamera) {
  672. this._deviceOrientationCamera = new DeviceOrientationCamera("deviceOrientationVRHelper", this._position.clone(), scene);
  673. // Copy data from existing camera
  674. if (this._scene.activeCamera) {
  675. this._deviceOrientationCamera.minZ = this._scene.activeCamera.minZ;
  676. this._deviceOrientationCamera.maxZ = this._scene.activeCamera.maxZ;
  677. // Set rotation from previous camera
  678. if (this._scene.activeCamera instanceof TargetCamera && this._scene.activeCamera.rotation) {
  679. var targetCamera = this._scene.activeCamera;
  680. if (targetCamera.rotationQuaternion) {
  681. this._deviceOrientationCamera.rotationQuaternion.copyFrom(targetCamera.rotationQuaternion);
  682. } else {
  683. this._deviceOrientationCamera.rotationQuaternion.copyFrom(Quaternion.RotationYawPitchRoll(targetCamera.rotation.y, targetCamera.rotation.x, targetCamera.rotation.z));
  684. }
  685. this._deviceOrientationCamera.rotation = targetCamera.rotation.clone();
  686. }
  687. }
  688. this._scene.activeCamera = this._deviceOrientationCamera;
  689. if (this._inputElement) {
  690. this._scene.activeCamera.attachControl(this._inputElement);
  691. }
  692. } else {
  693. this._existingCamera = this._scene.activeCamera;
  694. }
  695. if (this.webVROptions.useXR && (navigator as any).xr) {
  696. // force-check XR session support
  697. WebXRSessionManager.IsSessionSupportedAsync("immersive-vr").then((supported) => {
  698. if (supported) {
  699. Logger.Log("Using WebXR. It is recommended to use the WebXRDefaultExperience directly");
  700. // it is possible to use XR, let's do it!
  701. scene.createDefaultXRExperienceAsync({
  702. floorMeshes: webVROptions.floorMeshes || []
  703. }).then((xr) => {
  704. this.xr = xr;
  705. // connect observables
  706. this.xrTestDone = true;
  707. this._cameraGazer = new VRExperienceHelperCameraGazer(() => { return this.xr.baseExperience.camera; }, scene);
  708. this.xr.baseExperience.onStateChangedObservable.add((state) => {
  709. // support for entering / exiting
  710. switch (state) {
  711. case WebXRState.ENTERING_XR:
  712. this.onEnteringVRObservable.notifyObservers(this);
  713. if (!this._interactionsEnabled) {
  714. this.xr.pointerSelection.detach();
  715. }
  716. this.xr.pointerSelection.displayLaserPointer = this._displayLaserPointer;
  717. break;
  718. case WebXRState.EXITING_XR:
  719. this.onExitingVRObservable.notifyObservers(this);
  720. // resize to update width and height when exiting vr exits fullscreen
  721. this._scene.getEngine().resize();
  722. break;
  723. case WebXRState.IN_XR:
  724. this._hasEnteredVR = true;
  725. break;
  726. case WebXRState.NOT_IN_XR:
  727. this._hasEnteredVR = false;
  728. break;
  729. }
  730. });
  731. });
  732. } else {
  733. // XR not supported (thou exists), continue WebVR init
  734. this.completeVRInit(scene, webVROptions);
  735. }
  736. });
  737. } else {
  738. // no XR, continue init synchronous
  739. this.completeVRInit(scene, webVROptions);
  740. }
  741. }
  742. private completeVRInit(scene: Scene,
  743. webVROptions: VRExperienceHelperOptions): void {
  744. this.xrTestDone = true;
  745. // Create VR cameras
  746. if (webVROptions.createFallbackVRDeviceOrientationFreeCamera) {
  747. if (webVROptions.useMultiview) {
  748. if (!webVROptions.vrDeviceOrientationCameraMetrics) {
  749. webVROptions.vrDeviceOrientationCameraMetrics = VRCameraMetrics.GetDefault();
  750. }
  751. webVROptions.vrDeviceOrientationCameraMetrics.multiviewEnabled = true;
  752. }
  753. this._vrDeviceOrientationCamera = new VRDeviceOrientationFreeCamera("VRDeviceOrientationVRHelper", this._position, this._scene, true, webVROptions.vrDeviceOrientationCameraMetrics);
  754. this._vrDeviceOrientationCamera.angularSensibility = Number.MAX_VALUE;
  755. }
  756. this._webVRCamera = new WebVRFreeCamera("WebVRHelper", this._position, this._scene, webVROptions);
  757. this._webVRCamera.useStandingMatrix();
  758. this._cameraGazer = new VRExperienceHelperCameraGazer(() => { return this.currentVRCamera; }, scene);
  759. // Create default button
  760. if (!this._useCustomVRButton) {
  761. this._btnVR = <HTMLButtonElement>document.createElement("BUTTON");
  762. this._btnVR.className = "babylonVRicon";
  763. this._btnVR.id = "babylonVRiconbtn";
  764. this._btnVR.title = "Click to switch to VR";
  765. const url = !window.SVGSVGElement ? "https://cdn.babylonjs.com/Assets/vrButton.png" : "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";
  766. var css = ".babylonVRicon { position: absolute; right: 20px; height: 50px; width: 80px; background-color: rgba(51,51,51,0.7); background-image: url(" + url + "); 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) }";
  767. css += ".babylonVRicon.vrdisplaypresenting { display: none; }";
  768. // TODO: Add user feedback so that they know what state the VRDisplay is in (disconnected, connected, entering-VR)
  769. // css += ".babylonVRicon.vrdisplaysupported { }";
  770. // css += ".babylonVRicon.vrdisplayready { }";
  771. // css += ".babylonVRicon.vrdisplayrequesting { }";
  772. var style = document.createElement('style');
  773. style.appendChild(document.createTextNode(css));
  774. document.getElementsByTagName('head')[0].appendChild(style);
  775. this.moveButtonToBottomRight();
  776. }
  777. // VR button click event
  778. if (this._btnVR) {
  779. this._btnVR.addEventListener("click", () => {
  780. if (!this.isInVRMode) {
  781. this.enterVR();
  782. } else {
  783. this._scene.getEngine().disableVR();
  784. }
  785. });
  786. }
  787. // Window events
  788. let hostWindow = this._scene.getEngine().getHostWindow();
  789. if (!hostWindow) {
  790. return;
  791. }
  792. hostWindow.addEventListener("resize", this._onResize);
  793. document.addEventListener("fullscreenchange", this._onFullscreenChange, false);
  794. document.addEventListener("mozfullscreenchange", this._onFullscreenChange, false);
  795. document.addEventListener("webkitfullscreenchange", this._onFullscreenChange, false);
  796. document.addEventListener("msfullscreenchange", this._onFullscreenChange, false);
  797. (<any>document).onmsfullscreenchange = this._onFullscreenChange;
  798. // Display vr button when headset is connected
  799. if (webVROptions.createFallbackVRDeviceOrientationFreeCamera) {
  800. this.displayVRButton();
  801. } else {
  802. this._scene.getEngine().onVRDisplayChangedObservable.add((e) => {
  803. if (e.vrDisplay) {
  804. this.displayVRButton();
  805. }
  806. });
  807. }
  808. // Exiting VR mode using 'ESC' key on desktop
  809. this._onKeyDown = (event: KeyboardEvent) => {
  810. if (event.keyCode === 27 && this.isInVRMode) {
  811. this.exitVR();
  812. }
  813. };
  814. document.addEventListener("keydown", this._onKeyDown);
  815. // Exiting VR mode double tapping the touch screen
  816. this._scene.onPrePointerObservable.add(() => {
  817. if (this._hasEnteredVR && this.exitVROnDoubleTap) {
  818. this.exitVR();
  819. if (this._fullscreenVRpresenting) {
  820. this._scene.getEngine().exitFullscreen();
  821. }
  822. }
  823. }, PointerEventTypes.POINTERDOUBLETAP, false);
  824. // Listen for WebVR display changes
  825. this._onVRDisplayChanged = (eventArgs: IDisplayChangedEventArgs) => this.onVRDisplayChanged(eventArgs);
  826. this._onVrDisplayPresentChange = () => this.onVrDisplayPresentChange();
  827. this._onVRRequestPresentStart = () => {
  828. this._webVRrequesting = true;
  829. this.updateButtonVisibility();
  830. };
  831. this._onVRRequestPresentComplete = () => {
  832. this._webVRrequesting = false;
  833. this.updateButtonVisibility();
  834. };
  835. scene.getEngine().onVRDisplayChangedObservable.add(this._onVRDisplayChanged);
  836. scene.getEngine().onVRRequestPresentStart.add(this._onVRRequestPresentStart);
  837. scene.getEngine().onVRRequestPresentComplete.add(this._onVRRequestPresentComplete);
  838. hostWindow.addEventListener('vrdisplaypresentchange', this._onVrDisplayPresentChange);
  839. scene.onDisposeObservable.add(() => {
  840. this.dispose();
  841. });
  842. // Gamepad connection events
  843. this._webVRCamera.onControllerMeshLoadedObservable.add((webVRController) => this._onDefaultMeshLoaded(webVRController));
  844. this._scene.gamepadManager.onGamepadConnectedObservable.add(this._onNewGamepadConnected);
  845. this._scene.gamepadManager.onGamepadDisconnectedObservable.add(this._onNewGamepadDisconnected);
  846. this.updateButtonVisibility();
  847. //create easing functions
  848. this._circleEase = new CircleEase();
  849. this._circleEase.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT);
  850. this._teleportationEasing = this._circleEase;
  851. // Allow clicking in the vrDeviceOrientationCamera
  852. scene.onPointerObservable.add((e) => {
  853. if (this._interactionsEnabled) {
  854. if (scene.activeCamera === this.vrDeviceOrientationCamera && (e.event as PointerEvent).pointerType === "mouse") {
  855. if (e.type === PointerEventTypes.POINTERDOWN) {
  856. this._cameraGazer._selectionPointerDown();
  857. } else if (e.type === PointerEventTypes.POINTERUP) {
  858. this._cameraGazer._selectionPointerUp();
  859. }
  860. }
  861. }
  862. });
  863. if (this.webVROptions.floorMeshes) {
  864. this.enableTeleportation({ floorMeshes: this.webVROptions.floorMeshes });
  865. }
  866. }
  867. // Raised when one of the controller has loaded successfully its associated default mesh
  868. private _onDefaultMeshLoaded(webVRController: WebVRController) {
  869. if (this._leftController && this._leftController.webVRController == webVRController) {
  870. if (webVRController.mesh) {
  871. this._leftController._setLaserPointerParent(webVRController.mesh);
  872. }
  873. }
  874. if (this._rightController && this._rightController.webVRController == webVRController) {
  875. if (webVRController.mesh) {
  876. this._rightController._setLaserPointerParent(webVRController.mesh);
  877. }
  878. }
  879. try {
  880. this.onControllerMeshLoadedObservable.notifyObservers(webVRController);
  881. }
  882. catch (err) {
  883. Logger.Warn("Error in your custom logic onControllerMeshLoaded: " + err);
  884. }
  885. }
  886. private _onResize = () => {
  887. this.moveButtonToBottomRight();
  888. if (this._fullscreenVRpresenting && this._webVRready) {
  889. this.exitVR();
  890. }
  891. }
  892. private _onFullscreenChange = () => {
  893. let anyDoc = document as any;
  894. if (anyDoc.fullscreen !== undefined) {
  895. this._fullscreenVRpresenting = (<any>document).fullscreen;
  896. } else if (anyDoc.mozFullScreen !== undefined) {
  897. this._fullscreenVRpresenting = anyDoc.mozFullScreen;
  898. } else if (anyDoc.webkitIsFullScreen !== undefined) {
  899. this._fullscreenVRpresenting = anyDoc.webkitIsFullScreen;
  900. } else if (anyDoc.msIsFullScreen !== undefined) {
  901. this._fullscreenVRpresenting = anyDoc.msIsFullScreen;
  902. } else if ((<any>document).msFullscreenElement !== undefined) {
  903. this._fullscreenVRpresenting = (<any>document).msFullscreenElement;
  904. }
  905. if (!this._fullscreenVRpresenting && this._inputElement) {
  906. this.exitVR();
  907. if (!this._useCustomVRButton && this._btnVR) {
  908. this._btnVR.style.top = this._inputElement.offsetTop + this._inputElement.offsetHeight - 70 + "px";
  909. this._btnVR.style.left = this._inputElement.offsetLeft + this._inputElement.offsetWidth - 100 + "px";
  910. // make sure the button is visible after setting its position
  911. this.updateButtonVisibility();
  912. }
  913. }
  914. }
  915. /**
  916. * Gets a value indicating if we are currently in VR mode.
  917. */
  918. public get isInVRMode(): boolean {
  919. return (this.xr && this.webVROptions.useXR && this.xr.baseExperience.state === WebXRState.IN_XR) || (this._webVRpresenting || this._fullscreenVRpresenting);
  920. }
  921. private onVrDisplayPresentChange() {
  922. var vrDisplay = this._scene.getEngine().getVRDevice();
  923. if (vrDisplay) {
  924. var wasPresenting = this._webVRpresenting;
  925. this._webVRpresenting = vrDisplay.isPresenting;
  926. if (wasPresenting && !this._webVRpresenting) {
  927. this.exitVR();
  928. }
  929. } else {
  930. Logger.Warn('Detected VRDisplayPresentChange on an unknown VRDisplay. Did you can enterVR on the vrExperienceHelper?');
  931. }
  932. this.updateButtonVisibility();
  933. }
  934. private onVRDisplayChanged(eventArgs: IDisplayChangedEventArgs) {
  935. this._webVRsupported = eventArgs.vrSupported;
  936. this._webVRready = !!eventArgs.vrDisplay;
  937. this._webVRpresenting = eventArgs.vrDisplay && eventArgs.vrDisplay.isPresenting;
  938. this.updateButtonVisibility();
  939. }
  940. private moveButtonToBottomRight() {
  941. if (this._inputElement && !this._useCustomVRButton && this._btnVR) {
  942. const rect: ClientRect = this._inputElement.getBoundingClientRect();
  943. this._btnVR.style.top = rect.top + rect.height - 70 + "px";
  944. this._btnVR.style.left = rect.left + rect.width - 100 + "px";
  945. }
  946. }
  947. private displayVRButton() {
  948. if (!this._useCustomVRButton && !this._btnVRDisplayed && this._btnVR) {
  949. document.body.appendChild(this._btnVR);
  950. this._btnVRDisplayed = true;
  951. }
  952. }
  953. private updateButtonVisibility() {
  954. if (!this._btnVR || this._useCustomVRButton) {
  955. return;
  956. }
  957. this._btnVR.className = "babylonVRicon";
  958. if (this.isInVRMode) {
  959. this._btnVR.className += " vrdisplaypresenting";
  960. } else {
  961. if (this._webVRready) { this._btnVR.className += " vrdisplayready"; }
  962. if (this._webVRsupported) { this._btnVR.className += " vrdisplaysupported"; }
  963. if (this._webVRrequesting) { this._btnVR.className += " vrdisplayrequesting"; }
  964. }
  965. }
  966. private _cachedAngularSensibility = { angularSensibilityX: null, angularSensibilityY: null, angularSensibility: null };
  967. /**
  968. * Attempt to enter VR. If a headset is connected and ready, will request present on that.
  969. * Otherwise, will use the fullscreen API.
  970. */
  971. public enterVR() {
  972. if (this.xr) {
  973. this.xr.baseExperience.enterXRAsync("immersive-vr", "local-floor", this.xr.renderTarget);
  974. return;
  975. }
  976. if (this.onEnteringVRObservable) {
  977. try {
  978. this.onEnteringVRObservable.notifyObservers(this);
  979. }
  980. catch (err) {
  981. Logger.Warn("Error in your custom logic onEnteringVR: " + err);
  982. }
  983. }
  984. if (this._scene.activeCamera) {
  985. this._position = this._scene.activeCamera.position.clone();
  986. if (this.vrDeviceOrientationCamera) {
  987. this.vrDeviceOrientationCamera.rotation = Quaternion.FromRotationMatrix(this._scene.activeCamera.getWorldMatrix().getRotationMatrix()).toEulerAngles();
  988. this.vrDeviceOrientationCamera.angularSensibility = 2000;
  989. }
  990. if (this.webVRCamera) {
  991. var currentYRotation = this.webVRCamera.deviceRotationQuaternion.toEulerAngles().y;
  992. var desiredYRotation = Quaternion.FromRotationMatrix(this._scene.activeCamera.getWorldMatrix().getRotationMatrix()).toEulerAngles().y;
  993. var delta = desiredYRotation - currentYRotation;
  994. var currentGlobalRotation = this.webVRCamera.rotationQuaternion.toEulerAngles().y;
  995. this.webVRCamera.rotationQuaternion = Quaternion.FromEulerAngles(0, currentGlobalRotation + delta, 0);
  996. }
  997. // make sure that we return to the last active camera
  998. this._existingCamera = this._scene.activeCamera;
  999. // Remove and cache angular sensability to avoid camera rotation when in VR
  1000. if ((<any>this._existingCamera).angularSensibilityX) {
  1001. this._cachedAngularSensibility.angularSensibilityX = (<any>this._existingCamera).angularSensibilityX;
  1002. (<any>this._existingCamera).angularSensibilityX = Number.MAX_VALUE;
  1003. }
  1004. if ((<any>this._existingCamera).angularSensibilityY) {
  1005. this._cachedAngularSensibility.angularSensibilityY = (<any>this._existingCamera).angularSensibilityY;
  1006. (<any>this._existingCamera).angularSensibilityY = Number.MAX_VALUE;
  1007. }
  1008. if ((<any>this._existingCamera).angularSensibility) {
  1009. this._cachedAngularSensibility.angularSensibility = (<any>this._existingCamera).angularSensibility;
  1010. (<any>this._existingCamera).angularSensibility = Number.MAX_VALUE;
  1011. }
  1012. }
  1013. if (this._webVRrequesting) {
  1014. return;
  1015. }
  1016. // If WebVR is supported and a headset is connected
  1017. if (this._webVRready) {
  1018. if (!this._webVRpresenting) {
  1019. this._scene.getEngine().onVRRequestPresentComplete.addOnce((result) => {
  1020. this.onAfterEnteringVRObservable.notifyObservers({ success: result });
  1021. });
  1022. this._webVRCamera.position = this._position;
  1023. this._scene.activeCamera = this._webVRCamera;
  1024. }
  1025. }
  1026. else if (this._vrDeviceOrientationCamera) {
  1027. this._vrDeviceOrientationCamera.position = this._position;
  1028. if (this._scene.activeCamera) {
  1029. this._vrDeviceOrientationCamera.minZ = this._scene.activeCamera.minZ;
  1030. }
  1031. this._scene.activeCamera = this._vrDeviceOrientationCamera;
  1032. this._scene.getEngine().enterFullscreen(this.requestPointerLockOnFullScreen);
  1033. this.updateButtonVisibility();
  1034. this._vrDeviceOrientationCamera.onViewMatrixChangedObservable.addOnce(() => {
  1035. this.onAfterEnteringVRObservable.notifyObservers({ success: true });
  1036. });
  1037. }
  1038. if (this._scene.activeCamera && this._inputElement) {
  1039. this._scene.activeCamera.attachControl(this._inputElement);
  1040. }
  1041. if (this._interactionsEnabled) {
  1042. this._scene.registerBeforeRender(this.beforeRender);
  1043. }
  1044. if (this._displayLaserPointer) {
  1045. [this._leftController, this._rightController].forEach((controller) => {
  1046. if (controller) {
  1047. controller._activatePointer();
  1048. }
  1049. });
  1050. }
  1051. this._hasEnteredVR = true;
  1052. }
  1053. /**
  1054. * Attempt to exit VR, or fullscreen.
  1055. */
  1056. public exitVR() {
  1057. if (this.xr) {
  1058. this.xr.baseExperience.exitXRAsync();
  1059. return;
  1060. }
  1061. if (this._hasEnteredVR) {
  1062. if (this.onExitingVRObservable) {
  1063. try {
  1064. this.onExitingVRObservable.notifyObservers(this);
  1065. }
  1066. catch (err) {
  1067. Logger.Warn("Error in your custom logic onExitingVR: " + err);
  1068. }
  1069. }
  1070. if (this._webVRpresenting) {
  1071. this._scene.getEngine().disableVR();
  1072. }
  1073. if (this._scene.activeCamera) {
  1074. this._position = this._scene.activeCamera.position.clone();
  1075. }
  1076. if (this.vrDeviceOrientationCamera) {
  1077. this.vrDeviceOrientationCamera.angularSensibility = Number.MAX_VALUE;
  1078. }
  1079. if (this._deviceOrientationCamera) {
  1080. this._deviceOrientationCamera.position = this._position;
  1081. this._scene.activeCamera = this._deviceOrientationCamera;
  1082. // Restore angular sensibility
  1083. if (this._cachedAngularSensibility.angularSensibilityX) {
  1084. (<any>this._deviceOrientationCamera).angularSensibilityX = this._cachedAngularSensibility.angularSensibilityX;
  1085. this._cachedAngularSensibility.angularSensibilityX = null;
  1086. }
  1087. if (this._cachedAngularSensibility.angularSensibilityY) {
  1088. (<any>this._deviceOrientationCamera).angularSensibilityY = this._cachedAngularSensibility.angularSensibilityY;
  1089. this._cachedAngularSensibility.angularSensibilityY = null;
  1090. }
  1091. if (this._cachedAngularSensibility.angularSensibility) {
  1092. (<any>this._deviceOrientationCamera).angularSensibility = this._cachedAngularSensibility.angularSensibility;
  1093. this._cachedAngularSensibility.angularSensibility = null;
  1094. }
  1095. } else if (this._existingCamera) {
  1096. this._existingCamera.position = this._position;
  1097. this._scene.activeCamera = this._existingCamera;
  1098. if (this._inputElement) {
  1099. this._scene.activeCamera.attachControl(this._inputElement);
  1100. }
  1101. // Restore angular sensibility
  1102. if (this._cachedAngularSensibility.angularSensibilityX) {
  1103. (<any>this._existingCamera).angularSensibilityX = this._cachedAngularSensibility.angularSensibilityX;
  1104. this._cachedAngularSensibility.angularSensibilityX = null;
  1105. }
  1106. if (this._cachedAngularSensibility.angularSensibilityY) {
  1107. (<any>this._existingCamera).angularSensibilityY = this._cachedAngularSensibility.angularSensibilityY;
  1108. this._cachedAngularSensibility.angularSensibilityY = null;
  1109. }
  1110. if (this._cachedAngularSensibility.angularSensibility) {
  1111. (<any>this._existingCamera).angularSensibility = this._cachedAngularSensibility.angularSensibility;
  1112. this._cachedAngularSensibility.angularSensibility = null;
  1113. }
  1114. }
  1115. this.updateButtonVisibility();
  1116. if (this._interactionsEnabled) {
  1117. this._scene.unregisterBeforeRender(this.beforeRender);
  1118. this._cameraGazer._gazeTracker.isVisible = false;
  1119. if (this._leftController) {
  1120. this._leftController._gazeTracker.isVisible = false;
  1121. }
  1122. if (this._rightController) {
  1123. this._rightController._gazeTracker.isVisible = false;
  1124. }
  1125. }
  1126. // resize to update width and height when exiting vr exits fullscreen
  1127. this._scene.getEngine().resize();
  1128. [this._leftController, this._rightController].forEach((controller) => {
  1129. if (controller) {
  1130. controller._deactivatePointer();
  1131. }
  1132. });
  1133. this._hasEnteredVR = false;
  1134. // Update engine state to re enable non-vr camera input
  1135. var engine = this._scene.getEngine();
  1136. if (engine._onVrDisplayPresentChange) {
  1137. engine._onVrDisplayPresentChange();
  1138. }
  1139. }
  1140. }
  1141. /**
  1142. * The position of the vr experience helper.
  1143. */
  1144. public get position(): Vector3 {
  1145. return this._position;
  1146. }
  1147. /**
  1148. * Sets the position of the vr experience helper.
  1149. */
  1150. public set position(value: Vector3) {
  1151. this._position = value;
  1152. if (this._scene.activeCamera) {
  1153. this._scene.activeCamera.position = value;
  1154. }
  1155. }
  1156. /**
  1157. * Enables controllers and user interactions such as selecting and object or clicking on an object.
  1158. */
  1159. public enableInteractions() {
  1160. if (!this._interactionsEnabled) {
  1161. this._interactionsRequested = true;
  1162. // in XR it is enabled by default, but just to make sure, re-attach
  1163. if (this.xr) {
  1164. if (this.xr.baseExperience.state === WebXRState.IN_XR) {
  1165. this.xr.pointerSelection.attach();
  1166. }
  1167. return;
  1168. }
  1169. if (this._leftController) {
  1170. this._enableInteractionOnController(this._leftController);
  1171. }
  1172. if (this._rightController) {
  1173. this._enableInteractionOnController(this._rightController);
  1174. }
  1175. this.raySelectionPredicate = (mesh) => {
  1176. return mesh.isVisible && (mesh.isPickable || mesh.name === this._floorMeshName);
  1177. };
  1178. this.meshSelectionPredicate = () => {
  1179. return true;
  1180. };
  1181. this._raySelectionPredicate = (mesh) => {
  1182. if (this._isTeleportationFloor(mesh) || (mesh.name.indexOf("gazeTracker") === -1
  1183. && mesh.name.indexOf("teleportationTarget") === -1
  1184. && mesh.name.indexOf("torusTeleportation") === -1)) {
  1185. return this.raySelectionPredicate(mesh);
  1186. }
  1187. return false;
  1188. };
  1189. this._interactionsEnabled = true;
  1190. }
  1191. }
  1192. private get _noControllerIsActive() {
  1193. return !(this._leftController && this._leftController._activePointer) && !(this._rightController && this._rightController._activePointer);
  1194. }
  1195. private beforeRender = () => {
  1196. if (this._leftController && this._leftController._activePointer) {
  1197. this._castRayAndSelectObject(this._leftController);
  1198. }
  1199. if (this._rightController && this._rightController._activePointer) {
  1200. this._castRayAndSelectObject(this._rightController);
  1201. }
  1202. if (this._noControllerIsActive && (this._scene.getEngine().isPointerLock || this.enableGazeEvenWhenNoPointerLock)) {
  1203. this._castRayAndSelectObject(this._cameraGazer);
  1204. } else {
  1205. this._cameraGazer._gazeTracker.isVisible = false;
  1206. }
  1207. }
  1208. private _isTeleportationFloor(mesh: AbstractMesh): boolean {
  1209. for (var i = 0; i < this._floorMeshesCollection.length; i++) {
  1210. if (this._floorMeshesCollection[i].id === mesh.id) {
  1211. return true;
  1212. }
  1213. }
  1214. if (this._floorMeshName && mesh.name === this._floorMeshName) {
  1215. return true;
  1216. }
  1217. return false;
  1218. }
  1219. /**
  1220. * Adds a floor mesh to be used for teleportation.
  1221. * @param floorMesh the mesh to be used for teleportation.
  1222. */
  1223. public addFloorMesh(floorMesh: Mesh): void {
  1224. if (!this._floorMeshesCollection) {
  1225. return;
  1226. }
  1227. if (this._floorMeshesCollection.indexOf(floorMesh) > -1) {
  1228. return;
  1229. }
  1230. this._floorMeshesCollection.push(floorMesh);
  1231. }
  1232. /**
  1233. * Removes a floor mesh from being used for teleportation.
  1234. * @param floorMesh the mesh to be removed.
  1235. */
  1236. public removeFloorMesh(floorMesh: Mesh): void {
  1237. if (!this._floorMeshesCollection) {
  1238. return;
  1239. }
  1240. const meshIndex = this._floorMeshesCollection.indexOf(floorMesh);
  1241. if (meshIndex !== -1) {
  1242. this._floorMeshesCollection.splice(meshIndex, 1);
  1243. }
  1244. }
  1245. /**
  1246. * Enables interactions and teleportation using the VR controllers and gaze.
  1247. * @param vrTeleportationOptions options to modify teleportation behavior.
  1248. */
  1249. public enableTeleportation(vrTeleportationOptions: VRTeleportationOptions = {}) {
  1250. if (!this._teleportationInitialized) {
  1251. this._teleportationRequested = true;
  1252. this.enableInteractions();
  1253. if (this.webVROptions.useXR && (vrTeleportationOptions.floorMeshes || vrTeleportationOptions.floorMeshName)) {
  1254. const floorMeshes: AbstractMesh[] = vrTeleportationOptions.floorMeshes || [];
  1255. if (!floorMeshes.length) {
  1256. const floorMesh = this._scene.getMeshByName(vrTeleportationOptions.floorMeshName!);
  1257. if (floorMesh) {
  1258. floorMeshes.push(floorMesh);
  1259. }
  1260. }
  1261. if (this.xr) {
  1262. floorMeshes.forEach((mesh) => {
  1263. this.xr.teleportation.addFloorMesh(mesh);
  1264. });
  1265. if (!this.xr.teleportation.attached) {
  1266. this.xr.teleportation.attach();
  1267. }
  1268. return;
  1269. } else if (!this.xrTestDone) {
  1270. const waitForXr = () => {
  1271. if (this.xrTestDone) {
  1272. this._scene.unregisterBeforeRender(waitForXr);
  1273. if (this.xr) {
  1274. if (!this.xr.teleportation.attached) {
  1275. this.xr.teleportation.attach();
  1276. }
  1277. } else {
  1278. this.enableTeleportation(vrTeleportationOptions);
  1279. }
  1280. }
  1281. };
  1282. this._scene.registerBeforeRender(waitForXr);
  1283. return;
  1284. }
  1285. }
  1286. if (vrTeleportationOptions.floorMeshName) {
  1287. this._floorMeshName = vrTeleportationOptions.floorMeshName;
  1288. }
  1289. if (vrTeleportationOptions.floorMeshes) {
  1290. this._floorMeshesCollection = vrTeleportationOptions.floorMeshes;
  1291. }
  1292. if (vrTeleportationOptions.teleportationMode) {
  1293. this._teleportationMode = vrTeleportationOptions.teleportationMode;
  1294. }
  1295. if (vrTeleportationOptions.teleportationTime && vrTeleportationOptions.teleportationTime > 0) {
  1296. this._teleportationTime = vrTeleportationOptions.teleportationTime;
  1297. }
  1298. if (vrTeleportationOptions.teleportationSpeed && vrTeleportationOptions.teleportationSpeed > 0) {
  1299. this._teleportationSpeed = vrTeleportationOptions.teleportationSpeed;
  1300. }
  1301. if (vrTeleportationOptions.easingFunction !== undefined) {
  1302. this._teleportationEasing = vrTeleportationOptions.easingFunction;
  1303. }
  1304. if (this._leftController != null) {
  1305. this._enableTeleportationOnController(this._leftController);
  1306. }
  1307. if (this._rightController != null) {
  1308. this._enableTeleportationOnController(this._rightController);
  1309. }
  1310. // Creates an image processing post process for the vignette not relying
  1311. // on the main scene configuration for image processing to reduce setup and spaces
  1312. // (gamma/linear) conflicts.
  1313. const imageProcessingConfiguration = new ImageProcessingConfiguration();
  1314. imageProcessingConfiguration.vignetteColor = new Color4(0, 0, 0, 0);
  1315. imageProcessingConfiguration.vignetteEnabled = true;
  1316. this._postProcessMove = new ImageProcessingPostProcess("postProcessMove",
  1317. 1.0,
  1318. this._webVRCamera,
  1319. undefined,
  1320. undefined,
  1321. undefined,
  1322. undefined,
  1323. imageProcessingConfiguration);
  1324. this._webVRCamera.detachPostProcess(this._postProcessMove);
  1325. this._teleportationInitialized = true;
  1326. if (this._isDefaultTeleportationTarget) {
  1327. this._createTeleportationCircles();
  1328. this._teleportationTarget.scaling.scaleInPlace(this._webVRCamera.deviceScaleFactor);
  1329. }
  1330. }
  1331. }
  1332. private _onNewGamepadConnected = (gamepad: Gamepad) => {
  1333. if (gamepad.type !== Gamepad.POSE_ENABLED) {
  1334. if (gamepad.leftStick) {
  1335. gamepad.onleftstickchanged((stickValues) => {
  1336. if (this._teleportationInitialized && this.teleportationEnabled) {
  1337. // Listening to classic/xbox gamepad only if no VR controller is active
  1338. if ((!this._leftController && !this._rightController) ||
  1339. ((this._leftController && !this._leftController._activePointer) &&
  1340. (this._rightController && !this._rightController._activePointer))) {
  1341. this._checkTeleportWithRay(stickValues, this._cameraGazer);
  1342. this._checkTeleportBackwards(stickValues, this._cameraGazer);
  1343. }
  1344. }
  1345. });
  1346. }
  1347. if (gamepad.rightStick) {
  1348. gamepad.onrightstickchanged((stickValues) => {
  1349. if (this._teleportationInitialized) {
  1350. this._checkRotate(stickValues, this._cameraGazer);
  1351. }
  1352. });
  1353. }
  1354. if (gamepad.type === Gamepad.XBOX) {
  1355. (<Xbox360Pad>gamepad).onbuttondown((buttonPressed: Xbox360Button) => {
  1356. if (this._interactionsEnabled && buttonPressed === Xbox360Button.A) {
  1357. this._cameraGazer._selectionPointerDown();
  1358. }
  1359. });
  1360. (<Xbox360Pad>gamepad).onbuttonup((buttonPressed: Xbox360Button) => {
  1361. if (this._interactionsEnabled && buttonPressed === Xbox360Button.A) {
  1362. this._cameraGazer._selectionPointerUp();
  1363. }
  1364. });
  1365. }
  1366. } else {
  1367. var webVRController = <WebVRController>gamepad;
  1368. var controller = new VRExperienceHelperControllerGazer(webVRController, this._scene, this._cameraGazer._gazeTracker);
  1369. if (webVRController.hand === "right" || (this._leftController && this._leftController.webVRController != webVRController)) {
  1370. this._rightController = controller;
  1371. } else {
  1372. this._leftController = controller;
  1373. }
  1374. this._tryEnableInteractionOnController(controller);
  1375. }
  1376. }
  1377. // This only succeeds if the controller's mesh exists for the controller so this must be called whenever new controller is connected or when mesh is loaded
  1378. private _tryEnableInteractionOnController = (controller: VRExperienceHelperControllerGazer) => {
  1379. if (this._interactionsRequested && !controller._interactionsEnabled) {
  1380. this._enableInteractionOnController(controller);
  1381. }
  1382. if (this._teleportationRequested && !controller._teleportationEnabled) {
  1383. this._enableTeleportationOnController(controller);
  1384. }
  1385. }
  1386. private _onNewGamepadDisconnected = (gamepad: Gamepad) => {
  1387. if (gamepad instanceof WebVRController) {
  1388. if (gamepad.hand === "left" && this._leftController != null) {
  1389. this._leftController.dispose();
  1390. this._leftController = null;
  1391. }
  1392. if (gamepad.hand === "right" && this._rightController != null) {
  1393. this._rightController.dispose();
  1394. this._rightController = null;
  1395. }
  1396. }
  1397. }
  1398. private _enableInteractionOnController(controller: VRExperienceHelperControllerGazer) {
  1399. var controllerMesh = controller.webVRController.mesh;
  1400. if (controllerMesh) {
  1401. controller._interactionsEnabled = true;
  1402. if (this.isInVRMode && this._displayLaserPointer) {
  1403. controller._activatePointer();
  1404. }
  1405. if (this.webVROptions.laserToggle) {
  1406. controller.webVRController.onMainButtonStateChangedObservable.add((stateObject) => {
  1407. // Enabling / disabling laserPointer
  1408. if (this._displayLaserPointer && stateObject.value === 1) {
  1409. if (controller._activePointer) {
  1410. controller._deactivatePointer();
  1411. } else {
  1412. controller._activatePointer();
  1413. }
  1414. if (this.displayGaze) {
  1415. controller._gazeTracker.isVisible = controller._activePointer;
  1416. }
  1417. }
  1418. });
  1419. }
  1420. controller.webVRController.onTriggerStateChangedObservable.add((stateObject) => {
  1421. var gazer: VRExperienceHelperGazer = controller;
  1422. if (this._noControllerIsActive) {
  1423. gazer = this._cameraGazer;
  1424. }
  1425. if (!gazer._pointerDownOnMeshAsked) {
  1426. if (stateObject.value > this._padSensibilityUp) {
  1427. gazer._selectionPointerDown();
  1428. }
  1429. } else if (stateObject.value < this._padSensibilityDown) {
  1430. gazer._selectionPointerUp();
  1431. }
  1432. });
  1433. }
  1434. }
  1435. private _checkTeleportWithRay(stateObject: StickValues, gazer: VRExperienceHelperGazer) {
  1436. // Dont teleport if another gaze already requested teleportation
  1437. if (this._teleportationRequestInitiated && !gazer._teleportationRequestInitiated) {
  1438. return;
  1439. }
  1440. if (!gazer._teleportationRequestInitiated) {
  1441. if (stateObject.y < -this._padSensibilityUp && gazer._dpadPressed) {
  1442. gazer._activatePointer();
  1443. gazer._teleportationRequestInitiated = true;
  1444. }
  1445. } else {
  1446. // Listening to the proper controller values changes to confirm teleportation
  1447. if (Math.sqrt(stateObject.y * stateObject.y + stateObject.x * stateObject.x) < this._padSensibilityDown) {
  1448. if (this._teleportActive) {
  1449. this.teleportCamera(this._haloCenter);
  1450. }
  1451. gazer._teleportationRequestInitiated = false;
  1452. }
  1453. }
  1454. }
  1455. private _checkRotate(stateObject: StickValues, gazer: VRExperienceHelperGazer) {
  1456. // Only rotate when user is not currently selecting a teleportation location
  1457. if (gazer._teleportationRequestInitiated) {
  1458. return;
  1459. }
  1460. if (!gazer._rotationLeftAsked) {
  1461. if (stateObject.x < -this._padSensibilityUp && gazer._dpadPressed) {
  1462. gazer._rotationLeftAsked = true;
  1463. if (this._rotationAllowed) {
  1464. this._rotateCamera(false);
  1465. }
  1466. }
  1467. } else {
  1468. if (stateObject.x > -this._padSensibilityDown) {
  1469. gazer._rotationLeftAsked = false;
  1470. }
  1471. }
  1472. if (!gazer._rotationRightAsked) {
  1473. if (stateObject.x > this._padSensibilityUp && gazer._dpadPressed) {
  1474. gazer._rotationRightAsked = true;
  1475. if (this._rotationAllowed) {
  1476. this._rotateCamera(true);
  1477. }
  1478. }
  1479. } else {
  1480. if (stateObject.x < this._padSensibilityDown) {
  1481. gazer._rotationRightAsked = false;
  1482. }
  1483. }
  1484. }
  1485. private _checkTeleportBackwards(stateObject: StickValues, gazer: VRExperienceHelperGazer) {
  1486. // Only teleport backwards when user is not currently selecting a teleportation location
  1487. if (gazer._teleportationRequestInitiated) {
  1488. return;
  1489. }
  1490. // Teleport backwards
  1491. if (stateObject.y > this._padSensibilityUp && gazer._dpadPressed) {
  1492. if (!gazer._teleportationBackRequestInitiated) {
  1493. if (!this.currentVRCamera) {
  1494. return;
  1495. }
  1496. // Get rotation and position of the current camera
  1497. var rotation = Quaternion.FromRotationMatrix(this.currentVRCamera.getWorldMatrix().getRotationMatrix());
  1498. var position = this.currentVRCamera.position;
  1499. // If the camera has device position, use that instead
  1500. if ((<WebVRFreeCamera>this.currentVRCamera).devicePosition && (<WebVRFreeCamera>this.currentVRCamera).deviceRotationQuaternion) {
  1501. rotation = (<WebVRFreeCamera>this.currentVRCamera).deviceRotationQuaternion;
  1502. position = (<WebVRFreeCamera>this.currentVRCamera).devicePosition;
  1503. }
  1504. // Get matrix with only the y rotation of the device rotation
  1505. rotation.toEulerAnglesToRef(this._workingVector);
  1506. this._workingVector.z = 0;
  1507. this._workingVector.x = 0;
  1508. Quaternion.RotationYawPitchRollToRef(this._workingVector.y, this._workingVector.x, this._workingVector.z, this._workingQuaternion);
  1509. this._workingQuaternion.toRotationMatrix(this._workingMatrix);
  1510. // Rotate backwards ray by device rotation to cast at the ground behind the user
  1511. Vector3.TransformCoordinatesToRef(this._teleportBackwardsVector, this._workingMatrix, this._workingVector);
  1512. // Teleport if ray hit the ground and is not to far away eg. backwards off a cliff
  1513. var ray = new Ray(position, this._workingVector);
  1514. var hit = this._scene.pickWithRay(ray, this._raySelectionPredicate);
  1515. if (hit && hit.pickedPoint && hit.pickedMesh && this._isTeleportationFloor(hit.pickedMesh) && hit.distance < 5) {
  1516. this.teleportCamera(hit.pickedPoint);
  1517. }
  1518. gazer._teleportationBackRequestInitiated = true;
  1519. }
  1520. } else {
  1521. gazer._teleportationBackRequestInitiated = false;
  1522. }
  1523. }
  1524. private _enableTeleportationOnController(controller: VRExperienceHelperControllerGazer) {
  1525. var controllerMesh = controller.webVRController.mesh;
  1526. if (controllerMesh) {
  1527. if (!controller._interactionsEnabled) {
  1528. this._enableInteractionOnController(controller);
  1529. }
  1530. controller._interactionsEnabled = true;
  1531. controller._teleportationEnabled = true;
  1532. if (controller.webVRController.controllerType === PoseEnabledControllerType.VIVE) {
  1533. controller._dpadPressed = false;
  1534. controller.webVRController.onPadStateChangedObservable.add((stateObject) => {
  1535. controller._dpadPressed = stateObject.pressed;
  1536. if (!controller._dpadPressed) {
  1537. controller._rotationLeftAsked = false;
  1538. controller._rotationRightAsked = false;
  1539. controller._teleportationBackRequestInitiated = false;
  1540. }
  1541. });
  1542. }
  1543. controller.webVRController.onPadValuesChangedObservable.add((stateObject) => {
  1544. if (this.teleportationEnabled) {
  1545. this._checkTeleportBackwards(stateObject, controller);
  1546. this._checkTeleportWithRay(stateObject, controller);
  1547. }
  1548. this._checkRotate(stateObject, controller);
  1549. });
  1550. }
  1551. }
  1552. private _createTeleportationCircles() {
  1553. this._teleportationTarget = Mesh.CreateGround("teleportationTarget", 2, 2, 2, this._scene);
  1554. this._teleportationTarget.isPickable = false;
  1555. var length = 512;
  1556. var dynamicTexture = new DynamicTexture("DynamicTexture", length, this._scene, true);
  1557. dynamicTexture.hasAlpha = true;
  1558. var context = dynamicTexture.getContext();
  1559. var centerX = length / 2;
  1560. var centerY = length / 2;
  1561. var radius = 200;
  1562. context.beginPath();
  1563. context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
  1564. context.fillStyle = this._teleportationFillColor;
  1565. context.fill();
  1566. context.lineWidth = 10;
  1567. context.strokeStyle = this._teleportationBorderColor;
  1568. context.stroke();
  1569. context.closePath();
  1570. dynamicTexture.update();
  1571. var teleportationCircleMaterial = new StandardMaterial("TextPlaneMaterial", this._scene);
  1572. teleportationCircleMaterial.diffuseTexture = dynamicTexture;
  1573. this._teleportationTarget.material = teleportationCircleMaterial;
  1574. var torus = Mesh.CreateTorus("torusTeleportation", 0.75, 0.1, 25, this._scene, false);
  1575. torus.isPickable = false;
  1576. torus.parent = this._teleportationTarget;
  1577. var animationInnerCircle = new Animation("animationInnerCircle", "position.y", 30, Animation.ANIMATIONTYPE_FLOAT, Animation.ANIMATIONLOOPMODE_CYCLE);
  1578. var keys = [];
  1579. keys.push({
  1580. frame: 0,
  1581. value: 0
  1582. });
  1583. keys.push({
  1584. frame: 30,
  1585. value: 0.4
  1586. });
  1587. keys.push({
  1588. frame: 60,
  1589. value: 0
  1590. });
  1591. animationInnerCircle.setKeys(keys);
  1592. var easingFunction = new SineEase();
  1593. easingFunction.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT);
  1594. animationInnerCircle.setEasingFunction(easingFunction);
  1595. torus.animations = [];
  1596. torus.animations.push(animationInnerCircle);
  1597. this._scene.beginAnimation(torus, 0, 60, true);
  1598. this._hideTeleportationTarget();
  1599. }
  1600. private _displayTeleportationTarget() {
  1601. this._teleportActive = true;
  1602. if (this._teleportationInitialized) {
  1603. this._teleportationTarget.isVisible = true;
  1604. if (this._isDefaultTeleportationTarget) {
  1605. (<Mesh>this._teleportationTarget.getChildren()[0]).isVisible = true;
  1606. }
  1607. }
  1608. }
  1609. private _hideTeleportationTarget() {
  1610. this._teleportActive = false;
  1611. if (this._teleportationInitialized) {
  1612. this._teleportationTarget.isVisible = false;
  1613. if (this._isDefaultTeleportationTarget) {
  1614. (<Mesh>this._teleportationTarget.getChildren()[0]).isVisible = false;
  1615. }
  1616. }
  1617. }
  1618. private _rotateCamera(right: boolean) {
  1619. if (!(this.currentVRCamera instanceof FreeCamera)) {
  1620. return;
  1621. }
  1622. if (right) {
  1623. this._rotationAngle++;
  1624. }
  1625. else {
  1626. this._rotationAngle--;
  1627. }
  1628. this.currentVRCamera.animations = [];
  1629. var target = Quaternion.FromRotationMatrix(Matrix.RotationY(Math.PI / 4 * this._rotationAngle));
  1630. var animationRotation = new Animation("animationRotation", "rotationQuaternion", 90, Animation.ANIMATIONTYPE_QUATERNION,
  1631. Animation.ANIMATIONLOOPMODE_CONSTANT);
  1632. var animationRotationKeys = [];
  1633. animationRotationKeys.push({
  1634. frame: 0,
  1635. value: this.currentVRCamera.rotationQuaternion
  1636. });
  1637. animationRotationKeys.push({
  1638. frame: 6,
  1639. value: target
  1640. });
  1641. animationRotation.setKeys(animationRotationKeys);
  1642. animationRotation.setEasingFunction(this._circleEase);
  1643. this.currentVRCamera.animations.push(animationRotation);
  1644. this._postProcessMove.animations = [];
  1645. var animationPP = new Animation("animationPP", "vignetteWeight", 90, Animation.ANIMATIONTYPE_FLOAT,
  1646. Animation.ANIMATIONLOOPMODE_CONSTANT);
  1647. var vignetteWeightKeys = [];
  1648. vignetteWeightKeys.push({
  1649. frame: 0,
  1650. value: 0
  1651. });
  1652. vignetteWeightKeys.push({
  1653. frame: 3,
  1654. value: 4
  1655. });
  1656. vignetteWeightKeys.push({
  1657. frame: 6,
  1658. value: 0
  1659. });
  1660. animationPP.setKeys(vignetteWeightKeys);
  1661. animationPP.setEasingFunction(this._circleEase);
  1662. this._postProcessMove.animations.push(animationPP);
  1663. var animationPP2 = new Animation("animationPP2", "vignetteStretch", 90, Animation.ANIMATIONTYPE_FLOAT,
  1664. Animation.ANIMATIONLOOPMODE_CONSTANT);
  1665. var vignetteStretchKeys = [];
  1666. vignetteStretchKeys.push({
  1667. frame: 0,
  1668. value: 0
  1669. });
  1670. vignetteStretchKeys.push({
  1671. frame: 3,
  1672. value: 10
  1673. });
  1674. vignetteStretchKeys.push({
  1675. frame: 6,
  1676. value: 0
  1677. });
  1678. animationPP2.setKeys(vignetteStretchKeys);
  1679. animationPP2.setEasingFunction(this._circleEase);
  1680. this._postProcessMove.animations.push(animationPP2);
  1681. this._postProcessMove.imageProcessingConfiguration.vignetteWeight = 0;
  1682. this._postProcessMove.imageProcessingConfiguration.vignetteStretch = 0;
  1683. this._postProcessMove.samples = 4;
  1684. this._webVRCamera.attachPostProcess(this._postProcessMove);
  1685. this._scene.beginAnimation(this._postProcessMove, 0, 6, false, 1, () => {
  1686. this._webVRCamera.detachPostProcess(this._postProcessMove);
  1687. });
  1688. this._scene.beginAnimation(this.currentVRCamera, 0, 6, false, 1);
  1689. }
  1690. private _moveTeleportationSelectorTo(hit: PickingInfo, gazer: VRExperienceHelperGazer, ray: Ray) {
  1691. if (hit.pickedPoint) {
  1692. if (gazer._teleportationRequestInitiated) {
  1693. this._displayTeleportationTarget();
  1694. this._haloCenter.copyFrom(hit.pickedPoint);
  1695. this._teleportationTarget.position.copyFrom(hit.pickedPoint);
  1696. }
  1697. var pickNormal = this._convertNormalToDirectionOfRay(hit.getNormal(true, false), ray);
  1698. if (pickNormal) {
  1699. var axis1 = Vector3.Cross(Axis.Y, pickNormal);
  1700. var axis2 = Vector3.Cross(pickNormal, axis1);
  1701. Vector3.RotationFromAxisToRef(axis2, pickNormal, axis1, this._teleportationTarget.rotation);
  1702. }
  1703. this._teleportationTarget.position.y += 0.1;
  1704. }
  1705. }
  1706. private _workingVector = Vector3.Zero();
  1707. private _workingQuaternion = Quaternion.Identity();
  1708. private _workingMatrix = Matrix.Identity();
  1709. /**
  1710. * Time Constant Teleportation Mode
  1711. */
  1712. public static readonly TELEPORTATIONMODE_CONSTANTTIME = 0;
  1713. /**
  1714. * Speed Constant Teleportation Mode
  1715. */
  1716. public static readonly TELEPORTATIONMODE_CONSTANTSPEED = 1;
  1717. /**
  1718. * Teleports the users feet to the desired location
  1719. * @param location The location where the user's feet should be placed
  1720. */
  1721. public teleportCamera(location: Vector3) {
  1722. if (!(this.currentVRCamera instanceof FreeCamera)) {
  1723. return;
  1724. }
  1725. // Teleport the hmd to where the user is looking by moving the anchor to where they are looking minus the
  1726. // offset of the headset from the anchor.
  1727. if (this.webVRCamera.leftCamera) {
  1728. this._workingVector.copyFrom(this.webVRCamera.leftCamera.globalPosition);
  1729. this._workingVector.subtractInPlace(this.webVRCamera.position);
  1730. location.subtractToRef(this._workingVector, this._workingVector);
  1731. } else {
  1732. this._workingVector.copyFrom(location);
  1733. }
  1734. // Add height to account for user's height offset
  1735. if (this.isInVRMode) {
  1736. this._workingVector.y += this.webVRCamera.deviceDistanceToRoomGround() * this._webVRCamera.deviceScaleFactor;
  1737. } else {
  1738. this._workingVector.y += this._defaultHeight;
  1739. }
  1740. this.onBeforeCameraTeleport.notifyObservers(this._workingVector);
  1741. // Animations FPS
  1742. const FPS = 90;
  1743. var speedRatio, lastFrame;
  1744. if (this._teleportationMode == VRExperienceHelper.TELEPORTATIONMODE_CONSTANTSPEED) {
  1745. lastFrame = FPS;
  1746. var dist = Vector3.Distance(this.currentVRCamera.position, this._workingVector);
  1747. speedRatio = this._teleportationSpeed / dist;
  1748. } else {
  1749. // teleportationMode is TELEPORTATIONMODE_CONSTANTTIME
  1750. lastFrame = Math.round(this._teleportationTime * FPS / 1000);
  1751. speedRatio = 1;
  1752. }
  1753. // Create animation from the camera's position to the new location
  1754. this.currentVRCamera.animations = [];
  1755. var animationCameraTeleportation = new Animation("animationCameraTeleportation", "position", FPS, Animation.ANIMATIONTYPE_VECTOR3, Animation.ANIMATIONLOOPMODE_CONSTANT);
  1756. var animationCameraTeleportationKeys = [{
  1757. frame: 0,
  1758. value: this.currentVRCamera.position
  1759. },
  1760. {
  1761. frame: lastFrame,
  1762. value: this._workingVector
  1763. }
  1764. ];
  1765. animationCameraTeleportation.setKeys(animationCameraTeleportationKeys);
  1766. animationCameraTeleportation.setEasingFunction(this._teleportationEasing);
  1767. this.currentVRCamera.animations.push(animationCameraTeleportation);
  1768. this._postProcessMove.animations = [];
  1769. // Calculate the mid frame for vignette animations
  1770. var midFrame = Math.round(lastFrame / 2);
  1771. var animationPP = new Animation("animationPP", "vignetteWeight", FPS, Animation.ANIMATIONTYPE_FLOAT,
  1772. Animation.ANIMATIONLOOPMODE_CONSTANT);
  1773. var vignetteWeightKeys = [];
  1774. vignetteWeightKeys.push({
  1775. frame: 0,
  1776. value: 0
  1777. });
  1778. vignetteWeightKeys.push({
  1779. frame: midFrame,
  1780. value: 8
  1781. });
  1782. vignetteWeightKeys.push({
  1783. frame: lastFrame,
  1784. value: 0
  1785. });
  1786. animationPP.setKeys(vignetteWeightKeys);
  1787. this._postProcessMove.animations.push(animationPP);
  1788. var animationPP2 = new Animation("animationPP2", "vignetteStretch", FPS, Animation.ANIMATIONTYPE_FLOAT,
  1789. Animation.ANIMATIONLOOPMODE_CONSTANT);
  1790. var vignetteStretchKeys = [];
  1791. vignetteStretchKeys.push({
  1792. frame: 0,
  1793. value: 0
  1794. });
  1795. vignetteStretchKeys.push({
  1796. frame: midFrame,
  1797. value: 10
  1798. });
  1799. vignetteStretchKeys.push({
  1800. frame: lastFrame,
  1801. value: 0
  1802. });
  1803. animationPP2.setKeys(vignetteStretchKeys);
  1804. this._postProcessMove.animations.push(animationPP2);
  1805. this._postProcessMove.imageProcessingConfiguration.vignetteWeight = 0;
  1806. this._postProcessMove.imageProcessingConfiguration.vignetteStretch = 0;
  1807. this._webVRCamera.attachPostProcess(this._postProcessMove);
  1808. this._scene.beginAnimation(this._postProcessMove, 0, lastFrame, false, speedRatio, () => {
  1809. this._webVRCamera.detachPostProcess(this._postProcessMove);
  1810. });
  1811. this._scene.beginAnimation(this.currentVRCamera, 0, lastFrame, false, speedRatio, () => {
  1812. this.onAfterCameraTeleport.notifyObservers(this._workingVector);
  1813. });
  1814. this._hideTeleportationTarget();
  1815. }
  1816. private _convertNormalToDirectionOfRay(normal: Nullable<Vector3>, ray: Ray) {
  1817. if (normal) {
  1818. var angle = Math.acos(Vector3.Dot(normal, ray.direction));
  1819. if (angle < Math.PI / 2) {
  1820. normal.scaleInPlace(-1);
  1821. }
  1822. }
  1823. return normal;
  1824. }
  1825. private _castRayAndSelectObject(gazer: VRExperienceHelperGazer) {
  1826. if (!(this.currentVRCamera instanceof FreeCamera)) {
  1827. return;
  1828. }
  1829. var ray = gazer._getForwardRay(this._rayLength);
  1830. var hit = this._scene.pickWithRay(ray, this._raySelectionPredicate);
  1831. if (hit) {
  1832. // Populate the controllers mesh that can be used for drag/drop
  1833. if ((<any>gazer)._laserPointer) {
  1834. hit.originMesh = (<any>gazer)._laserPointer.parent;
  1835. }
  1836. this._scene.simulatePointerMove(hit, { pointerId: gazer._id });
  1837. }
  1838. gazer._currentHit = hit;
  1839. // Moving the gazeTracker on the mesh face targetted
  1840. if (hit && hit.pickedPoint) {
  1841. if (this._displayGaze) {
  1842. let multiplier = 1;
  1843. gazer._gazeTracker.isVisible = true;
  1844. if (gazer._isActionableMesh) {
  1845. multiplier = 3;
  1846. }
  1847. if (this.updateGazeTrackerScale) {
  1848. gazer._gazeTracker.scaling.x = hit.distance * multiplier;
  1849. gazer._gazeTracker.scaling.y = hit.distance * multiplier;
  1850. gazer._gazeTracker.scaling.z = hit.distance * multiplier;
  1851. }
  1852. var pickNormal = this._convertNormalToDirectionOfRay(hit.getNormal(), ray);
  1853. // To avoid z-fighting
  1854. let deltaFighting = 0.002;
  1855. if (pickNormal) {
  1856. var axis1 = Vector3.Cross(Axis.Y, pickNormal);
  1857. var axis2 = Vector3.Cross(pickNormal, axis1);
  1858. Vector3.RotationFromAxisToRef(axis2, pickNormal, axis1, gazer._gazeTracker.rotation);
  1859. }
  1860. gazer._gazeTracker.position.copyFrom(hit.pickedPoint);
  1861. if (gazer._gazeTracker.position.x < 0) {
  1862. gazer._gazeTracker.position.x += deltaFighting;
  1863. }
  1864. else {
  1865. gazer._gazeTracker.position.x -= deltaFighting;
  1866. }
  1867. if (gazer._gazeTracker.position.y < 0) {
  1868. gazer._gazeTracker.position.y += deltaFighting;
  1869. }
  1870. else {
  1871. gazer._gazeTracker.position.y -= deltaFighting;
  1872. }
  1873. if (gazer._gazeTracker.position.z < 0) {
  1874. gazer._gazeTracker.position.z += deltaFighting;
  1875. }
  1876. else {
  1877. gazer._gazeTracker.position.z -= deltaFighting;
  1878. }
  1879. }
  1880. // Changing the size of the laser pointer based on the distance from the targetted point
  1881. gazer._updatePointerDistance(hit.distance);
  1882. }
  1883. else {
  1884. gazer._updatePointerDistance();
  1885. gazer._gazeTracker.isVisible = false;
  1886. }
  1887. if (hit && hit.pickedMesh) {
  1888. // The object selected is the floor, we're in a teleportation scenario
  1889. if (this._teleportationInitialized && this._isTeleportationFloor(hit.pickedMesh) && hit.pickedPoint) {
  1890. // Moving the teleportation area to this targetted point
  1891. //Raise onSelectedMeshUnselected observable if ray collided floor mesh/meshes and a non floor mesh was previously selected
  1892. if (gazer._currentMeshSelected && !this._isTeleportationFloor(gazer._currentMeshSelected)) {
  1893. this._notifySelectedMeshUnselected(gazer._currentMeshSelected);
  1894. }
  1895. gazer._currentMeshSelected = null;
  1896. if (gazer._teleportationRequestInitiated) {
  1897. this._moveTeleportationSelectorTo(hit, gazer, ray);
  1898. }
  1899. return;
  1900. }
  1901. // If not, we're in a selection scenario
  1902. //this._teleportationAllowed = false;
  1903. if (hit.pickedMesh !== gazer._currentMeshSelected) {
  1904. if (this.meshSelectionPredicate(hit.pickedMesh)) {
  1905. this.onNewMeshPicked.notifyObservers(hit);
  1906. gazer._currentMeshSelected = hit.pickedMesh;
  1907. if (hit.pickedMesh.isPickable && hit.pickedMesh.actionManager) {
  1908. this.changeGazeColor(this._pickedGazeColor);
  1909. this.changeLaserColor(this._pickedLaserColor);
  1910. gazer._isActionableMesh = true;
  1911. }
  1912. else {
  1913. this.changeGazeColor(this._gazeColor);
  1914. this.changeLaserColor(this._laserColor);
  1915. gazer._isActionableMesh = false;
  1916. }
  1917. try {
  1918. this.onNewMeshSelected.notifyObservers(hit.pickedMesh);
  1919. let gazerAsControllerGazer = gazer as VRExperienceHelperControllerGazer;
  1920. if (gazerAsControllerGazer.webVRController) {
  1921. this.onMeshSelectedWithController.notifyObservers({ mesh: hit.pickedMesh, controller: gazerAsControllerGazer.webVRController });
  1922. }
  1923. }
  1924. catch (err) {
  1925. Logger.Warn("Error while raising onNewMeshSelected or onMeshSelectedWithController: " + err);
  1926. }
  1927. }
  1928. else {
  1929. this._notifySelectedMeshUnselected(gazer._currentMeshSelected);
  1930. gazer._currentMeshSelected = null;
  1931. this.changeGazeColor(this._gazeColor);
  1932. this.changeLaserColor(this._laserColor);
  1933. }
  1934. }
  1935. }
  1936. else {
  1937. this._notifySelectedMeshUnselected(gazer._currentMeshSelected);
  1938. gazer._currentMeshSelected = null;
  1939. //this._teleportationAllowed = false;
  1940. this.changeGazeColor(this._gazeColor);
  1941. this.changeLaserColor(this._laserColor);
  1942. }
  1943. }
  1944. private _notifySelectedMeshUnselected(mesh: Nullable<AbstractMesh>) {
  1945. if (mesh) {
  1946. this.onSelectedMeshUnselected.notifyObservers(mesh);
  1947. }
  1948. }
  1949. /**
  1950. * Permanently set new colors for the laser pointer
  1951. * @param color the new laser color
  1952. * @param pickedColor the new laser color when picked mesh detected
  1953. */
  1954. public setLaserColor(color: Color3, pickedColor: Color3 = this._pickedLaserColor) {
  1955. this._laserColor = color;
  1956. this._pickedLaserColor = pickedColor;
  1957. }
  1958. /**
  1959. * Set lighting enabled / disabled on the laser pointer of both controllers
  1960. * @param enabled should the lighting be enabled on the laser pointer
  1961. */
  1962. public setLaserLightingState(enabled: boolean = true) {
  1963. if (this._leftController) {
  1964. this._leftController._setLaserPointerLightingDisabled(!enabled);
  1965. }
  1966. if (this._rightController) {
  1967. this._rightController._setLaserPointerLightingDisabled(!enabled);
  1968. }
  1969. }
  1970. /**
  1971. * Permanently set new colors for the gaze pointer
  1972. * @param color the new gaze color
  1973. * @param pickedColor the new gaze color when picked mesh detected
  1974. */
  1975. public setGazeColor(color: Color3, pickedColor: Color3 = this._pickedGazeColor) {
  1976. this._gazeColor = color;
  1977. this._pickedGazeColor = pickedColor;
  1978. }
  1979. /**
  1980. * Sets the color of the laser ray from the vr controllers.
  1981. * @param color new color for the ray.
  1982. */
  1983. public changeLaserColor(color: Color3) {
  1984. if (!this.updateControllerLaserColor) {
  1985. return;
  1986. }
  1987. if (this._leftController) {
  1988. this._leftController._setLaserPointerColor(color);
  1989. }
  1990. if (this._rightController) {
  1991. this._rightController._setLaserPointerColor(color);
  1992. }
  1993. }
  1994. /**
  1995. * Sets the color of the ray from the vr headsets gaze.
  1996. * @param color new color for the ray.
  1997. */
  1998. public changeGazeColor(color: Color3) {
  1999. if (!this.updateGazeTrackerColor) {
  2000. return;
  2001. }
  2002. if (!(<StandardMaterial>this._cameraGazer._gazeTracker.material)) {
  2003. return;
  2004. }
  2005. (<StandardMaterial>this._cameraGazer._gazeTracker.material).emissiveColor = color;
  2006. if (this._leftController) {
  2007. (<StandardMaterial>this._leftController._gazeTracker.material).emissiveColor = color;
  2008. }
  2009. if (this._rightController) {
  2010. (<StandardMaterial>this._rightController._gazeTracker.material).emissiveColor = color;
  2011. }
  2012. }
  2013. /**
  2014. * Exits VR and disposes of the vr experience helper
  2015. */
  2016. public dispose() {
  2017. if (this.isInVRMode) {
  2018. this.exitVR();
  2019. }
  2020. if (this._postProcessMove) {
  2021. this._postProcessMove.dispose();
  2022. }
  2023. if (this._webVRCamera) {
  2024. this._webVRCamera.dispose();
  2025. }
  2026. if (this._vrDeviceOrientationCamera) {
  2027. this._vrDeviceOrientationCamera.dispose();
  2028. }
  2029. if (!this._useCustomVRButton && this._btnVR && this._btnVR.parentNode) {
  2030. document.body.removeChild(this._btnVR);
  2031. }
  2032. if (this._deviceOrientationCamera && (this._scene.activeCamera != this._deviceOrientationCamera)) {
  2033. this._deviceOrientationCamera.dispose();
  2034. }
  2035. if (this._cameraGazer) {
  2036. this._cameraGazer.dispose();
  2037. }
  2038. if (this._leftController) {
  2039. this._leftController.dispose();
  2040. }
  2041. if (this._rightController) {
  2042. this._rightController.dispose();
  2043. }
  2044. if (this._teleportationTarget) {
  2045. this._teleportationTarget.dispose();
  2046. }
  2047. if (this.xr) {
  2048. this.xr.dispose();
  2049. }
  2050. this._floorMeshesCollection = [];
  2051. document.removeEventListener("keydown", this._onKeyDown);
  2052. window.removeEventListener('vrdisplaypresentchange', this._onVrDisplayPresentChange);
  2053. window.removeEventListener("resize", this._onResize);
  2054. document.removeEventListener("fullscreenchange", this._onFullscreenChange);
  2055. document.removeEventListener("mozfullscreenchange", this._onFullscreenChange);
  2056. document.removeEventListener("webkitfullscreenchange", this._onFullscreenChange);
  2057. document.removeEventListener("msfullscreenchange", this._onFullscreenChange);
  2058. (<any>document).onmsfullscreenchange = null;
  2059. this._scene.getEngine().onVRDisplayChangedObservable.removeCallback(this._onVRDisplayChanged);
  2060. this._scene.getEngine().onVRRequestPresentStart.removeCallback(this._onVRRequestPresentStart);
  2061. this._scene.getEngine().onVRRequestPresentComplete.removeCallback(this._onVRRequestPresentComplete);
  2062. window.removeEventListener('vrdisplaypresentchange', this._onVrDisplayPresentChange);
  2063. this._scene.gamepadManager.onGamepadConnectedObservable.removeCallback(this._onNewGamepadConnected);
  2064. this._scene.gamepadManager.onGamepadDisconnectedObservable.removeCallback(this._onNewGamepadDisconnected);
  2065. this._scene.unregisterBeforeRender(this.beforeRender);
  2066. }
  2067. /**
  2068. * Gets the name of the VRExperienceHelper class
  2069. * @returns "VRExperienceHelper"
  2070. */
  2071. public getClassName(): string {
  2072. return "VRExperienceHelper";
  2073. }
  2074. }