diff --git a/engine/src/core/com/jme3/animation/SpatialAnimation.java b/engine/src/core/com/jme3/animation/SpatialAnimation.java new file mode 100644 index 000000000..d6635e3b5 --- /dev/null +++ b/engine/src/core/com/jme3/animation/SpatialAnimation.java @@ -0,0 +1,98 @@ +package com.jme3.animation; + +import java.io.IOException; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.scene.Spatial; + +/** + * This class animates the whole spatials. All spatial's children are also + * affected by their parent's movement. + * + * @author Marcin Roguski (Kaelthas) + */ +public class SpatialAnimation implements Animation { + /** The name of the animation. */ + private String name; + /** The length of the animation. */ + private float length; + /** The track of the animation. */ + private SpatialTrack track; + + /** + * Constructor. Stores the name and length of the animation. + * @param name the name of the animation + * @param length the length of the animation + */ + public SpatialAnimation(String name, float length) { + this.name = name; + this.length = length; + } + + @Override + public void setTime(float time, float blendAmount, AnimControl control, + AnimChannel channel) { + Spatial spatial = control.getSpatial(); + if (spatial != null) { + track.setTime(time, spatial); + } + } + + /** + * This method sets the animation track. + * @param track the animation track + */ + public void setTrack(SpatialTrack track) { + this.track = track; + } + + /** + * @return the animation track + */ + public SpatialTrack getTracks() { + return track; + } + + @Override + public String getName() { + return name; + } + + @Override + public float getLength() { + return length; + } + + @Override + public String toString() { + return "SpatialAnim[name=" + name + ", length=" + length + "]"; + } + + @Override + public Animation clone() { + try { + return (Animation) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(name, "name", null); + oc.write(length, "length", 0); + oc.write(track, "track", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + name = in.readString("name", null); + length = in.readFloat("length", 0); + track = (SpatialTrack) in.readSavable("track", null); + } +} diff --git a/engine/src/core/com/jme3/animation/SpatialTrack.java b/engine/src/core/com/jme3/animation/SpatialTrack.java new file mode 100644 index 000000000..483556249 --- /dev/null +++ b/engine/src/core/com/jme3/animation/SpatialTrack.java @@ -0,0 +1,195 @@ +package com.jme3.animation; + +import java.io.IOException; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; + +/** + * This class represents the track for spatial animation. + * + * @author Marcin Roguski (Kaelthas) + */ +public class SpatialTrack implements Savable { + /** Translations of the track. */ + private CompactVector3Array translations; + /** Rotations of the track. */ + private CompactQuaternionArray rotations; + /** Scales of the track. */ + private CompactVector3Array scales; + /** The times of the animations frames. */ + private float[] times; + + // temp vectors for interpolation + private transient final Vector3f tempV = new Vector3f(); + private transient final Quaternion tempQ = new Quaternion(); + private transient final Vector3f tempS = new Vector3f(); + private transient final Vector3f tempV2 = new Vector3f(); + private transient final Quaternion tempQ2 = new Quaternion(); + private transient final Vector3f tempS2 = new Vector3f(); + + public SpatialTrack() { + } + + /** + * Creates a spatial track for the given track data. + * + * @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(float[] times, Vector3f[] translations, + Quaternion[] rotations, Vector3f[] scales) { + this.setKeyframes(times, translations, rotations, scales); + } + + /** + * + * Modify the spatial which this track modifies. + * + * @param time + * the current time of the animation + * @param spatial + * the spatial that should be animated with this track + */ + public void setTime(float time, Spatial spatial) { + int lastFrame = times.length - 1; + if (time < 0 || lastFrame == 0) { + rotations.get(0, tempQ); + translations.get(0, tempV); + if (scales != null) { + scales.get(0, tempS); + } + } else if (time >= times[lastFrame]) { + rotations.get(lastFrame, tempQ); + translations.get(lastFrame, tempV); + if (scales != null) { + scales.get(lastFrame, tempS); + } + } else { + int startFrame = 0; + int endFrame = 1; + // use lastFrame so we never overflow the array + for (int i = 0; i < lastFrame && times[i] < time; ++i) { + startFrame = i; + endFrame = i + 1; + } + + float blend = (time - times[startFrame]) / (times[endFrame] - times[startFrame]); + + rotations.get(startFrame, tempQ); + translations.get(startFrame, tempV); + if (scales != null) { + scales.get(startFrame, tempS); + } + rotations.get(endFrame, tempQ2); + translations.get(endFrame, tempV2); + if (scales != null) { + scales.get(endFrame, tempS2); + } + tempQ.nlerp(tempQ2, blend); + tempV.interpolate(tempV2, blend); + tempS.interpolate(tempS2, blend); + } + spatial.setLocalTranslation(tempV); + spatial.setLocalRotation(tempQ); + if (scales != null) { + spatial.setLocalScale(tempS); + } + } + + /** + * Set the translations, rotations and scales for 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 void setKeyframes(float[] times, Vector3f[] translations, + Quaternion[] rotations, Vector3f[] scales) { + if (times.length == 0) { + throw new RuntimeException("BoneTrack with no keyframes!"); + } + + assert times.length == translations.length + && times.length == rotations.length; + + this.times = times; + this.translations = new CompactVector3Array(); + this.translations.add(translations); + this.translations.freeze(); + this.rotations = new CompactQuaternionArray(); + this.rotations.add(rotations); + this.rotations.freeze(); + + assert times.length == scales.length; + + if (scales != null) { + this.scales = new CompactVector3Array(); + this.scales.add(scales); + this.scales.freeze(); + } + } + + /** + * @return the array of rotations of this track + */ + public Quaternion[] getRotations() { + return rotations.toObjectArray(); + } + + /** + * @return the array of scales for this track + */ + public Vector3f[] getScales() { + return scales == null ? null : scales.toObjectArray(); + } + + /** + * @return the arrays of time for this track + */ + public float[] getTimes() { + return times; + } + + /** + * @return the array of translations of this track + */ + public Vector3f[] getTranslations() { + return translations.toObjectArray(); + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(translations, "translations", null); + oc.write(rotations, "rotations", null); + oc.write(times, "times", null); + oc.write(scales, "scales", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + translations = (CompactVector3Array) ic.readSavable("translations", null); + rotations = (CompactQuaternionArray) ic.readSavable("rotations", null); + times = ic.readFloatArray("times", null); + scales = (CompactVector3Array) ic.readSavable("scales", null); + } +} diff --git a/engine/src/test/jme3test/model/anim/TestSpatialAnim.java b/engine/src/test/jme3test/model/anim/TestSpatialAnim.java new file mode 100644 index 000000000..04c7a55ad --- /dev/null +++ b/engine/src/test/jme3test/model/anim/TestSpatialAnim.java @@ -0,0 +1,89 @@ +package jme3test.model.anim; + +import java.util.HashMap; + +import com.jme3.animation.AnimControl; +import com.jme3.animation.Animation; +import com.jme3.animation.SpatialAnimation; +import com.jme3.animation.SpatialTrack; +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; + +public class TestSpatialAnim extends SimpleApplication { + + public static void main(String[] args) { + TestSpatialAnim app = new TestSpatialAnim(); + app.start(); + } + + @Override + public void simpleInitApp() { + + AmbientLight al = new AmbientLight(); + rootNode.addLight(al); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(Vector3f.UNIT_XYZ.negate()); + rootNode.addLight(dl); + + // Create model + Box box = new Box(1, 1, 1); + Geometry geom = new Geometry("box", box); + geom.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m")); + Node model = new Node("model"); + model.attachChild(geom); + + Box child = new Box(0.5f, 0.5f, 0.5f); + Geometry childGeom = new Geometry("box", child); + childGeom.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m")); + Node childModel = new Node("childmodel"); + childModel.setLocalTranslation(2, 2, 2); + childModel.attachChild(childGeom); + model.attachChild(childModel); + + //animation parameters + float animTime = 5; + int fps = 25; + float totalXLength = 10; + + //calculating frames + int totalFrames = (int) (fps * animTime); + float dT = animTime / totalFrames, t = 0; + float dX = totalXLength / totalFrames, x = 0; + float[] times = new float[totalFrames]; + Vector3f[] translations = new Vector3f[totalFrames]; + Quaternion[] rotations = new Quaternion[totalFrames]; + Vector3f[] scales = new Vector3f[totalFrames]; + for (int i = 0; i < totalFrames; ++i) { + times[i] = t; + t += dT; + translations[i] = new Vector3f(x, 0, 0); + x += dX; + rotations[i] = Quaternion.IDENTITY; + scales[i] = Vector3f.UNIT_XYZ; + } + SpatialTrack spatialTrack = new SpatialTrack(times, translations, rotations, scales); + + //creating the animation + SpatialAnimation spatialAnimation = new SpatialAnimation("anim", animTime); + spatialAnimation.setTrack(spatialTrack); + + //create spatial animation control + AnimControl control = new AnimControl(); + HashMap animations = new HashMap(); + animations.put("anim", spatialAnimation); + control.setAnimations(animations); + model.addControl(control); + + rootNode.attachChild(model); + + //run animation + control.createChannel().setAnim("anim"); + } +}