From c2fcdefd0d610186fcd53eb2e07eda4e178c0ac7 Mon Sep 17 00:00:00 2001 From: Nehon Date: Thu, 1 Mar 2018 21:03:19 +0100 Subject: [PATCH] Adds a cpu fallback for morph animation when all morph targets cannot be handled on the gpu --- .../main/java/com/jme3/anim/MorphControl.java | 181 ++++++++++++++++-- .../jme3/anim/tween/action/ClipAction.java | 5 +- .../main/java/com/jme3/scene/Geometry.java | 43 +++++ .../src/main/java/com/jme3/scene/Mesh.java | 63 +----- .../java/com/jme3/scene/mesh/MorphTarget.java | 19 +- .../java/jme3test/model/TestGltfLoading.java | 17 +- .../anim/TestAnimMorphSerialization.java | 168 ++++++++++++++++ .../java/jme3test/model/anim/TestMorph.java | 11 +- .../jme3/scene/plugins/gltf/GltfLoader.java | 5 - 9 files changed, 405 insertions(+), 107 deletions(-) create mode 100644 jme3-examples/src/main/java/jme3test/model/anim/TestAnimMorphSerialization.java diff --git a/jme3-core/src/main/java/com/jme3/anim/MorphControl.java b/jme3-core/src/main/java/com/jme3/anim/MorphControl.java index 31abc8c9c..7af716b53 100644 --- a/jme3-core/src/main/java/com/jme3/anim/MorphControl.java +++ b/jme3-core/src/main/java/com/jme3/anim/MorphControl.java @@ -9,7 +9,9 @@ import com.jme3.scene.VertexBuffer; import com.jme3.scene.control.AbstractControl; import com.jme3.scene.mesh.MorphTarget; import com.jme3.shader.VarType; +import com.jme3.util.BufferUtils; import com.jme3.util.SafeArrayList; +import javafx.geometry.Pos; import java.nio.FloatBuffer; @@ -17,6 +19,7 @@ import java.nio.FloatBuffer; * 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. * If you want to use other types of buffers you will need a custom MorphControl and a custom shader. + * * @author Rémy Bouquet */ public class MorphControl extends AbstractControl { @@ -30,8 +33,17 @@ public class MorphControl extends AbstractControl { private boolean approximateTangents = true; 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 protected void controlUpdate(float tpf) { + if (!enabled) { + return; + } // gathering geometries in the sub graph. // This must be done in the update phase as the gathering might add a matparam override targets.clear(); @@ -40,15 +52,18 @@ public class MorphControl extends AbstractControl { @Override protected void controlRender(RenderManager rm, ViewPort vp) { + if (!enabled) { + return; + } for (Geometry target : targets) { Mesh mesh = target.getMesh(); - if (!mesh.isDirtyMorph()) { + if (!target.isDirtyMorph()) { continue; } int nbMaxBuffers = getRemainingBuffers(mesh, rm.getRenderer()); Material m = target.getMaterial(); - float weights[] = mesh.getMorphState(); + float weights[] = target.getMorphState(); MorphTarget morphTargets[] = mesh.getMorphTargets(); float matWeights[]; MatParam param = m.getParam("MorphWeights"); @@ -69,45 +84,171 @@ public class MorphControl extends AbstractControl { m.setInt("NumberOfTargetsBuffers", targetNumBuffers); int nbGPUTargets = 0; - int nbCPUBuffers = 0; + int lastGpuTargetIndex = 0; int boundBufferIdx = 0; + float cpuWeightSum = 0; + // binding the morphTargets buffer to the mesh morph buffers for (int i = 0; i < morphTargets.length; i++) { + // discard weights below the threshold if (weights[i] < MIN_WEIGHT) { continue; } if (nbGPUTargets >= maxGPUTargets) { - //TODO we should fallback to CPU there. - nbCPUBuffers++; + // we already bound all the available gpu slots we need to merge the remaining morph targets. + cpuWeightSum += weights[i]; continue; } - int start = VertexBuffer.Type.MorphTarget0.ordinal(); + lastGpuTargetIndex = i; + // binding the morph target's buffers to the mesh morph buffers. MorphTarget t = morphTargets[i]; - 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++; - } + boundBufferIdx = bindMorphtargetBuffer(mesh, targetNumBuffers, boundBufferIdx, t); + // setting the weight in the mat param array matWeights[nbGPUTargets] = weights[i]; nbGPUTargets++; - } + 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++) { 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) { - 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) { diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java index ee5920828..59d24261f 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java @@ -45,10 +45,9 @@ public class ClipAction extends BlendableAction { } private void interpolateMorphTrack(double t, MorphTrack track) { Geometry target = track.getTarget(); - float[] weights = new float[target.getMesh().getMorphTargets().length]; + float[] weights = target.getMorphState(); track.getDataAtTime(t, weights); - target.getMesh().setMorphState(weights); - + target.setMorphState(weights); // if (collectTransformDelegate != null) { // collectTransformDelegate.collectTransform(target, transform, getWeight(), this); diff --git a/jme3-core/src/main/java/com/jme3/scene/Geometry.java b/jme3-core/src/main/java/com/jme3/scene/Geometry.java index 78ea0c349..e68d75969 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Geometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/Geometry.java @@ -43,6 +43,7 @@ import com.jme3.material.Material; import com.jme3.math.Matrix4f; import com.jme3.renderer.Camera; import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.mesh.MorphTarget; import com.jme3.util.TempVars; import com.jme3.util.clone.Cloner; import com.jme3.util.clone.IdentityCloneFunction; @@ -86,6 +87,15 @@ public class Geometry extends Spatial { */ 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. */ @@ -576,6 +586,39 @@ public class Geometry extends Spatial { 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 public void write(JmeExporter ex) throws IOException { super.write(ex); diff --git a/jme3-core/src/main/java/com/jme3/scene/Mesh.java b/jme3-core/src/main/java/com/jme3/scene/Mesh.java index 217c38f5c..889963697 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Mesh.java +++ b/jme3-core/src/main/java/com/jme3/scene/Mesh.java @@ -185,9 +185,6 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { private Mode mode = Mode.Triangles; private SafeArrayList morphTargets; - private int numMorphBuffers = 0; - private float[] morphState; - private boolean dirtyMorph = true; /** * Creates a new mesh with no {@link VertexBuffer vertex buffers}. @@ -1527,52 +1524,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { if (morphTargets == null) { 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); } - 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() { return morphTargets.getArray(); } @@ -1581,21 +1535,10 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { return morphTargets != null && !morphTargets.isEmpty(); } - public boolean isDirtyMorph() { - return dirtyMorph; - } - @Override public void write(JmeExporter ex) throws IOException { OutputCapsule out = ex.getCapsule(this); -// HashMap map = new HashMap(); -// for (Entry 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(vertCount, "vertCount", -1); out.write(elementCount, "elementCount", -1); @@ -1630,6 +1573,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { } out.write(lodLevels, "lodLevels", null); + out.writeSavableArrayList(new ArrayList(morphTargets), "morphTargets", null); } @Override @@ -1669,6 +1613,11 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { lodLevels = new VertexBuffer[lodLevelsSavable.length]; System.arraycopy( lodLevelsSavable, 0, lodLevels, 0, lodLevels.length); } + + ArrayList l = in.readSavableArrayList("morphTargets", null); + if (l != null) { + morphTargets = new SafeArrayList(MorphTarget.class, l); + } } } diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java b/jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java index 8f65473a2..083d04fb7 100644 --- a/jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java @@ -1,13 +1,13 @@ package com.jme3.scene.mesh; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.Savable; +import com.jme3.export.*; import com.jme3.scene.VertexBuffer; import java.io.IOException; +import java.nio.Buffer; import java.nio.FloatBuffer; import java.util.EnumMap; +import java.util.Map; public class MorphTarget implements Savable { private EnumMap buffers = new EnumMap<>(VertexBuffer.Type.class); @@ -30,11 +30,22 @@ public class MorphTarget implements Savable { @Override public void write(JmeExporter ex) throws IOException { - + OutputCapsule oc = ex.getCapsule(this); + for (Map.Entry entry : buffers.entrySet()) { + Buffer roData = entry.getValue().asReadOnlyBuffer(); + oc.write((FloatBuffer) roData, entry.getKey().name(),null); + } } @Override 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); + } + } } } diff --git a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java index be2908194..61219cae2 100644 --- a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java +++ b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java @@ -33,8 +33,7 @@ package jme3test.model; import com.jme3.anim.AnimComposer; import com.jme3.anim.SkinningControl; -import com.jme3.app.ChaseCameraAppState; -import com.jme3.app.SimpleApplication; +import com.jme3.app.*; import com.jme3.asset.plugins.FileLocator; import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; @@ -45,7 +44,6 @@ import com.jme3.scene.*; import com.jme3.scene.control.Control; import com.jme3.scene.debug.custom.ArmatureDebugAppState; import com.jme3.scene.plugins.gltf.GltfModelKey; -import com.jme3.shader.VarType; import jme3test.model.anim.EraseTimer; import java.util.*; @@ -118,8 +116,8 @@ public class TestGltfLoading extends SimpleApplication { // rootNode.addLight(pl1); //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/scifigirl/scene.gltf", new Vector3f(0, -1, 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/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/morph/scene.gltf", new Vector3f(0, 0, 0), 0.2f); @@ -214,15 +212,8 @@ public class TestGltfLoading extends SimpleApplication { }, "nextAnim"); dumpScene(rootNode, 0); - } - public void setMorphTarget(int index) { - 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); + stateManager.attach(new DetailedProfilerState()); } private T findControl(Spatial s, Class controlClass) { diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMorphSerialization.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMorphSerialization.java new file mode 100644 index 000000000..1d3551df2 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMorphSerialization.java @@ -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 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(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java b/jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java index d02ff40c1..629046685 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java @@ -1,5 +1,6 @@ package jme3test.model.anim; +import com.jme3.anim.MorphControl; import com.jme3.app.ChaseCameraAppState; import com.jme3.app.SimpleApplication; import com.jme3.input.KeyInput; @@ -68,16 +69,16 @@ public class TestMorph extends SimpleApplication { target2.setBuffer(VertexBuffer.Type.Position, buffer); box.addMorphTarget(target2); - Geometry g = new Geometry("box", box); - final Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + final Geometry g = new Geometry("box", box); + Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); g.setMaterial(m); m.setColor("Color", ColorRGBA.Red); m.setInt("NumberOfMorphTargets", 2); rootNode.attachChild(g); - box.setActiveMorphTargets(0,1); - m.setParam("MorphWeights", VarType.FloatArray, weights); + g.setMorphState(weights); + g.addControl(new MorphControl()); ChaseCameraAppState chase = new ChaseCameraAppState(); chase.setTarget(rootNode); @@ -104,7 +105,7 @@ public class TestMorph extends SimpleApplication { if (name.equals("morphdown")) { weights[1] -= tpf; } - m.setParam("MorphWeights", VarType.FloatArray, weights); + g.setMorphState(weights); } }, "morphup", "morphdown", "morphleft", "morphright"); diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java index f46211a51..fce2bf8ad 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java @@ -803,11 +803,6 @@ public class GltfLoader implements AssetLoader { } else { trackData.timeArrays.add(new TrackData.TimeData(times, TrackData.Type.Morph)); 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; hasMorphTrack = true; }