vrExperienceHelper.ts 98 KB

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