浏览代码

Fix animation edge cases (one key, from === to)

Gary Hsu 6 年之前
父节点
当前提交
270d670949

+ 5 - 2
src/Animations/animation.ts

@@ -576,7 +576,10 @@ export class Animation {
             return highLimitValue.clone ? highLimitValue.clone() : highLimitValue;
         }
 
-        let keys = this.getKeys();
+        const keys = this.getKeys();
+        if (keys.length === 1) {
+            return this._getKeyValue(keys[0].value);
+        }
 
         // Try to get a hash to find the right key
         var startKeyIndex = Math.max(0, Math.min(keys.length - 1, Math.floor(keys.length * (currentFrame - keys[0].frame) / (keys[keys.length - 1].frame - keys[0].frame)) - 1));
@@ -591,7 +594,6 @@ export class Animation {
             var endKey = keys[key + 1];
 
             if (endKey.frame >= currentFrame) {
-
                 var startKey = keys[key];
                 var startValue = this._getKeyValue(startKey.value);
                 if (startKey.interpolation === AnimationKeyInterpolation.STEP) {
@@ -691,6 +693,7 @@ export class Animation {
                 break;
             }
         }
+
         return this._getKeyValue(keys[keys.length - 1].value);
     }
 

+ 21 - 33
src/Animations/runtimeAnimation.ts

@@ -425,44 +425,32 @@ export class RuntimeAnimation {
             return false;
         }
 
-        var returnValue = true;
+        let returnValue = true;
 
         let keys = this._animation.getKeys();
+        let min = keys[0].frame;
+        let max = keys[keys.length - 1].frame;
 
-        // Adding a start key at frame 0 if missing
-        if (keys[0].frame !== 0) {
-            var newKey = { frame: 0, value: keys[0].value };
+        // Add a start key at frame 0 if missing
+        if (min !== 0) {
+            const newKey = { frame: 0, value: keys[0].value };
             keys.splice(0, 0, newKey);
         }
-        // Adding a duplicate key when there is only one key at frame zero
-        else if (keys.length === 1) {
-            var newKey = { frame: 0.001, value: keys[0].value };
-            keys.push(newKey);
-        }
 
         // Check limits
-        if (from < keys[0].frame || from > keys[keys.length - 1].frame) {
-            from = keys[0].frame;
+        if (from < min || from > max) {
+            from = min;
         }
-        if (to < keys[0].frame || to > keys[keys.length - 1].frame) {
-            to = keys[keys.length - 1].frame;
+        if (to < min || to > max) {
+            to = max;
         }
 
-        //to and from cannot be the same key
-        if (from === to) {
-            if (from > keys[0].frame) {
-                from--;
-            } else if (to < keys[keys.length - 1].frame) {
-                to++;
-            }
-        }
+        const range = to - from;
+        let offsetValue: any;
 
-        // Compute ratio
-        var range = to - from;
-        var offsetValue: any;
-        // ratio represents the frame delta between from and to
-        var ratio = (delay * (this._animation.framePerSecond * speedRatio) / 1000.0) + this._ratioOffset;
-        var highLimitValue = 0;
+        // Compute ratio which represents the frame delta between from and to
+        const ratio = (delay * (this._animation.framePerSecond * speedRatio) / 1000.0) + this._ratioOffset;
+        let highLimitValue = 0;
 
         this._previousDelay = delay;
         this._previousRatio = ratio;
@@ -541,18 +529,17 @@ export class RuntimeAnimation {
         }
 
         // Compute value
-        var repeatCount = (ratio / range) >> 0;
-        var currentFrame = returnValue ? from + ratio % range : to;
+        let currentFrame = (returnValue && range !== 0) ? from + ratio % range : to;
 
         // Need to normalize?
         if (this._host && this._host.syncRoot) {
-            let syncRoot = this._host.syncRoot;
-            let hostNormalizedFrame = (syncRoot.masterFrame - syncRoot.fromFrame) / (syncRoot.toFrame - syncRoot.fromFrame);
+            const syncRoot = this._host.syncRoot;
+            const hostNormalizedFrame = (syncRoot.masterFrame - syncRoot.fromFrame) / (syncRoot.toFrame - syncRoot.fromFrame);
             currentFrame = from + (to - from) * hostNormalizedFrame;
         }
 
         // Reset events if looping
-        let events = this._events;
+        const events = this._events;
         if (range > 0 && this.currentFrame > currentFrame ||
             range < 0 && this.currentFrame < currentFrame) {
             if (onLoop) {
@@ -568,7 +555,8 @@ export class RuntimeAnimation {
             }
         }
 
-        var currentValue = this._interpolate(currentFrame, repeatCount, this._getCorrectLoopMode(), offsetValue, highLimitValue);
+        const repeatCount = range === 0 ? 0 : (ratio / range) >> 0;
+        const currentValue = this._interpolate(currentFrame, repeatCount, this._getCorrectLoopMode(), offsetValue, highLimitValue);
 
         // Set value
         this.setValue(currentValue, weight);

+ 53 - 0
tests/unit/babylon/src/Animations/babylon.animation.tests.ts

@@ -0,0 +1,53 @@
+/**
+ * Describes the test suite.
+ */
+describe('Babylon Animation', function() {
+    let subject: BABYLON.Engine;
+
+    /**
+     * Loads the dependencies.
+     */
+    before(function(done) {
+        this.timeout(180000);
+        (BABYLONDEVTOOLS).Loader
+            .useDist()
+            .testMode()
+            .load(function() {
+                // Force apply promise polyfill for consistent behavior between PhantomJS, IE11, and other browsers.
+                BABYLON.PromisePolyfill.Apply(true);
+                done();
+            });
+    });
+
+    /**
+     * Create a new engine subject before each test.
+     */
+    beforeEach(function() {
+        subject = new BABYLON.NullEngine({
+            renderHeight: 256,
+            renderWidth: 256,
+            textureSize: 256,
+            deterministicLockstep: false,
+            lockstepMaxSteps: 1
+        });
+
+        // Avoid creating normals in PBR materials.
+        subject.getCaps().standardDerivatives = true;
+    });
+
+    /**
+     * Animation tests.
+     */
+    describe('#Animation', () => {
+        it('one key', () => {
+            const scene = new BABYLON.Scene(subject);
+            const box = BABYLON.Mesh.CreateBox("box", 1, scene);
+            const animation = new BABYLON.Animation("anim", "position.x", 1, BABYLON.Animation.ANIMATIONTYPE_FLOAT);
+            animation.setKeys([{ frame: 0, value: 1 }]);
+            box.animations.push(animation);
+            scene.beginAnimation(box, 0, 0);
+            scene.render();
+            expect(box.position.x, "box.position.x").to.equal(1);
+        });
+    });
+});

+ 1 - 0
tests/unit/karma.conf.js

@@ -18,6 +18,7 @@ module.exports = function (config) {
             './tests/unit/babylon/src/Mesh/babylon.positionAndRotation.tests.js',
             './tests/unit/babylon/serializers/babylon.glTFSerializer.tests.js',
             './tests/unit/babylon/src/babylon.node.tests.js',
+            './tests/unit/babylon/src/Animations/babylon.animation.tests.js',
             './tests/unit/babylon/src/Animations/babylon.animationGroup.tests.js',
             './tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.js',
             './tests/unit/babylon/src/PostProcess/babylon.postProcess.tests.js',