- RagdollControl is now called KinematicRagdollControl

- better user API (setKinematicMode(), setRagdollMode(),...)
- You can now enable/disable the control anytime, with no side effects
- added more javadoc

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7370 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
rem..om 14 years ago
parent 93d0ed73e3
commit 427163475c
  1. 184
      engine/src/jbullet/com/jme3/bullet/control/KinematicRagdollControl.java
  2. 30
      engine/src/test/jme3test/bullet/TestBoneRagdoll.java

@ -78,6 +78,7 @@ 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>
@ -88,26 +89,25 @@ import java.util.logging.Logger;
* </ul>
*</p>
*<p>
*There are 2 behavior for this control :
*There are 2 modes for this control :
* <ul>
* <li><strong>The kinematic behavior :</strong><br>
* <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 just by enabling the control with setEnabled(true);
* disabling the control removes it from the phyic space
* this mode is enabled by calling setKinematicMode();
* </li>
* <li><strong>The ragdoll behavior :</strong><br>
* To enable this behavior, you need to call setRagdollEnabled(true) method.
* In this mode the charater is entirely controled by physics, so it will fall under the garvity and move if any force is applied to it.
* <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 RagdollControl implements PhysicsControl, PhysicsCollisionListener {
public class KinematicRagdollControl implements PhysicsControl, PhysicsCollisionListener {
protected static final Logger logger = Logger.getLogger(RagdollControl.class.getName());
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;
@ -117,7 +117,7 @@ public class RagdollControl implements PhysicsControl, PhysicsCollisionListener
protected float weightThreshold = 1.0f;
protected Spatial targetModel;
protected Vector3f initScale;
protected boolean control = false;
protected Mode mode = Mode.Kinetmatic;
protected boolean blendedControl = false;
protected float blendTime = 1.0f;
protected float blendStart = 0.0f;
@ -131,19 +131,28 @@ public class RagdollControl implements PhysicsControl, PhysicsCollisionListener
protected float totalMass = 0;
protected boolean added = false;
public RagdollControl() {
public enum Mode {
Kinetmatic,
Ragdoll
}
/**
* contruct a KinematicRagdollControl
*/
public KinematicRagdollControl() {
}
public RagdollControl(float weightThreshold) {
public KinematicRagdollControl(float weightThreshold) {
this.weightThreshold = weightThreshold;
}
public RagdollControl(RagdollPreset preset, float weightThreshold) {
public KinematicRagdollControl(RagdollPreset preset, float weightThreshold) {
this.preset = preset;
this.weightThreshold = weightThreshold;
}
public RagdollControl(RagdollPreset preset) {
public KinematicRagdollControl(RagdollPreset preset) {
this.preset = preset;
}
@ -157,7 +166,7 @@ public class RagdollControl implements PhysicsControl, PhysicsCollisionListener
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 (control) {
if (mode == mode.Ragdoll) {
for (PhysicsBoneLink link : boneLinks.values()) {
Vector3f position = vars.vect1;
@ -205,10 +214,6 @@ public class RagdollControl implements PhysicsControl, PhysicsCollisionListener
//the ragdoll does not have the controll, so the keyframed animation updates the physic position of the physic bonces
for (PhysicsBoneLink link : boneLinks.values()) {
if (!link.rigidBody.isKinematic()) {
link.rigidBody.setKinematic(true);
}
Vector3f position = vars.vect1;
//if blended control this means, keyframed animation is updating the skeleton,
@ -237,18 +242,9 @@ public class RagdollControl implements PhysicsControl, PhysicsCollisionListener
}
}
//setting skeleton transforms to the ragdoll
matchPhysicObjectToBone(link, position, 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);
}
//time control for blending
@ -293,10 +289,40 @@ public class RagdollControl implements PhysicsControl, PhysicsCollisionListener
}
/**
* 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);
// position.set(link.bone.getModelSpaceScale()).multLocal(targetModel.getWorldScale());
// //TODO support scale!
// link.rigidBody.getCollisionShape().setScale(position);
}
public Control cloneForSpatial(Spatial spatial) {
throw new UnsupportedOperationException("Not supported yet.");
}
public void reInit() {
setSpatial(targetModel);
addToPhysicsSpace();
}
public void setSpatial(Spatial model) {
if (model == null) {
removeFromPhysicsSpace();
@ -313,7 +339,7 @@ public class RagdollControl implements PhysicsControl, PhysicsCollisionListener
model.removeFromParent();
model.setLocalTranslation(Vector3f.ZERO);
model.setLocalRotation(Quaternion.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
@ -355,11 +381,10 @@ public class RagdollControl implements PhysicsControl, PhysicsCollisionListener
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);
}
}
}
private void boneRecursion(Spatial model, Bone bone, PhysicsRigidBody parent, int reccount) {
@ -370,6 +395,7 @@ public class RagdollControl implements PhysicsControl, PhysicsCollisionListener
link.bone = bone;
//creating the collision shape from the bone's associated vertices
PhysicsRigidBody shapeNode = new PhysicsRigidBody(makeShape(link, model), rootMass / (float) reccount);
shapeNode.setKinematic(mode == Mode.Kinetmatic);
totalMass += rootMass / (float) reccount;
link.rigidBody = shapeNode;
@ -458,7 +484,9 @@ public class RagdollControl implements PhysicsControl, PhysicsCollisionListener
}
private void addToPhysicsSpace() {
if (space == null) {
return;
}
if (baseRigidBody != null) {
space.add(baseRigidBody);
added = true;
@ -467,17 +495,13 @@ public class RagdollControl implements PhysicsControl, PhysicsCollisionListener
PhysicsBoneLink physicsBoneLink = it.next();
if (physicsBoneLink.rigidBody != null) {
space.add(physicsBoneLink.rigidBody);
added = true;
}
}
for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) {
PhysicsBoneLink physicsBoneLink = it.next();
if (physicsBoneLink.joint != null) {
space.add(physicsBoneLink.joint);
}
added = true;
}
}
}
/**
@ -520,6 +544,7 @@ public class RagdollControl implements PhysicsControl, PhysicsCollisionListener
p[i] = points.get(i);
}
return new HullCollisionShape(p);
}
@ -585,7 +610,9 @@ public class RagdollControl implements PhysicsControl, PhysicsCollisionListener
}
protected void removeFromPhysicsSpace() {
if (space == null) {
return;
}
if (baseRigidBody != null) {
space.remove(baseRigidBody);
}
@ -593,14 +620,11 @@ public class RagdollControl implements PhysicsControl, PhysicsCollisionListener
PhysicsBoneLink physicsBoneLink = it.next();
if (physicsBoneLink.joint != null) {
space.remove(physicsBoneLink.joint);
}
}
for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) {
PhysicsBoneLink physicsBoneLink = it.next();
if (physicsBoneLink.rigidBody != null) {
space.remove(physicsBoneLink.rigidBody);
}
}
}
added = false;
}
@ -723,7 +747,7 @@ public class RagdollControl implements PhysicsControl, PhysicsCollisionListener
PhysicsCollisionObject objA = event.getObjectA();
PhysicsCollisionObject objB = event.getObjectB();
//excluding collisions that do not involve the ragdoll
//excluding collisions that involve 2 parts of the ragdoll
if (event.getNodeA() == null && event.getNodeB() == null) {
return;
}
@ -773,40 +797,71 @@ public class RagdollControl implements PhysicsControl, PhysicsCollisionListener
* but will be able to physically interact with its physic environnement
* @param ragdollEnabled
*/
public void setRagdollEnabled(boolean ragdollEnabled) {
protected void setMode(Mode mode) {
this.mode = mode;
AnimControl animControl = targetModel.getControl(AnimControl.class);
animControl.setEnabled(!ragdollEnabled);
animControl.setEnabled(mode == Mode.Kinetmatic);
this.control = ragdollEnabled;
baseRigidBody.setKinematic(mode == Mode.Kinetmatic);
TempVars vars = TempVars.get();
assert vars.lock();
for (PhysicsBoneLink link : boneLinks.values()) {
link.rigidBody.setKinematic(!ragdollEnabled);
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);
}
}
assert vars.unlock();
for (Bone bone : skeleton.getRoots()) {
setUserControl(bone, ragdollEnabled);
setUserControl(bone, mode == Mode.Ragdoll);
}
}
/**
* 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);
}
}
/**
* Blend motion between the ragdoll actual physic state to the given animation, in the given blendTime
* Note that this disable the ragdoll behaviour of the control
* @param anim the animation name to blend to
* @param channel the channel to use for this animation
* Sets the control into Ragdoll mode
* The skeleton is entirely controlled by physics.
*/
public void setRagdollMode() {
if (mode != Mode.Ragdoll) {
setMode(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 blendRagdollToAnim(String anim, AnimChannel channel, float blendTime) {
public void blendToKinematicMode(float blendTime) {
if (mode == Mode.Kinetmatic) {
return;
}
blendedControl = true;
this.blendTime = blendTime;
control = false;
mode = Mode.Kinetmatic;
AnimControl animControl = targetModel.getControl(AnimControl.class);
animControl.setEnabled(true);
channel.setAnim(anim);
channel.setLoopMode(LoopMode.DontLoop);
TempVars vars = TempVars.get();
assert vars.lock();
//this.control = control;
for (PhysicsBoneLink link : boneLinks.values()) {
Vector3f p = link.rigidBody.getMotionState().getWorldLocation();
@ -842,12 +897,11 @@ public class RagdollControl implements PhysicsControl, PhysicsCollisionListener
}
/**
* returns true if the ragdoll behaviour is enabled
* retruns the mode of this control
* @return
*/
public boolean isRagdollEnabled() {
return control;
public Mode getMode() {
return mode;
}
/**

@ -39,12 +39,13 @@ import com.jme3.animation.LoopMode;
import com.jme3.bullet.BulletAppState;
import com.jme3.app.SimpleApplication;
import com.jme3.asset.TextureKey;
import com.jme3.bounding.BoundingBox;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.PhysicsCollisionEvent;
import com.jme3.bullet.collision.PhysicsCollisionObject;
import com.jme3.bullet.collision.RagdollCollisionListener;
import com.jme3.bullet.collision.shapes.SphereCollisionShape;
import com.jme3.bullet.control.RagdollControl;
import com.jme3.bullet.control.KinematicRagdollControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
@ -74,7 +75,7 @@ public class TestBoneRagdoll extends SimpleApplication implements RagdollCollisi
private BulletAppState bulletAppState;
Material matBullet;
Node model;
RagdollControl ragdoll;
KinematicRagdollControl ragdoll;
float bulletSize = 1f;
Material mat;
Material mat3;
@ -106,7 +107,7 @@ public class TestBoneRagdoll extends SimpleApplication implements RagdollCollisi
setupLight();
model = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");
// model.setLocalTranslation(5, 0, 5);
// model.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X));
//debug view
@ -119,7 +120,7 @@ public class TestBoneRagdoll extends SimpleApplication implements RagdollCollisi
skeletonDebug.setLocalTranslation(model.getLocalTranslation());
//Note: PhysicsRagdollControl is still TODO, constructor will change
ragdoll = new RagdollControl(0.5f);
ragdoll = new KinematicRagdollControl(0.5f);
setupSinbad(ragdoll);
ragdoll.addCollisionListener(this);
model.addControl(ragdoll);
@ -157,12 +158,16 @@ public class TestBoneRagdoll extends SimpleApplication implements RagdollCollisi
float[] angles = new float[3];
model.getLocalRotation().toAngles(angles);
q.fromAngleAxis(angles[1], Vector3f.UNIT_Y);
//q.lookAt(model.getLocalRotation().toRotationMatrix().getColumn(2), Vector3f.UNIT_Y);
model.setLocalRotation(q);
if (angles[0] < 0) {
ragdoll.blendRagdollToAnim("StandUpBack", animChannel, 1);
animChannel.setAnim("StandUpBack");
// ragdoll.setKinematicMode();
ragdoll.blendToKinematicMode(0.5f);
} else {
ragdoll.blendRagdollToAnim("StandUpFront", animChannel, 1);
animChannel.setAnim("StandUpFront");
ragdoll.blendToKinematicMode(0.5f);
// ragdoll.setKinematicMode();
}
}
@ -178,8 +183,10 @@ public class TestBoneRagdoll extends SimpleApplication implements RagdollCollisi
// bulletSize = 0;
}
if (name.equals("stop") && isPressed) {
bulletAppState.setEnabled(!bulletAppState.isEnabled());
// bulletAppState.setEnabled(!bulletAppState.isEnabled());
ragdoll.setEnabled(!ragdoll.isEnabled());
ragdoll.setRagdollMode();
}
if (name.equals("shoot") && !isPressed) {
Geometry bulletg = new Geometry("bullet", bullet);
@ -265,13 +272,11 @@ public class TestBoneRagdoll extends SimpleApplication implements RagdollCollisi
}
}
if (!ragdoll.isRagdollEnabled()) {
ragdoll.setRagdollEnabled(true);
ragdoll.setRagdollMode();
}
}
private void setupSinbad(RagdollControl ragdoll) {
private void setupSinbad(KinematicRagdollControl ragdoll) {
ragdoll.addBoneName("Ulna.L");
ragdoll.addBoneName("Ulna.R");
ragdoll.addBoneName("Chest");
@ -304,6 +309,7 @@ public class TestBoneRagdoll extends SimpleApplication implements RagdollCollisi
@Override
public void simpleUpdate(float tpf) {
System.out.println(((BoundingBox) model.getWorldBound()).getYExtent());
// elTime += tpf;
// if (elTime > 3) {
// elTime = 0;

Loading…
Cancel
Save