WebXRControllerTeleportation.ts 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. import { IWebXRFeature, WebXRFeaturesManager, WebXRFeatureName } from '../webXRFeaturesManager';
  2. import { Observer } from '../../Misc/observable';
  3. import { WebXRSessionManager } from '../webXRSessionManager';
  4. import { Nullable } from '../../types';
  5. import { WebXRInput } from '../webXRInput';
  6. import { WebXRInputSource } from '../webXRInputSource';
  7. import { WebXRControllerComponent, IWebXRMotionControllerAxesValue } from '../motionController/webXRControllerComponent';
  8. import { AbstractMesh } from '../../Meshes/abstractMesh';
  9. import { Vector3, Quaternion } from '../../Maths/math.vector';
  10. import { Ray } from '../../Culling/ray';
  11. import { Material } from '../../Materials/material';
  12. import { DynamicTexture } from '../../Materials/Textures/dynamicTexture';
  13. import { CylinderBuilder } from '../../Meshes/Builders/cylinderBuilder';
  14. import { SineEase, EasingFunction } from '../../Animations/easing';
  15. import { Animation } from '../../Animations/animation';
  16. import { Axis } from '../../Maths/math.axis';
  17. import { StandardMaterial } from '../../Materials/standardMaterial';
  18. import { GroundBuilder } from '../../Meshes/Builders/groundBuilder';
  19. import { TorusBuilder } from '../../Meshes/Builders/torusBuilder';
  20. import { PickingInfo } from '../../Collisions/pickingInfo';
  21. import { Curve3 } from '../../Maths/math.path';
  22. import { LinesBuilder } from '../../Meshes/Builders/linesBuilder';
  23. import { WebXRAbstractFeature } from './WebXRAbstractFeature';
  24. import { Color3 } from '../../Maths/math.color';
  25. import { Scene } from '../../scene';
  26. import { UtilityLayerRenderer } from '../../Rendering/utilityLayerRenderer';
  27. /**
  28. * The options container for the teleportation module
  29. */
  30. export interface IWebXRTeleportationOptions {
  31. /**
  32. * Babylon XR Input class for controller
  33. */
  34. xrInput: WebXRInput;
  35. /**
  36. * A list of meshes to use as floor meshes.
  37. * Meshes can be added and removed after initializing the feature using the
  38. * addFloorMesh and removeFloorMesh functions
  39. * If empty, rotation will still work
  40. */
  41. floorMeshes?: AbstractMesh[];
  42. /**
  43. * Provide your own teleportation mesh instead of babylon's wonderful doughnut.
  44. * If you want to support rotation, make sure your mesh has a direction indicator.
  45. *
  46. * When left untouched, the default mesh will be initialized.
  47. */
  48. teleportationTargetMesh?: AbstractMesh;
  49. /**
  50. * Values to configure the default target mesh
  51. */
  52. defaultTargetMeshOptions?: {
  53. /**
  54. * Fill color of the teleportation area
  55. */
  56. teleportationFillColor?: string;
  57. /**
  58. * Border color for the teleportation area
  59. */
  60. teleportationBorderColor?: string;
  61. /**
  62. * Disable the mesh's animation sequence
  63. */
  64. disableAnimation?: boolean;
  65. /**
  66. * Disable lighting on the material or the ring and arrow
  67. */
  68. disableLighting?: boolean;
  69. /**
  70. * Override the default material of the torus and arrow
  71. */
  72. torusArrowMaterial?: Material;
  73. };
  74. /**
  75. * Disable using the thumbstick and use the main component (usuallly trigger) on long press.
  76. * This will be automatically true if the controller doesnt have a thumbstick or touchpad.
  77. */
  78. useMainComponentOnly?: boolean;
  79. /**
  80. * If main component is used (no thumbstick), how long should the "long press" take before teleporting
  81. */
  82. timeToTeleport?: number;
  83. /**
  84. * Should meshes created here be added to a utility layer or the main scene
  85. */
  86. useUtilityLayer?: boolean;
  87. /**
  88. * if provided, this scene will be used to render meshes.
  89. */
  90. customUtilityLayerScene?: Scene;
  91. }
  92. /**
  93. * This is a teleportation feature to be used with webxr-enabled motion controllers.
  94. * When enabled and attached, the feature will allow a user to move aroundand rotate in the scene using
  95. * the input of the attached controllers.
  96. */
  97. export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
  98. /**
  99. * The module's name
  100. */
  101. public static readonly Name = WebXRFeatureName.TELEPORTATION;
  102. /**
  103. * The (Babylon) version of this module.
  104. * This is an integer representing the implementation version.
  105. * This number does not correspond to the webxr specs version
  106. */
  107. public static readonly Version = 1;
  108. /**
  109. * Is rotation enabled when moving forward?
  110. * Disabling this feature will prevent the user from deciding the direction when teleporting
  111. */
  112. public rotationEnabled: boolean = true;
  113. /**
  114. * Should the module support parabolic ray on top of direct ray
  115. * If enabled, the user will be able to point "at the sky" and move according to predefined radius distance
  116. * Very helpful when moving between floors / different heights
  117. */
  118. public parabolicRayEnabled: boolean = true;
  119. /**
  120. * The distance from the user to the inspection point in the direction of the controller
  121. * A higher number will allow the user to move further
  122. * defaults to 5 (meters, in xr units)
  123. */
  124. public parabolicCheckRadius: number = 5;
  125. /**
  126. * How much rotation should be applied when rotating right and left
  127. */
  128. public rotationAngle: number = Math.PI / 8;
  129. /**
  130. * Is movement backwards enabled
  131. */
  132. public backwardsMovementEnabled = true;
  133. /**
  134. * Distance to travel when moving backwards
  135. */
  136. public backwardsTeleportationDistance: number = 0.7;
  137. /**
  138. * Add a new mesh to the floor meshes array
  139. * @param mesh the mesh to use as floor mesh
  140. */
  141. public addFloorMesh(mesh: AbstractMesh) {
  142. this._floorMeshes.push(mesh);
  143. }
  144. /**
  145. * Remove a mesh from the floor meshes array
  146. * @param mesh the mesh to remove
  147. */
  148. public removeFloorMesh(mesh: AbstractMesh) {
  149. const index = this._floorMeshes.indexOf(mesh);
  150. if (index !== -1) {
  151. this._floorMeshes.splice(index, 1);
  152. }
  153. }
  154. /**
  155. * Remove a mesh from the floor meshes array using its name
  156. * @param name the mesh name to remove
  157. */
  158. public removeFloorMeshByName(name: string) {
  159. const mesh = this._xrSessionManager.scene.getMeshByName(name);
  160. if (mesh) {
  161. this.removeFloorMesh(mesh);
  162. }
  163. }
  164. private _tmpRay = new Ray(new Vector3(), new Vector3());
  165. private _tmpVector = new Vector3();
  166. private _floorMeshes: AbstractMesh[];
  167. private _controllers: {
  168. [controllerUniqueId: string]: {
  169. xrController: WebXRInputSource;
  170. teleportationComponent?: WebXRControllerComponent;
  171. teleportationState: {
  172. forward: boolean;
  173. backwards: boolean;
  174. currentRotation: number;
  175. baseRotation: number;
  176. rotating: boolean;
  177. }
  178. onAxisChangedObserver?: Nullable<Observer<IWebXRMotionControllerAxesValue>>;
  179. onButtonChangedObserver?: Nullable<Observer<WebXRControllerComponent>>;
  180. };
  181. } = {};
  182. /**
  183. * constructs a new anchor system
  184. * @param _xrSessionManager an instance of WebXRSessionManager
  185. * @param _options configuration object for this feature
  186. */
  187. constructor(_xrSessionManager: WebXRSessionManager, private _options: IWebXRTeleportationOptions) {
  188. super(_xrSessionManager);
  189. // create default mesh if not provided
  190. if (!this._options.teleportationTargetMesh) {
  191. this.createDefaultTargetMesh();
  192. }
  193. this._floorMeshes = this._options.floorMeshes || [];
  194. this.setTargetMeshVisibility(false);
  195. }
  196. private _selectionFeature: IWebXRFeature;
  197. /**
  198. * This function sets a selection feature that will be disabled when
  199. * the forward ray is shown and will be reattached when hidden.
  200. * This is used to remove the selection rays when moving.
  201. * @param selectionFeature the feature to disable when forward movement is enabled
  202. */
  203. public setSelectionFeature(selectionFeature: IWebXRFeature) {
  204. this._selectionFeature = selectionFeature;
  205. }
  206. public attach(): boolean {
  207. if (!super.attach()) {
  208. return false;
  209. }
  210. this._options.xrInput.controllers.forEach(this._attachController);
  211. this._addNewAttachObserver(this._options.xrInput.onControllerAddedObservable, this._attachController);
  212. this._addNewAttachObserver(this._options.xrInput.onControllerRemovedObservable, (controller) => {
  213. // REMOVE the controller
  214. this._detachController(controller.uniqueId);
  215. });
  216. return true;
  217. }
  218. public detach(): boolean {
  219. if (!super.detach()) {
  220. return false;
  221. }
  222. Object.keys(this._controllers).forEach((controllerId) => {
  223. this._detachController(controllerId);
  224. });
  225. this.setTargetMeshVisibility(false);
  226. return true;
  227. }
  228. public dispose(): void {
  229. super.dispose();
  230. this._options.teleportationTargetMesh && this._options.teleportationTargetMesh.dispose(false, true);
  231. }
  232. protected _onXRFrame(_xrFrame: XRFrame) {
  233. const frame = this._xrSessionManager.currentFrame;
  234. const scene = this._xrSessionManager.scene;
  235. if (!this.attach || !frame) { return; }
  236. // render target if needed
  237. const targetMesh = this._options.teleportationTargetMesh;
  238. if (this._currentTeleportationControllerId) {
  239. if (!targetMesh) {
  240. return;
  241. }
  242. targetMesh.rotationQuaternion = targetMesh.rotationQuaternion || new Quaternion();
  243. const controllerData = this._controllers[this._currentTeleportationControllerId];
  244. if (controllerData.teleportationState.forward) {
  245. // set the rotation
  246. Quaternion.RotationYawPitchRollToRef(controllerData.teleportationState.currentRotation + controllerData.teleportationState.baseRotation, 0, 0, targetMesh.rotationQuaternion);
  247. // set the ray and position
  248. let hitPossible = false;
  249. // first check if direct ray possible
  250. controllerData.xrController.getWorldPointerRayToRef(this._tmpRay);
  251. // pick grounds that are LOWER only. upper will use parabolic path
  252. let pick = scene.pickWithRay(this._tmpRay, (o) => {
  253. const index = this._floorMeshes.indexOf(o);
  254. if (index === -1) { return false; }
  255. return (this._floorMeshes[index].absolutePosition.y < this._options.xrInput.xrCamera.position.y);
  256. });
  257. if (pick && pick.pickedPoint) {
  258. hitPossible = true;
  259. this.setTargetMeshPosition(pick.pickedPoint);
  260. this.setTargetMeshVisibility(true);
  261. this._showParabolicPath(pick);
  262. } else {
  263. if (this.parabolicRayEnabled) {
  264. // radius compensation according to pointer rotation around X
  265. const xRotation = controllerData.xrController.pointer.rotationQuaternion!.toEulerAngles().x;
  266. const compensation = (1 + ((Math.PI / 2) - Math.abs(xRotation)));
  267. // check parabolic ray
  268. const radius = this.parabolicCheckRadius * compensation;
  269. this._tmpRay.origin.addToRef(this._tmpRay.direction.scale(radius * 2), this._tmpVector);
  270. this._tmpVector.y = this._tmpRay.origin.y;
  271. this._tmpRay.origin.addInPlace(this._tmpRay.direction.scale(radius));
  272. this._tmpVector.subtractToRef(this._tmpRay.origin, this._tmpRay.direction);
  273. this._tmpRay.direction.normalize();
  274. let pick = scene.pickWithRay(this._tmpRay, (o) => {
  275. return this._floorMeshes.indexOf(o) !== -1;
  276. });
  277. if (pick && pick.pickedPoint) {
  278. hitPossible = true;
  279. this.setTargetMeshPosition(pick.pickedPoint);
  280. this.setTargetMeshVisibility(true);
  281. this._showParabolicPath(pick);
  282. }
  283. }
  284. }
  285. // if needed, set visible:
  286. this.setTargetMeshVisibility(hitPossible);
  287. } else {
  288. this.setTargetMeshVisibility(false);
  289. }
  290. } else {
  291. this.setTargetMeshVisibility(false);
  292. }
  293. }
  294. private _currentTeleportationControllerId: string;
  295. private _attachController = (xrController: WebXRInputSource) => {
  296. if (this._controllers[xrController.uniqueId]) {
  297. // already attached
  298. return;
  299. }
  300. this._controllers[xrController.uniqueId] = {
  301. xrController,
  302. teleportationState: {
  303. forward: false,
  304. backwards: false,
  305. rotating: false,
  306. currentRotation: 0,
  307. baseRotation: 0
  308. }
  309. };
  310. const controllerData = this._controllers[xrController.uniqueId];
  311. // motion controller support
  312. xrController.onMotionControllerInitObservable.addOnce(() => {
  313. if (xrController.motionController) {
  314. const movementController = xrController.motionController.getComponentOfType(WebXRControllerComponent.THUMBSTICK) || xrController.motionController.getComponentOfType(WebXRControllerComponent.TOUCHPAD);
  315. if (!movementController || this._options.useMainComponentOnly) {
  316. // use trigger to move on long press
  317. const mainComponent = xrController.motionController.getMainComponent();
  318. if (!mainComponent) {
  319. return;
  320. }
  321. controllerData.onButtonChangedObserver = mainComponent.onButtonStateChangedObservable.add(() => {
  322. // did "pressed" changed?
  323. if (mainComponent.changes.pressed) {
  324. if (mainComponent.changes.pressed.current) {
  325. // simulate "forward" thumbstick push
  326. controllerData.teleportationState.forward = true;
  327. this._currentTeleportationControllerId = controllerData.xrController.uniqueId;
  328. controllerData.teleportationState.baseRotation = this._options.xrInput.xrCamera.rotationQuaternion.toEulerAngles().y;
  329. controllerData.teleportationState.currentRotation = 0;
  330. const timeToSelect = this._options.timeToTeleport || 3000;
  331. let timer = 0;
  332. const observer = this._xrSessionManager.onXRFrameObservable.add(() => {
  333. if (!mainComponent.pressed) {
  334. this._xrSessionManager.onXRFrameObservable.remove(observer);
  335. return;
  336. }
  337. timer += this._xrSessionManager.scene.getEngine().getDeltaTime();
  338. if (timer >= timeToSelect && this._currentTeleportationControllerId === controllerData.xrController.uniqueId && controllerData.teleportationState.forward) {
  339. this._teleportForward(xrController.uniqueId);
  340. }
  341. // failsafe
  342. if (timer >= timeToSelect) {
  343. this._xrSessionManager.onXRFrameObservable.remove(observer);
  344. }
  345. });
  346. } else {
  347. controllerData.teleportationState.forward = false;
  348. this._currentTeleportationControllerId = "";
  349. }
  350. }
  351. });
  352. } else {
  353. controllerData.onButtonChangedObserver = movementController.onButtonStateChangedObservable.add(() => {
  354. if (this._currentTeleportationControllerId === controllerData.xrController.uniqueId && controllerData.teleportationState.forward && !movementController.touched) {
  355. this._teleportForward(xrController.uniqueId);
  356. }
  357. });
  358. // use thumbstick (or touchpad if thumbstick not available)
  359. controllerData.onAxisChangedObserver = movementController.onAxisValueChangedObservable.add((axesData) => {
  360. if (axesData.y <= 0.7 && controllerData.teleportationState.backwards) {
  361. //if (this._currentTeleportationControllerId === controllerData.xrController.uniqueId) {
  362. controllerData.teleportationState.backwards = false;
  363. //this._currentTeleportationControllerId = "";
  364. //}
  365. }
  366. if (axesData.y > 0.7 && !controllerData.teleportationState.forward && this.backwardsMovementEnabled) {
  367. // teleport backwards
  368. if (!controllerData.teleportationState.backwards) {
  369. controllerData.teleportationState.backwards = true;
  370. // teleport backwards ONCE
  371. this._tmpVector.set(0, 0, this.backwardsTeleportationDistance!);
  372. this._tmpVector.rotateByQuaternionToRef(this._options.xrInput.xrCamera.rotationQuaternion!, this._tmpVector);
  373. this._tmpVector.addInPlace(this._options.xrInput.xrCamera.position);
  374. this._options.xrInput.xrCamera.position.subtractToRef(this._tmpVector, this._tmpVector);
  375. this._tmpRay.origin.copyFrom(this._tmpVector);
  376. this._tmpRay.direction.set(0, -1, 0);
  377. let pick = this._xrSessionManager.scene.pickWithRay(this._tmpRay, (o) => {
  378. return this._floorMeshes.indexOf(o) !== -1;
  379. });
  380. // pick must exist, but stay safe
  381. if (pick && pick.pickedPoint) {
  382. // Teleport the users feet to where they targeted
  383. this._options.xrInput.xrCamera.position.addInPlace(pick.pickedPoint);
  384. }
  385. }
  386. }
  387. if (axesData.y < -0.7 && !this._currentTeleportationControllerId && !controllerData.teleportationState.rotating) {
  388. controllerData.teleportationState.forward = true;
  389. this._currentTeleportationControllerId = controllerData.xrController.uniqueId;
  390. controllerData.teleportationState.baseRotation = this._options.xrInput.xrCamera.rotationQuaternion.toEulerAngles().y;
  391. }
  392. if (axesData.x) {
  393. if (!controllerData.teleportationState.forward) {
  394. if (!controllerData.teleportationState.rotating && Math.abs(axesData.x) > 0.7) {
  395. // rotate in the right direction positive is right
  396. controllerData.teleportationState.rotating = true;
  397. const rotation = this.rotationAngle * (axesData.x > 0 ? 1 : -1);
  398. this._options.xrInput.xrCamera.rotationQuaternion.multiplyInPlace(Quaternion.FromEulerAngles(0, rotation, 0));
  399. }
  400. } else {
  401. if (this._currentTeleportationControllerId === controllerData.xrController.uniqueId) {
  402. // set the rotation of the forward movement
  403. if (this.rotationEnabled) {
  404. setTimeout(() => {
  405. controllerData.teleportationState.currentRotation = Math.atan2(axesData.x, -axesData.y);
  406. });
  407. } else {
  408. controllerData.teleportationState.currentRotation = 0;
  409. }
  410. }
  411. }
  412. } else {
  413. controllerData.teleportationState.rotating = false;
  414. }
  415. });
  416. }
  417. }
  418. });
  419. }
  420. private _teleportForward(controllerId: string) {
  421. const controllerData = this._controllers[controllerId];
  422. controllerData.teleportationState.forward = false;
  423. this._currentTeleportationControllerId = "";
  424. // do the movement forward here
  425. if (this._options.teleportationTargetMesh && this._options.teleportationTargetMesh.isVisible) {
  426. const height = this._options.xrInput.xrCamera.realWorldHeight;
  427. this._options.xrInput.xrCamera.position.copyFrom(this._options.teleportationTargetMesh.position);
  428. this._options.xrInput.xrCamera.position.y += height;
  429. this._options.xrInput.xrCamera.rotationQuaternion.multiplyInPlace(Quaternion.FromEulerAngles(0, controllerData.teleportationState.currentRotation, 0));
  430. }
  431. }
  432. private _detachController(xrControllerUniqueId: string) {
  433. const controllerData = this._controllers[xrControllerUniqueId];
  434. if (!controllerData) { return; }
  435. if (controllerData.teleportationComponent) {
  436. if (controllerData.onAxisChangedObserver) {
  437. controllerData.teleportationComponent.onAxisValueChangedObservable.remove(controllerData.onAxisChangedObserver);
  438. }
  439. if (controllerData.onButtonChangedObserver) {
  440. controllerData.teleportationComponent.onButtonStateChangedObservable.remove(controllerData.onButtonChangedObserver);
  441. }
  442. }
  443. // remove from the map
  444. delete this._controllers[xrControllerUniqueId];
  445. }
  446. private createDefaultTargetMesh() {
  447. // set defaults
  448. this._options.defaultTargetMeshOptions = this._options.defaultTargetMeshOptions || {};
  449. const sceneToRenderTo = this._options.useUtilityLayer ? (this._options.customUtilityLayerScene || UtilityLayerRenderer.DefaultUtilityLayer.utilityLayerScene) : this._xrSessionManager.scene;
  450. let teleportationTarget = GroundBuilder.CreateGround("teleportationTarget", { width: 2, height: 2, subdivisions: 2 }, sceneToRenderTo);
  451. teleportationTarget.isPickable = false;
  452. let length = 512;
  453. let dynamicTexture = new DynamicTexture("teleportationPlaneDynamicTexture", length, sceneToRenderTo, true);
  454. dynamicTexture.hasAlpha = true;
  455. let context = dynamicTexture.getContext();
  456. let centerX = length / 2;
  457. let centerY = length / 2;
  458. let radius = 200;
  459. context.beginPath();
  460. context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
  461. context.fillStyle = this._options.defaultTargetMeshOptions.teleportationFillColor || "#444444";
  462. context.fill();
  463. context.lineWidth = 10;
  464. context.strokeStyle = this._options.defaultTargetMeshOptions.teleportationBorderColor || "#FFFFFF";
  465. context.stroke();
  466. context.closePath();
  467. dynamicTexture.update();
  468. const teleportationCircleMaterial = new StandardMaterial("teleportationPlaneMaterial", sceneToRenderTo);
  469. teleportationCircleMaterial.diffuseTexture = dynamicTexture;
  470. teleportationTarget.material = teleportationCircleMaterial;
  471. let torus = TorusBuilder.CreateTorus("torusTeleportation", {
  472. diameter: 0.75,
  473. thickness: 0.1,
  474. tessellation: 20
  475. }, sceneToRenderTo);
  476. torus.isPickable = false;
  477. torus.parent = teleportationTarget;
  478. if (!this._options.defaultTargetMeshOptions.disableAnimation) {
  479. let animationInnerCircle = new Animation("animationInnerCircle", "position.y", 30, Animation.ANIMATIONTYPE_FLOAT, Animation.ANIMATIONLOOPMODE_CYCLE);
  480. let keys = [];
  481. keys.push({
  482. frame: 0,
  483. value: 0
  484. });
  485. keys.push({
  486. frame: 30,
  487. value: 0.4
  488. });
  489. keys.push({
  490. frame: 60,
  491. value: 0
  492. });
  493. animationInnerCircle.setKeys(keys);
  494. let easingFunction = new SineEase();
  495. easingFunction.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT);
  496. animationInnerCircle.setEasingFunction(easingFunction);
  497. torus.animations = [];
  498. torus.animations.push(animationInnerCircle);
  499. sceneToRenderTo.beginAnimation(torus, 0, 60, true);
  500. }
  501. var cone = CylinderBuilder.CreateCylinder("cone", { diameterTop: 0, tessellation: 4 }, sceneToRenderTo);
  502. cone.isPickable = false;
  503. cone.scaling.set(0.5, 0.12, 0.2);
  504. cone.rotate(Axis.X, Math.PI / 2);
  505. cone.position.z = 0.6;
  506. cone.parent = torus;
  507. if (this._options.defaultTargetMeshOptions.torusArrowMaterial) {
  508. torus.material = this._options.defaultTargetMeshOptions.torusArrowMaterial;
  509. cone.material = this._options.defaultTargetMeshOptions.torusArrowMaterial;
  510. } else {
  511. const torusConeMaterial = new StandardMaterial("torusConsMat", sceneToRenderTo);
  512. torusConeMaterial.disableLighting = !!this._options.defaultTargetMeshOptions.disableLighting;
  513. if (torusConeMaterial.disableLighting) {
  514. torusConeMaterial.emissiveColor = new Color3(0.3, 0.3, 1.0);
  515. } else {
  516. torusConeMaterial.diffuseColor = new Color3(0.3, 0.3, 1.0);
  517. }
  518. torusConeMaterial.alpha = 0.9;
  519. torus.material = torusConeMaterial;
  520. cone.material = torusConeMaterial;
  521. }
  522. this._options.teleportationTargetMesh = teleportationTarget;
  523. }
  524. private setTargetMeshVisibility(visible: boolean) {
  525. if (!this._options.teleportationTargetMesh) { return; }
  526. if (this._options.teleportationTargetMesh.isVisible === visible) { return; }
  527. this._options.teleportationTargetMesh.isVisible = visible;
  528. this._options.teleportationTargetMesh.getChildren(undefined, false).forEach((m) => { (<any>(m)).isVisible = visible; });
  529. if (!visible) {
  530. if (this._quadraticBezierCurve) {
  531. this._quadraticBezierCurve.dispose();
  532. }
  533. if (this._selectionFeature) {
  534. this._selectionFeature.attach();
  535. }
  536. } else {
  537. if (this._selectionFeature) {
  538. this._selectionFeature.detach();
  539. }
  540. }
  541. }
  542. private setTargetMeshPosition(newPosition: Vector3) {
  543. if (!this._options.teleportationTargetMesh) { return; }
  544. this._options.teleportationTargetMesh.position.copyFrom(newPosition);
  545. this._options.teleportationTargetMesh.position.y += 0.01;
  546. }
  547. private _quadraticBezierCurve: AbstractMesh;
  548. private _showParabolicPath(pickInfo: PickingInfo) {
  549. if (!pickInfo.pickedPoint) { return; }
  550. const controllerData = this._controllers[this._currentTeleportationControllerId];
  551. const quadraticBezierVectors = Curve3.CreateQuadraticBezier(
  552. controllerData.xrController.pointer.absolutePosition,
  553. pickInfo.ray!.origin,
  554. pickInfo.pickedPoint,
  555. 25);
  556. if (this._quadraticBezierCurve) {
  557. this._quadraticBezierCurve.dispose();
  558. }
  559. this._quadraticBezierCurve = LinesBuilder.CreateLines("path line", { points: quadraticBezierVectors.getPoints() });
  560. this._quadraticBezierCurve.isPickable = false;
  561. }
  562. }
  563. WebXRFeaturesManager.AddWebXRFeature(WebXRMotionControllerTeleportation.Name, (xrSessionManager, options) => {
  564. return () => new WebXRMotionControllerTeleportation(xrSessionManager, options);
  565. }, WebXRMotionControllerTeleportation.Version, true);