1 /* 2 * To change this template, choose Tools | Templates 3 * and open the template in the editor. 4 */ 5 package com.jme3.animation; 6 7 import com.jme3.export.*; 8 import com.jme3.math.FastMath; 9 import com.jme3.math.Matrix4f; 10 import com.jme3.renderer.RenderManager; 11 import com.jme3.renderer.ViewPort; 12 import com.jme3.scene.*; 13 import com.jme3.scene.VertexBuffer.Type; 14 import com.jme3.scene.control.AbstractControl; 15 import com.jme3.scene.control.Control; 16 import com.jme3.util.TempVars; 17 import java.io.IOException; 18 import java.nio.ByteBuffer; 19 import java.nio.FloatBuffer; 20 import java.util.ArrayList; 21 22 /** 23 * The Skeleton control deforms a model according to a skeleton, 24 * It handles the computation of the deformation matrices and performs 25 * the transformations on the mesh 26 * 27 * @author Rmy Bouquet Based on AnimControl by Kirill Vainer 28 */ 29 public class SkeletonControl extends AbstractControl implements Cloneable { 30 31 /** 32 * The skeleton of the model 33 */ 34 private Skeleton skeleton; 35 /** 36 * List of targets which this controller effects. 37 */ 38 private Mesh[] targets; 39 /** 40 * Used to track when a mesh was updated. Meshes are only updated 41 * if they are visible in at least one camera. 42 */ 43 private boolean wasMeshUpdated = false; 44 45 /** 46 * Serialization only. Do not use. 47 */ 48 public SkeletonControl() { 49 } 50 51 /** 52 * Creates a skeleton control. 53 * The list of targets will be acquired automatically when 54 * the control is attached to a node. 55 * 56 * @param skeleton the skeleton 57 */ 58 public SkeletonControl(Skeleton skeleton) { 59 this.skeleton = skeleton; 60 } 61 62 /** 63 * Creates a skeleton control. 64 * 65 * @param targets the meshes controlled by the skeleton 66 * @param skeleton the skeleton 67 */ 68 @Deprecated 69 SkeletonControl(Mesh[] targets, Skeleton skeleton) { 70 this.skeleton = skeleton; 71 this.targets = targets; 72 } 73 74 private boolean isMeshAnimated(Mesh mesh) { 75 return mesh.getBuffer(Type.BindPosePosition) != null; 76 } 77 78 private Mesh[] findTargets(Node node) { 79 Mesh sharedMesh = null; 80 ArrayList<Mesh> animatedMeshes = new ArrayList<Mesh>(); 81 82 for (Spatial child : node.getChildren()) { 83 if (!(child instanceof Geometry)) { 84 continue; // could be an attachment node, ignore. 85 } 86 87 Geometry geom = (Geometry) child; 88 89 // is this geometry using a shared mesh? 90 Mesh childSharedMesh = geom.getUserData(UserData.JME_SHAREDMESH); 91 92 if (childSharedMesh != null) { 93 // Don't bother with non-animated shared meshes 94 if (isMeshAnimated(childSharedMesh)) { 95 // child is using shared mesh, 96 // so animate the shared mesh but ignore child 97 if (sharedMesh == null) { 98 sharedMesh = childSharedMesh; 99 } else if (sharedMesh != childSharedMesh) { 100 throw new IllegalStateException("Two conflicting shared meshes for " + node); 101 } 102 } 103 } else { 104 Mesh mesh = geom.getMesh(); 105 if (isMeshAnimated(mesh)) { 106 animatedMeshes.add(mesh); 107 } 108 } 109 } 110 111 if (sharedMesh != null) { 112 animatedMeshes.add(sharedMesh); 113 } 114 115 return animatedMeshes.toArray(new Mesh[animatedMeshes.size()]); 116 } 117 118 @Override 119 public void setSpatial(Spatial spatial) { 120 super.setSpatial(spatial); 121 if (spatial != null) { 122 Node node = (Node) spatial; 123 targets = findTargets(node); 124 } else { 125 targets = null; 126 } 127 } 128 129 @Override 130 protected void controlRender(RenderManager rm, ViewPort vp) { 131 if (!wasMeshUpdated) { 132 resetToBind(); // reset morph meshes to bind pose 133 134 Matrix4f[] offsetMatrices = skeleton.computeSkinningMatrices(); 135 136 // if hardware skinning is supported, the matrices and weight buffer 137 // will be sent by the SkinningShaderLogic object assigned to the shader 138 for (int i = 0; i < targets.length; i++) { 139 // NOTE: This assumes that code higher up 140 // Already ensured those targets are animated 141 // otherwise a crash will happen in skin update 142 //if (isMeshAnimated(targets[i])) { 143 softwareSkinUpdate(targets[i], offsetMatrices); 144 //} 145 } 146 147 wasMeshUpdated = true; 148 } 149 } 150 151 @Override 152 protected void controlUpdate(float tpf) { 153 wasMeshUpdated = false; 154 } 155 156 void resetToBind() { 157 for (Mesh mesh : targets) { 158 if (isMeshAnimated(mesh)) { 159 VertexBuffer bi = mesh.getBuffer(Type.BoneIndex); 160 ByteBuffer bib = (ByteBuffer) bi.getData(); 161 if (!bib.hasArray()) { 162 mesh.prepareForAnim(true); // prepare for software animation 163 } 164 VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition); 165 VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal); 166 VertexBuffer pos = mesh.getBuffer(Type.Position); 167 VertexBuffer norm = mesh.getBuffer(Type.Normal); 168 FloatBuffer pb = (FloatBuffer) pos.getData(); 169 FloatBuffer nb = (FloatBuffer) norm.getData(); 170 FloatBuffer bpb = (FloatBuffer) bindPos.getData(); 171 FloatBuffer bnb = (FloatBuffer) bindNorm.getData(); 172 pb.clear(); 173 nb.clear(); 174 bpb.clear(); 175 bnb.clear(); 176 177 //reseting bind tangents if there is a bind tangent buffer 178 VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent); 179 if (bindTangents != null) { 180 VertexBuffer tangents = mesh.getBuffer(Type.Tangent); 181 FloatBuffer tb = (FloatBuffer) tangents.getData(); 182 FloatBuffer btb = (FloatBuffer) bindTangents.getData(); 183 tb.clear(); 184 btb.clear(); 185 tb.put(btb).clear(); 186 } 187 188 189 pb.put(bpb).clear(); 190 nb.put(bnb).clear(); 191 } 192 } 193 } 194 195 public Control cloneForSpatial(Spatial spatial) { 196 Node clonedNode = (Node) spatial; 197 AnimControl ctrl = spatial.getControl(AnimControl.class); 198 SkeletonControl clone = new SkeletonControl(); 199 clone.setSpatial(clonedNode); 200 201 clone.skeleton = ctrl.getSkeleton(); 202 // Fix animated targets for the cloned node 203 clone.targets = findTargets(clonedNode); 204 205 // Fix attachments for the cloned node 206 for (int i = 0; i < clonedNode.getQuantity(); i++) { 207 // go through attachment nodes, apply them to correct bone 208 Spatial child = clonedNode.getChild(i); 209 if (child instanceof Node) { 210 Node clonedAttachNode = (Node) child; 211 Bone originalBone = (Bone) clonedAttachNode.getUserData("AttachedBone"); 212 213 if (originalBone != null) { 214 Bone clonedBone = clone.skeleton.getBone(originalBone.getName()); 215 216 clonedAttachNode.setUserData("AttachedBone", clonedBone); 217 clonedBone.setAttachmentsNode(clonedAttachNode); 218 } 219 } 220 } 221 222 return clone; 223 } 224 225 /** 226 * 227 * @param boneName the name of the bone 228 * @return the node attached to this bone 229 */ 230 public Node getAttachmentsNode(String boneName) { 231 Bone b = skeleton.getBone(boneName); 232 if (b == null) { 233 throw new IllegalArgumentException("Given bone name does not exist " 234 + "in the skeleton."); 235 } 236 237 Node n = b.getAttachmentsNode(); 238 Node model = (Node) spatial; 239 model.attachChild(n); 240 return n; 241 } 242 243 /** 244 * returns the skeleton of this control 245 * @return 246 */ 247 public Skeleton getSkeleton() { 248 return skeleton; 249 } 250 251 /** 252 * sets the skeleton for this control 253 * @param skeleton 254 */ 255 // public void setSkeleton(Skeleton skeleton) { 256 // this.skeleton = skeleton; 257 // } 258 /** 259 * returns the targets meshes of this control 260 * @return 261 */ 262 public Mesh[] getTargets() { 263 return targets; 264 } 265 266 /** 267 * sets the target meshes of this control 268 * @param targets 269 */ 270 // public void setTargets(Mesh[] targets) { 271 // this.targets = targets; 272 // } 273 /** 274 * Update the mesh according to the given transformation matrices 275 * @param mesh then mesh 276 * @param offsetMatrices the transformation matrices to apply 277 */ 278 private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) { 279 280 VertexBuffer tb = mesh.getBuffer(Type.Tangent); 281 if (tb == null) { 282 //if there are no tangents use the classic skinning 283 applySkinning(mesh, offsetMatrices); 284 } else { 285 //if there are tangents use the skinning with tangents 286 applySkinningTangents(mesh, offsetMatrices, tb); 287 } 288 289 290 } 291 292 /** 293 * Method to apply skinning transforms to a mesh's buffers 294 * @param mesh the mesh 295 * @param offsetMatrices the offset matices to apply 296 */ 297 private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) { 298 int maxWeightsPerVert = mesh.getMaxNumWeights(); 299 if (maxWeightsPerVert <= 0) { 300 throw new IllegalStateException("Max weights per vert is incorrectly set!"); 301 } 302 303 int fourMinusMaxWeights = 4 - maxWeightsPerVert; 304 305 // NOTE: This code assumes the vertex buffer is in bind pose 306 // resetToBind() has been called this frame 307 VertexBuffer vb = mesh.getBuffer(Type.Position); 308 FloatBuffer fvb = (FloatBuffer) vb.getData(); 309 fvb.rewind(); 310 311 VertexBuffer nb = mesh.getBuffer(Type.Normal); 312 FloatBuffer fnb = (FloatBuffer) nb.getData(); 313 fnb.rewind(); 314 315 // get boneIndexes and weights for mesh 316 ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData(); 317 FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); 318 319 ib.rewind(); 320 wb.rewind(); 321 322 float[] weights = wb.array(); 323 byte[] indices = ib.array(); 324 int idxWeights = 0; 325 326 TempVars vars = TempVars.get(); 327 328 329 float[] posBuf = vars.skinPositions; 330 float[] normBuf = vars.skinNormals; 331 332 int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length)); 333 int bufLength = posBuf.length; 334 for (int i = iterations - 1; i >= 0; i--) { 335 // read next set of positions and normals from native buffer 336 bufLength = Math.min(posBuf.length, fvb.remaining()); 337 fvb.get(posBuf, 0, bufLength); 338 fnb.get(normBuf, 0, bufLength); 339 int verts = bufLength / 3; 340 int idxPositions = 0; 341 342 // iterate vertices and apply skinning transform for each effecting bone 343 for (int vert = verts - 1; vert >= 0; vert--) { 344 float nmx = normBuf[idxPositions]; 345 float vtx = posBuf[idxPositions++]; 346 float nmy = normBuf[idxPositions]; 347 float vty = posBuf[idxPositions++]; 348 float nmz = normBuf[idxPositions]; 349 float vtz = posBuf[idxPositions++]; 350 351 float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0; 352 353 for (int w = maxWeightsPerVert - 1; w >= 0; w--) { 354 float weight = weights[idxWeights]; 355 Matrix4f mat = offsetMatrices[indices[idxWeights++]]; 356 357 rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight; 358 ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight; 359 rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight; 360 361 rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight; 362 rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight; 363 rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight; 364 } 365 366 idxWeights += fourMinusMaxWeights; 367 368 idxPositions -= 3; 369 normBuf[idxPositions] = rnx; 370 posBuf[idxPositions++] = rx; 371 normBuf[idxPositions] = rny; 372 posBuf[idxPositions++] = ry; 373 normBuf[idxPositions] = rnz; 374 posBuf[idxPositions++] = rz; 375 } 376 377 fvb.position(fvb.position() - bufLength); 378 fvb.put(posBuf, 0, bufLength); 379 fnb.position(fnb.position() - bufLength); 380 fnb.put(normBuf, 0, bufLength); 381 } 382 383 vars.release(); 384 385 vb.updateData(fvb); 386 nb.updateData(fnb); 387 388 } 389 390 /** 391 * Specific method for skinning with tangents to avoid cluttering the classic skinning calculation with 392 * null checks that would slow down the process even if tangents don't have to be computed. 393 * Also the iteration has additional indexes since tangent has 4 components instead of 3 for pos and norm 394 * @param maxWeightsPerVert maximum number of weights per vertex 395 * @param mesh the mesh 396 * @param offsetMatrices the offsetMaytrices to apply 397 * @param tb the tangent vertexBuffer 398 */ 399 private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) { 400 int maxWeightsPerVert = mesh.getMaxNumWeights(); 401 402 if (maxWeightsPerVert <= 0) { 403 throw new IllegalStateException("Max weights per vert is incorrectly set!"); 404 } 405 406 int fourMinusMaxWeights = 4 - maxWeightsPerVert; 407 408 // NOTE: This code assumes the vertex buffer is in bind pose 409 // resetToBind() has been called this frame 410 VertexBuffer vb = mesh.getBuffer(Type.Position); 411 FloatBuffer fvb = (FloatBuffer) vb.getData(); 412 fvb.rewind(); 413 414 VertexBuffer nb = mesh.getBuffer(Type.Normal); 415 416 FloatBuffer fnb = (FloatBuffer) nb.getData(); 417 fnb.rewind(); 418 419 420 FloatBuffer ftb = (FloatBuffer) tb.getData(); 421 ftb.rewind(); 422 423 424 // get boneIndexes and weights for mesh 425 ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData(); 426 FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); 427 428 ib.rewind(); 429 wb.rewind(); 430 431 float[] weights = wb.array(); 432 byte[] indices = ib.array(); 433 int idxWeights = 0; 434 435 TempVars vars = TempVars.get(); 436 437 438 float[] posBuf = vars.skinPositions; 439 float[] normBuf = vars.skinNormals; 440 float[] tanBuf = vars.skinTangents; 441 442 int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length)); 443 int bufLength = 0; 444 int tanLength = 0; 445 for (int i = iterations - 1; i >= 0; i--) { 446 // read next set of positions and normals from native buffer 447 bufLength = Math.min(posBuf.length, fvb.remaining()); 448 tanLength = Math.min(tanBuf.length, ftb.remaining()); 449 fvb.get(posBuf, 0, bufLength); 450 fnb.get(normBuf, 0, bufLength); 451 ftb.get(tanBuf, 0, tanLength); 452 int verts = bufLength / 3; 453 int idxPositions = 0; 454 //tangents has their own index because of the 4 components 455 int idxTangents = 0; 456 457 // iterate vertices and apply skinning transform for each effecting bone 458 for (int vert = verts - 1; vert >= 0; vert--) { 459 float nmx = normBuf[idxPositions]; 460 float vtx = posBuf[idxPositions++]; 461 float nmy = normBuf[idxPositions]; 462 float vty = posBuf[idxPositions++]; 463 float nmz = normBuf[idxPositions]; 464 float vtz = posBuf[idxPositions++]; 465 466 float tnx = tanBuf[idxTangents++]; 467 float tny = tanBuf[idxTangents++]; 468 float tnz = tanBuf[idxTangents++]; 469 470 //skipping the 4th component of the tangent since it doesn't have to be transformed 471 idxTangents++; 472 473 float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0, rtx = 0, rty = 0, rtz = 0; 474 475 for (int w = maxWeightsPerVert - 1; w >= 0; w--) { 476 float weight = weights[idxWeights]; 477 Matrix4f mat = offsetMatrices[indices[idxWeights++]]; 478 479 rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight; 480 ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight; 481 rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight; 482 483 rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight; 484 rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight; 485 rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight; 486 487 rtx += (tnx * mat.m00 + tny * mat.m01 + tnz * mat.m02) * weight; 488 rty += (tnx * mat.m10 + tny * mat.m11 + tnz * mat.m12) * weight; 489 rtz += (tnx * mat.m20 + tny * mat.m21 + tnz * mat.m22) * weight; 490 } 491 492 idxWeights += fourMinusMaxWeights; 493 494 idxPositions -= 3; 495 496 normBuf[idxPositions] = rnx; 497 posBuf[idxPositions++] = rx; 498 normBuf[idxPositions] = rny; 499 posBuf[idxPositions++] = ry; 500 normBuf[idxPositions] = rnz; 501 posBuf[idxPositions++] = rz; 502 503 idxTangents -= 4; 504 505 tanBuf[idxTangents++] = rtx; 506 tanBuf[idxTangents++] = rty; 507 tanBuf[idxTangents++] = rtz; 508 509 //once again skipping the 4th component of the tangent 510 idxTangents++; 511 } 512 513 fvb.position(fvb.position() - bufLength); 514 fvb.put(posBuf, 0, bufLength); 515 fnb.position(fnb.position() - bufLength); 516 fnb.put(normBuf, 0, bufLength); 517 ftb.position(ftb.position() - tanLength); 518 ftb.put(tanBuf, 0, tanLength); 519 } 520 521 vars.release(); 522 523 vb.updateData(fvb); 524 nb.updateData(fnb); 525 tb.updateData(ftb); 526 527 528 } 529 530 @Override 531 public void write(JmeExporter ex) throws IOException { 532 super.write(ex); 533 OutputCapsule oc = ex.getCapsule(this); 534 oc.write(targets, "targets", null); 535 oc.write(skeleton, "skeleton", null); 536 } 537 538 @Override 539 public void read(JmeImporter im) throws IOException { 540 super.read(im); 541 InputCapsule in = im.getCapsule(this); 542 Savable[] sav = in.readSavableArray("targets", null); 543 if (sav != null) { 544 targets = new Mesh[sav.length]; 545 System.arraycopy(sav, 0, targets, 0, sav.length); 546 } 547 skeleton = (Skeleton) in.readSavable("skeleton", null); 548 } 549 } 550