From 59b2e6871c65f58fdad78cd7229c292f6a177578 Mon Sep 17 00:00:00 2001 From: Scott Barta Date: Thu, 1 Mar 2012 12:35:35 -0800 Subject: Adds the jMonkeyEngine library to the build. Adds the jMonkeyEngine open source 3D game engine to the build. This is built as a static library and is only used by the Finsky client. Change-Id: I06a3f054df7b8a67757267d884854f70c5a16ca0 --- .../core/com/jme3/animation/SkeletonControl.java | 549 +++++++++++++++++++++ 1 file changed, 549 insertions(+) create mode 100644 engine/src/core/com/jme3/animation/SkeletonControl.java (limited to 'engine/src/core/com/jme3/animation/SkeletonControl.java') 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 animatedMeshes = new ArrayList(); + + 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); + } +} -- cgit v1.2.3