Added the base of the blending system. Smooth transition between anims

monkanim
Rémy Bouquet 7 years ago committed by Nehon
parent dd5b90e281
commit 449429974e
  1. 59
      jme3-core/src/main/java/com/jme3/anim/AnimClip.java
  2. 59
      jme3-core/src/main/java/com/jme3/anim/AnimComposer.java
  3. 3
      jme3-core/src/main/java/com/jme3/anim/Joint.java
  4. 108
      jme3-core/src/main/java/com/jme3/anim/JointTrack.java
  5. 111
      jme3-core/src/main/java/com/jme3/anim/SpatialTrack.java
  6. 46
      jme3-core/src/main/java/com/jme3/anim/TransformTrack.java
  7. 99
      jme3-core/src/main/java/com/jme3/anim/tween/AnimClipTween.java
  8. 64
      jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java
  9. 75
      jme3-core/src/main/java/com/jme3/anim/tween/action/SequenceAction.java
  10. 25
      jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java
  11. 10
      jme3-core/src/main/java/com/jme3/anim/util/HasLocalTransform.java
  12. 11
      jme3-core/src/main/java/com/jme3/anim/util/Weighted.java
  13. 2
      jme3-core/src/main/java/com/jme3/math/Transform.java
  14. 3
      jme3-core/src/main/java/com/jme3/scene/Spatial.java
  15. 4
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/Dashed100.frag
  16. 2
      jme3-examples/src/main/java/jme3test/export/TestOgreConvert.java
  17. 8
      jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java
  18. 30
      jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java
  19. 6
      jme3-examples/src/main/java/jme3test/model/anim/TestAnimSerialization.java
  20. 12
      jme3-examples/src/main/java/jme3test/model/anim/TestArmature.java
  21. 12
      jme3-examples/src/main/java/jme3test/model/anim/TestBaseAnimSerialization.java
  22. 8
      jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinning.java
  23. 6
      jme3-examples/src/main/java/jme3test/model/anim/TestModelExportingCloning.java
  24. 17
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
  25. 24
      jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SkeletonLoader.java

@ -11,12 +11,12 @@ import java.io.IOException;
/**
* Created by Nehon on 20/12/2017.
*/
public class AnimClip implements Tween, JmeCloneable, Savable {
public class AnimClip implements JmeCloneable, Savable {
private String name;
private double length;
private SafeArrayList<Tween> tracks = new SafeArrayList<>(Tween.class);
private TransformTrack[] tracks;
public AnimClip() {
}
@ -25,52 +25,27 @@ public class AnimClip implements Tween, JmeCloneable, Savable {
this.name = name;
}
public void setTracks(Tween[] tracks) {
for (Tween track : tracks) {
addTrack(track);
}
}
public void addTrack(Tween track) {
tracks.add(track);
public void setTracks(TransformTrack[] tracks) {
this.tracks = tracks;
for (TransformTrack track : tracks) {
if (track.getLength() > length) {
length = track.getLength();
}
}
public void removeTrack(Tween track) {
if (tracks.remove(track)) {
length = 0;
for (Tween t : tracks.getArray()) {
if (t.getLength() > length) {
length = t.getLength();
}
}
}
}
public String getName() {
return name;
}
@Override
public double getLength() {
return length;
}
@Override
public boolean interpolate(double t) {
// Sanity check the inputs
if (t < 0) {
return true;
}
for (Tween track : tracks.getArray()) {
if (t <= track.getLength()) {
track.interpolate(t);
}
}
return t <= length;
public TransformTrack[] getTracks() {
return tracks;
}
@Override
@ -84,9 +59,9 @@ public class AnimClip implements Tween, JmeCloneable, Savable {
@Override
public void cloneFields(Cloner cloner, Object original) {
SafeArrayList<Tween> newTracks = new SafeArrayList<>(Tween.class);
for (Tween track : tracks) {
newTracks.add(cloner.clone(track));
TransformTrack[] newTracks = new TransformTrack[tracks.length];
for (int i = 0; i < tracks.length; i++) {
newTracks[i] = (cloner.clone(tracks[i]));
}
this.tracks = newTracks;
}
@ -95,7 +70,7 @@ public class AnimClip implements Tween, JmeCloneable, Savable {
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
oc.write(name, "name", null);
oc.write(tracks.getArray(), "tracks", null);
oc.write(tracks, "tracks", null);
}
@ -105,9 +80,13 @@ public class AnimClip implements Tween, JmeCloneable, Savable {
name = ic.readString("name", null);
Savable[] arr = ic.readSavableArray("tracks", null);
if (arr != null) {
tracks = new SafeArrayList<>(Tween.class);
for (Savable savable : arr) {
addTrack((Tween) savable);
tracks = new TransformTrack[arr.length];
for (int i = 0; i < arr.length; i++) {
TransformTrack t = (TransformTrack) arr[i];
tracks[i] = t;
if (t.getLength() > length) {
length = t.getLength();
}
}
}
}

@ -1,5 +1,9 @@
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.SequenceAction;
import com.jme3.export.*;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
@ -16,7 +20,8 @@ public class AnimComposer extends AbstractControl {
private Map<String, AnimClip> animClipMap = new HashMap<>();
private AnimClip currentAnimClip;
private Action currentAction;
private Map<String, Action> actions = new HashMap<>();
private float time;
/**
@ -54,16 +59,45 @@ public class AnimComposer extends AbstractControl {
animClipMap.remove(anim.getName());
}
public void setCurrentAnimClip(String name) {
currentAnimClip = animClipMap.get(name);
public void setCurrentAction(String name) {
Action action = action(name);
if (currentAction != null) {
currentAction.reset();
}
currentAction = action;
time = 0;
if (currentAnimClip == null) {
throw new IllegalArgumentException("Unknown clip " + name);
}
public Action action(String name) {
Action action = actions.get(name);
if (action == null) {
AnimClipTween tween = tweenFromClip(name);
action = new SequenceAction(tween);
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);
actions.put(name, action);
return action;
}
public Action actionBlended(String name, Tween... tweens) {
return null;
}
public void reset() {
currentAnimClip = null;
currentAction = null;
time = 0;
}
@ -77,11 +111,11 @@ public class AnimComposer extends AbstractControl {
@Override
protected void controlUpdate(float tpf) {
if (currentAnimClip != null) {
if (currentAction != null) {
time += tpf;
boolean running = currentAnimClip.interpolate(time);
boolean running = currentAction.interpolate(time);
if (!running) {
time -= currentAnimClip.getLength();
time -= currentAction.getLength();
}
}
}
@ -108,6 +142,11 @@ public class AnimComposer extends AbstractControl {
for (String key : animClipMap.keySet()) {
clips.put(key, cloner.clone(animClipMap.get(key)));
}
Map<String, Action> act = new HashMap<>();
for (String key : actions.keySet()) {
act.put(key, cloner.clone(actions.get(key)));
}
actions = act;
animClipMap = clips;
}
@ -116,6 +155,7 @@ public class AnimComposer extends AbstractControl {
super.read(im);
InputCapsule ic = im.getCapsule(this);
animClipMap = (Map<String, AnimClip>) ic.readStringSavableMap("animClipMap", new HashMap<String, AnimClip>());
actions = (Map<String, Action>) ic.readStringSavableMap("actions", new HashMap<String, Action>());
}
@Override
@ -123,5 +163,6 @@ public class AnimComposer extends AbstractControl {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.writeStringSavableMap(animClipMap, "animClipMap", new HashMap<String, AnimClip>());
oc.writeStringSavableMap(actions, "actions", new HashMap<String, Action>());
}
}

@ -1,5 +1,6 @@
package com.jme3.anim;
import com.jme3.anim.util.HasLocalTransform;
import com.jme3.anim.util.JointModelTransform;
import com.jme3.export.*;
import com.jme3.material.MatParamOverride;
@ -18,7 +19,7 @@ import java.util.List;
* A Joint is the basic component of an armature designed to perform skeletal animation
* Created by Nehon on 15/12/2017.
*/
public class Joint implements Savable, JmeCloneable {
public class Joint implements Savable, JmeCloneable, HasLocalTransform {
private String name;
private Joint parent;

@ -1,108 +0,0 @@
/*
* Copyright (c) 2009-2012 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.export.*;
import com.jme3.math.*;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
* Contains a list of transforms and times for each keyframe.
*
* @author Rémy Bouquet
*/
public final class JointTrack extends TransformTrack implements JmeCloneable, Savable {
private Joint target;
/**
* Serialization-only. Do not use.
*/
public JointTrack() {
super();
}
/**
* Creates a joint track for the given joint index
*
* @param target The Joint target of this track
* @param times a float array with the time of each frame
* @param translations the translation of the bone for each frame
* @param rotations the rotation of the bone for each frame
* @param scales the scale of the bone for each frame
*/
public JointTrack(Joint target, float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
super(times, translations, rotations, scales);
this.target = target;
this.defaultTransform = target.getLocalTransform();
}
@Override
public boolean interpolate(double t) {
boolean running = super.interpolate(t);
Transform transform = getInterpolatedTransform();
target.setLocalTransform(transform);
return running;
}
public void setTarget(Joint target) {
this.target = target;
}
@Override
public Object jmeClone() {
return super.clone();
}
@Override
public void cloneFields(Cloner cloner, Object original) {
this.target = cloner.clone(target);
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(target, "target", null);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
target = (Joint) ic.readSavable("target", null);
}
}

@ -1,111 +0,0 @@
/*
* Copyright (c) 2009-2012 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.export.*;
import com.jme3.math.*;
import com.jme3.scene.Spatial;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
* Contains a list of transforms and times for each keyframe.
*
* @author Rémy Bouquet
*/
public final class SpatialTrack extends TransformTrack implements JmeCloneable, Savable {
private Spatial target;
/**
* Serialization-only. Do not use.
*/
public SpatialTrack() {
super();
}
/**
* Creates a spatial track for the given Spatial
*
* @param target The Spatial target of this track
* @param times a float array with the time of each frame
* @param translations the translation of the bone for each frame
* @param rotations the rotation of the bone for each frame
* @param scales the scale of the bone for each frame
*/
public SpatialTrack(Spatial target, float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
super(times, translations, rotations, scales);
this.target = target;
defaultTransform = target.getLocalTransform();
}
@Override
public boolean interpolate(double t) {
boolean running = super.interpolate(t);
Transform transform = getInterpolatedTransform();
target.setLocalTransform(transform);
return running;
}
public void setTarget(Spatial target) {
this.target = target;
}
@Override
public Object jmeClone() {
return super.clone();
}
@Override
public void cloneFields(Cloner cloner, Object original) {
this.target = cloner.clone(target);
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(target, "target", null);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
target = (Spatial) ic.readSavable("target", null);
}
}

@ -33,10 +33,13 @@ package com.jme3.anim;
import com.jme3.anim.interpolator.FrameInterpolator;
import com.jme3.anim.tween.Tween;
import com.jme3.anim.util.HasLocalTransform;
import com.jme3.animation.CompactQuaternionArray;
import com.jme3.animation.CompactVector3Array;
import com.jme3.export.*;
import com.jme3.math.*;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
@ -45,9 +48,10 @@ import java.io.IOException;
*
* @author Rémy Bouquet
*/
public abstract class TransformTrack implements Tween, Cloneable, Savable {
public class TransformTrack implements JmeCloneable, Savable {
private double length;
private HasLocalTransform target;
/**
* Transforms and times for track.
@ -55,8 +59,6 @@ public abstract class TransformTrack implements Tween, Cloneable, Savable {
private CompactVector3Array translations;
private CompactQuaternionArray rotations;
private CompactVector3Array scales;
private Transform transform = new Transform();
protected Transform defaultTransform = new Transform();
private FrameInterpolator interpolator = FrameInterpolator.DEFAULT;
private float[] times;
@ -74,7 +76,8 @@ public abstract class TransformTrack implements Tween, Cloneable, Savable {
* @param rotations the rotation of the bone for each frame
* @param scales the scale of the bone for each frame
*/
public TransformTrack(float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
public TransformTrack(HasLocalTransform target, float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
this.target = target;
this.setKeyframes(times, translations, rotations, scales);
}
@ -216,16 +219,13 @@ public abstract class TransformTrack implements Tween, Cloneable, Savable {
}
}
@Override
public double getLength() {
return length;
}
@Override
public boolean interpolate(double t) {
public void getTransformAtTime(double t, Transform transform) {
float time = (float) t;
transform.set(defaultTransform);
int lastFrame = times.length - 1;
if (time < 0 || lastFrame == 0) {
if (translations != null) {
@ -237,7 +237,7 @@ public abstract class TransformTrack implements Tween, Cloneable, Savable {
if (scales != null) {
scales.get(0, transform.getScale());
}
return true;
return;
}
int startFrame = 0;
@ -272,17 +272,18 @@ public abstract class TransformTrack implements Tween, Cloneable, Savable {
if (scales != null) {
transform.setScale(interpolated.getScale());
}
return time < length;
}
public void setFrameInterpolator(FrameInterpolator interpolator) {
this.interpolator = interpolator;
}
public Transform getInterpolatedTransform() {
return transform;
public HasLocalTransform getTarget() {
return target;
}
public void setFrameInterpolator(FrameInterpolator interpolator) {
this.interpolator = interpolator;
public void setTarget(HasLocalTransform target) {
this.target = target;
}
@Override
@ -292,6 +293,7 @@ public abstract class TransformTrack implements Tween, Cloneable, Savable {
oc.write(rotations, "rotations", null);
oc.write(times, "times", null);
oc.write(scales, "scales", null);
oc.write(target, "target", null);
}
@Override
@ -301,16 +303,22 @@ public abstract class TransformTrack implements Tween, Cloneable, Savable {
rotations = (CompactQuaternionArray) ic.readSavable("rotations", null);
times = ic.readFloatArray("times", null);
scales = (CompactVector3Array) ic.readSavable("scales", null);
target = (Joint) ic.readSavable("target", null);
setTimes(times);
}
@Override
public Object clone() {
public Object jmeClone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException("Error cloning", e);
TransformTrack clone = (TransformTrack) super.clone();
return clone;
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
@Override
public void cloneFields(Cloner cloner, Object original) {
this.target = cloner.clone(target);
}
}

@ -0,0 +1,99 @@
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);
}
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 void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
oc.write(clip, "clip", null);
}
@Override
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
clip = (AnimClip) ic.readSavable("clip", null);
}
@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 setWeight(float weight) {
// this.weight = weight;
// }
@Override
public void setParentAction(Action action) {
this.parentAction = action;
}
}

@ -0,0 +1,64 @@
package com.jme3.anim.tween.action;
import com.jme3.anim.tween.Tween;
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 java.io.IOException;
public abstract class Action implements Tween, Weighted {
protected Tween[] tweens;
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);
}
}
}
@Override
public double getLength() {
return length;
}
@Override
public boolean interpolate(double t) {
if (parentAction != null) {
weight = parentAction.getWeightForTween(this);
}
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;
}
@Override
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
tweens = (Tween[]) ic.readSavableArray("tweens", null);
}
@Override
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
oc.write(tweens, "tweens", null);
}
}

@ -0,0 +1,75 @@
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;
}
}
}

@ -61,10 +61,14 @@ public class AnimMigrationUtils {
armature.setBindPose();
skeletonArmatureMap.put(skeleton, armature);
List<TransformTrack> tracks = new ArrayList<>();
for (String animName : control.getAnimationNames()) {
tracks.clear();
Animation anim = control.getAnim(animName);
AnimClip clip = new AnimClip(animName);
Joint[] staticJoints = new Joint[joints.length];
System.arraycopy(joints, 0, staticJoints, 0, joints.length);
for (Track track : anim.getTracks()) {
if (track instanceof BoneTrack) {
@ -72,17 +76,20 @@ public class AnimMigrationUtils {
int index = boneTrack.getTargetBoneIndex();
Bone bone = skeleton.getBone(index);
Joint joint = joints[index];
JointTrack jointTrack = fromBoneTrack(boneTrack, bone, joint);
clip.addTrack(jointTrack);
TransformTrack jointTrack = fromBoneTrack(boneTrack, bone, joint);
tracks.add(jointTrack);
//this joint is animated let's remove it from the static joints
staticJoints[index] = null;
}
//TODO spatial tracks , Effect tracks, Audio tracks
}
for (int i = 0; i < staticJoints.length; i++) {
padJointTracks(clip, staticJoints[i]);
padJointTracks(tracks, staticJoints[i]);
}
clip.setTracks(tracks.toArray(new TransformTrack[tracks.size()]));
composer.addAnimClip(clip);
}
spatial.removeControl(control);
@ -95,7 +102,7 @@ public class AnimMigrationUtils {
}
}
public static void padJointTracks(AnimClip clip, Joint staticJoint) {
public static void padJointTracks(List<TransformTrack> tracks, Joint staticJoint) {
Joint j = staticJoint;
if (j != null) {
// joint has no track , we create one with the default pose
@ -103,8 +110,8 @@ public class AnimMigrationUtils {
Vector3f[] translations = new Vector3f[]{j.getLocalTranslation()};
Quaternion[] rotations = new Quaternion[]{j.getLocalRotation()};
Vector3f[] scales = new Vector3f[]{j.getLocalScale()};
JointTrack track = new JointTrack(j, times, translations, rotations, scales);
clip.addTrack(track);
TransformTrack track = new TransformTrack(j, times, translations, rotations, scales);
tracks.add(track);
}
}
@ -144,7 +151,7 @@ public class AnimMigrationUtils {
}
}
public static JointTrack fromBoneTrack(BoneTrack boneTrack, Bone bone, Joint joint) {
public static TransformTrack fromBoneTrack(BoneTrack boneTrack, Bone bone, Joint joint) {
float[] times = new float[boneTrack.getTimes().length];
int length = times.length;
System.arraycopy(boneTrack.getTimes(), 0, times, 0, length);
@ -178,8 +185,8 @@ public class AnimMigrationUtils {
scales[i] = newScale;
}
}
return new JointTrack(joint, times, translations, rotations, scales);
TransformTrack t = new TransformTrack(joint, times, translations, rotations, scales);
return t;
}
private static Joint fromBone(Bone b) {

@ -0,0 +1,10 @@
package com.jme3.anim.util;
import com.jme3.export.Savable;
import com.jme3.math.Transform;
public interface HasLocalTransform extends Savable {
public void setLocalTransform(Transform transform);
public Transform getLocalTransform();
}

@ -0,0 +1,11 @@
package com.jme3.anim.util;
import com.jme3.anim.tween.action.Action;
import com.jme3.math.Transform;
public interface Weighted {
// public void setWeight(float weight);
public void setParentAction(Action action);
}

@ -176,7 +176,7 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
}
/**
* Sets this matrix to the interpolation between the first matrix and the second by delta amount.
* Sets this transform to the interpolation between the first transform and the second by delta amount.
* @param t1 The beginning transform.
* @param t2 The ending transform.
* @param delta An amount between 0 and 1 representing how far to interpolate from t1 to t2.

@ -31,6 +31,7 @@
*/
package com.jme3.scene;
import com.jme3.anim.util.HasLocalTransform;
import com.jme3.asset.AssetKey;
import com.jme3.asset.CloneableSmartAsset;
import com.jme3.bounding.BoundingVolume;
@ -67,7 +68,7 @@ import java.util.logging.Logger;
* @author Joshua Slack
* @version $Revision: 4075 $, $Data$
*/
public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset, JmeCloneable {
public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset, JmeCloneable, HasLocalTransform {
private static final Logger logger = Logger.getLogger(Spatial.class.getName());

@ -2,8 +2,8 @@ void main(){
startPos.xy = (startPos * 0.5 + 0.5).xy * resolution;
float len = distance(gl_FragCoord.xy,startPos.xy);
outColor = inColor;
float factor = int(len * 0.25);
if(mod(factor, 2) > 0.0){
float factor = float(int(len * 0.25));
if(mod(factor, 2.0) > 0.0){
discard;
}

@ -71,7 +71,7 @@ public class TestOgreConvert extends SimpleApplication {
Node ogreModelReloaded = (Node) imp.load(bais, null, null);
AnimComposer composer = ogreModelReloaded.getControl(AnimComposer.class);
composer.setCurrentAnimClip("Walk");
composer.setCurrentAction("Walk");
rootNode.attachChild(ogreModelReloaded);
} catch (IOException ex){

@ -116,9 +116,9 @@ public class TestGltfLoading extends SimpleApplication {
//loadModel("Models/gltf/elephant/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
//loadModel("Models/gltf/buffalo/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
//loadModel("Models/gltf/war/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
loadModel("Models/gltf/ganjaarl/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
//loadModel("Models/gltf/ganjaarl/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
//loadModel("Models/gltf/hero/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
//loadModel("Models/gltf/mercy/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
loadModel("Models/gltf/mercy/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
//loadModel("Models/gltf/crab/scene.gltf", Vector3f.ZERO, 1);
//loadModel("Models/gltf/manta/scene.gltf", Vector3f.ZERO, 0.2f);
//loadModel("Models/gltf/bone/scene.gltf", Vector3f.ZERO, 0.1f);
@ -190,7 +190,7 @@ public class TestGltfLoading extends SimpleApplication {
if (isPressed && composer != null) {
String anim = anims.poll();
anims.add(anim);
composer.setCurrentAnimClip(anim);
composer.setCurrentAction(anim);
}
}
}, "nextAnim");
@ -262,7 +262,7 @@ public class TestGltfLoading extends SimpleApplication {
}
String anim = anims.poll();
anims.add(anim);
control.setCurrentAnimClip(anim);
control.setCurrentAction(anim);
composer = control;
}
if (s instanceof Node) {

@ -2,6 +2,7 @@ package jme3test.model.anim;
import com.jme3.anim.AnimComposer;
import com.jme3.anim.SkinningControl;
import com.jme3.anim.util.AnimMigrationUtils;
import com.jme3.app.ChaseCameraAppState;
import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput;
@ -24,8 +25,8 @@ public class TestAnimMigration extends SimpleApplication {
ArmatureDebugAppState debugAppState;
AnimComposer composer;
Queue<String> anims = new LinkedList<>();
boolean playAnim = true;
LinkedList<String> anims = new LinkedList<>();
boolean playAnim = false;
public static void main(String... argv) {
TestAnimMigration app = new TestAnimMigration();
@ -40,12 +41,12 @@ public class TestAnimMigration extends SimpleApplication {
rootNode.addLight(new DirectionalLight(new Vector3f(-1, -1, -1).normalizeLocal()));
rootNode.addLight(new AmbientLight(ColorRGBA.DarkGray));
//Spatial model = assetManager.loadModel("Models/Jaime/Jaime.j3o");
Spatial model = assetManager.loadModel("Models/Oto/Oto.mesh.xml").scale(0.2f).move(0, 1, 0);
Spatial model = assetManager.loadModel("Models/Jaime/Jaime.j3o");
// Spatial model = assetManager.loadModel("Models/Oto/Oto.mesh.xml").scale(0.2f).move(0, 1, 0);
//Spatial model = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");
//Spatial model = assetManager.loadModel("Models/Elephant/Elephant.mesh.xml").scale(0.02f);
// AnimMigrationUtils.migrate(model);
AnimMigrationUtils.migrate(model);
rootNode.attachChild(model);
@ -87,7 +88,7 @@ public class TestAnimMigration extends SimpleApplication {
if (playAnim) {
String anim = anims.poll();
anims.add(anim);
composer.setCurrentAnimClip(anim);
composer.setCurrentAction(anim);
System.err.println(anim);
} else {
composer.reset();
@ -102,7 +103,7 @@ public class TestAnimMigration extends SimpleApplication {
if (isPressed && composer != null) {
String anim = anims.poll();
anims.add(anim);
composer.setCurrentAnimClip(anim);
composer.setCurrentAction(anim);
System.err.println(anim);
}
}
@ -132,13 +133,26 @@ public class TestAnimMigration extends SimpleApplication {
for (String name : composer.getAnimClipsNames()) {
anims.add(name);
}
composer.actionSequence("Sequence",
composer.tweenFromClip("Walk"),
composer.tweenFromClip("Run"),
composer.tweenFromClip("Jumping"));
// composer.actionSequence("Sequence",
// composer.tweenFromClip("Walk"),
// composer.tweenFromClip("Dodge"),
// composer.tweenFromClip("push"));
anims.addFirst("Sequence");
if (anims.isEmpty()) {
return;
}
if (playAnim) {
String anim = anims.poll();
anims.add(anim);
composer.setCurrentAnimClip(anim);
composer.setCurrentAction(anim);
System.err.println(anim);
}

@ -102,7 +102,7 @@ public class TestAnimSerialization extends SimpleApplication {
if (playAnim) {
String anim = anims.poll();
anims.add(anim);
composer.setCurrentAnimClip(anim);
composer.setCurrentAction(anim);
System.err.println(anim);
} else {
composer.reset();
@ -117,7 +117,7 @@ public class TestAnimSerialization extends SimpleApplication {
if (isPressed && composer != null) {
String anim = anims.poll();
anims.add(anim);
composer.setCurrentAnimClip(anim);
composer.setCurrentAction(anim);
System.err.println(anim);
}
}
@ -144,7 +144,7 @@ public class TestAnimSerialization extends SimpleApplication {
if (playAnim) {
String anim = anims.poll();
anims.add(anim);
composer.setCurrentAnimClip(anim);
composer.setCurrentAction(anim);
System.err.println(anim);
}

@ -78,10 +78,10 @@ public class TestArmature extends SimpleApplication {
new Vector3f(1, 1, 1),
};
JointTrack track1 = new JointTrack(j1, times, null, rotations, scales);
JointTrack track2 = new JointTrack(j2, times, null, rotations, null);
clip.addTrack(track1);
clip.addTrack(track2);
TransformTrack track1 = new TransformTrack(j1, times, null, rotations, scales);
TransformTrack track2 = new TransformTrack(j2, times, null, rotations, null);
clip.setTracks(new TransformTrack[]{track1, track2});
//create the animComposer control
final AnimComposer composer = new AnimComposer();
@ -103,7 +103,7 @@ public class TestArmature extends SimpleApplication {
node.addControl(composer);
node.addControl(ac);
composer.setCurrentAnimClip("anim");
composer.setCurrentAction("anim");
ArmatureDebugAppState debugAppState = new ArmatureDebugAppState();
debugAppState.addArmatureFrom(ac);
@ -134,7 +134,7 @@ public class TestArmature extends SimpleApplication {
armature.resetToBindPose();
} else {
composer.setCurrentAnimClip("anim");
composer.setCurrentAction("anim");
}
}
}, "bind");

@ -85,10 +85,10 @@ public class TestBaseAnimSerialization extends SimpleApplication {
new Vector3f(1, 1, 1),
};
JointTrack track1 = new JointTrack(j1, times, null, rotations, scales);
JointTrack track2 = new JointTrack(j2, times, null, rotations, null);
clip.addTrack(track1);
clip.addTrack(track2);
TransformTrack track1 = new TransformTrack(j1, times, null, rotations, scales);
TransformTrack track2 = new TransformTrack(j2, times, null, rotations, null);
clip.setTracks(new TransformTrack[]{track1, track2});
//create the animComposer control
composer = new AnimComposer();
@ -125,7 +125,7 @@ public class TestBaseAnimSerialization extends SimpleApplication {
ac = newNode.getControl(SkinningControl.class);
ac.setHardwareSkinningPreferred(false);
armature = ac.getArmature();
composer.setCurrentAnimClip("anim");
composer.setCurrentAction("anim");
ArmatureDebugAppState debugAppState = new ArmatureDebugAppState();
debugAppState.addArmatureFrom(ac);
@ -156,7 +156,7 @@ public class TestBaseAnimSerialization extends SimpleApplication {
armature.resetToBindPose();
} else {
composer.setCurrentAnimClip("anim");
composer.setCurrentAction("anim");
}
}
}, "bind");

@ -48,9 +48,9 @@ import java.util.List;
public class TestHWSkinning extends SimpleApplication implements ActionListener{
private AnimComposer composer;
// private AnimComposer composer;
private String[] animNames = {"Dodge", "Walk", "pull", "push"};
private final static int SIZE = 50;
private final static int SIZE = 40;
private boolean hwSkinningEnable = true;
private List<SkinningControl> skControls = new ArrayList<SkinningControl>();
private BitmapText hwsText;
@ -80,9 +80,9 @@ public class TestHWSkinning extends SimpleApplication implements ActionListener{
Spatial model = (Spatial) assetManager.loadModel("Models/Oto/Oto.mesh.xml");
model.setLocalScale(0.1f);
model.setLocalTranslation(i - SIZE / 2, 0, j - SIZE / 2);
composer = model.getControl(AnimComposer.class);
AnimComposer composer = model.getControl(AnimComposer.class);
composer.setCurrentAnimClip(animNames[(i + j) % 4]);
composer.setCurrentAction(animNames[(i + j) % 4]);
SkinningControl skinningControl = model.getControl(SkinningControl.class);
skinningControl.setHardwareSkinningPreferred(hwSkinningEnable);
skControls.add(skinningControl);

@ -60,19 +60,19 @@ public class TestModelExportingCloning extends SimpleApplication {
Spatial originalModel = assetManager.loadModel("Models/Oto/Oto.mesh.xml");
composer = originalModel.getControl(AnimComposer.class);
composer.setCurrentAnimClip("Walk");
composer.setCurrentAction("Walk");
rootNode.attachChild(originalModel);
Spatial clonedModel = originalModel.clone();
clonedModel.move(10, 0, 0);
composer = clonedModel.getControl(AnimComposer.class);
composer.setCurrentAnimClip("push");
composer.setCurrentAction("push");
rootNode.attachChild(clonedModel);
Spatial exportedModel = BinaryExporter.saveAndLoad(assetManager, originalModel);
exportedModel.move(20, 0, 0);
composer = exportedModel.getControl(AnimComposer.class);
composer.setCurrentAnimClip("pull");
composer.setCurrentAction("pull");
rootNode.attachChild(exportedModel);
}
}

@ -793,6 +793,7 @@ public class GltfLoader implements AssetLoader {
List<Spatial> spatials = new ArrayList<>();
AnimClip anim = new AnimClip(name);
List<TransformTrack> ttracks = new ArrayList<>();
int skinIndex = -1;
List<Joint> usedJoints = new ArrayList<>();
@ -806,8 +807,8 @@ public class GltfLoader implements AssetLoader {
if (node instanceof Spatial) {
Spatial s = (Spatial) node;
spatials.add(s);
SpatialTrack track = new SpatialTrack(s, trackData.times, trackData.translations, trackData.rotations, trackData.scales);
anim.addTrack(track);
TransformTrack track = new TransformTrack(s, trackData.times, trackData.translations, trackData.rotations, trackData.scales);
ttracks.add(track);
} else if (node instanceof JointWrapper) {
JointWrapper jw = (JointWrapper) node;
usedJoints.add(jw.joint);
@ -822,8 +823,8 @@ public class GltfLoader implements AssetLoader {
}
}
JointTrack track = new JointTrack(jw.joint, trackData.times, trackData.translations, trackData.rotations, trackData.scales);
anim.addTrack(track);
TransformTrack track = new TransformTrack(jw.joint, trackData.times, trackData.translations, trackData.rotations, trackData.scales);
ttracks.add(track);
}
}
@ -834,19 +835,21 @@ public class GltfLoader implements AssetLoader {
if (skinIndex != -1) {
SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class);
for (Joint joint : skin.joints) {
if (!usedJoints.contains(joint)) {// && !equalBindAndLocalTransforms(joint)
if (!usedJoints.contains(joint)) {
//create a track
float[] times = new float[]{0};
Vector3f[] translations = new Vector3f[]{joint.getLocalTranslation()};
Quaternion[] rotations = new Quaternion[]{joint.getLocalRotation()};
Vector3f[] scales = new Vector3f[]{joint.getLocalScale()};
JointTrack track = new JointTrack(joint, times, translations, rotations, scales);
anim.addTrack(track);
TransformTrack track = new TransformTrack(joint, times, translations, rotations, scales);
ttracks.add(track);
}
}
}
anim.setTracks(ttracks.toArray(new TransformTrack[ttracks.size()]));
anim = customContentManager.readExtensionAndExtras("animations", animation, anim);
if (skinIndex != -1) {

@ -54,16 +54,16 @@ public class SkeletonLoader extends DefaultHandler implements AssetLoader {
private Stack<String> elementStack = new Stack<String>();
private HashMap<Integer, Joint> indexToJoint = new HashMap<>();
private HashMap<String, Joint> nameToJoint = new HashMap<>();
private JointTrack track;
private ArrayList<JointTrack> tracks = new ArrayList<>();
private TransformTrack track;
private ArrayList<TransformTrack> tracks = new ArrayList<>();
private AnimClip animClip;
private ArrayList<AnimClip> animClips;
private Joint joint;
private Armature armature;
private ArrayList<Float> times = new ArrayList<Float>();
private ArrayList<Vector3f> translations = new ArrayList<Vector3f>();
private ArrayList<Quaternion> rotations = new ArrayList<Quaternion>();
private ArrayList<Vector3f> scales = new ArrayList<Vector3f>();
private ArrayList<Float> times = new ArrayList<>();
private ArrayList<Vector3f> translations = new ArrayList<>();
private ArrayList<Quaternion> rotations = new ArrayList<>();
private ArrayList<Vector3f> scales = new ArrayList<>();
private float time = -1;
private Vector3f position;
private Quaternion rotation;
@ -92,7 +92,7 @@ public class SkeletonLoader extends DefaultHandler implements AssetLoader {
assert elementStack.peek().equals("tracks");
String jointName = SAXUtil.parseString(attribs.getValue("bone"));
joint = nameToJoint.get(jointName);
track = new JointTrack();
track = new TransformTrack();
track.setTarget(joint);
} else if (qName.equals("boneparent")) {
assert elementStack.peek().equals("bonehierarchy");
@ -163,10 +163,6 @@ public class SkeletonLoader extends DefaultHandler implements AssetLoader {
armature = new Armature(joints);
armature.setBindPose();
} else if (qName.equals("animation")) {
//nameToJoint contains the joints with no track
for (Joint j : unusedJoints) {
AnimMigrationUtils.padJointTracks(animClip, j);
}
animClips.add(animClip);
animClip = null;
} else if (qName.equals("track")) {
@ -176,7 +172,11 @@ public class SkeletonLoader extends DefaultHandler implements AssetLoader {
track = null;
}
} else if (qName.equals("tracks")) {
JointTrack[] trackList = tracks.toArray(new JointTrack[tracks.size()]);
//nameToJoint contains the joints with no track
for (Joint j : unusedJoints) {
AnimMigrationUtils.padJointTracks(tracks, j);
}
TransformTrack[] trackList = tracks.toArray(new TransformTrack[tracks.size()]);
animClip.setTracks(trackList);
tracks.clear();
} else if (qName.equals("keyframe")) {

Loading…
Cancel
Save