Browse Source

viewerModel class

Raanan Weber 7 years ago
parent
commit
776d1be845
3 changed files with 113 additions and 144 deletions
  1. 70 0
      Viewer/src/model/viewerModel.ts
  2. 7 110
      Viewer/src/viewer/defaultViewer.ts
  3. 36 34
      Viewer/src/viewer/viewer.ts

+ 70 - 0
Viewer/src/model/viewerModel.ts

@@ -0,0 +1,70 @@
+import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, AnimationGroup, Animatable, AbstractMesh, Tools, Scene, SceneLoader, Observable, SceneLoaderProgressEvent, Tags, ParticleSystem, Skeleton } from "babylonjs";
+import { IModelConfiguration } from "../configuration/configuration";
+
+export class ViewerModel {
+
+    public loader: ISceneLoaderPlugin | ISceneLoaderPluginAsync;
+
+    public animationGroups: Array<AnimationGroup>;
+    public animatables: Array<Animatable>;
+    public meshes: Array<AbstractMesh>;
+    public particleSystems: Array<ParticleSystem>;
+    public skeletons: Array<Skeleton>;
+
+    public onLoadedObservable: Observable<ViewerModel>;
+    public onLoadProgressObservable: Observable<SceneLoaderProgressEvent>;
+    public onLoadErrorObservable: Observable<{ message: string; exception: any }>;
+
+    constructor(private _modelConfiguration: IModelConfiguration, private _scene: Scene, disableAutoLoad = false) {
+        this.onLoadedObservable = new Observable();
+        this.onLoadErrorObservable = new Observable();
+        this.onLoadProgressObservable = new Observable();
+
+        if (!disableAutoLoad) {
+            this._initLoad();
+        }
+    }
+
+    public load() {
+        if (this.loader) {
+            Tools.Error("Model was already loaded or in the process of loading.");
+        } else {
+            this._initLoad();
+        }
+    }
+
+    private _initLoad() {
+        if (!this._modelConfiguration || !this._modelConfiguration.url) {
+            return Tools.Error("No model URL to load.");
+        }
+        let parts = this._modelConfiguration.url.split('/');
+        let filename = parts.pop() || this._modelConfiguration.url;
+        let base = parts.length ? parts.join('/') + '/' : './';
+
+        let plugin = this._modelConfiguration.loader;
+
+        this.loader = SceneLoader.ImportMesh(undefined, base, filename, this._scene, (meshes, particleSystems, skeletons) => {
+            meshes.forEach(mesh => {
+                Tags.AddTagsTo(mesh, "viewerMesh");
+            });
+            this.meshes = meshes;
+            this.particleSystems = particleSystems;
+            this.skeletons = skeletons;
+            this.onLoadedObservable.notifyObserversWithPromise(this);
+        }, (progressEvent) => {
+            this.onLoadProgressObservable.notifyObserversWithPromise(progressEvent);
+        }, (e, m, exception) => {
+            this.onLoadErrorObservable.notifyObserversWithPromise({ message: m, exception: exception });
+        }, plugin)!;
+    }
+
+    public dispose() {
+        this.animatables.forEach(a => {
+            a.stop();
+        });
+        this.particleSystems.forEach(ps => ps.dispose());
+        this.skeletons.forEach(s => s.dispose());
+        this.animationGroups.forEach(ag => ag.dispose());
+        this.meshes.forEach(m => m.dispose());
+    }
+}

+ 7 - 110
Viewer/src/viewer/defaultViewer.ts

@@ -5,6 +5,7 @@ import { Template, EventCallback } from './../templateManager';
 import { AbstractViewer } from './viewer';
 import { SpotLight, MirrorTexture, Plane, ShadowGenerator, Texture, BackgroundMaterial, Observable, ShadowLight, CubeTexture, BouncingBehavior, FramingBehavior, Behavior, Light, Engine, Scene, AutoRotationBehavior, AbstractMesh, Quaternion, StandardMaterial, ArcRotateCamera, ImageProcessingConfiguration, Color3, Vector3, SceneLoader, Mesh, HemisphericLight } from 'babylonjs';
 import { CameraBehavior } from '../interfaces';
+import { ViewerModel } from '../model/viewerModel';
 
 export class DefaultViewer extends AbstractViewer {
 
@@ -103,8 +104,8 @@ export class DefaultViewer extends AbstractViewer {
         this.containerElement.style.display = 'flex';
     }
 
-    protected configureModel(modelConfiguration: Partial<IModelConfiguration>, focusMeshes: Array<AbstractMesh> = this.scene.meshes) {
-        super.configureModel(modelConfiguration, focusMeshes);
+    protected configureModel(modelConfiguration: Partial<IModelConfiguration>, model: ViewerModel) {
+        super.configureModel(modelConfiguration, model);
 
         let navbar = this.templateManager.getTemplate('navBar');
         if (!navbar) return;
@@ -141,7 +142,7 @@ export class DefaultViewer extends AbstractViewer {
         });
     }
 
-    private onModelLoaded = (meshes: Array<AbstractMesh>) => {
+    private onModelLoaded = (model: ViewerModel) => {
         // with a short timeout, making sure everything is there already.
         let hideLoadingDelay = 500;
         if (this.configuration.lab && this.configuration.lab.hideLoadingDelay !== undefined) {
@@ -151,113 +152,9 @@ export class DefaultViewer extends AbstractViewer {
             this.hideLoadingScreen();
         }, hideLoadingDelay);
 
-        meshes[0].rotation.y += Math.PI;
-
-        return; //this.initEnvironment(meshes);
+        return;
     }
 
-    /*protected initEnvironment(focusMeshes: Array<AbstractMesh> = []): Promise<Scene> {
-        if (this.configuration.skybox) {
-            // Define a general environment textue
-            let texture;
-            // this is obligatory, but still - making sure it is there.
-            if (this.configuration.skybox.cubeTexture) {
-                if (typeof this.configuration.skybox.cubeTexture.url === 'string') {
-                    texture = CubeTexture.CreateFromPrefilteredData(this.configuration.skybox.cubeTexture.url, this.scene);
-                } else {
-                    texture = CubeTexture.CreateFromImages(this.configuration.skybox.cubeTexture.url, this.scene, this.configuration.skybox.cubeTexture.noMipMap);
-                }
-            }
-            if (texture) {
-                this.extendClassWithConfig(texture, this.configuration.skybox.cubeTexture);
-
-                let scale = this.configuration.skybox.scale || this.scene.activeCamera && (this.scene.activeCamera.maxZ - this.scene.activeCamera.minZ) / 2 || 1;
-
-                let box = this.scene.createDefaultSkybox(texture, this.configuration.skybox.pbr, scale, this.configuration.skybox.blur);
-
-                // before extending, set the material's imageprocessing configuration object, if needed:
-                if (this.configuration.skybox.material && this.configuration.skybox.material.imageProcessingConfiguration && box) {
-                    (<StandardMaterial>box.material).imageProcessingConfiguration = new ImageProcessingConfiguration();
-                }
-
-                this.extendClassWithConfig(box, this.configuration.skybox);
-
-                box && focusMeshes.push(box);
-            }
-        }
-
-        if (this.configuration.ground) {
-            let groundConfig = (typeof this.configuration.ground === 'boolean') ? {} : this.configuration.ground;
-
-            let groundSize = groundConfig.size || (this.configuration.skybox && this.configuration.skybox.scale) || 3000;
-
-            let ground = Mesh.CreatePlane("BackgroundPlane", groundSize, this.scene);
-            let backgroundMaterial = new BackgroundMaterial('groundmat', this.scene);
-            ground.rotation.x = Math.PI / 2; // Face up by default.
-            ground.receiveShadows = groundConfig.receiveShadows || false;
-
-            // position the ground correctly
-            let groundPosition = focusMeshes[0].getHierarchyBoundingVectors().min.y;
-            ground.position.y = groundPosition;
-
-            // default values
-            backgroundMaterial.alpha = 0.9;
-            backgroundMaterial.alphaMode = Engine.ALPHA_PREMULTIPLIED_PORTERDUFF;
-            backgroundMaterial.shadowLevel = 0.5;
-            backgroundMaterial.primaryLevel = 1;
-            backgroundMaterial.primaryColor = new Color3(0.2, 0.2, 0.3).toLinearSpace().scale(3);
-            backgroundMaterial.secondaryLevel = 0;
-            backgroundMaterial.tertiaryLevel = 0;
-            backgroundMaterial.useRGBColor = false;
-            backgroundMaterial.enableNoise = true;
-
-            // if config provided, extend the default values
-            if (groundConfig.material) {
-                this.extendClassWithConfig(ground, ground.material);
-            }
-
-            ground.material = backgroundMaterial;
-            if (this.configuration.ground === true || groundConfig.shadowOnly) {
-                // shadow only:
-                ground.receiveShadows = true;
-                const diffuseTexture = new Texture("https://assets.babylonjs.com/environments/backgroundGround.png", this.scene);
-                diffuseTexture.gammaSpace = false;
-                diffuseTexture.hasAlpha = true;
-                backgroundMaterial.diffuseTexture = diffuseTexture;
-            } else if (groundConfig.mirror) {
-                var mirror = new MirrorTexture("mirror", 512, this.scene);
-                mirror.mirrorPlane = new Plane(0, -1, 0, 0);
-                mirror.renderList = mirror.renderList || [];
-                focusMeshes.length && focusMeshes.forEach(m => {
-                    m && mirror.renderList && mirror.renderList.push(m);
-                });
-
-                backgroundMaterial.reflectionTexture = mirror;
-            } else {
-                if (groundConfig.material) {
-                    if (groundConfig.material.diffuseTexture) {
-                        const diffuseTexture = new Texture(groundConfig.material.diffuseTexture, this.scene);
-                        backgroundMaterial.diffuseTexture = diffuseTexture;
-                    }
-                }
-                // ground.material = new StandardMaterial('groundmat', this.scene);
-            }
-            //default configuration
-            if (this.configuration.ground === true) {
-                ground.receiveShadows = true;
-                if (ground.material)
-                    ground.material.alpha = 0.4;
-            }
-
-
-
-
-            this.extendClassWithConfig(ground, groundConfig);
-        }
-
-        return Promise.resolve(this.scene);
-    }*/
-
     public showOverlayScreen(subScreen: string) {
         let template = this.templateManager.getTemplate('overlay');
         if (!template) return Promise.resolve('Overlay template not found');
@@ -345,8 +242,8 @@ export class DefaultViewer extends AbstractViewer {
         }));
     }
 
-    protected configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, focusMeshes: Array<AbstractMesh> = this.scene.meshes) {
-        super.configureLights(lightsConfiguration, focusMeshes);
+    protected configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, model: ViewerModel) {
+        super.configureLights(lightsConfiguration, model);
         // labs feature - flashlight
         if (this.configuration.lab && this.configuration.lab.flashlight) {
             let pointerPosition = BABYLON.Vector3.Zero();

+ 36 - 34
Viewer/src/viewer/viewer.ts

@@ -6,6 +6,7 @@ import { ViewerConfiguration, ISceneConfiguration, ISceneOptimizerConfiguration,
 
 import * as deepmerge from '../../assets/deepmerge.min.js';
 import { CameraBehavior } from 'src/interfaces';
+import { ViewerModel } from '../model/viewerModel';
 
 export abstract class AbstractViewer {
 
@@ -16,6 +17,7 @@ export abstract class AbstractViewer {
     public camera: ArcRotateCamera;
     public sceneOptimizer: SceneOptimizer;
     public baseId: string;
+    public models: Array<ViewerModel>;
 
     /**
      * The last loader used to load a model. 
@@ -42,7 +44,7 @@ export abstract class AbstractViewer {
     // observables
     public onSceneInitObservable: Observable<Scene>;
     public onEngineInitObservable: Observable<Engine>;
-    public onModelLoadedObservable: Observable<AbstractMesh[]>;
+    public onModelLoadedObservable: Observable<ViewerModel>;
     public onModelLoadProgressObservable: Observable<SceneLoaderProgressEvent>;
     public onModelLoadErrorObservable: Observable<{ message: string; exception: any }>;
     public onLoaderInitObservable: Observable<ISceneLoaderPlugin | ISceneLoaderPluginAsync>;
@@ -397,7 +399,9 @@ export abstract class AbstractViewer {
         }
     }
 
-    protected configureCamera(cameraConfig: ICameraConfiguration, focusMeshes: Array<AbstractMesh> = this.scene.meshes) {
+    protected configureCamera(cameraConfig: ICameraConfiguration, model?: ViewerModel) {
+        let focusMeshes = model ? model.meshes : this.scene.meshes;
+
         if (!this.scene.activeCamera) {
             this.scene.createDefaultCamera(true, true, true);
             this.camera = <ArcRotateCamera>this.scene.activeCamera!;
@@ -430,7 +434,8 @@ export abstract class AbstractViewer {
             this.camera.upperRadiusLimit = sceneDiagonalLenght * 3;
     }
 
-    protected configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, focusMeshes: Array<AbstractMesh> = this.scene.meshes) {
+    protected configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, model?: ViewerModel) {
+        let focusMeshes = model ? model.meshes : this.scene.meshes;
         // sanity check!
         if (!Object.keys(lightsConfiguration).length) return;
 
@@ -526,7 +531,8 @@ export abstract class AbstractViewer {
         });*/
     }
 
-    protected configureModel(modelConfiguration: Partial<IModelConfiguration>, focusMeshes: Array<AbstractMesh> = this.scene.meshes) {
+    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 => {
@@ -747,9 +753,9 @@ export abstract class AbstractViewer {
     private isLoading: boolean;
     private nextLoading: Function;
 
-    public loadModel(model: any = this.configuration.model, clearScene: boolean = true): Promise<Scene> {
+    public loadModel(modelConfig: any = this.configuration.model, clearScene: boolean = true): Promise<Scene> {
         // no model was provided? Do nothing!
-        let modelUrl = (typeof model === 'string') ? model : model.url;
+        let modelUrl = (typeof modelConfig === 'string') ? modelConfig : modelConfig.url;
         if (!modelUrl) {
             return Promise.resolve(this.scene);
         }
@@ -757,28 +763,23 @@ export abstract class AbstractViewer {
             //another model is being model. Wait for it to finish, trigger the load afterwards
             this.nextLoading = () => {
                 delete this.nextLoading;
-                this.loadModel(model, clearScene);
+                this.loadModel(modelConfig, clearScene);
             }
             return Promise.resolve(this.scene);
         }
         this.isLoading = true;
-        if ((typeof model === 'string')) {
+        if ((typeof modelConfig === 'string')) {
             if (this.configuration.model && typeof this.configuration.model === 'object') {
-                this.configuration.model.url = model;
+                this.configuration.model.url = modelConfig;
             }
         } else {
             if (this.configuration.model) {
-                deepmerge(this.configuration.model, model)
+                deepmerge(this.configuration.model, modelConfig)
             } else {
-                this.configuration.model = model;
+                this.configuration.model = modelConfig;
             }
         }
 
-        let parts = modelUrl.split('/');
-        let filename = parts.pop();
-        let base = parts.join('/') + '/';
-        let plugin = (typeof model === 'string') ? undefined : model.loader;
-
         return Promise.resolve(this.scene).then((scene) => {
             if (!scene) return this.initScene();
 
@@ -791,33 +792,34 @@ export abstract class AbstractViewer {
             }
             return scene!;
         }).then(() => {
-            return new Promise<Array<AbstractMesh>>((resolve, reject) => {
-                this.lastUsedLoader = SceneLoader.ImportMesh(undefined, base, filename, this.scene, (meshes) => {
-                    meshes.forEach(mesh => {
-                        Tags.AddTagsTo(mesh, "viewerMesh");
-                    });
-                    resolve(meshes);
-                }, (progressEvent) => {
-                    this.onModelLoadProgressObservable.notifyObserversWithPromise(progressEvent);
-                }, (e, m, exception) => {
-                    // console.log(m, exception);
-                    this.onModelLoadErrorObservable.notifyObserversWithPromise({ message: m, exception: exception }).then(() => {
-                        reject(exception);
+            return new Promise<ViewerModel>((resolve, reject) => {
+                // at this point, configuration.model is an object, not a string
+                let model = new ViewerModel(<IModelConfiguration>this.configuration.model, this.scene);
+                this.lastUsedLoader = model.loader;
+                model.onLoadedObservable.add((model) => {
+                    resolve(model);
+                });
+                model.onLoadErrorObservable.add((errorObject) => {
+                    this.onModelLoadErrorObservable.notifyObserversWithPromise(errorObject).then(() => {
+                        reject(errorObject.exception);
                     });
-                }, plugin)!;
+                });
+                model.onLoadProgressObservable.add((progressEvent) => {
+                    return this.onModelLoadProgressObservable.notifyObserversWithPromise(progressEvent);
+                });
                 this.onLoaderInitObservable.notifyObserversWithPromise(this.lastUsedLoader);
             });
-        }).then((meshes: Array<AbstractMesh>) => {
-            return this.onModelLoadedObservable.notifyObserversWithPromise(meshes)
+        }).then((model: ViewerModel) => {
+            return this.onModelLoadedObservable.notifyObserversWithPromise(model)
                 .then(() => {
                     // update the models' configuration
-                    this.configureModel(this.configuration.model || model, meshes);
+                    this.configureModel(this.configuration.model || modelConfig, model);
                     this.configureLights(this.configuration.lights);
 
                     if (this.configuration.camera) {
-                        this.configureCamera(this.configuration.camera, meshes);
+                        this.configureCamera(this.configuration.camera, model);
                     }
-                    return this.initEnvironment(meshes);
+                    return this.initEnvironment(model.meshes);
                 }).then(() => {
                     this.isLoading = false;
                     if (this.nextLoading) {