Adds animation mask to the animation system

This commit is contained in:
Nehon 2018-04-02 09:08:16 +02:00
parent 06f8a00549
commit 9df4449f49
10 changed files with 195 additions and 15 deletions

View File

@ -66,6 +66,11 @@ public class AnimClip implements JmeCloneable, Savable {
this.tracks = newTracks; this.tracks = newTracks;
} }
@Override
public String toString() {
return "Clip " + name + ", " + length + 's';
}
@Override @Override
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this); OutputCapsule oc = ex.getCapsule(this);

View File

@ -8,6 +8,7 @@ import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort; import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl; import com.jme3.scene.control.AbstractControl;
import com.jme3.util.clone.Cloner; import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
@ -17,12 +18,16 @@ import java.util.*;
*/ */
public class AnimComposer extends AbstractControl { public class AnimComposer extends AbstractControl {
public static final String DEFAULT_LAYER = "Default";
private Map<String, AnimClip> animClipMap = new HashMap<>(); private Map<String, AnimClip> animClipMap = new HashMap<>();
private Action currentAction;
private Map<String, Action> actions = new HashMap<>(); private Map<String, Action> actions = new HashMap<>();
private float globalSpeed = 1f; private float globalSpeed = 1f;
private float time; private Map<String, Layer> layers = new LinkedHashMap<>();
public AnimComposer() {
layers.put(DEFAULT_LAYER, new Layer());
}
/** /**
* Retrieve an animation from the list of animations. * Retrieve an animation from the list of animations.
@ -60,8 +65,17 @@ public class AnimComposer extends AbstractControl {
} }
public Action setCurrentAction(String name) { public Action setCurrentAction(String name) {
currentAction = action(name); return setCurrentAction(name, DEFAULT_LAYER);
time = 0; }
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; return currentAction;
} }
@ -84,6 +98,12 @@ public class AnimComposer extends AbstractControl {
return action; 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) { public BaseAction actionSequence(String name, Tween... tweens) {
BaseAction action = new BaseAction(Tweens.sequence(tweens)); BaseAction action = new BaseAction(Tweens.sequence(tweens));
@ -103,8 +123,10 @@ public class AnimComposer extends AbstractControl {
} }
public void reset() { public void reset() {
currentAction = null; for (Layer layer : layers.values()) {
time = 0; layer.currentAction = null;
layer.time = 0;
}
} }
public Collection<AnimClip> getAnimClips() { public Collection<AnimClip> getAnimClips() {
@ -117,11 +139,17 @@ public class AnimComposer extends AbstractControl {
@Override @Override
protected void controlUpdate(float tpf) { protected void controlUpdate(float tpf) {
if (currentAction != null) { for (Layer layer : layers.values()) {
time += tpf; Action currentAction = layer.currentAction;
boolean running = currentAction.interpolate(time * globalSpeed); if (currentAction == null) {
continue;
}
layer.time += tpf;
currentAction.setMask(layer.mask);
boolean running = currentAction.interpolate(layer.time * globalSpeed);
currentAction.setMask(null);
if (!running) { if (!running) {
time = 0; layer.time = 0;
} }
} }
} }
@ -162,6 +190,14 @@ public class AnimComposer extends AbstractControl {
} }
actions = act; actions = act;
animClipMap = clips; animClipMap = clips;
Map<String, Layer> newLayers = new LinkedHashMap<>();
for (String key : layers.keySet()) {
newLayers.put(key, cloner.clone(layers.get(key)));
}
layers = newLayers;
} }
@Override @Override
@ -177,4 +213,26 @@ public class AnimComposer extends AbstractControl {
OutputCapsule oc = ex.getCapsule(this); OutputCapsule oc = ex.getCapsule(this);
oc.writeStringSavableMap(animClipMap, "animClipMap", new HashMap<String, AnimClip>()); oc.writeStringSavableMap(animClipMap, "animClipMap", new HashMap<String, AnimClip>());
} }
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;
}
}
} }

View File

@ -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);
}

View File

@ -46,6 +46,7 @@ public class Armature implements JmeCloneable, Savable {
List<Joint> rootJointList = new ArrayList<>(); List<Joint> rootJointList = new ArrayList<>();
for (int i = jointList.length - 1; i >= 0; i--) { for (int i = jointList.length - 1; i >= 0; i--) {
Joint joint = jointList[i]; Joint joint = jointList[i];
joint.setId(i);
instanciateJointModelTransform(joint); instanciateJointModelTransform(joint);
if (joint.getParent() == null) { if (joint.getParent() == null) {
rootJointList.add(joint); rootJointList.add(joint);
@ -276,7 +277,9 @@ public class Armature implements JmeCloneable, Savable {
throw new AssetLoadException("Cannnot find class for name " + className); throw new AssetLoadException("Cannnot find class for name " + className);
} }
int i = 0;
for (Joint joint : jointList) { for (Joint joint : jointList) {
joint.setId(i++);
instanciateJointModelTransform(joint); instanciateJointModelTransform(joint);
} }
createSkinningMatrices(); createSkinningMatrices();

View File

@ -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);
}
}
}

View File

@ -22,6 +22,7 @@ import java.util.List;
public class Joint implements Savable, JmeCloneable, HasLocalTransform { public class Joint implements Savable, JmeCloneable, HasLocalTransform {
private String name; private String name;
private int id;
private Joint parent; private Joint parent;
private SafeArrayList<Joint> children = new SafeArrayList<>(Joint.class); private SafeArrayList<Joint> children = new SafeArrayList<>(Joint.class);
private Geometry targetGeometry; private Geometry targetGeometry;
@ -280,6 +281,14 @@ public class Joint implements Savable, JmeCloneable, HasLocalTransform {
return inverseModelBindMatrix; return inverseModelBindMatrix;
} }
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override @Override
public Object jmeClone() { public Object jmeClone() {
try { try {

View File

@ -19,11 +19,11 @@ public class MatrixJointModelTransform implements JointModelTransform {
if (parent != null) { if (parent != null) {
((MatrixJointModelTransform) parent.getJointModelTransform()).getModelTransformMatrix().mult(modelTransformMatrix, modelTransformMatrix); ((MatrixJointModelTransform) parent.getJointModelTransform()).getModelTransformMatrix().mult(modelTransformMatrix, modelTransformMatrix);
} }
modelTransform.fromTransformMatrix(modelTransformMatrix);
} }
public void getOffsetTransform(Matrix4f outTransform, Matrix4f inverseModelBindMatrix) { public void getOffsetTransform(Matrix4f outTransform, Matrix4f inverseModelBindMatrix) {
outTransform.set(modelTransformMatrix).mult(inverseModelBindMatrix, outTransform); modelTransformMatrix.mult(inverseModelBindMatrix, outTransform);
} }
@Override @Override
@ -41,6 +41,7 @@ public class MatrixJointModelTransform implements JointModelTransform {
@Override @Override
public Transform getModelTransform() { public Transform getModelTransform() {
modelTransform.fromTransformMatrix(modelTransformMatrix);
return modelTransform; return modelTransform;
} }
} }

View File

@ -1,5 +1,6 @@
package com.jme3.anim.tween.action; package com.jme3.anim.tween.action;
import com.jme3.anim.AnimationMask;
import com.jme3.anim.tween.Tween; import com.jme3.anim.tween.Tween;
public abstract class Action implements Tween { public abstract class Action implements Tween {
@ -7,6 +8,7 @@ public abstract class Action implements Tween {
protected Action[] actions; protected Action[] actions;
private double length; private double length;
private double speed = 1; private double speed = 1;
private AnimationMask mask;
protected Action(Tween... tweens) { protected Action(Tween... tweens) {
this.actions = new Action[tweens.length]; this.actions = new Action[tweens.length];
@ -46,4 +48,12 @@ public abstract class Action implements Tween {
public void setSpeed(double speed) { public void setSpeed(double speed) {
this.speed = speed; this.speed = speed;
} }
public AnimationMask getMask() {
return mask;
}
public void setMask(AnimationMask mask) {
this.mask = mask;
}
} }

View File

@ -25,7 +25,11 @@ public class ClipAction extends BlendableAction {
AnimTrack[] tracks = clip.getTracks(); AnimTrack[] tracks = clip.getTracks();
for (AnimTrack track : tracks) { for (AnimTrack track : tracks) {
if (track instanceof TransformTrack) { 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) { } else if (track instanceof MorphTrack) {
interpolateMorphTrack(t, (MorphTrack) track); interpolateMorphTrack(t, (MorphTrack) track);
} }
@ -60,6 +64,10 @@ public class ClipAction extends BlendableAction {
} }
public String toString() {
return clip.toString();
}
@Override @Override
public Collection<HasLocalTransform> getTargets() { public Collection<HasLocalTransform> getTargets() {
List<HasLocalTransform> targets = new ArrayList<>(clip.getTracks().length); List<HasLocalTransform> targets = new ArrayList<>(clip.getTracks().length);

View File

@ -1,7 +1,6 @@
package jme3test.model.anim; package jme3test.model.anim;
import com.jme3.anim.AnimComposer; import com.jme3.anim.*;
import com.jme3.anim.SkinningControl;
import com.jme3.anim.tween.action.Action; import com.jme3.anim.tween.action.Action;
import com.jme3.anim.tween.action.BlendAction; import com.jme3.anim.tween.action.BlendAction;
import com.jme3.anim.tween.action.BlendableAction; import com.jme3.anim.tween.action.BlendableAction;
@ -125,8 +124,19 @@ public class TestAnimMigration extends SimpleApplication {
} }
}, "toggleArmature"); }, "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("blendUp", new KeyTrigger(KeyInput.KEY_UP));
inputManager.addMapping("blendDown", new KeyTrigger(KeyInput.KEY_DOWN)); inputManager.addMapping("blendDown", new KeyTrigger(KeyInput.KEY_DOWN));
inputManager.addListener(new AnalogListener() { inputManager.addListener(new AnalogListener() {
@Override @Override
@ -174,6 +184,8 @@ public class TestAnimMigration extends SimpleApplication {
composer.action("Walk").setSpeed(-1); composer.action("Walk").setSpeed(-1);
composer.makeLayer("LeftArm", ArmatureMask.createMask(sc.getArmature(), "shoulder.L"));
anims.addFirst("Sequence"); anims.addFirst("Sequence");
anims.addFirst("Blend"); anims.addFirst("Blend");