Proper serialisation for morph animation

monkanim
Nehon 7 years ago
parent c2fcdefd0d
commit bcd97c22eb
  1. 4
      jme3-core/src/main/java/com/jme3/anim/AnimClip.java
  2. 37
      jme3-core/src/main/java/com/jme3/anim/Armature.java
  3. 40
      jme3-core/src/main/java/com/jme3/anim/Joint.java
  4. 107
      jme3-core/src/main/java/com/jme3/anim/MorphControl.java
  5. 2
      jme3-core/src/main/java/com/jme3/anim/MorphTrack.java
  6. 2
      jme3-core/src/main/java/com/jme3/anim/TransformTrack.java
  7. 2
      jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java
  8. 4
      jme3-core/src/main/java/com/jme3/animation/BoneTrack.java
  9. 7
      jme3-core/src/main/java/com/jme3/material/MatParamOverride.java
  10. 8
      jme3-core/src/main/java/com/jme3/material/Material.java
  11. 2
      jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
  12. 44
      jme3-core/src/main/java/com/jme3/scene/Geometry.java
  13. 4
      jme3-core/src/main/java/com/jme3/scene/Mesh.java
  14. 9
      jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java
  15. 10
      jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java
  16. 36
      jme3-examples/src/main/java/jme3test/model/anim/TestAnimMorphSerialization.java
  17. 4
      jme3-examples/src/main/java/jme3test/model/anim/TestArmature.java
  18. 4
      jme3-examples/src/main/java/jme3test/model/anim/TestBaseAnimSerialization.java
  19. 1
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
  20. 2
      jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SkeletonLoader.java

@ -80,9 +80,9 @@ public class AnimClip implements JmeCloneable, Savable {
name = ic.readString("name", null); name = ic.readString("name", null);
Savable[] arr = ic.readSavableArray("tracks", null); Savable[] arr = ic.readSavableArray("tracks", null);
if (arr != null) { if (arr != null) {
tracks = new TransformTrack[arr.length]; tracks = new AnimTrack[arr.length];
for (int i = 0; i < arr.length; i++) { for (int i = 0; i < arr.length; i++) {
TransformTrack t = (TransformTrack) arr[i]; AnimTrack t = (AnimTrack) arr[i];
tracks[i] = t; tracks[i] = t;
if (t.getLength() > length) { if (t.getLength() > length) {
length = t.getLength(); length = t.getLength();

@ -174,28 +174,49 @@ public class Armature implements JmeCloneable, Savable {
/** /**
* Saves the current Armature state as its bind pose. * Saves the current Armature state as its bind pose.
* Note that the bind pose is supposed to be the one where the armature is aligned with the mesh to deform.
* Saving this pose will affect how skinning works.
*/ */
public void setBindPose() { public void saveBindPose() {
//make sure all bones are updated //make sure all bones are updated
update(); update();
//Save the current pose as bind pose //Save the current pose as bind pose
for (Joint joint : jointList) { for (Joint joint : jointList) {
joint.setBindPose(); joint.saveBindPose();
} }
} }
/** /**
* This methods sets this armature in its bind pose (aligned with the undeformed mesh) * This methods sets this armature in its bind pose (aligned with the mesh to deform)
* Note that this is only useful for debugging porpose. * Note that this is only useful for debugging purpose.
*/ */
public void resetToBindPose() { public void applyBindPose() {
for (Joint joint : rootJoints) { for (Joint joint : rootJoints) {
joint.resetToBindPose(); joint.applyBindPose();
} }
} }
/** /**
* Compute the skining matrices for each bone of the armature that would be used to transform vertices of associated meshes * Saves the current local transform as the initial transform.
* Initial transform is the one applied to the armature when loaded.
*/
public void saveInitialPose() {
for (Joint joint : jointList) {
joint.saveInitialPose();
}
}
/**
* Applies the initial pose to this armature
*/
public void applyInitialPose() {
for (Joint rootJoint : rootJoints) {
rootJoint.applyInitialPose();
}
}
/**
* Compute the skinning matrices for each bone of the armature that would be used to transform vertices of associated meshes
* *
* @return * @return
*/ */
@ -263,7 +284,7 @@ public class Armature implements JmeCloneable, Savable {
for (Joint rootJoint : rootJoints) { for (Joint rootJoint : rootJoints) {
rootJoint.update(); rootJoint.update();
} }
resetToBindPose(); applyInitialPose();
} }
@Override @Override

@ -37,6 +37,13 @@ public class Joint implements Savable, JmeCloneable, HasLocalTransform {
*/ */
private Transform localTransform = new Transform(); private Transform localTransform = new Transform();
/**
* The initial transform of the joint in local space. Relative to its parent.
* Or relative to the model's origin for the root joint.
* this transform is the transform applied when the armature is loaded.
*/
private Transform initialTransform = new Transform();
/** /**
* The transform of the joint in model space. Relative to the origin of the model. * The transform of the joint in model space. Relative to the origin of the model.
* this is either a MatrixJointModelTransform or a SeparateJointModelTransform * this is either a MatrixJointModelTransform or a SeparateJointModelTransform
@ -127,18 +134,43 @@ public class Joint implements Savable, JmeCloneable, HasLocalTransform {
jointModelTransform.getOffsetTransform(outTransform, inverseModelBindMatrix); jointModelTransform.getOffsetTransform(outTransform, inverseModelBindMatrix);
} }
protected void setBindPose() { /**
* Sets the current localTransform as the Bind transform.
*/
protected void saveBindPose() {
//Note that the whole Armature must be updated before calling this method. //Note that the whole Armature must be updated before calling this method.
getModelTransform().toTransformMatrix(inverseModelBindMatrix); getModelTransform().toTransformMatrix(inverseModelBindMatrix);
inverseModelBindMatrix.invertLocal(); inverseModelBindMatrix.invertLocal();
} }
protected void resetToBindPose() { /**
* Sets the current local transforms as the initial transform.
*/
protected void saveInitialPose() {
initialTransform.set(localTransform);
}
/**
* Sets the local transform with the bind transforms
*/
protected void applyBindPose() {
jointModelTransform.applyBindPose(localTransform, inverseModelBindMatrix, parent); jointModelTransform.applyBindPose(localTransform, inverseModelBindMatrix, parent);
updateModelTransforms(); updateModelTransforms();
for (Joint child : children.getArray()) { for (Joint child : children.getArray()) {
child.resetToBindPose(); child.applyBindPose();
}
}
/**
* Sets the local transform with the initial transform
*/
protected void applyInitialPose() {
setLocalTransform(initialTransform);
updateModelTransforms();
for (Joint child : children.getArray()) {
child.applyInitialPose();
} }
} }
@ -277,6 +309,7 @@ public class Joint implements Savable, JmeCloneable, HasLocalTransform {
name = input.readString("name", null); name = input.readString("name", null);
attachedNode = (Node) input.readSavable("attachedNode", null); attachedNode = (Node) input.readSavable("attachedNode", null);
targetGeometry = (Geometry) input.readSavable("targetGeometry", null); targetGeometry = (Geometry) input.readSavable("targetGeometry", null);
initialTransform = (Transform) input.readSavable("initialTransform", new Transform());
inverseModelBindMatrix = (Matrix4f) input.readSavable("inverseModelBindMatrix", inverseModelBindMatrix); inverseModelBindMatrix = (Matrix4f) input.readSavable("inverseModelBindMatrix", inverseModelBindMatrix);
ArrayList<Joint> childList = input.readSavableArrayList("children", null); ArrayList<Joint> childList = input.readSavableArrayList("children", null);
@ -292,6 +325,7 @@ public class Joint implements Savable, JmeCloneable, HasLocalTransform {
output.write(name, "name", null); output.write(name, "name", null);
output.write(attachedNode, "attachedNode", null); output.write(attachedNode, "attachedNode", null);
output.write(targetGeometry, "targetGeometry", null); output.write(targetGeometry, "targetGeometry", null);
output.write(initialTransform, "initialTransform", new Transform());
output.write(inverseModelBindMatrix, "inverseModelBindMatrix", new Matrix4f()); output.write(inverseModelBindMatrix, "inverseModelBindMatrix", new Matrix4f());
output.writeSavableArrayList(new ArrayList(children), "children", null); output.writeSavableArrayList(new ArrayList(children), "children", null);
} }

@ -1,11 +1,9 @@
package com.jme3.anim; package com.jme3.anim;
import com.jme3.export.*;
import com.jme3.material.*; import com.jme3.material.*;
import com.jme3.renderer.*; import com.jme3.renderer.*;
import com.jme3.scene.Geometry; import com.jme3.scene.*;
import com.jme3.scene.Mesh;
import com.jme3.scene.SceneGraphVisitorAdapter;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.control.AbstractControl; import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.mesh.MorphTarget; import com.jme3.scene.mesh.MorphTarget;
import com.jme3.shader.VarType; import com.jme3.shader.VarType;
@ -13,7 +11,10 @@ import com.jme3.util.BufferUtils;
import com.jme3.util.SafeArrayList; import com.jme3.util.SafeArrayList;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import java.io.IOException;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* A control that handle morph animation for Position, Normal and Tangent buffers. * A control that handle morph animation for Position, Normal and Tangent buffers.
@ -22,7 +23,9 @@ import java.nio.FloatBuffer;
* *
* @author Rémy Bouquet * @author Rémy Bouquet
*/ */
public class MorphControl extends AbstractControl { public class MorphControl extends AbstractControl implements Savable {
private static final Logger logger = Logger.getLogger(MorphControl.class.getName());
private static final int MAX_MORPH_BUFFERS = 14; private static final int MAX_MORPH_BUFFERS = 14;
private final static float MIN_WEIGHT = 0.005f; private final static float MIN_WEIGHT = 0.005f;
@ -55,33 +58,23 @@ public class MorphControl extends AbstractControl {
if (!enabled) { if (!enabled) {
return; return;
} }
for (Geometry target : targets) { for (Geometry geom : targets) {
Mesh mesh = target.getMesh(); Mesh mesh = geom.getMesh();
if (!target.isDirtyMorph()) { if (!geom.isDirtyMorph()) {
continue; continue;
} }
int nbMaxBuffers = getRemainingBuffers(mesh, rm.getRenderer());
Material m = target.getMaterial();
float weights[] = target.getMorphState(); Material m = geom.getMaterial();
float weights[] = geom.getMorphState();
MorphTarget morphTargets[] = mesh.getMorphTargets(); MorphTarget morphTargets[] = mesh.getMorphTargets();
float matWeights[]; float matWeights[];
MatParam param = m.getParam("MorphWeights");
//Number of buffer to handle for each morph target //Number of buffer to handle for each morph target
int targetNumBuffers = getTargetNumBuffers(morphTargets[0]); int targetNumBuffers = getTargetNumBuffers(morphTargets[0]);
// compute the max number of targets to send to the GPU
int maxGPUTargets = Math.min(nbMaxBuffers, MAX_MORPH_BUFFERS) / targetNumBuffers;
if (param == null) {
matWeights = new float[maxGPUTargets];
m.setParam("MorphWeights", VarType.FloatArray, matWeights);
} else {
matWeights = (float[]) param.getValue();
}
// setting the maximum number as the real number may change every frame and trigger a shader recompilation since it's bound to a define. int maxGPUTargets = getMaxGPUTargets(rm, geom, m, targetNumBuffers);
m.setInt("NumberOfMorphTargets", maxGPUTargets);
m.setInt("NumberOfTargetsBuffers", targetNumBuffers); MatParam param2 = m.getParam("MorphWeights");
matWeights = (float[]) param2.getValue();
int nbGPUTargets = 0; int nbGPUTargets = 0;
int lastGpuTargetIndex = 0; int lastGpuTargetIndex = 0;
@ -115,14 +108,14 @@ public class MorphControl extends AbstractControl {
} else if (cpuWeightSum > 0) { } else if (cpuWeightSum > 0) {
// we have more simultaneous morph targets than available gpu slots, // we have more simultaneous morph targets than available gpu slots,
// we merge the additional morph targets and bind them to the last gpu slot // we merge the additional morph targets and bind them to the last gpu slot
MorphTarget mt = target.getFallbackMorphTarget(); MorphTarget mt = geom.getFallbackMorphTarget();
if (mt == null) { if (mt == null) {
mt = initCpuMorphTarget(target); mt = initCpuMorphTarget(geom);
target.setFallbackMorphTarget(mt); geom.setFallbackMorphTarget(mt);
} }
// adding the last Gpu target weight // adding the last Gpu target weight
cpuWeightSum += matWeights[nbGPUTargets - 1]; cpuWeightSum += matWeights[nbGPUTargets - 1];
ensureTmpArraysCapacity(target.getVertexCount() * 3, targetNumBuffers); ensureTmpArraysCapacity(geom.getVertexCount() * 3, targetNumBuffers);
// merging all remaining targets in tmp arrays // merging all remaining targets in tmp arrays
for (int i = lastGpuTargetIndex; i < morphTargets.length; i++) { for (int i = lastGpuTargetIndex; i < morphTargets.length; i++) {
@ -130,7 +123,7 @@ public class MorphControl extends AbstractControl {
continue; continue;
} }
float weight = weights[i] / cpuWeightSum; float weight = weights[i] / cpuWeightSum;
MorphTarget t = target.getMesh().getMorphTargets()[i]; MorphTarget t = geom.getMesh().getMorphTargets()[i];
mergeMorphTargets(targetNumBuffers, weight, t, i == lastGpuTargetIndex); mergeMorphTargets(targetNumBuffers, weight, t, i == lastGpuTargetIndex);
} }
@ -143,7 +136,52 @@ public class MorphControl extends AbstractControl {
// setting the eight of the merged targets // setting the eight of the merged targets
matWeights[nbGPUTargets - 1] = cpuWeightSum; matWeights[nbGPUTargets - 1] = cpuWeightSum;
} }
geom.setDirtyMorph(false);
}
}
private int getMaxGPUTargets(RenderManager rm, Geometry geom, Material mat, int targetNumBuffers) {
if (geom.getNbSimultaneousGPUMorph() > -1) {
return geom.getNbSimultaneousGPUMorph();
}
// Evaluate the number of CPU slots remaining for morph buffers.
int nbMaxBuffers = getRemainingBuffers(geom.getMesh(), rm.getRenderer());
int realNumTargetsBuffers = geom.getMesh().getMorphTargets().length * targetNumBuffers;
// compute the max number of targets to send to the GPU
int maxGPUTargets = Math.min(realNumTargetsBuffers, Math.min(nbMaxBuffers, MAX_MORPH_BUFFERS)) / targetNumBuffers;
MatParam param = mat.getParam("MorphWeights");
if (param == null) {
// init the mat param if it doesn't exists.
float[] wts = new float[maxGPUTargets];
mat.setParam("MorphWeights", VarType.FloatArray, wts);
}
mat.setInt("NumberOfTargetsBuffers", targetNumBuffers);
// test compile the shader to find the accurate number of remaining attributes slots
boolean compilationOk = false;
// Note that if ever the shader has an unrelated issue we want to break at some point, hence the maxGPUTargets > 0
while (!compilationOk && maxGPUTargets > 0) {
// setting the maximum number as the real number may change every frame and trigger a shader recompilation since it's bound to a define.
mat.setInt("NumberOfMorphTargets", maxGPUTargets);
try {
// preload the spatial. this will trigger a shader compilation that will fail if the number of attributes is over the limit.
rm.preloadScene(spatial);
compilationOk = true;
} catch (RendererException e) {
logger.log(Level.FINE, geom.getName() + ": failed at " + maxGPUTargets);
// the compilation failed let's decrement the number of targets an try again.
maxGPUTargets--;
}
} }
logger.log(Level.FINE, geom.getName() + ": " + maxGPUTargets);
// set the number of GPU morph on the geom to not have to recompute it next frame.
geom.setNbSimultaneousGPUMorph(maxGPUTargets);
return maxGPUTargets;
} }
private int bindMorphtargetBuffer(Mesh mesh, int targetNumBuffers, int boundBufferIdx, MorphTarget t) { private int bindMorphtargetBuffer(Mesh mesh, int targetNumBuffers, int boundBufferIdx, MorphTarget t) {
@ -263,6 +301,17 @@ public class MorphControl extends AbstractControl {
return num; return num;
} }
/**
* Computes the number of remaining buffers on this mesh.
* This is supposed to give a hint on how many attributes will be used in the material and computes the remaining available slots for the morph attributes.
* However, the shader can declare attributes that are not used and not bound to a real buffer.
* That's why we attempt to compile the shader later on to avoid any compilation crash.
* This method is here to avoid too much render test iteration.
*
* @param mesh
* @param renderer
* @return
*/
private int getRemainingBuffers(Mesh mesh, Renderer renderer) { private int getRemainingBuffers(Mesh mesh, Renderer renderer) {
int nbUsedBuffers = 0; int nbUsedBuffers = 0;
for (VertexBuffer vb : mesh.getBufferList().getArray()) { for (VertexBuffer vb : mesh.getBufferList().getArray()) {

@ -187,6 +187,7 @@ public class MorphTrack implements AnimTrack<float[]> {
oc.write(weights, "weights", null); oc.write(weights, "weights", null);
oc.write(times, "times", null); oc.write(times, "times", null);
oc.write(target, "target", null); oc.write(target, "target", null);
oc.write(nbMorphTargets, "nbMorphTargets", 0);
} }
@Override @Override
@ -195,6 +196,7 @@ public class MorphTrack implements AnimTrack<float[]> {
weights = ic.readFloatArray("weights", null); weights = ic.readFloatArray("weights", null);
times = ic.readFloatArray("times", null); times = ic.readFloatArray("times", null);
target = (Geometry) ic.readSavable("target", null); target = (Geometry) ic.readSavable("target", null);
nbMorphTargets = ic.readInt("nbMorphTargets", 0);
setTimes(times); setTimes(times);
} }

@ -294,7 +294,7 @@ public class TransformTrack implements AnimTrack<Transform> {
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);
target = (Joint) ic.readSavable("target", null); target = (HasLocalTransform) ic.readSavable("target", null);
setTimes(times); setTimes(times);
} }

@ -58,7 +58,7 @@ public class AnimMigrationUtils {
} }
Armature armature = new Armature(joints); Armature armature = new Armature(joints);
armature.setBindPose(); armature.saveBindPose();
skeletonArmatureMap.put(skeleton, armature); skeletonArmatureMap.put(skeleton, armature);
List<TransformTrack> tracks = new ArrayList<>(); List<TransformTrack> tracks = new ArrayList<>();

@ -35,12 +35,8 @@ import com.jme3.export.*;
import com.jme3.math.Quaternion; import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
<<<<<<< HEAD
import com.jme3.util.clone.Cloner; import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable; import com.jme3.util.clone.JmeCloneable;
=======
>>>>>>> Draft of the new animation system
import java.io.IOException; import java.io.IOException;
import java.util.BitSet; import java.util.BitSet;

@ -140,6 +140,9 @@ public final class MatParamOverride extends MatParam {
super.write(ex); super.write(ex);
OutputCapsule oc = ex.getCapsule(this); OutputCapsule oc = ex.getCapsule(this);
oc.write(enabled, "enabled", true); oc.write(enabled, "enabled", true);
if (value == null) {
oc.write(true, "isNull", false);
}
} }
@Override @Override
@ -147,5 +150,9 @@ public final class MatParamOverride extends MatParam {
super.read(im); super.read(im);
InputCapsule ic = im.getCapsule(this); InputCapsule ic = im.getCapsule(this);
enabled = ic.readBoolean("enabled", true); enabled = ic.readBoolean("enabled", true);
boolean isNull = ic.readBoolean("isNull", false);
if (isNull) {
setValue(null);
}
} }
} }

@ -836,7 +836,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
* *
* @param renderManager The render manager to preload for * @param renderManager The render manager to preload for
*/ */
public void preload(RenderManager renderManager) { public void preload(RenderManager renderManager, Geometry geometry) {
if (technique == null) { if (technique == null) {
selectTechnique(TechniqueDef.DEFAULT_TECHNIQUE_NAME, renderManager); selectTechnique(TechniqueDef.DEFAULT_TECHNIQUE_NAME, renderManager);
} }
@ -847,9 +847,11 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
if (techniqueDef.isNoRender()) { if (techniqueDef.isNoRender()) {
return; return;
} }
// Get world overrides
SafeArrayList<MatParamOverride> overrides = geometry.getWorldMatParamOverrides();
Shader shader = technique.makeCurrent(renderManager, null, null, null, rendererCaps); Shader shader = technique.makeCurrent(renderManager, overrides, null, null, rendererCaps);
updateShaderMaterialParameters(renderer, shader, null, null); updateShaderMaterialParameters(renderer, shader, overrides, null);
renderManager.getRenderer().setShader(shader); renderManager.getRenderer().setShader(shader);
} }

@ -660,7 +660,7 @@ public class RenderManager {
throw new IllegalStateException("No material is set for Geometry: " + gm.getName()); throw new IllegalStateException("No material is set for Geometry: " + gm.getName());
} }
gm.getMaterial().preload(this); gm.getMaterial().preload(this, gm);
Mesh mesh = gm.getMesh(); Mesh mesh = gm.getMesh();
if (mesh != null if (mesh != null
&& mesh.getVertexCount() != 0 && mesh.getVertexCount() != 0

@ -95,6 +95,7 @@ public class Geometry extends Spatial {
// a Morph target that will be used to merge all targets that // a Morph target that will be used to merge all targets that
// can't be handled on the cpu on each frame. // can't be handled on the cpu on each frame.
private MorphTarget fallbackMorphTarget; private MorphTarget fallbackMorphTarget;
private int nbSimultaneousGPUMorph = -1;
/** /**
* Serialization only. Do not use. * Serialization only. Do not use.
@ -258,7 +259,7 @@ public class Geometry extends Spatial {
@Override @Override
public void setMaterial(Material material) { public void setMaterial(Material material) {
this.material = material; this.material = material;
nbSimultaneousGPUMorph = -1;
if (isGrouped()) { if (isGrouped()) {
groupNode.onMaterialChange(this); groupNode.onMaterialChange(this);
} }
@ -600,10 +601,28 @@ public class Geometry extends Spatial {
this.dirtyMorph = true; this.dirtyMorph = true;
} }
/**
* returns true if the morph state has changed on the last frame.
* @return
*/
public boolean isDirtyMorph() { public boolean isDirtyMorph() {
return dirtyMorph; return dirtyMorph;
} }
/**
* Seting this to true will stop this geometry morph buffer to be updated,
* unless the morph state changes
* @param dirtyMorph
*/
public void setDirtyMorph(boolean dirtyMorph) {
this.dirtyMorph = dirtyMorph;
}
/**
* returns the morph state of this Geometry.
* Used internally by the MorphControl.
* @return
*/
public float[] getMorphState() { public float[] getMorphState() {
if (morphState == null) { if (morphState == null) {
morphState = new float[mesh.getMorphTargets().length]; morphState = new float[mesh.getMorphTargets().length];
@ -611,6 +630,29 @@ public class Geometry extends Spatial {
return morphState; return morphState;
} }
/**
* Return the number of morph targets that can be handled on the GPU simultaneously for this geometry.
* Note that it depends on the material set on this geometry.
* This number is computed and set by the MorphControl, so it might be available only after the first frame.
* Else it's set to -1.
* @return the number of simultaneous morph targets handled on the GPU
*/
public int getNbSimultaneousGPUMorph() {
return nbSimultaneousGPUMorph;
}
/**
* Sets the number of morph targets that can be handled on the GPU simultaneously for this geometry.
* Note that it depends on the material set on this geometry.
* This number is computed and set by the MorphControl, so it might be available only after the first frame.
* Else it's set to -1.
* WARNING: setting this manually might crash the shader compilation if set too high. Do it at your own risk.
* @param nbSimultaneousGPUMorph the number of simultaneous morph targets to be handled on the GPU.
*/
public void setNbSimultaneousGPUMorph(int nbSimultaneousGPUMorph) {
this.nbSimultaneousGPUMorph = nbSimultaneousGPUMorph;
}
public MorphTarget getFallbackMorphTarget() { public MorphTarget getFallbackMorphTarget() {
return fallbackMorphTarget; return fallbackMorphTarget;
} }

@ -1573,7 +1573,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
} }
out.write(lodLevels, "lodLevels", null); out.write(lodLevels, "lodLevels", null);
out.writeSavableArrayList(new ArrayList(morphTargets), "morphTargets", null); if (morphTargets != null) {
out.writeSavableArrayList(new ArrayList(morphTargets), "morphTargets", null);
}
} }
@Override @Override

@ -216,15 +216,16 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
/** /**
* Morph animations targets. * Morph animations targets.
* Supports up tp 8 morph target buffers at the same time * Supports up tp 14 morph target buffers at the same time
* Limited due to the limited number of attributes you can bind to a vertex shader usually 16 * Limited due to the limited number of attributes you can bind to a vertex shader usually 16
* <p> * <p>
* MorphTarget buffers are either POSITION, NORMAL or TANGENT buffers. * MorphTarget buffers are either POSITION, NORMAL or TANGENT buffers.
* So we can support up to * So we can support up to
* 10 simultaneous POSITION targets * 14 simultaneous POSITION targets
* 5 simultaneous POSITION and NORMAL targets * 7 simultaneous POSITION and NORMAL targets
* 3 simultaneous POSTION, NORMAL and TANGENT targets. * 4 simultaneous POSTION, NORMAL and TANGENT targets.
* <p> * <p>
* Note that the MorphControl will find how many buffers can be supported for each mesh/material combination.
* Note that all buffers have 3 components (Vector3f) even the Tangent buffer that * Note that all buffers have 3 components (Vector3f) even the Tangent buffer that
* does not contain the w (handedness) component that will not be interpolated for morph animation. * does not contain the w (handedness) component that will not be interpolated for morph animation.
* <p> * <p>

@ -116,15 +116,15 @@ public class TestGltfLoading extends SimpleApplication {
// rootNode.addLight(pl1); // rootNode.addLight(pl1);
//loadModel("Models/gltf/polly/project_polly.gltf", new Vector3f(0, 0, 0), 0.5f); //loadModel("Models/gltf/polly/project_polly.gltf", new Vector3f(0, 0, 0), 0.5f);
loadModel("Models/gltf/zophrac/scene.gltf", new Vector3f(0, 0, 0), 0.1f); //loadModel("Models/gltf/zophrac/scene.gltf", new Vector3f(0, 0, 0), 0.1f);
// loadModel("Models/gltf/scifigirl/scene.gltf", new Vector3f(0, -1, 0), 0.1f); // loadModel("Models/gltf/scifigirl/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
//loadModel("Models/gltf/man/scene.gltf", new Vector3f(0, -1, 0), 0.1f); //loadModel("Models/gltf/man/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
//loadModel("Models/gltf/torus/scene.gltf", new Vector3f(0, -1, 0), 0.1f); //loadModel("Models/gltf/torus/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
//loadModel("Models/gltf/morph/scene.gltf", new Vector3f(0, 0, 0), 0.2f); //loadModel("Models/gltf/morph/scene.gltf", new Vector3f(0, 0, 0), 0.2f);
//loadModel("Models/gltf/morphCube/AnimatedMorphCube.gltf", new Vector3f(0, 0, 0), 1f); //loadModel("Models/gltf/morphCube/AnimatedMorphCube.gltf", new Vector3f(0, 0, 0), 1f);
// loadModel("Models/gltf/morph/SimpleMorph.gltf", new Vector3f(0, 0, 0), 0.1f); // loadModel("Models/gltf/morph/SimpleMorph.gltf", new Vector3f(0, 0, 0), 0.1f);
//loadModel("Models/gltf/nier/scene.gltf", new Vector3f(0, -1.5f, 0), 0.01f); //loadModel("Models/gltf/nier/scene.gltf", new Vector3f(0, -1.5f, 0), 0.01f);
//loadModel("Models/gltf/izzy/scene.gltf", new Vector3f(0, -1, 0), 0.01f); loadModel("Models/gltf/izzy/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
//loadModel("Models/gltf/darth/scene.gltf", new Vector3f(0, -1, 0), 0.01f); //loadModel("Models/gltf/darth/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
//loadModel("Models/gltf/mech/scene.gltf", new Vector3f(0, -1, 0), 0.01f); //loadModel("Models/gltf/mech/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
//loadModel("Models/gltf/elephant/scene.gltf", new Vector3f(0, -1, 0), 0.01f); //loadModel("Models/gltf/elephant/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
@ -132,7 +132,7 @@ public class TestGltfLoading extends SimpleApplication {
//loadModel("Models/gltf/war/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/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/crab/scene.gltf", Vector3f.ZERO, 1);
//loadModel("Models/gltf/manta/scene.gltf", Vector3f.ZERO, 0.2f); //loadModel("Models/gltf/manta/scene.gltf", Vector3f.ZERO, 0.2f);
//loadModel("Models/gltf/bone/scene.gltf", Vector3f.ZERO, 0.1f); //loadModel("Models/gltf/bone/scene.gltf", Vector3f.ZERO, 0.1f);
@ -213,7 +213,7 @@ public class TestGltfLoading extends SimpleApplication {
dumpScene(rootNode, 0); dumpScene(rootNode, 0);
stateManager.attach(new DetailedProfilerState()); // stateManager.attach(new DetailedProfilerState());
} }
private <T extends Control> T findControl(Spatial s, Class<T> controlClass) { private <T extends Control> T findControl(Spatial s, Class<T> controlClass) {

@ -1,7 +1,6 @@
package jme3test.model.anim; package jme3test.model.anim;
import com.jme3.anim.AnimComposer; import com.jme3.anim.*;
import com.jme3.anim.SkinningControl;
import com.jme3.anim.util.AnimMigrationUtils; import com.jme3.anim.util.AnimMigrationUtils;
import com.jme3.app.ChaseCameraAppState; import com.jme3.app.ChaseCameraAppState;
import com.jme3.app.SimpleApplication; import com.jme3.app.SimpleApplication;
@ -26,7 +25,7 @@ import java.util.Queue;
/** /**
* Created by Nehon on 18/12/2017. * Created by Nehon on 18/12/2017.
*/ */
public class TestAnimSerialization extends SimpleApplication { public class TestAnimMorphSerialization extends SimpleApplication {
ArmatureDebugAppState debugAppState; ArmatureDebugAppState debugAppState;
AnimComposer composer; AnimComposer composer;
@ -35,7 +34,7 @@ public class TestAnimSerialization extends SimpleApplication {
File file; File file;
public static void main(String... argv) { public static void main(String... argv) {
TestAnimSerialization app = new TestAnimSerialization(); TestAnimMorphSerialization app = new TestAnimMorphSerialization();
app.start(); app.start();
} }
@ -44,15 +43,14 @@ public class TestAnimSerialization extends SimpleApplication {
setTimer(new EraseTimer()); setTimer(new EraseTimer());
//cam.setFrustumPerspective(90f, (float) cam.getWidth() / cam.getHeight(), 0.01f, 10f); //cam.setFrustumPerspective(90f, (float) cam.getWidth() / cam.getHeight(), 0.01f, 10f);
viewPort.setBackgroundColor(ColorRGBA.DarkGray); viewPort.setBackgroundColor(ColorRGBA.DarkGray);
rootNode.addLight(new DirectionalLight(new Vector3f(-1, -1, -1).normalizeLocal())); //rootNode.addLight(new DirectionalLight(new Vector3f(-1, -1, -1).normalizeLocal()));
rootNode.addLight(new AmbientLight(ColorRGBA.DarkGray)); //rootNode.addLight(new AmbientLight(ColorRGBA.DarkGray));
Node probeNode = (Node) assetManager.loadModel("Scenes/defaultProbe.j3o");
Spatial model = assetManager.loadModel("Models/Jaime/Jaime.j3o"); rootNode.attachChild(probeNode);
Spatial model = assetManager.loadModel("Models/gltf/zophrac/scene.gltf");
AnimMigrationUtils.migrate(model);
File storageFolder = JmeSystem.getStorageFolder(); File storageFolder = JmeSystem.getStorageFolder();
file = new File(storageFolder.getPath() + File.separator + "newJaime.j3o"); file = new File(storageFolder.getPath() + File.separator + "zophrac.j3o");
BinaryExporter be = new BinaryExporter(); BinaryExporter be = new BinaryExporter();
try { try {
be.save(model, file); be.save(model, file);
@ -61,20 +59,20 @@ public class TestAnimSerialization extends SimpleApplication {
} }
assetManager.registerLocator(storageFolder.getPath(), FileLocator.class); assetManager.registerLocator(storageFolder.getPath(), FileLocator.class);
model = assetManager.loadModel("newJaime.j3o"); Spatial model2 = assetManager.loadModel("zophrac.j3o");
model2.setLocalScale(0.1f);
rootNode.attachChild(model); probeNode.attachChild(model2);
debugAppState = new ArmatureDebugAppState(); debugAppState = new ArmatureDebugAppState();
stateManager.attach(debugAppState); stateManager.attach(debugAppState);
setupModel(model); setupModel(model2);
flyCam.setEnabled(false); flyCam.setEnabled(false);
Node target = new Node("CamTarget"); Node target = new Node("CamTarget");
//target.setLocalTransform(model.getLocalTransform()); //target.setLocalTransform(model.getLocalTransform());
target.move(0, 1, 0); target.move(0, 0, 0);
ChaseCameraAppState chaseCam = new ChaseCameraAppState(); ChaseCameraAppState chaseCam = new ChaseCameraAppState();
chaseCam.setTarget(target); chaseCam.setTarget(target);
getStateManager().attach(chaseCam); getStateManager().attach(chaseCam);
@ -130,10 +128,14 @@ public class TestAnimSerialization extends SimpleApplication {
} }
composer = model.getControl(AnimComposer.class); composer = model.getControl(AnimComposer.class);
if (composer != null) { if (composer != null) {
// model.getControl(SkinningControl.class).setEnabled(false);
// model.getControl(MorphControl.class).setEnabled(false);
// composer.setEnabled(false);
SkinningControl sc = model.getControl(SkinningControl.class);
SkinningControl sc = model.getControl(SkinningControl.class);
debugAppState.addArmatureFrom(sc); debugAppState.addArmatureFrom(sc);
anims.clear(); anims.clear();
for (String name : composer.getAnimClipsNames()) { for (String name : composer.getAnimClipsNames()) {
anims.add(name); anims.add(name);

@ -52,7 +52,7 @@ public class TestArmature extends SimpleApplication {
final Armature armature = new Armature(joints); final Armature armature = new Armature(joints);
//armature.setModelTransformClass(SeparateJointModelTransform.class); //armature.setModelTransformClass(SeparateJointModelTransform.class);
armature.setBindPose(); armature.saveBindPose();
//create animations //create animations
AnimClip clip = new AnimClip("anim"); AnimClip clip = new AnimClip("anim");
@ -131,7 +131,7 @@ public class TestArmature extends SimpleApplication {
public void onAction(String name, boolean isPressed, float tpf) { public void onAction(String name, boolean isPressed, float tpf) {
if (isPressed) { if (isPressed) {
composer.reset(); composer.reset();
armature.resetToBindPose(); armature.applyBindPose();
} else { } else {
composer.setCurrentAction("anim"); composer.setCurrentAction("anim");

@ -59,7 +59,7 @@ public class TestBaseAnimSerialization extends SimpleApplication {
armature = new Armature(joints); armature = new Armature(joints);
//armature.setModelTransformClass(SeparateJointModelTransform.class); //armature.setModelTransformClass(SeparateJointModelTransform.class);
armature.setBindPose(); armature.saveBindPose();
//create animations //create animations
AnimClip clip = new AnimClip("anim"); AnimClip clip = new AnimClip("anim");
@ -153,7 +153,7 @@ public class TestBaseAnimSerialization extends SimpleApplication {
public void onAction(String name, boolean isPressed, float tpf) { public void onAction(String name, boolean isPressed, float tpf) {
if (isPressed) { if (isPressed) {
composer.reset(); composer.reset();
armature.resetToBindPose(); armature.applyBindPose();
} else { } else {
composer.setCurrentAction("anim"); composer.setCurrentAction("anim");

@ -1009,6 +1009,7 @@ public class GltfLoader implements AssetLoader {
skinnedSpatials.put(skinData, new ArrayList<Spatial>()); skinnedSpatials.put(skinData, new ArrayList<Spatial>());
armature.update(); armature.update();
armature.saveInitialPose();
} }
} }

@ -161,7 +161,7 @@ public class SkeletonLoader extends DefaultHandler implements AssetLoader {
} }
indexToJoint.clear(); indexToJoint.clear();
armature = new Armature(joints); armature = new Armature(joints);
armature.setBindPose(); armature.saveBindPose();
} else if (qName.equals("animation")) { } else if (qName.equals("animation")) {
animClips.add(animClip); animClips.add(animClip);
animClip = null; animClip = null;

Loading…
Cancel
Save