- Split of the AnimControl in two parts : - The AnimControl that handles skeleton transformation via tha animation data - The SkeletonControl that handles the skinning of the mesh using skeleton transformations - Ensured backward compatibility with old j3o files, and changed the ogre mesh loader to create the controls properly - Reverted change http://code.google.com/p/jmonkeyengine/source/detail?r=7142 transforms passed to the setUserTransform methods must be considered as increments to current transform - Fixed some issues in the ragdollControl and test case (still WIP don't use it) git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7163 75d07b2b-3a1a-0410-a2c5-0572b91ccdca3.0
parent
e4c8769be0
commit
dc030c897f
@ -0,0 +1,274 @@ |
|||||||
|
/* |
||||||
|
* To change this template, choose Tools | Templates |
||||||
|
* and open the template in the editor. |
||||||
|
*/ |
||||||
|
package com.jme3.animation; |
||||||
|
|
||||||
|
import com.jme3.export.InputCapsule; |
||||||
|
import com.jme3.export.JmeExporter; |
||||||
|
import com.jme3.export.JmeImporter; |
||||||
|
import com.jme3.export.OutputCapsule; |
||||||
|
import com.jme3.export.Savable; |
||||||
|
import com.jme3.math.FastMath; |
||||||
|
import com.jme3.math.Matrix4f; |
||||||
|
import com.jme3.renderer.RenderManager; |
||||||
|
import com.jme3.renderer.ViewPort; |
||||||
|
import com.jme3.scene.Geometry; |
||||||
|
import com.jme3.scene.Mesh; |
||||||
|
import com.jme3.scene.Node; |
||||||
|
import com.jme3.scene.Spatial; |
||||||
|
import com.jme3.scene.VertexBuffer; |
||||||
|
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; |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Nehon |
||||||
|
*/ |
||||||
|
public class SkeletonControl extends AbstractControl implements Savable, Cloneable { |
||||||
|
|
||||||
|
/** |
||||||
|
* The skelrton of the model |
||||||
|
*/ |
||||||
|
private Skeleton skeleton; |
||||||
|
/** |
||||||
|
* List of targets which this controller effects. |
||||||
|
*/ |
||||||
|
Mesh[] targets; |
||||||
|
|
||||||
|
public SkeletonControl() { |
||||||
|
} |
||||||
|
|
||||||
|
public SkeletonControl(Node model, Mesh[] targets, Skeleton skeleton) { |
||||||
|
super(model); |
||||||
|
this.skeleton = skeleton; |
||||||
|
this.targets = targets; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void controlUpdate(float tpf) { |
||||||
|
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++) { |
||||||
|
// only update targets with bone-vertex assignments
|
||||||
|
if (targets[i].getBuffer(Type.BoneIndex) != null) { |
||||||
|
softwareSkinUpdate(targets[i], offsetMatrices); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void resetToBind() { |
||||||
|
for (int i = 0; i < targets.length; i++) { |
||||||
|
Mesh mesh = targets[i]; |
||||||
|
if (targets[i].getBuffer(Type.BindPosePosition) != null) { |
||||||
|
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(); |
||||||
|
pb.put(bpb).clear(); |
||||||
|
nb.put(bnb).clear(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) { |
||||||
|
int maxWeightsPerVert = mesh.getMaxNumWeights(); |
||||||
|
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 * 3; |
||||||
|
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); |
||||||
|
} |
||||||
|
|
||||||
|
vb.updateData(fvb); |
||||||
|
nb.updateData(fnb); |
||||||
|
|
||||||
|
// mesh.updateBound();
|
||||||
|
} |
||||||
|
|
||||||
|
final void reset() { |
||||||
|
resetToBind(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void controlRender(RenderManager rm, ViewPort vp) { |
||||||
|
} |
||||||
|
|
||||||
|
public Control cloneForSpatial(Spatial spatial) { |
||||||
|
Node clonedNode = (Node) spatial; |
||||||
|
AnimControl ctrl = spatial.getControl(AnimControl.class); |
||||||
|
SkeletonControl clone = new SkeletonControl(); |
||||||
|
|
||||||
|
clone.skeleton = ctrl.getSkeleton(); |
||||||
|
Mesh[] meshes = new Mesh[targets.length]; |
||||||
|
for (int i = 0; i < meshes.length; i++) { |
||||||
|
meshes[i] = ((Geometry) clonedNode.getChild(i)).getMesh(); |
||||||
|
} |
||||||
|
for (int i = meshes.length; 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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
clone.targets = meshes; |
||||||
|
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; |
||||||
|
} |
||||||
|
|
||||||
|
public Skeleton getSkeleton() { |
||||||
|
return skeleton; |
||||||
|
} |
||||||
|
|
||||||
|
public void setSkeleton(Skeleton skeleton) { |
||||||
|
this.skeleton = skeleton; |
||||||
|
} |
||||||
|
|
||||||
|
public Mesh[] getTargets() { |
||||||
|
return targets; |
||||||
|
} |
||||||
|
|
||||||
|
public void setTargets(Mesh[] targets) { |
||||||
|
this.targets = targets; |
||||||
|
} |
||||||
|
|
||||||
|
@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); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue