Adds animation mask to the animation system
This commit is contained in:
parent
06f8a00549
commit
9df4449f49
@ -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);
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
12
jme3-core/src/main/java/com/jme3/anim/AnimationMask.java
Normal file
12
jme3-core/src/main/java/com/jme3/anim/AnimationMask.java
Normal 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);
|
||||||
|
|
||||||
|
}
|
@ -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();
|
||||||
|
62
jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java
Normal file
62
jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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");
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user