1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.jme3.bullet.control; 33 34 import com.jme3.animation.AnimControl; 35 import com.jme3.animation.Bone; 36 import com.jme3.animation.Skeleton; 37 import com.jme3.animation.SkeletonControl; 38 import com.jme3.asset.AssetManager; 39 import com.jme3.bullet.PhysicsSpace; 40 import com.jme3.bullet.collision.PhysicsCollisionEvent; 41 import com.jme3.bullet.collision.PhysicsCollisionListener; 42 import com.jme3.bullet.collision.PhysicsCollisionObject; 43 import com.jme3.bullet.collision.RagdollCollisionListener; 44 import com.jme3.bullet.collision.shapes.BoxCollisionShape; 45 import com.jme3.bullet.collision.shapes.HullCollisionShape; 46 import com.jme3.bullet.control.ragdoll.HumanoidRagdollPreset; 47 import com.jme3.bullet.control.ragdoll.RagdollPreset; 48 import com.jme3.bullet.control.ragdoll.RagdollUtils; 49 import com.jme3.bullet.joints.SixDofJoint; 50 import com.jme3.bullet.objects.PhysicsRigidBody; 51 import com.jme3.export.JmeExporter; 52 import com.jme3.export.JmeImporter; 53 import com.jme3.math.Quaternion; 54 import com.jme3.math.Vector3f; 55 import com.jme3.renderer.RenderManager; 56 import com.jme3.renderer.ViewPort; 57 import com.jme3.scene.Node; 58 import com.jme3.scene.Spatial; 59 import com.jme3.scene.control.Control; 60 import com.jme3.util.TempVars; 61 import java.io.IOException; 62 import java.util.*; 63 import java.util.logging.Level; 64 import java.util.logging.Logger; 65 66 /**<strong>This control is still a WIP, use it at your own risk</strong><br> 67 * To use this control you need a model with an AnimControl and a SkeletonControl.<br> 68 * This should be the case if you imported an animated model from Ogre or blender.<br> 69 * Note enabling/disabling the control add/removes it from the physic space<br> 70 * <p> 71 * This control creates collision shapes for each bones of the skeleton when you call spatial.addControl(ragdollControl). 72 * <ul> 73 * <li>The shape is HullCollision shape based on the vertices associated with each bone and based on a tweakable weight threshold (see setWeightThreshold)</li> 74 * <li>If you don't want each bone to be a collision shape, you can specify what bone to use by using the addBoneName method<br> 75 * By using this method, bone that are not used to create a shape, are "merged" to their parent to create the collision shape. 76 * </li> 77 * </ul> 78 *</p> 79 *<p> 80 *There are 2 modes for this control : 81 * <ul> 82 * <li><strong>The kinematic modes :</strong><br> 83 * this is the default behavior, this means that the collision shapes of the body are able to interact with physics enabled objects. 84 * in this mode physic shapes follow the moovements of the animated skeleton (for example animated by a key framed animation) 85 * this mode is enabled by calling setKinematicMode(); 86 * </li> 87 * <li><strong>The ragdoll modes :</strong><br> 88 * To enable this behavior, you need to call setRagdollMode() method. 89 * In this mode the charater is entirely controled by physics, so it will fall under the gravity and move if any force is applied to it. 90 * </li> 91 * </ul> 92 *</p> 93 * 94 * @author Normen Hansen and Rmy Bouquet (Nehon) 95 */ 96 public class KinematicRagdollControl implements PhysicsControl, PhysicsCollisionListener { 97 98 protected static final Logger logger = Logger.getLogger(KinematicRagdollControl.class.getName()); 99 protected Map<String, PhysicsBoneLink> boneLinks = new HashMap<String, PhysicsBoneLink>(); 100 protected Skeleton skeleton; 101 protected PhysicsSpace space; 102 protected boolean enabled = true; 103 protected boolean debug = false; 104 protected PhysicsRigidBody baseRigidBody; 105 protected float weightThreshold = -1.0f; 106 protected Spatial targetModel; 107 protected Vector3f initScale; 108 protected Mode mode = Mode.Kinetmatic; 109 protected boolean blendedControl = false; 110 protected float blendTime = 1.0f; 111 protected float blendStart = 0.0f; 112 protected List<RagdollCollisionListener> listeners; 113 protected float eventDispatchImpulseThreshold = 10; 114 protected RagdollPreset preset = new HumanoidRagdollPreset(); 115 protected Set<String> boneList = new TreeSet<String>(); 116 protected Vector3f modelPosition = new Vector3f(); 117 protected Quaternion modelRotation = new Quaternion(); 118 protected float rootMass = 15; 119 protected float totalMass = 0; 120 protected boolean added = false; 121 122 public static enum Mode { 123 124 Kinetmatic, 125 Ragdoll 126 } 127 128 protected class PhysicsBoneLink { 129 130 protected Bone bone; 131 protected Quaternion initalWorldRotation; 132 protected SixDofJoint joint; 133 protected PhysicsRigidBody rigidBody; 134 protected Quaternion startBlendingRot = new Quaternion(); 135 protected Vector3f startBlendingPos = new Vector3f(); 136 } 137 138 /** 139 * contruct a KinematicRagdollControl 140 */ 141 public KinematicRagdollControl() { 142 } 143 144 public KinematicRagdollControl(float weightThreshold) { 145 this.weightThreshold = weightThreshold; 146 } 147 148 public KinematicRagdollControl(RagdollPreset preset, float weightThreshold) { 149 this.preset = preset; 150 this.weightThreshold = weightThreshold; 151 } 152 153 public KinematicRagdollControl(RagdollPreset preset) { 154 this.preset = preset; 155 } 156 157 public void update(float tpf) { 158 if (!enabled) { 159 return; 160 } 161 TempVars vars = TempVars.get(); 162 163 Quaternion tmpRot1 = vars.quat1; 164 Quaternion tmpRot2 = vars.quat2; 165 166 //if the ragdoll has the control of the skeleton, we update each bone with its position in physic world space. 167 if (mode == mode.Ragdoll && targetModel.getLocalTranslation().equals(modelPosition)) { 168 for (PhysicsBoneLink link : boneLinks.values()) { 169 170 Vector3f position = vars.vect1; 171 172 //retrieving bone position in physic world space 173 Vector3f p = link.rigidBody.getMotionState().getWorldLocation(); 174 //transforming this position with inverse transforms of the model 175 targetModel.getWorldTransform().transformInverseVector(p, position); 176 177 //retrieving bone rotation in physic world space 178 Quaternion q = link.rigidBody.getMotionState().getWorldRotationQuat(); 179 180 //multiplying this rotation by the initialWorld rotation of the bone, 181 //then transforming it with the inverse world rotation of the model 182 tmpRot1.set(q).multLocal(link.initalWorldRotation); 183 tmpRot2.set(targetModel.getWorldRotation()).inverseLocal().mult(tmpRot1, tmpRot1); 184 tmpRot1.normalizeLocal(); 185 186 //if the bone is the root bone, we apply the physic's transform to the model, so its position and rotation are correctly updated 187 if (link.bone.getParent() == null) { 188 189 //offsetting the physic's position/rotation by the root bone inverse model space position/rotaion 190 modelPosition.set(p).subtractLocal(link.bone.getWorldBindPosition()); 191 targetModel.getParent().getWorldTransform().transformInverseVector(modelPosition, modelPosition); 192 modelRotation.set(q).multLocal(tmpRot2.set(link.bone.getWorldBindRotation()).inverseLocal()); 193 194 195 //applying transforms to the model 196 targetModel.setLocalTranslation(modelPosition); 197 198 targetModel.setLocalRotation(modelRotation); 199 200 //Applying computed transforms to the bone 201 link.bone.setUserTransformsWorld(position, tmpRot1); 202 203 } else { 204 //if boneList is empty, this means that every bone in the ragdoll has a collision shape, 205 //so we just update the bone position 206 if (boneList.isEmpty()) { 207 link.bone.setUserTransformsWorld(position, tmpRot1); 208 } else { 209 //boneList is not empty, this means some bones of the skeleton might not be associated with a collision shape. 210 //So we update them recusively 211 RagdollUtils.setTransform(link.bone, position, tmpRot1, false, boneList); 212 } 213 } 214 } 215 } else { 216 //the ragdoll does not have the controll, so the keyframed animation updates the physic position of the physic bonces 217 for (PhysicsBoneLink link : boneLinks.values()) { 218 219 Vector3f position = vars.vect1; 220 221 //if blended control this means, keyframed animation is updating the skeleton, 222 //but to allow smooth transition, we blend this transformation with the saved position of the ragdoll 223 if (blendedControl) { 224 Vector3f position2 = vars.vect2; 225 //initializing tmp vars with the start position/rotation of the ragdoll 226 position.set(link.startBlendingPos); 227 tmpRot1.set(link.startBlendingRot); 228 229 //interpolating between ragdoll position/rotation and keyframed position/rotation 230 tmpRot2.set(tmpRot1).nlerp(link.bone.getModelSpaceRotation(), blendStart / blendTime); 231 position2.set(position).interpolate(link.bone.getModelSpacePosition(), blendStart / blendTime); 232 tmpRot1.set(tmpRot2); 233 position.set(position2); 234 235 //updating bones transforms 236 if (boneList.isEmpty()) { 237 //we ensure we have the control to update the bone 238 link.bone.setUserControl(true); 239 link.bone.setUserTransformsWorld(position, tmpRot1); 240 //we give control back to the key framed animation. 241 link.bone.setUserControl(false); 242 } else { 243 RagdollUtils.setTransform(link.bone, position, tmpRot1, true, boneList); 244 } 245 246 } 247 //setting skeleton transforms to the ragdoll 248 matchPhysicObjectToBone(link, position, tmpRot1); 249 modelPosition.set(targetModel.getLocalTranslation()); 250 251 } 252 253 //time control for blending 254 if (blendedControl) { 255 blendStart += tpf; 256 if (blendStart > blendTime) { 257 blendedControl = false; 258 } 259 } 260 } 261 vars.release(); 262 263 } 264 265 /** 266 * Set the transforms of a rigidBody to match the transforms of a bone. 267 * this is used to make the ragdoll follow the skeleton motion while in Kinematic mode 268 * @param link the link containing the bone and the rigidBody 269 * @param position just a temp vector for position 270 * @param tmpRot1 just a temp quaternion for rotation 271 */ 272 private void matchPhysicObjectToBone(PhysicsBoneLink link, Vector3f position, Quaternion tmpRot1) { 273 //computing position from rotation and scale 274 targetModel.getWorldTransform().transformVector(link.bone.getModelSpacePosition(), position); 275 276 //computing rotation 277 tmpRot1.set(link.bone.getModelSpaceRotation()).multLocal(link.bone.getWorldBindInverseRotation()); 278 targetModel.getWorldRotation().mult(tmpRot1, tmpRot1); 279 tmpRot1.normalizeLocal(); 280 281 //updating physic location/rotation of the physic bone 282 link.rigidBody.setPhysicsLocation(position); 283 link.rigidBody.setPhysicsRotation(tmpRot1); 284 285 } 286 287 public Control cloneForSpatial(Spatial spatial) { 288 throw new UnsupportedOperationException("Not supported yet."); 289 } 290 291 /** 292 * rebuild the ragdoll 293 * this is useful if you applied scale on the ragdoll after it's been initialized 294 */ 295 public void reBuild() { 296 setSpatial(targetModel); 297 addToPhysicsSpace(); 298 } 299 300 public void setSpatial(Spatial model) { 301 if (model == null) { 302 removeFromPhysicsSpace(); 303 clearData(); 304 return; 305 } 306 targetModel = model; 307 Node parent = model.getParent(); 308 309 310 Vector3f initPosition = model.getLocalTranslation().clone(); 311 Quaternion initRotation = model.getLocalRotation().clone(); 312 initScale = model.getLocalScale().clone(); 313 314 model.removeFromParent(); 315 model.setLocalTranslation(Vector3f.ZERO); 316 model.setLocalRotation(Quaternion.IDENTITY); 317 model.setLocalScale(1); 318 //HACK ALERT change this 319 //I remove the skeletonControl and readd it to the spatial to make sure it's after the ragdollControl in the stack 320 //Find a proper way to order the controls. 321 SkeletonControl sc = model.getControl(SkeletonControl.class); 322 model.removeControl(sc); 323 model.addControl(sc); 324 //---- 325 326 removeFromPhysicsSpace(); 327 clearData(); 328 // put into bind pose and compute bone transforms in model space 329 // maybe dont reset to ragdoll out of animations? 330 scanSpatial(model); 331 332 333 if (parent != null) { 334 parent.attachChild(model); 335 336 } 337 model.setLocalTranslation(initPosition); 338 model.setLocalRotation(initRotation); 339 model.setLocalScale(initScale); 340 341 logger.log(Level.INFO, "Created physics ragdoll for skeleton {0}", skeleton); 342 } 343 344 /** 345 * Add a bone name to this control 346 * Using this method you can specify which bones of the skeleton will be used to build the collision shapes. 347 * @param name 348 */ 349 public void addBoneName(String name) { 350 boneList.add(name); 351 } 352 353 private void scanSpatial(Spatial model) { 354 AnimControl animControl = model.getControl(AnimControl.class); 355 Map<Integer, List<Float>> pointsMap = null; 356 if (weightThreshold == -1.0f) { 357 pointsMap = RagdollUtils.buildPointMap(model); 358 } 359 360 skeleton = animControl.getSkeleton(); 361 skeleton.resetAndUpdate(); 362 for (int i = 0; i < skeleton.getRoots().length; i++) { 363 Bone childBone = skeleton.getRoots()[i]; 364 if (childBone.getParent() == null) { 365 logger.log(Level.INFO, "Found root bone in skeleton {0}", skeleton); 366 baseRigidBody = new PhysicsRigidBody(new BoxCollisionShape(Vector3f.UNIT_XYZ.mult(0.1f)), 1); 367 baseRigidBody.setKinematic(mode == Mode.Kinetmatic); 368 boneRecursion(model, childBone, baseRigidBody, 1, pointsMap); 369 } 370 } 371 } 372 373 private void boneRecursion(Spatial model, Bone bone, PhysicsRigidBody parent, int reccount, Map<Integer, List<Float>> pointsMap) { 374 PhysicsRigidBody parentShape = parent; 375 if (boneList.isEmpty() || boneList.contains(bone.getName())) { 376 377 PhysicsBoneLink link = new PhysicsBoneLink(); 378 link.bone = bone; 379 380 //creating the collision shape 381 HullCollisionShape shape = null; 382 if (pointsMap != null) { 383 //build a shape for the bone, using the vertices that are most influenced by this bone 384 shape = RagdollUtils.makeShapeFromPointMap(pointsMap, RagdollUtils.getBoneIndices(link.bone, skeleton, boneList), initScale, link.bone.getModelSpacePosition()); 385 } else { 386 //build a shape for the bone, using the vertices associated with this bone with a weight above the threshold 387 shape = RagdollUtils.makeShapeFromVerticeWeights(model, RagdollUtils.getBoneIndices(link.bone, skeleton, boneList), initScale, link.bone.getModelSpacePosition(), weightThreshold); 388 } 389 390 PhysicsRigidBody shapeNode = new PhysicsRigidBody(shape, rootMass / (float) reccount); 391 392 shapeNode.setKinematic(mode == Mode.Kinetmatic); 393 totalMass += rootMass / (float) reccount; 394 395 link.rigidBody = shapeNode; 396 link.initalWorldRotation = bone.getModelSpaceRotation().clone(); 397 398 if (parent != null) { 399 //get joint position for parent 400 Vector3f posToParent = new Vector3f(); 401 if (bone.getParent() != null) { 402 bone.getModelSpacePosition().subtract(bone.getParent().getModelSpacePosition(), posToParent).multLocal(initScale); 403 } 404 405 SixDofJoint joint = new SixDofJoint(parent, shapeNode, posToParent, new Vector3f(0, 0, 0f), true); 406 preset.setupJointForBone(bone.getName(), joint); 407 408 link.joint = joint; 409 joint.setCollisionBetweenLinkedBodys(false); 410 } 411 boneLinks.put(bone.getName(), link); 412 shapeNode.setUserObject(link); 413 parentShape = shapeNode; 414 } 415 416 for (Iterator<Bone> it = bone.getChildren().iterator(); it.hasNext();) { 417 Bone childBone = it.next(); 418 boneRecursion(model, childBone, parentShape, reccount + 1, pointsMap); 419 } 420 } 421 422 /** 423 * Set the joint limits for the joint between the given bone and its parent. 424 * This method can't work before attaching the control to a spatial 425 * @param boneName the name of the bone 426 * @param maxX the maximum rotation on the x axis (in radians) 427 * @param minX the minimum rotation on the x axis (in radians) 428 * @param maxY the maximum rotation on the y axis (in radians) 429 * @param minY the minimum rotation on the z axis (in radians) 430 * @param maxZ the maximum rotation on the z axis (in radians) 431 * @param minZ the minimum rotation on the z axis (in radians) 432 */ 433 public void setJointLimit(String boneName, float maxX, float minX, float maxY, float minY, float maxZ, float minZ) { 434 PhysicsBoneLink link = boneLinks.get(boneName); 435 if (link != null) { 436 RagdollUtils.setJointLimit(link.joint, maxX, minX, maxY, minY, maxZ, minZ); 437 } else { 438 logger.log(Level.WARNING, "Not joint was found for bone {0}. make sure you call spatial.addControl(ragdoll) before setting joints limit", boneName); 439 } 440 } 441 442 /** 443 * Return the joint between the given bone and its parent. 444 * This return null if it's called before attaching the control to a spatial 445 * @param boneName the name of the bone 446 * @return the joint between the given bone and its parent 447 */ 448 public SixDofJoint getJoint(String boneName) { 449 PhysicsBoneLink link = boneLinks.get(boneName); 450 if (link != null) { 451 return link.joint; 452 } else { 453 logger.log(Level.WARNING, "Not joint was found for bone {0}. make sure you call spatial.addControl(ragdoll) before setting joints limit", boneName); 454 return null; 455 } 456 } 457 458 private void clearData() { 459 boneLinks.clear(); 460 baseRigidBody = null; 461 } 462 463 private void addToPhysicsSpace() { 464 if (space == null) { 465 return; 466 } 467 if (baseRigidBody != null) { 468 space.add(baseRigidBody); 469 added = true; 470 } 471 for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) { 472 PhysicsBoneLink physicsBoneLink = it.next(); 473 if (physicsBoneLink.rigidBody != null) { 474 space.add(physicsBoneLink.rigidBody); 475 if (physicsBoneLink.joint != null) { 476 space.add(physicsBoneLink.joint); 477 478 } 479 added = true; 480 } 481 } 482 } 483 484 protected void removeFromPhysicsSpace() { 485 if (space == null) { 486 return; 487 } 488 if (baseRigidBody != null) { 489 space.remove(baseRigidBody); 490 } 491 for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) { 492 PhysicsBoneLink physicsBoneLink = it.next(); 493 if (physicsBoneLink.joint != null) { 494 space.remove(physicsBoneLink.joint); 495 if (physicsBoneLink.rigidBody != null) { 496 space.remove(physicsBoneLink.rigidBody); 497 } 498 } 499 } 500 added = false; 501 } 502 503 /** 504 * enable or disable the control 505 * note that if enabled is true and that the physic space has been set on the ragdoll, the ragdoll is added to the physic space 506 * if enabled is false the ragdoll is removed from physic space. 507 * @param enabled 508 */ 509 public void setEnabled(boolean enabled) { 510 if (this.enabled == enabled) { 511 return; 512 } 513 this.enabled = enabled; 514 if (!enabled && space != null) { 515 removeFromPhysicsSpace(); 516 } else if (enabled && space != null) { 517 addToPhysicsSpace(); 518 } 519 } 520 521 /** 522 * returns true if the control is enabled 523 * @return 524 */ 525 public boolean isEnabled() { 526 return enabled; 527 } 528 529 protected void attachDebugShape(AssetManager manager) { 530 for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) { 531 PhysicsBoneLink physicsBoneLink = it.next(); 532 physicsBoneLink.rigidBody.createDebugShape(manager); 533 } 534 debug = true; 535 } 536 537 protected void detachDebugShape() { 538 for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) { 539 PhysicsBoneLink physicsBoneLink = it.next(); 540 physicsBoneLink.rigidBody.detachDebugShape(); 541 } 542 debug = false; 543 } 544 545 /** 546 * For internal use only 547 * specific render for the ragdoll(if debugging) 548 * @param rm 549 * @param vp 550 */ 551 public void render(RenderManager rm, ViewPort vp) { 552 if (enabled && space != null && space.getDebugManager() != null) { 553 if (!debug) { 554 attachDebugShape(space.getDebugManager()); 555 } 556 for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) { 557 PhysicsBoneLink physicsBoneLink = it.next(); 558 Spatial debugShape = physicsBoneLink.rigidBody.debugShape(); 559 if (debugShape != null) { 560 debugShape.setLocalTranslation(physicsBoneLink.rigidBody.getMotionState().getWorldLocation()); 561 debugShape.setLocalRotation(physicsBoneLink.rigidBody.getMotionState().getWorldRotationQuat()); 562 debugShape.updateGeometricState(); 563 rm.renderScene(debugShape, vp); 564 } 565 } 566 } 567 } 568 569 /** 570 * set the physic space to this ragdoll 571 * @param space 572 */ 573 public void setPhysicsSpace(PhysicsSpace space) { 574 if (space == null) { 575 removeFromPhysicsSpace(); 576 this.space = space; 577 } else { 578 if (this.space == space) { 579 return; 580 } 581 this.space = space; 582 addToPhysicsSpace(); 583 this.space.addCollisionListener(this); 584 } 585 } 586 587 /** 588 * returns the physic space 589 * @return 590 */ 591 public PhysicsSpace getPhysicsSpace() { 592 return space; 593 } 594 595 /** 596 * serialize this control 597 * @param ex 598 * @throws IOException 599 */ 600 public void write(JmeExporter ex) throws IOException { 601 throw new UnsupportedOperationException("Not supported yet."); 602 } 603 604 /** 605 * de-serialize this control 606 * @param im 607 * @throws IOException 608 */ 609 public void read(JmeImporter im) throws IOException { 610 throw new UnsupportedOperationException("Not supported yet."); 611 } 612 613 /** 614 * For internal use only 615 * callback for collisionevent 616 * @param event 617 */ 618 public void collision(PhysicsCollisionEvent event) { 619 PhysicsCollisionObject objA = event.getObjectA(); 620 PhysicsCollisionObject objB = event.getObjectB(); 621 622 //excluding collisions that involve 2 parts of the ragdoll 623 if (event.getNodeA() == null && event.getNodeB() == null) { 624 return; 625 } 626 627 //discarding low impulse collision 628 if (event.getAppliedImpulse() < eventDispatchImpulseThreshold) { 629 return; 630 } 631 632 boolean hit = false; 633 Bone hitBone = null; 634 PhysicsCollisionObject hitObject = null; 635 636 //Computing which bone has been hit 637 if (objA.getUserObject() instanceof PhysicsBoneLink) { 638 PhysicsBoneLink link = (PhysicsBoneLink) objA.getUserObject(); 639 if (link != null) { 640 hit = true; 641 hitBone = link.bone; 642 hitObject = objB; 643 } 644 } 645 646 if (objB.getUserObject() instanceof PhysicsBoneLink) { 647 PhysicsBoneLink link = (PhysicsBoneLink) objB.getUserObject(); 648 if (link != null) { 649 hit = true; 650 hitBone = link.bone; 651 hitObject = objA; 652 653 } 654 } 655 656 //dispatching the event if the ragdoll has been hit 657 if (hit && listeners != null) { 658 for (RagdollCollisionListener listener : listeners) { 659 listener.collide(hitBone, hitObject, event); 660 } 661 } 662 663 } 664 665 /** 666 * Enable or disable the ragdoll behaviour. 667 * if ragdollEnabled is true, the character motion will only be powerd by physics 668 * else, the characted will be animated by the keyframe animation, 669 * but will be able to physically interact with its physic environnement 670 * @param ragdollEnabled 671 */ 672 protected void setMode(Mode mode) { 673 this.mode = mode; 674 AnimControl animControl = targetModel.getControl(AnimControl.class); 675 animControl.setEnabled(mode == Mode.Kinetmatic); 676 677 baseRigidBody.setKinematic(mode == Mode.Kinetmatic); 678 TempVars vars = TempVars.get(); 679 680 for (PhysicsBoneLink link : boneLinks.values()) { 681 link.rigidBody.setKinematic(mode == Mode.Kinetmatic); 682 if (mode == Mode.Ragdoll) { 683 Quaternion tmpRot1 = vars.quat1; 684 Vector3f position = vars.vect1; 685 //making sure that the ragdoll is at the correct place. 686 matchPhysicObjectToBone(link, position, tmpRot1); 687 } 688 689 } 690 vars.release(); 691 692 for (Bone bone : skeleton.getRoots()) { 693 RagdollUtils.setUserControl(bone, mode == Mode.Ragdoll); 694 } 695 } 696 697 /** 698 * Smoothly blend from Ragdoll mode to Kinematic mode 699 * This is useful to blend ragdoll actual position to a keyframe animation for example 700 * @param blendTime the blending time between ragdoll to anim. 701 */ 702 public void blendToKinematicMode(float blendTime) { 703 if (mode == Mode.Kinetmatic) { 704 return; 705 } 706 blendedControl = true; 707 this.blendTime = blendTime; 708 mode = Mode.Kinetmatic; 709 AnimControl animControl = targetModel.getControl(AnimControl.class); 710 animControl.setEnabled(true); 711 712 713 TempVars vars = TempVars.get(); 714 for (PhysicsBoneLink link : boneLinks.values()) { 715 716 Vector3f p = link.rigidBody.getMotionState().getWorldLocation(); 717 Vector3f position = vars.vect1; 718 719 targetModel.getWorldTransform().transformInverseVector(p, position); 720 721 Quaternion q = link.rigidBody.getMotionState().getWorldRotationQuat(); 722 Quaternion q2 = vars.quat1; 723 Quaternion q3 = vars.quat2; 724 725 q2.set(q).multLocal(link.initalWorldRotation).normalizeLocal(); 726 q3.set(targetModel.getWorldRotation()).inverseLocal().mult(q2, q2); 727 q2.normalizeLocal(); 728 link.startBlendingPos.set(position); 729 link.startBlendingRot.set(q2); 730 link.rigidBody.setKinematic(true); 731 } 732 vars.release(); 733 734 for (Bone bone : skeleton.getRoots()) { 735 RagdollUtils.setUserControl(bone, false); 736 } 737 738 blendStart = 0; 739 } 740 741 /** 742 * Set the control into Kinematic mode 743 * In theis mode, the collision shapes follow the movements of the skeleton, 744 * and can interact with physical environement 745 */ 746 public void setKinematicMode() { 747 if (mode != Mode.Kinetmatic) { 748 setMode(Mode.Kinetmatic); 749 } 750 } 751 752 /** 753 * Sets the control into Ragdoll mode 754 * The skeleton is entirely controlled by physics. 755 */ 756 public void setRagdollMode() { 757 if (mode != Mode.Ragdoll) { 758 setMode(Mode.Ragdoll); 759 } 760 } 761 762 /** 763 * retruns the mode of this control 764 * @return 765 */ 766 public Mode getMode() { 767 return mode; 768 } 769 770 /** 771 * add a 772 * @param listener 773 */ 774 public void addCollisionListener(RagdollCollisionListener listener) { 775 if (listeners == null) { 776 listeners = new ArrayList<RagdollCollisionListener>(); 777 } 778 listeners.add(listener); 779 } 780 781 public void setRootMass(float rootMass) { 782 this.rootMass = rootMass; 783 } 784 785 public float getTotalMass() { 786 return totalMass; 787 } 788 789 public float getWeightThreshold() { 790 return weightThreshold; 791 } 792 793 public void setWeightThreshold(float weightThreshold) { 794 this.weightThreshold = weightThreshold; 795 } 796 797 public float getEventDispatchImpulseThreshold() { 798 return eventDispatchImpulseThreshold; 799 } 800 801 public void setEventDispatchImpulseThreshold(float eventDispatchImpulseThreshold) { 802 this.eventDispatchImpulseThreshold = eventDispatchImpulseThreshold; 803 } 804 805 /** 806 * Set the CcdMotionThreshold of all the bone's rigidBodies of the ragdoll 807 * @see PhysicsRigidBody#setCcdMotionThreshold(float) 808 * @param value 809 */ 810 public void setCcdMotionThreshold(float value) { 811 for (PhysicsBoneLink link : boneLinks.values()) { 812 link.rigidBody.setCcdMotionThreshold(value); 813 } 814 } 815 816 /** 817 * Set the CcdSweptSphereRadius of all the bone's rigidBodies of the ragdoll 818 * @see PhysicsRigidBody#setCcdSweptSphereRadius(float) 819 * @param value 820 */ 821 public void setCcdSweptSphereRadius(float value) { 822 for (PhysicsBoneLink link : boneLinks.values()) { 823 link.rigidBody.setCcdSweptSphereRadius(value); 824 } 825 } 826 827 /** 828 * Set the CcdMotionThreshold of the given bone's rigidBodies of the ragdoll 829 * @see PhysicsRigidBody#setCcdMotionThreshold(float) 830 * @param value 831 * @deprecated use getBoneRigidBody(String BoneName).setCcdMotionThreshold(float) instead 832 */ 833 @Deprecated 834 public void setBoneCcdMotionThreshold(String boneName, float value) { 835 PhysicsBoneLink link = boneLinks.get(boneName); 836 if (link != null) { 837 link.rigidBody.setCcdMotionThreshold(value); 838 } 839 } 840 841 /** 842 * Set the CcdSweptSphereRadius of the given bone's rigidBodies of the ragdoll 843 * @see PhysicsRigidBody#setCcdSweptSphereRadius(float) 844 * @param value 845 * @deprecated use getBoneRigidBody(String BoneName).setCcdSweptSphereRadius(float) instead 846 */ 847 @Deprecated 848 public void setBoneCcdSweptSphereRadius(String boneName, float value) { 849 PhysicsBoneLink link = boneLinks.get(boneName); 850 if (link != null) { 851 link.rigidBody.setCcdSweptSphereRadius(value); 852 } 853 } 854 855 /** 856 * return the rigidBody associated to the given bone 857 * @param boneName the name of the bone 858 * @return the associated rigidBody. 859 */ 860 public PhysicsRigidBody getBoneRigidBody(String boneName) { 861 PhysicsBoneLink link = boneLinks.get(boneName); 862 if (link != null) { 863 return link.rigidBody; 864 } 865 return null; 866 } 867 } 868