Explorar o código

added model loader and changed the way models can be loaded
Also exported the ViewerModel class so it can be extended

Raanan Weber %!s(int64=7) %!d(string=hai) anos
pai
achega
38854bc7cb

+ 17 - 2
Viewer/dist/basicExample.html

@@ -17,7 +17,7 @@
     </head>
 
     <body>
-        <babylon configuration="config.json" model.title="Damaged Helmet" model.subtitle="BabylonJS" model.thumbnail="https://www.babylonjs.com/img/favicon/apple-icon-144x144.png"
+        <babylon id="babylon-viewer" configuration="config.json" model.title="Damaged Helmet" model.subtitle="BabylonJS" model.thumbnail="https://www.babylonjs.com/img/favicon/apple-icon-144x144.png"
             model.url="https://www.babylonjs.com/Assets/DamagedHelmet/glTF/DamagedHelmet.gltf" camera.behaviors.auto-rotate="0"
             templates.nav-bar.params.disable-on-fullscreen="true"></babylon>
         <script src="viewer.js"></script>
@@ -28,7 +28,22 @@
             // a simple way of disabling auto init 
             BabylonViewer.disableInit = true;
             // Initializing the viewer on specific HTML tags.
-            BabylonViewer.InitTags('babylon');
+            //BabylonViewer.InitTags('babylon');
+            var state = 0;
+            window.onclick = function () {
+                if (state === 0) {
+                    console.log("loading");
+                    BabylonViewer.InitTags('babylon');
+                    state++;
+                } else if (state === 1) {
+                    /*BabylonViewer.viewerManager.getViewerPromiseById('babylon-viewer').then(function (viewer) {
+                        viewer.dispose();
+                        console.log("disposed");
+                    });*/
+                    BabylonViewer.disposeAll();
+                }
+
+            }
         </script>
     </body>
 

+ 1 - 0
Viewer/src/configuration/configuration.ts

@@ -68,6 +68,7 @@ export interface ViewerConfiguration {
 
 export interface IModelConfiguration {
     url?: string;
+    root?: string; //optional
     loader?: string; // obj, gltf?
     position?: { x: number, y: number, z: number };
     rotation?: { x: number, y: number, z: number, w?: number };

+ 7 - 1
Viewer/src/index.ts

@@ -1,7 +1,13 @@
+/// <reference path="../../dist/preview release/babylon.d.ts"/>
+/// <reference path="../../dist/babylon.glTF2Interface.d.ts"/>
+/// <reference path="../../dist/preview release/loaders/babylon.glTFFileLoader.d.ts"/>
+
 import { mapperManager } from './configuration/mappers';
 import { viewerManager } from './viewer/viewerManager';
 import { DefaultViewer } from './viewer/defaultViewer';
 import { AbstractViewer } from './viewer/viewer';
+import { ModelLoader } from './model/modelLoader';
+import { ViewerModel } from './model/viewerModel';
 
 /**
  * BabylonJS Viewer
@@ -35,4 +41,4 @@ function disposeAll() {
 }
 
 // public API for initialization
-export { InitTags, DefaultViewer, AbstractViewer, viewerManager, mapperManager, disposeAll };
+export { InitTags, DefaultViewer, AbstractViewer, viewerManager, mapperManager, disposeAll, ModelLoader, ViewerModel };

+ 37 - 0
Viewer/src/model/modelLoader.ts

@@ -0,0 +1,37 @@
+import { AbstractViewer } from "..";
+import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, Tools, SceneLoader, Tags, GLTFFileLoader } from "babylonjs";
+import { IModelConfiguration } from "../configuration/configuration";
+import { ViewerModel, ModelState } from "./viewerModel";
+
+export class ModelLoader {
+
+    private _loadId: number;
+    private _disposed = false;
+
+    private _loaders: Array<ISceneLoaderPlugin | ISceneLoaderPluginAsync>;
+
+    constructor(private _viewer: AbstractViewer) {
+        this._loaders = [];
+        this._loadId = 0;
+    }
+
+    public load(modelConfiguration: IModelConfiguration): ViewerModel {
+
+        const model = new ViewerModel(this._viewer.scene, modelConfiguration);
+
+        model.loadId = this._loadId++;
+        this._loaders.push(model.loader);
+
+        return model;
+    }
+
+    public dispose() {
+        this._loaders.forEach(loader => {
+            if (loader.name === "gltf") {
+                (<GLTFFileLoader>loader).dispose();
+            }
+        });
+        this._loaders.length = 0;
+        this._disposed = true;
+    }
+}

+ 165 - 9
Viewer/src/model/viewerModel.ts

@@ -1,13 +1,24 @@
-import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, AnimationGroup, Animatable, AbstractMesh, Tools, Scene, SceneLoader, Observable, SceneLoaderProgressEvent, Tags, ParticleSystem, Skeleton, IDisposable, Nullable, Animation } from "babylonjs";
+import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, AnimationGroup, Animatable, AbstractMesh, Tools, Scene, SceneLoader, Observable, SceneLoaderProgressEvent, Tags, ParticleSystem, Skeleton, IDisposable, Nullable, Animation, GLTFFileLoader, Quaternion } from "babylonjs";
 import { IModelConfiguration } from "../configuration/configuration";
 import { IModelAnimation, GroupModelAnimation, AnimationPlayMode } from "./modelAnimation";
 
+import * as deepmerge from '../../assets/deepmerge.min.js';
+
+export enum ModelState {
+    INIT,
+    LOADING,
+    LOADED,
+    CANCELED,
+    ERROR
+}
+
 export class ViewerModel implements IDisposable {
 
     public loader: ISceneLoaderPlugin | ISceneLoaderPluginAsync;
 
     private _animations: Array<IModelAnimation>;
     public meshes: Array<AbstractMesh>;
+    public rootMesh: AbstractMesh;
     public particleSystems: Array<ParticleSystem>;
     public skeletons: Array<Skeleton>;
     public currentAnimation: IModelAnimation;
@@ -16,10 +27,21 @@ export class ViewerModel implements IDisposable {
     public onLoadProgressObservable: Observable<SceneLoaderProgressEvent>;
     public onLoadErrorObservable: Observable<{ message: string; exception: any }>;
 
-    constructor(private _scene: Scene, private _modelConfiguration?: IModelConfiguration, disableAutoLoad = false) {
+    public onAfterConfigure: Observable<ViewerModel>;
+
+    public state: ModelState;
+    public loadId: number;
+
+    private _loaderDisposed: boolean = false;
+    private _loadedUrl: string;
+
+    constructor(private _scene: Scene, private _modelConfiguration: IModelConfiguration, disableAutoLoad = false) {
         this.onLoadedObservable = new Observable();
         this.onLoadErrorObservable = new Observable();
         this.onLoadProgressObservable = new Observable();
+        this.onAfterConfigure = new Observable();
+
+        this.state = ModelState.INIT;
 
         this._animations = [];
 
@@ -36,6 +58,29 @@ export class ViewerModel implements IDisposable {
         }
     }
 
+    public cancelLoad() {
+        // ATM only available in the GLTF Loader
+        if (this.loader && this.loader.name === "gltf") {
+            let gltfLoader = (<GLTFFileLoader>this.loader);
+            gltfLoader.dispose();
+            this.state = ModelState.CANCELED;
+        }
+    }
+
+    public get configuration(): IModelConfiguration {
+        return this.configuration;
+    }
+
+    public set configuration(newConfiguration: IModelConfiguration) {
+        this._modelConfiguration = newConfiguration;
+        this._configureModel();
+    }
+
+    public updateConfiguration(newConfiguration: Partial<IModelConfiguration>) {
+        this._modelConfiguration = deepmerge(this._modelConfiguration, newConfiguration);
+        this._configureModel();
+    }
+
     public getAnimations() {
         return this._animations;
     }
@@ -69,15 +114,115 @@ export class ViewerModel implements IDisposable {
         }
     }
 
+    private _configureModel() {
+        let meshesWithNoParent: Array<AbstractMesh> = this.meshes.filter(m => !m.parent);
+        let updateMeshesWithNoParent = (variable: string, value: any, param?: string) => {
+            meshesWithNoParent.forEach(mesh => {
+                if (param) {
+                    mesh[variable][param] = value;
+                } else {
+                    mesh[variable] = value;
+                }
+            });
+        }
+        let updateXYZ = (variable: string, configValues: { x: number, y: number, z: number, w?: number }) => {
+            if (configValues.x !== undefined) {
+                updateMeshesWithNoParent(variable, configValues.x, 'x');
+            }
+            if (configValues.y !== undefined) {
+                updateMeshesWithNoParent(variable, configValues.y, 'y');
+            }
+            if (configValues.z !== undefined) {
+                updateMeshesWithNoParent(variable, configValues.z, 'z');
+            }
+            if (configValues.w !== undefined) {
+                updateMeshesWithNoParent(variable, configValues.w, 'w');
+            }
+        }
+        // position?
+        if (this._modelConfiguration.position) {
+            updateXYZ('position', this._modelConfiguration.position);
+        }
+        if (this._modelConfiguration.rotation) {
+            //quaternion?
+            if (this._modelConfiguration.rotation.w) {
+                meshesWithNoParent.forEach(mesh => {
+                    if (!mesh.rotationQuaternion) {
+                        mesh.rotationQuaternion = new Quaternion();
+                    }
+                })
+                updateXYZ('rotationQuaternion', this._modelConfiguration.rotation);
+            } else {
+                updateXYZ('rotation', this._modelConfiguration.rotation);
+            }
+        }
+        if (this._modelConfiguration.scaling) {
+            updateXYZ('scaling', this._modelConfiguration.scaling);
+        }
+
+        if (this._modelConfiguration.castShadow) {
+            this.meshes.forEach(mesh => {
+                Tags.AddTagsTo(mesh, 'castShadow');
+            });
+        }
+
+        if (this._modelConfiguration.normalize) {
+            let center = false;
+            let unitSize = false;
+            let parentIndex;
+            if (this._modelConfiguration.normalize === true) {
+                center = true;
+                unitSize = true;
+                parentIndex = 0;
+            } else {
+                center = !!this._modelConfiguration.normalize.center;
+                unitSize = !!this._modelConfiguration.normalize.unitSize;
+                parentIndex = this._modelConfiguration.normalize.parentIndex;
+            }
+
+            let meshesToNormalize: Array<AbstractMesh> = [];
+            if (parentIndex !== undefined) {
+                meshesToNormalize.push(this.meshes[parentIndex]);
+            } else {
+                meshesToNormalize = meshesWithNoParent;
+            }
+
+            if (unitSize) {
+                meshesToNormalize.forEach(mesh => {
+                    mesh.normalizeToUnitCube(true);
+                    mesh.computeWorldMatrix(true);
+                });
+            }
+            if (center) {
+                meshesToNormalize.forEach(mesh => {
+                    const boundingInfo = mesh.getHierarchyBoundingVectors(true);
+                    const sizeVec = boundingInfo.max.subtract(boundingInfo.min);
+                    const halfSizeVec = sizeVec.scale(0.5);
+                    const center = boundingInfo.min.add(halfSizeVec);
+                    mesh.position = center.scale(-1);
+
+                    // Set on ground.
+                    mesh.position.y += halfSizeVec.y;
+
+                    // Recompute Info.
+                    mesh.computeWorldMatrix(true);
+                });
+            }
+        }
+        this.onAfterConfigure.notifyObservers(this);
+    }
+
     private _initLoad() {
-        if (!this._modelConfiguration || !this._modelConfiguration.url) {
-            return Tools.Error("No model URL to load.");
+        if (!this._modelConfiguration.url) {
+            this.state = ModelState.ERROR;
+            Tools.Error("No URL provided");
+            return;
         }
-        let parts = this._modelConfiguration.url.split('/');
-        let filename = parts.pop() || this._modelConfiguration.url;
-        let base = parts.length ? parts.join('/') + '/' : './';
 
+        let filename = Tools.GetFilename(this._modelConfiguration.url) || this._modelConfiguration.url;
+        let base = this._modelConfiguration.root || Tools.GetFolderPath(this._modelConfiguration.url);
         let plugin = this._modelConfiguration.loader;
+        this._loadedUrl = this._modelConfiguration.url;
 
         //temp solution for animation group handling
         let animationsArray = this._scene.animationGroups.slice();
@@ -124,18 +269,29 @@ export class ViewerModel implements IDisposable {
                     this.playAnimation(animationName);
                 }
             }
-
             this.onLoadedObservable.notifyObserversWithPromise(this);
         }, (progressEvent) => {
             this.onLoadProgressObservable.notifyObserversWithPromise(progressEvent);
         }, (e, m, exception) => {
+            this.state = ModelState.ERROR;
+            Tools.Error("Load Error: There was an error loading the model. " + m);
             this.onLoadErrorObservable.notifyObserversWithPromise({ message: m, exception: exception });
         }, plugin)!;
 
-        this.loader['animationStartMode'] = 0;
+        if (this.loader.name === "gltf") {
+            let gltfLoader = (<GLTFFileLoader>this.loader);
+            gltfLoader.animationStartMode = 0;
+            gltfLoader.onDispose = () => {
+                this._loaderDisposed = true;
+            }
+        }
+
     }
 
     public dispose() {
+        if (this.loader && this.loader.name === "gltf") {
+            (<GLTFFileLoader>this.loader).dispose();
+        }
         this.particleSystems.forEach(ps => ps.dispose());
         this.skeletons.forEach(s => s.dispose());
         this._animations.forEach(ag => ag.dispose());

+ 9 - 119
Viewer/src/viewer/viewer.ts

@@ -8,6 +8,7 @@ import * as deepmerge from '../../assets/deepmerge.min.js';
 import { CameraBehavior } from 'src/interfaces';
 import { ViewerModel } from '../model/viewerModel';
 import { GroupModelAnimation } from '../model/modelAnimation';
+import { ModelLoader } from '../model/modelLoader';
 
 export abstract class AbstractViewer {
 
@@ -24,6 +25,7 @@ export abstract class AbstractViewer {
      * The last loader used to load a model. 
      */
     public lastUsedLoader: ISceneLoaderPlugin | ISceneLoaderPluginAsync;
+    public modelLoader: ModelLoader;
 
     protected configuration: ViewerConfiguration;
     public environmentHelper: EnvironmentHelper;
@@ -73,6 +75,7 @@ export abstract class AbstractViewer {
 
         this.registeredOnBeforerenderFunctions = [];
         this.models = [];
+        this.modelLoader = new ModelLoader(this);
 
         // add this viewer to the viewer manager
         viewerManager.addViewer(this);
@@ -527,100 +530,9 @@ export abstract class AbstractViewer {
     }
 
     protected configureModel(modelConfiguration: Partial<IModelConfiguration>, model?: ViewerModel) {
-        let focusMeshes = model ? model.meshes : this.scene.meshes;
-        let meshesWithNoParent: Array<AbstractMesh> = focusMeshes.filter(m => !m.parent);
-        let updateMeshesWithNoParent = (variable: string, value: any, param?: string) => {
-            meshesWithNoParent.forEach(mesh => {
-                if (param) {
-                    mesh[variable][param] = value;
-                } else {
-                    mesh[variable] = value;
-                }
-            });
-        }
-        let updateXYZ = (variable: string, configValues: { x: number, y: number, z: number, w?: number }) => {
-            if (configValues.x !== undefined) {
-                updateMeshesWithNoParent(variable, configValues.x, 'x');
-            }
-            if (configValues.y !== undefined) {
-                updateMeshesWithNoParent(variable, configValues.y, 'y');
-            }
-            if (configValues.z !== undefined) {
-                updateMeshesWithNoParent(variable, configValues.z, 'z');
-            }
-            if (configValues.w !== undefined) {
-                updateMeshesWithNoParent(variable, configValues.w, 'w');
-            }
-        }
-        // position?
-        if (modelConfiguration.position) {
-            updateXYZ('position', modelConfiguration.position);
-        }
-        if (modelConfiguration.rotation) {
-            if (modelConfiguration.rotation.w) {
-                meshesWithNoParent.forEach(mesh => {
-                    if (!mesh.rotationQuaternion) {
-                        mesh.rotationQuaternion = new Quaternion();
-                    }
-                })
-                updateXYZ('rotationQuaternion', modelConfiguration.rotation);
-            } else {
-                updateXYZ('rotation', modelConfiguration.rotation);
-            }
-        }
-        if (modelConfiguration.scaling) {
-            updateXYZ('scaling', modelConfiguration.scaling);
-        }
-
-        if (modelConfiguration.castShadow) {
-            focusMeshes.forEach(mesh => {
-                Tags.AddTagsTo(mesh, 'castShadow');
-            });
-        }
-
-        if (modelConfiguration.normalize) {
-            let center = false;
-            let unitSize = false;
-            let parentIndex;
-            if (modelConfiguration.normalize === true) {
-                center = true;
-                unitSize = true;
-                parentIndex = 0;
-            } else {
-                center = !!modelConfiguration.normalize.center;
-                unitSize = !!modelConfiguration.normalize.unitSize;
-                parentIndex = modelConfiguration.normalize.parentIndex;
-            }
-
-            let meshesToNormalize: Array<AbstractMesh> = [];
-            if (parentIndex !== undefined) {
-                meshesToNormalize.push(focusMeshes[parentIndex]);
-            } else {
-                meshesToNormalize = meshesWithNoParent;
-            }
-
-            if (unitSize) {
-                meshesToNormalize.forEach(mesh => {
-                    mesh.normalizeToUnitCube(true);
-                    mesh.computeWorldMatrix(true);
-                });
-            }
-            if (center) {
-                meshesToNormalize.forEach(mesh => {
-                    const boundingInfo = mesh.getHierarchyBoundingVectors(true);
-                    const sizeVec = boundingInfo.max.subtract(boundingInfo.min);
-                    const halfSizeVec = sizeVec.scale(0.5);
-                    const center = boundingInfo.min.add(halfSizeVec);
-                    mesh.position = center.scale(-1);
-
-                    // Set on ground.
-                    mesh.position.y += halfSizeVec.y;
-
-                    // Recompute Info.
-                    mesh.computeWorldMatrix(true);
-                });
-            }
-        }
+        this.models.forEach(model => {
+            model.configuration = modelConfiguration;
+        })
     }
 
     public dispose() {
@@ -791,12 +703,8 @@ export abstract class AbstractViewer {
             return Promise.reject("no model configuration found");
         }
         if (this.isLoading) {
-            //another model is being model. Wait for it to finish, trigger the load afterwards
-            /*this.nextLoading = () => {
-                delete this.nextLoading;
-                return this.loadModel(modelConfig, clearScene);
-            }*/
-            return Promise.reject("sanother model is curently being loaded.");
+            // We can decide here whether or not to cancel the lst load, but the developer can do that.
+            return Promise.reject("another model is curently being loaded.");
         }
         this.isLoading = true;
         if ((typeof modelConfig === 'string')) {
@@ -822,7 +730,7 @@ export abstract class AbstractViewer {
         }).then(() => {
             return new Promise<ViewerModel>((resolve, reject) => {
                 // at this point, configuration.model is an object, not a string
-                let model = new ViewerModel(this.scene, <IModelConfiguration>this.configuration.model);
+                let model = this.modelLoader.load(<IModelConfiguration>this.configuration.model);
                 this.models.push(model);
                 this.lastUsedLoader = model.loader;
                 model.onLoadedObservable.add((model) => {
@@ -841,8 +749,6 @@ export abstract class AbstractViewer {
         }).then((model: ViewerModel) => {
             return this.onModelLoadedObservable.notifyObserversWithPromise(model)
                 .then(() => {
-                    // update the models' configuration
-                    this.configureModel(this.configuration.model || modelConfig, model);
                     this.configureLights(this.configuration.lights);
 
                     if (this.configuration.camera) {
@@ -851,27 +757,11 @@ export abstract class AbstractViewer {
                     return this.initEnvironment(model);
                 }).then(() => {
                     this.isLoading = false;
-                    /*if (this.nextLoading) {
-                        return this.nextLoading();
-                    }*/
                     return model;
                 });
         });
     }
 
-    public addModel(meshes: Array<AbstractMesh>, skeletons: Array<Skeleton>, particleSystems: Array<ParticleSystem>, animationGroups: Array<AnimationGroup>): ViewerModel {
-        let model = new ViewerModel(this.scene);
-        model.meshes = meshes;
-        model.skeletons = skeletons;
-        model.particleSystems = particleSystems;
-        let animations = model.getAnimations();
-        animationGroups.forEach(ag => {
-            animations.push(new GroupModelAnimation(ag));
-        });
-
-        return model;
-    }
-
     protected initEnvironment(model?: ViewerModel): Promise<Scene> {
         this.configureEnvironment(this.configuration.skybox, this.configuration.ground);
 

+ 21 - 1
dist/preview release/viewer/babylon.viewer.d.ts

@@ -205,6 +205,7 @@ declare module BabylonViewer {
 
     export interface IModelConfiguration {
         url?: string;
+        root?: string;
         loader?: string; // obj, gltf?
         position?: { x: number, y: number, z: number };
         rotation?: { x: number, y: number, z: number, w?: number };
@@ -430,9 +431,24 @@ declare module BabylonViewer {
         goToFrame(frameNumber: number): any;
     }
 
-    export interface ViewerModel extends BABYLON.IDisposable {
+    export enum ModelState {
+        INIT,
+        LOADING,
+        LOADED,
+        ERROR
+    }
+
+    export class ModelLoader {
+        constructor(viewer: AbstractViewer);
+        load(modelConfiguration: IModelConfiguration): ViewerModel;
+        dispose(): void;
+    }
+
+    export class ViewerModel {
+        constructor(scene: Scene, modelConfiguration: IModelConfiguration, disableAutoLoad: boolean);
         loader: BABYLON.ISceneLoaderPlugin | BABYLON.ISceneLoaderPluginAsync;
         meshes: Array<BABYLON.AbstractMesh>;
+        ootMesh: BABYLON.AbstractMesh;
         particleSystems: Array<BABYLON.ParticleSystem>;
         skeletons: Array<BABYLON.Skeleton>;
         currentAnimation: IModelAnimation;
@@ -442,6 +458,9 @@ declare module BabylonViewer {
             message: string;
             exception: any;
         }>;
+        onAfterConfigure: BABYLON.Observable<ViewerModel>;
+        state: ModelState;
+        loadId: number;
         load(): void;
         getAnimations(): Array<IModelAnimation>;
         getAnimationNames(): string[];
@@ -459,6 +478,7 @@ declare module BabylonViewer {
         sceneOptimizer: BABYLON.SceneOptimizer;
         baseId: string;
         models: Array<ViewerModel>;
+        modelLoader: ModelLoader;
         lastUsedLoader: BABYLON.ISceneLoaderPlugin | BABYLON.ISceneLoaderPluginAsync;
         protected configuration: ViewerConfiguration;
         environmentHelper: BABYLON.EnvironmentHelper;