Better blending structure

monkanim
Rémy Bouquet 7 years ago committed by Nehon
parent a8845a1506
commit ce88350abf
  1. 40
      jme3-core/src/main/java/com/jme3/anim/AnimComposer.java
  2. 87
      jme3-core/src/main/java/com/jme3/anim/tween/AnimClipTween.java
  3. 6
      jme3-core/src/main/java/com/jme3/anim/tween/ContainsTweens.java
  4. 30
      jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java
  5. 39
      jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java
  6. 40
      jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java
  7. 129
      jme3-core/src/main/java/com/jme3/anim/tween/action/BlendAction.java
  8. 83
      jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java
  9. 64
      jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java
  10. 14
      jme3-core/src/main/java/com/jme3/anim/tween/action/LinearBlendSpace.java
  11. 75
      jme3-core/src/main/java/com/jme3/anim/tween/action/SequenceAction.java
  12. 10
      jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java

@ -1,11 +1,8 @@
package com.jme3.anim;
import com.jme3.anim.tween.AnimClipTween;
import com.jme3.anim.tween.Tween;
import com.jme3.anim.tween.action.Action;
import com.jme3.anim.tween.action.BlendAction;
import com.jme3.anim.tween.action.BlendSpace;
import com.jme3.anim.tween.action.SequenceAction;
import com.jme3.anim.tween.Tweens;
import com.jme3.anim.tween.action.*;
import com.jme3.export.*;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
@ -62,40 +59,37 @@ public class AnimComposer extends AbstractControl {
}
public void setCurrentAction(String name) {
Action action = action(name);
if (currentAction != null) {
currentAction.reset();
}
currentAction = action;
currentAction = action(name);
time = 0;
}
public Action action(String name) {
Action action = actions.get(name);
if (action == null) {
AnimClipTween tween = tweenFromClip(name);
action = new SequenceAction(tween);
AnimClip clip = animClipMap.get(name);
if (clip == null) {
throw new IllegalArgumentException("Cannot find clip named " + name);
}
action = new ClipAction(clip);
actions.put(name, action);
}
return action;
}
public AnimClipTween tweenFromClip(String clipName) {
AnimClip clip = animClipMap.get(clipName);
if (clip == null) {
throw new IllegalArgumentException("Cannot find clip named " + clipName);
}
return new AnimClipTween(clip);
}
public SequenceAction actionSequence(String name, Tween... tweens) {
SequenceAction action = new SequenceAction(tweens);
public BaseAction actionSequence(String name, Tween... tweens) {
BaseAction action = new BaseAction(Tweens.sequence(tweens));
actions.put(name, action);
return action;
}
public BlendAction actionBlended(String name, BlendSpace blendSpace, Tween... tweens) {
BlendAction action = new BlendAction(blendSpace, tweens);
public BlendAction actionBlended(String name, BlendSpace blendSpace, String... clips) {
BlendableAction[] acts = new BlendableAction[clips.length];
for (int i = 0; i < acts.length; i++) {
BlendableAction ba = (BlendableAction) action(clips[i]);
acts[i] = ba;
}
BlendAction action = new BlendAction(blendSpace, acts);
actions.put(name, action);
return action;
}

@ -1,87 +0,0 @@
package com.jme3.anim.tween;
import com.jme3.anim.AnimClip;
import com.jme3.anim.TransformTrack;
import com.jme3.anim.tween.action.Action;
import com.jme3.anim.util.HasLocalTransform;
import com.jme3.anim.util.Weighted;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.Transform;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
public class AnimClipTween implements Tween, Weighted, JmeCloneable {
private AnimClip clip;
private Transform transform = new Transform();
private float weight = 1f;
private Action parentAction;
public AnimClipTween() {
}
public AnimClipTween(AnimClip clip) {
this.clip = clip;
}
@Override
public double getLength() {
return clip.getLength();
}
@Override
public boolean interpolate(double t) {
// Sanity check the inputs
if (t < 0) {
return true;
}
if (parentAction != null) {
weight = parentAction.getWeightForTween(this);
}
if (weight == 0) {
//weight is 0 let's not interpolate
return t < clip.getLength();
}
TransformTrack[] tracks = clip.getTracks();
for (TransformTrack track : tracks) {
HasLocalTransform target = track.getTarget();
transform.set(target.getLocalTransform());
track.getTransformAtTime(t, transform);
if (weight == 1f) {
target.setLocalTransform(transform);
} else {
Transform tr = target.getLocalTransform();
tr.interpolateTransforms(tr, transform, weight);
target.setLocalTransform(tr);
}
}
return t < clip.getLength();
}
@Override
public Object jmeClone() {
try {
AnimClipTween clone = (AnimClipTween) super.clone();
return clone;
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
@Override
public void cloneFields(Cloner cloner, Object original) {
clip = cloner.clone(clip);
}
@Override
public void setParentAction(Action action) {
this.parentAction = action;
}
}

@ -0,0 +1,6 @@
package com.jme3.anim.tween;
public interface ContainsTweens {
public Tween[] getTweens();
}

@ -221,7 +221,7 @@ public class Tweens {
}
}
private static class Sequence implements Tween {
private static class Sequence implements Tween, ContainsTweens {
private final Tween[] delegates;
private int current = 0;
private double baseTime;
@ -279,9 +279,14 @@ public class Tweens {
public String toString() {
return getClass().getSimpleName() + "[delegates=" + Arrays.asList(delegates) + "]";
}
@Override
public Tween[] getTweens() {
return delegates;
}
}
private static class Parallel implements Tween {
private static class Parallel implements Tween, ContainsTweens {
private final Tween[] delegates;
private final boolean[] done;
private double length;
@ -343,6 +348,11 @@ public class Tweens {
public String toString() {
return getClass().getSimpleName() + "[delegates=" + Arrays.asList(delegates) + "]";
}
@Override
public Tween[] getTweens() {
return delegates;
}
}
private static class Delay extends AbstractTween {
@ -356,14 +366,15 @@ public class Tweens {
}
}
private static class Stretch implements Tween {
private static class Stretch implements Tween, ContainsTweens {
private final Tween delegate;
private final Tween[] delegate = new Tween[1];
private final double length;
private final double scale;
public Stretch(Tween delegate, double length) {
this.delegate = delegate;
this.delegate[0] = delegate;
this.length = length;
// Caller desires delegate to be 'length' instead of
@ -382,6 +393,11 @@ public class Tweens {
return length;
}
@Override
public Tween[] getTweens() {
return delegate;
}
@Override
public boolean interpolate(double t) {
if (t < 0) {
@ -392,12 +408,12 @@ public class Tweens {
} else {
t = length;
}
return delegate.interpolate(t);
return delegate[0].interpolate(t);
}
@Override
public String toString() {
return getClass().getSimpleName() + "[delegate=" + delegate + ", length=" + length + "]";
return getClass().getSimpleName() + "[delegate=" + delegate[0] + ", length=" + length + "]";
}
}

@ -1,23 +1,21 @@
package com.jme3.anim.tween.action;
import com.jme3.anim.tween.Tween;
import com.jme3.anim.util.Weighted;
import com.jme3.export.*;
import java.io.IOException;
public abstract class Action implements Tween {
public abstract class Action implements Tween, Weighted {
protected Tween[] tweens;
protected Action[] actions;
protected float weight = 1;
protected double length;
protected Action parentAction;
protected Action(Tween... tweens) {
this.tweens = tweens;
for (Tween tween : tweens) {
if (tween instanceof Weighted) {
((Weighted) tween).setParentAction(this);
this.actions = new Action[tweens.length];
for (int i = 0; i < tweens.length; i++) {
Tween tween = tweens[i];
if (tween instanceof Action) {
this.actions[i] = (Action) tween;
} else {
this.actions[i] = new BaseAction(tween);
}
}
}
@ -27,23 +25,8 @@ public abstract class Action implements Tween, Weighted {
return length;
}
@Override
public boolean interpolate(double t) {
if (parentAction != null) {
weight = parentAction.getWeightForTween(this);
public void setWeight(float weight) {
this.weight = weight;
}
return doInterpolate(t);
}
public abstract float getWeightForTween(Tween tween);
public abstract boolean doInterpolate(double t);
public abstract void reset();
@Override
public void setParentAction(Action parentAction) {
this.parentAction = parentAction;
}
}

@ -0,0 +1,40 @@
package com.jme3.anim.tween.action;
import com.jme3.anim.tween.ContainsTweens;
import com.jme3.anim.tween.Tween;
import com.jme3.util.SafeArrayList;
public class BaseAction extends Action {
private Tween tween;
private SafeArrayList<Action> subActions = new SafeArrayList<>(Action.class);
public BaseAction(Tween tween) {
this.tween = tween;
length = tween.getLength();
gatherActions(tween);
}
private void gatherActions(Tween tween) {
if (tween instanceof Action) {
subActions.add((Action) tween);
} else if (tween instanceof ContainsTweens) {
Tween[] tweens = ((ContainsTweens) tween).getTweens();
for (Tween t : tweens) {
gatherActions(t);
}
}
}
@Override
public void setWeight(float weight) {
for (Action action : subActions.getArray()) {
action.setWeight(weight);
}
}
@Override
public boolean interpolate(double t) {
return tween.interpolate(t);
}
}

@ -1,80 +1,129 @@
package com.jme3.anim.tween.action;
import com.jme3.anim.tween.Tween;
import com.jme3.anim.tween.Tweens;
import com.jme3.anim.util.HasLocalTransform;
import com.jme3.math.Transform;
public class BlendAction extends Action {
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class BlendAction extends BlendableAction {
private Tween firstActiveTween;
private Tween secondActiveTween;
private int firstActiveIndex;
private int secondActiveIndex;
private BlendSpace blendSpace;
private float blendWeight;
private double[] timeFactor;
private Map<HasLocalTransform, Transform> targetMap = new HashMap<>();
public BlendAction(BlendSpace blendSpace, Tween... tweens) {
super(tweens);
public BlendAction(BlendSpace blendSpace, BlendableAction... actions) {
super(actions);
timeFactor = new double[actions.length];
this.blendSpace = blendSpace;
blendSpace.setBlendAction(this);
for (Tween tween : tweens) {
if (tween.getLength() > length) {
length = tween.getLength();
for (BlendableAction action : actions) {
if (action.getLength() > length) {
length = action.getLength();
}
Collection<HasLocalTransform> targets = action.getTargets();
for (HasLocalTransform target : targets) {
Transform t = targetMap.get(target);
if (t == null) {
t = new Transform();
targetMap.put(target, t);
}
}
}
//Blending effect maybe unexpected when blended animation don't have the same length
//Stretching any tween that doesn't have the same length.
for (int i = 0; i < tweens.length; i++) {
if (tweens[i].getLength() != length) {
tweens[i] = Tweens.stretch(length, tweens[i]);
//Stretching any action that doesn't have the same length.
for (int i = 0; i < this.actions.length; i++) {
this.timeFactor[i] = 1;
if (this.actions[i].getLength() != length) {
double actionLength = this.actions[i].getLength();
if (actionLength > 0 && length > 0) {
this.timeFactor[i] = this.actions[i].getLength() / length;
}
}
}
}
@Override
public float getWeightForTween(Tween tween) {
public void doInterpolate(double t) {
blendWeight = blendSpace.getWeight();
if (tween == firstActiveTween) {
return 1f;
BlendableAction firstActiveAction = (BlendableAction) actions[firstActiveIndex];
BlendableAction secondActiveAction = (BlendableAction) actions[secondActiveIndex];
firstActiveAction.setCollectTransformDelegate(this);
secondActiveAction.setCollectTransformDelegate(this);
//only interpolate the first action if the weight if below 1.
if (blendWeight < 1f) {
firstActiveAction.setWeight(1f);
firstActiveAction.interpolate(t * timeFactor[firstActiveIndex]);
if (blendWeight == 0) {
for (HasLocalTransform target : targetMap.keySet()) {
collect(target, targetMap.get(target));
}
return weight * blendWeight;
}
@Override
public boolean doInterpolate(double t) {
if (firstActiveTween == null) {
blendSpace.getWeight();
}
boolean running = this.firstActiveTween.interpolate(t);
this.secondActiveTween.interpolate(t);
//Second action should be interpolated
secondActiveAction.setWeight(blendWeight);
secondActiveAction.interpolate(t * timeFactor[secondActiveIndex]);
firstActiveAction.setCollectTransformDelegate(null);
secondActiveAction.setCollectTransformDelegate(null);
if (!running) {
return false;
}
return true;
protected Action[] getActions() {
return actions;
}
@Override
public void reset() {
public BlendSpace getBlendSpace() {
return blendSpace;
}
protected void setFirstActiveIndex(int index) {
this.firstActiveIndex = index;
}
protected Tween[] getTweens() {
return tweens;
protected void setSecondActiveIndex(int index) {
this.secondActiveIndex = index;
}
public BlendSpace getBlendSpace() {
return blendSpace;
@Override
public Collection<HasLocalTransform> getTargets() {
return targetMap.keySet();
}
protected void setFirstActiveTween(Tween firstActiveTween) {
this.firstActiveTween = firstActiveTween;
@Override
public void collectTransform(HasLocalTransform target, Transform t, float weight, BlendableAction source) {
Transform tr = targetMap.get(target);
if (weight == 1) {
tr.set(t);
} else if (weight > 0) {
tr.interpolateTransforms(tr, t, weight);
}
protected void setSecondActiveTween(Tween secondActiveTween) {
this.secondActiveTween = secondActiveTween;
if (source == actions[secondActiveIndex]) {
collect(target, tr);
}
}
private void collect(HasLocalTransform target, Transform tr) {
if (collectTransformDelegate != null) {
collectTransformDelegate.collectTransform(target, tr, this.weight, this);
} else {
if (getTransitionWeight() == 1) {
target.setLocalTransform(tr);
} else {
Transform trans = target.getLocalTransform();
trans.interpolateTransforms(trans, tr, getTransitionWeight());
target.setLocalTransform(trans);
}
}
}
}

@ -0,0 +1,83 @@
package com.jme3.anim.tween.action;
import com.jme3.anim.tween.AbstractTween;
import com.jme3.anim.tween.Tween;
import com.jme3.anim.util.HasLocalTransform;
import com.jme3.math.Transform;
import java.util.Collection;
public abstract class BlendableAction extends Action {
protected BlendableAction collectTransformDelegate;
private float transitionWeight = 1.0f;
private double transitionLength = 0.4f;
private TransitionTween transition = new TransitionTween(transitionLength);
public BlendableAction(Tween... tweens) {
super(tweens);
}
public void setCollectTransformDelegate(BlendableAction delegate) {
this.collectTransformDelegate = delegate;
}
@Override
public boolean interpolate(double t) {
// Sanity check the inputs
if (t < 0) {
return true;
}
if (collectTransformDelegate == null) {
if (transition.getLength() > getLength()) {
transition.setLength(getLength());
}
transition.interpolate(t);
} else {
transitionWeight = 1f;
}
if (weight == 0) {
//weight is 0 let's not interpolate
return t < getLength();
}
doInterpolate(t);
return t < getLength();
}
protected abstract void doInterpolate(double t);
public abstract Collection<HasLocalTransform> getTargets();
public abstract void collectTransform(HasLocalTransform target, Transform t, float weight, BlendableAction source);
public double getTransitionLength() {
return transitionLength;
}
public void setTransitionLength(double transitionLength) {
this.transitionLength = transitionLength;
}
protected float getTransitionWeight() {
return transitionWeight;
}
private class TransitionTween extends AbstractTween {
public TransitionTween(double length) {
super(length);
}
@Override
protected void doInterpolate(double t) {
transitionWeight = (float) t;
}
}
}

@ -0,0 +1,64 @@
package com.jme3.anim.tween.action;
import com.jme3.anim.AnimClip;
import com.jme3.anim.TransformTrack;
import com.jme3.anim.tween.AbstractTween;
import com.jme3.anim.util.HasLocalTransform;
import com.jme3.math.Transform;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class ClipAction extends BlendableAction {
private AnimClip clip;
private Transform transform = new Transform();
public ClipAction(AnimClip clip) {
this.clip = clip;
length = clip.getLength();
}
@Override
public void doInterpolate(double t) {
TransformTrack[] tracks = clip.getTracks();
for (TransformTrack track : tracks) {
HasLocalTransform target = track.getTarget();
transform.set(target.getLocalTransform());
track.getTransformAtTime(t, transform);
if (collectTransformDelegate != null) {
collectTransformDelegate.collectTransform(target, transform, weight, this);
} else {
this.collectTransform(target, transform, getTransitionWeight(), this);
}
}
}
public void reset() {
}
@Override
public Collection<HasLocalTransform> getTargets() {
List<HasLocalTransform> targets = new ArrayList<>(clip.getTracks().length);
for (TransformTrack track : clip.getTracks()) {
targets.add(track.getTarget());
}
return targets;
}
@Override
public void collectTransform(HasLocalTransform target, Transform t, float weight, BlendableAction source) {
if (weight == 1f) {
target.setLocalTransform(t);
} else {
Transform tr = target.getLocalTransform();
tr.interpolateTransforms(tr, t, weight);
target.setLocalTransform(tr);
}
}
}

@ -1,7 +1,5 @@
package com.jme3.anim.tween.action;
import com.jme3.anim.tween.Tween;
public class LinearBlendSpace implements BlendSpace {
private BlendAction action;
@ -17,24 +15,24 @@ public class LinearBlendSpace implements BlendSpace {
@Override
public void setBlendAction(BlendAction action) {
this.action = action;
Tween[] tweens = action.getTweens();
step = maxValue / (float) (tweens.length - 1);
Action[] actions = action.getActions();
step = maxValue / (float) (actions.length - 1);
}
@Override
public float getWeight() {
Tween[] tweens = action.getTweens();
Action[] actions = action.getActions();
float lowStep = 0, highStep = 0;
int lowIndex = 0, highIndex = 0;
for (int i = 0; i < tweens.length && highStep < value; i++) {
for (int i = 0; i < actions.length && highStep < value; i++) {
lowStep = highStep;
lowIndex = i;
highStep += step;
}
highIndex = lowIndex + 1;
action.setFirstActiveTween(tweens[lowIndex]);
action.setSecondActiveTween(tweens[highIndex]);
action.setFirstActiveIndex(lowIndex);
action.setSecondActiveIndex(highIndex);
if (highStep == lowStep) {
return 0;

@ -1,75 +0,0 @@
package com.jme3.anim.tween.action;
import com.jme3.anim.tween.AbstractTween;
import com.jme3.anim.tween.Tween;
public class SequenceAction extends Action {
private int currentIndex = 0;
private double accumTime;
private double transitionTime = 0;
private float mainWeight = 1.0f;
private double transitionLength = 0.4f;
private TransitionTween transition = new TransitionTween(transitionLength);
public SequenceAction(Tween... tweens) {
super(tweens);
for (Tween tween : tweens) {
length += tween.getLength();
}
}
@Override
public float getWeightForTween(Tween tween) {
return weight * mainWeight;
}
@Override
public boolean doInterpolate(double t) {
Tween currentTween = tweens[currentIndex];
if (transition.getLength() > currentTween.getLength()) {
transition.setLength(currentTween.getLength());
}
transition.interpolate(t - transitionTime);
boolean running = currentTween.interpolate(t - accumTime);
if (!running) {
accumTime += currentTween.getLength();
currentIndex++;
transitionTime = accumTime;
transition.setLength(transitionLength);
}
if (t >= length) {
reset();
return false;
}
return true;
}
public void reset() {
currentIndex = 0;
accumTime = 0;
transitionTime = 0;
mainWeight = 1;
}
public void setTransitionLength(double transitionLength) {
this.transitionLength = transitionLength;
}
private class TransitionTween extends AbstractTween {
public TransitionTween(double length) {
super(length);
}
@Override
protected void doInterpolate(double t) {
mainWeight = (float) t;
}
}
}

@ -3,6 +3,7 @@ package jme3test.model.anim;
import com.jme3.anim.AnimComposer;
import com.jme3.anim.SkinningControl;
import com.jme3.anim.tween.action.BlendAction;
import com.jme3.anim.tween.action.BlendableAction;
import com.jme3.anim.tween.action.LinearBlendSpace;
import com.jme3.anim.util.AnimMigrationUtils;
import com.jme3.app.ChaseCameraAppState;
@ -159,13 +160,12 @@ public class TestAnimMigration extends SimpleApplication {
anims.add(name);
}
composer.actionSequence("Sequence",
composer.tweenFromClip("Walk"),
composer.tweenFromClip("Run"),
composer.tweenFromClip("Jumping"));
composer.action("Walk"),
composer.action("Run"),
composer.action("Jumping"));
action = composer.actionBlended("Blend", new LinearBlendSpace(4),
composer.tweenFromClip("Walk"),
composer.tweenFromClip("Jumping"));
"Walk", "Punches", "Jumping", "Taunt");
action.getBlendSpace().setValue(2);

Loading…
Cancel
Save