| /* |
| * 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; |
| } |
| } |