/** * 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); } }
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.Kinematic); boneRecursion(model, childBone, baseRigidBody, 1, pointsMap); } } }
/** * Enable or disable the ragdoll behaviour. if ragdollEnabled is true, the character motion will * only be powerd by physics else, the characted will be animated by the keyframe animation, but * will be able to physically interact with its physic environnement * * @param ragdollEnabled */ protected void setMode(Mode mode) { this.mode = mode; AnimControl animControl = targetModel.getControl(AnimControl.class); animControl.setEnabled(mode == Mode.Kinematic); baseRigidBody.setKinematic(mode == Mode.Kinematic); TempVars vars = TempVars.get(); for (PhysicsBoneLink link : boneLinks.values()) { link.rigidBody.setKinematic(mode == Mode.Kinematic); if (mode == Mode.Ragdoll) { Quaternion tmpRot1 = vars.quat1; Vector3f position = vars.vect1; // making sure that the ragdoll is at the correct place. matchPhysicObjectToBone(link, position, tmpRot1); } } vars.release(); for (Bone bone : skeleton.getRoots()) { RagdollUtils.setUserControl(bone, mode == Mode.Ragdoll); } }
/** * Smoothly blend from Ragdoll mode to Kinematic mode This is useful to blend ragdoll actual * position to a keyframe animation for example * * @param blendTime the blending time between ragdoll to anim. */ public void blendToKinematicMode(float blendTime) { if (mode == Mode.Kinematic) { return; } blendedControl = true; this.blendTime = blendTime; mode = Mode.Kinematic; AnimControl animControl = targetModel.getControl(AnimControl.class); animControl.setEnabled(true); TempVars vars = TempVars.get(); for (PhysicsBoneLink link : boneLinks.values()) { Vector3f p = link.rigidBody.getMotionState().getWorldLocation(); Vector3f position = vars.vect1; targetModel.getWorldTransform().transformInverseVector(p, position); Quaternion q = link.rigidBody.getMotionState().getWorldRotationQuat(); Quaternion q2 = vars.quat1; Quaternion q3 = vars.quat2; q2.set(q).multLocal(link.initalWorldRotation).normalizeLocal(); q3.set(targetModel.getWorldRotation()).inverseLocal().mult(q2, q2); q2.normalizeLocal(); link.startBlendingPos.set(position); link.startBlendingRot.set(q2); link.rigidBody.setKinematic(true); } vars.release(); for (Bone bone : skeleton.getRoots()) { RagdollUtils.setUserControl(bone, false); } blendStart = 0; }
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.Kinematic); totalMass += rootMass / (float) reccount; link.rigidBody = shapeNode; link.initalWorldRotation = bone.getModelSpaceRotation().clone(); if (parent != null) { // get joint position for parent Vector3f posToParent = new Vector3f(); if (bone.getParent() != null) { bone.getModelSpacePosition() .subtract(bone.getParent().getModelSpacePosition(), posToParent) .multLocal(initScale); } SixDofJoint joint = new SixDofJoint(parent, shapeNode, posToParent, new Vector3f(0, 0, 0f), true); preset.setupJointForBone(bone.getName(), joint); link.joint = joint; joint.setCollisionBetweenLinkedBodys(false); } boneLinks.put(bone.getName(), link); shapeNode.setUserObject(link); parentShape = shapeNode; } for (Iterator<Bone> it = bone.getChildren().iterator(); it.hasNext(); ) { Bone childBone = it.next(); boneRecursion(model, childBone, parentShape, reccount + 1, pointsMap); } }
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(); }