Skip to content

Commit

Permalink
Merge pull request BabylonJS#15344 from ryantrem/scene-loader-options
Browse files Browse the repository at this point in the history
SceneLoader Options
  • Loading branch information
ryantrem authored Aug 7, 2024
2 parents 8749f61 + 576d126 commit aa72b27
Show file tree
Hide file tree
Showing 8 changed files with 738 additions and 142 deletions.
635 changes: 556 additions & 79 deletions packages/dev/core/src/Loading/sceneLoader.ts

Large diffs are not rendered by default.

17 changes: 15 additions & 2 deletions packages/dev/loaders/src/OBJ/objFileLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ import { SolidParser } from "./solidParser";
import type { Mesh } from "core/Meshes/mesh";
import { StandardMaterial } from "core/Materials/standardMaterial";

// eslint-disable-next-line @typescript-eslint/naming-convention
const PLUGIN_OBJ = "obj";

declare module "core/Loading/sceneLoader" {
// eslint-disable-next-line jsdoc/require-jsdoc
export interface SceneLoaderPluginOptions {
/**
* Defines options for the obj loader.
*/
[PLUGIN_OBJ]?: {};
}
}

/**
* OBJ file type loader.
* This is a babylon scene loader plugin.
Expand Down Expand Up @@ -74,11 +87,11 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync, ISceneLoaderPlugi
/**
* Defines the name of the plugin.
*/
public name = "obj";
public readonly name = PLUGIN_OBJ;
/**
* Defines the extension the plugin is able to load.
*/
public extensions = ".obj";
public readonly extensions = ".obj";

private _assetContainer: Nullable<AssetContainer> = null;

Expand Down
19 changes: 16 additions & 3 deletions packages/dev/loaders/src/SPLAT/splatFileLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@ import { GaussianSplattingMesh } from "core/Meshes/GaussianSplatting/gaussianSpl
import type { AssetContainer } from "core/assetContainer";
import type { Scene } from "core/scene";

// eslint-disable-next-line @typescript-eslint/naming-convention
const PLUGIN_SPLAT = "splat";

declare module "core/Loading/sceneLoader" {
// eslint-disable-next-line jsdoc/require-jsdoc
export interface SceneLoaderPluginOptions {
/**
* Defines options for the splat loader.
*/
[PLUGIN_SPLAT]?: {};
}
}

/**
* @experimental
* SPLAT file type loader.
Expand All @@ -20,18 +33,18 @@ export class SPLATFileLoader implements ISceneLoaderPluginAsync, ISceneLoaderPlu
/**
* Defines the name of the plugin.
*/
public name = "splat";
public readonly name = PLUGIN_SPLAT;

/**
* Defines the extensions the splat loader is able to load.
* force data to come in as an ArrayBuffer
*/
public extensions: ISceneLoaderPluginExtensions = {
public readonly extensions = {
// eslint-disable-next-line @typescript-eslint/naming-convention
".splat": { isBinary: true },
// eslint-disable-next-line @typescript-eslint/naming-convention
".ply": { isBinary: true },
};
} as const satisfies ISceneLoaderPluginExtensions;

//private _loadingOptions: SPLATLoadingOptions;
/**
Expand Down
18 changes: 15 additions & 3 deletions packages/dev/loaders/src/STL/stlFileLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ import { SceneLoader } from "core/Loading/sceneLoader";
import { AssetContainer } from "core/assetContainer";
import type { Scene } from "core/scene";

const PLUGIN_STL = "stl";

declare module "core/Loading/sceneLoader" {
// eslint-disable-next-line jsdoc/require-jsdoc
export interface SceneLoaderPluginOptions {
/**
* Defines options for the stl loader.
*/
[PLUGIN_STL]?: {};
}
}

/**
* STL file type loader.
* This is a babylon scene loader plugin.
Expand All @@ -27,16 +39,16 @@ export class STLFileLoader implements ISceneLoaderPlugin {
/**
* Defines the name of the plugin.
*/
public name = "stl";
public readonly name = PLUGIN_STL;

/**
* Defines the extensions the stl loader is able to load.
* force data to come in as an ArrayBuffer
* we'll convert to string if it looks like it's an ASCII .stl
*/
public extensions: ISceneLoaderPluginExtensions = {
public readonly extensions = {
".stl": { isBinary: true },
};
} as const satisfies ISceneLoaderPluginExtensions;

/**
* Defines if Y and Z axes are swapped or not when loading an STL file.
Expand Down
18 changes: 18 additions & 0 deletions packages/dev/loaders/src/glTF/2.0/Extensions/MSFT_lod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@ import type { IProperty, IMSFTLOD } from "babylonjs-gltf2interface";

const NAME = "MSFT_lod";

declare module "../../glTFFileLoader" {
// eslint-disable-next-line jsdoc/require-jsdoc
export interface GLTFLoaderExtensionOptions {
/**
* Defines options for the MSFT_lod extension.
*/
[NAME]?: Partial<{
/**
* Maximum number of LODs to load, starting from the lowest LOD.
*/
maxLODsToLoad: number;
}>;
}
}

interface IBufferInfo {
start: number;
end: number;
Expand Down Expand Up @@ -76,6 +91,9 @@ export class MSFT_lod implements IGLTFLoaderExtension {
*/
constructor(loader: GLTFLoader) {
this._loader = loader;
// Options takes precedence. The maxLODsToLoad extension property is retained for back compat.
// For new extensions, they should only use options.
this.maxLODsToLoad = this._loader.parent.extensionOptions[NAME]?.maxLODsToLoad ?? this.maxLODsToLoad;
this.enabled = this._loader.isExtensionUsed(NAME);
}

Expand Down
23 changes: 17 additions & 6 deletions packages/dev/loaders/src/glTF/2.0/glTFLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,13 +565,21 @@ export class GLTFLoader implements IGLTFLoader {

private _loadExtensions(): void {
for (const name in GLTFLoader._RegisteredExtensions) {
const extension = GLTFLoader._RegisteredExtensions[name].factory(this);
if (extension.name !== name) {
Logger.Warn(`The name of the glTF loader extension instance does not match the registered name: ${extension.name} !== ${name}`);
}
// Don't load explicitly disabled extensions.
if (this.parent.extensionOptions[name]?.enabled === false) {
// But warn if the disabled extension is used by the model.
if (this.isExtensionUsed(name)) {
Logger.Warn(`Extension ${name} is used but has been explicitly disabled.`);
}
} else {
const extension = GLTFLoader._RegisteredExtensions[name].factory(this);
if (extension.name !== name) {
Logger.Warn(`The name of the glTF loader extension instance does not match the registered name: ${extension.name} !== ${name}`);
}

this._extensions.push(extension);
this._parent.onExtensionLoadedObservable.notifyObservers(extension);
this._extensions.push(extension);
this._parent.onExtensionLoadedObservable.notifyObservers(extension);
}
}

this._extensions.sort((a, b) => (a.order || Number.MAX_VALUE) - (b.order || Number.MAX_VALUE));
Expand All @@ -583,6 +591,9 @@ export class GLTFLoader implements IGLTFLoader {
for (const name of this._gltf.extensionsRequired) {
const available = this._extensions.some((extension) => extension.name === name && extension.enabled);
if (!available) {
if (this.parent.extensionOptions[name]?.enabled === false) {
throw new Error(`Required extension ${name} is disabled`);
}
throw new Error(`Required extension ${name} is not available`);
}
}
Expand Down
148 changes: 100 additions & 48 deletions packages/dev/loaders/src/glTF/glTFFileLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
ISceneLoaderAsyncResult,
} from "core/Loading/sceneLoader";
import { SceneLoader } from "core/Loading/sceneLoader";
import type { SceneLoaderPluginOptions } from "core/Loading/sceneLoader";
import { AssetContainer } from "core/assetContainer";
import type { Scene, IDisposable } from "core/scene";
import type { WebRequest } from "core/Misc/webRequest";
Expand All @@ -31,6 +32,23 @@ import { RuntimeError, ErrorCodes } from "core/Misc/error";
import type { TransformNode } from "core/Meshes/transformNode";
import type { MorphTargetManager } from "core/Morph/morphTargetManager";

const PLUGIN_GLTF = "gltf";

/**
* Defines options for glTF loader extensions. This interface is extended by specific extensions.
*/
export interface GLTFLoaderExtensionOptions extends Record<string, Record<string, unknown> | undefined> {}

declare module "core/Loading/sceneLoader" {
// eslint-disable-next-line jsdoc/require-jsdoc
export interface SceneLoaderPluginOptions {
/**
* Defines options for the glTF loader.
*/
[PLUGIN_GLTF]?: Partial<GLTFLoaderOptions>;
}
}

interface IFileRequestInfo extends IFileRequest {
_lengthComputable?: boolean;
_loaded?: number;
Expand Down Expand Up @@ -167,55 +185,26 @@ export interface IGLTFLoader extends IDisposable {
}

/**
* File loader for loading glTF files into a scene.
* Adds default/implicit options to extension specific options.
*/
export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISceneLoaderPluginFactory {
/** @internal */
public static _CreateGLTF1Loader: (parent: GLTFFileLoader) => IGLTFLoader;

/** @internal */
public static _CreateGLTF2Loader: (parent: GLTFFileLoader) => IGLTFLoader;

// --------------
// Common options
// --------------

type DefaultExtensionOptions<BaseExtensionOptions> = {
/**
* Raised when the asset has been parsed
* Defines if the extension is enabled
*/
public onParsedObservable = new Observable<IGLTFLoaderData>();
enabled?: boolean;
} & BaseExtensionOptions;

private _onParsedObserver: Nullable<Observer<IGLTFLoaderData>>;

/**
* Raised when the asset has been parsed
*/
public set onParsed(callback: (loaderData: IGLTFLoaderData) => void) {
if (this._onParsedObserver) {
this.onParsedObservable.remove(this._onParsedObserver);
class GLTFLoaderOptions {
// eslint-disable-next-line babylonjs/available
public constructor(options?: Partial<Readonly<GLTFLoaderOptions>>) {
if (options) {
for (const key in this) {
const typedKey = key as keyof GLTFLoaderOptions;
(this as Record<keyof GLTFLoaderOptions, unknown>)[typedKey] = options[typedKey] ?? this[typedKey];
}
}
this._onParsedObserver = this.onParsedObservable.add(callback);
}

// ----------
// V1 options
// ----------

/**
* Set this property to false to disable incremental loading which delays the loader from calling the success callback until after loading the meshes and shaders.
* Textures always loads asynchronously. For example, the success callback can compute the bounding information of the loaded meshes when incremental loading is disabled.
* Defaults to true.
* @internal
*/
public static IncrementalLoading = true;

/**
* Set this property to true in order to work with homogeneous coordinates, available with some converters and exporters.
* Defaults to false. See https://en.wikipedia.org/wiki/Homogeneous_coordinates.
* @internal
*/
public static HomogeneousCoordinates = false;

// ----------
// V2 options
// ----------
Expand Down Expand Up @@ -329,6 +318,69 @@ export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISc
*/
public customRootNode?: Nullable<TransformNode>;

/**
* Defines options for glTF extensions.
*/
public extensionOptions: {
// NOTE: This type is doing two things:
// 1. Adding an implicit 'enabled' property to the options for each extension.
// 2. Creating a mapped type of all the options of all the extensions to make it just look like a consolidated plain object in intellisense for the user.
[Extension in keyof GLTFLoaderExtensionOptions]: {
[Option in keyof DefaultExtensionOptions<GLTFLoaderExtensionOptions[Extension]>]: DefaultExtensionOptions<GLTFLoaderExtensionOptions[Extension]>[Option];
};
} = {};
}

/**
* File loader for loading glTF files into a scene.
*/
export class GLTFFileLoader extends GLTFLoaderOptions implements IDisposable, ISceneLoaderPluginAsync, ISceneLoaderPluginFactory {
/** @internal */
public static _CreateGLTF1Loader: (parent: GLTFFileLoader) => IGLTFLoader;

/** @internal */
public static _CreateGLTF2Loader: (parent: GLTFFileLoader) => IGLTFLoader;

// --------------
// Common options
// --------------

/**
* Raised when the asset has been parsed
*/
public onParsedObservable = new Observable<IGLTFLoaderData>();

private _onParsedObserver: Nullable<Observer<IGLTFLoaderData>>;

/**
* Raised when the asset has been parsed
*/
public set onParsed(callback: (loaderData: IGLTFLoaderData) => void) {
if (this._onParsedObserver) {
this.onParsedObservable.remove(this._onParsedObserver);
}
this._onParsedObserver = this.onParsedObservable.add(callback);
}

// ----------
// V1 options
// ----------

/**
* Set this property to false to disable incremental loading which delays the loader from calling the success callback until after loading the meshes and shaders.
* Textures always loads asynchronously. For example, the success callback can compute the bounding information of the loaded meshes when incremental loading is disabled.
* Defaults to true.
* @internal
*/
public static IncrementalLoading = true;

/**
* Set this property to true in order to work with homogeneous coordinates, available with some converters and exporters.
* Defaults to false. See https://en.wikipedia.org/wiki/Homogeneous_coordinates.
* @internal
*/
public static HomogeneousCoordinates = false;

/**
* Observable raised when the loader creates a mesh after parsing the glTF properties of the mesh.
* Note that the observable is raised as soon as the mesh object is created, meaning some data may not have been setup yet for this mesh (vertex data, morph targets, material, ...)
Expand Down Expand Up @@ -551,18 +603,18 @@ export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISc
private _progressCallback?: (event: ISceneLoaderProgressEvent) => void;
private _requests = new Array<IFileRequestInfo>();

private static _MagicBase64Encoded = "Z2xURg"; // "glTF" base64 encoded (without the quotes!)
private static readonly _MagicBase64Encoded = "Z2xURg"; // "glTF" base64 encoded (without the quotes!)

/**
* Name of the loader ("gltf")
*/
public name = "gltf";
public readonly name = PLUGIN_GLTF;

/** @internal */
public extensions: ISceneLoaderPluginExtensions = {
public readonly extensions = {
".gltf": { isBinary: false },
".glb": { isBinary: true },
};
} as const satisfies ISceneLoaderPluginExtensions;

/**
* Disposes the loader, releases resources during load, and cancels any outstanding requests.
Expand Down Expand Up @@ -863,8 +915,8 @@ export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISc
public rewriteRootURL?(rootUrl: string, responseURL?: string): string;

/** @internal */
public createPlugin(): ISceneLoaderPluginAsync {
return new GLTFFileLoader();
public createPlugin(options: SceneLoaderPluginOptions): ISceneLoaderPluginAsync {
return new GLTFFileLoader(options[PLUGIN_GLTF]);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/tools/viewer-alpha/src/viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export class Viewer implements IDisposable {
await this._loadModelLock.lockAsync(async () => {
this._throwIfDisposedOrAborted(abortSignal, abortController.signal);
this._details.model?.dispose();
this._details.model = await SceneLoader.LoadAssetContainerAsync("", finalSource, this._details.scene);
this._details.model = await SceneLoader.LoadAssetContainerAsync(finalSource, this._details.scene);
this._details.model.addAllToScene();
this._reframeCamera();
});
Expand Down

0 comments on commit aa72b27

Please sign in to comment.