Home | History | Annotate | Download | only in control
      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