From 9df4449f49d39c9a0287c49f073b7e8cce66b862 Mon Sep 17 00:00:00 2001 From: Nehon Date: Mon, 2 Apr 2018 09:08:16 +0200 Subject: [PATCH] Adds animation mask to the animation system --- .../src/main/java/com/jme3/anim/AnimClip.java | 5 ++ .../main/java/com/jme3/anim/AnimComposer.java | 78 ++++++++++++++++--- .../java/com/jme3/anim/AnimationMask.java | 12 +++ .../src/main/java/com/jme3/anim/Armature.java | 3 + .../main/java/com/jme3/anim/ArmatureMask.java | 62 +++++++++++++++ .../src/main/java/com/jme3/anim/Joint.java | 9 +++ .../jme3/anim/MatrixJointModelTransform.java | 5 +- .../com/jme3/anim/tween/action/Action.java | 10 +++ .../jme3/anim/tween/action/ClipAction.java | 10 ++- .../model/anim/TestAnimMigration.java | 16 +++- 10 files changed, 195 insertions(+), 15 deletions(-) create mode 100644 jme3-core/src/main/java/com/jme3/anim/AnimationMask.java create mode 100644 jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimClip.java b/jme3-core/src/main/java/com/jme3/anim/AnimClip.java index 7f8c022c5..9b86858a3 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimClip.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimClip.java @@ -66,6 +66,11 @@ public class AnimClip implements JmeCloneable, Savable { this.tracks = newTracks; } + @Override + public String toString() { + return "Clip " + name + ", " + length + 's'; + } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java index 73bdd4b4b..4b115037b 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java @@ -8,6 +8,7 @@ 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.*; @@ -17,12 +18,16 @@ import java.util.*; */ public class AnimComposer extends AbstractControl { + public static final String DEFAULT_LAYER = "Default"; private Map animClipMap = new HashMap<>(); - private Action currentAction; private Map actions = new HashMap<>(); private float globalSpeed = 1f; - private float time; + private Map layers = new LinkedHashMap<>(); + + public AnimComposer() { + layers.put(DEFAULT_LAYER, new Layer()); + } /** * Retrieve an animation from the list of animations. @@ -60,8 +65,17 @@ public class AnimComposer extends AbstractControl { } public Action setCurrentAction(String name) { - currentAction = action(name); - time = 0; + return setCurrentAction(name, DEFAULT_LAYER); + } + + public Action setCurrentAction(String actionName, String layerName) { + Layer l = layers.get(layerName); + if (l == null) { + throw new IllegalArgumentException("Unknown layer " + layerName); + } + Action currentAction = action(actionName); + l.time = 0; + l.currentAction = currentAction; return currentAction; } @@ -84,6 +98,12 @@ public class AnimComposer extends AbstractControl { return action; } + public void makeLayer(String name, AnimationMask mask){ + Layer l = new Layer(); + l.mask = mask; + layers.put(name, l); + } + public BaseAction actionSequence(String name, Tween... tweens) { BaseAction action = new BaseAction(Tweens.sequence(tweens)); @@ -103,8 +123,10 @@ public class AnimComposer extends AbstractControl { } public void reset() { - currentAction = null; - time = 0; + for (Layer layer : layers.values()) { + layer.currentAction = null; + layer.time = 0; + } } public Collection getAnimClips() { @@ -117,11 +139,17 @@ public class AnimComposer extends AbstractControl { @Override protected void controlUpdate(float tpf) { - if (currentAction != null) { - time += tpf; - boolean running = currentAction.interpolate(time * globalSpeed); + for (Layer layer : layers.values()) { + Action currentAction = layer.currentAction; + if (currentAction == null) { + continue; + } + layer.time += tpf; + currentAction.setMask(layer.mask); + boolean running = currentAction.interpolate(layer.time * globalSpeed); + currentAction.setMask(null); if (!running) { - time = 0; + layer.time = 0; } } } @@ -162,6 +190,14 @@ public class AnimComposer extends AbstractControl { } actions = act; animClipMap = clips; + + Map newLayers = new LinkedHashMap<>(); + for (String key : layers.keySet()) { + newLayers.put(key, cloner.clone(layers.get(key))); + } + + layers = newLayers; + } @Override @@ -177,4 +213,26 @@ public class AnimComposer extends AbstractControl { OutputCapsule oc = ex.getCapsule(this); oc.writeStringSavableMap(animClipMap, "animClipMap", new HashMap()); } + + public static class Layer implements JmeCloneable { + private Action currentAction; + private AnimationMask mask; + private float weight; + private float time; + + @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) { + currentAction = null; + } + } } diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java b/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java new file mode 100644 index 000000000..2aa8328b6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java @@ -0,0 +1,12 @@ +package com.jme3.anim; + +/** + * Created by Nehon + * An AnimationMask is defining a subset of elements on which an animation will be applied. + * Most used implementation is the ArmatureMask that defines a subset of joints in an Armature. + */ +public interface AnimationMask { + + boolean contains(Object target); + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/anim/Armature.java b/jme3-core/src/main/java/com/jme3/anim/Armature.java index 39a664ede..ad7f5b0ac 100644 --- a/jme3-core/src/main/java/com/jme3/anim/Armature.java +++ b/jme3-core/src/main/java/com/jme3/anim/Armature.java @@ -46,6 +46,7 @@ public class Armature implements JmeCloneable, Savable { List rootJointList = new ArrayList<>(); for (int i = jointList.length - 1; i >= 0; i--) { Joint joint = jointList[i]; + joint.setId(i); instanciateJointModelTransform(joint); if (joint.getParent() == null) { rootJointList.add(joint); @@ -276,7 +277,9 @@ public class Armature implements JmeCloneable, Savable { throw new AssetLoadException("Cannnot find class for name " + className); } + int i = 0; for (Joint joint : jointList) { + joint.setId(i++); instanciateJointModelTransform(joint); } createSkinningMatrices(); diff --git a/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java b/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java new file mode 100644 index 000000000..b492eb4ed --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java @@ -0,0 +1,62 @@ +package com.jme3.anim; + +import java.util.BitSet; + +public class ArmatureMask implements AnimationMask { + + private BitSet affectedJoints = new BitSet(); + + @Override + public boolean contains(Object target) { + return affectedJoints.get(((Joint) target).getId()); + } + + public static ArmatureMask createMask(Armature armature, String fromJoint) { + ArmatureMask mask = new ArmatureMask(); + mask.addFromJoint(armature, fromJoint); + return mask; + } + + public static ArmatureMask createMask(Armature armature, String... joints) { + ArmatureMask mask = new ArmatureMask(); + mask.addBones(armature, joints); + for (String joint : joints) { + mask.affectedJoints.set(armature.getJoint(joint).getId()); + } + return mask; + } + + /** + * Add joints to be influenced by this animation mask. + */ + public void addBones(Armature armature, String... jointNames) { + for (String jointName : jointNames) { + Joint joint = findJoint(armature, jointName); + affectedJoints.set(joint.getId()); + } + } + + private Joint findJoint(Armature armature, String jointName) { + Joint joint = armature.getJoint(jointName); + if (joint == null) { + throw new IllegalArgumentException("Cannot find joint " + jointName); + } + return joint; + } + + /** + * Add a joint and all its sub armature joints to be influenced by this animation mask. + */ + public void addFromJoint(Armature armature, String jointName) { + Joint joint = findJoint(armature, jointName); + recurseAddJoint(joint); + } + + private void recurseAddJoint(Joint joint) { + affectedJoints.set(joint.getId()); + for (Joint j : joint.getChildren()) { + recurseAddJoint(j); + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/anim/Joint.java b/jme3-core/src/main/java/com/jme3/anim/Joint.java index f4c0a0d1b..bf672fa29 100644 --- a/jme3-core/src/main/java/com/jme3/anim/Joint.java +++ b/jme3-core/src/main/java/com/jme3/anim/Joint.java @@ -22,6 +22,7 @@ import java.util.List; public class Joint implements Savable, JmeCloneable, HasLocalTransform { private String name; + private int id; private Joint parent; private SafeArrayList children = new SafeArrayList<>(Joint.class); private Geometry targetGeometry; @@ -280,6 +281,14 @@ public class Joint implements Savable, JmeCloneable, HasLocalTransform { return inverseModelBindMatrix; } + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + @Override public Object jmeClone() { try { diff --git a/jme3-core/src/main/java/com/jme3/anim/MatrixJointModelTransform.java b/jme3-core/src/main/java/com/jme3/anim/MatrixJointModelTransform.java index 27964f325..5f1aadad7 100644 --- a/jme3-core/src/main/java/com/jme3/anim/MatrixJointModelTransform.java +++ b/jme3-core/src/main/java/com/jme3/anim/MatrixJointModelTransform.java @@ -19,11 +19,11 @@ public class MatrixJointModelTransform implements JointModelTransform { if (parent != null) { ((MatrixJointModelTransform) parent.getJointModelTransform()).getModelTransformMatrix().mult(modelTransformMatrix, modelTransformMatrix); } - modelTransform.fromTransformMatrix(modelTransformMatrix); + } public void getOffsetTransform(Matrix4f outTransform, Matrix4f inverseModelBindMatrix) { - outTransform.set(modelTransformMatrix).mult(inverseModelBindMatrix, outTransform); + modelTransformMatrix.mult(inverseModelBindMatrix, outTransform); } @Override @@ -41,6 +41,7 @@ public class MatrixJointModelTransform implements JointModelTransform { @Override public Transform getModelTransform() { + modelTransform.fromTransformMatrix(modelTransformMatrix); return modelTransform; } } diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java index 25379bf92..85bd9ae01 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java @@ -1,5 +1,6 @@ package com.jme3.anim.tween.action; +import com.jme3.anim.AnimationMask; import com.jme3.anim.tween.Tween; public abstract class Action implements Tween { @@ -7,6 +8,7 @@ public abstract class Action implements Tween { protected Action[] actions; private double length; private double speed = 1; + private AnimationMask mask; protected Action(Tween... tweens) { this.actions = new Action[tweens.length]; @@ -46,4 +48,12 @@ public abstract class Action implements Tween { public void setSpeed(double speed) { this.speed = speed; } + + public AnimationMask getMask() { + return mask; + } + + public void setMask(AnimationMask mask) { + this.mask = mask; + } } diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java index 59d24261f..9f0d49f20 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java @@ -25,7 +25,11 @@ public class ClipAction extends BlendableAction { AnimTrack[] tracks = clip.getTracks(); for (AnimTrack track : tracks) { if (track instanceof TransformTrack) { - interpolateTransformTrack(t, (TransformTrack) track); + TransformTrack tt = (TransformTrack) track; + if(getMask() != null && !getMask().contains(tt.getTarget())){ + continue; + } + interpolateTransformTrack(t, tt); } else if (track instanceof MorphTrack) { interpolateMorphTrack(t, (MorphTrack) track); } @@ -60,6 +64,10 @@ public class ClipAction extends BlendableAction { } + public String toString() { + return clip.toString(); + } + @Override public Collection getTargets() { List targets = new ArrayList<>(clip.getTracks().length); diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java index cc90a6053..df19c652e 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java @@ -1,7 +1,6 @@ package jme3test.model.anim; -import com.jme3.anim.AnimComposer; -import com.jme3.anim.SkinningControl; +import com.jme3.anim.*; import com.jme3.anim.tween.action.Action; import com.jme3.anim.tween.action.BlendAction; import com.jme3.anim.tween.action.BlendableAction; @@ -125,8 +124,19 @@ public class TestAnimMigration extends SimpleApplication { } }, "toggleArmature"); + inputManager.addMapping("mask", new KeyTrigger(KeyInput.KEY_M)); + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed) { + composer.setCurrentAction("Wave", "LeftArm"); + } + } + }, "mask"); + inputManager.addMapping("blendUp", new KeyTrigger(KeyInput.KEY_UP)); inputManager.addMapping("blendDown", new KeyTrigger(KeyInput.KEY_DOWN)); + inputManager.addListener(new AnalogListener() { @Override @@ -174,6 +184,8 @@ public class TestAnimMigration extends SimpleApplication { composer.action("Walk").setSpeed(-1); + composer.makeLayer("LeftArm", ArmatureMask.createMask(sc.getArmature(), "shoulder.L")); + anims.addFirst("Sequence"); anims.addFirst("Blend");