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