import { WebXRGenericTriggerMotionController } from "./webXRGenericMotionController.js"; import { Tools } from "../../Misc/tools.js"; import { WebXRProfiledMotionController } from "./webXRProfiledMotionController.js"; /** * The MotionController Manager manages all registered motion controllers and loads the right one when needed. * * When this repository is complete: https://github.com/immersive-web/webxr-input-profiles/tree/master/packages/assets * it should be replaced with auto-loaded controllers. * * When using a model try to stay as generic as possible. Eventually there will be no need in any of the controller classes */ const controllerCache = []; /** * Motion controller manager is managing the different webxr profiles and makes sure the right * controller is being loaded. */ export class WebXRMotionControllerManager { /** * Clear the cache used for profile loading and reload when requested again */ static ClearProfilesCache() { this._ProfilesList = null; this._ProfileLoadingPromises = {}; } /** * Register the default fallbacks. * This function is called automatically when this file is imported. */ static DefaultFallbacks() { this.RegisterFallbacksForProfileId("google-daydream", ["generic-touchpad"]); this.RegisterFallbacksForProfileId("htc-vive-focus", ["generic-trigger-touchpad"]); this.RegisterFallbacksForProfileId("htc-vive", ["generic-trigger-squeeze-touchpad"]); this.RegisterFallbacksForProfileId("magicleap-one", ["generic-trigger-squeeze-touchpad"]); this.RegisterFallbacksForProfileId("windows-mixed-reality", ["generic-trigger-squeeze-touchpad-thumbstick"]); this.RegisterFallbacksForProfileId("microsoft-mixed-reality", ["windows-mixed-reality", "generic-trigger-squeeze-touchpad-thumbstick"]); this.RegisterFallbacksForProfileId("oculus-go", ["generic-trigger-touchpad"]); this.RegisterFallbacksForProfileId("oculus-touch-v2", ["oculus-touch", "generic-trigger-squeeze-thumbstick"]); this.RegisterFallbacksForProfileId("oculus-touch", ["generic-trigger-squeeze-thumbstick"]); this.RegisterFallbacksForProfileId("samsung-gearvr", ["windows-mixed-reality", "generic-trigger-squeeze-touchpad-thumbstick"]); this.RegisterFallbacksForProfileId("samsung-odyssey", ["generic-touchpad"]); this.RegisterFallbacksForProfileId("valve-index", ["generic-trigger-squeeze-touchpad-thumbstick"]); this.RegisterFallbacksForProfileId("generic-hand-select", ["generic-trigger"]); } /** * Find a fallback profile if the profile was not found. There are a few predefined generic profiles. * @param profileId the profile to which a fallback needs to be found * @returns an array with corresponding fallback profiles */ static FindFallbackWithProfileId(profileId) { const returnArray = this._Fallbacks[profileId] || []; returnArray.unshift(profileId); return returnArray; } /** * When acquiring a new xrInput object (usually by the WebXRInput class), match it with the correct profile. * The order of search: * * 1) Iterate the profiles array of the xr input and try finding a corresponding motion controller * 2) (If not found) search in the gamepad id and try using it (legacy versions only) * 3) search for registered fallbacks (should be redundant, nonetheless it makes sense to check) * 4) return the generic trigger controller if none were found * * @param xrInput the xrInput to which a new controller is initialized * @param scene the scene to which the model will be added * @param forceProfile force a certain profile for this controller * @returns A promise that fulfils with the motion controller class for this profile id or the generic standard class if none was found */ static GetMotionControllerWithXRInput(xrInput, scene, forceProfile) { const profileArray = []; if (forceProfile) { profileArray.push(forceProfile); } profileArray.push(...(xrInput.profiles || [])); // emulator support if (profileArray.length && !profileArray[0]) { // remove the first "undefined" that the emulator is adding profileArray.pop(); } // legacy support - try using the gamepad id if (xrInput.gamepad && xrInput.gamepad.id) { switch (xrInput.gamepad.id) { case xrInput.gamepad.id.match(/oculus touch/gi) ? xrInput.gamepad.id : undefined: // oculus in gamepad id profileArray.push("oculus-touch-v2"); break; } } // make sure microsoft/windows mixed reality works correctly const windowsMRIdx = profileArray.indexOf("windows-mixed-reality"); if (windowsMRIdx !== -1) { profileArray.splice(windowsMRIdx, 0, "microsoft-mixed-reality"); } if (!profileArray.length) { profileArray.push("generic-trigger"); } if (this.UseOnlineRepository) { const firstFunction = this.PrioritizeOnlineRepository ? this._LoadProfileFromRepository : this._LoadProfilesFromAvailableControllers; const secondFunction = this.PrioritizeOnlineRepository ? this._LoadProfilesFromAvailableControllers : this._LoadProfileFromRepository; return firstFunction.call(this, profileArray, xrInput, scene).catch(() => { return secondFunction.call(this, profileArray, xrInput, scene); }); } else { // use only available functions return this._LoadProfilesFromAvailableControllers(profileArray, xrInput, scene); } } /** * Register a new controller based on its profile. This function will be called by the controller classes themselves. * * If you are missing a profile, make sure it is imported in your source, otherwise it will not register. * * @param type the profile type to register * @param constructFunction the function to be called when loading this profile */ static RegisterController(type, constructFunction) { this._AvailableControllers[type] = constructFunction; } /** * Register a fallback to a specific profile. * @param profileId the profileId that will receive the fallbacks * @param fallbacks A list of fallback profiles */ static RegisterFallbacksForProfileId(profileId, fallbacks) { if (this._Fallbacks[profileId]) { this._Fallbacks[profileId].push(...fallbacks); } else { this._Fallbacks[profileId] = fallbacks; } } /** * Will update the list of profiles available in the repository * @returns a promise that resolves to a map of profiles available online */ static UpdateProfilesList() { this._ProfilesList = Tools.LoadFileAsync(this.BaseRepositoryUrl + "/profiles/profilesList.json", false).then((data) => { return JSON.parse(data); }); return this._ProfilesList; } /** * Clear the controller's cache (usually happens at the end of a session) */ static ClearControllerCache() { controllerCache.forEach((cacheItem) => { cacheItem.meshes.forEach((mesh) => { mesh.dispose(false, true); }); }); controllerCache.length = 0; } static _LoadProfileFromRepository(profileArray, xrInput, scene) { return Promise.resolve() .then(() => { if (!this._ProfilesList) { return this.UpdateProfilesList(); } else { return this._ProfilesList; } }) .then((profilesList) => { // load the right profile for (let i = 0; i < profileArray.length; ++i) { // defensive if (!profileArray[i]) { continue; } if (profilesList[profileArray[i]]) { return profileArray[i]; } } throw new Error(`neither controller ${profileArray[0]} nor all fallbacks were found in the repository,`); }) .then((profileToLoad) => { // load the profile if (!this._ProfileLoadingPromises[profileToLoad]) { this._ProfileLoadingPromises[profileToLoad] = Tools.LoadFileAsync(`${this.BaseRepositoryUrl}/profiles/${profileToLoad}/profile.json`, false).then((data) => JSON.parse(data)); } return this._ProfileLoadingPromises[profileToLoad]; }) .then((profile) => { return new WebXRProfiledMotionController(scene, xrInput, profile, this.BaseRepositoryUrl, this.DisableControllerCache ? undefined : controllerCache); }); } static _LoadProfilesFromAvailableControllers(profileArray, xrInput, scene) { // check fallbacks for (let i = 0; i < profileArray.length; ++i) { // defensive if (!profileArray[i]) { continue; } const fallbacks = this.FindFallbackWithProfileId(profileArray[i]); for (let j = 0; j < fallbacks.length; ++j) { const constructionFunction = this._AvailableControllers[fallbacks[j]]; if (constructionFunction) { return Promise.resolve(constructionFunction(xrInput, scene)); } } } throw new Error(`no controller requested was found in the available controllers list`); } } WebXRMotionControllerManager._AvailableControllers = {}; WebXRMotionControllerManager._Fallbacks = {}; // cache for loading WebXRMotionControllerManager._ProfileLoadingPromises = {}; /** * The base URL of the online controller repository. Can be changed at any time. */ WebXRMotionControllerManager.BaseRepositoryUrl = "https://immersive-web.github.io/webxr-input-profiles/packages/viewer/dist"; /** * Which repository gets priority - local or online */ WebXRMotionControllerManager.PrioritizeOnlineRepository = true; /** * Use the online repository, or use only locally-defined controllers */ WebXRMotionControllerManager.UseOnlineRepository = true; /** * Disable the controller cache and load the models each time a new WebXRProfileMotionController is loaded. * Defaults to true. */ WebXRMotionControllerManager.DisableControllerCache = true; // register the generic profile(s) here so we will at least have them WebXRMotionControllerManager.RegisterController(WebXRGenericTriggerMotionController.ProfileId, (xrInput, scene) => { return new WebXRGenericTriggerMotionController(scene, xrInput.gamepad, xrInput.handedness); }); // register fallbacks WebXRMotionControllerManager.DefaultFallbacks(); //# sourceMappingURL=webXRMotionControllerManager.js.map