소스 검색

Some enhancement to new animation system (#1845)

* Some enhancement to new animation system, including:
* Option to enable/disable animation mask propagation to child actions.
* Option to control max transition weight. For example useful for controlling smooth animation transition when an animation is removed from an upper layer.
* Added animation loop support in AnimLayer.
* AnimLayer can now also keep action name, so one can easily lookup currently playing action name in an specific layer.

* Minor Javadoc fix.

* AnimLayer: clear `currentActionName` inside `cloneFields` method.
Ali-RS 3 년 전
부모
커밋
7911a61f47

+ 16 - 4
jme3-core/src/main/java/com/jme3/anim/AnimComposer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2022 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -115,7 +115,7 @@ public class AnimComposer extends AbstractControl {
     }
 
     /**
-     * Run an action on the default layer.
+     * Run an action on the default layer. By default action will loop.
      *
      * @param name The name of the action to run.
      * @return The action corresponding to the given name.
@@ -125,16 +125,28 @@ public class AnimComposer extends AbstractControl {
     }
 
     /**
-     * Run an action on specified layer.
+     * Run an action on specified layer. By default action will loop.
      *
      * @param actionName The name of the action to run.
      * @param layerName The layer on which action should run.
      * @return The action corresponding to the given name.
      */
     public Action setCurrentAction(String actionName, String layerName) {
+        return setCurrentAction(actionName, layerName, true);
+    }
+
+    /**
+     * Run an action on specified layer.
+     *
+     * @param actionName The name of the action to run.
+     * @param layerName The layer on which action should run.
+     * @param loop True if the action must loop.
+     * @return The action corresponding to the given name.
+     */
+    public Action setCurrentAction(String actionName, String layerName, boolean loop) {
         AnimLayer l = getLayer(layerName);
         Action currentAction = action(actionName);
-        l.setCurrentAction(currentAction);
+        l.setCurrentAction(actionName, currentAction, loop);
 
         return currentAction;
     }

+ 68 - 2
jme3-core/src/main/java/com/jme3/anim/AnimLayer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2022 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -53,6 +53,10 @@ public class AnimLayer implements JmeCloneable {
      * The Action currently running on this layer, or null if none.
      */
     private Action currentAction;
+    /**
+     * The name of Action currently running on this layer, or null if none.
+     */
+    private String currentActionName;
     /**
      * The composer that owns this layer. Were it not for cloning, this field
      * would be final.
@@ -77,6 +81,8 @@ public class AnimLayer implements JmeCloneable {
      */
     final private String name;
 
+    private boolean loop = true;
+
     /**
      * Instantiates a layer without a manager or a current Action, owned by the
      * specified composer.
@@ -105,6 +111,15 @@ public class AnimLayer implements JmeCloneable {
         return currentAction;
     }
 
+    /**
+     * Returns the name of the Action that's currently running.
+     *
+     * @return the pre-existing instance, or null if none
+     */
+    public String getCurrentActionName() {
+        return currentActionName;
+    }
+
     /**
      * Returns the current manager.
      *
@@ -145,14 +160,42 @@ public class AnimLayer implements JmeCloneable {
 
     /**
      * Runs the specified Action, starting from time = 0. This cancels any
-     * Action previously running on this layer.
+     * Action previously running on this layer. By default Action will loop.
      *
      * @param actionToRun the Action to run (alias created) or null for no
      *     action
      */
     public void setCurrentAction(Action actionToRun) {
+        this.setCurrentAction(null, actionToRun);
+    }
+
+    /**
+     * Runs the specified Action, starting from time = 0. This cancels any
+     * Action previously running on this layer. By default Action will loop.
+     *
+     * @param actionName the Action name or null for no action name
+     * @param actionToRun the Action to run (alias created) or null for no
+     *     action
+     */
+    public void setCurrentAction(String actionName, Action actionToRun) {
+        this.setCurrentAction(actionName, actionToRun, true);
+    }
+
+    /**
+     * Runs the specified Action, starting from time = 0. This cancels any
+     * Action previously running on this layer.
+     *
+     * @param actionName the Action name or null for no action name
+     * @param actionToRun the Action to run (alias created) or null for no
+     *     action
+     * @param loop true if Action must loop. If it is false, Action will be
+     *     removed after finished running
+     */
+    public void setCurrentAction(String actionName, Action actionToRun, boolean loop) {
         this.time = 0.0;
         this.currentAction = actionToRun;
+        this.currentActionName = actionName;
+        this.loop = loop;
     }
 
     /**
@@ -181,6 +224,24 @@ public class AnimLayer implements JmeCloneable {
         }
     }
 
+    /**
+     * @return True if the Action will keep looping after it is done playing,
+     * otherwise, returns false
+     */
+    public boolean isLooping() {
+        return loop;
+    }
+
+    /**
+     * Sets the looping mode for this layer. The default is true.
+     *
+     * @param loop True if the action should keep looping after it is done
+     * playing
+     */
+    public void setLooping(boolean loop) {
+        this.loop = loop;
+    }
+
     /**
      * Updates the animation time and the current Action during a
      * controlUpdate().
@@ -211,6 +272,10 @@ public class AnimLayer implements JmeCloneable {
 
         if (!running) { // went past the end of the current Action
             time = 0.0;
+            if (!loop) {
+                // Clear the current action
+                setCurrentAction(null);
+            }
         }
     }
 
@@ -229,6 +294,7 @@ public class AnimLayer implements JmeCloneable {
     public void cloneFields(Cloner cloner, Object original) {
         composer = cloner.clone(composer);
         currentAction = null;
+        currentActionName = null;
     }
 
     @Override

+ 60 - 0
jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java

@@ -1,5 +1,37 @@
+/*
+ * Copyright (c) 2009-2022 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
 package com.jme3.anim.tween.action;
 
+import com.jme3.anim.AnimationMask;
 import com.jme3.anim.tween.ContainsTweens;
 import com.jme3.anim.tween.Tween;
 import com.jme3.util.SafeArrayList;
@@ -9,6 +41,7 @@ import java.util.List;
 public class BaseAction extends Action {
 
     final private Tween tween;
+    private boolean maskPropagationEnabled = true;
 
     public BaseAction(Tween tween) {
         this.tween = tween;
@@ -30,6 +63,33 @@ public class BaseAction extends Action {
         }
     }
 
+    /**
+     * @return true if mask propagation to child actions is enabled else returns false
+     */
+    public boolean isMaskPropagationEnabled() {
+        return maskPropagationEnabled;
+    }
+
+    /**
+     *
+     * @param maskPropagationEnabled If true, then mask set by AnimLayer will be
+     *                               forwarded to all child actions (Default=true)
+     */
+    public void setMaskPropagationEnabled(boolean maskPropagationEnabled) {
+        this.maskPropagationEnabled = maskPropagationEnabled;
+    }
+
+    @Override
+    public void setMask(AnimationMask mask) {
+        super.setMask(mask);
+
+        if (maskPropagationEnabled) {
+            for (Action action : actions) {
+                action.setMask(mask);
+            }
+        }
+    }
+
     @Override
     public boolean interpolate(double t) {
         return tween.interpolate(t);

+ 50 - 1
jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java

@@ -1,3 +1,34 @@
+/*
+ * Copyright (c) 2009-2022 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
 package com.jme3.anim.tween.action;
 
 import com.jme3.anim.tween.AbstractTween;
@@ -11,6 +42,7 @@ public abstract class BlendableAction extends Action {
 
     protected BlendableAction collectTransformDelegate;
     private float transitionWeight = 1.0f;
+    private double maxTransitionWeight = 1.0;
     private double transitionLength = 0.4f;
     private float weight = 1f;
     private TransitionTween transition = new TransitionTween(transitionLength);
@@ -81,6 +113,23 @@ public abstract class BlendableAction extends Action {
         return transitionWeight;
     }
 
+    /**
+     * @param maxTransitionWeight The max transition weight. Must be &gt= 0 and &lt=1 (default=1)
+     */
+    public void setMaxTransitionWeight(double maxTransitionWeight) {
+        assert maxTransitionWeight >= 0 && maxTransitionWeight <= 1;
+
+        this.maxTransitionWeight = maxTransitionWeight;
+    }
+
+    /**
+     *
+     * @return The max transition weight (default=1)
+     */
+    public double getMaxTransitionWeight() {
+        return maxTransitionWeight;
+    }
+
     /**
      * Create a shallow clone for the JME cloner.
      *
@@ -121,7 +170,7 @@ public abstract class BlendableAction extends Action {
 
         @Override
         protected void doInterpolate(double t) {
-            transitionWeight = (float) t;
+            transitionWeight = (float) Math.min(t, maxTransitionWeight);
         }
     }
 

+ 31 - 4
jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017-2021 jMonkeyEngine
+ * Copyright (c) 2017-2022 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -32,8 +32,7 @@
 package jme3test.model.anim;
 
 import com.jme3.anim.*;
-import com.jme3.anim.tween.action.BlendAction;
-import com.jme3.anim.tween.action.LinearBlendSpace;
+import com.jme3.anim.tween.action.*;
 import com.jme3.anim.util.AnimMigrationUtils;
 import com.jme3.app.ChaseCameraAppState;
 import com.jme3.app.SimpleApplication;
@@ -157,9 +156,10 @@ public class TestAnimMigration extends SimpleApplication {
             @Override
             public void onAction(String name, boolean isPressed, float tpf) {
                 if (isPressed) {
-                    composer.setCurrentAction("Wave", "LeftArm");
+                    ((BlendableAction)composer.setCurrentAction("Wave", "LeftArm", false)).setMaxTransitionWeight(0.9);
                 }
             }
+
         }, "mask");
 
         inputManager.addMapping("blendUp", new KeyTrigger(KeyInput.KEY_UP));
@@ -184,6 +184,33 @@ public class TestAnimMigration extends SimpleApplication {
                 //System.err.println(blendValue);
             }
         }, "blendUp", "blendDown");
+
+        inputManager.addMapping("maxTransitionWeightInc", new KeyTrigger(KeyInput.KEY_ADD));
+        inputManager.addMapping("maxTransitionWeightDec", new KeyTrigger(KeyInput.KEY_SUBTRACT));
+
+        inputManager.addListener(new AnalogListener() {
+
+            @Override
+            public void onAnalog(String name, float value, float tpf) {
+                if (name.equals("maxTransitionWeightInc")) {
+                    Action action = composer.getCurrentAction();
+                    if (action instanceof BlendableAction) {
+                        BlendableAction ba = (BlendableAction) action;
+                        ba.setMaxTransitionWeight(Math.min(ba.getMaxTransitionWeight() + 0.01, 1.0));
+                        System.out.println("MaxTransitionWeight=" + ba.getMaxTransitionWeight());
+                    }
+                }
+                if (name.equals("maxTransitionWeightDec")) {
+                    Action action = composer.getCurrentAction();
+                    if (action instanceof BlendableAction) {
+                        BlendableAction ba = (BlendableAction) action;
+                        ba.setMaxTransitionWeight(Math.max(ba.getMaxTransitionWeight() - 0.01, 0.0));
+                        System.out.println("MaxTransitionWeight=" + ba.getMaxTransitionWeight());
+                    }
+                }
+                //System.err.println(blendValue);
+            }
+        }, "maxTransitionWeightInc", "maxTransitionWeightDec");
     }
 
     private void setupModel(Spatial model) {