123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956 |
- import { Nullable } from "babylonjs/types";
- import { Observable, Observer } from "babylonjs/Misc/observable";
- import { Tools } from "babylonjs/Misc/tools";
- import { Camera } from "babylonjs/Cameras/camera";
- import { AnimationGroup } from "babylonjs/Animations/animationGroup";
- import { Skeleton } from "babylonjs/Bones/skeleton";
- import { IParticleSystem } from "babylonjs/Particles/IParticleSystem";
- import { BaseTexture } from "babylonjs/Materials/Textures/baseTexture";
- import { Material } from "babylonjs/Materials/material";
- import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
- import { SceneLoader, ISceneLoaderPluginFactory, ISceneLoaderPlugin, ISceneLoaderPluginAsync, SceneLoaderProgressEvent, ISceneLoaderPluginExtensions } from "babylonjs/Loading/sceneLoader";
- import { AssetContainer } from "babylonjs/assetContainer";
- import { Scene, IDisposable } from "babylonjs/scene";
- /**
- * Interface for glTF validation results
- */
- interface IGLTFValidationResults {
- info: {
- generator: string;
- hasAnimations: boolean;
- hasDefaultScene: boolean;
- hasMaterials: boolean;
- hasMorphTargets: boolean;
- hasSkins: boolean;
- hasTextures: boolean;
- maxAttributesUsed: number;
- primitivesCount: number
- };
- issues: {
- messages: Array<string>;
- numErrors: number;
- numHints: number;
- numInfos: number;
- numWarnings: number;
- truncated: boolean
- };
- mimeType: string;
- uri: string;
- validatedAt: string;
- validatorVersion: string;
- }
- /**
- * Interface for glTF validation options
- */
- interface IGLTFValidationOptions {
- uri?: string;
- externalResourceFunction?: (uri: string) => Promise<Uint8Array>;
- validateAccessorData?: boolean;
- maxIssues?: number;
- ignoredIssues?: Array<string>;
- severityOverrides?: Object;
- }
- /**
- * glTF validator object
- */
- declare var GLTFValidator: {
- validateBytes: (data: Uint8Array, options?: IGLTFValidationOptions) => Promise<IGLTFValidationResults>;
- validateString: (json: string, options?: IGLTFValidationOptions) => Promise<IGLTFValidationResults>;
- };
- /**
- * Mode that determines the coordinate system to use.
- */
- export enum GLTFLoaderCoordinateSystemMode {
- /**
- * Automatically convert the glTF right-handed data to the appropriate system based on the current coordinate system mode of the scene.
- */
- AUTO,
- /**
- * Sets the useRightHandedSystem flag on the scene.
- */
- FORCE_RIGHT_HANDED,
- }
- /**
- * Mode that determines what animations will start.
- */
- export enum GLTFLoaderAnimationStartMode {
- /**
- * No animation will start.
- */
- NONE,
- /**
- * The first animation will start.
- */
- FIRST,
- /**
- * All animations will start.
- */
- ALL,
- }
- /**
- * Interface that contains the data for the glTF asset.
- */
- export interface IGLTFLoaderData {
- /**
- * Object that represents the glTF JSON.
- */
- json: Object;
- /**
- * The BIN chunk of a binary glTF.
- */
- bin: Nullable<ArrayBufferView>;
- }
- /**
- * Interface for extending the loader.
- */
- export interface IGLTFLoaderExtension {
- /**
- * The name of this extension.
- */
- readonly name: string;
- /**
- * Defines whether this extension is enabled.
- */
- enabled: boolean;
- }
- /**
- * Loader state.
- */
- export enum GLTFLoaderState {
- /**
- * The asset is loading.
- */
- LOADING,
- /**
- * The asset is ready for rendering.
- */
- READY,
- /**
- * The asset is completely loaded.
- */
- COMPLETE
- }
- /** @hidden */
- export interface IGLTFLoader extends IDisposable {
- readonly state: Nullable<GLTFLoaderState>;
- importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string) => Promise<{ meshes: AbstractMesh[], particleSystems: IParticleSystem[], skeletons: Skeleton[], animationGroups: AnimationGroup[] }>;
- loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string) => Promise<void>;
- }
- /**
- * File loader for loading glTF files into a scene.
- */
- export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISceneLoaderPluginFactory {
- /** @hidden */
- public static _CreateGLTF1Loader: (parent: GLTFFileLoader) => IGLTFLoader;
- /** @hidden */
- 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.
- * @hidden
- */
- 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.
- * @hidden
- */
- public static HomogeneousCoordinates = false;
- // ----------
- // V2 options
- // ----------
- /**
- * The coordinate system mode. Defaults to AUTO.
- */
- public coordinateSystemMode = GLTFLoaderCoordinateSystemMode.AUTO;
- /**
- * The animation start mode. Defaults to FIRST.
- */
- public animationStartMode = GLTFLoaderAnimationStartMode.FIRST;
- /**
- * Defines if the loader should compile materials before raising the success callback. Defaults to false.
- */
- public compileMaterials = false;
- /**
- * Defines if the loader should also compile materials with clip planes. Defaults to false.
- */
- public useClipPlane = false;
- /**
- * Defines if the loader should compile shadow generators before raising the success callback. Defaults to false.
- */
- public compileShadowGenerators = false;
- /**
- * Defines if the Alpha blended materials are only applied as coverage.
- * If false, (default) The luminance of each pixel will reduce its opacity to simulate the behaviour of most physical materials.
- * If true, no extra effects are applied to transparent pixels.
- */
- public transparencyAsCoverage = false;
- /**
- * Function called before loading a url referenced by the asset.
- */
- public preprocessUrlAsync = (url: string) => Promise.resolve(url);
- /**
- * Observable raised when the loader creates a mesh after parsing the glTF properties of the mesh.
- */
- public readonly onMeshLoadedObservable = new Observable<AbstractMesh>();
- private _onMeshLoadedObserver: Nullable<Observer<AbstractMesh>>;
- /**
- * Callback raised when the loader creates a mesh after parsing the glTF properties of the mesh.
- */
- public set onMeshLoaded(callback: (mesh: AbstractMesh) => void) {
- if (this._onMeshLoadedObserver) {
- this.onMeshLoadedObservable.remove(this._onMeshLoadedObserver);
- }
- this._onMeshLoadedObserver = this.onMeshLoadedObservable.add(callback);
- }
- /**
- * Observable raised when the loader creates a texture after parsing the glTF properties of the texture.
- */
- public readonly onTextureLoadedObservable = new Observable<BaseTexture>();
- private _onTextureLoadedObserver: Nullable<Observer<BaseTexture>>;
- /**
- * Callback raised when the loader creates a texture after parsing the glTF properties of the texture.
- */
- public set onTextureLoaded(callback: (texture: BaseTexture) => void) {
- if (this._onTextureLoadedObserver) {
- this.onTextureLoadedObservable.remove(this._onTextureLoadedObserver);
- }
- this._onTextureLoadedObserver = this.onTextureLoadedObservable.add(callback);
- }
- /**
- * Observable raised when the loader creates a material after parsing the glTF properties of the material.
- */
- public readonly onMaterialLoadedObservable = new Observable<Material>();
- private _onMaterialLoadedObserver: Nullable<Observer<Material>>;
- /**
- * Callback raised when the loader creates a material after parsing the glTF properties of the material.
- */
- public set onMaterialLoaded(callback: (material: Material) => void) {
- if (this._onMaterialLoadedObserver) {
- this.onMaterialLoadedObservable.remove(this._onMaterialLoadedObserver);
- }
- this._onMaterialLoadedObserver = this.onMaterialLoadedObservable.add(callback);
- }
- /**
- * Observable raised when the loader creates a camera after parsing the glTF properties of the camera.
- */
- public readonly onCameraLoadedObservable = new Observable<Camera>();
- private _onCameraLoadedObserver: Nullable<Observer<Camera>>;
- /**
- * Callback raised when the loader creates a camera after parsing the glTF properties of the camera.
- */
- public set onCameraLoaded(callback: (camera: Camera) => void) {
- if (this._onCameraLoadedObserver) {
- this.onCameraLoadedObservable.remove(this._onCameraLoadedObserver);
- }
- this._onCameraLoadedObserver = this.onCameraLoadedObservable.add(callback);
- }
- /**
- * Observable raised when the asset is completely loaded, immediately before the loader is disposed.
- * For assets with LODs, raised when all of the LODs are complete.
- * For assets without LODs, raised when the model is complete, immediately after the loader resolves the returned promise.
- */
- public readonly onCompleteObservable = new Observable<void>();
- private _onCompleteObserver: Nullable<Observer<void>>;
- /**
- * Callback raised when the asset is completely loaded, immediately before the loader is disposed.
- * For assets with LODs, raised when all of the LODs are complete.
- * For assets without LODs, raised when the model is complete, immediately after the loader resolves the returned promise.
- */
- public set onComplete(callback: () => void) {
- if (this._onCompleteObserver) {
- this.onCompleteObservable.remove(this._onCompleteObserver);
- }
- this._onCompleteObserver = this.onCompleteObservable.add(callback);
- }
- /**
- * Observable raised when an error occurs.
- */
- public readonly onErrorObservable = new Observable<any>();
- private _onErrorObserver: Nullable<Observer<any>>;
- /**
- * Callback raised when an error occurs.
- */
- public set onError(callback: (reason: any) => void) {
- if (this._onErrorObserver) {
- this.onErrorObservable.remove(this._onErrorObserver);
- }
- this._onErrorObserver = this.onErrorObservable.add(callback);
- }
- /**
- * Observable raised after the loader is disposed.
- */
- public readonly onDisposeObservable = new Observable<void>();
- private _onDisposeObserver: Nullable<Observer<void>>;
- /**
- * Callback raised after the loader is disposed.
- */
- public set onDispose(callback: () => void) {
- if (this._onDisposeObserver) {
- this.onDisposeObservable.remove(this._onDisposeObserver);
- }
- this._onDisposeObserver = this.onDisposeObservable.add(callback);
- }
- /**
- * Observable raised after a loader extension is created.
- * Set additional options for a loader extension in this event.
- */
- public readonly onExtensionLoadedObservable = new Observable<IGLTFLoaderExtension>();
- private _onExtensionLoadedObserver: Nullable<Observer<IGLTFLoaderExtension>>;
- /**
- * Callback raised after a loader extension is created.
- */
- public set onExtensionLoaded(callback: (extension: IGLTFLoaderExtension) => void) {
- if (this._onExtensionLoadedObserver) {
- this.onExtensionLoadedObservable.remove(this._onExtensionLoadedObserver);
- }
- this._onExtensionLoadedObserver = this.onExtensionLoadedObservable.add(callback);
- }
- /**
- * Defines if the loader logging is enabled.
- */
- public get loggingEnabled(): boolean {
- return this._loggingEnabled;
- }
- public set loggingEnabled(value: boolean) {
- if (this._loggingEnabled === value) {
- return;
- }
- this._loggingEnabled = value;
- if (this._loggingEnabled) {
- this._log = this._logEnabled;
- }
- else {
- this._log = this._logDisabled;
- }
- }
- /**
- * Defines if the loader should capture performance counters.
- */
- public get capturePerformanceCounters(): boolean {
- return this._capturePerformanceCounters;
- }
- public set capturePerformanceCounters(value: boolean) {
- if (this._capturePerformanceCounters === value) {
- return;
- }
- this._capturePerformanceCounters = value;
- if (this._capturePerformanceCounters) {
- this._startPerformanceCounter = this._startPerformanceCounterEnabled;
- this._endPerformanceCounter = this._endPerformanceCounterEnabled;
- }
- else {
- this._startPerformanceCounter = this._startPerformanceCounterDisabled;
- this._endPerformanceCounter = this._endPerformanceCounterDisabled;
- }
- }
- /**
- * Defines if the loader should validate the asset.
- */
- public validate = false;
- /**
- * Observable raised after validation when validate is set to true. The event data is the result of the validation.
- */
- public readonly onValidatedObservable = new Observable<IGLTFValidationResults>();
- private _onValidatedObserver: Nullable<Observer<IGLTFValidationResults>>;
- /**
- * Callback raised after a loader extension is created.
- */
- public set onValidated(callback: (results: IGLTFValidationResults) => void) {
- if (this._onValidatedObserver) {
- this.onValidatedObservable.remove(this._onValidatedObserver);
- }
- this._onValidatedObserver = this.onValidatedObservable.add(callback);
- }
- private _loader: Nullable<IGLTFLoader> = null;
- /**
- * Name of the loader ("gltf")
- */
- public name = "gltf";
- /**
- * Supported file extensions of the loader (.gltf, .glb)
- */
- public extensions: ISceneLoaderPluginExtensions = {
- ".gltf": { isBinary: false },
- ".glb": { isBinary: true }
- };
- /**
- * Disposes the loader, releases resources during load, and cancels any outstanding requests.
- */
- public dispose(): void {
- if (this._loader) {
- this._loader.dispose();
- this._loader = null;
- }
- this._clear();
- this.onDisposeObservable.notifyObservers(undefined);
- this.onDisposeObservable.clear();
- }
- /** @hidden */
- public _clear(): void {
- this.preprocessUrlAsync = (url) => Promise.resolve(url);
- this.onMeshLoadedObservable.clear();
- this.onTextureLoadedObservable.clear();
- this.onMaterialLoadedObservable.clear();
- this.onCameraLoadedObservable.clear();
- this.onCompleteObservable.clear();
- this.onExtensionLoadedObservable.clear();
- }
- /**
- * Imports one or more meshes from the loaded glTF data and adds them to the scene
- * @param meshesNames a string or array of strings of the mesh names that should be loaded from the file
- * @param scene the scene the meshes should be added to
- * @param data the glTF data to load
- * @param rootUrl root url to load from
- * @param onProgress event that fires when loading progress has occured
- * @param fileName Defines the name of the file to load
- * @returns a promise containg the loaded meshes, particles, skeletons and animations
- */
- public importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string): Promise<{ meshes: AbstractMesh[], particleSystems: IParticleSystem[], skeletons: Skeleton[], animationGroups: AnimationGroup[] }> {
- return this._parseAsync(scene, data, rootUrl, fileName).then((loaderData) => {
- this._log(`Loading ${fileName || ""}`);
- this._loader = this._getLoader(loaderData);
- return this._loader.importMeshAsync(meshesNames, scene, loaderData, rootUrl, onProgress, fileName);
- });
- }
- /**
- * Imports all objects from the loaded glTF data and adds them to the scene
- * @param scene the scene the objects should be added to
- * @param data the glTF data to load
- * @param rootUrl root url to load from
- * @param onProgress event that fires when loading progress has occured
- * @param fileName Defines the name of the file to load
- * @returns a promise which completes when objects have been loaded to the scene
- */
- public loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string): Promise<void> {
- return this._parseAsync(scene, data, rootUrl, fileName).then((loaderData) => {
- this._log(`Loading ${fileName || ""}`);
- this._loader = this._getLoader(loaderData);
- return this._loader.loadAsync(scene, loaderData, rootUrl, onProgress, fileName);
- });
- }
- /**
- * Load into an asset container.
- * @param scene The scene to load into
- * @param data The data to import
- * @param rootUrl The root url for scene and resources
- * @param onProgress The callback when the load progresses
- * @param fileName Defines the name of the file to load
- * @returns The loaded asset container
- */
- public loadAssetContainerAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string): Promise<AssetContainer> {
- return this._parseAsync(scene, data, rootUrl, fileName).then((loaderData) => {
- this._log(`Loading ${fileName || ""}`);
- this._loader = this._getLoader(loaderData);
- // Get materials/textures when loading to add to container
- let materials: Array<Material> = [];
- this.onMaterialLoadedObservable.add((material) => {
- materials.push(material);
- });
- let textures: Array<BaseTexture> = [];
- this.onTextureLoadedObservable.add((texture) => {
- textures.push(texture);
- });
- return this._loader.importMeshAsync(null, scene, loaderData, rootUrl, onProgress, fileName).then((result) => {
- const container = new AssetContainer(scene);
- Array.prototype.push.apply(container.meshes, result.meshes);
- Array.prototype.push.apply(container.particleSystems, result.particleSystems);
- Array.prototype.push.apply(container.skeletons, result.skeletons);
- Array.prototype.push.apply(container.animationGroups, result.animationGroups);
- Array.prototype.push.apply(container.materials, materials);
- Array.prototype.push.apply(container.textures, textures);
- container.removeAllFromScene();
- return container;
- });
- });
- }
- /**
- * If the data string can be loaded directly.
- * @param data string contianing the file data
- * @returns if the data can be loaded directly
- */
- public canDirectLoad(data: string): boolean {
- return ((data.indexOf("scene") !== -1) && (data.indexOf("node") !== -1));
- }
- /**
- * Rewrites a url by combining a root url and response url.
- */
- public rewriteRootURL: (rootUrl: string, responseURL?: string) => string;
- /**
- * Instantiates a glTF file loader plugin.
- * @returns the created plugin
- */
- public createPlugin(): ISceneLoaderPlugin | ISceneLoaderPluginAsync {
- return new GLTFFileLoader();
- }
- /**
- * The loader state or null if the loader is not active.
- */
- public get loaderState(): Nullable<GLTFLoaderState> {
- return this._loader ? this._loader.state : null;
- }
- /**
- * Returns a promise that resolves when the asset is completely loaded.
- * @returns a promise that resolves when the asset is completely loaded.
- */
- public whenCompleteAsync(): Promise<void> {
- return new Promise((resolve, reject) => {
- this.onCompleteObservable.addOnce(() => {
- resolve();
- });
- this.onErrorObservable.addOnce((reason) => {
- reject(reason);
- });
- });
- }
- private _parseAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, fileName?: string): Promise<IGLTFLoaderData> {
- return Promise.resolve().then(() => {
- return this._validateAsync(scene, data, rootUrl, fileName).then(() => {
- const unpacked = (data instanceof ArrayBuffer) ? this._unpackBinary(data) : { json: data, bin: null };
- this._startPerformanceCounter("Parse JSON");
- this._log(`JSON length: ${unpacked.json.length}`);
- const loaderData: IGLTFLoaderData = {
- json: JSON.parse(unpacked.json),
- bin: unpacked.bin
- };
- this._endPerformanceCounter("Parse JSON");
- this.onParsedObservable.notifyObservers(loaderData);
- this.onParsedObservable.clear();
- return loaderData;
- });
- });
- }
- private _validateAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, fileName?: string): Promise<void> {
- if (!this.validate || typeof GLTFValidator === "undefined") {
- return Promise.resolve();
- }
- this._startPerformanceCounter("Validate JSON");
- const options: IGLTFValidationOptions = {
- externalResourceFunction: (uri) => {
- return this.preprocessUrlAsync(rootUrl + uri)
- .then((url) => scene._loadFileAsync(url, true, true))
- .then((data) => new Uint8Array(data as ArrayBuffer));
- }
- };
- if (fileName && fileName.substr(0, 5) !== "data:") {
- options.uri = (rootUrl === "file:" ? fileName : `${rootUrl}${fileName}`);
- }
- const promise = (data instanceof ArrayBuffer)
- ? GLTFValidator.validateBytes(new Uint8Array(data), options)
- : GLTFValidator.validateString(data, options);
- return promise.then((result) => {
- this._endPerformanceCounter("Validate JSON");
- this.onValidatedObservable.notifyObservers(result);
- this.onValidatedObservable.clear();
- }, (reason) => {
- this._endPerformanceCounter("Validate JSON");
- Tools.Warn(`Failed to validate: ${reason}`);
- this.onValidatedObservable.clear();
- });
- }
- private _getLoader(loaderData: IGLTFLoaderData): IGLTFLoader {
- const asset = (<any>loaderData.json).asset || {};
- this._log(`Asset version: ${asset.version}`);
- asset.minVersion && this._log(`Asset minimum version: ${asset.minVersion}`);
- asset.generator && this._log(`Asset generator: ${asset.generator}`);
- const version = GLTFFileLoader._parseVersion(asset.version);
- if (!version) {
- throw new Error("Invalid version: " + asset.version);
- }
- if (asset.minVersion !== undefined) {
- const minVersion = GLTFFileLoader._parseVersion(asset.minVersion);
- if (!minVersion) {
- throw new Error("Invalid minimum version: " + asset.minVersion);
- }
- if (GLTFFileLoader._compareVersion(minVersion, { major: 2, minor: 0 }) > 0) {
- throw new Error("Incompatible minimum version: " + asset.minVersion);
- }
- }
- const createLoaders: { [key: number]: (parent: GLTFFileLoader) => IGLTFLoader } = {
- 1: GLTFFileLoader._CreateGLTF1Loader,
- 2: GLTFFileLoader._CreateGLTF2Loader
- };
- const createLoader = createLoaders[version.major];
- if (!createLoader) {
- throw new Error("Unsupported version: " + asset.version);
- }
- return createLoader(this);
- }
- private _unpackBinary(data: ArrayBuffer): { json: string, bin: Nullable<ArrayBufferView> } {
- this._startPerformanceCounter("Unpack binary");
- this._log(`Binary length: ${data.byteLength}`);
- const Binary = {
- Magic: 0x46546C67
- };
- const binaryReader = new BinaryReader(data);
- const magic = binaryReader.readUint32();
- if (magic !== Binary.Magic) {
- throw new Error("Unexpected magic: " + magic);
- }
- const version = binaryReader.readUint32();
- if (this.loggingEnabled) {
- this._log(`Binary version: ${version}`);
- }
- let unpacked: { json: string, bin: Nullable<ArrayBufferView> };
- switch (version) {
- case 1: {
- unpacked = this._unpackBinaryV1(binaryReader);
- break;
- }
- case 2: {
- unpacked = this._unpackBinaryV2(binaryReader);
- break;
- }
- default: {
- throw new Error("Unsupported version: " + version);
- }
- }
- this._endPerformanceCounter("Unpack binary");
- return unpacked;
- }
- private _unpackBinaryV1(binaryReader: BinaryReader): { json: string, bin: Nullable<ArrayBufferView> } {
- const ContentFormat = {
- JSON: 0
- };
- const length = binaryReader.readUint32();
- if (length != binaryReader.getLength()) {
- throw new Error("Length in header does not match actual data length: " + length + " != " + binaryReader.getLength());
- }
- const contentLength = binaryReader.readUint32();
- const contentFormat = binaryReader.readUint32();
- let content: string;
- switch (contentFormat) {
- case ContentFormat.JSON: {
- content = GLTFFileLoader._decodeBufferToText(binaryReader.readUint8Array(contentLength));
- break;
- }
- default: {
- throw new Error("Unexpected content format: " + contentFormat);
- }
- }
- const bytesRemaining = binaryReader.getLength() - binaryReader.getPosition();
- const body = binaryReader.readUint8Array(bytesRemaining);
- return {
- json: content,
- bin: body
- };
- }
- private _unpackBinaryV2(binaryReader: BinaryReader): { json: string, bin: Nullable<ArrayBufferView> } {
- const ChunkFormat = {
- JSON: 0x4E4F534A,
- BIN: 0x004E4942
- };
- const length = binaryReader.readUint32();
- if (length !== binaryReader.getLength()) {
- throw new Error("Length in header does not match actual data length: " + length + " != " + binaryReader.getLength());
- }
- // JSON chunk
- const chunkLength = binaryReader.readUint32();
- const chunkFormat = binaryReader.readUint32();
- if (chunkFormat !== ChunkFormat.JSON) {
- throw new Error("First chunk format is not JSON");
- }
- const json = GLTFFileLoader._decodeBufferToText(binaryReader.readUint8Array(chunkLength));
- // Look for BIN chunk
- let bin: Nullable<Uint8Array> = null;
- while (binaryReader.getPosition() < binaryReader.getLength()) {
- const chunkLength = binaryReader.readUint32();
- const chunkFormat = binaryReader.readUint32();
- switch (chunkFormat) {
- case ChunkFormat.JSON: {
- throw new Error("Unexpected JSON chunk");
- }
- case ChunkFormat.BIN: {
- bin = binaryReader.readUint8Array(chunkLength);
- break;
- }
- default: {
- // ignore unrecognized chunkFormat
- binaryReader.skipBytes(chunkLength);
- break;
- }
- }
- }
- return {
- json: json,
- bin: bin
- };
- }
- private static _parseVersion(version: string): Nullable<{ major: number, minor: number }> {
- if (version === "1.0" || version === "1.0.1") {
- return {
- major: 1,
- minor: 0
- };
- }
- const match = (version + "").match(/^(\d+)\.(\d+)/);
- if (!match) {
- return null;
- }
- return {
- major: parseInt(match[1]),
- minor: parseInt(match[2])
- };
- }
- private static _compareVersion(a: { major: number, minor: number }, b: { major: number, minor: number }): number {
- if (a.major > b.major) { return 1; }
- if (a.major < b.major) { return -1; }
- if (a.minor > b.minor) { return 1; }
- if (a.minor < b.minor) { return -1; }
- return 0;
- }
- private static _decodeBufferToText(buffer: Uint8Array): string {
- if (typeof TextDecoder !== "undefined") {
- return new TextDecoder().decode(buffer);
- }
- let result = "";
- const length = buffer.byteLength;
- for (let i = 0; i < length; i++) {
- result += String.fromCharCode(buffer[i]);
- }
- return result;
- }
- private static readonly _logSpaces = " ";
- private _logIndentLevel = 0;
- private _loggingEnabled = false;
- /** @hidden */
- public _log = this._logDisabled;
- /** @hidden */
- public _logOpen(message: string): void {
- this._log(message);
- this._logIndentLevel++;
- }
- /** @hidden */
- public _logClose(): void {
- --this._logIndentLevel;
- }
- private _logEnabled(message: string): void {
- const spaces = GLTFFileLoader._logSpaces.substr(0, this._logIndentLevel * 2);
- Tools.Log(`${spaces}${message}`);
- }
- private _logDisabled(message: string): void {
- }
- private _capturePerformanceCounters = false;
- /** @hidden */
- public _startPerformanceCounter = this._startPerformanceCounterDisabled;
- /** @hidden */
- public _endPerformanceCounter = this._endPerformanceCounterDisabled;
- private _startPerformanceCounterEnabled(counterName: string): void {
- Tools.StartPerformanceCounter(counterName);
- }
- private _startPerformanceCounterDisabled(counterName: string): void {
- }
- private _endPerformanceCounterEnabled(counterName: string): void {
- Tools.EndPerformanceCounter(counterName);
- }
- private _endPerformanceCounterDisabled(counterName: string): void {
- }
- }
- class BinaryReader {
- private _arrayBuffer: ArrayBuffer;
- private _dataView: DataView;
- private _byteOffset: number;
- constructor(arrayBuffer: ArrayBuffer) {
- this._arrayBuffer = arrayBuffer;
- this._dataView = new DataView(arrayBuffer);
- this._byteOffset = 0;
- }
- public getPosition(): number {
- return this._byteOffset;
- }
- public getLength(): number {
- return this._arrayBuffer.byteLength;
- }
- public readUint32(): number {
- const value = this._dataView.getUint32(this._byteOffset, true);
- this._byteOffset += 4;
- return value;
- }
- public readUint8Array(length: number): Uint8Array {
- const value = new Uint8Array(this._arrayBuffer, this._byteOffset, length);
- this._byteOffset += length;
- return value;
- }
- public skipBytes(length: number): void {
- this._byteOffset += length;
- }
- }
- if (SceneLoader) {
- SceneLoader.RegisterPlugin(new GLTFFileLoader());
- }
|