/* * Copyright (c) 2009-2012 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.bullet.control; import com.jme3.animation.AnimControl; import com.jme3.animation.Bone; import com.jme3.animation.Skeleton; import com.jme3.animation.SkeletonControl; import com.jme3.bullet.PhysicsSpace; import com.jme3.bullet.collision.PhysicsCollisionEvent; import com.jme3.bullet.collision.PhysicsCollisionListener; import com.jme3.bullet.collision.PhysicsCollisionObject; import com.jme3.bullet.collision.RagdollCollisionListener; import com.jme3.bullet.collision.shapes.BoxCollisionShape; import com.jme3.bullet.collision.shapes.HullCollisionShape; import com.jme3.bullet.control.ragdoll.HumanoidRagdollPreset; import com.jme3.bullet.control.ragdoll.RagdollPreset; import com.jme3.bullet.control.ragdoll.RagdollUtils; import com.jme3.bullet.joints.SixDofJoint; import com.jme3.bullet.objects.PhysicsRigidBody; import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; import com.jme3.export.Savable; import com.jme3.math.FastMath; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.control.Control; import com.jme3.util.TempVars; import com.jme3.util.clone.Cloner; import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; /** * <strong>This control is still a WIP, use it at your own risk</strong><br> To * use this control you need a model with an AnimControl and a * SkeletonControl.<br> This should be the case if you imported an animated * model from Ogre or blender.<br> Note enabling/disabling the control * add/removes it from the physic space<br> <p> This control creates collision * shapes for each bones of the skeleton when you call * spatial.addControl(ragdollControl). <ul> <li>The shape is HullCollision shape * based on the vertices associated with each bone and based on a tweakable * weight threshold (see setWeightThreshold)</li> <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> By using this method, bone that are not used to create * a shape, are "merged" to their parent to create the collision shape. </li> * </ul> </p> <p> There are 2 modes for this control : <ul> <li><strong>The * kinematic modes :</strong><br> this is the default behavior, this means that * the collision shapes of the body are able to interact with physics enabled * objects. in this mode physic shapes follow the moovements of the animated * skeleton (for example animated by a key framed animation) this mode is * enabled by calling setKinematicMode(); </li> <li><strong>The ragdoll modes * :</strong><br> To enable this behavior, you need to call setRagdollMode() * method. 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. </li> * </ul> </p> * * @author Normen Hansen and Rémy Bouquet (Nehon) */ public class KinematicRagdollControl extends AbstractPhysicsControl implements PhysicsCollisionListener, JmeCloneable { protected static final Logger logger = Logger.getLogger(KinematicRagdollControl.class.getName()); protected List<RagdollCollisionListener> listeners; protected final Set<String> boneList = new TreeSet<String>(); protected final Map<String, PhysicsBoneLink> boneLinks = new HashMap<String, PhysicsBoneLink>(); protected final Vector3f modelPosition = new Vector3f(); protected final Quaternion modelRotation = new Quaternion(); protected final PhysicsRigidBody baseRigidBody; protected Spatial targetModel; protected Skeleton skeleton; protected RagdollPreset preset = new HumanoidRagdollPreset(); protected Vector3f initScale; protected Mode mode = Mode.Kinematic; protected boolean debug = false; protected boolean blendedControl = false; protected float weightThreshold = -1.0f; protected float blendStart = 0.0f; protected float blendTime = 1.0f; protected float eventDispatchImpulseThreshold = 10; protected float rootMass = 15; protected float totalMass = 0; private Map<String, Vector3f> ikTargets = new HashMap<String, Vector3f>(); private Map<String, Integer> ikChainDepth = new HashMap<String, Integer>(); private float ikRotSpeed = 7f; private float limbDampening = 0.6f; private float IKThreshold = 0.1f; public static enum Mode { Kinematic, Ragdoll, IK } public class PhysicsBoneLink implements Savable { protected PhysicsRigidBody rigidBody; protected Bone bone; protected SixDofJoint joint; protected Quaternion initalWorldRotation; protected Quaternion startBlendingRot = new Quaternion(); protected Vector3f startBlendingPos = new Vector3f(); public PhysicsBoneLink() { } public Bone getBone() { return bone; } public PhysicsRigidBody getRigidBody() { return rigidBody; } public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(rigidBody, "rigidBody", null); oc.write(bone, "bone", null); oc.write(joint, "joint", null); oc.write(initalWorldRotation, "initalWorldRotation", null); oc.write(startBlendingRot, "startBlendingRot", new Quaternion()); oc.write(startBlendingPos, "startBlendingPos", new Vector3f()); } public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); rigidBody = (PhysicsRigidBody) ic.readSavable("rigidBody", null); bone = (Bone) ic.readSavable("bone", null); joint = (SixDofJoint) ic.readSavable("joint", null); initalWorldRotation = (Quaternion) ic.readSavable("initalWorldRotation", null); startBlendingRot = (Quaternion) ic.readSavable("startBlendingRot", null); startBlendingPos = (Vector3f) ic.readSavable("startBlendingPos", null); } } /** * contruct a KinematicRagdollControl */ public KinematicRagdollControl() { baseRigidBody = new PhysicsRigidBody(new BoxCollisionShape(Vector3f.UNIT_XYZ.mult(0.1f)), 1); baseRigidBody.setKinematic(mode == Mode.Kinematic); } public KinematicRagdollControl(float weightThreshold) { this(); this.weightThreshold = weightThreshold; } public KinematicRagdollControl(RagdollPreset preset, float weightThreshold) { this(); this.preset = preset; this.weightThreshold = weightThreshold; } public KinematicRagdollControl(RagdollPreset preset) { this(); this.preset = preset; } public void update(float tpf) { if (!enabled) { return; } if(mode == Mode.IK){ ikUpdate(tpf); } else if (mode == mode.Ragdoll && targetModel.getLocalTranslation().equals(modelPosition)) { //if the ragdoll has the control of the skeleton, we update each bone with its position in physic world space. ragDollUpdate(tpf); } else { kinematicUpdate(tpf); } } protected void ragDollUpdate(float tpf) { TempVars vars = TempVars.get(); Quaternion tmpRot1 = vars.quat1; Quaternion tmpRot2 = vars.quat2; for (PhysicsBoneLink link : boneLinks.values()) { Vector3f position = vars.vect1; //retrieving bone position in physic world space Vector3f p = link.rigidBody.getMotionState().getWorldLocation(); //transforming this position with inverse transforms of the model targetModel.getWorldTransform().transformInverseVector(p, position); //retrieving bone rotation in physic world space Quaternion q = link.rigidBody.getMotionState().getWorldRotationQuat(); //multiplying this rotation by the initialWorld rotation of the bone, //then transforming it with the inverse world rotation of the model tmpRot1.set(q).multLocal(link.initalWorldRotation); tmpRot2.set(targetModel.getWorldRotation()).inverseLocal().mult(tmpRot1, tmpRot1); tmpRot1.normalizeLocal(); //if the bone is the root bone, we apply the physic's transform to the model, so its position and rotation are correctly updated if (link.bone.getParent() == null) { //offsetting the physic's position/rotation by the root bone inverse model space position/rotaion modelPosition.set(p).subtractLocal(link.bone.getBindPosition()); targetModel.getParent().getWorldTransform().transformInverseVector(modelPosition, modelPosition); modelRotation.set(q).multLocal(tmpRot2.set(link.bone.getBindRotation()).inverseLocal()); //applying transforms to the model targetModel.setLocalTranslation(modelPosition); targetModel.setLocalRotation(modelRotation); //Applying computed transforms to the bone link.bone.setUserTransformsInModelSpace(position, tmpRot1); } else { //if boneList is empty, this means that every bone in the ragdoll has a collision shape, //so we just update the bone position if (boneList.isEmpty()) { link.bone.setUserTransformsInModelSpace(position, tmpRot1); } else { //boneList is not empty, this means some bones of the skeleton might not be associated with a collision shape. //So we update them recusively RagdollUtils.setTransform(link.bone, position, tmpRot1, false, boneList); } } } vars.release(); } protected void kinematicUpdate(float tpf) { //the ragdoll does not have the controll, so the keyframed animation updates the physic position of the physic bonces TempVars vars = TempVars.get(); Quaternion tmpRot1 = vars.quat1; Quaternion tmpRot2 = vars.quat2; Vector3f position = vars.vect1; for (PhysicsBoneLink link : boneLinks.values()) { // if(link.usedbyIK){ // continue; // } //if blended control this means, keyframed animation is updating the skeleton, //but to allow smooth transition, we blend this transformation with the saved position of the ragdoll if (blendedControl) { Vector3f position2 = vars.vect2; //initializing tmp vars with the start position/rotation of the ragdoll position.set(link.startBlendingPos); tmpRot1.set(link.startBlendingRot); //interpolating between ragdoll position/rotation and keyframed position/rotation tmpRot2.set(tmpRot1).nlerp(link.bone.getModelSpaceRotation(), blendStart / blendTime); position2.set(position).interpolateLocal(link.bone.getModelSpacePosition(), blendStart / blendTime); tmpRot1.set(tmpRot2); position.set(position2); //updating bones transforms if (boneList.isEmpty()) { //we ensure we have the control to update the bone link.bone.setUserControl(true); link.bone.setUserTransformsInModelSpace(position, tmpRot1); //we give control back to the key framed animation. link.bone.setUserControl(false); } else { RagdollUtils.setTransform(link.bone, position, tmpRot1, true, boneList); } } //setting skeleton transforms to the ragdoll matchPhysicObjectToBone(link, position, tmpRot1); modelPosition.set(targetModel.getLocalTranslation()); } //time control for blending if (blendedControl) { blendStart += tpf; if (blendStart > blendTime) { blendedControl = false; } } vars.release(); } private void ikUpdate(float tpf){ TempVars vars = TempVars.get(); Quaternion tmpRot1 = vars.quat1; Quaternion[] tmpRot2 = new Quaternion[]{vars.quat2, new Quaternion()}; Iterator<String> it = ikTargets.keySet().iterator(); float distance; Bone bone; String boneName; while (it.hasNext()) { boneName = it.next(); bone = (Bone) boneLinks.get(boneName).bone; if (!bone.hasUserControl()) { Logger.getLogger(KinematicRagdollControl.class.getSimpleName()).log(Level.FINE, "{0} doesn't have user control", boneName); continue; } distance = bone.getModelSpacePosition().distance(ikTargets.get(boneName)); if (distance < IKThreshold) { Logger.getLogger(KinematicRagdollControl.class.getSimpleName()).log(Level.FINE, "Distance is close enough"); continue; } int depth = 0; int maxDepth = ikChainDepth.get(bone.getName()); updateBone(boneLinks.get(bone.getName()), tpf * (float) FastMath.sqrt(distance), vars, tmpRot1, tmpRot2, bone, ikTargets.get(boneName), depth, maxDepth); Vector3f position = vars.vect1; for (PhysicsBoneLink link : boneLinks.values()) { matchPhysicObjectToBone(link, position, tmpRot1); } } vars.release(); } public void updateBone(PhysicsBoneLink link, float tpf, TempVars vars, Quaternion tmpRot1, Quaternion[] tmpRot2, Bone tipBone, Vector3f target, int depth, int maxDepth) { if (link == null || link.bone.getParent() == null) { return; } Quaternion preQuat = link.bone.getLocalRotation(); Vector3f vectorAxis; float[] measureDist = new float[]{Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY}; for (int dirIndex = 0; dirIndex < 3; dirIndex++) { if (dirIndex == 0) { vectorAxis = Vector3f.UNIT_Z; } else if (dirIndex == 1) { vectorAxis = Vector3f.UNIT_X; } else { vectorAxis = Vector3f.UNIT_Y; } for (int posOrNeg = 0; posOrNeg < 2; posOrNeg++) { float rot = ikRotSpeed * tpf / (link.rigidBody.getMass() * 2); rot = FastMath.clamp(rot, link.joint.getRotationalLimitMotor(dirIndex).getLoLimit(), link.joint.getRotationalLimitMotor(dirIndex).getHiLimit()); tmpRot1.fromAngleAxis(rot, vectorAxis); // tmpRot1.fromAngleAxis(rotSpeed * tpf / (link.rigidBody.getMass() * 2), vectorAxis); tmpRot2[posOrNeg] = link.bone.getLocalRotation().mult(tmpRot1); tmpRot2[posOrNeg].normalizeLocal(); ikRotSpeed = -ikRotSpeed; link.bone.setLocalRotation(tmpRot2[posOrNeg]); link.bone.update(); measureDist[posOrNeg] = tipBone.getModelSpacePosition().distance(target); link.bone.setLocalRotation(preQuat); } if (measureDist[0] < measureDist[1]) { link.bone.setLocalRotation(tmpRot2[0]); } else if (measureDist[0] > measureDist[1]) { link.bone.setLocalRotation(tmpRot2[1]); } } link.bone.getLocalRotation().normalizeLocal(); link.bone.update(); // link.usedbyIK = true; if (link.bone.getParent() != null && depth < maxDepth) { updateBone(boneLinks.get(link.bone.getParent().getName()), tpf * limbDampening, vars, tmpRot1, tmpRot2, tipBone, target, depth + 1, maxDepth); } } /** * Set the transforms of a rigidBody to match the transforms of a bone. this * is used to make the ragdoll follow the skeleton motion while in Kinematic * mode * * @param link the link containing the bone and the rigidBody * @param position just a temp vector for position * @param tmpRot1 just a temp quaternion for rotation */ protected void matchPhysicObjectToBone(PhysicsBoneLink link, Vector3f position, Quaternion tmpRot1) { //computing position from rotation and scale targetModel.getWorldTransform().transformVector(link.bone.getModelSpacePosition(), position); //computing rotation tmpRot1.set(link.bone.getModelSpaceRotation()).multLocal(link.bone.getModelBindInverseRotation()); targetModel.getWorldRotation().mult(tmpRot1, tmpRot1); tmpRot1.normalizeLocal(); //updating physic location/rotation of the physic bone link.rigidBody.setPhysicsLocation(position); link.rigidBody.setPhysicsRotation(tmpRot1); } /** * rebuild the ragdoll this is useful if you applied scale on the ragdoll * after it's been initialized, same as reattaching. */ public void reBuild() { if (spatial == null) { return; } removeSpatialData(spatial); createSpatialData(spatial); } @Override protected void createSpatialData(Spatial model) { targetModel = model; Node parent = model.getParent(); Vector3f initPosition = model.getLocalTranslation().clone(); Quaternion initRotation = model.getLocalRotation().clone(); initScale = model.getLocalScale().clone(); model.removeFromParent(); model.setLocalTranslation(Vector3f.ZERO); model.setLocalRotation(Quaternion.IDENTITY); model.setLocalScale(1); //HACK ALERT change this //I remove the skeletonControl and readd it to the spatial to make sure it's after the ragdollControl in the stack //Find a proper way to order the controls. SkeletonControl sc = model.getControl(SkeletonControl.class); if(sc == null){ throw new IllegalArgumentException("The root node of the model should have a SkeletonControl. Make sure the control is there and that it's not on a sub node."); } model.removeControl(sc); model.addControl(sc); // put into bind pose and compute bone transforms in model space // maybe dont reset to ragdoll out of animations? scanSpatial(model); if (parent != null) { parent.attachChild(model); } model.setLocalTranslation(initPosition); model.setLocalRotation(initRotation); model.setLocalScale(initScale); if (added) { addPhysics(space); } logger.log(Level.FINE, "Created physics ragdoll for skeleton {0}", skeleton); } @Override protected void removeSpatialData(Spatial spat) { if (added) { removePhysics(space); } boneLinks.clear(); } /** * Add a bone name to this control Using this method you can specify which * bones of the skeleton will be used to build the collision shapes. * * @param name */ public void addBoneName(String name) { boneList.add(name); } protected void scanSpatial(Spatial model) { AnimControl animControl = model.getControl(AnimControl.class); Map<Integer, List<Float>> pointsMap = null; if (weightThreshold == -1.0f) { pointsMap = RagdollUtils.buildPointMap(model); } skeleton = animControl.getSkeleton(); skeleton.resetAndUpdate(); for (int i = 0; i < skeleton.getRoots().length; i++) { Bone childBone = skeleton.getRoots()[i]; if (childBone.getParent() == null) { logger.log(Level.FINE, "Found root bone in skeleton {0}", skeleton); boneRecursion(model, childBone, baseRigidBody, 1, pointsMap); } } } protected void boneRecursion(Spatial model, Bone bone, PhysicsRigidBody parent, int reccount, Map<Integer, List<Float>> pointsMap) { PhysicsRigidBody parentShape = parent; if (boneList.isEmpty() || boneList.contains(bone.getName())) { PhysicsBoneLink link = new PhysicsBoneLink(); link.bone = bone; //creating the collision shape HullCollisionShape shape = null; if (pointsMap != null) { //build a shape for the bone, using the vertices that are most influenced by this bone shape = RagdollUtils.makeShapeFromPointMap(pointsMap, RagdollUtils.getBoneIndices(link.bone, skeleton, boneList), initScale, link.bone.getModelSpacePosition()); } else { //build a shape for the bone, using the vertices associated with this bone with a weight above the threshold shape = RagdollUtils.makeShapeFromVerticeWeights(model, RagdollUtils.getBoneIndices(link.bone, skeleton, boneList), initScale, link.bone.getModelSpacePosition(), weightThreshold); } PhysicsRigidBody shapeNode = new PhysicsRigidBody(shape, rootMass / (float) reccount); shapeNode.setKinematic(mode == Mode.Kinematic); totalMass += rootMass / (float) reccount; link.rigidBody = shapeNode; link.initalWorldRotation = bone.getModelSpaceRotation().clone(); if (parent != null) { //get joint position for parent Vector3f posToParent = new Vector3f(); if (bone.getParent() != null) { bone.getModelSpacePosition().subtract(bone.getParent().getModelSpacePosition(), posToParent).multLocal(initScale); } SixDofJoint joint = new SixDofJoint(parent, shapeNode, posToParent, new Vector3f(0, 0, 0f), true); preset.setupJointForBone(bone.getName(), joint); link.joint = joint; joint.setCollisionBetweenLinkedBodys(false); } boneLinks.put(bone.getName(), link); shapeNode.setUserObject(link); parentShape = shapeNode; } for (Iterator<Bone> it = bone.getChildren().iterator(); it.hasNext();) { Bone childBone = it.next(); boneRecursion(model, childBone, parentShape, reccount + 1, pointsMap); } } /** * Set the joint limits for the joint between the given bone and its parent. * This method can't work before attaching the control to a spatial * * @param boneName the name of the bone * @param maxX the maximum rotation on the x axis (in radians) * @param minX the minimum rotation on the x axis (in radians) * @param maxY the maximum rotation on the y axis (in radians) * @param minY the minimum rotation on the z axis (in radians) * @param maxZ the maximum rotation on the z axis (in radians) * @param minZ the minimum rotation on the z axis (in radians) */ public void setJointLimit(String boneName, float maxX, float minX, float maxY, float minY, float maxZ, float minZ) { PhysicsBoneLink link = boneLinks.get(boneName); if (link != null) { RagdollUtils.setJointLimit(link.joint, maxX, minX, maxY, minY, maxZ, minZ); } else { logger.log(Level.WARNING, "Not joint was found for bone {0}. make sure you call spatial.addControl(ragdoll) before setting joints limit", boneName); } } /** * Return the joint between the given bone and its parent. This return null * if it's called before attaching the control to a spatial * * @param boneName the name of the bone * @return the joint between the given bone and its parent */ public SixDofJoint getJoint(String boneName) { PhysicsBoneLink link = boneLinks.get(boneName); if (link != null) { return link.joint; } else { logger.log(Level.WARNING, "Not joint was found for bone {0}. make sure you call spatial.addControl(ragdoll) before setting joints limit", boneName); return null; } } @Override protected void setPhysicsLocation(Vector3f vec) { if (baseRigidBody != null) { baseRigidBody.setPhysicsLocation(vec); } } @Override protected void setPhysicsRotation(Quaternion quat) { if (baseRigidBody != null) { baseRigidBody.setPhysicsRotation(quat); } } @Override protected void addPhysics(PhysicsSpace space) { if (baseRigidBody != null) { space.add(baseRigidBody); } for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) { PhysicsBoneLink physicsBoneLink = it.next(); if (physicsBoneLink.rigidBody != null) { space.add(physicsBoneLink.rigidBody); if (physicsBoneLink.joint != null) { space.add(physicsBoneLink.joint); } } } space.addCollisionListener(this); } @Override protected void removePhysics(PhysicsSpace space) { if (baseRigidBody != null) { space.remove(baseRigidBody); } for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) { PhysicsBoneLink physicsBoneLink = it.next(); if (physicsBoneLink.joint != null) { space.remove(physicsBoneLink.joint); if (physicsBoneLink.rigidBody != null) { space.remove(physicsBoneLink.rigidBody); } } } space.removeCollisionListener(this); } /** * For internal use only callback for collisionevent * * @param event */ public void collision(PhysicsCollisionEvent event) { PhysicsCollisionObject objA = event.getObjectA(); PhysicsCollisionObject objB = event.getObjectB(); //excluding collisions that involve 2 parts of the ragdoll if (event.getNodeA() == null && event.getNodeB() == null) { return; } //discarding low impulse collision if (event.getAppliedImpulse() < eventDispatchImpulseThreshold) { return; } boolean hit = false; Bone hitBone = null; PhysicsCollisionObject hitObject = null; //Computing which bone has been hit if (objA.getUserObject() instanceof PhysicsBoneLink) { PhysicsBoneLink link = (PhysicsBoneLink) objA.getUserObject(); if (link != null) { hit = true; hitBone = link.bone; hitObject = objB; } } if (objB.getUserObject() instanceof PhysicsBoneLink) { PhysicsBoneLink link = (PhysicsBoneLink) objB.getUserObject(); if (link != null) { hit = true; hitBone = link.bone; hitObject = objA; } } //dispatching the event if the ragdoll has been hit if (hit && listeners != null) { for (RagdollCollisionListener listener : listeners) { listener.collide(hitBone, hitObject, event); } } } /** * Enable or disable the ragdoll behaviour. if ragdollEnabled is true, the * character motion will only be powerd by physics else, the characted will * be animated by the keyframe animation, but will be able to physically * interact with its physic environnement * * @param ragdollEnabled */ protected void setMode(Mode mode) { this.mode = mode; AnimControl animControl = targetModel.getControl(AnimControl.class); animControl.setEnabled(mode == Mode.Kinematic); baseRigidBody.setKinematic(mode == Mode.Kinematic); if (mode != Mode.IK) { TempVars vars = TempVars.get(); for (PhysicsBoneLink link : boneLinks.values()) { link.rigidBody.setKinematic(mode == Mode.Kinematic); if (mode == Mode.Ragdoll) { Quaternion tmpRot1 = vars.quat1; Vector3f position = vars.vect1; //making sure that the ragdoll is at the correct place. matchPhysicObjectToBone(link, position, tmpRot1); } } vars.release(); } if(mode != Mode.IK){ for (Bone bone : skeleton.getRoots()) { RagdollUtils.setUserControl(bone, mode == Mode.Ragdoll); } } } /** * Smoothly blend from Ragdoll mode to Kinematic mode This is useful to * blend ragdoll actual position to a keyframe animation for example * * @param blendTime the blending time between ragdoll to anim. */ public void blendToKinematicMode(float blendTime) { if (mode == Mode.Kinematic) { return; } blendedControl = true; this.blendTime = blendTime; mode = Mode.Kinematic; AnimControl animControl = targetModel.getControl(AnimControl.class); animControl.setEnabled(true); TempVars vars = TempVars.get(); for (PhysicsBoneLink link : boneLinks.values()) { Vector3f p = link.rigidBody.getMotionState().getWorldLocation(); Vector3f position = vars.vect1; targetModel.getWorldTransform().transformInverseVector(p, position); Quaternion q = link.rigidBody.getMotionState().getWorldRotationQuat(); Quaternion q2 = vars.quat1; Quaternion q3 = vars.quat2; q2.set(q).multLocal(link.initalWorldRotation).normalizeLocal(); q3.set(targetModel.getWorldRotation()).inverseLocal().mult(q2, q2); q2.normalizeLocal(); link.startBlendingPos.set(position); link.startBlendingRot.set(q2); link.rigidBody.setKinematic(true); } vars.release(); for (Bone bone : skeleton.getRoots()) { RagdollUtils.setUserControl(bone, false); } blendStart = 0; } /** * Set the control into Kinematic mode In theis mode, the collision shapes * follow the movements of the skeleton, and can interact with physical * environement */ public void setKinematicMode() { if (mode != Mode.Kinematic) { setMode(Mode.Kinematic); } } /** * Sets the control into Ragdoll mode The skeleton is entirely controlled by * physics. */ public void setRagdollMode() { if (mode != Mode.Ragdoll) { setMode(Mode.Ragdoll); } } /** * Sets the control into Inverse Kinematics mode. The affected bones are affected by IK. * physics. */ public void setIKMode() { if (mode != Mode.IK) { setMode(Mode.IK); } } /** * retruns the mode of this control * * @return */ public Mode getMode() { return mode; } /** * add a * * @param listener */ public void addCollisionListener(RagdollCollisionListener listener) { if (listeners == null) { listeners = new ArrayList<RagdollCollisionListener>(); } listeners.add(listener); } public void setRootMass(float rootMass) { this.rootMass = rootMass; } public float getTotalMass() { return totalMass; } public float getWeightThreshold() { return weightThreshold; } public void setWeightThreshold(float weightThreshold) { this.weightThreshold = weightThreshold; } public float getEventDispatchImpulseThreshold() { return eventDispatchImpulseThreshold; } public void setEventDispatchImpulseThreshold(float eventDispatchImpulseThreshold) { this.eventDispatchImpulseThreshold = eventDispatchImpulseThreshold; } /** * Set the CcdMotionThreshold of all the bone's rigidBodies of the ragdoll * * @see PhysicsRigidBody#setCcdMotionThreshold(float) * @param value */ public void setCcdMotionThreshold(float value) { for (PhysicsBoneLink link : boneLinks.values()) { link.rigidBody.setCcdMotionThreshold(value); } } /** * Set the CcdSweptSphereRadius of all the bone's rigidBodies of the ragdoll * * @see PhysicsRigidBody#setCcdSweptSphereRadius(float) * @param value */ public void setCcdSweptSphereRadius(float value) { for (PhysicsBoneLink link : boneLinks.values()) { link.rigidBody.setCcdSweptSphereRadius(value); } } /** * return the rigidBody associated to the given bone * * @param boneName the name of the bone * @return the associated rigidBody. */ public PhysicsRigidBody getBoneRigidBody(String boneName) { PhysicsBoneLink link = boneLinks.get(boneName); if (link != null) { return link.rigidBody; } return null; } /** * For internal use only specific render for the ragdoll(if debugging) * * @param rm * @param vp */ @Override public void render(RenderManager rm, ViewPort vp) { } @Override public Control cloneForSpatial(Spatial spatial) { KinematicRagdollControl control = new KinematicRagdollControl(preset, weightThreshold); control.setMode(mode); control.setRootMass(rootMass); control.setWeightThreshold(weightThreshold); control.setApplyPhysicsLocal(applyLocal); return control; } @Override public Object jmeClone() { KinematicRagdollControl control = new KinematicRagdollControl(preset, weightThreshold); control.setMode(mode); control.setRootMass(rootMass); control.setWeightThreshold(weightThreshold); control.setApplyPhysicsLocal(applyLocal); control.spatial = this.spatial; return control; } public Vector3f setIKTarget(Bone bone, Vector3f worldPos, int chainLength) { Vector3f target = worldPos.subtract(targetModel.getWorldTranslation()); ikTargets.put(bone.getName(), target); ikChainDepth.put(bone.getName(), chainLength); int i = 0; while (i < chainLength+2 && bone.getParent() != null) { if (!bone.hasUserControl()) { bone.setUserControl(true); } bone = bone.getParent(); i++; } // setIKMode(); return target; } public void removeIKTarget(Bone bone) { int depth = ikChainDepth.remove(bone.getName()); int i = 0; while (i < depth+2 && bone.getParent() != null) { if (bone.hasUserControl()) { // matchPhysicObjectToBone(boneLinks.get(bone.getName()), position, tmpRot1); bone.setUserControl(false); } bone = bone.getParent(); i++; } } public void removeAllIKTargets(){ ikTargets.clear(); ikChainDepth.clear(); applyUserControl(); } public void applyUserControl() { for (Bone bone : skeleton.getRoots()) { RagdollUtils.setUserControl(bone, false); } if (ikTargets.isEmpty()) { setKinematicMode(); } else { Iterator iterator = ikTargets.keySet().iterator(); TempVars vars = TempVars.get(); while (iterator.hasNext()) { Bone bone = (Bone) iterator.next(); while (bone.getParent() != null) { Quaternion tmpRot1 = vars.quat1; Vector3f position = vars.vect1; matchPhysicObjectToBone(boneLinks.get(bone.getName()), position, tmpRot1); bone.setUserControl(true); bone = bone.getParent(); } } vars.release(); } } public float getIkRotSpeed() { return ikRotSpeed; } public void setIkRotSpeed(float ikRotSpeed) { this.ikRotSpeed = ikRotSpeed; } public float getIKThreshold() { return IKThreshold; } public void setIKThreshold(float IKThreshold) { this.IKThreshold = IKThreshold; } public float getLimbDampening() { return limbDampening; } public void setLimbDampening(float limbDampening) { this.limbDampening = limbDampening; } public Bone getBone(String name){ return skeleton.getBone(name); } /** * serialize this control * * @param ex * @throws IOException */ @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write(boneList.toArray(new String[boneList.size()]), "boneList", new String[0]); oc.write(boneLinks.values().toArray(new PhysicsBoneLink[boneLinks.size()]), "boneLinks", new PhysicsBoneLink[0]); oc.write(modelPosition, "modelPosition", new Vector3f()); oc.write(modelRotation, "modelRotation", new Quaternion()); oc.write(targetModel, "targetModel", null); oc.write(skeleton, "skeleton", null); // oc.write(preset, "preset", null);//TODO oc.write(initScale, "initScale", null); oc.write(mode, "mode", null); oc.write(blendedControl, "blendedControl", false); oc.write(weightThreshold, "weightThreshold", -1.0f); oc.write(blendStart, "blendStart", 0.0f); oc.write(blendTime, "blendTime", 1.0f); oc.write(eventDispatchImpulseThreshold, "eventDispatchImpulseThreshold", 10); oc.write(rootMass, "rootMass", 15); oc.write(totalMass, "totalMass", 0); oc.write(ikRotSpeed, "rotSpeed", 7f); oc.write(limbDampening, "limbDampening", 0.6f); } /** * de-serialize this control * * @param im * @throws IOException */ @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule ic = im.getCapsule(this); String[] loadedBoneList = ic.readStringArray("boneList", new String[0]); boneList.addAll(Arrays.asList(loadedBoneList)); PhysicsBoneLink[] loadedBoneLinks = (PhysicsBoneLink[]) ic.readSavableArray("boneList", new PhysicsBoneLink[0]); for (PhysicsBoneLink physicsBoneLink : loadedBoneLinks) { boneLinks.put(physicsBoneLink.bone.getName(), physicsBoneLink); } modelPosition.set((Vector3f) ic.readSavable("modelPosition", new Vector3f())); modelRotation.set((Quaternion) ic.readSavable("modelRotation", new Quaternion())); targetModel = (Spatial) ic.readSavable("targetModel", null); skeleton = (Skeleton) ic.readSavable("skeleton", null); // preset //TODO initScale = (Vector3f) ic.readSavable("initScale", null); mode = ic.readEnum("mode", Mode.class, Mode.Kinematic); blendedControl = ic.readBoolean("blendedControl", false); weightThreshold = ic.readFloat("weightThreshold", -1.0f); blendStart = ic.readFloat("blendStart", 0.0f); blendTime = ic.readFloat("blendTime", 1.0f); eventDispatchImpulseThreshold = ic.readFloat("eventDispatchImpulseThreshold", 10); rootMass = ic.readFloat("rootMass", 15); totalMass = ic.readFloat("totalMass", 0); } }