aboutsummaryrefslogtreecommitdiff
path: root/engine/src/core/com/jme3/animation/SkeletonControl.java
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/core/com/jme3/animation/SkeletonControl.java')
-rw-r--r--engine/src/core/com/jme3/animation/SkeletonControl.java549
1 files changed, 549 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/animation/SkeletonControl.java b/engine/src/core/com/jme3/animation/SkeletonControl.java
new file mode 100644
index 0000000..3c8e117
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/SkeletonControl.java
@@ -0,0 +1,549 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix4f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.*;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.control.AbstractControl;
+import com.jme3.scene.control.Control;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+
+/**
+ * The Skeleton control deforms a model according to a skeleton,
+ * It handles the computation of the deformation matrices and performs
+ * the transformations on the mesh
+ *
+ * @author Rémy Bouquet Based on AnimControl by Kirill Vainer
+ */
+public class SkeletonControl extends AbstractControl implements Cloneable {
+
+ /**
+ * The skeleton of the model
+ */
+ private Skeleton skeleton;
+ /**
+ * List of targets which this controller effects.
+ */
+ private Mesh[] targets;
+ /**
+ * Used to track when a mesh was updated. Meshes are only updated
+ * if they are visible in at least one camera.
+ */
+ private boolean wasMeshUpdated = false;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public SkeletonControl() {
+ }
+
+ /**
+ * Creates a skeleton control.
+ * The list of targets will be acquired automatically when
+ * the control is attached to a node.
+ *
+ * @param skeleton the skeleton
+ */
+ public SkeletonControl(Skeleton skeleton) {
+ this.skeleton = skeleton;
+ }
+
+ /**
+ * Creates a skeleton control.
+ *
+ * @param targets the meshes controlled by the skeleton
+ * @param skeleton the skeleton
+ */
+ @Deprecated
+ SkeletonControl(Mesh[] targets, Skeleton skeleton) {
+ this.skeleton = skeleton;
+ this.targets = targets;
+ }
+
+ private boolean isMeshAnimated(Mesh mesh) {
+ return mesh.getBuffer(Type.BindPosePosition) != null;
+ }
+
+ private Mesh[] findTargets(Node node) {
+ Mesh sharedMesh = null;
+ ArrayList<Mesh> animatedMeshes = new ArrayList<Mesh>();
+
+ for (Spatial child : node.getChildren()) {
+ if (!(child instanceof Geometry)) {
+ continue; // could be an attachment node, ignore.
+ }
+
+ Geometry geom = (Geometry) child;
+
+ // is this geometry using a shared mesh?
+ Mesh childSharedMesh = geom.getUserData(UserData.JME_SHAREDMESH);
+
+ if (childSharedMesh != null) {
+ // Don't bother with non-animated shared meshes
+ if (isMeshAnimated(childSharedMesh)) {
+ // child is using shared mesh,
+ // so animate the shared mesh but ignore child
+ if (sharedMesh == null) {
+ sharedMesh = childSharedMesh;
+ } else if (sharedMesh != childSharedMesh) {
+ throw new IllegalStateException("Two conflicting shared meshes for " + node);
+ }
+ }
+ } else {
+ Mesh mesh = geom.getMesh();
+ if (isMeshAnimated(mesh)) {
+ animatedMeshes.add(mesh);
+ }
+ }
+ }
+
+ if (sharedMesh != null) {
+ animatedMeshes.add(sharedMesh);
+ }
+
+ return animatedMeshes.toArray(new Mesh[animatedMeshes.size()]);
+ }
+
+ @Override
+ public void setSpatial(Spatial spatial) {
+ super.setSpatial(spatial);
+ if (spatial != null) {
+ Node node = (Node) spatial;
+ targets = findTargets(node);
+ } else {
+ targets = null;
+ }
+ }
+
+ @Override
+ protected void controlRender(RenderManager rm, ViewPort vp) {
+ if (!wasMeshUpdated) {
+ resetToBind(); // reset morph meshes to bind pose
+
+ Matrix4f[] offsetMatrices = skeleton.computeSkinningMatrices();
+
+ // if hardware skinning is supported, the matrices and weight buffer
+ // will be sent by the SkinningShaderLogic object assigned to the shader
+ for (int i = 0; i < targets.length; i++) {
+ // NOTE: This assumes that code higher up
+ // Already ensured those targets are animated
+ // otherwise a crash will happen in skin update
+ //if (isMeshAnimated(targets[i])) {
+ softwareSkinUpdate(targets[i], offsetMatrices);
+ //}
+ }
+
+ wasMeshUpdated = true;
+ }
+ }
+
+ @Override
+ protected void controlUpdate(float tpf) {
+ wasMeshUpdated = false;
+ }
+
+ void resetToBind() {
+ for (Mesh mesh : targets) {
+ if (isMeshAnimated(mesh)) {
+ VertexBuffer bi = mesh.getBuffer(Type.BoneIndex);
+ ByteBuffer bib = (ByteBuffer) bi.getData();
+ if (!bib.hasArray()) {
+ mesh.prepareForAnim(true); // prepare for software animation
+ }
+ VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
+ VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
+ VertexBuffer pos = mesh.getBuffer(Type.Position);
+ VertexBuffer norm = mesh.getBuffer(Type.Normal);
+ FloatBuffer pb = (FloatBuffer) pos.getData();
+ FloatBuffer nb = (FloatBuffer) norm.getData();
+ FloatBuffer bpb = (FloatBuffer) bindPos.getData();
+ FloatBuffer bnb = (FloatBuffer) bindNorm.getData();
+ pb.clear();
+ nb.clear();
+ bpb.clear();
+ bnb.clear();
+
+ //reseting bind tangents if there is a bind tangent buffer
+ VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent);
+ if (bindTangents != null) {
+ VertexBuffer tangents = mesh.getBuffer(Type.Tangent);
+ FloatBuffer tb = (FloatBuffer) tangents.getData();
+ FloatBuffer btb = (FloatBuffer) bindTangents.getData();
+ tb.clear();
+ btb.clear();
+ tb.put(btb).clear();
+ }
+
+
+ pb.put(bpb).clear();
+ nb.put(bnb).clear();
+ }
+ }
+ }
+
+ public Control cloneForSpatial(Spatial spatial) {
+ Node clonedNode = (Node) spatial;
+ AnimControl ctrl = spatial.getControl(AnimControl.class);
+ SkeletonControl clone = new SkeletonControl();
+ clone.setSpatial(clonedNode);
+
+ clone.skeleton = ctrl.getSkeleton();
+ // Fix animated targets for the cloned node
+ clone.targets = findTargets(clonedNode);
+
+ // Fix attachments for the cloned node
+ for (int i = 0; i < clonedNode.getQuantity(); i++) {
+ // go through attachment nodes, apply them to correct bone
+ Spatial child = clonedNode.getChild(i);
+ if (child instanceof Node) {
+ Node clonedAttachNode = (Node) child;
+ Bone originalBone = (Bone) clonedAttachNode.getUserData("AttachedBone");
+
+ if (originalBone != null) {
+ Bone clonedBone = clone.skeleton.getBone(originalBone.getName());
+
+ clonedAttachNode.setUserData("AttachedBone", clonedBone);
+ clonedBone.setAttachmentsNode(clonedAttachNode);
+ }
+ }
+ }
+
+ return clone;
+ }
+
+ /**
+ *
+ * @param boneName the name of the bone
+ * @return the node attached to this bone
+ */
+ public Node getAttachmentsNode(String boneName) {
+ Bone b = skeleton.getBone(boneName);
+ if (b == null) {
+ throw new IllegalArgumentException("Given bone name does not exist "
+ + "in the skeleton.");
+ }
+
+ Node n = b.getAttachmentsNode();
+ Node model = (Node) spatial;
+ model.attachChild(n);
+ return n;
+ }
+
+ /**
+ * returns the skeleton of this control
+ * @return
+ */
+ public Skeleton getSkeleton() {
+ return skeleton;
+ }
+
+ /**
+ * sets the skeleton for this control
+ * @param skeleton
+ */
+// public void setSkeleton(Skeleton skeleton) {
+// this.skeleton = skeleton;
+// }
+ /**
+ * returns the targets meshes of this control
+ * @return
+ */
+ public Mesh[] getTargets() {
+ return targets;
+ }
+
+ /**
+ * sets the target meshes of this control
+ * @param targets
+ */
+// public void setTargets(Mesh[] targets) {
+// this.targets = targets;
+// }
+ /**
+ * Update the mesh according to the given transformation matrices
+ * @param mesh then mesh
+ * @param offsetMatrices the transformation matrices to apply
+ */
+ private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) {
+
+ VertexBuffer tb = mesh.getBuffer(Type.Tangent);
+ if (tb == null) {
+ //if there are no tangents use the classic skinning
+ applySkinning(mesh, offsetMatrices);
+ } else {
+ //if there are tangents use the skinning with tangents
+ applySkinningTangents(mesh, offsetMatrices, tb);
+ }
+
+
+ }
+
+ /**
+ * Method to apply skinning transforms to a mesh's buffers
+ * @param mesh the mesh
+ * @param offsetMatrices the offset matices to apply
+ */
+ private void applySkinning(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
+ // resetToBind() has been called this frame
+ VertexBuffer vb = mesh.getBuffer(Type.Position);
+ FloatBuffer fvb = (FloatBuffer) vb.getData();
+ fvb.rewind();
+
+ VertexBuffer nb = mesh.getBuffer(Type.Normal);
+ FloatBuffer fnb = (FloatBuffer) nb.getData();
+ fnb.rewind();
+
+ // get boneIndexes and weights for mesh
+ ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
+ FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
+
+ ib.rewind();
+ wb.rewind();
+
+ float[] weights = wb.array();
+ byte[] indices = ib.array();
+ int idxWeights = 0;
+
+ TempVars vars = TempVars.get();
+
+
+ float[] posBuf = vars.skinPositions;
+ float[] normBuf = vars.skinNormals;
+
+ int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length));
+ int bufLength = posBuf.length;
+ for (int i = iterations - 1; i >= 0; i--) {
+ // read next set of positions and normals from native buffer
+ bufLength = Math.min(posBuf.length, fvb.remaining());
+ fvb.get(posBuf, 0, bufLength);
+ fnb.get(normBuf, 0, bufLength);
+ int verts = bufLength / 3;
+ int idxPositions = 0;
+
+ // iterate vertices and apply skinning transform for each effecting bone
+ for (int vert = verts - 1; vert >= 0; vert--) {
+ float nmx = normBuf[idxPositions];
+ float vtx = posBuf[idxPositions++];
+ float nmy = normBuf[idxPositions];
+ float vty = posBuf[idxPositions++];
+ float nmz = normBuf[idxPositions];
+ float vtz = posBuf[idxPositions++];
+
+ float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0;
+
+ for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
+ float weight = weights[idxWeights];
+ Matrix4f mat = offsetMatrices[indices[idxWeights++]];
+
+ rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
+ ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
+ rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
+
+ rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
+ rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
+ rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
+ }
+
+ idxWeights += fourMinusMaxWeights;
+
+ idxPositions -= 3;
+ normBuf[idxPositions] = rnx;
+ posBuf[idxPositions++] = rx;
+ normBuf[idxPositions] = rny;
+ posBuf[idxPositions++] = ry;
+ normBuf[idxPositions] = rnz;
+ posBuf[idxPositions++] = rz;
+ }
+
+ fvb.position(fvb.position() - bufLength);
+ fvb.put(posBuf, 0, bufLength);
+ fnb.position(fnb.position() - bufLength);
+ fnb.put(normBuf, 0, bufLength);
+ }
+
+ vars.release();
+
+ vb.updateData(fvb);
+ nb.updateData(fnb);
+
+ }
+
+ /**
+ * Specific method for skinning with tangents to avoid cluttering the classic skinning calculation with
+ * null checks that would slow down the process even if tangents don't have to be computed.
+ * Also the iteration has additional indexes since tangent has 4 components instead of 3 for pos and norm
+ * @param maxWeightsPerVert maximum number of weights per vertex
+ * @param mesh the mesh
+ * @param offsetMatrices the offsetMaytrices to apply
+ * @param tb the tangent vertexBuffer
+ */
+ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) {
+ 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
+ // resetToBind() has been called this frame
+ VertexBuffer vb = mesh.getBuffer(Type.Position);
+ FloatBuffer fvb = (FloatBuffer) vb.getData();
+ fvb.rewind();
+
+ VertexBuffer nb = mesh.getBuffer(Type.Normal);
+
+ FloatBuffer fnb = (FloatBuffer) nb.getData();
+ fnb.rewind();
+
+
+ FloatBuffer ftb = (FloatBuffer) tb.getData();
+ ftb.rewind();
+
+
+ // get boneIndexes and weights for mesh
+ ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
+ FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
+
+ ib.rewind();
+ wb.rewind();
+
+ float[] weights = wb.array();
+ byte[] indices = ib.array();
+ int idxWeights = 0;
+
+ TempVars vars = TempVars.get();
+
+
+ float[] posBuf = vars.skinPositions;
+ float[] normBuf = vars.skinNormals;
+ float[] tanBuf = vars.skinTangents;
+
+ int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length));
+ int bufLength = 0;
+ int tanLength = 0;
+ for (int i = iterations - 1; i >= 0; i--) {
+ // read next set of positions and normals from native buffer
+ bufLength = Math.min(posBuf.length, fvb.remaining());
+ tanLength = Math.min(tanBuf.length, ftb.remaining());
+ fvb.get(posBuf, 0, bufLength);
+ fnb.get(normBuf, 0, bufLength);
+ ftb.get(tanBuf, 0, tanLength);
+ int verts = bufLength / 3;
+ int idxPositions = 0;
+ //tangents has their own index because of the 4 components
+ int idxTangents = 0;
+
+ // iterate vertices and apply skinning transform for each effecting bone
+ for (int vert = verts - 1; vert >= 0; vert--) {
+ float nmx = normBuf[idxPositions];
+ float vtx = posBuf[idxPositions++];
+ float nmy = normBuf[idxPositions];
+ float vty = posBuf[idxPositions++];
+ float nmz = normBuf[idxPositions];
+ float vtz = posBuf[idxPositions++];
+
+ float tnx = tanBuf[idxTangents++];
+ float tny = tanBuf[idxTangents++];
+ float tnz = tanBuf[idxTangents++];
+
+ //skipping the 4th component of the tangent since it doesn't have to be transformed
+ idxTangents++;
+
+ float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0, rtx = 0, rty = 0, rtz = 0;
+
+ for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
+ float weight = weights[idxWeights];
+ Matrix4f mat = offsetMatrices[indices[idxWeights++]];
+
+ rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
+ ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
+ rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
+
+ rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
+ rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
+ rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
+
+ rtx += (tnx * mat.m00 + tny * mat.m01 + tnz * mat.m02) * weight;
+ rty += (tnx * mat.m10 + tny * mat.m11 + tnz * mat.m12) * weight;
+ rtz += (tnx * mat.m20 + tny * mat.m21 + tnz * mat.m22) * weight;
+ }
+
+ idxWeights += fourMinusMaxWeights;
+
+ idxPositions -= 3;
+
+ normBuf[idxPositions] = rnx;
+ posBuf[idxPositions++] = rx;
+ normBuf[idxPositions] = rny;
+ posBuf[idxPositions++] = ry;
+ normBuf[idxPositions] = rnz;
+ posBuf[idxPositions++] = rz;
+
+ idxTangents -= 4;
+
+ tanBuf[idxTangents++] = rtx;
+ tanBuf[idxTangents++] = rty;
+ tanBuf[idxTangents++] = rtz;
+
+ //once again skipping the 4th component of the tangent
+ idxTangents++;
+ }
+
+ fvb.position(fvb.position() - bufLength);
+ fvb.put(posBuf, 0, bufLength);
+ fnb.position(fnb.position() - bufLength);
+ fnb.put(normBuf, 0, bufLength);
+ ftb.position(ftb.position() - tanLength);
+ ftb.put(tanBuf, 0, tanLength);
+ }
+
+ vars.release();
+
+ vb.updateData(fvb);
+ nb.updateData(fnb);
+ tb.updateData(ftb);
+
+
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(targets, "targets", null);
+ oc.write(skeleton, "skeleton", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule in = im.getCapsule(this);
+ Savable[] sav = in.readSavableArray("targets", null);
+ if (sav != null) {
+ targets = new Mesh[sav.length];
+ System.arraycopy(sav, 0, targets, 0, sav.length);
+ }
+ skeleton = (Skeleton) in.readSavable("skeleton", null);
+ }
+}