浏览代码

DOcumentation and a small change to model loading

Raanan Weber 7 年之前
父节点
当前提交
33ae3883dd

+ 42 - 3
Viewer/src/configuration/mappers.ts

@@ -70,15 +70,27 @@ class HTMLMapper implements IMapper {
     }
 }
 
+/**
+ * A simple string-to-JSON mapper.
+ * This is the main mapper, used to analyze downloaded JSON-Configuration or JSON payload
+ */
 class JSONMapper implements IMapper {
-    map(rawSource: any) {
+    map(rawSource: string) {
         return JSON.parse(rawSource);
     }
 }
 
-// TODO - Dom configuration mapper.
+/**
+ * The DOM Mapper will traverse an entire DOM Tree and will load the configuration from the
+ * DOM elements and attributes.
+ */
 class DOMMapper implements IMapper {
 
+    /**
+     * The mapping function that will convert HTML data to a viewer configuration object
+     * @param baseElement the baseElement from which to start traversing
+     * @returns a ViewerCOnfiguration object from the provided HTML Element
+     */
     map(baseElement: HTMLElement): ViewerConfiguration {
         let htmlMapper = new HTMLMapper();
         let config = htmlMapper.map(baseElement);
@@ -88,6 +100,7 @@ class DOMMapper implements IMapper {
             if (children.length) {
                 for (let i = 0; i < children.length; ++i) {
                     let item = <HTMLElement>children.item(i);
+                    // use the HTML Mapper to read configuration from a single element
                     let configMapped = htmlMapper.map(item);
                     let key = kebabToCamel(item.nodeName.toLowerCase());
                     if (item.attributes.getNamedItem('array') && item.attributes.getNamedItem('array').nodeValue === 'true') {
@@ -96,7 +109,7 @@ class DOMMapper implements IMapper {
                         if (element.attributes.getNamedItem('array') && element.attributes.getNamedItem('array').nodeValue === 'true') {
                             partConfig.push(configMapped)
                         } else if (partConfig[key]) {
-                            //exists already! problem... probably an array
+                            //exists already! probably an array
                             element.setAttribute('array', 'true');
                             let oldItem = partConfig[key];
                             partConfig = [oldItem, configMapped]
@@ -118,9 +131,16 @@ class DOMMapper implements IMapper {
 
 }
 
+/**
+ * The MapperManager manages the different implemented mappers.
+ * It allows the user to register new mappers as well and use them to parse their own configuration data
+ */
 export class MapperManager {
 
     private _mappers: { [key: string]: IMapper };
+    /**
+     * The default mapper is the JSON mapper.
+     */
     public static DefaultMapper = 'json';
 
     constructor() {
@@ -131,6 +151,11 @@ export class MapperManager {
         }
     }
 
+    /**
+     * Get a specific configuration mapper.
+     * 
+     * @param type the name of the mapper to load
+     */
     public getMapper(type: string) {
         if (!this._mappers[type]) {
             Tools.Error("No mapper defined for " + type);
@@ -138,14 +163,28 @@ export class MapperManager {
         return this._mappers[type] || this._mappers[MapperManager.DefaultMapper];
     }
 
+    /**
+     * Use this functio to register your own configuration mapper.
+     * After a mapper is registered, it can be used to parse the specific type fo configuration to the standard ViewerConfiguration.
+     * @param type the name of the mapper. This will be used to define the configuration type and/or to get the mapper
+     * @param mapper The implemented mapper 
+     */
     public registerMapper(type: string, mapper: IMapper) {
         this._mappers[type] = mapper;
     }
 
+    /**
+     * Dispose the mapper manager and all of its mappers.
+     */
     public dispose() {
         this._mappers = {};
     }
 
 }
 
+/**
+ * mapperManager is a singleton of the type MapperManager.
+ * The mapperManager can be disposed directly with calling mapperManager.dispose()
+ * or indirectly with using BabylonViewer.disposeAll()
+ */
 export let mapperManager = new MapperManager();

+ 121 - 3
Viewer/src/model/modelAnimation.ts

@@ -1,10 +1,16 @@
 import { AnimationGroup, Animatable, Skeleton } from "babylonjs";
 
+/**
+ * Animation play mode enum - is the animation looping or playing once
+ */
 export enum AnimationPlayMode {
     ONCE,
     LOOP
 }
 
+/**
+ * An enum representing the current state of an animation object
+ */
 export enum AnimationState {
     INIT,
     PLAYING,
@@ -13,28 +19,89 @@ export enum AnimationState {
     ENDED
 }
 
+/**
+ * This interface can be implemented to define new types of ModelAnimation objects.
+ */
 export interface IModelAnimation {
+    /**
+     * Current animation state (playing, stopped etc')
+     */
     readonly state: AnimationState;
+    /**
+     * the name of the animation
+     */
     readonly name: string;
+    /**
+     * Get the max numbers of frame available in the animation group
+     * 
+     * In correlation to an arry, this would be ".length"
+     */
     readonly frames: number;
+    /**
+     * Get the current frame playing right now. 
+     * This can be used to poll the frame currently playing (and, for exmaple, display a progress bar with the data)
+     * 
+     * In correlation to an array, this would be the current index
+     */
     readonly currentFrame: number;
+    /**
+     * Animation's FPS value
+     */
     readonly fps: number;
+    /**
+     * Get or set the animation's speed ration (Frame-to-fps)
+     */
     speedRatio: number;
+    /**
+     * Gets or sets the aimation's play mode.
+     */
     playMode: AnimationPlayMode;
+    /**
+     * Start the animation
+     */
     start();
+    /**
+     * Stop the animation.
+     * This will fail silently if the animation group is already stopped.
+     */
     stop();
+    /**
+     * Pause the animation
+     * This will fail silently if the animation is not currently playing
+     */
     pause();
+    /**
+     * Reset this animation
+     */
     reset();
+    /**
+     * Restart the animation
+     */
     restart();
+    /**
+     * Go to a specific 
+     * @param frameNumber the frame number to go to
+     */
     goToFrame(frameNumber: number);
+    /**
+     * Dispose this animation
+     */
     dispose();
 }
 
+/**
+ * The GroupModelAnimation is an implementation of the IModelAnimation interface using BABYLON's
+ * native GroupAnimation class.
+ */
 export class GroupModelAnimation implements IModelAnimation {
 
     private _playMode: AnimationPlayMode;
     private _state: AnimationState;
 
+    /**
+     * Create a new GroupModelAnimation object using an AnimationGroup object
+     * @param _animationGroup The aniamtion group to base the class on
+     */
     constructor(private _animationGroup: AnimationGroup) {
         this._state = AnimationState.INIT;
         this._playMode = AnimationPlayMode.LOOP;
@@ -42,31 +109,42 @@ export class GroupModelAnimation implements IModelAnimation {
         this._animationGroup.onAnimationEndObservable.add(() => {
             this.stop();
             this._state = AnimationState.ENDED;
-        })
+        });
     }
 
+    /**
+     * Get the animation's name
+     */
     public get name() {
         return this._animationGroup.name;
     }
 
+    /**
+     * Get the current animation's state
+     */
     public get state() {
         return this._state;
     }
 
     /**
-     * Gets or sets the speed ratio to use for all animations
+     * Gets the speed ratio to use for all animations
      */
     public get speedRatio(): number {
         return this._animationGroup.speedRatio;
     }
 
     /**
-     * Gets or sets the speed ratio to use for all animations
+     * Sets the speed ratio to use for all animations
      */
     public set speedRatio(value: number) {
         this._animationGroup.speedRatio = value;
     }
 
+    /**
+     * Get the max numbers of frame available in the animation group
+     * 
+     * In correlation to an arry, this would be ".length"
+     */
     public get frames(): number {
         let animationFrames = this._animationGroup.targetedAnimations.map(ta => {
             let keys = ta.animation.getKeys();
@@ -75,6 +153,12 @@ export class GroupModelAnimation implements IModelAnimation {
         return Math.max.apply(null, animationFrames);
     }
 
+    /**
+     * Get the current frame playing right now. 
+     * This can be used to poll the frame currently playing (and, for exmaple, display a progress bar with the data)
+     * 
+     * In correlation to an array, this would be the current index
+     */
     public get currentFrame(): number {
         // get the first currentFrame found
         for (let i = 0; i < this._animationGroup.animatables.length; ++i) {
@@ -92,6 +176,9 @@ export class GroupModelAnimation implements IModelAnimation {
         return 0;
     }
 
+    /**
+     * Get the FPS value of this animation
+     */
     public get fps(): number {
         // get the first currentFrame found
         for (let i = 0; i < this._animationGroup.animatables.length; ++i) {
@@ -109,10 +196,18 @@ export class GroupModelAnimation implements IModelAnimation {
         return 0;
     }
 
+    /**
+     * What is the animation'S play mode (looping or played once)
+     */
     public get playMode(): AnimationPlayMode {
         return this._playMode;
     }
 
+    /**
+     * Set the play mode.
+     * If the animation is played, it will continue playing at least once more, depending on the new play mode set.
+     * If the animation is not set, the will be initialized and will wait for the user to start playing it.
+     */
     public set playMode(value: AnimationPlayMode) {
         if (value === this._playMode) {
             return;
@@ -128,14 +223,24 @@ export class GroupModelAnimation implements IModelAnimation {
         }
     }
 
+    /**
+     * Reset the animation group
+     */
     reset() {
         this._animationGroup.reset();
     }
 
+    /**
+     * Restart the animation group
+     */
     restart() {
         this._animationGroup.restart();
     }
 
+    /**
+     * 
+     * @param frameNumber Go to a specific frame in the animation
+     */
     goToFrame(frameNumber: number) {
         // this._animationGroup.goToFrame(frameNumber);
         this._animationGroup['_animatables'].forEach(a => {
@@ -143,6 +248,9 @@ export class GroupModelAnimation implements IModelAnimation {
         })
     }
 
+    /**
+     * Start playing the animation.
+     */
     public start() {
         this._animationGroup.start(this.playMode === AnimationPlayMode.LOOP, this.speedRatio);
         if (this._animationGroup.isStarted) {
@@ -150,11 +258,18 @@ export class GroupModelAnimation implements IModelAnimation {
         }
     }
 
+    /**
+     * Pause the animation
+     */
     pause() {
         this._animationGroup.pause();
         this._state = AnimationState.PAUSED;
     }
 
+    /**
+     * Stop the animation.
+     * This will fail silently if the animation group is already stopped.
+     */
     public stop() {
         this._animationGroup.stop();
         if (!this._animationGroup.isStarted) {
@@ -162,6 +277,9 @@ export class GroupModelAnimation implements IModelAnimation {
         }
     }
 
+    /**
+     * Dispose this animation object.
+     */
     public dispose() {
         this._animationGroup.dispose();
     }

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

@@ -3,6 +3,12 @@ import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, Tools, SceneLoader, Tags,
 import { IModelConfiguration } from "../configuration/configuration";
 import { ViewerModel, ModelState } from "./viewerModel";
 
+/**
+ * An instance of the class is in charge of loading the model correctly.
+ * This class will continously be expended with tasks required from the specific loaders Babylon has.
+ * 
+ * A Model loader is unique per (Abstract)Viewer. It is being generated by the viewer
+ */
 export class ModelLoader {
 
     private _loadId: number;
@@ -10,21 +16,81 @@ export class ModelLoader {
 
     private _loaders: Array<ISceneLoaderPlugin | ISceneLoaderPluginAsync>;
 
+    /**
+     * Create a new Model loader
+     * @param _viewer the viewer using this model loader
+     */
     constructor(private _viewer: AbstractViewer) {
         this._loaders = [];
         this._loadId = 0;
     }
 
+    /**
+     * Load a model using predefined configuration
+     * @param modelConfiguration the modelConfiguration to use to load the model
+     */
     public load(modelConfiguration: IModelConfiguration): ViewerModel {
 
         const model = new ViewerModel(this._viewer.scene, modelConfiguration);
 
+        if (!modelConfiguration.url) {
+            model.state = ModelState.ERROR;
+            Tools.Error("No URL provided");
+            return model;
+        }
+
+        let filename = Tools.GetFilename(modelConfiguration.url) || modelConfiguration.url;
+        let base = modelConfiguration.root || Tools.GetFolderPath(modelConfiguration.url);
+        let plugin = modelConfiguration.loader;
+
+        model.loader = SceneLoader.ImportMesh(undefined, base, filename, this._viewer.scene, (meshes, particleSystems, skeletons) => {
+            meshes.forEach(mesh => {
+                Tags.AddTagsTo(mesh, "viewerMesh");
+            });
+            model.meshes = meshes;
+            model.particleSystems = particleSystems;
+            model.skeletons = skeletons;
+
+            model.initAnimations();
+            model.onLoadedObservable.notifyObserversWithPromise(model);
+        }, (progressEvent) => {
+            model.onLoadProgressObservable.notifyObserversWithPromise(progressEvent);
+        }, (e, m, exception) => {
+            model.state = ModelState.ERROR;
+            Tools.Error("Load Error: There was an error loading the model. " + m);
+            model.onLoadErrorObservable.notifyObserversWithPromise({ message: m, exception: exception });
+        }, plugin)!;
+
+        if (model.loader.name === "gltf") {
+            let gltfLoader = (<GLTFFileLoader>model.loader);
+            gltfLoader.animationStartMode = 0;
+            gltfLoader.onAnimationGroupLoaded = ag => {
+                model.addAnimationGroup(ag);
+            }
+        }
+
         model.loadId = this._loadId++;
         this._loaders.push(model.loader);
 
         return model;
     }
 
+    public cancelLoad(model: ViewerModel) {
+        const loader = model.loader || this._loaders[model.loadId];
+        // ATM only available in the GLTF Loader
+        if (loader && loader.name === "gltf") {
+            let gltfLoader = (<GLTFFileLoader>loader);
+            gltfLoader.dispose();
+            model.state = ModelState.CANCELED;
+        } else {
+            Tools.Warn("This type of loader cannot cancel the request");
+        }
+    }
+
+    /**
+     * dispose the model loader.
+     * If loaders are registered and are in the middle of loading, they will be disposed and the request(s) will be cancelled.
+     */
     public dispose() {
         this._loaders.forEach(loader => {
             if (loader.name === "gltf") {

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

@@ -4,6 +4,7 @@ import { IModelAnimation, GroupModelAnimation, AnimationPlayMode } from "./model
 
 import * as deepmerge from '../../assets/deepmerge.min.js';
 
+
 export enum ModelState {
     INIT,
     LOADING,
@@ -12,30 +13,72 @@ export enum ModelState {
     ERROR
 }
 
+/**
+ * The viewer model is a container for all assets representing a sngle loaded model.
+ */
 export class ViewerModel implements IDisposable {
 
+    /**
+     * The loader used to load this model.
+     */
     public loader: ISceneLoaderPlugin | ISceneLoaderPluginAsync;
 
     private _animations: Array<IModelAnimation>;
+
+    /**
+     * the list of meshes that are a part of this model
+     */
     public meshes: Array<AbstractMesh> = [];
+    /**
+     * This model's root mesh (the parent of all other meshes).
+     * This mesh also exist in the meshes array.
+     */
     public rootMesh: AbstractMesh;
+    /**
+     * ParticleSystems connected to this model
+     */
     public particleSystems: Array<ParticleSystem> = [];
+    /**
+     * Skeletons defined in this model
+     */
     public skeletons: Array<Skeleton> = [];
+    /**
+     * The current model animation.
+     * On init, this will be undefined.
+     */
     public currentAnimation: IModelAnimation;
 
+    /**
+     * Observers registered here will be executed when the model is done loading
+     */
     public onLoadedObservable: Observable<ViewerModel>;
+    /**
+     * Observers registered here will be executed when the loader notified of a progress event
+     */
     public onLoadProgressObservable: Observable<SceneLoaderProgressEvent>;
+    /**
+     * Observers registered here will be executed when the loader notified of an error.
+     */
     public onLoadErrorObservable: Observable<{ message: string; exception: any }>;
 
+    /**
+     * Observers registered here will be executed every time the model is being configured.
+     * This can be used to extend the model's configuration without extending the class itself
+     */
     public onAfterConfigure: Observable<ViewerModel>;
 
+    /**
+     * The current model state (loaded, error, etc)
+     */
     public state: ModelState;
+    /**
+     * A loadID provided by the modelLoader, unique to ths (Abstract)Viewer instance.
+     */
     public loadId: number;
-
-    private _loaderDisposed: boolean = false;
     private _loadedUrl: string;
+    private _modelConfiguration: IModelConfiguration;
 
-    constructor(private _scene: Scene, private _modelConfiguration: IModelConfiguration, disableAutoLoad = false) {
+    constructor(private _scene: Scene, modelConfiguration: IModelConfiguration) {
         this.onLoadedObservable = new Observable();
         this.onLoadErrorObservable = new Observable();
         this.onLoadProgressObservable = new Observable();
@@ -44,43 +87,38 @@ export class ViewerModel implements IDisposable {
         this.state = ModelState.INIT;
 
         this._animations = [];
-
-        if (!disableAutoLoad) {
-            this._initLoad();
-        }
-    }
-
-    public load() {
-        if (this.loader) {
-            Tools.Error("Model was already loaded or in the process of loading.");
-        } else {
-            this._initLoad();
-        }
-    }
-
-    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;
-        }
+        //create a copy of the configuration to make sure it doesn't change even after it is changed in the viewer
+        this._modelConfiguration = deepmerge({}, modelConfiguration);
     }
 
+    /**
+     * Get the model's configuration
+     */
     public get configuration(): IModelConfiguration {
         return this._modelConfiguration;
     }
 
+    /**
+     * (Re-)set the model's entire configuration
+     * @param newConfiguration the new configuration to replace the new one
+     */
     public set configuration(newConfiguration: IModelConfiguration) {
         this._modelConfiguration = newConfiguration;
         this._configureModel();
     }
 
+    /**
+     * Update the current configuration with new values.
+     * Configuration will not be overwritten, but merged with the new configuration.
+     * Priority is to the new configuration
+     * @param newConfiguration the configuration to be merged into the current configuration;
+     */
     public updateConfiguration(newConfiguration: Partial<IModelConfiguration>) {
         this._modelConfiguration = deepmerge(this._modelConfiguration, newConfiguration);
         this._configureModel();
     }
 
+
     public initAnimations() {
         this._animations.forEach(a => {
             a.dispose();
@@ -116,18 +154,32 @@ export class ViewerModel implements IDisposable {
         }
     }
 
+    /**
+     * Add a new animation group to this model.
+     * @param animationGroup the new animation group to be added
+     */
     public addAnimationGroup(animationGroup: AnimationGroup) {
         this._animations.push(new GroupModelAnimation(animationGroup));
     }
 
-    public getAnimations() {
+    /**
+     * Get the ModelAnimation array
+     */
+    public getAnimations(): Array<IModelAnimation> {
         return this._animations;
     }
 
-    public getAnimationNames() {
+    /**
+     * Get the animations' names. Using the names you can play a specific animation.
+     */
+    public getAnimationNames(): Array<string> {
         return this._animations.map(a => a.name);
     }
 
+    /**
+     * Get an animation by the provided name. Used mainly when playing n animation.
+     * @param name the name of the animation to find
+     */
     protected _getAnimationByName(name: string): Nullable<IModelAnimation> {
         // can't use .find, noe available on IE
         let filtered = this._animations.filter(a => a.name === name);
@@ -139,6 +191,11 @@ export class ViewerModel implements IDisposable {
         }
     }
 
+    /**
+     * Choose an initialized animation using its name and start playing it
+     * @param name the name of the animation to play
+     * @returns The model aniamtion to be played.
+     */
     public playAnimation(name: string): IModelAnimation {
         let animation = this._getAnimationByName(name);
         if (animation) {
@@ -251,50 +308,9 @@ export class ViewerModel implements IDisposable {
         this.onAfterConfigure.notifyObservers(this);
     }
 
-    private _initLoad() {
-        if (!this._modelConfiguration.url) {
-            this.state = ModelState.ERROR;
-            Tools.Error("No URL provided");
-            return;
-        }
-
-        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;
-
-
-        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.initAnimations();
-            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)!;
-
-        if (this.loader.name === "gltf") {
-            let gltfLoader = (<GLTFFileLoader>this.loader);
-            gltfLoader.animationStartMode = 0;
-            gltfLoader.onDispose = () => {
-                this._loaderDisposed = true;
-            }
-            gltfLoader.onAnimationGroupLoaded = ag => {
-                this.addAnimationGroup(ag);
-            }
-        }
-
-    }
-
+    /**
+     * Dispose this model, including all of its associated assets.
+     */
     public dispose() {
         this.onAfterConfigure.clear();
         this.onLoadedObservable.clear();

+ 3 - 2
dist/preview release/viewer/babylon.viewer.d.ts

@@ -435,17 +435,19 @@ declare module BabylonViewer {
         INIT,
         LOADING,
         LOADED,
+        CANCELED,
         ERROR
     }
 
     export class ModelLoader {
         constructor(viewer: AbstractViewer);
         load(modelConfiguration: IModelConfiguration): ViewerModel;
+        cancelLoad(model: ViewerModel): void;
         dispose(): void;
     }
 
     export class ViewerModel {
-        constructor(scene: BABYLON.Scene, modelConfiguration: IModelConfiguration, disableAutoLoad: boolean);
+        constructor(scene: BABYLON.Scene, modelConfiguration: IModelConfiguration);
         loader: BABYLON.ISceneLoaderPlugin | BABYLON.ISceneLoaderPluginAsync;
         meshes: Array<BABYLON.AbstractMesh>;
         rootMesh: BABYLON.AbstractMesh;
@@ -461,7 +463,6 @@ declare module BabylonViewer {
         onAfterConfigure: BABYLON.Observable<ViewerModel>;
         state: ModelState;
         loadId: number;
-        load(): void;
         initAnimations(): void;
         addAnimationGroup(animationGroup: BABYLON.AnimationGroup): void;
         getAnimations(): Array<IModelAnimation>;