Jelajahi Sumber

convert inner class Layer to a top-level class (#1656)

* replace AnimComposer's inner class with the new AnimLayer class

* AnimComposer: add the getLayer() method

* AnimComposer:  simplify the code using getLayer()

* AnimComposer:  add the getLayerNames() method

* AnimComposer:  add the getLayerName() method

* AnimComposer:  delete an import that's no longer needed

* AnimLayer:  rename 2 formal args per capdevon's advice

* AnimLayer:  add an omitted word to the setTime() javadoc

* replace AnimComposer.getLayerName() with AnimLayer.getName()

* AnimLayer:  instead of "real" time, call it "application" time
Stephen Gold 3 tahun lalu
induk
melakukan
853d558dee

+ 54 - 109
jme3-core/src/main/java/com/jme3/anim/AnimComposer.java

@@ -42,8 +42,6 @@ import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.control.AbstractControl;
 import com.jme3.util.clone.Cloner;
-import com.jme3.util.clone.JmeCloneable;
-
 import java.io.IOException;
 import java.util.*;
 
@@ -63,13 +61,13 @@ public class AnimComposer extends AbstractControl {
 
     private Map<String, Action> actions = new HashMap<>();
     private float globalSpeed = 1f;
-    private Map<String, Layer> layers = new LinkedHashMap<>();
+    private Map<String, AnimLayer> layers = new LinkedHashMap<>(4);
 
     /**
      * Instantiate a composer with a single layer, no actions, and no clips.
      */
     public AnimComposer() {
-        layers.put(DEFAULT_LAYER, new Layer(this));
+        layers.put(DEFAULT_LAYER, new AnimLayer(this, DEFAULT_LAYER, null));
     }
 
     /**
@@ -135,14 +133,10 @@ public class AnimComposer extends AbstractControl {
      * @return The action corresponding to the given name.
      */
     public Action setCurrentAction(String actionName, String layerName) {
-        Layer l = layers.get(layerName);
-        if (l == null) {
-            throw new IllegalArgumentException("Unknown layer " + layerName);
-        }
-        
+        AnimLayer l = getLayer(layerName);
         Action currentAction = action(actionName);
-        l.time = 0;
-        l.currentAction = currentAction;
+        l.setCurrentAction(currentAction);
+
         return currentAction;
     }
     
@@ -162,12 +156,10 @@ public class AnimComposer extends AbstractControl {
      * @return The action corresponding to the given name.
      */
     public Action getCurrentAction(String layerName) {
-        Layer l = layers.get(layerName);
-        if (l == null) {
-            throw new IllegalArgumentException("Unknown layer " + layerName);
-        }
-        
-        return l.currentAction;
+        AnimLayer l = getLayer(layerName);
+        Action result = l.getCurrentAction();
+
+        return result;
     }
     
     /**
@@ -183,13 +175,8 @@ public class AnimComposer extends AbstractControl {
      * @param layerName The name of the layer we want to remove its action.
      */
     public void removeCurrentAction(String layerName) {
-        Layer l = layers.get(layerName);
-        if (l == null) {
-            throw new IllegalArgumentException("Unknown layer " + layerName);
-        }
-        
-        l.time = 0;
-        l.currentAction = null;
+        AnimLayer l = getLayer(layerName);
+        l.setCurrentAction(null);
     }
     
     /**
@@ -208,11 +195,10 @@ public class AnimComposer extends AbstractControl {
      * @return the time (in seconds)
      */
     public double getTime(String layerName) {
-        Layer l = layers.get(layerName);
-        if (l == null) {
-            throw new IllegalArgumentException("Unknown layer " + layerName);
-        }
-        return l.time;
+        AnimLayer l = getLayer(layerName);
+        double result = l.getTime();
+
+        return result;
     }
     
     /**
@@ -231,19 +217,12 @@ public class AnimComposer extends AbstractControl {
      * @param time the desired time (in seconds)
      */
     public void setTime(String layerName, double time) {
-        Layer l = layers.get(layerName);
-        if (l == null) {
-            throw new IllegalArgumentException("Unknown layer " + layerName);
-        }
-        if (l.currentAction == null) {
+        AnimLayer l = getLayer(layerName);
+        if (l.getCurrentAction() == null) {
             throw new RuntimeException("There is no action running in layer " + layerName);
         }
-        double length = l.currentAction.getLength();
-        if (time >= 0) {
-            l.time = time % length;
-        } else {
-            l.time = time % length + length;
-        }
+
+        l.setTime(time);
     }
 
     /**
@@ -324,8 +303,7 @@ public class AnimComposer extends AbstractControl {
      * @param mask the desired mask for the new layer (alias created)
      */
     public void makeLayer(String name, AnimationMask mask) {
-        Layer l = new Layer(this);
-        l.mask = mask;
+        AnimLayer l = new AnimLayer(this, name, mask);
         layers.put(name, l);
     }
 
@@ -376,9 +354,8 @@ public class AnimComposer extends AbstractControl {
      * Reset all layers to t=0 with no current action.
      */
     public void reset() {
-        for (Layer layer : layers.values()) {
-            layer.currentAction = null;
-            layer.time = 0;
+        for (AnimLayer layer : layers.values()) {
+            layer.setCurrentAction(null);
         }
     }
 
@@ -410,20 +387,8 @@ public class AnimComposer extends AbstractControl {
      */
     @Override
     protected void controlUpdate(float tpf) {
-        for (Layer layer : layers.values()) {
-            Action currentAction = layer.currentAction;
-            if (currentAction == null) {
-                continue;
-            }
-            layer.advance(tpf);
-
-            currentAction.setMask(layer.mask);
-            boolean running = currentAction.interpolate(layer.time);
-            currentAction.setMask(null);
-
-            if (!running) {
-                layer.time = 0;
-            }
+        for (AnimLayer layer : layers.values()) {
+            layer.update(tpf);
         }
     }
 
@@ -456,6 +421,20 @@ public class AnimComposer extends AbstractControl {
         this.globalSpeed = globalSpeed;
     }
 
+    /**
+     * Provides access to the named layer.
+     *
+     * @param layerName the name of the layer to access
+     * @return the pre-existing instance
+     */
+    public AnimLayer getLayer(String layerName) {
+        AnimLayer result = layers.get(layerName);
+        if (result == null) {
+            throw new IllegalArgumentException("Unknown layer " + layerName);
+        }
+        return result;
+    }
+
     /**
      * Access the manager of the named layer.
      *
@@ -463,12 +442,20 @@ public class AnimComposer extends AbstractControl {
      * @return the current manager (typically an AnimEvent) or null for none
      */
     public Object getLayerManager(String layerName) {
-        Layer layer = layers.get(layerName);
-        if (layer == null) {
-            throw new IllegalArgumentException("Unknown layer " + layerName);
-        }
+        AnimLayer layer = getLayer(layerName);
+        Object result = layer.getManager();
 
-        return layer.manager;
+        return result;
+    }
+
+    /**
+     * Enumerates the names of all layers.
+     *
+     * @return an unmodifiable set of names
+     */
+    public Set<String> getLayerNames() {
+        Set<String> result = Collections.unmodifiableSet(layers.keySet());
+        return result;
     }
 
     /**
@@ -479,12 +466,8 @@ public class AnimComposer extends AbstractControl {
      * none
      */
     public void setLayerManager(String layerName, Object manager) {
-        Layer layer = layers.get(layerName);
-        if (layer == null) {
-            throw new IllegalArgumentException("Unknown layer " + layerName);
-        }
-
-        layer.manager = manager;
+        AnimLayer layer = getLayer(layerName);
+        layer.setManager(manager);
     }
 
     /**
@@ -525,7 +508,7 @@ public class AnimComposer extends AbstractControl {
         actions = act;
         animClipMap = clips;
 
-        Map<String, Layer> newLayers = new LinkedHashMap<>();
+        Map<String, AnimLayer> newLayers = new LinkedHashMap<>();
         for (String key : layers.keySet()) {
             newLayers.put(key, cloner.clone(layers.get(key)));
         }
@@ -564,42 +547,4 @@ public class AnimComposer extends AbstractControl {
         oc.writeStringSavableMap(animClipMap, "animClipMap", new HashMap<String, AnimClip>());
         oc.write(globalSpeed, "globalSpeed", 1f);
     }
-
-    private static class Layer implements JmeCloneable {
-        private AnimComposer ac;
-        private Action currentAction;
-        private AnimationMask mask;
-        private double time;
-        private Object manager;
-
-        public Layer(AnimComposer ac) {
-            this.ac = ac;
-        }
-        
-        public void advance(float tpf) {
-            time += tpf * currentAction.getSpeed() * ac.globalSpeed;
-            // make sure negative time is in [0, length] range
-            if (time < 0) {
-                double length = currentAction.getLength();
-                time = (time % length + length) % length;
-            }
-
-        }
-
-        @Override
-        public Object jmeClone() {
-            try {
-                Layer clone = (Layer) super.clone();
-                return clone;
-            } catch (CloneNotSupportedException ex) {
-                throw new AssertionError();
-            }
-        }
-
-        @Override
-        public void cloneFields(Cloner cloner, Object original) {
-            ac = cloner.clone(ac);
-            currentAction = null;
-        }
-    }
 }

+ 242 - 0
jme3-core/src/main/java/com/jme3/anim/AnimLayer.java

@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2009-2021 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;
+
+import com.jme3.anim.tween.action.Action;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
+
+/**
+ * A named portion of an AnimComposer that can run (at most) one Action at a
+ * time.
+ *
+ * <p>A composer with multiple layers can run multiple actions simultaneously.
+ * For instance, one layer could run a "wave" action on the model's upper body
+ * while another ran a "walk" action on the model's lower body.
+ *
+ * <p>A layer cannot be shared between multiple composers.
+ *
+ * <p>Animation time may advance at a different rate from application time,
+ * based on speedup factors in the composer and the current Action.
+ */
+public class AnimLayer implements JmeCloneable {
+    /**
+     * The Action currently running on this layer, or null if none.
+     */
+    private Action currentAction;
+    /**
+     * The composer that owns this layer. Were it not for cloning, this field
+     * would be final.
+     */
+    private AnimComposer composer;
+    /**
+     * Limits the portion of the model animated by this layer. If null, this
+     * layer can animate the entire model.
+     */
+    private final AnimationMask mask;
+    /**
+     * The current animation time, in scaled seconds. Always non-negative.
+     */
+    private double time;
+    /**
+     * The software object (such as an AnimEvent) that currently controls this
+     * layer, or null if unknown.
+     */
+    private Object manager;
+    /**
+     * The name of this layer.
+     */
+    final private String name;
+
+    /**
+     * Instantiates a layer without a manager or a current Action, owned by the
+     * specified composer.
+     *
+     * @param composer the owner (not null, alias created)
+     * @param name the layer name (not null)
+     * @param mask the AnimationMask (alias created) or null to allow this layer
+     *     to animate the entire model
+     */
+    AnimLayer(AnimComposer composer, String name, AnimationMask mask) {
+        assert composer != null;
+        this.composer = composer;
+
+        assert name != null;
+        this.name = name;
+
+        this.mask = mask;
+    }
+
+    /**
+     * Returns the Action that's currently running.
+     *
+     * @return the pre-existing instance, or null if none
+     */
+    public Action getCurrentAction() {
+        return currentAction;
+    }
+
+    /**
+     * Returns the current manager.
+     *
+     * @return the pre-existing object (such as an AnimEvent) or null for
+     *     unknown
+     */
+    public Object getManager() {
+        return manager;
+    }
+
+    /**
+     * Returns the animation mask.
+     *
+     * @return the pre-existing instance, or null if this layer can animate the
+     *     entire model
+     */
+    public AnimationMask getMask() {
+        return mask;
+    }
+
+    /**
+     * Returns the layer name.
+     *
+     * @return the name of this layer
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns the animation time, in scaled seconds.
+     *
+     * @return the current animation time (not negative)
+     */
+    public double getTime() {
+        return time;
+    }
+
+    /**
+     * Runs the specified Action, starting from time = 0. This cancels any
+     * Action previously running on this layer.
+     *
+     * @param actionToRun the Action to run (alias created) or null for no
+     *     action
+     */
+    public void setCurrentAction(Action actionToRun) {
+        this.time = 0.0;
+        this.currentAction = actionToRun;
+    }
+
+    /**
+     * Assigns the specified manager. This cancels any manager previously
+     * assigned.
+     *
+     * @param manager the desired manager (such as an AnimEvent, alias created)
+     *     or null for unknown manager
+     */
+    public void setManager(Object manager) {
+        this.manager = manager;
+    }
+
+    /**
+     * Changes the animation time, wrapping the specified time to fit the
+     * current Action. An Action must be running.
+     *
+     * @param animationTime the desired time (in scaled seconds)
+     */
+    public void setTime(double animationTime) {
+        double length = currentAction.getLength();
+        if (animationTime >= 0.0) {
+            time = animationTime % length;
+        } else {
+            time = (animationTime % length) + length;
+        }
+    }
+
+    /**
+     * Updates the animation time and the current Action during a
+     * controlUpdate().
+     *
+     * @param appDeltaTimeInSeconds the amount application time to advance the
+     *     current Action, in seconds
+     */
+    void update(float appDeltaTimeInSeconds) {
+        if (currentAction == null) {
+            return;
+        }
+
+        double speedup = currentAction.getSpeed() * composer.getGlobalSpeed();
+        double scaledDeltaTime = speedup * appDeltaTimeInSeconds;
+        time += scaledDeltaTime;
+
+        // wrap negative times to the [0, length] range:
+        if (time < 0.0) {
+            double length = currentAction.getLength();
+            time = (time % length + length) % length;
+        }
+
+        // update the current Action, filtered by this layer's mask:
+        currentAction.setMask(mask);
+        boolean running = currentAction.interpolate(time);
+        currentAction.setMask(null);
+
+        if (!running) { // went past the end of the current Action
+            time = 0.0;
+        }
+    }
+
+    /**
+     * Converts this shallow-cloned layer into a deep-cloned one, using the
+     * specified Cloner and original to resolve copied fields.
+     *
+     * <p>The clone's current Action gets nulled out. Its manager and mask get
+     * aliased to the original's manager and mask.
+     *
+     * @param cloner the Cloner that's cloning this layer (not null)
+     * @param original the instance from which this layer was shallow-cloned
+     *     (not null, unaffected)
+     */
+    @Override
+    public void cloneFields(Cloner cloner, Object original) {
+        composer = cloner.clone(composer);
+        currentAction = null;
+    }
+
+    @Override
+    public Object jmeClone() {
+        try {
+            AnimLayer clone = (AnimLayer) super.clone();
+            return clone;
+        } catch (CloneNotSupportedException exception) {
+            throw new AssertionError();
+        }
+    }
+}