• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 Rémy 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      */
KinematicRagdollControl()141     public KinematicRagdollControl() {
142     }
143 
KinematicRagdollControl(float weightThreshold)144     public KinematicRagdollControl(float weightThreshold) {
145         this.weightThreshold = weightThreshold;
146     }
147 
KinematicRagdollControl(RagdollPreset preset, float weightThreshold)148     public KinematicRagdollControl(RagdollPreset preset, float weightThreshold) {
149         this.preset = preset;
150         this.weightThreshold = weightThreshold;
151     }
152 
KinematicRagdollControl(RagdollPreset preset)153     public KinematicRagdollControl(RagdollPreset preset) {
154         this.preset = preset;
155     }
156 
update(float tpf)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      */
matchPhysicObjectToBone(PhysicsBoneLink link, Vector3f position, Quaternion tmpRot1)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 
cloneForSpatial(Spatial spatial)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      */
reBuild()295     public void reBuild() {
296         setSpatial(targetModel);
297         addToPhysicsSpace();
298     }
299 
setSpatial(Spatial model)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      */
addBoneName(String name)349     public void addBoneName(String name) {
350         boneList.add(name);
351     }
352 
scanSpatial(Spatial model)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 
boneRecursion(Spatial model, Bone bone, PhysicsRigidBody parent, int reccount, Map<Integer, List<Float>> pointsMap)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      */
setJointLimit(String boneName, float maxX, float minX, float maxY, float minY, float maxZ, float minZ)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      */
getJoint(String boneName)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 
clearData()458     private void clearData() {
459         boneLinks.clear();
460         baseRigidBody = null;
461     }
462 
addToPhysicsSpace()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 
removeFromPhysicsSpace()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      */
setEnabled(boolean enabled)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      */
isEnabled()525     public boolean isEnabled() {
526         return enabled;
527     }
528 
attachDebugShape(AssetManager manager)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 
detachDebugShape()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      */
render(RenderManager rm, ViewPort vp)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      */
setPhysicsSpace(PhysicsSpace space)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      */
getPhysicsSpace()591     public PhysicsSpace getPhysicsSpace() {
592         return space;
593     }
594 
595     /**
596      * serialize this control
597      * @param ex
598      * @throws IOException
599      */
write(JmeExporter ex)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      */
read(JmeImporter im)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      */
collision(PhysicsCollisionEvent event)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      */
setMode(Mode mode)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      */
blendToKinematicMode(float blendTime)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      */
setKinematicMode()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      */
setRagdollMode()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      */
getMode()766     public Mode getMode() {
767         return mode;
768     }
769 
770     /**
771      * add a
772      * @param listener
773      */
addCollisionListener(RagdollCollisionListener listener)774     public void addCollisionListener(RagdollCollisionListener listener) {
775         if (listeners == null) {
776             listeners = new ArrayList<RagdollCollisionListener>();
777         }
778         listeners.add(listener);
779     }
780 
setRootMass(float rootMass)781     public void setRootMass(float rootMass) {
782         this.rootMass = rootMass;
783     }
784 
getTotalMass()785     public float getTotalMass() {
786         return totalMass;
787     }
788 
getWeightThreshold()789     public float getWeightThreshold() {
790         return weightThreshold;
791     }
792 
setWeightThreshold(float weightThreshold)793     public void setWeightThreshold(float weightThreshold) {
794         this.weightThreshold = weightThreshold;
795     }
796 
getEventDispatchImpulseThreshold()797     public float getEventDispatchImpulseThreshold() {
798         return eventDispatchImpulseThreshold;
799     }
800 
setEventDispatchImpulseThreshold(float eventDispatchImpulseThreshold)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      */
setCcdMotionThreshold(float value)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      */
setCcdSweptSphereRadius(float value)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
setBoneCcdMotionThreshold(String boneName, float value)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
setBoneCcdSweptSphereRadius(String boneName, float value)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      */
getBoneRigidBody(String boneName)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