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 */
32package com.jme3.bullet.control;
33
34import com.jme3.animation.AnimControl;
35import com.jme3.animation.Bone;
36import com.jme3.animation.Skeleton;
37import com.jme3.animation.SkeletonControl;
38import com.jme3.asset.AssetManager;
39import com.jme3.bullet.PhysicsSpace;
40import com.jme3.bullet.collision.PhysicsCollisionEvent;
41import com.jme3.bullet.collision.PhysicsCollisionListener;
42import com.jme3.bullet.collision.PhysicsCollisionObject;
43import com.jme3.bullet.collision.RagdollCollisionListener;
44import com.jme3.bullet.collision.shapes.BoxCollisionShape;
45import com.jme3.bullet.collision.shapes.HullCollisionShape;
46import com.jme3.bullet.control.ragdoll.HumanoidRagdollPreset;
47import com.jme3.bullet.control.ragdoll.RagdollPreset;
48import com.jme3.bullet.control.ragdoll.RagdollUtils;
49import com.jme3.bullet.joints.SixDofJoint;
50import com.jme3.bullet.objects.PhysicsRigidBody;
51import com.jme3.export.JmeExporter;
52import com.jme3.export.JmeImporter;
53import com.jme3.math.Quaternion;
54import com.jme3.math.Vector3f;
55import com.jme3.renderer.RenderManager;
56import com.jme3.renderer.ViewPort;
57import com.jme3.scene.Node;
58import com.jme3.scene.Spatial;
59import com.jme3.scene.control.Control;
60import com.jme3.util.TempVars;
61import java.io.IOException;
62import java.util.*;
63import java.util.logging.Level;
64import 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 */
96public 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