babylon.vrExperienceHelper.ts 86 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967
  1. module BABYLON {
  2. /**
  3. * Options to modify the vr teleportation behavior.
  4. */
  5. export interface VRTeleportationOptions {
  6. /**
  7. * The name of the mesh which should be used as the teleportation floor. (default: null)
  8. */
  9. floorMeshName?: string;
  10. /**
  11. * A list of meshes to be used as the teleportation floor. (default: empty)
  12. */
  13. floorMeshes?: Mesh[];
  14. }
  15. /**
  16. * Options to modify the vr experience helper's behavior.
  17. */
  18. export interface VRExperienceHelperOptions extends WebVROptions {
  19. /**
  20. * Create a DeviceOrientationCamera to be used as your out of vr camera. (default: true)
  21. */
  22. createDeviceOrientationCamera?: boolean;
  23. /**
  24. * Create a VRDeviceOrientationFreeCamera to be used for VR when no external HMD is found. (default: true)
  25. */
  26. createFallbackVRDeviceOrientationFreeCamera?: boolean;
  27. /**
  28. * Uses the main button on the controller to toggle the laser casted. (default: true)
  29. */
  30. laserToggle?: boolean;
  31. /**
  32. * A list of meshes to be used as the teleportation floor. If specified, teleportation will be enabled (default: undefined)
  33. */
  34. floorMeshes?: Mesh[];
  35. }
  36. class VRExperienceHelperGazer implements IDisposable {
  37. /** @hidden */
  38. public _gazeTracker: Mesh;
  39. /** @hidden */
  40. public _currentMeshSelected: Nullable<AbstractMesh>;
  41. /** @hidden */
  42. public _currentHit: Nullable<PickingInfo>;
  43. public static _idCounter = 0;
  44. /** @hidden */
  45. public _id: number;
  46. /** @hidden */
  47. public _pointerDownOnMeshAsked: boolean = false;
  48. /** @hidden */
  49. public _isActionableMesh: boolean = false;
  50. /** @hidden */
  51. public _interactionsEnabled: boolean;
  52. /** @hidden */
  53. public _teleportationEnabled: boolean;
  54. /** @hidden */
  55. public _teleportationRequestInitiated = false;
  56. /** @hidden */
  57. public _teleportationBackRequestInitiated = false;
  58. /** @hidden */
  59. public _rotationRightAsked = false;
  60. /** @hidden */
  61. public _rotationLeftAsked = false;
  62. /** @hidden */
  63. public _dpadPressed = true;
  64. /** @hidden */
  65. public _activePointer = false;
  66. constructor(public scene: Scene, gazeTrackerToClone: Nullable<Mesh> = null) {
  67. this._id = VRExperienceHelperGazer._idCounter++;
  68. // Gaze tracker
  69. if (!gazeTrackerToClone) {
  70. this._gazeTracker = Mesh.CreateTorus("gazeTracker", 0.0035, 0.0025, 20, scene, false);
  71. this._gazeTracker.bakeCurrentTransformIntoVertices();
  72. this._gazeTracker.isPickable = false;
  73. this._gazeTracker.isVisible = false;
  74. var targetMat = new StandardMaterial("targetMat", scene);
  75. targetMat.specularColor = Color3.Black();
  76. targetMat.emissiveColor = new Color3(0.7, 0.7, 0.7);
  77. targetMat.backFaceCulling = false;
  78. this._gazeTracker.material = targetMat;
  79. } else {
  80. this._gazeTracker = gazeTrackerToClone.clone("gazeTracker");
  81. }
  82. }
  83. /** @hidden */
  84. public _getForwardRay(length: number): Ray {
  85. return new Ray(Vector3.Zero(), new Vector3(0, 0, length));
  86. }
  87. /** @hidden */
  88. public _selectionPointerDown() {
  89. this._pointerDownOnMeshAsked = true;
  90. if (this._currentHit) {
  91. this.scene.simulatePointerDown(this._currentHit, { pointerId: this._id });
  92. }
  93. }
  94. /** @hidden */
  95. public _selectionPointerUp() {
  96. if (this._currentHit) {
  97. this.scene.simulatePointerUp(this._currentHit, { pointerId: this._id });
  98. }
  99. this._pointerDownOnMeshAsked = false;
  100. }
  101. /** @hidden */
  102. public _activatePointer() {
  103. this._activePointer = true;
  104. }
  105. /** @hidden */
  106. public _deactivatePointer() {
  107. this._activePointer = false;
  108. }
  109. /** @hidden */
  110. public _updatePointerDistance(distance: number = 100) {
  111. }
  112. public dispose() {
  113. this._interactionsEnabled = false;
  114. this._teleportationEnabled = false;
  115. if (this._gazeTracker) {
  116. this._gazeTracker.dispose();
  117. }
  118. }
  119. }
  120. class VRExperienceHelperControllerGazer extends VRExperienceHelperGazer {
  121. private _laserPointer: Mesh;
  122. private _meshAttachedObserver: Nullable<Observer<AbstractMesh>>;
  123. constructor(public webVRController: WebVRController, scene: Scene, gazeTrackerToClone: Mesh) {
  124. super(scene, gazeTrackerToClone);
  125. // Laser pointer
  126. this._laserPointer = Mesh.CreateCylinder("laserPointer", 1, 0.004, 0.0002, 20, 1, scene, false);
  127. var laserPointerMaterial = new StandardMaterial("laserPointerMat", scene);
  128. laserPointerMaterial.emissiveColor = new Color3(0.7, 0.7, 0.7);
  129. laserPointerMaterial.alpha = 0.6;
  130. this._laserPointer.material = laserPointerMaterial;
  131. this._laserPointer.rotation.x = Math.PI / 2;
  132. this._laserPointer.position.z = -0.5;
  133. this._laserPointer.isVisible = false;
  134. this._laserPointer.isPickable = false;
  135. if (!webVRController.mesh) {
  136. // Create an empty mesh that is used prior to loading the high quality model
  137. var preloadMesh = new Mesh("preloadControllerMesh", scene);
  138. var preloadPointerPose = new Mesh(PoseEnabledController.POINTING_POSE, scene);
  139. preloadPointerPose.rotation.x = -0.7;
  140. preloadMesh.addChild(preloadPointerPose);
  141. webVRController.attachToMesh(preloadMesh);
  142. }
  143. this._setLaserPointerParent(webVRController.mesh!);
  144. this._meshAttachedObserver = webVRController._meshAttachedObservable.add((mesh) => {
  145. this._setLaserPointerParent(mesh);
  146. });
  147. }
  148. _getForwardRay(length: number): Ray {
  149. return this.webVRController.getForwardRay(length);
  150. }
  151. /** @hidden */
  152. public _activatePointer() {
  153. super._activatePointer();
  154. this._laserPointer.isVisible = true;
  155. }
  156. /** @hidden */
  157. public _deactivatePointer() {
  158. super._deactivatePointer();
  159. this._laserPointer.isVisible = false;
  160. }
  161. /** @hidden */
  162. public _setLaserPointerColor(color: Color3) {
  163. (<StandardMaterial>this._laserPointer.material).emissiveColor = color;
  164. }
  165. /** @hidden */
  166. public _setLaserPointerParent(mesh: AbstractMesh) {
  167. var makeNotPick = (root: AbstractMesh) => {
  168. root.isPickable = false;
  169. root.getChildMeshes().forEach((c) => {
  170. makeNotPick(c);
  171. });
  172. };
  173. makeNotPick(mesh);
  174. var meshChildren = mesh.getChildren(undefined, false);
  175. let laserParent: TransformNode = mesh;
  176. this.webVRController._pointingPoseNode = null;
  177. for (var i = 0; i < meshChildren.length; i++) {
  178. if (meshChildren[i].name && meshChildren[i].name.indexOf(PoseEnabledController.POINTING_POSE) >= 0) {
  179. laserParent = <TransformNode>meshChildren[i];
  180. this.webVRController._pointingPoseNode = laserParent;
  181. break;
  182. }
  183. }
  184. this._laserPointer.parent = laserParent;
  185. }
  186. public _updatePointerDistance(distance: number = 100) {
  187. this._laserPointer.scaling.y = distance;
  188. this._laserPointer.position.z = -distance / 2;
  189. }
  190. dispose() {
  191. super.dispose();
  192. this._laserPointer.dispose();
  193. if (this._meshAttachedObserver) {
  194. this.webVRController._meshAttachedObservable.remove(this._meshAttachedObserver);
  195. }
  196. }
  197. }
  198. class VRExperienceHelperCameraGazer extends VRExperienceHelperGazer {
  199. constructor(private getCamera: () => Nullable<Camera>, scene: Scene) {
  200. super(scene);
  201. }
  202. _getForwardRay(length: number): Ray {
  203. var camera = this.getCamera();
  204. if (camera) {
  205. return camera.getForwardRay(length);
  206. } else {
  207. return new Ray(Vector3.Zero(), Vector3.Forward());
  208. }
  209. }
  210. }
  211. /**
  212. * Helps to quickly add VR support to an existing scene.
  213. * See http://doc.babylonjs.com/how_to/webvr_helper
  214. */
  215. export class VRExperienceHelper {
  216. private _scene: Scene;
  217. private _position: Vector3;
  218. private _btnVR: HTMLButtonElement;
  219. private _btnVRDisplayed: boolean;
  220. // Can the system support WebVR, even if a headset isn't plugged in?
  221. private _webVRsupported = false;
  222. // If WebVR is supported, is a headset plugged in and are we ready to present?
  223. private _webVRready = false;
  224. // Are we waiting for the requestPresent callback to complete?
  225. private _webVRrequesting = false;
  226. // Are we presenting to the headset right now? (this is the vrDevice state)
  227. private _webVRpresenting = false;
  228. // Have we entered VR? (this is the VRExperienceHelper state)
  229. private _hasEnteredVR: boolean;
  230. // Are we presenting in the fullscreen fallback?
  231. private _fullscreenVRpresenting = false;
  232. private _canvas: Nullable<HTMLCanvasElement>;
  233. private _webVRCamera: WebVRFreeCamera;
  234. private _vrDeviceOrientationCamera: Nullable<VRDeviceOrientationFreeCamera>;
  235. private _deviceOrientationCamera: Nullable<DeviceOrientationCamera>;
  236. private _existingCamera: Camera;
  237. private _onKeyDown: (event: KeyboardEvent) => void;
  238. private _onVrDisplayPresentChange: any;
  239. private _onVRDisplayChanged: (eventArgs: IDisplayChangedEventArgs) => void;
  240. private _onVRRequestPresentStart: () => void;
  241. private _onVRRequestPresentComplete: (success: boolean) => void;
  242. /**
  243. * Observable raised when entering VR.
  244. */
  245. public onEnteringVRObservable = new Observable<VRExperienceHelper>();
  246. /**
  247. * Observable raised when exiting VR.
  248. */
  249. public onExitingVRObservable = new Observable<VRExperienceHelper>();
  250. /**
  251. * Observable raised when controller mesh is loaded.
  252. */
  253. public onControllerMeshLoadedObservable = new Observable<WebVRController>();
  254. /** Return this.onEnteringVRObservable
  255. * Note: This one is for backward compatibility. Please use onEnteringVRObservable directly
  256. */
  257. public get onEnteringVR(): Observable<VRExperienceHelper> {
  258. return this.onEnteringVRObservable;
  259. }
  260. /** Return this.onExitingVRObservable
  261. * Note: This one is for backward compatibility. Please use onExitingVRObservable directly
  262. */
  263. public get onExitingVR(): Observable<VRExperienceHelper> {
  264. return this.onExitingVRObservable;
  265. }
  266. /** Return this.onControllerMeshLoadedObservable
  267. * Note: This one is for backward compatibility. Please use onControllerMeshLoadedObservable directly
  268. */
  269. public get onControllerMeshLoaded(): Observable<WebVRController> {
  270. return this.onControllerMeshLoadedObservable;
  271. }
  272. private _rayLength: number;
  273. private _useCustomVRButton: boolean = false;
  274. private _teleportationRequested: boolean = false;
  275. private _teleportActive = false;
  276. private _floorMeshName: string;
  277. private _floorMeshesCollection: Mesh[] = [];
  278. private _rotationAllowed: boolean = true;
  279. private _teleportBackwardsVector = new Vector3(0, -1, -1);
  280. private _teleportationTarget: Mesh;
  281. private _isDefaultTeleportationTarget = true;
  282. private _postProcessMove: ImageProcessingPostProcess;
  283. private _teleportationFillColor: string = "#444444";
  284. private _teleportationBorderColor: string = "#FFFFFF";
  285. private _rotationAngle: number = 0;
  286. private _haloCenter = new Vector3(0, 0, 0);
  287. private _cameraGazer: VRExperienceHelperCameraGazer;
  288. private _padSensibilityUp = 0.65;
  289. private _padSensibilityDown = 0.35;
  290. private _leftController: Nullable<VRExperienceHelperControllerGazer> = null;
  291. private _rightController: Nullable<VRExperienceHelperControllerGazer> = null;
  292. /**
  293. * Observable raised when a new mesh is selected based on meshSelectionPredicate
  294. */
  295. public onNewMeshSelected = new Observable<AbstractMesh>();
  296. /**
  297. * Observable raised when a new mesh is picked based on meshSelectionPredicate
  298. */
  299. public onNewMeshPicked = new Observable<PickingInfo>();
  300. private _circleEase: CircleEase;
  301. /**
  302. * Observable raised before camera teleportation
  303. */
  304. public onBeforeCameraTeleport = new Observable<Vector3>();
  305. /**
  306. * Observable raised after camera teleportation
  307. */
  308. public onAfterCameraTeleport = new Observable<Vector3>();
  309. /**
  310. * Observable raised when current selected mesh gets unselected
  311. */
  312. public onSelectedMeshUnselected = new Observable<AbstractMesh>();
  313. private _raySelectionPredicate: (mesh: AbstractMesh) => boolean;
  314. /**
  315. * To be optionaly changed by user to define custom ray selection
  316. */
  317. public raySelectionPredicate: (mesh: AbstractMesh) => boolean;
  318. /**
  319. * To be optionaly changed by user to define custom selection logic (after ray selection)
  320. */
  321. public meshSelectionPredicate: (mesh: AbstractMesh) => boolean;
  322. /**
  323. * Set teleportation enabled. If set to false camera teleportation will be disabled but camera rotation will be kept.
  324. */
  325. public teleportationEnabled: boolean = true;
  326. private _defaultHeight: number;
  327. private _teleportationInitialized = false;
  328. private _interactionsEnabled = false;
  329. private _interactionsRequested = false;
  330. private _displayGaze = true;
  331. private _displayLaserPointer = true;
  332. /**
  333. * The mesh used to display where the user is going to teleport.
  334. */
  335. public get teleportationTarget(): Mesh {
  336. return this._teleportationTarget;
  337. }
  338. /**
  339. * Sets the mesh to be used to display where the user is going to teleport.
  340. */
  341. public set teleportationTarget(value: Mesh) {
  342. if (value) {
  343. value.name = "teleportationTarget";
  344. this._isDefaultTeleportationTarget = false;
  345. this._teleportationTarget = value;
  346. }
  347. }
  348. /**
  349. * 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
  350. * when set bakeCurrentTransformIntoVertices will be called on the mesh.
  351. * See http://doc.babylonjs.com/resources/baking_transformations
  352. */
  353. public get gazeTrackerMesh(): Mesh {
  354. return this._cameraGazer._gazeTracker;
  355. }
  356. public set gazeTrackerMesh(value: Mesh) {
  357. if (value) {
  358. // Dispose of existing meshes
  359. if (this._cameraGazer._gazeTracker) {
  360. this._cameraGazer._gazeTracker.dispose();
  361. }
  362. if (this._leftController && this._leftController._gazeTracker) {
  363. this._leftController._gazeTracker.dispose();
  364. }
  365. if (this._rightController && this._rightController._gazeTracker) {
  366. this._rightController._gazeTracker.dispose();
  367. }
  368. // Set and create gaze trackers on head and controllers
  369. this._cameraGazer._gazeTracker = value;
  370. this._cameraGazer._gazeTracker.bakeCurrentTransformIntoVertices();
  371. this._cameraGazer._gazeTracker.isPickable = false;
  372. this._cameraGazer._gazeTracker.isVisible = false;
  373. this._cameraGazer._gazeTracker.name = "gazeTracker";
  374. if (this._leftController) {
  375. this._leftController._gazeTracker = this._cameraGazer._gazeTracker.clone("gazeTracker");
  376. }
  377. if (this._rightController) {
  378. this._rightController._gazeTracker = this._cameraGazer._gazeTracker.clone("gazeTracker");
  379. }
  380. }
  381. }
  382. /**
  383. * If the gaze trackers scale should be updated to be constant size when pointing at near/far meshes
  384. */
  385. public updateGazeTrackerScale = true;
  386. /**
  387. * The gaze tracking mesh corresponding to the left controller
  388. */
  389. public get leftControllerGazeTrackerMesh(): Nullable<Mesh> {
  390. if (this._leftController) {
  391. return this._leftController._gazeTracker;
  392. }
  393. return null;
  394. }
  395. /**
  396. * The gaze tracking mesh corresponding to the right controller
  397. */
  398. public get rightControllerGazeTrackerMesh(): Nullable<Mesh> {
  399. if (this._rightController) {
  400. return this._rightController._gazeTracker;
  401. }
  402. return null;
  403. }
  404. /**
  405. * If the ray of the gaze should be displayed.
  406. */
  407. public get displayGaze(): boolean {
  408. return this._displayGaze;
  409. }
  410. /**
  411. * Sets if the ray of the gaze should be displayed.
  412. */
  413. public set displayGaze(value: boolean) {
  414. this._displayGaze = value;
  415. if (!value) {
  416. this._cameraGazer._gazeTracker.isVisible = false;
  417. if (this._leftController) {
  418. this._leftController._gazeTracker.isVisible = false;
  419. }
  420. if (this._rightController) {
  421. this._rightController._gazeTracker.isVisible = false;
  422. }
  423. }
  424. }
  425. /**
  426. * If the ray of the LaserPointer should be displayed.
  427. */
  428. public get displayLaserPointer(): boolean {
  429. return this._displayLaserPointer;
  430. }
  431. /**
  432. * Sets if the ray of the LaserPointer should be displayed.
  433. */
  434. public set displayLaserPointer(value: boolean) {
  435. this._displayLaserPointer = value;
  436. if (!value) {
  437. if (this._rightController) {
  438. this._rightController._deactivatePointer();
  439. this._rightController._gazeTracker.isVisible = false;
  440. }
  441. if (this._leftController) {
  442. this._leftController._deactivatePointer();
  443. this._leftController._gazeTracker.isVisible = false;
  444. }
  445. }
  446. else {
  447. if (this._rightController) {
  448. this._rightController._activatePointer();
  449. }
  450. if (this._leftController) {
  451. this._leftController._activatePointer();
  452. }
  453. }
  454. }
  455. /**
  456. * The deviceOrientationCamera used as the camera when not in VR.
  457. */
  458. public get deviceOrientationCamera(): Nullable<DeviceOrientationCamera> {
  459. return this._deviceOrientationCamera;
  460. }
  461. /**
  462. * Based on the current WebVR support, returns the current VR camera used.
  463. */
  464. public get currentVRCamera(): Nullable<Camera> {
  465. if (this._webVRready) {
  466. return this._webVRCamera;
  467. }
  468. else {
  469. return this._scene.activeCamera;
  470. }
  471. }
  472. /**
  473. * The webVRCamera which is used when in VR.
  474. */
  475. public get webVRCamera(): WebVRFreeCamera {
  476. return this._webVRCamera;
  477. }
  478. /**
  479. * The deviceOrientationCamera that is used as a fallback when vr device is not connected.
  480. */
  481. public get vrDeviceOrientationCamera(): Nullable<VRDeviceOrientationFreeCamera> {
  482. return this._vrDeviceOrientationCamera;
  483. }
  484. private get _teleportationRequestInitiated(): boolean {
  485. var result = this._cameraGazer._teleportationRequestInitiated
  486. || (this._leftController !== null && this._leftController._teleportationRequestInitiated)
  487. || (this._rightController !== null && this._rightController._teleportationRequestInitiated);
  488. return result;
  489. }
  490. /**
  491. * Instantiates a VRExperienceHelper.
  492. * Helps to quickly add VR support to an existing scene.
  493. * @param scene The scene the VRExperienceHelper belongs to.
  494. * @param webVROptions Options to modify the vr experience helper's behavior.
  495. */
  496. constructor(scene: Scene,
  497. /** Options to modify the vr experience helper's behavior. */
  498. public webVROptions: VRExperienceHelperOptions = {}) {
  499. this._scene = scene;
  500. this._canvas = scene.getEngine().getRenderingCanvas();
  501. // Parse options
  502. if (webVROptions.createFallbackVRDeviceOrientationFreeCamera === undefined) {
  503. webVROptions.createFallbackVRDeviceOrientationFreeCamera = true;
  504. }
  505. if (webVROptions.createDeviceOrientationCamera === undefined) {
  506. webVROptions.createDeviceOrientationCamera = true;
  507. }
  508. if (webVROptions.laserToggle === undefined) {
  509. webVROptions.laserToggle = true;
  510. }
  511. if (webVROptions.defaultHeight === undefined) {
  512. webVROptions.defaultHeight = 1.7;
  513. }
  514. if (webVROptions.useCustomVRButton) {
  515. this._useCustomVRButton = true;
  516. if (webVROptions.customVRButton) {
  517. this._btnVR = webVROptions.customVRButton;
  518. }
  519. }
  520. if (webVROptions.rayLength) {
  521. this._rayLength = webVROptions.rayLength;
  522. }
  523. this._defaultHeight = webVROptions.defaultHeight;
  524. if (webVROptions.positionScale) {
  525. this._rayLength *= webVROptions.positionScale;
  526. this._defaultHeight *= webVROptions.positionScale;
  527. }
  528. this._hasEnteredVR = false;
  529. // Set position
  530. if (this._scene.activeCamera) {
  531. this._position = this._scene.activeCamera.position.clone();
  532. } else {
  533. this._position = new Vector3(0, this._defaultHeight, 0);
  534. }
  535. // Set non-vr camera
  536. if (webVROptions.createDeviceOrientationCamera || !this._scene.activeCamera) {
  537. this._deviceOrientationCamera = new DeviceOrientationCamera("deviceOrientationVRHelper", this._position.clone(), scene);
  538. // Copy data from existing camera
  539. if (this._scene.activeCamera) {
  540. this._deviceOrientationCamera.minZ = this._scene.activeCamera.minZ;
  541. this._deviceOrientationCamera.maxZ = this._scene.activeCamera.maxZ;
  542. // Set rotation from previous camera
  543. if (this._scene.activeCamera instanceof TargetCamera && this._scene.activeCamera.rotation) {
  544. var targetCamera = this._scene.activeCamera;
  545. if (targetCamera.rotationQuaternion) {
  546. this._deviceOrientationCamera.rotationQuaternion.copyFrom(targetCamera.rotationQuaternion);
  547. } else {
  548. this._deviceOrientationCamera.rotationQuaternion.copyFrom(Quaternion.RotationYawPitchRoll(targetCamera.rotation.y, targetCamera.rotation.x, targetCamera.rotation.z));
  549. }
  550. this._deviceOrientationCamera.rotation = targetCamera.rotation.clone();
  551. }
  552. }
  553. this._scene.activeCamera = this._deviceOrientationCamera;
  554. if (this._canvas) {
  555. this._scene.activeCamera.attachControl(this._canvas);
  556. }
  557. } else {
  558. this._existingCamera = this._scene.activeCamera;
  559. }
  560. // Create VR cameras
  561. if (webVROptions.createFallbackVRDeviceOrientationFreeCamera) {
  562. this._vrDeviceOrientationCamera = new VRDeviceOrientationFreeCamera("VRDeviceOrientationVRHelper", this._position, this._scene);
  563. this._vrDeviceOrientationCamera.angularSensibility = Number.MAX_VALUE;
  564. }
  565. this._webVRCamera = new WebVRFreeCamera("WebVRHelper", this._position, this._scene, webVROptions);
  566. this._webVRCamera.useStandingMatrix();
  567. this._cameraGazer = new VRExperienceHelperCameraGazer(() => { return this.currentVRCamera; }, scene);
  568. // Create default button
  569. if (!this._useCustomVRButton) {
  570. this._btnVR = <HTMLButtonElement>document.createElement("BUTTON");
  571. this._btnVR.className = "babylonVRicon";
  572. this._btnVR.id = "babylonVRiconbtn";
  573. this._btnVR.title = "Click to switch to VR";
  574. var css = ".babylonVRicon { position: absolute; right: 20px; height: 50px; width: 80px; background-color: rgba(51,51,51,0.7); background-image: url(data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%222048%22%20height%3D%221152%22%20viewBox%3D%220%200%202048%201152%22%20version%3D%221.1%22%3E%3Cpath%20transform%3D%22rotate%28180%201024%2C576.0000000000001%29%22%20d%3D%22m1109%2C896q17%2C0%2030%2C-12t13%2C-30t-12.5%2C-30.5t-30.5%2C-12.5l-170%2C0q-18%2C0%20-30.5%2C12.5t-12.5%2C30.5t13%2C30t30%2C12l170%2C0zm-85%2C256q59%2C0%20132.5%2C-1.5t154.5%2C-5.5t164.5%2C-11.5t163%2C-20t150%2C-30t124.5%2C-41.5q23%2C-11%2042%2C-24t38%2C-30q27%2C-25%2041%2C-61.5t14%2C-72.5l0%2C-257q0%2C-123%20-47%2C-232t-128%2C-190t-190%2C-128t-232%2C-47l-81%2C0q-37%2C0%20-68.5%2C14t-60.5%2C34.5t-55.5%2C45t-53%2C45t-53%2C34.5t-55.5%2C14t-55.5%2C-14t-53%2C-34.5t-53%2C-45t-55.5%2C-45t-60.5%2C-34.5t-68.5%2C-14l-81%2C0q-123%2C0%20-232%2C47t-190%2C128t-128%2C190t-47%2C232l0%2C257q0%2C68%2038%2C115t97%2C73q54%2C24%20124.5%2C41.5t150%2C30t163%2C20t164.5%2C11.5t154.5%2C5.5t132.5%2C1.5zm939%2C-298q0%2C39%20-24.5%2C67t-58.5%2C42q-54%2C23%20-122%2C39.5t-143.5%2C28t-155.5%2C19t-157%2C11t-148.5%2C5t-129.5%2C1.5q-59%2C0%20-130%2C-1.5t-148%2C-5t-157%2C-11t-155.5%2C-19t-143.5%2C-28t-122%2C-39.5q-34%2C-14%20-58.5%2C-42t-24.5%2C-67l0%2C-257q0%2C-106%2040.5%2C-199t110%2C-162.5t162.5%2C-109.5t199%2C-40l81%2C0q27%2C0%2052%2C14t50%2C34.5t51%2C44.5t55.5%2C44.5t63.5%2C34.5t74%2C14t74%2C-14t63.5%2C-34.5t55.5%2C-44.5t51%2C-44.5t50%2C-34.5t52%2C-14l14%2C0q37%2C0%2070%2C0.5t64.5%2C4.5t63.5%2C12t68%2C23q71%2C30%20128.5%2C78.5t98.5%2C110t63.5%2C133.5t22.5%2C149l0%2C257z%22%20fill%3D%22white%22%20/%3E%3C/svg%3E%0A); background-size: 80%; background-repeat:no-repeat; background-position: center; border: none; outline: none; transition: transform 0.125s ease-out } .babylonVRicon:hover { transform: scale(1.05) } .babylonVRicon:active {background-color: rgba(51,51,51,1) } .babylonVRicon:focus {background-color: rgba(51,51,51,1) }";
  575. css += ".babylonVRicon.vrdisplaypresenting { display: none; }";
  576. // TODO: Add user feedback so that they know what state the VRDisplay is in (disconnected, connected, entering-VR)
  577. // css += ".babylonVRicon.vrdisplaysupported { }";
  578. // css += ".babylonVRicon.vrdisplayready { }";
  579. // css += ".babylonVRicon.vrdisplayrequesting { }";
  580. var style = document.createElement('style');
  581. style.appendChild(document.createTextNode(css));
  582. document.getElementsByTagName('head')[0].appendChild(style);
  583. this.moveButtonToBottomRight();
  584. }
  585. // VR button click event
  586. if (this._btnVR) {
  587. this._btnVR.addEventListener("click", () => {
  588. if (!this.isInVRMode) {
  589. this.enterVR();
  590. } else {
  591. this.exitVR();
  592. }
  593. });
  594. }
  595. // Window events
  596. window.addEventListener("resize", this._onResize);
  597. document.addEventListener("fullscreenchange", this._onFullscreenChange, false);
  598. document.addEventListener("mozfullscreenchange", this._onFullscreenChange, false);
  599. document.addEventListener("webkitfullscreenchange", this._onFullscreenChange, false);
  600. document.addEventListener("msfullscreenchange", this._onFullscreenChange, false);
  601. (<any>document).onmsfullscreenchange = this._onFullscreenChange;
  602. // Display vr button when headset is connected
  603. if (webVROptions.createFallbackVRDeviceOrientationFreeCamera) {
  604. this.displayVRButton();
  605. } else {
  606. this._scene.getEngine().onVRDisplayChangedObservable.add((e) => {
  607. if (e.vrDisplay) {
  608. this.displayVRButton();
  609. }
  610. });
  611. }
  612. // Exiting VR mode using 'ESC' key on desktop
  613. this._onKeyDown = (event: KeyboardEvent) => {
  614. if (event.keyCode === 27 && this.isInVRMode) {
  615. this.exitVR();
  616. }
  617. };
  618. document.addEventListener("keydown", this._onKeyDown);
  619. // Exiting VR mode double tapping the touch screen
  620. this._scene.onPrePointerObservable.add((pointerInfo, eventState) => {
  621. if (this.isInVRMode) {
  622. this.exitVR();
  623. if (this._fullscreenVRpresenting) {
  624. this._scene.getEngine().switchFullscreen(true);
  625. }
  626. }
  627. }, PointerEventTypes.POINTERDOUBLETAP, false);
  628. // Listen for WebVR display changes
  629. this._onVRDisplayChanged = (eventArgs: IDisplayChangedEventArgs) => this.onVRDisplayChanged(eventArgs);
  630. this._onVrDisplayPresentChange = () => this.onVrDisplayPresentChange();
  631. this._onVRRequestPresentStart = () => {
  632. this._webVRrequesting = true;
  633. this.updateButtonVisibility();
  634. };
  635. this._onVRRequestPresentComplete = (success: boolean) => {
  636. this._webVRrequesting = false;
  637. this.updateButtonVisibility();
  638. };
  639. scene.getEngine().onVRDisplayChangedObservable.add(this._onVRDisplayChanged);
  640. scene.getEngine().onVRRequestPresentStart.add(this._onVRRequestPresentStart);
  641. scene.getEngine().onVRRequestPresentComplete.add(this._onVRRequestPresentComplete);
  642. window.addEventListener('vrdisplaypresentchange', this._onVrDisplayPresentChange);
  643. scene.onDisposeObservable.add(() => {
  644. this.dispose();
  645. });
  646. // Gamepad connection events
  647. this._webVRCamera.onControllerMeshLoadedObservable.add((webVRController) => this._onDefaultMeshLoaded(webVRController));
  648. this._scene.gamepadManager.onGamepadConnectedObservable.add(this._onNewGamepadConnected);
  649. this._scene.gamepadManager.onGamepadDisconnectedObservable.add(this._onNewGamepadDisconnected);
  650. this.updateButtonVisibility();
  651. //create easing functions
  652. this._circleEase = new CircleEase();
  653. this._circleEase.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT);
  654. if (this.webVROptions.floorMeshes) {
  655. this.enableTeleportation({ floorMeshes: this.webVROptions.floorMeshes });
  656. }
  657. }
  658. // Raised when one of the controller has loaded successfully its associated default mesh
  659. private _onDefaultMeshLoaded(webVRController: WebVRController) {
  660. if (this._leftController && this._leftController.webVRController == webVRController) {
  661. if (webVRController.mesh) {
  662. this._leftController._setLaserPointerParent(webVRController.mesh);
  663. }
  664. }
  665. if (this._rightController && this._rightController.webVRController == webVRController) {
  666. if (webVRController.mesh) {
  667. this._rightController._setLaserPointerParent(webVRController.mesh);
  668. }
  669. }
  670. try {
  671. this.onControllerMeshLoadedObservable.notifyObservers(webVRController);
  672. }
  673. catch (err) {
  674. Tools.Warn("Error in your custom logic onControllerMeshLoaded: " + err);
  675. }
  676. }
  677. private _onResize = () => {
  678. this.moveButtonToBottomRight();
  679. if (this._fullscreenVRpresenting && this._webVRready) {
  680. this.exitVR();
  681. }
  682. }
  683. private _onFullscreenChange = () => {
  684. if ((<any>document).fullscreen !== undefined) {
  685. this._fullscreenVRpresenting = (<any>document).fullscreen;
  686. } else if (document.mozFullScreen !== undefined) {
  687. this._fullscreenVRpresenting = document.mozFullScreen;
  688. } else if (document.webkitIsFullScreen !== undefined) {
  689. this._fullscreenVRpresenting = document.webkitIsFullScreen;
  690. } else if (document.msIsFullScreen !== undefined) {
  691. this._fullscreenVRpresenting = document.msIsFullScreen;
  692. } else if ((<any>document).msFullscreenElement !== undefined) {
  693. this._fullscreenVRpresenting = (<any>document).msFullscreenElement;
  694. }
  695. if (!this._fullscreenVRpresenting && this._canvas) {
  696. this.exitVR();
  697. if (!this._useCustomVRButton) {
  698. this._btnVR.style.top = this._canvas.offsetTop + this._canvas.offsetHeight - 70 + "px";
  699. this._btnVR.style.left = this._canvas.offsetLeft + this._canvas.offsetWidth - 100 + "px";
  700. }
  701. }
  702. }
  703. /**
  704. * Gets a value indicating if we are currently in VR mode.
  705. */
  706. public get isInVRMode(): boolean {
  707. return this._webVRpresenting || this._fullscreenVRpresenting;
  708. }
  709. private onVrDisplayPresentChange() {
  710. var vrDisplay = this._scene.getEngine().getVRDevice();
  711. if (vrDisplay) {
  712. var wasPresenting = this._webVRpresenting;
  713. this._webVRpresenting = vrDisplay.isPresenting;
  714. if (wasPresenting && !this._webVRpresenting) {
  715. this.exitVR();
  716. }
  717. } else {
  718. Tools.Warn('Detected VRDisplayPresentChange on an unknown VRDisplay. Did you can enterVR on the vrExperienceHelper?');
  719. }
  720. this.updateButtonVisibility();
  721. }
  722. private onVRDisplayChanged(eventArgs: IDisplayChangedEventArgs) {
  723. this._webVRsupported = eventArgs.vrSupported;
  724. this._webVRready = !!eventArgs.vrDisplay;
  725. this._webVRpresenting = eventArgs.vrDisplay && eventArgs.vrDisplay.isPresenting;
  726. this.updateButtonVisibility();
  727. }
  728. private moveButtonToBottomRight() {
  729. if (this._canvas && !this._useCustomVRButton) {
  730. this._btnVR.style.top = this._canvas.offsetTop + this._canvas.offsetHeight - 70 + "px";
  731. this._btnVR.style.left = this._canvas.offsetLeft + this._canvas.offsetWidth - 100 + "px";
  732. }
  733. }
  734. private displayVRButton() {
  735. if (!this._useCustomVRButton && !this._btnVRDisplayed) {
  736. document.body.appendChild(this._btnVR);
  737. this._btnVRDisplayed = true;
  738. }
  739. }
  740. private updateButtonVisibility() {
  741. if (!this._btnVR || this._useCustomVRButton) {
  742. return;
  743. }
  744. this._btnVR.className = "babylonVRicon";
  745. if (this.isInVRMode) {
  746. this._btnVR.className += " vrdisplaypresenting";
  747. } else {
  748. if (this._webVRready) { this._btnVR.className += " vrdisplayready"; }
  749. if (this._webVRsupported) { this._btnVR.className += " vrdisplaysupported"; }
  750. if (this._webVRrequesting) { this._btnVR.className += " vrdisplayrequesting"; }
  751. }
  752. }
  753. private _cachedAngularSensibility = {angularSensibilityX: null, angularSensibilityY: null, angularSensibility: null};
  754. /**
  755. * Attempt to enter VR. If a headset is connected and ready, will request present on that.
  756. * Otherwise, will use the fullscreen API.
  757. */
  758. public enterVR() {
  759. if (this.onEnteringVRObservable) {
  760. try {
  761. this.onEnteringVRObservable.notifyObservers(this);
  762. }
  763. catch (err) {
  764. Tools.Warn("Error in your custom logic onEnteringVR: " + err);
  765. }
  766. }
  767. if (this._scene.activeCamera) {
  768. this._position = this._scene.activeCamera.position.clone();
  769. if (this.vrDeviceOrientationCamera) {
  770. this.vrDeviceOrientationCamera.rotation = BABYLON.Quaternion.FromRotationMatrix(this._scene.activeCamera.getWorldMatrix().getRotationMatrix()).toEulerAngles();
  771. this.vrDeviceOrientationCamera.angularSensibility = 2000;
  772. }
  773. if (this.webVRCamera) {
  774. var currentYRotation = this.webVRCamera.deviceRotationQuaternion.toEulerAngles().y;
  775. var desiredYRotation = BABYLON.Quaternion.FromRotationMatrix(this._scene.activeCamera.getWorldMatrix().getRotationMatrix()).toEulerAngles().y;
  776. var delta = desiredYRotation - currentYRotation;
  777. var currentGlobalRotation = this.webVRCamera.rotationQuaternion.toEulerAngles().y;
  778. this.webVRCamera.rotationQuaternion = Quaternion.FromEulerAngles(0, currentGlobalRotation + delta, 0);
  779. }
  780. // make sure that we return to the last active camera
  781. this._existingCamera = this._scene.activeCamera;
  782. // Remove and cache angular sensability to avoid camera rotation when in VR
  783. if ((<any>this._existingCamera).angularSensibilityX) {
  784. this._cachedAngularSensibility.angularSensibilityX = (<any>this._existingCamera).angularSensibilityX;
  785. (<any>this._existingCamera).angularSensibilityX = Number.MAX_VALUE;
  786. }
  787. if ((<any>this._existingCamera).angularSensibilityY) {
  788. this._cachedAngularSensibility.angularSensibilityY = (<any>this._existingCamera).angularSensibilityY;
  789. (<any>this._existingCamera).angularSensibilityY = Number.MAX_VALUE;
  790. }
  791. if ((<any>this._existingCamera).angularSensibility) {
  792. this._cachedAngularSensibility.angularSensibility = (<any>this._existingCamera).angularSensibility;
  793. (<any>this._existingCamera).angularSensibility = Number.MAX_VALUE;
  794. }
  795. }
  796. if (this._webVRrequesting) {
  797. return;
  798. }
  799. // If WebVR is supported and a headset is connected
  800. if (this._webVRready) {
  801. if (!this._webVRpresenting) {
  802. this._webVRCamera.position = this._position;
  803. this._scene.activeCamera = this._webVRCamera;
  804. }
  805. }
  806. else if (this._vrDeviceOrientationCamera) {
  807. this._vrDeviceOrientationCamera.position = this._position;
  808. if (this._scene.activeCamera) {
  809. this._vrDeviceOrientationCamera.minZ = this._scene.activeCamera.minZ;
  810. }
  811. this._scene.activeCamera = this._vrDeviceOrientationCamera;
  812. this._scene.getEngine().switchFullscreen(true);
  813. this.updateButtonVisibility();
  814. }
  815. if (this._scene.activeCamera && this._canvas) {
  816. this._scene.activeCamera.attachControl(this._canvas);
  817. }
  818. if (this._interactionsEnabled) {
  819. this._scene.registerBeforeRender(this.beforeRender);
  820. }
  821. this._hasEnteredVR = true;
  822. }
  823. /**
  824. * Attempt to exit VR, or fullscreen.
  825. */
  826. public exitVR() {
  827. if (this._hasEnteredVR) {
  828. if (this.onExitingVRObservable) {
  829. try {
  830. this.onExitingVRObservable.notifyObservers(this);
  831. }
  832. catch (err) {
  833. Tools.Warn("Error in your custom logic onExitingVR: " + err);
  834. }
  835. }
  836. if (this._webVRpresenting) {
  837. this._scene.getEngine().disableVR();
  838. }
  839. if (this._scene.activeCamera) {
  840. this._position = this._scene.activeCamera.position.clone();
  841. }
  842. if (this.vrDeviceOrientationCamera) {
  843. this.vrDeviceOrientationCamera.angularSensibility = Number.MAX_VALUE;
  844. }
  845. if (this._deviceOrientationCamera) {
  846. this._deviceOrientationCamera.position = this._position;
  847. this._scene.activeCamera = this._deviceOrientationCamera;
  848. if (this._canvas) {
  849. this._scene.activeCamera.attachControl(this._canvas);
  850. }
  851. // Restore angular sensibility
  852. if (this._cachedAngularSensibility.angularSensibilityX) {
  853. (<any>this._deviceOrientationCamera).angularSensibilityX = this._cachedAngularSensibility.angularSensibilityX;
  854. this._cachedAngularSensibility.angularSensibilityX = null;
  855. }
  856. if (this._cachedAngularSensibility.angularSensibilityY) {
  857. (<any>this._deviceOrientationCamera).angularSensibilityY = this._cachedAngularSensibility.angularSensibilityY;
  858. this._cachedAngularSensibility.angularSensibilityY = null;
  859. }
  860. if (this._cachedAngularSensibility.angularSensibility) {
  861. (<any>this._deviceOrientationCamera).angularSensibility = this._cachedAngularSensibility.angularSensibility;
  862. this._cachedAngularSensibility.angularSensibility = null;
  863. }
  864. } else if (this._existingCamera) {
  865. this._existingCamera.position = this._position;
  866. this._scene.activeCamera = this._existingCamera;
  867. // Restore angular sensibility
  868. if (this._cachedAngularSensibility.angularSensibilityX) {
  869. (<any>this._existingCamera).angularSensibilityX = this._cachedAngularSensibility.angularSensibilityX;
  870. this._cachedAngularSensibility.angularSensibilityX = null;
  871. }
  872. if (this._cachedAngularSensibility.angularSensibilityY) {
  873. (<any>this._existingCamera).angularSensibilityY = this._cachedAngularSensibility.angularSensibilityY;
  874. this._cachedAngularSensibility.angularSensibilityY = null;
  875. }
  876. if (this._cachedAngularSensibility.angularSensibility) {
  877. (<any>this._existingCamera).angularSensibility = this._cachedAngularSensibility.angularSensibility;
  878. this._cachedAngularSensibility.angularSensibility = null;
  879. }
  880. }
  881. this.updateButtonVisibility();
  882. if (this._interactionsEnabled) {
  883. this._scene.unregisterBeforeRender(this.beforeRender);
  884. this._cameraGazer._gazeTracker.isVisible = false;
  885. if (this._leftController) {
  886. this._leftController._gazeTracker.isVisible = false;
  887. }
  888. if (this._rightController) {
  889. this._rightController._gazeTracker.isVisible = false;
  890. }
  891. }
  892. // resize to update width and height when exiting vr exits fullscreen
  893. this._scene.getEngine().resize();
  894. this._hasEnteredVR = false;
  895. }
  896. }
  897. /**
  898. * The position of the vr experience helper.
  899. */
  900. public get position(): Vector3 {
  901. return this._position;
  902. }
  903. /**
  904. * Sets the position of the vr experience helper.
  905. */
  906. public set position(value: Vector3) {
  907. this._position = value;
  908. if (this._scene.activeCamera) {
  909. this._scene.activeCamera.position = value;
  910. }
  911. }
  912. /**
  913. * Enables controllers and user interactions such as selecting and object or clicking on an object.
  914. */
  915. public enableInteractions() {
  916. if (!this._interactionsEnabled) {
  917. this._interactionsRequested = true;
  918. if (this._leftController) {
  919. this._enableInteractionOnController(this._leftController);
  920. }
  921. if (this._rightController) {
  922. this._enableInteractionOnController(this._rightController);
  923. }
  924. this.raySelectionPredicate = (mesh) => {
  925. return mesh.isVisible && (mesh.isPickable || mesh.name === this._floorMeshName);
  926. };
  927. this.meshSelectionPredicate = (mesh) => {
  928. return true;
  929. };
  930. this._raySelectionPredicate = (mesh) => {
  931. if (this._isTeleportationFloor(mesh) || (mesh.name.indexOf("gazeTracker") === -1
  932. && mesh.name.indexOf("teleportationTarget") === -1
  933. && mesh.name.indexOf("torusTeleportation") === -1)) {
  934. return this.raySelectionPredicate(mesh);
  935. }
  936. return false;
  937. };
  938. this._interactionsEnabled = true;
  939. }
  940. }
  941. private get _noControllerIsActive() {
  942. return !(this._leftController && this._leftController._activePointer) && !(this._rightController && this._rightController._activePointer);
  943. }
  944. private beforeRender = () => {
  945. if (this._leftController && this._leftController._activePointer) {
  946. this._castRayAndSelectObject(this._leftController);
  947. }
  948. if (this._rightController && this._rightController._activePointer) {
  949. this._castRayAndSelectObject(this._rightController);
  950. }
  951. if (this._noControllerIsActive) {
  952. this._castRayAndSelectObject(this._cameraGazer);
  953. } else {
  954. this._cameraGazer._gazeTracker.isVisible = false;
  955. }
  956. }
  957. private _isTeleportationFloor(mesh: AbstractMesh): boolean {
  958. for (var i = 0; i < this._floorMeshesCollection.length; i++) {
  959. if (this._floorMeshesCollection[i].id === mesh.id) {
  960. return true;
  961. }
  962. }
  963. if (this._floorMeshName && mesh.name === this._floorMeshName) {
  964. return true;
  965. }
  966. return false;
  967. }
  968. /**
  969. * Adds a floor mesh to be used for teleportation.
  970. * @param floorMesh the mesh to be used for teleportation.
  971. */
  972. public addFloorMesh(floorMesh: Mesh): void {
  973. if (!this._floorMeshesCollection) {
  974. return;
  975. }
  976. if (this._floorMeshesCollection.indexOf(floorMesh) > -1) {
  977. return;
  978. }
  979. this._floorMeshesCollection.push(floorMesh);
  980. }
  981. /**
  982. * Removes a floor mesh from being used for teleportation.
  983. * @param floorMesh the mesh to be removed.
  984. */
  985. public removeFloorMesh(floorMesh: Mesh): void {
  986. if (!this._floorMeshesCollection) {
  987. return;
  988. }
  989. const meshIndex = this._floorMeshesCollection.indexOf(floorMesh);
  990. if (meshIndex !== -1) {
  991. this._floorMeshesCollection.splice(meshIndex, 1);
  992. }
  993. }
  994. /**
  995. * Enables interactions and teleportation using the VR controllers and gaze.
  996. * @param vrTeleportationOptions options to modify teleportation behavior.
  997. */
  998. public enableTeleportation(vrTeleportationOptions: VRTeleportationOptions = {}) {
  999. if (!this._teleportationInitialized) {
  1000. this._teleportationRequested = true;
  1001. this.enableInteractions();
  1002. if (vrTeleportationOptions.floorMeshName) {
  1003. this._floorMeshName = vrTeleportationOptions.floorMeshName;
  1004. }
  1005. if (vrTeleportationOptions.floorMeshes) {
  1006. this._floorMeshesCollection = vrTeleportationOptions.floorMeshes;
  1007. }
  1008. if (this._leftController != null) {
  1009. this._enableTeleportationOnController(this._leftController);
  1010. }
  1011. if (this._rightController != null) {
  1012. this._enableTeleportationOnController(this._rightController);
  1013. }
  1014. // Creates an image processing post process for the vignette not relying
  1015. // on the main scene configuration for image processing to reduce setup and spaces
  1016. // (gamma/linear) conflicts.
  1017. const imageProcessingConfiguration = new ImageProcessingConfiguration();
  1018. imageProcessingConfiguration.vignetteColor = new Color4(0, 0, 0, 0);
  1019. imageProcessingConfiguration.vignetteEnabled = true;
  1020. this._postProcessMove = new ImageProcessingPostProcess("postProcessMove",
  1021. 1.0,
  1022. this._webVRCamera,
  1023. undefined,
  1024. undefined,
  1025. undefined,
  1026. undefined,
  1027. imageProcessingConfiguration);
  1028. this._webVRCamera.detachPostProcess(this._postProcessMove);
  1029. this._teleportationInitialized = true;
  1030. if (this._isDefaultTeleportationTarget) {
  1031. this._createTeleportationCircles();
  1032. this._teleportationTarget.scaling.scaleInPlace(this._webVRCamera.deviceScaleFactor);
  1033. }
  1034. }
  1035. }
  1036. private _onNewGamepadConnected = (gamepad: Gamepad) => {
  1037. if (gamepad.type !== Gamepad.POSE_ENABLED) {
  1038. if (gamepad.leftStick) {
  1039. gamepad.onleftstickchanged((stickValues) => {
  1040. if (this._teleportationInitialized && this.teleportationEnabled) {
  1041. // Listening to classic/xbox gamepad only if no VR controller is active
  1042. if ((!this._leftController && !this._rightController) ||
  1043. ((this._leftController && !this._leftController._activePointer) &&
  1044. (this._rightController && !this._rightController._activePointer))) {
  1045. this._checkTeleportWithRay(stickValues, this._cameraGazer);
  1046. this._checkTeleportBackwards(stickValues, this._cameraGazer);
  1047. }
  1048. }
  1049. });
  1050. }
  1051. if (gamepad.rightStick) {
  1052. gamepad.onrightstickchanged((stickValues) => {
  1053. if (this._teleportationInitialized) {
  1054. this._checkRotate(stickValues, this._cameraGazer);
  1055. }
  1056. });
  1057. }
  1058. if (gamepad.type === Gamepad.XBOX) {
  1059. (<Xbox360Pad>gamepad).onbuttondown((buttonPressed: Xbox360Button) => {
  1060. if (this._interactionsEnabled && buttonPressed === Xbox360Button.A) {
  1061. this._cameraGazer._selectionPointerDown();
  1062. }
  1063. });
  1064. (<Xbox360Pad>gamepad).onbuttonup((buttonPressed: Xbox360Button) => {
  1065. if (this._interactionsEnabled && buttonPressed === Xbox360Button.A) {
  1066. this._cameraGazer._selectionPointerUp();
  1067. }
  1068. });
  1069. }
  1070. } else {
  1071. var webVRController = <WebVRController>gamepad;
  1072. var controller = new VRExperienceHelperControllerGazer(webVRController, this._scene, this._cameraGazer._gazeTracker);
  1073. if (webVRController.hand === "right" || (this._leftController && this._leftController.webVRController != webVRController)) {
  1074. this._rightController = controller;
  1075. } else {
  1076. this._leftController = controller;
  1077. }
  1078. this._tryEnableInteractionOnController(controller);
  1079. }
  1080. }
  1081. // 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
  1082. private _tryEnableInteractionOnController = (controller: VRExperienceHelperControllerGazer) => {
  1083. if (this._interactionsRequested && !controller._interactionsEnabled) {
  1084. this._enableInteractionOnController(controller);
  1085. }
  1086. if (this._teleportationRequested && !controller._teleportationEnabled) {
  1087. this._enableTeleportationOnController(controller);
  1088. }
  1089. }
  1090. private _onNewGamepadDisconnected = (gamepad: Gamepad) => {
  1091. if (gamepad instanceof WebVRController) {
  1092. if (gamepad.hand === "left" && this._leftController != null) {
  1093. this._leftController.dispose();
  1094. this._leftController = null;
  1095. }
  1096. if (gamepad.hand === "right" && this._rightController != null) {
  1097. this._rightController.dispose();
  1098. this._rightController = null;
  1099. }
  1100. }
  1101. }
  1102. private _enableInteractionOnController(controller: VRExperienceHelperControllerGazer) {
  1103. var controllerMesh = controller.webVRController.mesh;
  1104. if (controllerMesh) {
  1105. controller._interactionsEnabled = true;
  1106. controller._activatePointer();
  1107. if (this.webVROptions.laserToggle) {
  1108. controller.webVRController.onMainButtonStateChangedObservable.add((stateObject) => {
  1109. // Enabling / disabling laserPointer
  1110. if (this._displayLaserPointer && stateObject.value === 1) {
  1111. if (controller._activePointer) {
  1112. controller._deactivatePointer();
  1113. } else {
  1114. controller._activatePointer();
  1115. }
  1116. if (this.displayGaze) {
  1117. controller._gazeTracker.isVisible = controller._activePointer;
  1118. }
  1119. }
  1120. });
  1121. }
  1122. controller.webVRController.onTriggerStateChangedObservable.add((stateObject) => {
  1123. var gazer: VRExperienceHelperGazer = controller;
  1124. if (this._noControllerIsActive) {
  1125. gazer = this._cameraGazer;
  1126. }
  1127. if (!gazer._pointerDownOnMeshAsked) {
  1128. if (stateObject.value > this._padSensibilityUp) {
  1129. gazer._selectionPointerDown();
  1130. }
  1131. } else if (stateObject.value < this._padSensibilityDown) {
  1132. gazer._selectionPointerUp();
  1133. }
  1134. });
  1135. }
  1136. }
  1137. private _checkTeleportWithRay(stateObject: StickValues, gazer: VRExperienceHelperGazer) {
  1138. // Dont teleport if another gaze already requested teleportation
  1139. if (this._teleportationRequestInitiated && !gazer._teleportationRequestInitiated) {
  1140. return;
  1141. }
  1142. if (!gazer._teleportationRequestInitiated) {
  1143. if (stateObject.y < -this._padSensibilityUp && gazer._dpadPressed) {
  1144. gazer._activatePointer();
  1145. gazer._teleportationRequestInitiated = true;
  1146. }
  1147. } else {
  1148. // Listening to the proper controller values changes to confirm teleportation
  1149. if (Math.sqrt(stateObject.y * stateObject.y + stateObject.x * stateObject.x) < this._padSensibilityDown) {
  1150. if (this._teleportActive) {
  1151. this.teleportCamera(this._haloCenter);
  1152. }
  1153. gazer._teleportationRequestInitiated = false;
  1154. }
  1155. }
  1156. }
  1157. private _checkRotate(stateObject: StickValues, gazer: VRExperienceHelperGazer) {
  1158. // Only rotate when user is not currently selecting a teleportation location
  1159. if (gazer._teleportationRequestInitiated) {
  1160. return;
  1161. }
  1162. if (!gazer._rotationLeftAsked) {
  1163. if (stateObject.x < -this._padSensibilityUp && gazer._dpadPressed) {
  1164. gazer._rotationLeftAsked = true;
  1165. if (this._rotationAllowed) {
  1166. this._rotateCamera(false);
  1167. }
  1168. }
  1169. } else {
  1170. if (stateObject.x > -this._padSensibilityDown) {
  1171. gazer._rotationLeftAsked = false;
  1172. }
  1173. }
  1174. if (!gazer._rotationRightAsked) {
  1175. if (stateObject.x > this._padSensibilityUp && gazer._dpadPressed) {
  1176. gazer._rotationRightAsked = true;
  1177. if (this._rotationAllowed) {
  1178. this._rotateCamera(true);
  1179. }
  1180. }
  1181. } else {
  1182. if (stateObject.x < this._padSensibilityDown) {
  1183. gazer._rotationRightAsked = false;
  1184. }
  1185. }
  1186. }
  1187. private _checkTeleportBackwards(stateObject: StickValues, gazer: VRExperienceHelperGazer) {
  1188. // Only teleport backwards when user is not currently selecting a teleportation location
  1189. if (gazer._teleportationRequestInitiated) {
  1190. return;
  1191. }
  1192. // Teleport backwards
  1193. if (stateObject.y > this._padSensibilityUp && gazer._dpadPressed) {
  1194. if (!gazer._teleportationBackRequestInitiated) {
  1195. if (!this.currentVRCamera) {
  1196. return;
  1197. }
  1198. // Get rotation and position of the current camera
  1199. var rotation = Quaternion.FromRotationMatrix(this.currentVRCamera.getWorldMatrix().getRotationMatrix());
  1200. var position = this.currentVRCamera.position;
  1201. // If the camera has device position, use that instead
  1202. if ((<WebVRFreeCamera>this.currentVRCamera).devicePosition && (<WebVRFreeCamera>this.currentVRCamera).deviceRotationQuaternion) {
  1203. rotation = (<WebVRFreeCamera>this.currentVRCamera).deviceRotationQuaternion;
  1204. position = (<WebVRFreeCamera>this.currentVRCamera).devicePosition;
  1205. }
  1206. // Get matrix with only the y rotation of the device rotation
  1207. rotation.toEulerAnglesToRef(this._workingVector);
  1208. this._workingVector.z = 0;
  1209. this._workingVector.x = 0;
  1210. Quaternion.RotationYawPitchRollToRef(this._workingVector.y, this._workingVector.x, this._workingVector.z, this._workingQuaternion);
  1211. this._workingQuaternion.toRotationMatrix(this._workingMatrix);
  1212. // Rotate backwards ray by device rotation to cast at the ground behind the user
  1213. Vector3.TransformCoordinatesToRef(this._teleportBackwardsVector, this._workingMatrix, this._workingVector);
  1214. // Teleport if ray hit the ground and is not to far away eg. backwards off a cliff
  1215. var ray = new Ray(position, this._workingVector);
  1216. var hit = this._scene.pickWithRay(ray, this._raySelectionPredicate);
  1217. if (hit && hit.pickedPoint && hit.pickedMesh && this._isTeleportationFloor(hit.pickedMesh) && hit.distance < 5) {
  1218. this.teleportCamera(hit.pickedPoint);
  1219. }
  1220. gazer._teleportationBackRequestInitiated = true;
  1221. }
  1222. } else {
  1223. gazer._teleportationBackRequestInitiated = false;
  1224. }
  1225. }
  1226. private _enableTeleportationOnController(controller: VRExperienceHelperControllerGazer) {
  1227. var controllerMesh = controller.webVRController.mesh;
  1228. if (controllerMesh) {
  1229. if (!controller._interactionsEnabled) {
  1230. this._enableInteractionOnController(controller);
  1231. }
  1232. controller._interactionsEnabled = true;
  1233. controller._teleportationEnabled = true;
  1234. if (controller.webVRController.controllerType === PoseEnabledControllerType.VIVE) {
  1235. controller._dpadPressed = false;
  1236. controller.webVRController.onPadStateChangedObservable.add((stateObject) => {
  1237. controller._dpadPressed = stateObject.pressed;
  1238. if (!controller._dpadPressed) {
  1239. controller._rotationLeftAsked = false;
  1240. controller._rotationRightAsked = false;
  1241. controller._teleportationBackRequestInitiated = false;
  1242. }
  1243. });
  1244. }
  1245. controller.webVRController.onPadValuesChangedObservable.add((stateObject) => {
  1246. if (this.teleportationEnabled) {
  1247. this._checkTeleportBackwards(stateObject, controller);
  1248. this._checkTeleportWithRay(stateObject, controller);
  1249. }
  1250. this._checkRotate(stateObject, controller);
  1251. });
  1252. }
  1253. }
  1254. private _createTeleportationCircles() {
  1255. this._teleportationTarget = Mesh.CreateGround("teleportationTarget", 2, 2, 2, this._scene);
  1256. this._teleportationTarget.isPickable = false;
  1257. var length = 512;
  1258. var dynamicTexture = new DynamicTexture("DynamicTexture", length, this._scene, true);
  1259. dynamicTexture.hasAlpha = true;
  1260. var context = dynamicTexture.getContext();
  1261. var centerX = length / 2;
  1262. var centerY = length / 2;
  1263. var radius = 200;
  1264. context.beginPath();
  1265. context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
  1266. context.fillStyle = this._teleportationFillColor;
  1267. context.fill();
  1268. context.lineWidth = 10;
  1269. context.strokeStyle = this._teleportationBorderColor;
  1270. context.stroke();
  1271. context.closePath();
  1272. dynamicTexture.update();
  1273. var teleportationCircleMaterial = new StandardMaterial("TextPlaneMaterial", this._scene);
  1274. teleportationCircleMaterial.diffuseTexture = dynamicTexture;
  1275. this._teleportationTarget.material = teleportationCircleMaterial;
  1276. var torus = Mesh.CreateTorus("torusTeleportation", 0.75, 0.1, 25, this._scene, false);
  1277. torus.isPickable = false;
  1278. torus.parent = this._teleportationTarget;
  1279. var animationInnerCircle = new Animation("animationInnerCircle", "position.y", 30, Animation.ANIMATIONTYPE_FLOAT, Animation.ANIMATIONLOOPMODE_CYCLE);
  1280. var keys = [];
  1281. keys.push({
  1282. frame: 0,
  1283. value: 0
  1284. });
  1285. keys.push({
  1286. frame: 30,
  1287. value: 0.4
  1288. });
  1289. keys.push({
  1290. frame: 60,
  1291. value: 0
  1292. });
  1293. animationInnerCircle.setKeys(keys);
  1294. var easingFunction = new SineEase();
  1295. easingFunction.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT);
  1296. animationInnerCircle.setEasingFunction(easingFunction);
  1297. torus.animations = [];
  1298. torus.animations.push(animationInnerCircle);
  1299. this._scene.beginAnimation(torus, 0, 60, true);
  1300. this._hideTeleportationTarget();
  1301. }
  1302. private _displayTeleportationTarget() {
  1303. this._teleportActive = true;
  1304. if (this._teleportationInitialized) {
  1305. this._teleportationTarget.isVisible = true;
  1306. if (this._isDefaultTeleportationTarget) {
  1307. (<Mesh>this._teleportationTarget.getChildren()[0]).isVisible = true;
  1308. }
  1309. }
  1310. }
  1311. private _hideTeleportationTarget() {
  1312. this._teleportActive = false;
  1313. if (this._teleportationInitialized) {
  1314. this._teleportationTarget.isVisible = false;
  1315. if (this._isDefaultTeleportationTarget) {
  1316. (<Mesh>this._teleportationTarget.getChildren()[0]).isVisible = false;
  1317. }
  1318. }
  1319. }
  1320. private _rotateCamera(right: boolean) {
  1321. if (!(this.currentVRCamera instanceof FreeCamera)) {
  1322. return;
  1323. }
  1324. if (right) {
  1325. this._rotationAngle++;
  1326. }
  1327. else {
  1328. this._rotationAngle--;
  1329. }
  1330. this.currentVRCamera.animations = [];
  1331. var target = Quaternion.FromRotationMatrix(Matrix.RotationY(Math.PI / 4 * this._rotationAngle));
  1332. var animationRotation = new Animation("animationRotation", "rotationQuaternion", 90, Animation.ANIMATIONTYPE_QUATERNION,
  1333. Animation.ANIMATIONLOOPMODE_CONSTANT);
  1334. var animationRotationKeys = [];
  1335. animationRotationKeys.push({
  1336. frame: 0,
  1337. value: this.currentVRCamera.rotationQuaternion
  1338. });
  1339. animationRotationKeys.push({
  1340. frame: 6,
  1341. value: target
  1342. });
  1343. animationRotation.setKeys(animationRotationKeys);
  1344. animationRotation.setEasingFunction(this._circleEase);
  1345. this.currentVRCamera.animations.push(animationRotation);
  1346. this._postProcessMove.animations = [];
  1347. var animationPP = new Animation("animationPP", "vignetteWeight", 90, Animation.ANIMATIONTYPE_FLOAT,
  1348. Animation.ANIMATIONLOOPMODE_CONSTANT);
  1349. var vignetteWeightKeys = [];
  1350. vignetteWeightKeys.push({
  1351. frame: 0,
  1352. value: 0
  1353. });
  1354. vignetteWeightKeys.push({
  1355. frame: 3,
  1356. value: 4
  1357. });
  1358. vignetteWeightKeys.push({
  1359. frame: 6,
  1360. value: 0
  1361. });
  1362. animationPP.setKeys(vignetteWeightKeys);
  1363. animationPP.setEasingFunction(this._circleEase);
  1364. this._postProcessMove.animations.push(animationPP);
  1365. var animationPP2 = new Animation("animationPP2", "vignetteStretch", 90, Animation.ANIMATIONTYPE_FLOAT,
  1366. Animation.ANIMATIONLOOPMODE_CONSTANT);
  1367. var vignetteStretchKeys = [];
  1368. vignetteStretchKeys.push({
  1369. frame: 0,
  1370. value: 0
  1371. });
  1372. vignetteStretchKeys.push({
  1373. frame: 3,
  1374. value: 10
  1375. });
  1376. vignetteStretchKeys.push({
  1377. frame: 6,
  1378. value: 0
  1379. });
  1380. animationPP2.setKeys(vignetteStretchKeys);
  1381. animationPP2.setEasingFunction(this._circleEase);
  1382. this._postProcessMove.animations.push(animationPP2);
  1383. this._postProcessMove.imageProcessingConfiguration.vignetteWeight = 0;
  1384. this._postProcessMove.imageProcessingConfiguration.vignetteStretch = 0;
  1385. this._postProcessMove.samples = 4;
  1386. this._webVRCamera.attachPostProcess(this._postProcessMove);
  1387. this._scene.beginAnimation(this._postProcessMove, 0, 6, false, 1, () => {
  1388. this._webVRCamera.detachPostProcess(this._postProcessMove);
  1389. });
  1390. this._scene.beginAnimation(this.currentVRCamera, 0, 6, false, 1);
  1391. }
  1392. private _moveTeleportationSelectorTo(hit: PickingInfo, gazer: VRExperienceHelperGazer, ray: Ray) {
  1393. if (hit.pickedPoint) {
  1394. if (gazer._teleportationRequestInitiated) {
  1395. this._displayTeleportationTarget();
  1396. this._haloCenter.copyFrom(hit.pickedPoint);
  1397. this._teleportationTarget.position.copyFrom(hit.pickedPoint);
  1398. }
  1399. var pickNormal = this._convertNormalToDirectionOfRay(hit.getNormal(true, false), ray);
  1400. if (pickNormal) {
  1401. var axis1 = Vector3.Cross(Axis.Y, pickNormal);
  1402. var axis2 = Vector3.Cross(pickNormal, axis1);
  1403. Vector3.RotationFromAxisToRef(axis2, pickNormal, axis1, this._teleportationTarget.rotation);
  1404. }
  1405. this._teleportationTarget.position.y += 0.1;
  1406. }
  1407. }
  1408. private _workingVector = Vector3.Zero();
  1409. private _workingQuaternion = Quaternion.Identity();
  1410. private _workingMatrix = Matrix.Identity();
  1411. /**
  1412. * Teleports the users feet to the desired location
  1413. * @param location The location where the user's feet should be placed
  1414. */
  1415. public teleportCamera(location: Vector3) {
  1416. if (!(this.currentVRCamera instanceof FreeCamera)) {
  1417. return;
  1418. }
  1419. // Teleport the hmd to where the user is looking by moving the anchor to where they are looking minus the
  1420. // offset of the headset from the anchor.
  1421. if (this.webVRCamera.leftCamera) {
  1422. this._workingVector.copyFrom(this.webVRCamera.leftCamera.globalPosition);
  1423. this._workingVector.subtractInPlace(this.webVRCamera.position);
  1424. location.subtractToRef(this._workingVector, this._workingVector);
  1425. } else {
  1426. this._workingVector.copyFrom(location);
  1427. }
  1428. // Add height to account for user's height offset
  1429. if (this.isInVRMode) {
  1430. this._workingVector.y += this.webVRCamera.deviceDistanceToRoomGround() * this._webVRCamera.deviceScaleFactor;
  1431. } else {
  1432. this._workingVector.y += this._defaultHeight;
  1433. }
  1434. this.onBeforeCameraTeleport.notifyObservers(this._workingVector);
  1435. // Create animation from the camera's position to the new location
  1436. this.currentVRCamera.animations = [];
  1437. var animationCameraTeleportation = new Animation("animationCameraTeleportation", "position", 90, Animation.ANIMATIONTYPE_VECTOR3, Animation.ANIMATIONLOOPMODE_CONSTANT);
  1438. var animationCameraTeleportationKeys = [{
  1439. frame: 0,
  1440. value: this.currentVRCamera.position
  1441. },
  1442. {
  1443. frame: 11,
  1444. value: this._workingVector
  1445. }
  1446. ];
  1447. animationCameraTeleportation.setKeys(animationCameraTeleportationKeys);
  1448. animationCameraTeleportation.setEasingFunction(this._circleEase);
  1449. this.currentVRCamera.animations.push(animationCameraTeleportation);
  1450. this._postProcessMove.animations = [];
  1451. var animationPP = new Animation("animationPP", "vignetteWeight", 90, Animation.ANIMATIONTYPE_FLOAT,
  1452. Animation.ANIMATIONLOOPMODE_CONSTANT);
  1453. var vignetteWeightKeys = [];
  1454. vignetteWeightKeys.push({
  1455. frame: 0,
  1456. value: 0
  1457. });
  1458. vignetteWeightKeys.push({
  1459. frame: 5,
  1460. value: 8
  1461. });
  1462. vignetteWeightKeys.push({
  1463. frame: 11,
  1464. value: 0
  1465. });
  1466. animationPP.setKeys(vignetteWeightKeys);
  1467. this._postProcessMove.animations.push(animationPP);
  1468. var animationPP2 = new Animation("animationPP2", "vignetteStretch", 90, Animation.ANIMATIONTYPE_FLOAT,
  1469. Animation.ANIMATIONLOOPMODE_CONSTANT);
  1470. var vignetteStretchKeys = [];
  1471. vignetteStretchKeys.push({
  1472. frame: 0,
  1473. value: 0
  1474. });
  1475. vignetteStretchKeys.push({
  1476. frame: 5,
  1477. value: 10
  1478. });
  1479. vignetteStretchKeys.push({
  1480. frame: 11,
  1481. value: 0
  1482. });
  1483. animationPP2.setKeys(vignetteStretchKeys);
  1484. this._postProcessMove.animations.push(animationPP2);
  1485. this._postProcessMove.imageProcessingConfiguration.vignetteWeight = 0;
  1486. this._postProcessMove.imageProcessingConfiguration.vignetteStretch = 0;
  1487. this._webVRCamera.attachPostProcess(this._postProcessMove);
  1488. this._scene.beginAnimation(this._postProcessMove, 0, 11, false, 1, () => {
  1489. this._webVRCamera.detachPostProcess(this._postProcessMove);
  1490. });
  1491. this._scene.beginAnimation(this.currentVRCamera, 0, 11, false, 1, () => {
  1492. this.onAfterCameraTeleport.notifyObservers(this._workingVector);
  1493. });
  1494. this._hideTeleportationTarget();
  1495. }
  1496. private _convertNormalToDirectionOfRay(normal: Nullable<Vector3>, ray: Ray) {
  1497. if (normal) {
  1498. var angle = Math.acos(BABYLON.Vector3.Dot(normal, ray.direction));
  1499. if (angle < Math.PI / 2) {
  1500. normal.scaleInPlace(-1);
  1501. }
  1502. }
  1503. return normal;
  1504. }
  1505. private _castRayAndSelectObject(gazer: VRExperienceHelperGazer) {
  1506. if (!(this.currentVRCamera instanceof FreeCamera)) {
  1507. return;
  1508. }
  1509. var ray = gazer._getForwardRay(this._rayLength);
  1510. var hit = this._scene.pickWithRay(ray, this._raySelectionPredicate);
  1511. if (hit) {
  1512. // Populate the contrllers mesh that can be used for drag/drop
  1513. if ((<any>gazer)._laserPointer) {
  1514. hit.originMesh = (<any>gazer)._laserPointer.parent;
  1515. }
  1516. this._scene.simulatePointerMove(hit, { pointerId: gazer._id });
  1517. }
  1518. gazer._currentHit = hit;
  1519. // Moving the gazeTracker on the mesh face targetted
  1520. if (hit && hit.pickedPoint) {
  1521. if (this._displayGaze) {
  1522. let multiplier = 1;
  1523. gazer._gazeTracker.isVisible = true;
  1524. if (gazer._isActionableMesh) {
  1525. multiplier = 3;
  1526. }
  1527. if (this.updateGazeTrackerScale) {
  1528. gazer._gazeTracker.scaling.x = hit.distance * multiplier;
  1529. gazer._gazeTracker.scaling.y = hit.distance * multiplier;
  1530. gazer._gazeTracker.scaling.z = hit.distance * multiplier;
  1531. }
  1532. var pickNormal = this._convertNormalToDirectionOfRay(hit.getNormal(), ray);
  1533. // To avoid z-fighting
  1534. let deltaFighting = 0.002;
  1535. if (pickNormal) {
  1536. var axis1 = Vector3.Cross(Axis.Y, pickNormal);
  1537. var axis2 = Vector3.Cross(pickNormal, axis1);
  1538. Vector3.RotationFromAxisToRef(axis2, pickNormal, axis1, gazer._gazeTracker.rotation);
  1539. }
  1540. gazer._gazeTracker.position.copyFrom(hit.pickedPoint);
  1541. if (gazer._gazeTracker.position.x < 0) {
  1542. gazer._gazeTracker.position.x += deltaFighting;
  1543. }
  1544. else {
  1545. gazer._gazeTracker.position.x -= deltaFighting;
  1546. }
  1547. if (gazer._gazeTracker.position.y < 0) {
  1548. gazer._gazeTracker.position.y += deltaFighting;
  1549. }
  1550. else {
  1551. gazer._gazeTracker.position.y -= deltaFighting;
  1552. }
  1553. if (gazer._gazeTracker.position.z < 0) {
  1554. gazer._gazeTracker.position.z += deltaFighting;
  1555. }
  1556. else {
  1557. gazer._gazeTracker.position.z -= deltaFighting;
  1558. }
  1559. }
  1560. // Changing the size of the laser pointer based on the distance from the targetted point
  1561. gazer._updatePointerDistance(hit.distance);
  1562. }
  1563. else {
  1564. gazer._updatePointerDistance();
  1565. gazer._gazeTracker.isVisible = false;
  1566. }
  1567. if (hit && hit.pickedMesh) {
  1568. // The object selected is the floor, we're in a teleportation scenario
  1569. if (this._teleportationInitialized && this._isTeleportationFloor(hit.pickedMesh) && hit.pickedPoint) {
  1570. // Moving the teleportation area to this targetted point
  1571. //Raise onSelectedMeshUnselected observable if ray collided floor mesh/meshes and a non floor mesh was previously selected
  1572. if (gazer._currentMeshSelected && !this._isTeleportationFloor(gazer._currentMeshSelected)) {
  1573. this._notifySelectedMeshUnselected(gazer._currentMeshSelected);
  1574. }
  1575. gazer._currentMeshSelected = null;
  1576. if (gazer._teleportationRequestInitiated) {
  1577. this._moveTeleportationSelectorTo(hit, gazer, ray);
  1578. }
  1579. return;
  1580. }
  1581. // If not, we're in a selection scenario
  1582. //this._teleportationAllowed = false;
  1583. if (hit.pickedMesh !== gazer._currentMeshSelected) {
  1584. if (this.meshSelectionPredicate(hit.pickedMesh)) {
  1585. this.onNewMeshPicked.notifyObservers(hit);
  1586. gazer._currentMeshSelected = hit.pickedMesh;
  1587. if (hit.pickedMesh.isPickable && hit.pickedMesh.actionManager) {
  1588. this.changeGazeColor(new Color3(0, 0, 1));
  1589. this.changeLaserColor(new Color3(0.2, 0.2, 1));
  1590. gazer._isActionableMesh = true;
  1591. }
  1592. else {
  1593. this.changeGazeColor(new Color3(0.7, 0.7, 0.7));
  1594. this.changeLaserColor(new Color3(0.7, 0.7, 0.7));
  1595. gazer._isActionableMesh = false;
  1596. }
  1597. try {
  1598. this.onNewMeshSelected.notifyObservers(hit.pickedMesh);
  1599. }
  1600. catch (err) {
  1601. Tools.Warn("Error in your custom logic onNewMeshSelected: " + err);
  1602. }
  1603. }
  1604. else {
  1605. this._notifySelectedMeshUnselected(gazer._currentMeshSelected);
  1606. gazer._currentMeshSelected = null;
  1607. this.changeGazeColor(new Color3(0.7, 0.7, 0.7));
  1608. this.changeLaserColor(new Color3(0.7, 0.7, 0.7));
  1609. }
  1610. }
  1611. }
  1612. else {
  1613. this._notifySelectedMeshUnselected(gazer._currentMeshSelected);
  1614. gazer._currentMeshSelected = null;
  1615. //this._teleportationAllowed = false;
  1616. this.changeGazeColor(new Color3(0.7, 0.7, 0.7));
  1617. this.changeLaserColor(new Color3(0.7, 0.7, 0.7));
  1618. }
  1619. }
  1620. private _notifySelectedMeshUnselected(mesh: Nullable<AbstractMesh>) {
  1621. if (mesh) {
  1622. this.onSelectedMeshUnselected.notifyObservers(mesh);
  1623. }
  1624. }
  1625. /**
  1626. * Sets the color of the laser ray from the vr controllers.
  1627. * @param color new color for the ray.
  1628. */
  1629. public changeLaserColor(color: Color3) {
  1630. if (this._leftController) {
  1631. this._leftController._setLaserPointerColor(color);
  1632. }
  1633. if (this._rightController) {
  1634. this._rightController._setLaserPointerColor(color);
  1635. }
  1636. }
  1637. /**
  1638. * Sets the color of the ray from the vr headsets gaze.
  1639. * @param color new color for the ray.
  1640. */
  1641. public changeGazeColor(color: Color3) {
  1642. if (!(<StandardMaterial>this._cameraGazer._gazeTracker.material)) {
  1643. return;
  1644. }
  1645. (<StandardMaterial>this._cameraGazer._gazeTracker.material).emissiveColor = color;
  1646. if (this._leftController) {
  1647. (<StandardMaterial>this._leftController._gazeTracker.material).emissiveColor = color;
  1648. }
  1649. if (this._rightController) {
  1650. (<StandardMaterial>this._rightController._gazeTracker.material).emissiveColor = color;
  1651. }
  1652. }
  1653. /**
  1654. * Exits VR and disposes of the vr experience helper
  1655. */
  1656. public dispose() {
  1657. if (this.isInVRMode) {
  1658. this.exitVR();
  1659. }
  1660. if (this._postProcessMove) {
  1661. this._postProcessMove.dispose();
  1662. }
  1663. if (this._webVRCamera) {
  1664. this._webVRCamera.dispose();
  1665. }
  1666. if (this._vrDeviceOrientationCamera) {
  1667. this._vrDeviceOrientationCamera.dispose();
  1668. }
  1669. if (!this._useCustomVRButton && this._btnVR.parentNode) {
  1670. document.body.removeChild(this._btnVR);
  1671. }
  1672. if (this._deviceOrientationCamera && (this._scene.activeCamera != this._deviceOrientationCamera)) {
  1673. this._deviceOrientationCamera.dispose();
  1674. }
  1675. if (this._cameraGazer) {
  1676. this._cameraGazer.dispose();
  1677. }
  1678. if (this._leftController) {
  1679. this._leftController.dispose();
  1680. }
  1681. if (this._rightController) {
  1682. this._rightController.dispose();
  1683. }
  1684. if (this._teleportationTarget) {
  1685. this._teleportationTarget.dispose();
  1686. }
  1687. this._floorMeshesCollection = [];
  1688. document.removeEventListener("keydown", this._onKeyDown);
  1689. window.removeEventListener('vrdisplaypresentchange', this._onVrDisplayPresentChange);
  1690. window.removeEventListener("resize", this._onResize);
  1691. document.removeEventListener("fullscreenchange", this._onFullscreenChange);
  1692. document.removeEventListener("mozfullscreenchange", this._onFullscreenChange);
  1693. document.removeEventListener("webkitfullscreenchange", this._onFullscreenChange);
  1694. document.removeEventListener("msfullscreenchange", this._onFullscreenChange);
  1695. (<any>document).onmsfullscreenchange = null;
  1696. this._scene.getEngine().onVRDisplayChangedObservable.removeCallback(this._onVRDisplayChanged);
  1697. this._scene.getEngine().onVRRequestPresentStart.removeCallback(this._onVRRequestPresentStart);
  1698. this._scene.getEngine().onVRRequestPresentComplete.removeCallback(this._onVRRequestPresentComplete);
  1699. window.removeEventListener('vrdisplaypresentchange', this._onVrDisplayPresentChange);
  1700. this._scene.gamepadManager.onGamepadConnectedObservable.removeCallback(this._onNewGamepadConnected);
  1701. this._scene.gamepadManager.onGamepadDisconnectedObservable.removeCallback(this._onNewGamepadDisconnected);
  1702. this._scene.unregisterBeforeRender(this.beforeRender);
  1703. }
  1704. /**
  1705. * Gets the name of the VRExperienceHelper class
  1706. * @returns "VRExperienceHelper"
  1707. */
  1708. public getClassName(): string {
  1709. return "VRExperienceHelper";
  1710. }
  1711. }
  1712. }