WebXRControllerPointerSelection.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. import { WebXRFeaturesManager, WebXRFeatureName } from "../webXRFeaturesManager";
  2. import { WebXRSessionManager } from '../webXRSessionManager';
  3. import { AbstractMesh } from '../../Meshes/abstractMesh';
  4. import { Observer } from '../../Misc/observable';
  5. import { WebXRInput } from '../webXRInput';
  6. import { WebXRInputSource } from '../webXRInputSource';
  7. import { Scene } from '../../scene';
  8. import { WebXRControllerComponent } from '../motionController/webXRControllerComponent';
  9. import { Nullable } from '../../types';
  10. import { Vector3 } from '../../Maths/math.vector';
  11. import { Color3 } from '../../Maths/math.color';
  12. import { Axis } from '../../Maths/math.axis';
  13. import { StandardMaterial } from '../../Materials/standardMaterial';
  14. import { CylinderBuilder } from '../../Meshes/Builders/cylinderBuilder';
  15. import { TorusBuilder } from '../../Meshes/Builders/torusBuilder';
  16. import { Ray } from '../../Culling/ray';
  17. import { PickingInfo } from '../../Collisions/pickingInfo';
  18. import { WebXRAbstractFeature } from './WebXRAbstractFeature';
  19. import { UtilityLayerRenderer } from '../../Rendering/utilityLayerRenderer';
  20. import { WebXRAbstractMotionController } from '../motionController/webXRAbstractMotionController';
  21. /**
  22. * Options interface for the pointer selection module
  23. */
  24. export interface IWebXRControllerPointerSelectionOptions {
  25. /**
  26. * if provided, this scene will be used to render meshes.
  27. */
  28. customUtilityLayerScene?: Scene;
  29. /**
  30. * Disable the pointer up event when the xr controller in screen and gaze mode is disposed (meaning - when the user removed the finger from the screen)
  31. * If not disabled, the last picked point will be used to execute a pointer up event
  32. * If disabled, pointer up event will be triggered right after the pointer down event.
  33. * Used in screen and gaze target ray mode only
  34. */
  35. disablePointerUpOnTouchOut: boolean;
  36. /**
  37. * For gaze mode (time to select instead of press)
  38. */
  39. forceGazeMode: boolean;
  40. /**
  41. * Factor to be applied to the pointer-moved function in the gaze mode. How sensitive should the gaze mode be when checking if the pointer moved
  42. * to start a new countdown to the pointer down event.
  43. * Defaults to 1.
  44. */
  45. gazeModePointerMovedFactor?: number;
  46. /**
  47. * Different button type to use instead of the main component
  48. */
  49. overrideButtonId?: string;
  50. /**
  51. * use this rendering group id for the meshes (optional)
  52. */
  53. renderingGroupId?: number;
  54. /**
  55. * The amount of time in milliseconds it takes between pick found something to a pointer down event.
  56. * Used in gaze modes. Tracked pointer uses the trigger, screen uses touch events
  57. * 3000 means 3 seconds between pointing at something and selecting it
  58. */
  59. timeToSelect?: number;
  60. /**
  61. * Should meshes created here be added to a utility layer or the main scene
  62. */
  63. useUtilityLayer?: boolean;
  64. /**
  65. * the xr input to use with this pointer selection
  66. */
  67. xrInput: WebXRInput;
  68. }
  69. /**
  70. * A module that will enable pointer selection for motion controllers of XR Input Sources
  71. */
  72. export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
  73. private static _idCounter = 0;
  74. private _attachController = (xrController: WebXRInputSource) => {
  75. if (this._controllers[xrController.uniqueId]) {
  76. // already attached
  77. return;
  78. }
  79. // only support tracker pointer
  80. const { laserPointer, selectionMesh } = this._generateNewMeshPair(xrController);
  81. // get two new meshes
  82. this._controllers[xrController.uniqueId] = {
  83. xrController,
  84. laserPointer,
  85. selectionMesh,
  86. meshUnderPointer: null,
  87. pick: null,
  88. tmpRay: new Ray(new Vector3(), new Vector3()),
  89. id: WebXRControllerPointerSelection._idCounter++
  90. };
  91. switch (xrController.inputSource.targetRayMode) {
  92. case "tracked-pointer":
  93. return this._attachTrackedPointerRayMode(xrController);
  94. case "gaze":
  95. return this._attachGazeMode(xrController);
  96. case "screen":
  97. return this._attachScreenRayMode(xrController);
  98. }
  99. }
  100. private _controllers: {
  101. [controllerUniqueId: string]: {
  102. xrController: WebXRInputSource;
  103. selectionComponent?: WebXRControllerComponent;
  104. onButtonChangedObserver?: Nullable<Observer<WebXRControllerComponent>>;
  105. onFrameObserver?: Nullable<Observer<XRFrame>>;
  106. laserPointer: AbstractMesh;
  107. selectionMesh: AbstractMesh;
  108. meshUnderPointer: Nullable<AbstractMesh>;
  109. pick: Nullable<PickingInfo>;
  110. id: number;
  111. tmpRay: Ray;
  112. // event support
  113. eventListeners?: {[event in XREventType]?: ((event: XRInputSourceEvent) => void)};
  114. };
  115. } = {};
  116. private _scene: Scene;
  117. private _tmpVectorForPickCompare = new Vector3();
  118. /**
  119. * The module's name
  120. */
  121. public static readonly Name = WebXRFeatureName.POINTER_SELECTION;
  122. /**
  123. * The (Babylon) version of this module.
  124. * This is an integer representing the implementation version.
  125. * This number does not correspond to the WebXR specs version
  126. */
  127. public static readonly Version = 1;
  128. /**
  129. * Disable lighting on the laser pointer (so it will always be visible)
  130. */
  131. public disablePointerLighting: boolean = true;
  132. /**
  133. * Disable lighting on the selection mesh (so it will always be visible)
  134. */
  135. public disableSelectionMeshLighting: boolean = true;
  136. /**
  137. * Should the laser pointer be displayed
  138. */
  139. public displayLaserPointer: boolean = true;
  140. /**
  141. * Should the selection mesh be displayed (The ring at the end of the laser pointer)
  142. */
  143. public displaySelectionMesh: boolean = true;
  144. /**
  145. * This color will be set to the laser pointer when selection is triggered
  146. */
  147. public laserPointerPickedColor: Color3 = new Color3(0.9, 0.9, 0.9);
  148. /**
  149. * Default color of the laser pointer
  150. */
  151. public laserPointerDefaultColor: Color3 = new Color3(0.7, 0.7, 0.7);
  152. /**
  153. * default color of the selection ring
  154. */
  155. public selectionMeshDefaultColor: Color3 = new Color3(0.8, 0.8, 0.8);
  156. /**
  157. * This color will be applied to the selection ring when selection is triggered
  158. */
  159. public selectionMeshPickedColor: Color3 = new Color3(0.3, 0.3, 1.0);
  160. /**
  161. * Optional filter to be used for ray selection. This predicate shares behavior with
  162. * scene.pointerMovePredicate which takes priority if it is also assigned.
  163. */
  164. public raySelectionPredicate: (mesh: AbstractMesh) => boolean;
  165. /**
  166. * constructs a new background remover module
  167. * @param _xrSessionManager the session manager for this module
  168. * @param _options read-only options to be used in this module
  169. */
  170. constructor(_xrSessionManager: WebXRSessionManager, private readonly _options: IWebXRControllerPointerSelectionOptions) {
  171. super(_xrSessionManager);
  172. this._scene = this._xrSessionManager.scene;
  173. }
  174. /**
  175. * attach this feature
  176. * Will usually be called by the features manager
  177. *
  178. * @returns true if successful.
  179. */
  180. public attach(): boolean {
  181. if (!super.attach()) {
  182. return false;
  183. }
  184. this._options.xrInput.controllers.forEach(this._attachController);
  185. this._addNewAttachObserver(this._options.xrInput.onControllerAddedObservable, this._attachController);
  186. this._addNewAttachObserver(this._options.xrInput.onControllerRemovedObservable, (controller) => {
  187. // REMOVE the controller
  188. this._detachController(controller.uniqueId);
  189. });
  190. this._scene.constantlyUpdateMeshUnderPointer = true;
  191. return true;
  192. }
  193. /**
  194. * detach this feature.
  195. * Will usually be called by the features manager
  196. *
  197. * @returns true if successful.
  198. */
  199. public detach(): boolean {
  200. if (!super.detach()) {
  201. return false;
  202. }
  203. Object.keys(this._controllers).forEach((controllerId) => {
  204. this._detachController(controllerId);
  205. });
  206. return true;
  207. }
  208. /**
  209. * Will get the mesh under a specific pointer.
  210. * `scene.meshUnderPointer` will only return one mesh - either left or right.
  211. * @param controllerId the controllerId to check
  212. * @returns The mesh under pointer or null if no mesh is under the pointer
  213. */
  214. public getMeshUnderPointer(controllerId: string): Nullable<AbstractMesh> {
  215. if (this._controllers[controllerId]) {
  216. return this._controllers[controllerId].meshUnderPointer;
  217. } else {
  218. return null;
  219. }
  220. }
  221. /**
  222. * Get the xr controller that correlates to the pointer id in the pointer event
  223. *
  224. * @param id the pointer id to search for
  225. * @returns the controller that correlates to this id or null if not found
  226. */
  227. public getXRControllerByPointerId(id: number): Nullable<WebXRInputSource> {
  228. const keys = Object.keys(this._controllers);
  229. for (let i = 0; i < keys.length; ++i) {
  230. if (this._controllers[keys[i]].id === id) {
  231. return this._controllers[keys[i]].xrController;
  232. }
  233. }
  234. return null;
  235. }
  236. protected _onXRFrame(_xrFrame: XRFrame) {
  237. Object.keys(this._controllers).forEach((id) => {
  238. const controllerData = this._controllers[id];
  239. // Every frame check collisions/input
  240. controllerData.xrController.getWorldPointerRayToRef(controllerData.tmpRay);
  241. controllerData.pick = this._scene.pickWithRay(controllerData.tmpRay,
  242. this._scene.pointerMovePredicate || this.raySelectionPredicate);
  243. const pick = controllerData.pick;
  244. if (pick && pick.pickedPoint && pick.hit) {
  245. // Update laser state
  246. this._updatePointerDistance(controllerData.laserPointer, pick.distance);
  247. // Update cursor state
  248. controllerData.selectionMesh.position.copyFrom(pick.pickedPoint);
  249. controllerData.selectionMesh.scaling.x = Math.sqrt(pick.distance);
  250. controllerData.selectionMesh.scaling.y = Math.sqrt(pick.distance);
  251. controllerData.selectionMesh.scaling.z = Math.sqrt(pick.distance);
  252. // To avoid z-fighting
  253. let pickNormal = this._convertNormalToDirectionOfRay(pick.getNormal(true), controllerData.tmpRay);
  254. let deltaFighting = 0.001;
  255. controllerData.selectionMesh.position.copyFrom(pick.pickedPoint);
  256. if (pickNormal) {
  257. let axis1 = Vector3.Cross(Axis.Y, pickNormal);
  258. let axis2 = Vector3.Cross(pickNormal, axis1);
  259. Vector3.RotationFromAxisToRef(axis2, pickNormal, axis1, controllerData.selectionMesh.rotation);
  260. controllerData.selectionMesh.position.addInPlace(pickNormal.scale(deltaFighting));
  261. }
  262. controllerData.selectionMesh.isVisible = true && this.displaySelectionMesh;
  263. controllerData.meshUnderPointer = pick.pickedMesh;
  264. } else {
  265. controllerData.selectionMesh.isVisible = false;
  266. controllerData.meshUnderPointer = null;
  267. }
  268. });
  269. }
  270. private _attachGazeMode(xrController: WebXRInputSource) {
  271. const controllerData = this._controllers[xrController.uniqueId];
  272. // attached when touched, detaches when raised
  273. const timeToSelect = this._options.timeToSelect || 3000;
  274. const sceneToRenderTo = this._options.useUtilityLayer ? (this._options.customUtilityLayerScene || UtilityLayerRenderer.DefaultUtilityLayer.utilityLayerScene) : this._scene;
  275. let oldPick = new PickingInfo();
  276. let discMesh = TorusBuilder.CreateTorus("selection", {
  277. diameter: 0.0035 * 15,
  278. thickness: 0.0025 * 6,
  279. tessellation: 20
  280. }, sceneToRenderTo);
  281. discMesh.isVisible = false;
  282. discMesh.isPickable = false;
  283. discMesh.parent = controllerData.selectionMesh;
  284. let timer = 0;
  285. let downTriggered = false;
  286. controllerData.onFrameObserver = this._xrSessionManager.onXRFrameObservable.add(() => {
  287. if (!controllerData.pick) { return; }
  288. controllerData.laserPointer.material!.alpha = 0;
  289. discMesh.isVisible = false;
  290. if (controllerData.pick.hit) {
  291. if (!this._pickingMoved(oldPick, controllerData.pick)) {
  292. if (timer > timeToSelect / 10) {
  293. discMesh.isVisible = true;
  294. }
  295. timer += this._scene.getEngine().getDeltaTime();
  296. if (timer >= timeToSelect) {
  297. this._scene.simulatePointerDown(controllerData.pick, { pointerId: controllerData.id });
  298. downTriggered = true;
  299. // pointer up right after down, if disable on touch out
  300. if (this._options.disablePointerUpOnTouchOut) {
  301. this._scene.simulatePointerUp(controllerData.pick, { pointerId: controllerData.id });
  302. }
  303. discMesh.isVisible = false;
  304. } else {
  305. const scaleFactor = 1 - (timer / timeToSelect);
  306. discMesh.scaling.set(scaleFactor, scaleFactor, scaleFactor);
  307. }
  308. } else {
  309. if (downTriggered) {
  310. if (!this._options.disablePointerUpOnTouchOut) {
  311. this._scene.simulatePointerUp(controllerData.pick, { pointerId: controllerData.id });
  312. }
  313. }
  314. downTriggered = false;
  315. timer = 0;
  316. }
  317. } else {
  318. downTriggered = false;
  319. timer = 0;
  320. }
  321. this._scene.simulatePointerMove(controllerData.pick, { pointerId: controllerData.id });
  322. oldPick = controllerData.pick;
  323. });
  324. if (this._options.renderingGroupId !== undefined) {
  325. discMesh.renderingGroupId = this._options.renderingGroupId;
  326. }
  327. xrController.onDisposeObservable.addOnce(() => {
  328. if (controllerData.pick && !this._options.disablePointerUpOnTouchOut && downTriggered) {
  329. this._scene.simulatePointerUp(controllerData.pick, { pointerId: controllerData.id });
  330. }
  331. discMesh.dispose();
  332. });
  333. }
  334. private _attachScreenRayMode(xrController: WebXRInputSource) {
  335. const controllerData = this._controllers[xrController.uniqueId];
  336. let downTriggered = false;
  337. controllerData.onFrameObserver = this._xrSessionManager.onXRFrameObservable.add(() => {
  338. if (!controllerData.pick || (this._options.disablePointerUpOnTouchOut && downTriggered)) { return; }
  339. if (!downTriggered) {
  340. this._scene.simulatePointerDown(controllerData.pick, { pointerId: controllerData.id });
  341. downTriggered = true;
  342. if (this._options.disablePointerUpOnTouchOut) {
  343. this._scene.simulatePointerUp(controllerData.pick, { pointerId: controllerData.id });
  344. }
  345. } else {
  346. this._scene.simulatePointerMove(controllerData.pick, { pointerId: controllerData.id });
  347. }
  348. });
  349. xrController.onDisposeObservable.addOnce(() => {
  350. if (controllerData.pick && downTriggered && !this._options.disablePointerUpOnTouchOut) {
  351. this._scene.simulatePointerUp(controllerData.pick, { pointerId: controllerData.id });
  352. }
  353. });
  354. }
  355. private _attachTrackedPointerRayMode(xrController: WebXRInputSource) {
  356. const controllerData = this._controllers[xrController.uniqueId];
  357. if (this._options.forceGazeMode) {
  358. return this._attachGazeMode(xrController);
  359. }
  360. controllerData.onFrameObserver = this._xrSessionManager.onXRFrameObservable.add(() => {
  361. controllerData.laserPointer.isVisible = this.displayLaserPointer;
  362. (<StandardMaterial>controllerData.laserPointer.material).disableLighting = this.disablePointerLighting;
  363. (<StandardMaterial>controllerData.selectionMesh.material).disableLighting = this.disableSelectionMeshLighting;
  364. if (controllerData.pick) {
  365. this._scene.simulatePointerMove(controllerData.pick, { pointerId: controllerData.id });
  366. }
  367. });
  368. if (xrController.inputSource.gamepad) {
  369. const init = (motionController: WebXRAbstractMotionController) => {
  370. if (this._options.overrideButtonId) {
  371. controllerData.selectionComponent = motionController.getComponent(this._options.overrideButtonId);
  372. }
  373. if (!controllerData.selectionComponent) {
  374. controllerData.selectionComponent = motionController.getMainComponent();
  375. }
  376. controllerData.onButtonChangedObserver = controllerData.selectionComponent.onButtonStateChangedObservable.add((component) => {
  377. if (component.changes.pressed) {
  378. const pressed = component.changes.pressed.current;
  379. if (controllerData.pick) {
  380. if (pressed) {
  381. this._scene.simulatePointerDown(controllerData.pick, { pointerId: controllerData.id });
  382. (<StandardMaterial>controllerData.selectionMesh.material).emissiveColor = this.selectionMeshPickedColor;
  383. (<StandardMaterial>controllerData.laserPointer.material).emissiveColor = this.laserPointerPickedColor;
  384. } else {
  385. this._scene.simulatePointerUp(controllerData.pick, { pointerId: controllerData.id });
  386. (<StandardMaterial>controllerData.selectionMesh.material).emissiveColor = this.selectionMeshDefaultColor;
  387. (<StandardMaterial>controllerData.laserPointer.material).emissiveColor = this.laserPointerDefaultColor;
  388. }
  389. }
  390. }
  391. });
  392. };
  393. if (xrController.motionController) {
  394. init(xrController.motionController);
  395. } else {
  396. xrController.onMotionControllerInitObservable.add(init);
  397. }
  398. } else {
  399. // use the select and squeeze events
  400. const selectStartListener = (event: XRInputSourceEvent) => {
  401. if (event.inputSource === controllerData.xrController.inputSource && controllerData.pick) {
  402. this._scene.simulatePointerDown(controllerData.pick, { pointerId: controllerData.id });
  403. (<StandardMaterial>controllerData.selectionMesh.material).emissiveColor = this.selectionMeshPickedColor;
  404. (<StandardMaterial>controllerData.laserPointer.material).emissiveColor = this.laserPointerPickedColor;
  405. }
  406. };
  407. const selectEndListener = (event: XRInputSourceEvent) => {
  408. if (event.inputSource === controllerData.xrController.inputSource && controllerData.pick) {
  409. this._scene.simulatePointerUp(controllerData.pick, { pointerId: controllerData.id });
  410. (<StandardMaterial>controllerData.selectionMesh.material).emissiveColor = this.selectionMeshDefaultColor;
  411. (<StandardMaterial>controllerData.laserPointer.material).emissiveColor = this.laserPointerDefaultColor;
  412. }
  413. };
  414. controllerData.eventListeners = {
  415. selectend: selectEndListener,
  416. selectstart: selectStartListener
  417. };
  418. this._xrSessionManager.session.addEventListener('selectstart', selectStartListener);
  419. this._xrSessionManager.session.addEventListener('selectend', selectEndListener);
  420. }
  421. }
  422. private _convertNormalToDirectionOfRay(normal: Nullable<Vector3>, ray: Ray) {
  423. if (normal) {
  424. let angle = Math.acos(Vector3.Dot(normal, ray.direction));
  425. if (angle < Math.PI / 2) {
  426. normal.scaleInPlace(-1);
  427. }
  428. }
  429. return normal;
  430. }
  431. private _detachController(xrControllerUniqueId: string) {
  432. const controllerData = this._controllers[xrControllerUniqueId];
  433. if (!controllerData) { return; }
  434. if (controllerData.selectionComponent) {
  435. if (controllerData.onButtonChangedObserver) {
  436. controllerData.selectionComponent.onButtonStateChangedObservable.remove(controllerData.onButtonChangedObserver);
  437. }
  438. }
  439. if (controllerData.onFrameObserver) {
  440. this._xrSessionManager.onXRFrameObservable.remove(controllerData.onFrameObserver);
  441. }
  442. if (controllerData.eventListeners) {
  443. Object.keys(controllerData.eventListeners).forEach((eventName: string) => {
  444. const func = controllerData.eventListeners && controllerData.eventListeners[eventName as XREventType];
  445. if (func) {
  446. this._xrSessionManager.session.removeEventListener(eventName, func);
  447. }
  448. });
  449. }
  450. controllerData.selectionMesh.dispose();
  451. controllerData.laserPointer.dispose();
  452. // remove from the map
  453. delete this._controllers[xrControllerUniqueId];
  454. }
  455. private _generateNewMeshPair(xrController: WebXRInputSource) {
  456. const sceneToRenderTo = this._options.useUtilityLayer ? (this._options.customUtilityLayerScene || UtilityLayerRenderer.DefaultUtilityLayer.utilityLayerScene) : this._scene;
  457. const laserPointer = CylinderBuilder.CreateCylinder("laserPointer", {
  458. height: 1,
  459. diameterTop: 0.0002,
  460. diameterBottom: 0.004,
  461. tessellation: 20,
  462. subdivisions: 1
  463. }, sceneToRenderTo);
  464. laserPointer.parent = xrController.pointer;
  465. let laserPointerMaterial = new StandardMaterial("laserPointerMat", sceneToRenderTo);
  466. laserPointerMaterial.emissiveColor = this.laserPointerDefaultColor;
  467. laserPointerMaterial.alpha = 0.7;
  468. laserPointer.material = laserPointerMaterial;
  469. laserPointer.rotation.x = Math.PI / 2;
  470. this._updatePointerDistance(laserPointer, 1);
  471. laserPointer.isPickable = false;
  472. // Create a gaze tracker for the XR controller
  473. const selectionMesh = TorusBuilder.CreateTorus("gazeTracker", {
  474. diameter: 0.0035 * 3,
  475. thickness: 0.0025 * 3,
  476. tessellation: 20
  477. }, sceneToRenderTo);
  478. selectionMesh.bakeCurrentTransformIntoVertices();
  479. selectionMesh.isPickable = false;
  480. selectionMesh.isVisible = false;
  481. let targetMat = new StandardMaterial("targetMat", sceneToRenderTo);
  482. targetMat.specularColor = Color3.Black();
  483. targetMat.emissiveColor = this.selectionMeshDefaultColor;
  484. targetMat.backFaceCulling = false;
  485. selectionMesh.material = targetMat;
  486. if (this._options.renderingGroupId !== undefined) {
  487. laserPointer.renderingGroupId = this._options.renderingGroupId;
  488. selectionMesh.renderingGroupId = this._options.renderingGroupId;
  489. }
  490. return {
  491. laserPointer,
  492. selectionMesh
  493. };
  494. }
  495. private _pickingMoved(oldPick: PickingInfo, newPick: PickingInfo) {
  496. if (!oldPick.hit || !newPick.hit) { return true; }
  497. if (!oldPick.pickedMesh || !oldPick.pickedPoint || !newPick.pickedMesh || !newPick.pickedPoint) { return true; }
  498. if (oldPick.pickedMesh !== newPick.pickedMesh) { return true; }
  499. oldPick.pickedPoint?.subtractToRef(newPick.pickedPoint, this._tmpVectorForPickCompare);
  500. this._tmpVectorForPickCompare.set(Math.abs(this._tmpVectorForPickCompare.x), Math.abs(this._tmpVectorForPickCompare.y), Math.abs(this._tmpVectorForPickCompare.z));
  501. const delta = (this._options.gazeModePointerMovedFactor || 1) * 0.01 * newPick.distance;
  502. const length = this._tmpVectorForPickCompare.length();
  503. console.log(delta, length, newPick.distance);
  504. if (length > delta) { return true; }
  505. return false;
  506. }
  507. private _updatePointerDistance(_laserPointer: AbstractMesh, distance: number = 100) {
  508. _laserPointer.scaling.y = distance;
  509. // a bit of distance from the controller
  510. if (this._scene.useRightHandedSystem) {
  511. distance *= -1;
  512. }
  513. _laserPointer.position.z = (distance / 2) + 0.05;
  514. }
  515. /** @hidden */
  516. public get lasterPointerDefaultColor(): Color3 {
  517. // here due to a typo
  518. return this.laserPointerDefaultColor;
  519. }
  520. }
  521. //register the plugin
  522. WebXRFeaturesManager.AddWebXRFeature(WebXRControllerPointerSelection.Name, (xrSessionManager, options) => {
  523. return () => new WebXRControllerPointerSelection(xrSessionManager, options);
  524. }, WebXRControllerPointerSelection.Version, true);