diff --git a/engine/src/core/com/jme3/animation/AnimControl.java b/engine/src/core/com/jme3/animation/AnimControl.java index 7b27567a5..02bd46a91 100644 --- a/engine/src/core/com/jme3/animation/AnimControl.java +++ b/engine/src/core/com/jme3/animation/AnimControl.java @@ -111,7 +111,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone this.skeleton = skeleton; - skeletonControl = new SkeletonControl(model, meshes, this.skeleton); + skeletonControl = new SkeletonControl(meshes, this.skeleton); reset(); } @@ -389,7 +389,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone Mesh[] tg = null; tg = new Mesh[sav.length]; System.arraycopy(sav, 0, tg, 0, sav.length); - skeletonControl = new SkeletonControl((Node) spatial, tg, skeleton); + skeletonControl = new SkeletonControl(tg, skeleton); spatial.addControl(skeletonControl); } //------ diff --git a/engine/src/core/com/jme3/animation/Bone.java b/engine/src/core/com/jme3/animation/Bone.java index b242e4d5d..3c4187993 100644 --- a/engine/src/core/com/jme3/animation/Bone.java +++ b/engine/src/core/com/jme3/animation/Bone.java @@ -382,11 +382,13 @@ public final class Bone implements Savable { worldRot.set(rotation); } - protected Vector3f tmpVec=new Vector3f(); - protected Quaternion tmpQuat=new Quaternion(); + protected Vector3f tmpVec = new Vector3f(); + protected Quaternion tmpQuat = new Quaternion(); + public Quaternion getTmpQuat() { return tmpQuat; } + public Vector3f getTmpVec() { return tmpVec; } diff --git a/engine/src/core/com/jme3/animation/SkeletonControl.java b/engine/src/core/com/jme3/animation/SkeletonControl.java index 4cdaf380c..f77bfe604 100644 --- a/engine/src/core/com/jme3/animation/SkeletonControl.java +++ b/engine/src/core/com/jme3/animation/SkeletonControl.java @@ -44,8 +44,7 @@ public class SkeletonControl extends AbstractControl implements Savable, Cloneab public SkeletonControl() { } - public SkeletonControl(Node model, Mesh[] targets, Skeleton skeleton) { - super(model); + public SkeletonControl(Mesh[] targets, Skeleton skeleton) { this.skeleton = skeleton; this.targets = targets; } @@ -95,6 +94,9 @@ public class SkeletonControl extends AbstractControl implements Savable, Cloneab private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) { int maxWeightsPerVert = mesh.getMaxNumWeights(); + if (maxWeightsPerVert <= 0) + throw new IllegalStateException("Max weights per vert is incorrectly set!"); + int fourMinusMaxWeights = 4 - maxWeightsPerVert; // NOTE: This code assumes the vertex buffer is in bind pose diff --git a/engine/src/core/com/jme3/scene/Mesh.java b/engine/src/core/com/jme3/scene/Mesh.java index 056d1ac13..c03afc215 100644 --- a/engine/src/core/com/jme3/scene/Mesh.java +++ b/engine/src/core/com/jme3/scene/Mesh.java @@ -168,6 +168,41 @@ public class Mesh implements Savable, Cloneable { return clone; } + public void generateBindPose(boolean swAnim){ + if (swAnim){ + VertexBuffer pos = getBuffer(Type.Position); + if (pos == null || getBuffer(Type.BoneIndex) == null) { + // ignore, this mesh doesn't have positional data + // or it doesn't have bone-vertex assignments, so its not animated + return; + } + + VertexBuffer bindPos = new VertexBuffer(Type.BindPosePosition); + bindPos.setupData(Usage.CpuOnly, + 3, + Format.Float, + BufferUtils.clone(pos.getData())); + setBuffer(bindPos); + + // XXX: note that this method also sets stream mode + // so that animation is faster. this is not needed for hardware skinning + pos.setUsage(Usage.Stream); + + VertexBuffer norm = getBuffer(Type.Normal); + if (norm != null) { + VertexBuffer bindNorm = new VertexBuffer(Type.BindPoseNormal); + bindNorm.setupData(Usage.CpuOnly, + 3, + Format.Float, + BufferUtils.clone(norm.getData())); + setBuffer(bindNorm); + norm.setUsage(Usage.Stream); + } + + norm.setUsage(Usage.Stream); + } + } + public void prepareForAnim(boolean swAnim){ if (swAnim){ // convert indices diff --git a/engine/src/ogre/com/jme3/scene/plugins/ogre/MeshLoader.java b/engine/src/ogre/com/jme3/scene/plugins/ogre/MeshLoader.java index 1ac1064bb..92fb39db7 100644 --- a/engine/src/ogre/com/jme3/scene/plugins/ogre/MeshLoader.java +++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/MeshLoader.java @@ -705,37 +705,6 @@ public class MeshLoader extends DefaultHandler implements AssetLoader { public void characters(char ch[], int start, int length) { } - private void createBindPose(Mesh mesh) { - VertexBuffer pos = mesh.getBuffer(Type.Position); - if (pos == null || mesh.getBuffer(Type.BoneIndex) == null) { - // ignore, this mesh doesn't have positional data - // or it doesn't have bone-vertex assignments, so its not animated - return; - } - - VertexBuffer bindPos = new VertexBuffer(Type.BindPosePosition); - bindPos.setupData(Usage.CpuOnly, - 3, - Format.Float, - BufferUtils.clone(pos.getData())); - mesh.setBuffer(bindPos); - - // XXX: note that this method also sets stream mode - // so that animation is faster. this is not needed for hardware skinning - pos.setUsage(Usage.Stream); - - VertexBuffer norm = mesh.getBuffer(Type.Normal); - if (norm != null) { - VertexBuffer bindNorm = new VertexBuffer(Type.BindPoseNormal); - bindNorm.setupData(Usage.CpuOnly, - 3, - Format.Float, - BufferUtils.clone(norm.getData())); - mesh.setBuffer(bindNorm); - norm.setUsage(Usage.Stream); - } - } - private Node compileModel() { String nodeName; if (meshName == null) { @@ -757,7 +726,7 @@ public class MeshLoader extends DefaultHandler implements AssetLoader { boolean useShared = usesSharedGeom.get(i); // create bind pose if (!useShared) { - createBindPose(m); + m.generateBindPose(!HARDWARE_SKINNING); newMeshes.add(m); } else { VertexBuffer bindPos = sharedmesh.getBuffer(Type.BindPosePosition); diff --git a/engine/src/test/jme3test/model/anim/TestCustomAnim.java b/engine/src/test/jme3test/model/anim/TestCustomAnim.java new file mode 100644 index 000000000..e3f0a4209 --- /dev/null +++ b/engine/src/test/jme3test/model/anim/TestCustomAnim.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.model.anim; + +import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; +import com.jme3.animation.SkeletonControl; +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.scene.shape.Box; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +public class TestCustomAnim extends SimpleApplication { + + private Bone bone; + private Skeleton skeleton; + private Quaternion rotation = new Quaternion(); + + public static void main(String[] args) { + TestCustomAnim app = new TestCustomAnim(); + app.start(); + } + + @Override + public void simpleInitApp() { + + AmbientLight al = new AmbientLight(); + rootNode.addLight(al); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(Vector3f.UNIT_XYZ.negate()); + rootNode.addLight(dl); + + Box box = new Box(1, 1, 1); + + // Setup bone weight buffer + FloatBuffer weights = FloatBuffer.allocate( box.getVertexCount() * 4 ); + VertexBuffer weightsBuf = new VertexBuffer(Type.BoneWeight); + weightsBuf.setupData(Usage.CpuOnly, 4, Format.Float, weights); + box.setBuffer(weightsBuf); + + // Setup bone index buffer + ByteBuffer indices = ByteBuffer.allocate( box.getVertexCount() * 4 ); + VertexBuffer indicesBuf = new VertexBuffer(Type.BoneIndex); + indicesBuf.setupData(Usage.CpuOnly, 4, Format.UnsignedByte, indices); + box.setBuffer(indicesBuf); + + // Create bind pose buffers + box.generateBindPose(true); + + // Create skeleton + bone = new Bone("root"); + bone.setBindTransforms(Vector3f.ZERO, Quaternion.IDENTITY, Vector3f.UNIT_XYZ); + bone.setUserControl(true); + skeleton = new Skeleton(new Bone[]{ bone }); + + // Assign all verticies to bone 0 with weight 1 + for (int i = 0; i < box.getVertexCount() * 4; i += 4){ + // assign vertex to bone index 0 + indices.array()[i+0] = 0; + indices.array()[i+1] = 0; + indices.array()[i+2] = 0; + indices.array()[i+3] = 0; + + // set weight to 1 only for first entry + weights.array()[i+0] = 1; + weights.array()[i+1] = 0; + weights.array()[i+2] = 0; + weights.array()[i+3] = 0; + } + + // Maximum number of weights per bone is 1 + box.setMaxNumWeights(1); + + // Create model + Geometry geom = new Geometry("box", box); + geom.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m")); + Node model = new Node("model"); + model.attachChild(geom); + + // Create skeleton control + SkeletonControl skeletonControl = new SkeletonControl(new Mesh[]{ box }, skeleton); + model.addControl(skeletonControl); + + rootNode.attachChild(model); + } + + @Override + public void simpleUpdate(float tpf){ + // Rotate around X axis + Quaternion rotate = new Quaternion(); + rotate.fromAngleAxis(tpf, Vector3f.UNIT_X); + + // Combine rotation with previous + rotation.multLocal(rotate); + + // Set new rotation into bone + bone.setUserTransforms(Vector3f.ZERO, rotation, Vector3f.UNIT_XYZ); + + // After changing skeleton transforms, must update world data + skeleton.updateWorldVectors(); + } + +}