/*
* Copyright (c) 2009-2010 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.asset.AssetManager;
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.JmeExporter;
import com.jme3.export.JmeImporter;
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 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 implements PhysicsControl, PhysicsCollisionListener {
protected static final Logger logger = Logger.getLogger(KinematicRagdollControl.class.getName());
protected Map<String, PhysicsBoneLink> boneLinks = new HashMap<String, PhysicsBoneLink>();
protected Skeleton skeleton;
protected PhysicsSpace space;
protected boolean enabled = true;
protected boolean debug = false;
protected PhysicsRigidBody baseRigidBody;
protected float weightThreshold = -1.0f;
protected Spatial targetModel;
protected Vector3f initScale;
protected Mode mode = Mode.Kinetmatic;
protected boolean blendedControl = false;
protected float blendTime = 1.0f;
protected float blendStart = 0.0f;
protected List<RagdollCollisionListener> listeners;
protected float eventDispatchImpulseThreshold = 10;
protected RagdollPreset preset = new HumanoidRagdollPreset();
protected Set<String> boneList = new TreeSet<String>();
protected Vector3f modelPosition = new Vector3f();
protected Quaternion modelRotation = new Quaternion();
protected float rootMass = 15;
protected float totalMass = 0;
protected boolean added = false;
public static enum Mode {
Kinetmatic,
Ragdoll
}
protected class PhysicsBoneLink {
protected Bone bone;
protected Quaternion initalWorldRotation;
protected SixDofJoint joint;
protected PhysicsRigidBody rigidBody;
protected Quaternion startBlendingRot = new Quaternion();
protected Vector3f startBlendingPos = new Vector3f();
}
/**
* contruct a KinematicRagdollControl
*/
public KinematicRagdollControl() {
}
public KinematicRagdollControl(float weightThreshold) {
this.weightThreshold = weightThreshold;
}
public KinematicRagdollControl(RagdollPreset preset, float weightThreshold) {
this.preset = preset;
this.weightThreshold = weightThreshold;
}
public KinematicRagdollControl(RagdollPreset preset) {
this.preset = preset;
}
public void update(float tpf) {
if (!enabled) {
return;
}
TempVars vars = TempVars.get();
Quaternion tmpRot1 = vars.quat1;
Quaternion tmpRot2 = vars.quat2;
//if the ragdoll has the control of the skeleton, we update each bone with its position in physic world space.
if (mode == mode.Ragdoll && targetModel.getLocalTranslation().equals(modelPosition)) {
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.getWorldBindPosition());
targetModel.getParent().getWorldTransform().transformInverseVector(modelPosition, modelPosition);
modelRotation.set(q).multLocal(tmpRot2.set(link.bone.getWorldBindRotation()).inverseLocal());
//applying transforms to the model
targetModel.setLocalTranslation(modelPosition);
targetModel.setLocalRotation(modelRotation);
//Applying computed transforms to the bone
link.bone.setUserTransformsWorld(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.setUserTransformsWorld(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);
}
}
}
} else {
//the ragdoll does not have the controll, so the keyframed animation updates the physic position of the physic bonces
for (PhysicsBoneLink link : boneLinks.values()) {
Vector3f position = vars.vect1;
//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).interpolate(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.setUserTransformsWorld(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();
}
/**
* 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
*/
private 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.getWorldBindInverseRotation());
targetModel.getWorldRotation().mult(tmpRot1, tmpRot1);
tmpRot1.normalizeLocal();
//updating physic location/rotation of the physic bone
link.rigidBody.setPhysicsLocation(position);
link.rigidBody.setPhysicsRotation(tmpRot1);
}
public Control cloneForSpatial(Spatial spatial) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* rebuild the ragdoll
* this is useful if you applied scale on the ragdoll after it's been initialized
*/
public void reBuild() {
setSpatial(targetModel);
addToPhysicsSpace();
}
public void setSpatial(Spatial model) {
if (model == null) {
removeFromPhysicsSpace();
clearData();
return;
}
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);
model.removeControl(sc);
model.addControl(sc);
//----
removeFromPhysicsSpace();
clearData();
// 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);
logger.log(Level.INFO, "Created physics ragdoll for skeleton {0}", skeleton);
}
/**
* 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);
}
private 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.INFO, "Found root bone in skeleton {0}", skeleton);
baseRigidBody = new PhysicsRigidBody(new BoxCollisionShape(Vector3f.UNIT_XYZ.mult(0.1f)), 1);
baseRigidBody.setKinematic(mode == Mode.Kinetmatic);
boneRecursion(model, childBone, baseRigidBody, 1, pointsMap);
}
}
}
private 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.Kinetmatic);
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;
}
}
private void clearData() {
boneLinks.clear();
baseRigidBody = null;
}
private void addToPhysicsSpace() {
if (space == null) {
return;
}
if (baseRigidBody != null) {
space.add(baseRigidBody);
added = true;
}
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);
}
added = true;
}
}
}
protected void removeFromPhysicsSpace() {
if (space == null) {
return;
}
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);
}
}
}
added = false;
}
/**
* enable or disable the control
* 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
* if enabled is false the ragdoll is removed from physic space.
* @param enabled
*/
public void setEnabled(boolean enabled) {
if (this.enabled == enabled) {
return;
}
this.enabled = enabled;
if (!enabled && space != null) {
removeFromPhysicsSpace();
} else if (enabled && space != null) {
addToPhysicsSpace();
}
}
/**
* returns true if the control is enabled
* @return
*/
public boolean isEnabled() {
return enabled;
}
protected void attachDebugShape(AssetManager manager) {
for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) {
PhysicsBoneLink physicsBoneLink = it.next();
physicsBoneLink.rigidBody.createDebugShape(manager);
}
debug = true;
}
protected void detachDebugShape() {
for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) {
PhysicsBoneLink physicsBoneLink = it.next();
physicsBoneLink.rigidBody.detachDebugShape();
}
debug = false;
}
/**
* For internal use only
* specific render for the ragdoll(if debugging)
* @param rm
* @param vp
*/
public void render(RenderManager rm, ViewPort vp) {
if (enabled && space != null && space.getDebugManager() != null) {
if (!debug) {
attachDebugShape(space.getDebugManager());
}
for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) {
PhysicsBoneLink physicsBoneLink = it.next();
Spatial debugShape = physicsBoneLink.rigidBody.debugShape();
if (debugShape != null) {
debugShape.setLocalTranslation(physicsBoneLink.rigidBody.getMotionState().getWorldLocation());
debugShape.setLocalRotation(physicsBoneLink.rigidBody.getMotionState().getWorldRotationQuat());
debugShape.updateGeometricState();
rm.renderScene(debugShape, vp);
}
}
}
}
/**
* set the physic space to this ragdoll
* @param space
*/
public void setPhysicsSpace(PhysicsSpace space) {
if (space == null) {
removeFromPhysicsSpace();
this.space = space;
} else {
if (this.space == space) {
return;
}
this.space = space;
addToPhysicsSpace();
this.space.addCollisionListener(this);
}
}
/**
* returns the physic space
* @return
*/
public PhysicsSpace getPhysicsSpace() {
return space;
}
/**
* serialize this control
* @param ex
* @throws IOException
*/
public void write(JmeExporter ex) throws IOException {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* de-serialize this control
* @param im
* @throws IOException
*/
public void read(JmeImporter im) throws IOException {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* 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.Kinetmatic);
baseRigidBody.setKinematic(mode == Mode.Kinetmatic);
TempVars vars = TempVars.get();
for (PhysicsBoneLink link : boneLinks.values()) {
link.rigidBody.setKinematic(mode == Mode.Kinetmatic);
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();
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.Kinetmatic) {
return;
}
blendedControl = true;
this.blendTime = blendTime;
mode = Mode.Kinetmatic;
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.Kinetmatic) {
setMode(Mode.Kinetmatic);
}
}
/**
* Sets the control into Ragdoll mode
* The skeleton is entirely controlled by physics.
*/
public void setRagdollMode() {
if (mode != Mode.Ragdoll) {
setMode(Mode.Ragdoll);
}
}
/**
* 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);
}
}
/**
* Set the CcdMotionThreshold of the given bone's rigidBodies of the ragdoll
* @see PhysicsRigidBody#setCcdMotionThreshold(float)
* @param value
* @deprecated use getBoneRigidBody(String BoneName).setCcdMotionThreshold(float) instead
*/
@Deprecated
public void setBoneCcdMotionThreshold(String boneName, float value) {
PhysicsBoneLink link = boneLinks.get(boneName);
if (link != null) {
link.rigidBody.setCcdMotionThreshold(value);
}
}
/**
* Set the CcdSweptSphereRadius of the given bone's rigidBodies of the ragdoll
* @see PhysicsRigidBody#setCcdSweptSphereRadius(float)
* @param value
* @deprecated use getBoneRigidBody(String BoneName).setCcdSweptSphereRadius(float) instead
*/
@Deprecated
public void setBoneCcdSweptSphereRadius(String boneName, float value) {
PhysicsBoneLink link = boneLinks.get(boneName);
if (link != null) {
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;
}
}