From a1a94864245b9da53611c047c2670b333802262d Mon Sep 17 00:00:00 2001 From: Nehon Date: Sun, 24 Dec 2017 15:16:30 +0100 Subject: [PATCH] New anim system proper serialization --- .../src/main/java/com/jme3/anim/AnimClip.java | 2 +- .../main/java/com/jme3/anim/AnimComposer.java | 37 +++ .../src/main/java/com/jme3/anim/Armature.java | 36 ++- .../src/main/java/com/jme3/anim/Joint.java | 4 +- .../jme3/anim/MatrixJointModelTransform.java | 32 --- .../anim/SeparateJointModelTransform.java | 33 +-- .../java/com/jme3/anim/TransformTrack.java | 41 ++-- .../jme3/anim/util/JointModelTransform.java | 4 +- .../model/anim/TestAnimMigration.java | 6 +- .../model/anim/TestAnimSerialization.java | 168 ++++++++++++++ .../jme3test/model/anim/TestArmature.java | 2 +- .../model/anim/TestBaseAnimSerialization.java | 217 ++++++++++++++++++ 12 files changed, 482 insertions(+), 100 deletions(-) create mode 100644 jme3-examples/src/main/java/jme3test/model/anim/TestAnimSerialization.java create mode 100644 jme3-examples/src/main/java/jme3test/model/anim/TestBaseAnimSerialization.java diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimClip.java b/jme3-core/src/main/java/com/jme3/anim/AnimClip.java index 00eff2c05..8bd64e0e1 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimClip.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimClip.java @@ -105,7 +105,7 @@ public class AnimClip implements Tween, JmeCloneable, Savable { if (arr != null) { tracks = new SafeArrayList<>(Tween.class); for (Savable savable : arr) { - tracks.add((Tween) savable); + addTrack((Tween) savable); } } } diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java index e70a4c3ea..731f92a19 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java @@ -1,9 +1,12 @@ package com.jme3.anim; +import com.jme3.export.*; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.scene.control.AbstractControl; +import com.jme3.util.clone.Cloner; +import java.io.IOException; import java.util.*; /** @@ -87,4 +90,38 @@ public class AnimComposer extends AbstractControl { protected void controlRender(RenderManager rm, ViewPort vp) { } + + @Override + public Object jmeClone() { + try { + AnimComposer clone = (AnimComposer) super.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + @Override + public void cloneFields(Cloner cloner, Object original) { + super.cloneFields(cloner, original); + Map clips = new HashMap<>(); + for (String key : animClipMap.keySet()) { + clips.put(key, cloner.clone(animClipMap.get(key))); + } + animClipMap = clips; + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + animClipMap = (Map) ic.readStringSavableMap("animClipMap", new HashMap()); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.writeStringSavableMap(animClipMap, "animClipMap", new HashMap()); + } } diff --git a/jme3-core/src/main/java/com/jme3/anim/Armature.java b/jme3-core/src/main/java/com/jme3/anim/Armature.java index 0d37c35f0..370618e7f 100644 --- a/jme3-core/src/main/java/com/jme3/anim/Armature.java +++ b/jme3-core/src/main/java/com/jme3/anim/Armature.java @@ -1,6 +1,7 @@ package com.jme3.anim; import com.jme3.anim.util.JointModelTransform; +import com.jme3.asset.AssetLoadException; import com.jme3.export.*; import com.jme3.math.Matrix4f; import com.jme3.util.clone.Cloner; @@ -46,11 +47,7 @@ public class Armature implements JmeCloneable, Savable { List rootJointList = new ArrayList<>(); for (int i = jointList.length - 1; i >= 0; i--) { Joint joint = jointList[i]; - try { - joint.setJointModelTransform(modelTransformClass.newInstance()); - } catch (InstantiationException | IllegalAccessException e) { - throw new IllegalArgumentException(e); - } + instanciateJointModelTransform(joint); if (joint.getParent() == null) { rootJointList.add(joint); } @@ -94,11 +91,15 @@ public class Armature implements JmeCloneable, Savable { return; } for (Joint joint : jointList) { - try { - joint.setJointModelTransform(modelTransformClass.newInstance()); - } catch (InstantiationException | IllegalAccessException e) { - throw new IllegalArgumentException(e); - } + instanciateJointModelTransform(joint); + } + } + + private void instanciateJointModelTransform(Joint joint) { + try { + joint.setJointModelTransform(modelTransformClass.newInstance()); + } catch (InstantiationException | IllegalAccessException e) { + throw new IllegalArgumentException(e); } } @@ -226,6 +227,9 @@ public class Armature implements JmeCloneable, Savable { this.rootJoints = cloner.clone(rootJoints); this.jointList = cloner.clone(jointList); this.skinningMatrixes = cloner.clone(skinningMatrixes); + for (Joint joint : jointList) { + instanciateJointModelTransform(joint); + } } @@ -241,11 +245,22 @@ public class Armature implements JmeCloneable, Savable { jointList = new Joint[jointListAsSavable.length]; System.arraycopy(jointListAsSavable, 0, jointList, 0, jointListAsSavable.length); + String className = input.readString("modelTransformClass", MatrixJointModelTransform.class.getCanonicalName()); + try { + modelTransformClass = (Class) Class.forName(className); + } catch (ClassNotFoundException e) { + throw new AssetLoadException("Cannnot find class for name " + className); + } + + for (Joint joint : jointList) { + instanciateJointModelTransform(joint); + } createSkinningMatrices(); for (Joint rootJoint : rootJoints) { rootJoint.update(); } + resetToBindPose(); } @Override @@ -253,5 +268,6 @@ public class Armature implements JmeCloneable, Savable { OutputCapsule output = ex.getCapsule(this); output.write(rootJoints, "rootJoints", null); output.write(jointList, "jointList", null); + output.write(modelTransformClass.getCanonicalName(), "modelTransformClass", MatrixJointModelTransform.class.getCanonicalName()); } } diff --git a/jme3-core/src/main/java/com/jme3/anim/Joint.java b/jme3-core/src/main/java/com/jme3/anim/Joint.java index f85b95c38..6ff53f59c 100644 --- a/jme3-core/src/main/java/com/jme3/anim/Joint.java +++ b/jme3-core/src/main/java/com/jme3/anim/Joint.java @@ -259,10 +259,10 @@ public class Joint implements Savable, JmeCloneable { @Override public void cloneFields(Cloner cloner, Object original) { this.children = cloner.clone(children); + this.parent = cloner.clone(parent); this.attachedNode = cloner.clone(attachedNode); this.targetGeometry = cloner.clone(targetGeometry); this.localTransform = cloner.clone(localTransform); - this.jointModelTransform = cloner.clone(jointModelTransform); this.inverseModelBindMatrix = cloner.clone(inverseModelBindMatrix); } @@ -276,7 +276,6 @@ public class Joint implements Savable, JmeCloneable { attachedNode = (Node) input.readSavable("attachedNode", null); targetGeometry = (Geometry) input.readSavable("targetGeometry", null); inverseModelBindMatrix = (Matrix4f) input.readSavable("inverseModelBindMatrix", inverseModelBindMatrix); - jointModelTransform = (JointModelTransform) input.readSavable("jointModelTransform", null); ArrayList childList = input.readSavableArrayList("children", null); for (int i = childList.size() - 1; i >= 0; i--) { @@ -293,7 +292,6 @@ public class Joint implements Savable, JmeCloneable { output.write(targetGeometry, "targetGeometry", null); output.write(inverseModelBindMatrix, "inverseModelBindMatrix", new Matrix4f()); output.writeSavableArrayList(children, "children", null); - output.write(jointModelTransform, "jointModelTransform", null); } } diff --git a/jme3-core/src/main/java/com/jme3/anim/MatrixJointModelTransform.java b/jme3-core/src/main/java/com/jme3/anim/MatrixJointModelTransform.java index 9c141b5ff..27964f325 100644 --- a/jme3-core/src/main/java/com/jme3/anim/MatrixJointModelTransform.java +++ b/jme3-core/src/main/java/com/jme3/anim/MatrixJointModelTransform.java @@ -1,12 +1,8 @@ package com.jme3.anim; import com.jme3.anim.util.JointModelTransform; -import com.jme3.export.*; import com.jme3.math.Matrix4f; import com.jme3.math.Transform; -import com.jme3.util.clone.Cloner; - -import java.io.IOException; /** * This JointModelTransform implementation accumulate joints transforms in a Matrix4f to properly @@ -47,32 +43,4 @@ public class MatrixJointModelTransform implements JointModelTransform { public Transform getModelTransform() { return modelTransform; } - - @Override - public void write(JmeExporter ex) throws IOException { - OutputCapsule oc = ex.getCapsule(this); - oc.write(modelTransformMatrix, "modelTransformMatrix", new Matrix4f()); - } - - @Override - public void read(JmeImporter im) throws IOException { - InputCapsule ic = im.getCapsule(this); - modelTransformMatrix = (Matrix4f) ic.readSavable("modelTransformMatrix", new Matrix4f()); - modelTransform.fromTransformMatrix(modelTransformMatrix); - } - - @Override - public Object jmeClone() { - try { - MatrixJointModelTransform clone = (MatrixJointModelTransform) super.clone(); - return clone; - } catch (CloneNotSupportedException ex) { - throw new AssertionError(); - } - } - - @Override - public void cloneFields(Cloner cloner, Object original) { - modelTransformMatrix = modelTransformMatrix.clone(); - } } diff --git a/jme3-core/src/main/java/com/jme3/anim/SeparateJointModelTransform.java b/jme3-core/src/main/java/com/jme3/anim/SeparateJointModelTransform.java index f5089f3d7..c06b97ba4 100644 --- a/jme3-core/src/main/java/com/jme3/anim/SeparateJointModelTransform.java +++ b/jme3-core/src/main/java/com/jme3/anim/SeparateJointModelTransform.java @@ -1,12 +1,8 @@ package com.jme3.anim; import com.jme3.anim.util.JointModelTransform; -import com.jme3.export.*; import com.jme3.math.Matrix4f; import com.jme3.math.Transform; -import com.jme3.util.clone.Cloner; - -import java.io.IOException; /** * This JointModelTransform implementation accumulates model transform in a Transform class @@ -33,8 +29,7 @@ public class SeparateJointModelTransform implements JointModelTransform { @Override public void applyBindPose(Transform localTransform, Matrix4f inverseModelBindMatrix, Joint parent) { - localTransform.fromTransformMatrix(inverseModelBindMatrix); - localTransform.invert(); //model transform + localTransform.fromTransformMatrix(inverseModelBindMatrix.invert()); if (parent != null) { localTransform.combineWithParent(parent.getModelTransform().invert()); } @@ -45,30 +40,4 @@ public class SeparateJointModelTransform implements JointModelTransform { return modelTransform; } - @Override - public void write(JmeExporter ex) throws IOException { - OutputCapsule oc = ex.getCapsule(this); - oc.write(modelTransform, "modelTransform", new Transform()); - } - - @Override - public void read(JmeImporter im) throws IOException { - InputCapsule ic = im.getCapsule(this); - modelTransform = (Transform) ic.readSavable("modelTransform", new Transform()); - } - - @Override - public Object jmeClone() { - try { - SeparateJointModelTransform clone = (SeparateJointModelTransform) super.clone(); - return clone; - } catch (CloneNotSupportedException ex) { - throw new AssertionError(); - } - } - - @Override - public void cloneFields(Cloner cloner, Object original) { - modelTransform = modelTransform.clone(); - } } diff --git a/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java b/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java index 5a2c329d9..5e0064ac5 100644 --- a/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java +++ b/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java @@ -307,6 +307,7 @@ public abstract class TransformTrack implements Tween, JmeCloneable, Savable { rotations = (CompactQuaternionArray) ic.readSavable("rotations", null); times = ic.readFloatArray("times", null); scales = (CompactVector3Array) ic.readSavable("scales", null); + setTimes(times); } @Override @@ -322,23 +323,33 @@ public abstract class TransformTrack implements Tween, JmeCloneable, Savable { public void cloneFields(Cloner cloner, Object original) { int tablesLength = times.length; - times = this.times.clone(); - Vector3f[] sourceTranslations = this.getTranslations(); - Quaternion[] sourceRotations = this.getRotations(); - Vector3f[] sourceScales = this.getScales(); - - Vector3f[] translations = new Vector3f[tablesLength]; - Quaternion[] rotations = new Quaternion[tablesLength]; - Vector3f[] scales = new Vector3f[tablesLength]; - for (int i = 0; i < tablesLength; ++i) { - translations[i] = sourceTranslations[i].clone(); - rotations[i] = sourceRotations[i].clone(); - scales[i] = sourceScales != null ? sourceScales[i].clone() : new Vector3f(1.0f, 1.0f, 1.0f); + setTimes(this.times.clone()); + if (translations != null) { + Vector3f[] sourceTranslations = this.getTranslations(); + Vector3f[] translations = new Vector3f[tablesLength]; + for (int i = 0; i < tablesLength; ++i) { + translations[i] = sourceTranslations[i].clone(); + } + setKeyframesTranslation(translations); + } + if (rotations != null) { + Quaternion[] sourceRotations = this.getRotations(); + Quaternion[] rotations = new Quaternion[tablesLength]; + for (int i = 0; i < tablesLength; ++i) { + rotations[i] = sourceRotations[i].clone(); + } + setKeyframesRotation(rotations); + } + + if (scales != null) { + Vector3f[] sourceScales = this.getScales(); + Vector3f[] scales = new Vector3f[tablesLength]; + for (int i = 0; i < tablesLength; ++i) { + scales[i] = sourceScales[i].clone(); + } + setKeyframesScale(scales); } - setKeyframesTranslation(translations); - setKeyframesScale(scales); - setKeyframesRotation(rotations); setFrameInterpolator(this.interpolator); } } diff --git a/jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java b/jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java index 48cc68c67..c73c03316 100644 --- a/jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java +++ b/jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java @@ -1,16 +1,14 @@ package com.jme3.anim.util; import com.jme3.anim.Joint; -import com.jme3.export.Savable; import com.jme3.math.Matrix4f; import com.jme3.math.Transform; -import com.jme3.util.clone.JmeCloneable; /** * Implementations of this interface holds accumulated model transform of a Joint. * Implementation might choose different accumulation strategy. */ -public interface JointModelTransform extends JmeCloneable, Savable { +public interface JointModelTransform { void updateModelTransform(Transform localTransform, Joint parent); diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java index 333f09cc2..1525165ad 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java @@ -41,9 +41,9 @@ public class TestAnimMigration extends SimpleApplication { rootNode.addLight(new DirectionalLight(new Vector3f(-1, -1, -1).normalizeLocal())); rootNode.addLight(new AmbientLight(ColorRGBA.DarkGray)); - Spatial model = assetManager.loadModel("Models/Jaime/Jaime.j3o"); + //Spatial model = assetManager.loadModel("Models/Jaime/Jaime.j3o"); //Spatial model = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); - //Spatial model = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); + Spatial model = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); //Spatial model = assetManager.loadModel("Models/Elephant/Elephant.mesh.xml"); AnimMigrationUtils.migrate(model); @@ -52,7 +52,7 @@ public class TestAnimMigration extends SimpleApplication { debugAppState = new ArmatureDebugAppState(); - stateManager.attach(debugAppState); + //stateManager.attach(debugAppState); setupModel(model); diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimSerialization.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimSerialization.java new file mode 100644 index 000000000..6b2196813 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimSerialization.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.setCurrentAnimClip(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.setCurrentAnimClip(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.setCurrentAnimClip(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/TestArmature.java b/jme3-examples/src/main/java/jme3test/model/anim/TestArmature.java index 80ecb0132..eefb08c75 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestArmature.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestArmature.java @@ -51,7 +51,7 @@ public class TestArmature extends SimpleApplication { Joint[] joints = new Joint[]{root, j1, j2, j3}; final Armature armature = new Armature(joints); -// armature.setModelTransformClass(SeparateJointModelTransform.class); + //armature.setModelTransformClass(SeparateJointModelTransform.class); armature.setBindPose(); //create animations diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestBaseAnimSerialization.java b/jme3-examples/src/main/java/jme3test/model/anim/TestBaseAnimSerialization.java new file mode 100644 index 000000000..512a777f3 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestBaseAnimSerialization.java @@ -0,0 +1,217 @@ +package jme3test.model.anim; + +import com.jme3.anim.*; +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.material.Material; +import com.jme3.math.*; +import com.jme3.scene.*; +import com.jme3.scene.debug.custom.ArmatureDebugAppState; +import com.jme3.scene.shape.Cylinder; +import com.jme3.system.JmeSystem; + +import java.io.File; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +/** + * Created by Nehon on 18/12/2017. + */ +public class TestBaseAnimSerialization extends SimpleApplication { + + Joint j1; + Joint j2; + AnimComposer composer; + Armature armature; + File file; + + public static void main(String... argv) { + TestBaseAnimSerialization app = new TestBaseAnimSerialization(); + app.start(); + } + + @Override + public void simpleInitApp() { + setTimer(new EraseTimer()); + renderManager.setSinglePassLightBatchSize(2); + //cam.setFrustumPerspective(90f, (float) cam.getWidth() / cam.getHeight(), 0.01f, 10f); + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + + //create armature + Joint root = new Joint("Root_Joint"); + j1 = new Joint("Joint_1"); + j2 = new Joint("Joint_2"); + Joint j3 = new Joint("Joint_3"); + root.addChild(j1); + j1.addChild(j2); + j2.addChild(j3); + root.setLocalTranslation(new Vector3f(0, 0, 0.5f)); + j1.setLocalTranslation(new Vector3f(0, 0.0f, -0.5f)); + j2.setLocalTranslation(new Vector3f(0, 0.0f, -0.3f)); + j3.setLocalTranslation(new Vector3f(0, 0, -0.2f)); + Joint[] joints = new Joint[]{root, j1, j2, j3}; + + armature = new Armature(joints); + //armature.setModelTransformClass(SeparateJointModelTransform.class); + armature.setBindPose(); + + //create animations + AnimClip clip = new AnimClip("anim"); + float[] times = new float[]{0, 2, 4}; + Quaternion[] rotations = new Quaternion[]{ + new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X), + new Quaternion().fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X), + new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X) + }; + Vector3f[] translations = new Vector3f[]{ + new Vector3f(0, 0.2f, 0), + new Vector3f(0, 1.0f, 0), + new Vector3f(0, 0.2f, 0), + }; + Vector3f[] scales = new Vector3f[]{ + new Vector3f(1, 1, 1), + new Vector3f(1, 1, 2), + new Vector3f(1, 1, 1), + }; + Vector3f[] scales2 = new Vector3f[]{ + new Vector3f(1, 1, 1), + new Vector3f(1, 1, 0.5f), + new Vector3f(1, 1, 1), + }; + + JointTrack track1 = new JointTrack(j1, times, null, rotations, scales); + JointTrack track2 = new JointTrack(j2, times, null, rotations, null); + clip.addTrack(track1); + clip.addTrack(track2); + + //create the animComposer control + composer = new AnimComposer(); + composer.addAnimClip(clip); + + //create the SkinningControl + SkinningControl ac = new SkinningControl(armature); + Node node = new Node("Test Armature"); + + //Create the mesh to deform. + Geometry cylinder = new Geometry("cylinder", createMesh()); + Material m = new Material(assetManager, "Common/MatDefs/Misc/fakeLighting.j3md"); + m.setColor("Color", ColorRGBA.randomColor()); + cylinder.setMaterial(m); + node.attachChild(cylinder); + node.addControl(composer); + node.addControl(ac); + + File storageFolder = JmeSystem.getStorageFolder(); + file = new File(storageFolder.getPath() + File.separator + "test.j3o"); + BinaryExporter be = new BinaryExporter(); + try { + be.save(node, file); + } catch (IOException e) { + e.printStackTrace(); + } + + assetManager.registerLocator(storageFolder.getPath(), FileLocator.class); + Node newNode = (Node) assetManager.loadModel("test.j3o"); + + rootNode.attachChild(newNode); + + composer = newNode.getControl(AnimComposer.class); + ac = newNode.getControl(SkinningControl.class); + ac.setHardwareSkinningPreferred(false); + armature = ac.getArmature(); + composer.setCurrentAnimClip("anim"); + + ArmatureDebugAppState debugAppState = new ArmatureDebugAppState(); + debugAppState.addArmatureFrom(ac); + stateManager.attach(debugAppState); + + flyCam.setEnabled(false); + + ChaseCameraAppState chaseCam = new ChaseCameraAppState(); + chaseCam.setTarget(node); + 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); + + + inputManager.addMapping("bind", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed) { + composer.reset(); + armature.resetToBindPose(); + + } else { + composer.setCurrentAnimClip("anim"); + } + } + }, "bind"); + } + + private Mesh createMesh() { + Cylinder c = new Cylinder(30, 16, 0.1f, 1, true); + + ShortBuffer jointIndex = (ShortBuffer) VertexBuffer.createBuffer(VertexBuffer.Format.UnsignedShort, 4, c.getVertexCount()); + jointIndex.rewind(); + c.setMaxNumWeights(1); + FloatBuffer jointWeight = (FloatBuffer) VertexBuffer.createBuffer(VertexBuffer.Format.Float, 4, c.getVertexCount()); + jointWeight.rewind(); + VertexBuffer vb = c.getBuffer(VertexBuffer.Type.Position); + FloatBuffer fvb = (FloatBuffer) vb.getData(); + fvb.rewind(); + for (int i = 0; i < c.getVertexCount(); i++) { + fvb.get(); + fvb.get(); + float z = fvb.get(); + int index = 0; + if (z > 0) { + index = 0; + } else if (z > -0.2) { + index = 1; + } else { + index = 2; + } + jointIndex.put((short) index).put((short) 0).put((short) 0).put((short) 0); + jointWeight.put(1f).put(0f).put(0f).put(0f); + + } + c.setBuffer(VertexBuffer.Type.BoneIndex, 4, jointIndex); + c.setBuffer(VertexBuffer.Type.BoneWeight, 4, jointWeight); + + c.updateCounts(); + c.updateBound(); + + VertexBuffer weightsHW = new VertexBuffer(VertexBuffer.Type.HWBoneWeight); + VertexBuffer indicesHW = new VertexBuffer(VertexBuffer.Type.HWBoneIndex); + + indicesHW.setUsage(VertexBuffer.Usage.CpuOnly); + weightsHW.setUsage(VertexBuffer.Usage.CpuOnly); + c.setBuffer(weightsHW); + c.setBuffer(indicesHW); + c.generateBindPose(); + + c.prepareForAnim(false); + + return c; + } + + @Override + public void destroy() { + super.destroy(); + file.delete(); + } +}