WebXR Features Manager
Why is it needed
The Features Manager, our XR plugin system, was born out of a simple need: stay backward compatible while still delivering cutting-edge APIs in a production system.
Since APIs like Hit Test and anchors are constantly changing, and currently still have different support in different browser versions, there was a need for the “versioning” of the current development to keep up with API modifications over time.
If you are a bit impatient, you can check the selected WebXR features section, but it is recommended that you at least read this section quickly.
How to use
Construct a new features manager
If you are using the base WebXR experience helper, a features manager will be created for you and will be available in xrBaseHelper.featuresManager. If not, you only need to provide an XR session manager object to initialize a new one:
const fm = new FeaturesManager(xrSessionManager);Note that even before creating the features manager, you can call its static methods (check availability, register a new feature, etc.).
What is available
To check what feature is available, use the features manager's static GetAvailableFeatures function, which will return an array of strings corresponding to specific features:
const availableFeatures = WebXRFeaturesManager.GetAvailableFeatures(); // in plain JS, use BABYLON.WebXRFeaturesManager
// availableFeatures = ["xr-hit-test", "xr-pointer-selection", ...]To find out whether a specific feature is available, use this code:
const availableFeatures = WebXRFeaturesManager.GetAvailableFeatures();// using indexOf, but you can use any other method - includes, find, findIndex, in, etc'if (availableFeatures.indexOf(WebXRFeatureName.POINTER_SELECTION) !== -1) { // Pointer selection is available}WebXRFeatureName will always contain the list of possible features.
When using ES6, make sure to import the feature you want to use, otherwise it won't be registered:
import { WebXRControllerPhysics} from '@babylonjs/core/XR/features/WebXRControllerPhysics';
// The physics controller support will now be available and can be enabled:
fm.enableFeature(WebXRControllerPhysics.Name, "latest");Versioning
Just like any plugin system, the features are versioned numerically. The version is a number. The higher the number, the newer the version.
Two extra definitions are available: stable for the latest stable version of a feature, and latest, which is always updated to the plugin with the highest version number. Keep in mind that latest will always be available, but stable might not be for features that are not yet considered stable.
To get the available versions use the GetAvailableVersions static method. It will return an array of available versions, for example:
const availableVersions = featuresManager.GetAvailableVersions(WebXRFeatureName.POINTER_SELECTION);
// availableVersions = ["latest", "stable", "1", "2"];This means that you can ask for version 1, but also for the stable version, which will be either "1" or "2" depending on our definition. The "latest" version will load "2".
Enable and disable a feature
Enabling a feature means that the feature is ready to be used in a working session. When a feature is enabled, it is ready to be attached, which technically means it can actively influence the scene.
As an example, enabling the teleportation feature will register the onControllerAdded and onControllerRemoved observers. Attaching this feature will register all observers required for each controller to work properly, while detaching it will remove such observers. Furthermore, disabling the teleportation feature will make sure that the meshes are invisible and the onControllerAdded and onControllerRemoved observers will be removed. Finally, disposing of the feature will release everything, including all associated meshes.
To enable a feature, use a constructed features manager's enableFeature function with the name and version you wish to load:
// get the features managerconst fm = xr.baseExperience.featuresManager;
// disable an already-enabled featurefm.disableFeature(WebXRFeatureName.POINTER_SELECTION);
// enable the latest version of the "Hit Test" featureconst xrHitTestLatest = fm.enableFeature(WebXRFeatureName.HIT_TEST, "latest");
// enable the specific version of the "Hit Test" feature. This will disable an older implementation and enable the new oneconst xrHitTest1 = fm.enableFeature(WebXRFeatureName.HIT_TES, 1);Configuring the feature
Every feature currently has a different configuration options object that can be provided when enabling the feature. Each feature has an options interface. For example, version "1" of the "Hit Test" feature has the following options available:
interface IWebXRHitTestOptions { /** * Only test when user interacted with the scene. Default - hit test every frame */ testOnPointerDownOnly ? : boolean; /** * The node to use to transform the local results to world coordinates */ worldParentNode ? : TransformNode;}To use it:
// get the features managerconst fm = xr.baseExperience.featuresManager;
// enable the latest version of the "Hit Test" feature with a configuration object. The old "enabled" version will be disposed ofconst xrHitTestLatest = fm.enableFeature(WebXRFeatureName.HIT_TEST, "latest", { testOnPointerDownOnly: true});
// enable the specific version of the "Hit Test" feature with default configuration. The old config will be lostconst xrHitTest1 = fm.enableFeature(WebXRFeatureName.HIT_TES, 1);Attach and detach a feature
Once you have a feature enabled, you can synchronously attach (or detach) it to the scene:
import { WebXRFeatureName } from "@babylonjs/core/XR/webXRFeaturesManager";// enable version "1" of the "Hit Test" feature// Feature will be auto-attached once the session startsconst xrHitTest1 = fm.enableFeature(WebXRFeatureName.HIT_TEST, 1);// detach the feature, but keep it enabled (for reattachment). This also disables auto-attach, if not already attachedxrHitTest1.detach();// re-attachxrHitTest1.attach();Enabling a feature will attach it automatically when an XR session starts. If you want to only enable but not attach a feature (so you have full control of when it starts working), set the attachIfPossible argument to false (defaults to true):
// get the features managerconst fm = xr.baseExperience.featuresManager;const xrHitTestLatest = fm.enableFeature(WebXRFeatureName.HIT_TEST, "latest", { testOnPointerDownOnly: true}, false /* prevent attaching automatically */ );ES6 passive loader
When using the ES6 module loader, you will notice that no features apart from teleportation and pointer selection are available. You'll have to import other features (and their versions) yourself. This ensures that the resulting bundle includes only the code your experience actually needs to load, when it needs it. Wonderful for tree shaking!
So this won't work in ES6
const featuresManager = giveMeMyFeaturesManagerSomehow();// The "Hit Test" feature is not availableconst xrHitTest1 = featuresManager.enableFeature("xr-hit-test", 1);// Error thrown, "feature not found"Adding an import will make it work:
// other imports...import { WebXRHitTestLegacy} from "@babylonjs/core/Cameras/xr/features/WebXRHitTestLegacy";
const featuresManager = giveMeMyFeaturesManagerSomehow();// feature will now be enabledconst xrHitTest1 = featuresManager.enableFeature(WebXRFeatureName.HIT_TEST /* Same as "xr-hit-test" */ , 1);Writing a new feature
If you want to add your own feature, here are a few guidelines:
IWebXRFeature
The feature should implement the IWebXRFeature interface, which looks like this:
interface IWebXRFeature extends IDisposable { /** * Is this feature attached */ attached: boolean; /** * Should auto-attach be disabled? */ disableAutoAttach: boolean; /** * Attach the feature to the session * Will usually be called by the features manager * * @param force should attachment be forced (even when already attached) * @returns true if successful. */ attach(force ? : boolean): boolean; /** * Detach the feature from the session * Will usually be called by the features manager * * @returns true if successful. */ detach(): boolean;}To make this process easier, you can instead extend the WebXRAbstractFeature, which has a few extra helper functions and implements most functions needed for a working feature.
After creating the feature, you will need to register it with the features manager, so it can be enabled.
To register it, use the static AddWebXRFeature function:
const nameOfFeature = "awesome-name";const version = 1;const isTheFeatureStable = trueWebXRFeaturesManager.AddWebXRFeature(nameOfFeature, (xrSessionManager, options) => { return () => new MyNewFeature(xrSessionManager, options);}, version, isTheFeatureStable);This way you can enable the feature using its name whenever you wish to use it.