Adds a cpu fallback for morph animation when all morph targets cannot be handled on the gpu

shader-nodes-enhancement
Nehon 7 years ago committed by Rémy Bouquet
parent 42215f4890
commit 4048f8ba26
  1. 181
      jme3-core/src/main/java/com/jme3/anim/MorphControl.java
  2. 5
      jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java
  3. 43
      jme3-core/src/main/java/com/jme3/scene/Geometry.java
  4. 63
      jme3-core/src/main/java/com/jme3/scene/Mesh.java
  5. 19
      jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java
  6. 17
      jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java
  7. 168
      jme3-examples/src/main/java/jme3test/model/anim/TestAnimMorphSerialization.java
  8. 11
      jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java
  9. 5
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java

@ -9,7 +9,9 @@ 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;
import com.jme3.util.BufferUtils;
import com.jme3.util.SafeArrayList; import com.jme3.util.SafeArrayList;
import javafx.geometry.Pos;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
@ -17,6 +19,7 @@ import java.nio.FloatBuffer;
* A control that handle morph animation for Position, Normal and Tangent buffers. * A control that handle morph animation for Position, Normal and Tangent buffers.
* All stock shaders only support morphing these 3 buffers, but note that MorphTargets can have any type of buffers. * All stock shaders only support morphing these 3 buffers, but note that MorphTargets can have any type of buffers.
* If you want to use other types of buffers you will need a custom MorphControl and a custom shader. * If you want to use other types of buffers you will need a custom MorphControl and a custom shader.
*
* @author Rémy Bouquet * @author Rémy Bouquet
*/ */
public class MorphControl extends AbstractControl { public class MorphControl extends AbstractControl {
@ -30,8 +33,17 @@ public class MorphControl extends AbstractControl {
private boolean approximateTangents = true; private boolean approximateTangents = true;
private MatParamOverride nullNumberOfBones = new MatParamOverride(VarType.Int, "NumberOfBones", null); private MatParamOverride nullNumberOfBones = new MatParamOverride(VarType.Int, "NumberOfBones", null);
private float[] tmpPosArray;
private float[] tmpNormArray;
private float[] tmpTanArray;
private static final VertexBuffer.Type bufferTypes[] = VertexBuffer.Type.values();
@Override @Override
protected void controlUpdate(float tpf) { protected void controlUpdate(float tpf) {
if (!enabled) {
return;
}
// gathering geometries in the sub graph. // gathering geometries in the sub graph.
// This must be done in the update phase as the gathering might add a matparam override // This must be done in the update phase as the gathering might add a matparam override
targets.clear(); targets.clear();
@ -40,15 +52,18 @@ public class MorphControl extends AbstractControl {
@Override @Override
protected void controlRender(RenderManager rm, ViewPort vp) { protected void controlRender(RenderManager rm, ViewPort vp) {
if (!enabled) {
return;
}
for (Geometry target : targets) { for (Geometry target : targets) {
Mesh mesh = target.getMesh(); Mesh mesh = target.getMesh();
if (!mesh.isDirtyMorph()) { if (!target.isDirtyMorph()) {
continue; continue;
} }
int nbMaxBuffers = getRemainingBuffers(mesh, rm.getRenderer()); int nbMaxBuffers = getRemainingBuffers(mesh, rm.getRenderer());
Material m = target.getMaterial(); Material m = target.getMaterial();
float weights[] = mesh.getMorphState(); float weights[] = target.getMorphState();
MorphTarget morphTargets[] = mesh.getMorphTargets(); MorphTarget morphTargets[] = mesh.getMorphTargets();
float matWeights[]; float matWeights[];
MatParam param = m.getParam("MorphWeights"); MatParam param = m.getParam("MorphWeights");
@ -69,45 +84,171 @@ public class MorphControl extends AbstractControl {
m.setInt("NumberOfTargetsBuffers", targetNumBuffers); m.setInt("NumberOfTargetsBuffers", targetNumBuffers);
int nbGPUTargets = 0; int nbGPUTargets = 0;
int nbCPUBuffers = 0; int lastGpuTargetIndex = 0;
int boundBufferIdx = 0; int boundBufferIdx = 0;
float cpuWeightSum = 0;
// binding the morphTargets buffer to the mesh morph buffers
for (int i = 0; i < morphTargets.length; i++) { for (int i = 0; i < morphTargets.length; i++) {
// discard weights below the threshold
if (weights[i] < MIN_WEIGHT) { if (weights[i] < MIN_WEIGHT) {
continue; continue;
} }
if (nbGPUTargets >= maxGPUTargets) { if (nbGPUTargets >= maxGPUTargets) {
//TODO we should fallback to CPU there. // we already bound all the available gpu slots we need to merge the remaining morph targets.
nbCPUBuffers++; cpuWeightSum += weights[i];
continue; continue;
} }
int start = VertexBuffer.Type.MorphTarget0.ordinal(); lastGpuTargetIndex = i;
// binding the morph target's buffers to the mesh morph buffers.
MorphTarget t = morphTargets[i]; MorphTarget t = morphTargets[i];
if (targetNumBuffers >= 1) { boundBufferIdx = bindMorphtargetBuffer(mesh, targetNumBuffers, boundBufferIdx, t);
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Position)); // setting the weight in the mat param array
boundBufferIdx++;
}
if (targetNumBuffers >= 2) {
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Normal));
boundBufferIdx++;
}
if (!approximateTangents && targetNumBuffers == 3) {
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Tangent));
boundBufferIdx++;
}
matWeights[nbGPUTargets] = weights[i]; matWeights[nbGPUTargets] = weights[i];
nbGPUTargets++; nbGPUTargets++;
} }
if (nbGPUTargets < matWeights.length) { if (nbGPUTargets < matWeights.length) {
// if we have less simultaneous GPU targets than the length of the weight array, the array is padded with 0
for (int i = nbGPUTargets; i < matWeights.length; i++) { for (int i = nbGPUTargets; i < matWeights.length; i++) {
matWeights[i] = 0; matWeights[i] = 0;
} }
} else if (cpuWeightSum > 0) {
// we have more simultaneous morph targets than available gpu slots,
// we merge the additional morph targets and bind them to the last gpu slot
MorphTarget mt = target.getFallbackMorphTarget();
if (mt == null) {
mt = initCpuMorphTarget(target);
target.setFallbackMorphTarget(mt);
}
// adding the last Gpu target weight
cpuWeightSum += matWeights[nbGPUTargets - 1];
ensureTmpArraysCapacity(target.getVertexCount() * 3, targetNumBuffers);
// merging all remaining targets in tmp arrays
for (int i = lastGpuTargetIndex; i < morphTargets.length; i++) {
if (weights[i] < MIN_WEIGHT) {
continue;
}
float weight = weights[i] / cpuWeightSum;
MorphTarget t = target.getMesh().getMorphTargets()[i];
mergeMorphTargets(targetNumBuffers, weight, t, i == lastGpuTargetIndex);
}
// writing the tmp arrays to the float buffer
writeCpuBuffer(targetNumBuffers, mt);
// binding the merged morph target
bindMorphtargetBuffer(mesh, targetNumBuffers, (nbGPUTargets - 1) * targetNumBuffers, mt);
// setting the eight of the merged targets
matWeights[nbGPUTargets - 1] = cpuWeightSum;
} }
} }
} }
private int bindMorphtargetBuffer(Mesh mesh, int targetNumBuffers, int boundBufferIdx, MorphTarget t) {
int start = VertexBuffer.Type.MorphTarget0.ordinal();
if (targetNumBuffers >= 1) {
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Position));
boundBufferIdx++;
}
if (targetNumBuffers >= 2) {
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Normal));
boundBufferIdx++;
}
if (!approximateTangents && targetNumBuffers == 3) {
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Tangent));
boundBufferIdx++;
}
return boundBufferIdx;
}
private void writeCpuBuffer(int targetNumBuffers, MorphTarget mt) {
if (targetNumBuffers >= 1) {
FloatBuffer dest = mt.getBuffer(VertexBuffer.Type.Position);
dest.rewind();
dest.put(tmpPosArray, 0, dest.capacity());
}
if (targetNumBuffers >= 2) {
FloatBuffer dest = mt.getBuffer(VertexBuffer.Type.Normal);
dest.rewind();
dest.put(tmpNormArray, 0, dest.capacity());
}
if (!approximateTangents && targetNumBuffers == 3) {
FloatBuffer dest = mt.getBuffer(VertexBuffer.Type.Tangent);
dest.rewind();
dest.put(tmpTanArray, 0, dest.capacity());
}
}
private void mergeMorphTargets(int targetNumBuffers, float weight, MorphTarget t, boolean init) {
if (targetNumBuffers >= 1) {
mergeTargetBuffer(tmpPosArray, weight, t.getBuffer(VertexBuffer.Type.Position), init);
}
if (targetNumBuffers >= 2) {
mergeTargetBuffer(tmpNormArray, weight, t.getBuffer(VertexBuffer.Type.Normal), init);
}
if (!approximateTangents && targetNumBuffers == 3) {
mergeTargetBuffer(tmpTanArray, weight, t.getBuffer(VertexBuffer.Type.Tangent), init);
}
}
private void ensureTmpArraysCapacity(int capacity, int targetNumBuffers) {
if (targetNumBuffers >= 1) {
tmpPosArray = ensureCapacity(tmpPosArray, capacity);
}
if (targetNumBuffers >= 2) {
tmpNormArray = ensureCapacity(tmpNormArray, capacity);
}
if (!approximateTangents && targetNumBuffers == 3) {
tmpTanArray = ensureCapacity(tmpTanArray, capacity);
}
}
private void mergeTargetBuffer(float[] array, float weight, FloatBuffer src, boolean init) {
src.rewind();
for (int j = 0; j < src.capacity(); j++) {
if (init) {
array[j] = 0;
}
array[j] += weight * src.get();
}
}
private void activateBuffer(Mesh mesh, int idx, int start, FloatBuffer b) { private void activateBuffer(Mesh mesh, int idx, int start, FloatBuffer b) {
mesh.setBuffer(VertexBuffer.Type.values()[start + idx], 3, b); VertexBuffer.Type t = bufferTypes[start + idx];
VertexBuffer vb = mesh.getBuffer(t);
// only set the buffer if it's different
if (vb == null || vb.getData() != b) {
mesh.setBuffer(t, 3, b);
}
}
private float[] ensureCapacity(float[] tmpArray, int size) {
if (tmpArray == null || tmpArray.length < size) {
return new float[size];
}
return tmpArray;
}
private MorphTarget initCpuMorphTarget(Geometry geom) {
MorphTarget res = new MorphTarget();
MorphTarget mt = geom.getMesh().getMorphTargets()[0];
FloatBuffer b = mt.getBuffer(VertexBuffer.Type.Position);
if (b != null) {
res.setBuffer(VertexBuffer.Type.Position, BufferUtils.createFloatBuffer(b.capacity()));
}
b = mt.getBuffer(VertexBuffer.Type.Normal);
if (b != null) {
res.setBuffer(VertexBuffer.Type.Normal, BufferUtils.createFloatBuffer(b.capacity()));
}
if (!approximateTangents) {
b = mt.getBuffer(VertexBuffer.Type.Tangent);
if (b != null) {
res.setBuffer(VertexBuffer.Type.Tangent, BufferUtils.createFloatBuffer(b.capacity()));
}
}
return res;
} }
private int getTargetNumBuffers(MorphTarget morphTarget) { private int getTargetNumBuffers(MorphTarget morphTarget) {

@ -45,10 +45,9 @@ public class ClipAction extends BlendableAction {
} }
private void interpolateMorphTrack(double t, MorphTrack track) { private void interpolateMorphTrack(double t, MorphTrack track) {
Geometry target = track.getTarget(); Geometry target = track.getTarget();
float[] weights = new float[target.getMesh().getMorphTargets().length]; float[] weights = target.getMorphState();
track.getDataAtTime(t, weights); track.getDataAtTime(t, weights);
target.getMesh().setMorphState(weights); target.setMorphState(weights);
// if (collectTransformDelegate != null) { // if (collectTransformDelegate != null) {
// collectTransformDelegate.collectTransform(target, transform, getWeight(), this); // collectTransformDelegate.collectTransform(target, transform, getWeight(), this);

@ -43,6 +43,7 @@ import com.jme3.material.Material;
import com.jme3.math.Matrix4f; import com.jme3.math.Matrix4f;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.mesh.MorphTarget;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner; import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.IdentityCloneFunction; import com.jme3.util.clone.IdentityCloneFunction;
@ -86,6 +87,15 @@ public class Geometry extends Spatial {
*/ */
protected int startIndex = -1; protected int startIndex = -1;
/**
* Morph state variable for morph animation
*/
private float[] morphState;
private boolean dirtyMorph = true;
// a Morph target that will be used to merge all targets that
// can't be handled on the cpu on each frame.
private MorphTarget fallbackMorphTarget;
/** /**
* Serialization only. Do not use. * Serialization only. Do not use.
*/ */
@ -576,6 +586,39 @@ public class Geometry extends Spatial {
this.material = cloner.clone(material); this.material = cloner.clone(material);
} }
public void setMorphState(float[] state) {
if (mesh == null || mesh.getMorphTargets().length == 0){
return;
}
int nbMorphTargets = mesh.getMorphTargets().length;
if (morphState == null) {
morphState = new float[nbMorphTargets];
}
System.arraycopy(state, 0, morphState, 0, morphState.length);
this.dirtyMorph = true;
}
public boolean isDirtyMorph() {
return dirtyMorph;
}
public float[] getMorphState() {
if (morphState == null) {
morphState = new float[mesh.getMorphTargets().length];
}
return morphState;
}
public MorphTarget getFallbackMorphTarget() {
return fallbackMorphTarget;
}
public void setFallbackMorphTarget(MorphTarget fallbackMorphTarget) {
this.fallbackMorphTarget = fallbackMorphTarget;
}
@Override @Override
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
super.write(ex); super.write(ex);

@ -185,9 +185,6 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
private Mode mode = Mode.Triangles; private Mode mode = Mode.Triangles;
private SafeArrayList<MorphTarget> morphTargets; private SafeArrayList<MorphTarget> morphTargets;
private int numMorphBuffers = 0;
private float[] morphState;
private boolean dirtyMorph = true;
/** /**
* Creates a new mesh with no {@link VertexBuffer vertex buffers}. * Creates a new mesh with no {@link VertexBuffer vertex buffers}.
@ -1527,52 +1524,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
if (morphTargets == null) { if (morphTargets == null) {
morphTargets = new SafeArrayList<>(MorphTarget.class); morphTargets = new SafeArrayList<>(MorphTarget.class);
} }
// if (numMorphBuffers == 0) {
// numMorphBuffers = target.getNumBuffers();
// int start = Type.MorphTarget0.ordinal();
// int end = start + numMorphBuffers;
// for (int i = start; i < end; i++) {
// VertexBuffer vb = new VertexBuffer(Type.values()[i]);
// setBuffer(vb);
// }
// } else if (target.getNumBuffers() != numMorphBuffers) {
// throw new IllegalArgumentException("Morph target has different number of buffers");
// }
morphTargets.add(target); morphTargets.add(target);
} }
public void setMorphState(float[] state) {
if (morphTargets.isEmpty()) {
return;
}
if (morphState == null) {
morphState = new float[morphTargets.size()];
}
System.arraycopy(state, 0, morphState, 0, morphState.length);
this.dirtyMorph = true;
}
public float[] getMorphState() {
if (morphState == null) {
morphState = new float[morphTargets.size()];
}
return morphState;
}
public void setActiveMorphTargets(int... targetsIndex) {
int start = Type.MorphTarget0.ordinal();
for (int i = 0; i < targetsIndex.length; i++) {
MorphTarget t = morphTargets.get(targetsIndex[i]);
int idx = 0;
for (Type type : t.getBuffers().keySet()) {
FloatBuffer b = t.getBuffer(type);
setBuffer(Type.values()[start + i + idx], 3, b);
idx++;
}
}
}
public MorphTarget[] getMorphTargets() { public MorphTarget[] getMorphTargets() {
return morphTargets.getArray(); return morphTargets.getArray();
} }
@ -1581,21 +1535,10 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
return morphTargets != null && !morphTargets.isEmpty(); return morphTargets != null && !morphTargets.isEmpty();
} }
public boolean isDirtyMorph() {
return dirtyMorph;
}
@Override @Override
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
OutputCapsule out = ex.getCapsule(this); OutputCapsule out = ex.getCapsule(this);
// HashMap<String, VertexBuffer> map = new HashMap<String, VertexBuffer>();
// for (Entry<VertexBuffer> buf : buffers){
// if (buf.getValue() != null)
// map.put(buf.getKey()+"a", buf.getValue());
// }
// out.writeStringSavableMap(map, "buffers", null);
out.write(meshBound, "modelBound", null); out.write(meshBound, "modelBound", null);
out.write(vertCount, "vertCount", -1); out.write(vertCount, "vertCount", -1);
out.write(elementCount, "elementCount", -1); out.write(elementCount, "elementCount", -1);
@ -1630,6 +1573,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
} }
out.write(lodLevels, "lodLevels", null); out.write(lodLevels, "lodLevels", null);
out.writeSavableArrayList(new ArrayList(morphTargets), "morphTargets", null);
} }
@Override @Override
@ -1669,6 +1613,11 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
lodLevels = new VertexBuffer[lodLevelsSavable.length]; lodLevels = new VertexBuffer[lodLevelsSavable.length];
System.arraycopy( lodLevelsSavable, 0, lodLevels, 0, lodLevels.length); System.arraycopy( lodLevelsSavable, 0, lodLevels, 0, lodLevels.length);
} }
ArrayList<Savable> l = in.readSavableArrayList("morphTargets", null);
if (l != null) {
morphTargets = new SafeArrayList(MorphTarget.class, l);
}
} }
} }

@ -1,13 +1,13 @@
package com.jme3.scene.mesh; package com.jme3.scene.mesh;
import com.jme3.export.JmeExporter; import com.jme3.export.*;
import com.jme3.export.JmeImporter;
import com.jme3.export.Savable;
import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer;
import java.io.IOException; import java.io.IOException;
import java.nio.Buffer;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.Map;
public class MorphTarget implements Savable { public class MorphTarget implements Savable {
private EnumMap<VertexBuffer.Type, FloatBuffer> buffers = new EnumMap<>(VertexBuffer.Type.class); private EnumMap<VertexBuffer.Type, FloatBuffer> buffers = new EnumMap<>(VertexBuffer.Type.class);
@ -30,11 +30,22 @@ public class MorphTarget implements Savable {
@Override @Override
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
for (Map.Entry<VertexBuffer.Type, FloatBuffer> entry : buffers.entrySet()) {
Buffer roData = entry.getValue().asReadOnlyBuffer();
oc.write((FloatBuffer) roData, entry.getKey().name(),null);
}
} }
@Override @Override
public void read(JmeImporter im) throws IOException { public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
for (VertexBuffer.Type type : VertexBuffer.Type.values()) {
FloatBuffer b = ic.readFloatBuffer(type.name(), null);
if(b!= null){
setBuffer(type, b);
}
}
} }
} }

@ -33,8 +33,7 @@ package jme3test.model;
import com.jme3.anim.AnimComposer; import com.jme3.anim.AnimComposer;
import com.jme3.anim.SkinningControl; import com.jme3.anim.SkinningControl;
import com.jme3.app.ChaseCameraAppState; import com.jme3.app.*;
import com.jme3.app.SimpleApplication;
import com.jme3.asset.plugins.FileLocator; import com.jme3.asset.plugins.FileLocator;
import com.jme3.input.KeyInput; import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.ActionListener;
@ -45,7 +44,6 @@ import com.jme3.scene.*;
import com.jme3.scene.control.Control; import com.jme3.scene.control.Control;
import com.jme3.scene.debug.custom.ArmatureDebugAppState; import com.jme3.scene.debug.custom.ArmatureDebugAppState;
import com.jme3.scene.plugins.gltf.GltfModelKey; import com.jme3.scene.plugins.gltf.GltfModelKey;
import com.jme3.shader.VarType;
import jme3test.model.anim.EraseTimer; import jme3test.model.anim.EraseTimer;
import java.util.*; import java.util.*;
@ -118,8 +116,8 @@ 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);
@ -214,15 +212,8 @@ public class TestGltfLoading extends SimpleApplication {
}, "nextAnim"); }, "nextAnim");
dumpScene(rootNode, 0); dumpScene(rootNode, 0);
}
public void setMorphTarget(int index) { stateManager.attach(new DetailedProfilerState());
g = (Geometry) probeNode.getChild("0");
g.getMesh().setActiveMorphTargets(index);
g.getMaterial().setInt("NumberOfMorphTargets", 1);
g.getMaterial().setInt("NumberOfTargetsBuffers", 3);
float[] weights = {1.0f};
g.getMaterial().setParam("MorphWeights", VarType.FloatArray, weights);
} }
private <T extends Control> T findControl(Spatial s, Class<T> controlClass) { private <T extends Control> T findControl(Spatial s, Class<T> controlClass) {

@ -0,0 +1,168 @@
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.asset.plugins.FileLocator;
import com.jme3.export.binary.BinaryExporter;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.math.*;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.debug.custom.ArmatureDebugAppState;
import com.jme3.system.JmeSystem;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;
/**
* Created by Nehon on 18/12/2017.
*/
public class TestAnimSerialization extends SimpleApplication {
ArmatureDebugAppState debugAppState;
AnimComposer composer;
Queue<String> anims = new LinkedList<>();
boolean playAnim = true;
File file;
public static void main(String... argv) {
TestAnimSerialization app = new TestAnimSerialization();
app.start();
}
@Override
public void simpleInitApp() {
setTimer(new EraseTimer());
//cam.setFrustumPerspective(90f, (float) cam.getWidth() / cam.getHeight(), 0.01f, 10f);
viewPort.setBackgroundColor(ColorRGBA.DarkGray);
rootNode.addLight(new DirectionalLight(new Vector3f(-1, -1, -1).normalizeLocal()));
rootNode.addLight(new AmbientLight(ColorRGBA.DarkGray));
Spatial model = assetManager.loadModel("Models/Jaime/Jaime.j3o");
AnimMigrationUtils.migrate(model);
File storageFolder = JmeSystem.getStorageFolder();
file = new File(storageFolder.getPath() + File.separator + "newJaime.j3o");
BinaryExporter be = new BinaryExporter();
try {
be.save(model, file);
} catch (IOException e) {
e.printStackTrace();
}
assetManager.registerLocator(storageFolder.getPath(), FileLocator.class);
model = assetManager.loadModel("newJaime.j3o");
rootNode.attachChild(model);
debugAppState = new ArmatureDebugAppState();
stateManager.attach(debugAppState);
setupModel(model);
flyCam.setEnabled(false);
Node target = new Node("CamTarget");
//target.setLocalTransform(model.getLocalTransform());
target.move(0, 1, 0);
ChaseCameraAppState chaseCam = new ChaseCameraAppState();
chaseCam.setTarget(target);
getStateManager().attach(chaseCam);
chaseCam.setInvertHorizontalAxis(true);
chaseCam.setInvertVerticalAxis(true);
chaseCam.setZoomSpeed(0.5f);
chaseCam.setMinVerticalRotation(-FastMath.HALF_PI);
chaseCam.setRotationSpeed(3);
chaseCam.setDefaultDistance(3);
chaseCam.setMinDistance(0.01f);
chaseCam.setZoomSpeed(0.01f);
chaseCam.setDefaultVerticalRotation(0.3f);
initInputs();
}
public void initInputs() {
inputManager.addMapping("toggleAnim", new KeyTrigger(KeyInput.KEY_RETURN));
inputManager.addListener(new ActionListener() {
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (isPressed) {
playAnim = !playAnim;
if (playAnim) {
String anim = anims.poll();
anims.add(anim);
composer.setCurrentAction(anim);
System.err.println(anim);
} else {
composer.reset();
}
}
}
}, "toggleAnim");
inputManager.addMapping("nextAnim", new KeyTrigger(KeyInput.KEY_RIGHT));
inputManager.addListener(new ActionListener() {
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (isPressed && composer != null) {
String anim = anims.poll();
anims.add(anim);
composer.setCurrentAction(anim);
System.err.println(anim);
}
}
}, "nextAnim");
}
private void setupModel(Spatial model) {
if (composer != null) {
return;
}
composer = model.getControl(AnimComposer.class);
if (composer != null) {
SkinningControl sc = model.getControl(SkinningControl.class);
debugAppState.addArmatureFrom(sc);
anims.clear();
for (String name : composer.getAnimClipsNames()) {
anims.add(name);
}
if (anims.isEmpty()) {
return;
}
if (playAnim) {
String anim = anims.poll();
anims.add(anim);
composer.setCurrentAction(anim);
System.err.println(anim);
}
} else {
if (model instanceof Node) {
Node n = (Node) model;
for (Spatial child : n.getChildren()) {
setupModel(child);
}
}
}
}
@Override
public void destroy() {
super.destroy();
file.delete();
}
}

@ -1,5 +1,6 @@
package jme3test.model.anim; package jme3test.model.anim;
import com.jme3.anim.MorphControl;
import com.jme3.app.ChaseCameraAppState; import com.jme3.app.ChaseCameraAppState;
import com.jme3.app.SimpleApplication; import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput; import com.jme3.input.KeyInput;
@ -68,16 +69,16 @@ public class TestMorph extends SimpleApplication {
target2.setBuffer(VertexBuffer.Type.Position, buffer); target2.setBuffer(VertexBuffer.Type.Position, buffer);
box.addMorphTarget(target2); box.addMorphTarget(target2);
Geometry g = new Geometry("box", box); final Geometry g = new Geometry("box", box);
final Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
g.setMaterial(m); g.setMaterial(m);
m.setColor("Color", ColorRGBA.Red); m.setColor("Color", ColorRGBA.Red);
m.setInt("NumberOfMorphTargets", 2); m.setInt("NumberOfMorphTargets", 2);
rootNode.attachChild(g); rootNode.attachChild(g);
box.setActiveMorphTargets(0,1); g.setMorphState(weights);
m.setParam("MorphWeights", VarType.FloatArray, weights); g.addControl(new MorphControl());
ChaseCameraAppState chase = new ChaseCameraAppState(); ChaseCameraAppState chase = new ChaseCameraAppState();
chase.setTarget(rootNode); chase.setTarget(rootNode);
@ -104,7 +105,7 @@ public class TestMorph extends SimpleApplication {
if (name.equals("morphdown")) { if (name.equals("morphdown")) {
weights[1] -= tpf; weights[1] -= tpf;
} }
m.setParam("MorphWeights", VarType.FloatArray, weights); g.setMorphState(weights);
} }
}, "morphup", "morphdown", "morphleft", "morphright"); }, "morphup", "morphdown", "morphleft", "morphright");

@ -803,11 +803,6 @@ public class GltfLoader implements AssetLoader {
} else { } else {
trackData.timeArrays.add(new TrackData.TimeData(times, TrackData.Type.Morph)); trackData.timeArrays.add(new TrackData.TimeData(times, TrackData.Type.Morph));
float[] weights = readAccessorData(dataIndex, floatArrayPopulator); float[] weights = readAccessorData(dataIndex, floatArrayPopulator);
Geometry g = fetchFromCache("nodes", targetNode, Geometry.class);
int expectedSize = g.getMesh().getMorphTargets().length * times.length;
if( expectedSize != weights.length ){
throw new AssetLoadException("Morph animation should contain " + expectedSize + " entries, got" + weights.length);
}
trackData.weights = weights; trackData.weights = weights;
hasMorphTrack = true; hasMorphTrack = true;
} }

Loading…
Cancel
Save