UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

348 lines (347 loc) 12 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { path } from "../core/path.js"; import { Debug } from "../core/debug.js"; import { ABSOLUTE_URL } from "./asset/constants.js"; import { SceneRegistryItem } from "./scene-registry-item.js"; class SceneRegistry { /** * Create a new SceneRegistry instance. * * @param {AppBase} app - The application. */ constructor(app) { /** * @type {AppBase} * @private */ __publicField(this, "_app"); /** * @type {SceneRegistryItem[]} * @private */ __publicField(this, "_list", []); /** @private */ __publicField(this, "_index", {}); /** @private */ __publicField(this, "_urlIndex", {}); this._app = app; } /** @ignore */ destroy() { this._app = null; } /** * Return the list of scene. * * @returns {SceneRegistryItem[]} All items in the registry. */ list() { return this._list; } /** * Add a new item to the scene registry. * * @param {string} name - The name of the scene. * @param {string} url - The url of the scene file. * @returns {boolean} Returns true if the scene was successfully added to the registry, false otherwise. */ add(name, url) { if (this._index.hasOwnProperty(name)) { Debug.warn(`pc.SceneRegistry: trying to add more than one scene called: ${name}`); return false; } const item = new SceneRegistryItem(name, url); const i = this._list.push(item); this._index[item.name] = i - 1; this._urlIndex[item.url] = i - 1; return true; } /** * Find a Scene by name and return the {@link SceneRegistryItem}. * * @param {string} name - The name of the scene. * @returns {SceneRegistryItem|null} The stored data about a scene or null if no scene with * that name exists. */ find(name) { if (this._index.hasOwnProperty(name)) { return this._list[this._index[name]]; } return null; } /** * Find a scene by the URL and return the {@link SceneRegistryItem}. * * @param {string} url - The URL to search by. * @returns {SceneRegistryItem|null} The stored data about a scene or null if no scene with * that URL exists. */ findByUrl(url) { if (this._urlIndex.hasOwnProperty(url)) { return this._list[this._urlIndex[url]]; } return null; } /** * Remove an item from the scene registry. * * @param {string} name - The name of the scene. */ remove(name) { if (this._index.hasOwnProperty(name)) { const idx = this._index[name]; let item = this._list[idx]; delete this._urlIndex[item.url]; delete this._index[name]; this._list.splice(idx, 1); for (let i = 0; i < this._list.length; i++) { item = this._list[i]; this._index[item.name] = i; this._urlIndex[item.url] = i; } } } /** * Private function to load scene data with the option to cache. This allows us to retain * expected behavior of loadSceneSettings and loadSceneHierarchy where they don't store loaded * data which may be undesired behavior with projects that have many scenes. * * @param {SceneRegistryItem | string} sceneItem - The scene item (which can be found with * {@link find}, URL of the scene file (e.g."scene_id.json") or name of the scene. * @param {boolean} storeInCache - Whether to store the loaded data in the scene item. * @param {LoadSceneDataCallback} callback - The function to call after loading, * passed (err, sceneItem) where err is null if no errors occurred. * @private */ _loadSceneData(sceneItem, storeInCache, callback) { const app = this._app; let url = sceneItem; if (typeof sceneItem === "string") { sceneItem = this.findByUrl(url) || this.find(url) || new SceneRegistryItem("Untitled", url); } url = sceneItem.url; if (!url) { callback("Cannot find scene to load"); return; } if (sceneItem.loaded) { callback(null, sceneItem); return; } if (app.assets && app.assets.prefix && !ABSOLUTE_URL.test(url)) { url = path.join(app.assets.prefix, url); } sceneItem._onLoadedCallbacks.push(callback); if (!sceneItem._loading) { const handler = app.loader.getHandler("hierarchy"); handler.load(url, (err, data) => { sceneItem.data = data; sceneItem._loading = false; for (let i = 0; i < sceneItem._onLoadedCallbacks.length; i++) { sceneItem._onLoadedCallbacks[i](err, sceneItem); } if (!storeInCache) { sceneItem.data = null; } sceneItem._onLoadedCallbacks.length = 0; }); } sceneItem._loading = true; } /** * Loads and stores the scene data to reduce the number of the network requests when the same * scenes are loaded multiple times. Can also be used to load data before calling * {@link loadSceneHierarchy} and {@link loadSceneSettings} to make scene loading quicker for * the user. * * @param {SceneRegistryItem | string} sceneItem - The scene item (which can be found with * {@link find}, URL of the scene file (e.g."scene_id.json") or name of the scene. * @param {LoadSceneDataCallback} callback - The function to call after loading, * passed (err, sceneItem) where err is null if no errors occurred. * @example * const sceneItem = app.scenes.find("Scene Name"); * app.scenes.loadSceneData(sceneItem, (err, sceneItem) => { * if (err) { * // error * } * }); */ loadSceneData(sceneItem, callback) { this._loadSceneData(sceneItem, true, callback); } /** * Unloads scene data that has been loaded previously using {@link loadSceneData}. * * @param {SceneRegistryItem | string} sceneItem - The scene item (which can be found with * {@link find} or URL of the scene file. Usually this will be "scene_id.json". * @example * const sceneItem = app.scenes.find("Scene Name"); * app.scenes.unloadSceneData(sceneItem); */ unloadSceneData(sceneItem) { if (typeof sceneItem === "string") { sceneItem = this.findByUrl(sceneItem); } if (sceneItem) { sceneItem.data = null; } } _loadSceneHierarchy(sceneItem, onBeforeAddHierarchy, callback) { this._loadSceneData(sceneItem, false, (err, sceneItem2) => { if (err) { if (callback) { callback(err); } return; } if (onBeforeAddHierarchy) { onBeforeAddHierarchy(sceneItem2); } const app = this._app; const _loaded = () => { const handler = app.loader.getHandler("hierarchy"); app.systems.script.preloading = true; const entity = handler.open(sceneItem2.url, sceneItem2.data); app.systems.script.preloading = false; app.loader.clearCache(sceneItem2.url, "hierarchy"); app.root.addChild(entity); app.systems.fire("initialize", entity); app.systems.fire("postInitialize", entity); app.systems.fire("postPostInitialize", entity); if (callback) callback(null, entity); }; app._preloadScripts(sceneItem2.data, _loaded); }); } /** * Load a scene file, create and initialize the Entity hierarchy and add the hierarchy to the * application root Entity. * * @param {SceneRegistryItem | string} sceneItem - The scene item (which can be found with * {@link find}, URL of the scene file (e.g."scene_id.json") or name of the scene. * @param {LoadHierarchyCallback} callback - The function to call after loading, * passed (err, entity) where err is null if no errors occurred. * @example * const sceneItem = app.scenes.find("Scene Name"); * app.scenes.loadSceneHierarchy(sceneItem, (err, entity) => { * if (!err) { * const e = app.root.find("My New Entity"); * } else { * // error * } * }); */ loadSceneHierarchy(sceneItem, callback) { this._loadSceneHierarchy(sceneItem, null, callback); } /** * Load a scene file and apply the scene settings to the current scene. * * @param {SceneRegistryItem | string} sceneItem - The scene item (which can be found with * {@link find}, URL of the scene file (e.g."scene_id.json") or name of the scene. * @param {LoadSettingsCallback} callback - The function called after the settings * are applied. Passed (err) where err is null if no error occurred. * @example * const sceneItem = app.scenes.find("Scene Name"); * app.scenes.loadSceneSettings(sceneItem, (err) => { * if (!err) { * // success * } else { * // error * } * }); */ loadSceneSettings(sceneItem, callback) { this._loadSceneData(sceneItem, false, (err, sceneItem2) => { if (!err) { this._app.applySceneSettings(sceneItem2.data.settings); if (callback) { callback(null); } } else { if (callback) { callback(err); } } }); } /** * Change to a new scene. Calling this function will load the scene data, delete all * entities and graph nodes under `app.root` and load the scene settings and hierarchy. * * @param {SceneRegistryItem | string} sceneItem - The scene item (which can be found with * {@link find}, URL of the scene file (e.g."scene_id.json") or name of the scene. * @param {ChangeSceneCallback} [callback] - The function to call after loading, * passed (err, entity) where err is null if no errors occurred. * @example * app.scenes.changeScene("Scene Name", (err, entity) => { * if (!err) { * // success * } else { * // error * } * }); */ changeScene(sceneItem, callback) { const app = this._app; const onBeforeAddHierarchy = (sceneItem2) => { const { children } = app.root; while (children.length) { children[0].destroy(); } app.applySceneSettings(sceneItem2.data.settings); }; this._loadSceneHierarchy(sceneItem, onBeforeAddHierarchy, callback); } /** * Load the scene hierarchy and scene settings. This is an internal method used by the * {@link AppBase}. * * @param {string} url - The URL of the scene file. * @param {LoadSceneCallback} callback - The function called after the settings are * applied. Passed (err, scene) where err is null if no error occurred and scene is the * {@link Scene}. */ loadScene(url, callback) { const app = this._app; const handler = app.loader.getHandler("scene"); if (app.assets && app.assets.prefix && !ABSOLUTE_URL.test(url)) { url = path.join(app.assets.prefix, url); } handler.load(url, (err, data) => { if (!err) { const _loaded = () => { app.systems.script.preloading = true; const scene = handler.open(url, data); const sceneItem = this.findByUrl(url); if (sceneItem && !sceneItem.loaded) { sceneItem.data = data; } app.systems.script.preloading = false; app.loader.clearCache(url, "scene"); app.loader.patch({ resource: scene, type: "scene" }, app.assets); app.root.addChild(scene.root); if (app.systems.rigidbody && typeof Ammo !== "undefined") { app.systems.rigidbody.gravity.set(scene._gravity.x, scene._gravity.y, scene._gravity.z); } if (callback) { callback(null, scene); } }; app._preloadScripts(data, _loaded); } else { if (callback) { callback(err); } } }); } } export { SceneRegistry };