Forráskód Böngészése

animation configuration and model animation
using both animation groups and skeleton animations

Raanan Weber 7 éve
szülő
commit
f3b77e508f

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

@@ -85,6 +85,11 @@ export interface IModelConfiguration {
     subtitle?: string;
     thumbnail?: string; // URL or data-url
 
+    animation?: {
+        autoStart?: string;
+        playOnce?: boolean;
+    }
+
     // [propName: string]: any; // further configuration, like title and creator
 }
 

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

@@ -26,9 +26,12 @@ class HTMLMapper implements IMapper {
                     } else if (val === "false") {
                         val = false;
                     } else {
-                        let number = parseFloat(val);
-                        if (!isNaN(number)) {
-                            val = number;
+                        var isnum = /^\d+$/.test(val);
+                        if (isnum) {
+                            let number = parseFloat(val);
+                            if (!isNaN(number)) {
+                                val = number;
+                            }
                         }
                     }
                     currentConfig[camelKey] = val;

+ 117 - 0
Viewer/src/model/modelAnimation.ts

@@ -0,0 +1,117 @@
+import { AnimationGroup, Animatable, Skeleton, IDisposable } from "babylonjs";
+
+export enum AnimationPlayMode {
+    ONCE,
+    LOOP
+}
+
+export enum AnimationState {
+    INIT,
+    STARTED,
+    STOPPED
+}
+
+export interface IModelAnimation extends IDisposable {
+    readonly state: AnimationState;
+    readonly name: string;
+    readonly frames: number;
+    speedRatio: number;
+    playMode: AnimationPlayMode;
+    start();
+    stop();
+    pause();
+    reset();
+    restart();
+    goToFrame(frameNumber: number);
+}
+
+export class GroupModelAnimation implements IModelAnimation {
+
+    private _playMode: AnimationPlayMode;
+    private _state: AnimationState;
+
+    constructor(private _animationGroup: AnimationGroup) {
+        this._state = AnimationState.INIT;
+        this._playMode = AnimationPlayMode.LOOP;
+    }
+
+    public get name() {
+        return this._animationGroup.name;
+    }
+
+    public get state() {
+        return this._state;
+    }
+
+    /**
+     * Gets or sets 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
+     */
+    public set speedRatio(value: number) {
+        this._animationGroup.speedRatio = value;
+    }
+
+    public get frames(): number {
+        let animationFrames = this._animationGroup.targetedAnimations.map(ta => {
+            let keys = ta.animation.getKeys();
+            return keys[keys.length - 1].frame;
+        });
+        return Math.max.apply(null, animationFrames);
+    }
+
+    public get playMode(): AnimationPlayMode {
+        return this._playMode;
+    }
+
+    public set playMode(value: AnimationPlayMode) {
+        if (value === this._playMode) {
+            return;
+        }
+
+        this._playMode = value;
+
+        if (this.state === AnimationState.STARTED) {
+            this.start();
+        }
+    }
+
+    reset() {
+        this._animationGroup.reset();
+    }
+
+    restart() {
+        this._animationGroup.restart();
+    }
+
+    goToFrame(frameNumber: number) {
+        this._animationGroup.goToFrame(frameNumber);
+    }
+
+    public start() {
+        this._animationGroup.start(this.playMode === AnimationPlayMode.LOOP, this.speedRatio);
+        if (this._animationGroup.isStarted) {
+            this._state = AnimationState.STARTED;
+        }
+    }
+
+    pause() {
+        this._animationGroup.pause();
+    }
+
+    public stop() {
+        this._animationGroup.stop();
+        if (!this._animationGroup.isStarted) {
+            this._state = AnimationState.STOPPED;
+        }
+    }
+
+    public dispose() {
+        this._animationGroup.dispose();
+    }
+}

+ 62 - 8
Viewer/src/model/viewerModel.ts

@@ -1,12 +1,12 @@
-import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, AnimationGroup, Animatable, AbstractMesh, Tools, Scene, SceneLoader, Observable, SceneLoaderProgressEvent, Tags, ParticleSystem, Skeleton } from "babylonjs";
+import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, AnimationGroup, Animatable, AbstractMesh, Tools, Scene, SceneLoader, Observable, SceneLoaderProgressEvent, Tags, ParticleSystem, Skeleton, IDisposable, Nullable, Animation } from "babylonjs";
 import { IModelConfiguration } from "../configuration/configuration";
+import { IModelAnimation, GroupModelAnimation, AnimationPlayMode } from "./modelAnimation";
 
-export class ViewerModel {
+export class ViewerModel implements IDisposable {
 
     public loader: ISceneLoaderPlugin | ISceneLoaderPluginAsync;
 
-    public animationGroups: Array<AnimationGroup>;
-    public animatables: Array<Animatable>;
+    private _animations: Array<IModelAnimation>;
     public meshes: Array<AbstractMesh>;
     public particleSystems: Array<ParticleSystem>;
     public skeletons: Array<Skeleton>;
@@ -20,6 +20,8 @@ export class ViewerModel {
         this.onLoadErrorObservable = new Observable();
         this.onLoadProgressObservable = new Observable();
 
+        this._animations = [];
+
         if (!disableAutoLoad) {
             this._initLoad();
         }
@@ -33,6 +35,25 @@ export class ViewerModel {
         }
     }
 
+    public getAnimations() {
+        return this._animations;
+    }
+
+    public getAnimationNames() {
+        return this._animations.map(a => a.name);
+    }
+
+    public getAnimationByName(name: string): Nullable<IModelAnimation> {
+        // can't use .find, noe available on IE
+        let filtered = this._animations.filter(a => a.name === name);
+        // what the next line means - if two animations have the same name, they will not be returned!
+        if (filtered.length === 1) {
+            return filtered[0];
+        } else {
+            return null;
+        }
+    }
+
     private _initLoad() {
         if (!this._modelConfiguration || !this._modelConfiguration.url) {
             return Tools.Error("No model URL to load.");
@@ -50,21 +71,54 @@ export class ViewerModel {
             this.meshes = meshes;
             this.particleSystems = particleSystems;
             this.skeletons = skeletons;
+
+            // check if this is a gltf loader and load the animations
+            if (this.loader['_loader'] && this.loader['_loader']['_gltf'] && this.loader['_loader']['_gltf'].animations) {
+                this.loader['_loader']['_gltf'].animations.forEach(animation => {
+                    this._animations.push(new GroupModelAnimation(animation._babylonAnimationGroup));
+                });
+            } else {
+                skeletons.forEach((skeleton, idx) => {
+                    let ag = new BABYLON.AnimationGroup("animation-" + idx, this._scene);
+                    skeleton.getAnimatables().forEach(a => {
+                        if (a.animations[0]) {
+                            ag.addTargetedAnimation(a.animations[0], a);
+                        }
+                    });
+                    this._animations.push(new GroupModelAnimation(ag));
+                });
+            }
+
+            if (this._modelConfiguration.animation) {
+                if (this._modelConfiguration.animation.playOnce) {
+                    this._animations.forEach(a => {
+                        a.playMode = AnimationPlayMode.ONCE;
+                    });
+                }
+                if (this._modelConfiguration.animation.autoStart) {
+                    let animation = this.getAnimationByName(this._modelConfiguration.animation.autoStart);
+                    if (animation) {
+                        animation.start();
+                    }
+                }
+            }
+
+            console.log(this.getAnimationNames());
+
             this.onLoadedObservable.notifyObserversWithPromise(this);
         }, (progressEvent) => {
             this.onLoadProgressObservable.notifyObserversWithPromise(progressEvent);
         }, (e, m, exception) => {
             this.onLoadErrorObservable.notifyObserversWithPromise({ message: m, exception: exception });
         }, plugin)!;
+
+        this.loader['animationStartMode'] = 0;
     }
 
     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._animations.forEach(ag => ag.dispose());
         this.meshes.forEach(m => m.dispose());
     }
 }