Spatial tracks now can keep a reference to the spatial they apply to. This allows to group multiples spatial tracks in a single animation.

fix-456
Nehon 7 years ago committed by Rémy Bouquet
parent 6b3093aa3e
commit d7b2e08d95
  1. 20
      jme3-core/src/main/java/com/jme3/animation/Animation.java
  2. 48
      jme3-core/src/main/java/com/jme3/animation/SpatialTrack.java
  3. 106
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java

@ -93,6 +93,24 @@ public class Animation implements Savable, Cloneable, JmeCloneable {
return length; return length;
} }
/**
* Set the length of the animation
*
* @param length
*/
public void setLength(float length) {
this.length = length;
}
/**
* Sets the name of the animation
*
* @param name
*/
public void setName(String name) {
this.name = name;
}
/** /**
* This method sets the current time of the animation. * This method sets the current time of the animation.
* This method behaves differently for every known track type. * This method behaves differently for every known track type.
@ -209,7 +227,7 @@ public class Animation implements Savable, Cloneable, JmeCloneable {
// isn't cloned at all... even though they all implement clone() methods. -pspeed // isn't cloned at all... even though they all implement clone() methods. -pspeed
SafeArrayList<Track> newTracks = new SafeArrayList<>(Track.class); SafeArrayList<Track> newTracks = new SafeArrayList<>(Track.class);
for( Track track : tracks ) { for( Track track : tracks ) {
if( track instanceof ClonableTrack ) { if (track instanceof JmeCloneable) {
newTracks.add(cloner.clone(track)); newTracks.add(cloner.clone(track));
} else { } else {
// this is the part that seems fishy // this is the part that seems fishy

@ -39,6 +39,9 @@ import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
@ -47,7 +50,7 @@ import java.util.Arrays;
* *
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class SpatialTrack implements Track { public class SpatialTrack implements Track, JmeCloneable {
/** /**
* Translations of the track. * Translations of the track.
@ -64,6 +67,12 @@ public class SpatialTrack implements Track {
*/ */
private CompactVector3Array scales; private CompactVector3Array scales;
/**
* The spatial to which this track applies.
* Note that this is optional, if no spatial is defined, the AnimControl's Spatial will be used.
*/
private Spatial trackSpatial;
/** /**
* The times of the animations frames. * The times of the animations frames.
*/ */
@ -97,7 +106,10 @@ public class SpatialTrack implements Track {
* the current time of the animation * the current time of the animation
*/ */
public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
Spatial spatial = control.getSpatial(); Spatial spatial = trackSpatial;
if (spatial == null) {
spatial = control.getSpatial();
}
Vector3f tempV = vars.vect1; Vector3f tempV = vars.vect1;
Vector3f tempS = vars.vect2; Vector3f tempS = vars.vect2;
@ -153,10 +165,12 @@ public class SpatialTrack implements Track {
tempS.interpolateLocal(tempS2, blend); tempS.interpolateLocal(tempS2, blend);
} }
if (translations != null) if (translations != null) {
spatial.setLocalTranslation(tempV); spatial.setLocalTranslation(tempV);
if (rotations != null) }
if (rotations != null) {
spatial.setLocalRotation(tempQ); spatial.setLocalRotation(tempQ);
}
if (scales != null) { if (scales != null) {
spatial.setLocalScale(tempS); spatial.setLocalScale(tempS);
} }
@ -236,17 +250,26 @@ public class SpatialTrack implements Track {
return times == null ? 0 : times[times.length - 1] - times[0]; return times == null ? 0 : times[times.length - 1] - times[0];
} }
@Override
public Track clone() {
return (Track) jmeClone();
}
@Override @Override
public float[] getKeyFrameTimes() { public float[] getKeyFrameTimes() {
return times; return times;
} }
/** public void setTrackSpatial(Spatial trackSpatial) {
* This method creates a clone of the current object. this.trackSpatial = trackSpatial;
* @return a clone of the current object }
*/
public Spatial getTrackSpatial() {
return trackSpatial;
}
@Override @Override
public SpatialTrack clone() { public Object jmeClone() {
int tablesLength = times.length; int tablesLength = times.length;
float[] timesCopy = this.times.clone(); float[] timesCopy = this.times.clone();
@ -258,6 +281,11 @@ public class SpatialTrack implements Track {
return new SpatialTrack(timesCopy, translationsCopy, rotationsCopy, scalesCopy); return new SpatialTrack(timesCopy, translationsCopy, rotationsCopy, scalesCopy);
} }
@Override
public void cloneFields(Cloner cloner, Object original) {
this.trackSpatial = cloner.clone(((SpatialTrack) original).trackSpatial);
}
@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);
@ -265,6 +293,7 @@ public class SpatialTrack implements Track {
oc.write(rotations, "rotations", null); oc.write(rotations, "rotations", null);
oc.write(times, "times", null); oc.write(times, "times", null);
oc.write(scales, "scales", null); oc.write(scales, "scales", null);
oc.write(trackSpatial, "trackSpatial", null);
} }
@Override @Override
@ -274,5 +303,6 @@ public class SpatialTrack implements Track {
rotations = (CompactQuaternionArray) ic.readSavable("rotations", null); rotations = (CompactQuaternionArray) ic.readSavable("rotations", null);
times = ic.readFloatArray("times", null); times = ic.readFloatArray("times", null);
scales = (CompactVector3Array) ic.readSavable("scales", null); scales = (CompactVector3Array) ic.readSavable("scales", null);
trackSpatial = (Spatial) ic.readSavable("trackSpatial", null);
} }
} }

@ -104,7 +104,7 @@ public class GltfLoader implements AssetLoader {
JsonPrimitive defaultScene = root.getAsJsonPrimitive("scene"); JsonPrimitive defaultScene = root.getAsJsonPrimitive("scene");
Node n = loadScenes(defaultScene); Node n = readScenes(defaultScene);
setupControls(); setupControls();
@ -126,7 +126,7 @@ public class GltfLoader implements AssetLoader {
return "2.0".equals(version); return "2.0".equals(version);
} }
private Node loadScenes(JsonPrimitive defaultScene) throws IOException { private Node readScenes(JsonPrimitive defaultScene) throws IOException {
if (scenes == null) { if (scenes == null) {
//no scene... lets handle this later... //no scene... lets handle this later...
throw new AssetLoadException("Gltf files with no scene is not yet supported"); throw new AssetLoadException("Gltf files with no scene is not yet supported");
@ -141,15 +141,15 @@ public class GltfLoader implements AssetLoader {
sceneNode.setName(getAsString(scene.getAsJsonObject(), "name")); sceneNode.setName(getAsString(scene.getAsJsonObject(), "name"));
JsonArray sceneNodes = scene.getAsJsonObject().getAsJsonArray("nodes"); JsonArray sceneNodes = scene.getAsJsonObject().getAsJsonArray("nodes");
for (JsonElement node : sceneNodes) { for (JsonElement node : sceneNodes) {
loadChild(sceneNode, node); readChild(sceneNode, node);
} }
root.attachChild(sceneNode); root.attachChild(sceneNode);
} }
//Loading animations //Loading animations
if (animations != null) { if (animations != null) {
for (JsonElement animation : animations) { for (int i = 0; i < animations.size(); i++) {
loadAnimation(animation.getAsJsonObject()); readAnimation(i);
} }
} }
@ -162,7 +162,7 @@ public class GltfLoader implements AssetLoader {
return root; return root;
} }
private Object loadNode(int nodeIndex) throws IOException { private Object readNode(int nodeIndex) throws IOException {
Object obj = fetchFromCache("nodes", nodeIndex, Object.class); Object obj = fetchFromCache("nodes", nodeIndex, Object.class);
if (obj != null) { if (obj != null) {
if (obj instanceof Bone) { if (obj instanceof Bone) {
@ -183,7 +183,7 @@ public class GltfLoader implements AssetLoader {
//there is a mesh in this node, however gltf can split meshes in primitives (some kind of sub meshes), //there is a mesh in this node, however gltf can split meshes in primitives (some kind of sub meshes),
//We don't have this in JME so we have to make one mesh and one Geometry for each primitive. //We don't have this in JME so we have to make one mesh and one Geometry for each primitive.
Geometry[] primitives = loadMeshPrimitives(meshIndex); Geometry[] primitives = readMeshPrimitives(meshIndex);
if (primitives.length == 1 && children == null) { if (primitives.length == 1 && children == null) {
//only one geometry, let's not wrap it in another node unless the node has children. //only one geometry, let's not wrap it in another node unless the node has children.
spatial = primitives[0]; spatial = primitives[0];
@ -195,7 +195,7 @@ public class GltfLoader implements AssetLoader {
} }
spatial = node; spatial = node;
} }
spatial.setName(loadMeshName(meshIndex)); spatial.setName(readMeshName(meshIndex));
} else { } else {
//no mesh, we have a node. Can be a camera node or a regular node. //no mesh, we have a node. Can be a camera node or a regular node.
@ -214,22 +214,22 @@ public class GltfLoader implements AssetLoader {
if (children != null) { if (children != null) {
for (JsonElement child : children) { for (JsonElement child : children) {
loadChild(spatial, child); readChild(spatial, child);
} }
} }
if (spatial.getName() == null) { if (spatial.getName() == null) {
spatial.setName(getAsString(nodeData.getAsJsonObject(), "name")); spatial.setName(getAsString(nodeData.getAsJsonObject(), "name"));
} }
spatial.setLocalTransform(loadTransforms(nodeData)); spatial.setLocalTransform(readTransforms(nodeData));
addToCache("nodes", nodeIndex, spatial, nodes.size()); addToCache("nodes", nodeIndex, spatial, nodes.size());
return spatial; return spatial;
} }
private void loadChild(Spatial parent, JsonElement child) throws IOException { private void readChild(Spatial parent, JsonElement child) throws IOException {
int index = child.getAsInt(); int index = child.getAsInt();
Object loaded = loadNode(child.getAsInt()); Object loaded = readNode(child.getAsInt());
if (loaded instanceof Spatial) { if (loaded instanceof Spatial) {
((Node) parent).attachChild((Spatial) loaded); ((Node) parent).attachChild((Spatial) loaded);
} else if (loaded instanceof Bone) { } else if (loaded instanceof Bone) {
@ -240,7 +240,7 @@ public class GltfLoader implements AssetLoader {
} }
} }
private Transform loadTransforms(JsonObject nodeData) { private Transform readTransforms(JsonObject nodeData) {
Transform transform = new Transform(); Transform transform = new Transform();
JsonArray matrix = nodeData.getAsJsonArray("matrix"); JsonArray matrix = nodeData.getAsJsonArray("matrix");
if (matrix != null) { if (matrix != null) {
@ -280,7 +280,7 @@ public class GltfLoader implements AssetLoader {
return transform; return transform;
} }
private Geometry[] loadMeshPrimitives(int meshIndex) throws IOException { private Geometry[] readMeshPrimitives(int meshIndex) throws IOException {
Geometry[] geomArray = (Geometry[]) fetchFromCache("meshes", meshIndex, Object.class); Geometry[] geomArray = (Geometry[]) fetchFromCache("meshes", meshIndex, Object.class);
if (geomArray != null) { if (geomArray != null) {
//cloning the geoms. //cloning the geoms.
@ -305,12 +305,12 @@ public class GltfLoader implements AssetLoader {
mesh.setMode(getMeshMode(mode)); mesh.setMode(getMeshMode(mode));
Integer indices = getAsInteger(meshObject, "indices"); Integer indices = getAsInteger(meshObject, "indices");
if (indices != null) { if (indices != null) {
mesh.setBuffer(loadAccessorData(indices, new VertexBufferPopulator(VertexBuffer.Type.Index))); mesh.setBuffer(readAccessorData(indices, new VertexBufferPopulator(VertexBuffer.Type.Index)));
} }
JsonObject attributes = meshObject.getAsJsonObject("attributes"); JsonObject attributes = meshObject.getAsJsonObject("attributes");
assertNotNull(attributes, "No attributes defined for mesh " + mesh); assertNotNull(attributes, "No attributes defined for mesh " + mesh);
for (Map.Entry<String, JsonElement> entry : attributes.entrySet()) { for (Map.Entry<String, JsonElement> entry : attributes.entrySet()) {
mesh.setBuffer(loadAccessorData(entry.getValue().getAsInt(), new VertexBufferPopulator(getVertexBufferType(entry.getKey())))); mesh.setBuffer(readAccessorData(entry.getValue().getAsInt(), new VertexBufferPopulator(getVertexBufferType(entry.getKey()))));
} }
if (mesh.getBuffer(VertexBuffer.Type.BoneIndex) != null) { if (mesh.getBuffer(VertexBuffer.Type.BoneIndex) != null) {
@ -334,7 +334,7 @@ public class GltfLoader implements AssetLoader {
geom.setMaterial(defaultMat); geom.setMaterial(defaultMat);
} else { } else {
useNormalsFlag = false; useNormalsFlag = false;
geom.setMaterial(loadMaterial(materialIndex)); geom.setMaterial(readMaterial(materialIndex));
if (geom.getMaterial().getAdditionalRenderState().getBlendMode() == RenderState.BlendMode.Alpha) { if (geom.getMaterial().getAdditionalRenderState().getBlendMode() == RenderState.BlendMode.Alpha) {
//Alpha blending is on on this material let's place the geom in the transparent bucket //Alpha blending is on on this material let's place the geom in the transparent bucket
geom.setQueueBucket(RenderQueue.Bucket.Transparent); geom.setQueueBucket(RenderQueue.Bucket.Transparent);
@ -361,7 +361,7 @@ public class GltfLoader implements AssetLoader {
return geomArray; return geomArray;
} }
private <R> R loadAccessorData(int accessorIndex, Populator<R> populator) throws IOException { private <R> R readAccessorData(int accessorIndex, Populator<R> populator) throws IOException {
assertNotNull(accessors, "No accessor attribute in the gltf file"); assertNotNull(accessors, "No accessor attribute in the gltf file");
@ -447,7 +447,7 @@ public class GltfLoader implements AssetLoader {
} }
private Material loadMaterial(int materialIndex) { private Material readMaterial(int materialIndex) {
assertNotNull(materials, "There is no material defined yet a mesh references one"); assertNotNull(materials, "There is no material defined yet a mesh references one");
JsonObject matData = materials.get(materialIndex).getAsJsonObject(); JsonObject matData = materials.get(materialIndex).getAsJsonObject();
@ -504,13 +504,13 @@ public class GltfLoader implements AssetLoader {
Integer sourceIndex = getAsInteger(textureData, "source"); Integer sourceIndex = getAsInteger(textureData, "source");
Integer samplerIndex = getAsInteger(textureData, "sampler"); Integer samplerIndex = getAsInteger(textureData, "sampler");
Texture2D texture2d = loadImage(sourceIndex); Texture2D texture2d = readImage(sourceIndex);
readSampler(samplerIndex, texture2d); readSampler(samplerIndex, texture2d);
return texture2d; return texture2d;
} }
private Texture2D loadImage(int sourceIndex) { private Texture2D readImage(int sourceIndex) {
if (images == null) { if (images == null) {
throw new AssetLoadException("No image defined"); throw new AssetLoadException("No image defined");
} }
@ -533,7 +533,8 @@ public class GltfLoader implements AssetLoader {
} }
private void loadAnimation(JsonObject animation) throws IOException { private void readAnimation(int animationIndex) throws IOException {
JsonObject animation = animations.get(animationIndex).getAsJsonObject();
JsonArray channels = animation.getAsJsonArray("channels"); JsonArray channels = animation.getAsJsonArray("channels");
JsonArray samplers = animation.getAsJsonArray("samplers"); JsonArray samplers = animation.getAsJsonArray("samplers");
String name = getAsString(animation, "name"); String name = getAsString(animation, "name");
@ -583,7 +584,7 @@ public class GltfLoader implements AssetLoader {
float[] times = fetchFromCache("accessors", timeIndex, float[].class); float[] times = fetchFromCache("accessors", timeIndex, float[].class);
if (times == null) { if (times == null) {
times = loadAccessorData(timeIndex, floatArrayPopulator); times = readAccessorData(timeIndex, floatArrayPopulator);
addToCache("accessors", timeIndex, times, accessors.size()); addToCache("accessors", timeIndex, times, accessors.size());
} }
if (animData.times == null) { if (animData.times == null) {
@ -599,38 +600,41 @@ public class GltfLoader implements AssetLoader {
animData.length = times[times.length - 1]; animData.length = times[times.length - 1];
} }
if (targetPath.equals("translation")) { if (targetPath.equals("translation")) {
Vector3f[] translations = loadAccessorData(dataIndex, vector3fArrayPopulator); Vector3f[] translations = readAccessorData(dataIndex, vector3fArrayPopulator);
animData.translations = translations; animData.translations = translations;
} else if (targetPath.equals("scale")) { } else if (targetPath.equals("scale")) {
Vector3f[] scales = loadAccessorData(dataIndex, vector3fArrayPopulator); Vector3f[] scales = readAccessorData(dataIndex, vector3fArrayPopulator);
animData.scales = scales; animData.scales = scales;
} else if (targetPath.equals("rotation")) { } else if (targetPath.equals("rotation")) {
Quaternion[] rotations = loadAccessorData(dataIndex, quaternionArrayPopulator); Quaternion[] rotations = readAccessorData(dataIndex, quaternionArrayPopulator);
animData.rotations = rotations; animData.rotations = rotations;
} }
} }
if (name == null) {
name = "anim_" + animationIndex;
}
List<Spatial> spatials = new ArrayList<>();
Animation anim = new Animation();
anim.setName(name);
for (int i = 0; i < animatedNodes.length; i++) { for (int i = 0; i < animatedNodes.length; i++) {
AnimData animData = animatedNodes[i]; AnimData animData = animatedNodes[i];
if (animData == null) { if (animData == null) {
continue; continue;
} }
if (animData.length > anim.getLength()) {
anim.setLength(animData.length);
}
Object node = fetchFromCache("nodes", i, Object.class); Object node = fetchFromCache("nodes", i, Object.class);
if (node instanceof Spatial) { if (node instanceof Spatial) {
Spatial s = (Spatial) node; Spatial s = (Spatial) node;
AnimControl control = s.getControl(AnimControl.class); spatials.add(s);
if (control == null) { SpatialTrack track = new SpatialTrack(animData.times, animData.translations, animData.rotations, animData.scales);
control = new AnimControl(); track.setTrackSpatial(s);
s.addControl(control); anim.addTrack(track);
}
if (name == null) {
name = s.getName() + "_anim_" + control.getAnimationNames().size();
}
Animation anim = new Animation(name, animData.length);
anim.addTrack(new SpatialTrack(animData.times, animData.translations, animData.rotations, animData.scales));
control.addAnim(anim);
} else { } else {
//At some point we'll have bone animation //At some point we'll have bone animation
//TODO support for bone animation. //TODO support for bone animation.
@ -639,11 +643,23 @@ public class GltfLoader implements AssetLoader {
} }
} }
if (!spatials.isEmpty()) {
Spatial spatial = null;
if (spatials.size() == 1) {
spatial = spatials.get(0);
} else {
spatial = findCommonAncestor(spatials);
}
AnimControl control = spatial.getControl(AnimControl.class);
if (control == null) {
control = new AnimControl();
spatial.addControl(control);
}
control.addAnim(anim);
}
} }
//private void readAnimationSampler()
private void readSampler(int samplerIndex, Texture2D texture) { private void readSampler(int samplerIndex, Texture2D texture) {
if (samplers == null) { if (samplers == null) {
throw new AssetLoadException("No samplers defined"); throw new AssetLoadException("No samplers defined");
@ -679,7 +695,7 @@ public class GltfLoader implements AssetLoader {
Integer matricesIndex = getAsInteger(skin, "inverseBindMatrices"); Integer matricesIndex = getAsInteger(skin, "inverseBindMatrices");
Matrix4f[] inverseBindMatrices = null; Matrix4f[] inverseBindMatrices = null;
if (matricesIndex != null) { if (matricesIndex != null) {
inverseBindMatrices = loadAccessorData(matricesIndex, matrix4fArrayPopulator); inverseBindMatrices = readAccessorData(matricesIndex, matrix4fArrayPopulator);
} else { } else {
inverseBindMatrices = new Matrix4f[joints.size()]; inverseBindMatrices = new Matrix4f[joints.size()];
for (int i = 0; i < inverseBindMatrices.length; i++) { for (int i = 0; i < inverseBindMatrices.length; i++) {
@ -693,7 +709,7 @@ public class GltfLoader implements AssetLoader {
Bone[] bones = new Bone[joints.size()]; Bone[] bones = new Bone[joints.size()];
for (int i = 0; i < joints.size(); i++) { for (int i = 0; i < joints.size(); i++) {
bones[i] = loadNodeAsBone(joints.get(i).getAsInt(), inverseBindMatrices[i]); bones[i] = readNodeAsBone(joints.get(i).getAsInt(), inverseBindMatrices[i]);
} }
for (int i = 0; i < joints.size(); i++) { for (int i = 0; i < joints.size(); i++) {
findChildren(joints.get(i).getAsInt()); findChildren(joints.get(i).getAsInt());
@ -709,7 +725,7 @@ public class GltfLoader implements AssetLoader {
} }
private Bone loadNodeAsBone(int nodeIndex, Matrix4f inverseBindMatrix) throws IOException { private Bone readNodeAsBone(int nodeIndex, Matrix4f inverseBindMatrix) throws IOException {
Bone bone = fetchFromCache("nodes", nodeIndex, Bone.class); Bone bone = fetchFromCache("nodes", nodeIndex, Bone.class);
if (bone != null) { if (bone != null) {
@ -722,7 +738,7 @@ public class GltfLoader implements AssetLoader {
name = "Bone_" + nodeIndex; name = "Bone_" + nodeIndex;
} }
bone = new Bone(name); bone = new Bone(name);
Transform boneTransforms = loadTransforms(nodeData); Transform boneTransforms = readTransforms(nodeData);
Transform inverseBind = new Transform(); Transform inverseBind = new Transform();
inverseBind.fromTransformMatrix(inverseBindMatrix); inverseBind.fromTransformMatrix(inverseBindMatrix);
// boneTransforms.combineWithParent(inverseBind); // boneTransforms.combineWithParent(inverseBind);
@ -758,7 +774,7 @@ public class GltfLoader implements AssetLoader {
} }
} }
private String loadMeshName(int meshIndex) { private String readMeshName(int meshIndex) {
JsonObject meshData = meshes.get(meshIndex).getAsJsonObject(); JsonObject meshData = meshes.get(meshIndex).getAsJsonObject();
return getAsString(meshData, "name"); return getAsString(meshData, "name");
} }

Loading…
Cancel
Save