954 lines
35 KiB
954 lines
35 KiB
14 years ago
|
/*
|
||
|
* 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.
|
||
|
*/
|
||
14 years ago
|
package com.jme3.bullet.control;
|
||
|
|
||
14 years ago
|
import com.jme3.animation.AnimChannel;
|
||
14 years ago
|
import com.jme3.bullet.control.ragdoll.RagdollPreset;
|
||
|
import com.jme3.bullet.control.ragdoll.HumanoidRagdollPreset;
|
||
14 years ago
|
import com.jme3.animation.AnimControl;
|
||
|
import com.jme3.animation.Bone;
|
||
14 years ago
|
import com.jme3.animation.LoopMode;
|
||
14 years ago
|
import com.jme3.animation.Skeleton;
|
||
14 years ago
|
import com.jme3.animation.SkeletonControl;
|
||
14 years ago
|
import com.jme3.asset.AssetManager;
|
||
|
import com.jme3.bullet.PhysicsSpace;
|
||
14 years ago
|
import com.jme3.bullet.collision.PhysicsCollisionEvent;
|
||
|
import com.jme3.bullet.collision.PhysicsCollisionListener;
|
||
|
import com.jme3.bullet.collision.PhysicsCollisionObject;
|
||
14 years ago
|
import com.jme3.bullet.collision.RagdollCollisionListener;
|
||
14 years ago
|
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
|
||
14 years ago
|
import com.jme3.bullet.collision.shapes.HullCollisionShape;
|
||
14 years ago
|
import com.jme3.bullet.joints.SixDofJoint;
|
||
14 years ago
|
import com.jme3.bullet.objects.PhysicsRigidBody;
|
||
|
import com.jme3.export.JmeExporter;
|
||
|
import com.jme3.export.JmeImporter;
|
||
|
import com.jme3.math.Quaternion;
|
||
14 years ago
|
import com.jme3.math.Transform;
|
||
14 years ago
|
import com.jme3.math.Vector3f;
|
||
|
import com.jme3.renderer.RenderManager;
|
||
|
import com.jme3.renderer.ViewPort;
|
||
14 years ago
|
import com.jme3.scene.Geometry;
|
||
|
import com.jme3.scene.Mesh;
|
||
|
import com.jme3.scene.Node;
|
||
14 years ago
|
import com.jme3.scene.Spatial;
|
||
14 years ago
|
import com.jme3.scene.VertexBuffer.Type;
|
||
14 years ago
|
import com.jme3.scene.control.Control;
|
||
|
import com.jme3.util.TempVars;
|
||
|
import java.io.IOException;
|
||
14 years ago
|
import java.nio.ByteBuffer;
|
||
|
import java.nio.FloatBuffer;
|
||
14 years ago
|
import java.util.ArrayList;
|
||
14 years ago
|
import java.util.HashMap;
|
||
14 years ago
|
import java.util.Iterator;
|
||
14 years ago
|
import java.util.LinkedList;
|
||
14 years ago
|
import java.util.List;
|
||
14 years ago
|
import java.util.Map;
|
||
14 years ago
|
import java.util.logging.Level;
|
||
|
import java.util.logging.Logger;
|
||
|
|
||
14 years ago
|
/**<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>
|
||
14 years ago
|
* Note enabling/disabling the control add/removes it from the physic space<br>
|
||
14 years ago
|
* <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>
|
||
14 years ago
|
*There are 2 modes for this control :
|
||
14 years ago
|
* <ul>
|
||
14 years ago
|
* <li><strong>The kinematic modes :</strong><br>
|
||
14 years ago
|
* 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)
|
||
14 years ago
|
* this mode is enabled by calling setKinematicMode();
|
||
14 years ago
|
* </li>
|
||
14 years ago
|
* <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.
|
||
14 years ago
|
* </li>
|
||
|
* </ul>
|
||
|
*</p>
|
||
|
*
|
||
14 years ago
|
* @author Normen Hansen and Rémy Bouquet (Nehon)
|
||
|
*/
|
||
14 years ago
|
public class KinematicRagdollControl implements PhysicsControl, PhysicsCollisionListener {
|
||
14 years ago
|
|
||
14 years ago
|
protected static final Logger logger = Logger.getLogger(KinematicRagdollControl.class.getName());
|
||
14 years ago
|
protected Map<String, PhysicsBoneLink> boneLinks = new HashMap<String, PhysicsBoneLink>();
|
||
14 years ago
|
protected Skeleton skeleton;
|
||
|
protected PhysicsSpace space;
|
||
|
protected boolean enabled = true;
|
||
14 years ago
|
protected boolean debug = false;
|
||
14 years ago
|
protected PhysicsRigidBody baseRigidBody;
|
||
14 years ago
|
protected float weightThreshold = 1.0f;
|
||
|
protected Spatial targetModel;
|
||
|
protected Vector3f initScale;
|
||
14 years ago
|
protected Mode mode = Mode.Kinetmatic;
|
||
14 years ago
|
protected boolean blendedControl = false;
|
||
|
protected float blendTime = 1.0f;
|
||
|
protected float blendStart = 0.0f;
|
||
14 years ago
|
protected List<RagdollCollisionListener> listeners;
|
||
14 years ago
|
protected float eventDispatchImpulseThreshold = 10;
|
||
|
protected RagdollPreset preset = new HumanoidRagdollPreset();
|
||
14 years ago
|
protected List<String> boneList = new LinkedList<String>();
|
||
14 years ago
|
protected Vector3f modelPosition = new Vector3f();
|
||
14 years ago
|
protected Quaternion modelRotation = new Quaternion();
|
||
14 years ago
|
protected float rootMass = 15;
|
||
14 years ago
|
protected float totalMass = 0;
|
||
14 years ago
|
protected boolean added = false;
|
||
14 years ago
|
|
||
14 years ago
|
public enum Mode {
|
||
|
|
||
|
Kinetmatic,
|
||
|
Ragdoll
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* contruct a KinematicRagdollControl
|
||
|
*/
|
||
|
public KinematicRagdollControl() {
|
||
14 years ago
|
}
|
||
|
|
||
14 years ago
|
public KinematicRagdollControl(float weightThreshold) {
|
||
14 years ago
|
this.weightThreshold = weightThreshold;
|
||
|
}
|
||
|
|
||
14 years ago
|
public KinematicRagdollControl(RagdollPreset preset, float weightThreshold) {
|
||
14 years ago
|
this.preset = preset;
|
||
|
this.weightThreshold = weightThreshold;
|
||
|
}
|
||
|
|
||
14 years ago
|
public KinematicRagdollControl(RagdollPreset preset) {
|
||
14 years ago
|
this.preset = preset;
|
||
|
}
|
||
|
|
||
14 years ago
|
public void update(float tpf) {
|
||
|
if (!enabled) {
|
||
|
return;
|
||
|
}
|
||
14 years ago
|
TempVars vars = TempVars.get();
|
||
|
assert vars.lock();
|
||
14 years ago
|
Quaternion tmpRot1 = vars.quat1;
|
||
|
Quaternion tmpRot2 = vars.quat2;
|
||
14 years ago
|
|
||
14 years ago
|
//if the ragdoll has the control of the skeleton, we update each bone with its position in physic world space.
|
||
14 years ago
|
if (mode == mode.Ragdoll) {
|
||
14 years ago
|
for (PhysicsBoneLink link : boneLinks.values()) {
|
||
14 years ago
|
|
||
14 years ago
|
Vector3f position = vars.vect1;
|
||
|
|
||
14 years ago
|
//retrieving bone position in physic world space
|
||
|
Vector3f p = link.rigidBody.getMotionState().getWorldLocation();
|
||
|
//transforming this position with inverse transforms of the model
|
||
14 years ago
|
targetModel.getWorldTransform().transformInverseVector(p, position);
|
||
14 years ago
|
|
||
14 years ago
|
//retrieving bone rotation in physic world space
|
||
14 years ago
|
Quaternion q = link.rigidBody.getMotionState().getWorldRotationQuat();
|
||
14 years ago
|
|
||
14 years ago
|
//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
|
||
14 years ago
|
if (link.bone.getParent() == null) {
|
||
14 years ago
|
//offsetting the physic's position/rotation by the root bone inverse model space position/rotaion
|
||
14 years ago
|
modelPosition.set(p).subtractLocal(link.bone.getInitialPos());
|
||
14 years ago
|
modelRotation.set(q).multLocal(tmpRot2.set(link.bone.getInitialRot()).inverseLocal());
|
||
|
|
||
|
//applying transforms to the model
|
||
14 years ago
|
targetModel.setLocalTranslation(modelPosition);
|
||
14 years ago
|
targetModel.setLocalRotation(modelRotation);
|
||
14 years ago
|
|
||
|
//Applying computed transforms to the bone
|
||
|
link.bone.setUserTransformsWorld(position, tmpRot1);
|
||
14 years ago
|
|
||
14 years ago
|
} else {
|
||
14 years ago
|
//if boneList is empty, this means that every bone in the ragdoll has a collision shape,
|
||
|
//so we just update the bone position
|
||
14 years ago
|
if (boneList.isEmpty()) {
|
||
14 years ago
|
link.bone.setUserTransformsWorld(position, tmpRot1);
|
||
14 years ago
|
} else {
|
||
14 years ago
|
//boneList is not empty, this means some bones of the skeleton might not be associated with a collision shape.
|
||
|
//So we update them recusively
|
||
|
setTransform(link.bone, position, tmpRot1, false);
|
||
14 years ago
|
}
|
||
14 years ago
|
}
|
||
14 years ago
|
}
|
||
14 years ago
|
} else {
|
||
14 years ago
|
//the ragdoll does not have the controll, so the keyframed animation updates the physic position of the physic bonces
|
||
14 years ago
|
for (PhysicsBoneLink link : boneLinks.values()) {
|
||
14 years ago
|
|
||
|
Vector3f position = vars.vect1;
|
||
14 years ago
|
|
||
14 years ago
|
//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) {
|
||
14 years ago
|
Vector3f position2 = vars.vect2;
|
||
14 years ago
|
//initializing tmp vars with the start position/rotation of the ragdoll
|
||
|
position.set(link.startBlendingPos);
|
||
|
tmpRot1.set(link.startBlendingRot);
|
||
14 years ago
|
|
||
14 years ago
|
//interpolating between ragdoll position/rotation and keyframed position/rotation
|
||
|
tmpRot2.set(tmpRot1).slerp(link.bone.getModelSpaceRotation(), blendStart / blendTime);
|
||
14 years ago
|
position2.set(position).interpolate(link.bone.getModelSpacePosition(), blendStart / blendTime);
|
||
14 years ago
|
tmpRot1.set(tmpRot2);
|
||
14 years ago
|
position.set(position2);
|
||
14 years ago
|
|
||
|
//updating bones transforms
|
||
14 years ago
|
if (boneList.isEmpty()) {
|
||
14 years ago
|
//we ensure we have the control to update the bone
|
||
14 years ago
|
link.bone.setUserControl(true);
|
||
14 years ago
|
link.bone.setUserTransformsWorld(position, tmpRot1);
|
||
|
//we give control back to the key framed animation.
|
||
|
link.bone.setUserControl(false);
|
||
14 years ago
|
} else {
|
||
14 years ago
|
setTransform(link.bone, position, tmpRot1, true);
|
||
14 years ago
|
}
|
||
|
|
||
|
}
|
||
14 years ago
|
//setting skeleton transforms to the ragdoll
|
||
|
matchPhysicObjectToBone(link, position, tmpRot1);
|
||
14 years ago
|
|
||
14 years ago
|
}
|
||
14 years ago
|
|
||
|
//time control for blending
|
||
14 years ago
|
if (blendedControl) {
|
||
|
blendStart += tpf;
|
||
|
if (blendStart > blendTime) {
|
||
|
blendedControl = false;
|
||
|
|
||
|
}
|
||
|
}
|
||
14 years ago
|
}
|
||
14 years ago
|
assert vars.unlock();
|
||
14 years ago
|
|
||
14 years ago
|
}
|
||
|
|
||
14 years ago
|
/**
|
||
|
* Updates a bone position and rotation.
|
||
|
* if the child bones are not in the bone list this means, they are not associated with a physic shape.
|
||
|
* So they have to be updated
|
||
|
* @param bone the bone
|
||
|
* @param pos the position
|
||
|
* @param rot the rotation
|
||
|
*/
|
||
|
private void setTransform(Bone bone, Vector3f pos, Quaternion rot, boolean restoreBoneControl) {
|
||
|
//we ensure that we have the control
|
||
|
if (restoreBoneControl) {
|
||
|
bone.setUserControl(true);
|
||
|
}
|
||
|
//we set te user transforms of the bone
|
||
14 years ago
|
bone.setUserTransformsWorld(pos, rot);
|
||
|
for (Bone childBone : bone.getChildren()) {
|
||
14 years ago
|
//each child bone that is not in the list is updated
|
||
14 years ago
|
if (!boneList.contains(childBone.getName())) {
|
||
14 years ago
|
Transform t = childBone.getCombinedTransform(pos, rot);
|
||
14 years ago
|
setTransform(childBone, t.getTranslation(), t.getRotation(), restoreBoneControl);
|
||
14 years ago
|
}
|
||
|
}
|
||
14 years ago
|
//we give back the control to the keyframed animation
|
||
|
if (restoreBoneControl) {
|
||
|
bone.setUserControl(false);
|
||
|
}
|
||
|
|
||
14 years ago
|
}
|
||
|
|
||
14 years ago
|
/**
|
||
|
* 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);
|
||
|
}
|
||
|
|
||
14 years ago
|
public Control cloneForSpatial(Spatial spatial) {
|
||
|
throw new UnsupportedOperationException("Not supported yet.");
|
||
|
}
|
||
|
|
||
14 years ago
|
public void reInit() {
|
||
|
setSpatial(targetModel);
|
||
|
addToPhysicsSpace();
|
||
|
}
|
||
|
|
||
14 years ago
|
public void setSpatial(Spatial model) {
|
||
14 years ago
|
if (model == null) {
|
||
|
removeFromPhysicsSpace();
|
||
|
clearData();
|
||
|
return;
|
||
|
}
|
||
14 years ago
|
targetModel = model;
|
||
14 years ago
|
Node parent = model.getParent();
|
||
|
|
||
14 years ago
|
|
||
|
Vector3f initPosition = model.getLocalTranslation().clone();
|
||
14 years ago
|
Quaternion initRotation = model.getLocalRotation().clone();
|
||
14 years ago
|
initScale = model.getLocalScale().clone();
|
||
14 years ago
|
|
||
14 years ago
|
model.removeFromParent();
|
||
|
model.setLocalTranslation(Vector3f.ZERO);
|
||
14 years ago
|
model.setLocalRotation(Quaternion.IDENTITY);
|
||
14 years ago
|
model.setLocalScale(1);
|
||
14 years ago
|
//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);
|
||
|
//----
|
||
14 years ago
|
|
||
14 years ago
|
removeFromPhysicsSpace();
|
||
|
clearData();
|
||
|
// put into bind pose and compute bone transforms in model space
|
||
|
// maybe dont reset to ragdoll out of animations?
|
||
|
scanSpatial(model);
|
||
|
|
||
14 years ago
|
|
||
14 years ago
|
if (parent != null) {
|
||
|
parent.attachChild(model);
|
||
|
|
||
|
}
|
||
|
model.setLocalTranslation(initPosition);
|
||
|
model.setLocalRotation(initRotation);
|
||
|
model.setLocalScale(initScale);
|
||
|
|
||
14 years ago
|
logger.log(Level.INFO, "Created physics ragdoll for skeleton {0}", skeleton);
|
||
14 years ago
|
}
|
||
|
|
||
|
public void addBoneName(String name) {
|
||
14 years ago
|
boneList.add(name);
|
||
14 years ago
|
}
|
||
|
|
||
|
private void scanSpatial(Spatial model) {
|
||
14 years ago
|
AnimControl animControl = model.getControl(AnimControl.class);
|
||
14 years ago
|
|
||
14 years ago
|
skeleton = animControl.getSkeleton();
|
||
|
skeleton.resetAndUpdate();
|
||
14 years ago
|
for (int i = 0; i < skeleton.getRoots().length; i++) {
|
||
14 years ago
|
Bone childBone = skeleton.getRoots()[i];
|
||
14 years ago
|
if (childBone.getParent() == null) {
|
||
|
logger.log(Level.INFO, "Found root bone in skeleton {0}", skeleton);
|
||
14 years ago
|
baseRigidBody = new PhysicsRigidBody(new BoxCollisionShape(Vector3f.UNIT_XYZ.mult(0.1f)), 1);
|
||
14 years ago
|
baseRigidBody.setKinematic(mode == Mode.Kinetmatic);
|
||
14 years ago
|
boneRecursion(model, childBone, baseRigidBody, 1);
|
||
14 years ago
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
14 years ago
|
private void boneRecursion(Spatial model, Bone bone, PhysicsRigidBody parent, int reccount) {
|
||
14 years ago
|
PhysicsRigidBody parentShape = parent;
|
||
|
if (boneList.isEmpty() || boneList.contains(bone.getName())) {
|
||
14 years ago
|
|
||
14 years ago
|
PhysicsBoneLink link = new PhysicsBoneLink();
|
||
|
link.bone = bone;
|
||
14 years ago
|
//creating the collision shape from the bone's associated vertices
|
||
|
PhysicsRigidBody shapeNode = new PhysicsRigidBody(makeShape(link, model), rootMass / (float) reccount);
|
||
14 years ago
|
shapeNode.setKinematic(mode == Mode.Kinetmatic);
|
||
14 years ago
|
totalMass += rootMass / (float) reccount;
|
||
14 years ago
|
|
||
14 years ago
|
link.rigidBody = shapeNode;
|
||
14 years ago
|
link.initalWorldRotation = bone.getModelSpaceRotation().clone();
|
||
14 years ago
|
|
||
14 years ago
|
if (parent != null) {
|
||
|
//get joint position for parent
|
||
|
Vector3f posToParent = new Vector3f();
|
||
|
if (bone.getParent() != null) {
|
||
14 years ago
|
bone.getModelSpacePosition().subtract(bone.getParent().getModelSpacePosition(), posToParent).multLocal(initScale);
|
||
14 years ago
|
}
|
||
|
|
||
|
//Joint local position from parent
|
||
|
link.pivotA = posToParent;
|
||
|
//joint local position from current bone
|
||
|
link.pivotB = new Vector3f(0, 0, 0f);
|
||
14 years ago
|
|
||
14 years ago
|
SixDofJoint joint = new SixDofJoint(parent, shapeNode, link.pivotA, link.pivotB, true);
|
||
|
preset.setupJointForBone(bone.getName(), joint);
|
||
14 years ago
|
|
||
14 years ago
|
link.joint = joint;
|
||
|
joint.setCollisionBetweenLinkedBodys(false);
|
||
|
}
|
||
|
boneLinks.put(bone.getName(), link);
|
||
|
shapeNode.setUserObject(link);
|
||
|
parentShape = shapeNode;
|
||
14 years ago
|
}
|
||
|
|
||
|
for (Iterator<Bone> it = bone.getChildren().iterator(); it.hasNext();) {
|
||
|
Bone childBone = it.next();
|
||
14 years ago
|
boneRecursion(model, childBone, parentShape, reccount + 1);
|
||
14 years ago
|
}
|
||
14 years ago
|
|
||
14 years ago
|
|
||
14 years ago
|
}
|
||
|
|
||
14 years ago
|
/**
|
||
|
* 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) {
|
||
|
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 setJointLimit(SixDofJoint joint, float maxX, float minX, float maxY, float minY, float maxZ, float minZ) {
|
||
|
|
||
|
joint.getRotationalLimitMotor(0).setHiLimit(maxX);
|
||
|
joint.getRotationalLimitMotor(0).setLoLimit(minX);
|
||
|
joint.getRotationalLimitMotor(1).setHiLimit(maxY);
|
||
|
joint.getRotationalLimitMotor(1).setLoLimit(minY);
|
||
|
joint.getRotationalLimitMotor(2).setHiLimit(maxZ);
|
||
|
joint.getRotationalLimitMotor(2).setLoLimit(minZ);
|
||
|
}
|
||
|
|
||
14 years ago
|
private void clearData() {
|
||
|
boneLinks.clear();
|
||
|
baseRigidBody = null;
|
||
|
}
|
||
|
|
||
|
private void addToPhysicsSpace() {
|
||
14 years ago
|
if (space == null) {
|
||
|
return;
|
||
|
}
|
||
14 years ago
|
if (baseRigidBody != null) {
|
||
|
space.add(baseRigidBody);
|
||
14 years ago
|
added = true;
|
||
14 years ago
|
}
|
||
14 years ago
|
for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) {
|
||
14 years ago
|
PhysicsBoneLink physicsBoneLink = it.next();
|
||
|
if (physicsBoneLink.rigidBody != null) {
|
||
|
space.add(physicsBoneLink.rigidBody);
|
||
14 years ago
|
if (physicsBoneLink.joint != null) {
|
||
|
space.add(physicsBoneLink.joint);
|
||
|
|
||
|
}
|
||
14 years ago
|
added = true;
|
||
14 years ago
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
14 years ago
|
/**
|
||
|
* Create a hull collision shape from linked vertices to this bone.
|
||
|
*
|
||
|
* @param link
|
||
|
* @param model
|
||
|
* @return
|
||
|
*/
|
||
|
protected HullCollisionShape makeShape(PhysicsBoneLink link, Spatial model) {
|
||
14 years ago
|
Bone bone = link.bone;
|
||
14 years ago
|
List<Integer> boneIndices = null;
|
||
|
if (boneList.isEmpty()) {
|
||
|
boneIndices = new LinkedList<Integer>();
|
||
|
boneIndices.add(skeleton.getBoneIndex(bone));
|
||
|
} else {
|
||
|
boneIndices = getBoneIndices(bone, skeleton);
|
||
|
}
|
||
|
|
||
14 years ago
|
ArrayList<Float> points = new ArrayList<Float>();
|
||
|
if (model instanceof Geometry) {
|
||
|
Geometry g = (Geometry) model;
|
||
14 years ago
|
for (Integer index : boneIndices) {
|
||
14 years ago
|
points.addAll(getPoints(g.getMesh(), index, bone.getModelSpacePosition()));
|
||
14 years ago
|
}
|
||
14 years ago
|
} else if (model instanceof Node) {
|
||
|
Node node = (Node) model;
|
||
|
for (Spatial s : node.getChildren()) {
|
||
|
if (s instanceof Geometry) {
|
||
|
Geometry g = (Geometry) s;
|
||
14 years ago
|
for (Integer index : boneIndices) {
|
||
14 years ago
|
points.addAll(getPoints(g.getMesh(), index, bone.getModelSpacePosition()));
|
||
14 years ago
|
}
|
||
|
|
||
14 years ago
|
}
|
||
|
}
|
||
|
}
|
||
|
float[] p = new float[points.size()];
|
||
|
for (int i = 0; i < points.size(); i++) {
|
||
|
p[i] = points.get(i);
|
||
|
}
|
||
|
|
||
14 years ago
|
|
||
14 years ago
|
return new HullCollisionShape(p);
|
||
|
}
|
||
|
|
||
14 years ago
|
//retruns the list of bone indices of the given bone and its child(if they are not in the boneList)
|
||
|
protected List<Integer> getBoneIndices(Bone bone, Skeleton skeleton) {
|
||
14 years ago
|
List<Integer> list = new LinkedList<Integer>();
|
||
|
list.add(skeleton.getBoneIndex(bone));
|
||
|
for (Bone chilBone : bone.getChildren()) {
|
||
|
if (!boneList.contains(chilBone.getName())) {
|
||
|
list.addAll(getBoneIndices(chilBone, skeleton));
|
||
|
}
|
||
|
}
|
||
|
return list;
|
||
|
}
|
||
|
|
||
14 years ago
|
/**
|
||
|
* returns a list of points for the given bone
|
||
|
* @param mesh
|
||
|
* @param boneIndex
|
||
|
* @param offset
|
||
|
* @param link
|
||
|
* @return
|
||
|
*/
|
||
|
private List<Float> getPoints(Mesh mesh, int boneIndex, Vector3f offset) {
|
||
14 years ago
|
|
||
|
FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);
|
||
|
ByteBuffer boneIndices = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
|
||
|
FloatBuffer boneWeight = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
|
||
|
|
||
|
vertices.rewind();
|
||
|
boneIndices.rewind();
|
||
|
boneWeight.rewind();
|
||
|
|
||
|
ArrayList<Float> results = new ArrayList<Float>();
|
||
|
|
||
|
int vertexComponents = mesh.getVertexCount() * 3;
|
||
14 years ago
|
|
||
14 years ago
|
for (int i = 0; i < vertexComponents; i += 3) {
|
||
|
int k;
|
||
|
boolean add = false;
|
||
|
int start = i / 3 * 4;
|
||
|
for (k = start; k < start + 4; k++) {
|
||
|
if (boneIndices.get(k) == boneIndex && boneWeight.get(k) >= weightThreshold) {
|
||
|
add = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (add) {
|
||
14 years ago
|
|
||
14 years ago
|
Vector3f pos = new Vector3f();
|
||
|
pos.x = vertices.get(i);
|
||
|
pos.y = vertices.get(i + 1);
|
||
|
pos.z = vertices.get(i + 2);
|
||
14 years ago
|
pos.subtractLocal(offset).multLocal(initScale);
|
||
14 years ago
|
results.add(pos.x);
|
||
|
results.add(pos.y);
|
||
|
results.add(pos.z);
|
||
14 years ago
|
|
||
14 years ago
|
}
|
||
|
}
|
||
14 years ago
|
|
||
14 years ago
|
return results;
|
||
|
}
|
||
|
|
||
14 years ago
|
protected void removeFromPhysicsSpace() {
|
||
14 years ago
|
if (space == null) {
|
||
|
return;
|
||
|
}
|
||
14 years ago
|
if (baseRigidBody != null) {
|
||
|
space.remove(baseRigidBody);
|
||
|
}
|
||
14 years ago
|
for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) {
|
||
14 years ago
|
PhysicsBoneLink physicsBoneLink = it.next();
|
||
|
if (physicsBoneLink.joint != null) {
|
||
|
space.remove(physicsBoneLink.joint);
|
||
14 years ago
|
if (physicsBoneLink.rigidBody != null) {
|
||
|
space.remove(physicsBoneLink.rigidBody);
|
||
|
}
|
||
14 years ago
|
}
|
||
|
}
|
||
14 years ago
|
added = false;
|
||
14 years ago
|
}
|
||
|
|
||
14 years ago
|
/**
|
||
|
* enable or disable the control
|
||
14 years ago
|
* 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
|
||
14 years ago
|
* if enabled is false the ragdoll is removed from physic space.
|
||
|
* @param enabled
|
||
|
*/
|
||
14 years ago
|
public void setEnabled(boolean enabled) {
|
||
14 years ago
|
if (this.enabled == enabled) {
|
||
|
return;
|
||
|
}
|
||
14 years ago
|
this.enabled = enabled;
|
||
14 years ago
|
if (!enabled && space != null) {
|
||
14 years ago
|
removeFromPhysicsSpace();
|
||
14 years ago
|
} else if (enabled && space != null) {
|
||
14 years ago
|
addToPhysicsSpace();
|
||
|
}
|
||
|
}
|
||
|
|
||
14 years ago
|
/**
|
||
|
* returns true if the control is enabled
|
||
|
* @return
|
||
|
*/
|
||
14 years ago
|
public boolean isEnabled() {
|
||
|
return enabled;
|
||
|
}
|
||
|
|
||
14 years ago
|
protected void attachDebugShape(AssetManager manager) {
|
||
14 years ago
|
for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) {
|
||
14 years ago
|
PhysicsBoneLink physicsBoneLink = it.next();
|
||
14 years ago
|
physicsBoneLink.rigidBody.createDebugShape(manager);
|
||
14 years ago
|
}
|
||
|
debug = true;
|
||
|
}
|
||
|
|
||
14 years ago
|
protected void detachDebugShape() {
|
||
14 years ago
|
for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) {
|
||
14 years ago
|
PhysicsBoneLink physicsBoneLink = it.next();
|
||
|
physicsBoneLink.rigidBody.detachDebugShape();
|
||
|
}
|
||
|
debug = false;
|
||
|
}
|
||
|
|
||
14 years ago
|
/**
|
||
|
* For internal use only
|
||
|
* specific render for the ragdoll(if debugging)
|
||
|
* @param rm
|
||
|
* @param vp
|
||
|
*/
|
||
14 years ago
|
public void render(RenderManager rm, ViewPort vp) {
|
||
|
if (enabled && space != null && space.getDebugManager() != null) {
|
||
|
if (!debug) {
|
||
|
attachDebugShape(space.getDebugManager());
|
||
|
}
|
||
14 years ago
|
for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) {
|
||
14 years ago
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
14 years ago
|
/**
|
||
|
* set the physic space to this ragdoll
|
||
|
* @param space
|
||
|
*/
|
||
14 years ago
|
public void setPhysicsSpace(PhysicsSpace space) {
|
||
|
if (space == null) {
|
||
|
removeFromPhysicsSpace();
|
||
|
this.space = space;
|
||
|
} else {
|
||
|
if (this.space == space) {
|
||
|
return;
|
||
|
}
|
||
|
this.space = space;
|
||
|
addToPhysicsSpace();
|
||
|
}
|
||
14 years ago
|
this.space.addCollisionListener(this);
|
||
14 years ago
|
}
|
||
|
|
||
14 years ago
|
/**
|
||
|
* returns the physic space
|
||
|
* @return
|
||
|
*/
|
||
14 years ago
|
public PhysicsSpace getPhysicsSpace() {
|
||
|
return space;
|
||
|
}
|
||
|
|
||
14 years ago
|
/**
|
||
|
* serialize this control
|
||
|
* @param ex
|
||
|
* @throws IOException
|
||
|
*/
|
||
14 years ago
|
public void write(JmeExporter ex) throws IOException {
|
||
|
throw new UnsupportedOperationException("Not supported yet.");
|
||
|
}
|
||
|
|
||
14 years ago
|
/**
|
||
|
* de-serialize this control
|
||
|
* @param im
|
||
|
* @throws IOException
|
||
|
*/
|
||
14 years ago
|
public void read(JmeImporter im) throws IOException {
|
||
|
throw new UnsupportedOperationException("Not supported yet.");
|
||
|
}
|
||
|
|
||
14 years ago
|
/**
|
||
|
* For internal use only
|
||
|
* callback for collisionevent
|
||
|
* @param event
|
||
|
*/
|
||
14 years ago
|
public void collision(PhysicsCollisionEvent event) {
|
||
|
PhysicsCollisionObject objA = event.getObjectA();
|
||
|
PhysicsCollisionObject objB = event.getObjectB();
|
||
14 years ago
|
|
||
14 years ago
|
//excluding collisions that involve 2 parts of the ragdoll
|
||
14 years ago
|
if (event.getNodeA() == null && event.getNodeB() == null) {
|
||
14 years ago
|
return;
|
||
|
}
|
||
14 years ago
|
|
||
14 years ago
|
//discarding low impulse collision
|
||
|
if (event.getAppliedImpulse() < eventDispatchImpulseThreshold) {
|
||
14 years ago
|
return;
|
||
|
}
|
||
|
|
||
|
boolean hit = false;
|
||
14 years ago
|
Bone hitBone = null;
|
||
|
PhysicsCollisionObject hitObject = null;
|
||
14 years ago
|
|
||
14 years ago
|
//Computing which bone has been hit
|
||
14 years ago
|
if (objA.getUserObject() instanceof PhysicsBoneLink) {
|
||
|
PhysicsBoneLink link = (PhysicsBoneLink) objA.getUserObject();
|
||
|
if (link != null) {
|
||
|
hit = true;
|
||
|
hitBone = link.bone;
|
||
|
hitObject = objB;
|
||
14 years ago
|
}
|
||
|
}
|
||
14 years ago
|
|
||
|
if (objB.getUserObject() instanceof PhysicsBoneLink) {
|
||
|
PhysicsBoneLink link = (PhysicsBoneLink) objB.getUserObject();
|
||
|
if (link != null) {
|
||
|
hit = true;
|
||
|
hitBone = link.bone;
|
||
|
hitObject = objA;
|
||
|
|
||
14 years ago
|
}
|
||
14 years ago
|
}
|
||
14 years ago
|
|
||
14 years ago
|
//dispatching the event if the ragdoll has been hit
|
||
|
if (hit) {
|
||
14 years ago
|
for (RagdollCollisionListener listener : listeners) {
|
||
14 years ago
|
listener.collide(hitBone, hitObject, event);
|
||
14 years ago
|
}
|
||
14 years ago
|
}
|
||
|
|
||
|
}
|
||
|
|
||
14 years ago
|
/**
|
||
|
* 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
|
||
|
*/
|
||
14 years ago
|
protected void setMode(Mode mode) {
|
||
|
this.mode = mode;
|
||
14 years ago
|
AnimControl animControl = targetModel.getControl(AnimControl.class);
|
||
14 years ago
|
animControl.setEnabled(mode == Mode.Kinetmatic);
|
||
14 years ago
|
|
||
14 years ago
|
baseRigidBody.setKinematic(mode == Mode.Kinetmatic);
|
||
|
TempVars vars = TempVars.get();
|
||
|
assert vars.lock();
|
||
14 years ago
|
for (PhysicsBoneLink link : boneLinks.values()) {
|
||
14 years ago
|
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);
|
||
|
}
|
||
|
|
||
14 years ago
|
}
|
||
14 years ago
|
assert vars.unlock();
|
||
14 years ago
|
|
||
14 years ago
|
for (Bone bone : skeleton.getRoots()) {
|
||
14 years ago
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the control into Ragdoll mode
|
||
|
* The skeleton is entirely controlled by physics.
|
||
|
*/
|
||
|
public void setRagdollMode() {
|
||
|
if (mode != Mode.Ragdoll) {
|
||
|
setMode(Mode.Ragdoll);
|
||
14 years ago
|
}
|
||
|
}
|
||
|
|
||
14 years ago
|
/**
|
||
14 years ago
|
* Smoothly blend from Ragdoll mode to Kinematic mode
|
||
|
* This is useful to blend ragdoll actual position to a keyframe animation for example
|
||
14 years ago
|
* @param blendTime the blending time between ragdoll to anim.
|
||
|
*/
|
||
14 years ago
|
public void blendToKinematicMode(float blendTime) {
|
||
|
if (mode == Mode.Kinetmatic) {
|
||
|
return;
|
||
|
}
|
||
14 years ago
|
blendedControl = true;
|
||
14 years ago
|
this.blendTime = blendTime;
|
||
14 years ago
|
mode = Mode.Kinetmatic;
|
||
14 years ago
|
AnimControl animControl = targetModel.getControl(AnimControl.class);
|
||
|
animControl.setEnabled(true);
|
||
14 years ago
|
|
||
|
|
||
14 years ago
|
|
||
|
TempVars vars = TempVars.get();
|
||
|
assert vars.lock();
|
||
|
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;
|
||
|
|
||
14 years ago
|
q2.set(q).multLocal(link.initalWorldRotation).normalizeLocal();
|
||
14 years ago
|
q3.set(targetModel.getWorldRotation()).inverseLocal().mult(q2, q2);
|
||
14 years ago
|
q2.normalizeLocal();
|
||
14 years ago
|
link.startBlendingPos.set(position);
|
||
|
link.startBlendingRot.set(q2);
|
||
|
link.rigidBody.setKinematic(true);
|
||
|
}
|
||
|
assert vars.unlock();
|
||
|
|
||
|
for (Bone bone : skeleton.getRoots()) {
|
||
|
setUserControl(bone, false);
|
||
|
}
|
||
|
|
||
|
blendStart = 0;
|
||
|
}
|
||
|
|
||
14 years ago
|
private void setUserControl(Bone bone, boolean bool) {
|
||
|
bone.setUserControl(bool);
|
||
|
for (Bone child : bone.getChildren()) {
|
||
|
setUserControl(child, bool);
|
||
|
}
|
||
14 years ago
|
}
|
||
|
|
||
14 years ago
|
/**
|
||
14 years ago
|
* retruns the mode of this control
|
||
14 years ago
|
* @return
|
||
|
*/
|
||
14 years ago
|
public Mode getMode() {
|
||
|
return mode;
|
||
14 years ago
|
}
|
||
14 years ago
|
|
||
14 years ago
|
/**
|
||
|
* add a
|
||
|
* @param listener
|
||
|
*/
|
||
14 years ago
|
public void addCollisionListener(RagdollCollisionListener listener) {
|
||
|
if (listeners == null) {
|
||
|
listeners = new ArrayList<RagdollCollisionListener>();
|
||
|
}
|
||
|
listeners.add(listener);
|
||
|
}
|
||
|
|
||
14 years ago
|
protected static class PhysicsBoneLink {
|
||
14 years ago
|
|
||
14 years ago
|
Bone bone;
|
||
|
Quaternion initalWorldRotation;
|
||
14 years ago
|
SixDofJoint joint;
|
||
14 years ago
|
PhysicsRigidBody rigidBody;
|
||
|
Vector3f pivotA;
|
||
|
Vector3f pivotB;
|
||
14 years ago
|
Quaternion startBlendingRot = new Quaternion();
|
||
|
Vector3f startBlendingPos = new Vector3f();
|
||
14 years ago
|
}
|
||
|
|
||
|
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;
|
||
14 years ago
|
}
|
||
|
}
|