vrExperienceHelper.ts 84 KB

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