playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
348 lines (347 loc) • 12 kB
JavaScript
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
};