holopy3/Assets/Plugins/RootMotion/FinalIK/Ragdoll Utility/RagdollUtility.cs
2020-12-10 15:25:12 +01:00

404 lines
12 KiB
C#

using UnityEngine;
using System.Collections;
using RootMotion.FinalIK;
namespace RootMotion.FinalIK {
/// <summary>
/// Ragdoll Utility controls switching characters in and out of ragdoll mode. It also enables you to use IK effects on top of ragdoll simulation.
/// </summary>
[RequireComponent(typeof(Animator))]
public class RagdollUtility : MonoBehaviour {
#region Main Interface
[Tooltip("If you have multiple IK components, then this should be the one that solves last each frame.")]
/// <summary>
/// If you have multiple IK components, then this should be the one that solves last each frame.
/// </summary>
public IK ik;
[Tooltip("How long does it take to blend from ragdoll to animation?")]
/// <summary>
/// How long does it take to blend from ragdoll to animation?
/// </summary>
public float ragdollToAnimationTime = 0.2f;
[Tooltip("If true, IK can be used on top of physical ragdoll simulation.")]
/// <summary>
/// If true, IK can be used on top of physical ragdoll simulation.
/// </summary>
public bool applyIkOnRagdoll;
[Tooltip("How much velocity transfer from animation to ragdoll?")]
/// <summary>
/// How much velocity transfer from animation to ragdoll?
/// </summary>
public float applyVelocity = 1f;
[Tooltip("How much angular velocity to transfer from animation to ragdoll?")]
/// <summary>
/// How much angular velocity to transfer from animation to ragdoll?
/// </summary>
public float applyAngularVelocity = 1f;
/// <summary>
/// Switches to ragdoll.
/// </summary>
public void EnableRagdoll() {
if (isRagdoll) return;
StopAllCoroutines();
enableRagdollFlag = true;
}
/// <summary>
/// Blends back to animation.
/// </summary>
public void DisableRagdoll() {
if (!isRagdoll) return;
StoreLocalState();
StopAllCoroutines();
StartCoroutine(DisableRagdollSmooth());
}
#endregion Main Interface
// The rigidbodies and their associates
public class Rigidbone {
public Rigidbody r;
public Transform t;
public Collider collider;
public Joint joint;
public Rigidbody c;
public bool updateAnchor;
public Vector3 deltaPosition;
public Quaternion deltaRotation;
public float deltaTime;
public Vector3 lastPosition;
public Quaternion lastRotation;
// Constructor
public Rigidbone (Rigidbody r) {
this.r = r;
t = r.transform;
joint = t.GetComponent<Joint>();
collider = t.GetComponent<Collider>();
if (joint != null) {
c = joint.connectedBody;
updateAnchor = c != null;
}
lastPosition = t.position;
lastRotation = t.rotation;
}
// Store position and rotation deltas
public void RecordVelocity() {
deltaPosition = t.position - lastPosition;
lastPosition = t.position;
deltaRotation = RootMotion.QuaTools.FromToRotation(lastRotation, t.rotation);
lastRotation = t.rotation;
deltaTime = Time.deltaTime;
}
// Go to ragdoll
public void WakeUp(float velocityWeight, float angularVelocityWeight) {
// Joint anchors need to be updated when there are animated bones in between ragdoll bones
if (updateAnchor) {
joint.connectedAnchor = t.InverseTransformPoint(c.position);
}
r.isKinematic = false;
// Transfer velocity from animation
if (velocityWeight != 0f) {
r.velocity = (deltaPosition / deltaTime) * velocityWeight;
}
// Transfer angular velocity from animation
if (angularVelocityWeight != 0f) {
float angle = 0f;
Vector3 axis = Vector3.zero;
deltaRotation.ToAngleAxis(out angle, out axis);
angle *= Mathf.Deg2Rad;
angle /= deltaTime;
axis *= angle * angularVelocityWeight;
r.angularVelocity = Vector3.ClampMagnitude(axis, r.maxAngularVelocity);
}
r.WakeUp();
}
}
// All child Transforms of the root.
public class Child {
public Transform t;
public Vector3 localPosition;
public Quaternion localRotation;
// Constructor
public Child(Transform transform) {
t = transform;
localPosition = t.localPosition;
localRotation = t.localRotation;
}
// Force to the last stored local state
public void FixTransform(float weight) {
if (weight <= 0f) return;
if (weight >= 1f) {
t.localPosition = localPosition;
t.localRotation = localRotation;
return;
}
t.localPosition = Vector3.Lerp(t.localPosition, localPosition, weight);
t.localRotation = Quaternion.Lerp(t.localRotation, localRotation, weight);
}
// Remember the local state, that is the local position and rotation of the transform
public void StoreLocalState() {
localPosition = t.localPosition;
localRotation = t.localRotation;
}
}
private Animator animator;
private Rigidbone[] rigidbones = new Rigidbone[0];
private Child[] children = new Child[0];
private bool enableRagdollFlag;
private AnimatorUpdateMode animatorUpdateMode;
private IK[] allIKComponents = new IK[0];
private bool[] fixTransforms = new bool[0];
private float ragdollWeight;
private float ragdollWeightV;
private bool fixedFrame;
private bool[] disabledIKComponents = new bool[0];
// Find all necessary components and initiate
public void Start() {
animator = GetComponent<Animator>();
allIKComponents = (IK[])GetComponentsInChildren<IK>();
disabledIKComponents = new bool[allIKComponents.Length];
fixTransforms = new bool[allIKComponents.Length];
if (ik != null) ik.GetIKSolver().OnPostUpdate += AfterLastIK;
// Gather all the rigidbodies and their associates
Rigidbody[] rigidbodies = (Rigidbody[])GetComponentsInChildren<Rigidbody>();
int firstIndex = rigidbodies[0].gameObject == gameObject? 1: 0;
rigidbones = new Rigidbone[firstIndex == 0? rigidbodies.Length: rigidbodies.Length - 1];
for (int i = 0; i < rigidbones.Length; i++) {
rigidbones[i] = new Rigidbone(rigidbodies[i + firstIndex]);
}
// Find all the child Transforms
Transform[] C = (Transform[])GetComponentsInChildren<Transform>();
children = new Child[C.Length - 1];
for (int i = 0; i < children.Length; i++) {
children[i] = new Child(C[i + 1]);
}
}
// Smoothly blends out of Ragdoll
private IEnumerator DisableRagdollSmooth() {
// ...make all rigidbodies kinematic
for (int i = 0; i < rigidbones.Length; i++) {
rigidbones[i].r.isKinematic = true;
}
// Reset IK components
for (int i = 0; i < allIKComponents.Length; i++) {
allIKComponents[i].fixTransforms = fixTransforms[i];
if (disabledIKComponents[i]) allIKComponents[i].enabled = true;
}
// Animator has not updated yet.
animator.updateMode = animatorUpdateMode;
animator.enabled = true;
// Blend back to animation
while (ragdollWeight > 0f) {
ragdollWeight = Mathf.SmoothDamp(ragdollWeight, 0f, ref ragdollWeightV, ragdollToAnimationTime);
if (ragdollWeight < 0.001f) ragdollWeight = 0f;
yield return null;
}
yield return null;
}
void Update() {
if (!isRagdoll) return;
// Disable IK components if applyIKOnRagdoll has been set to false while in ragdoll.
if (!applyIkOnRagdoll) {
bool disableIK = false;
for (int i = 0; i < allIKComponents.Length; i++) {
if (allIKComponents[i].enabled) {
disableIK = true;
break;
}
}
if (disableIK) {
for (int i = 0; i < allIKComponents.Length; i++) disabledIKComponents[i] = false;
}
for (int i = 0; i < allIKComponents.Length; i++) {
if (allIKComponents[i].enabled) {
allIKComponents[i].enabled = false;
disabledIKComponents[i] = true;
}
}
} else {
// Enable IK components if applyIKOnRagdoll has been set to true while in ragdoll
bool enableIK = false;
for (int i = 0; i < allIKComponents.Length; i++) {
if (disabledIKComponents[i]) {
enableIK = true;
break;
}
}
if (enableIK) {
for (int i = 0; i < allIKComponents.Length; i++) {
if (disabledIKComponents[i]) {
allIKComponents[i].enabled = true;
}
}
for (int i = 0; i < allIKComponents.Length; i++) disabledIKComponents[i] = false;
}
}
}
void FixedUpdate() {
// When in ragdoll, move the bones to where they were after the last physics simulation, so IK won't screw up the physics
if (isRagdoll && applyIkOnRagdoll) FixTransforms(1f);
fixedFrame = true;
}
void LateUpdate() {
// When Mecanim has animated...
if (animator.updateMode != AnimatorUpdateMode.AnimatePhysics || (animator.updateMode == AnimatorUpdateMode.AnimatePhysics && fixedFrame)) {
AfterAnimation();
}
fixedFrame = false;
// No IK so the final pose of the character is the current pose
if (!ikUsed) OnFinalPose();
}
// Called by the last IK component after it has updated
private void AfterLastIK() {
// We should have the final pose of the character
if (ikUsed) OnFinalPose();
}
// When animation has been applied by Mecanim
private void AfterAnimation() {
if (isRagdoll) {
// If is ragdoll, no animation has been applied, but we need to remember the pose after the physics step just the same
StoreLocalState();
} else {
// Blending from ragdoll to animation. When ragdollWeight is zero, nothing happens here
FixTransforms(ragdollWeight);
}
}
// When we have the final pose of the character for this frame
private void OnFinalPose() {
if (!isRagdoll) RecordVelocities();
if (enableRagdollFlag) RagdollEnabler();
}
// Switching to ragdoll
private void RagdollEnabler() {
// Remember the last animated pose
StoreLocalState();
// Disable IK components if necessary
for (int i = 0; i < allIKComponents.Length; i++) disabledIKComponents[i] = false;
if (!applyIkOnRagdoll) {
for (int i = 0; i < allIKComponents.Length; i++) {
if (allIKComponents[i].enabled) {
allIKComponents[i].enabled = false;
disabledIKComponents[i] = true;
}
}
}
// Switch Animator update mode to AnimatePhysics, so IK is updated in the fixed time step
animatorUpdateMode = animator.updateMode;
animator.updateMode = AnimatorUpdateMode.AnimatePhysics;
// Disable the Animator so it won't overwrite physics
animator.enabled = false;
for (int i = 0; i < rigidbones.Length; i++) rigidbones[i].WakeUp(applyVelocity, applyAngularVelocity);
// Remember some variables so we can revert to them when coming back from ragdoll
for (int i = 0; i < fixTransforms.Length; i++) {
fixTransforms[i] = allIKComponents[i].fixTransforms;
allIKComponents[i].fixTransforms = false;
}
ragdollWeight = 1f;
ragdollWeightV = 0f;
enableRagdollFlag = false;
}
// Is the character currently in ragdoll mode?
private bool isRagdoll { get { return !rigidbones[0].r.isKinematic && !animator.enabled; }}
// Store position and rotation deltas for all the rigidbodies
private void RecordVelocities() {
foreach (Rigidbone r in rigidbones) r.RecordVelocity();
}
// Is there any IK components acting on the character?
private bool ikUsed {
get {
if (ik == null) return false;
if (ik.enabled && ik.GetIKSolver().IKPositionWeight > 0) return true;
foreach (IK k in allIKComponents) {
if (k.enabled && k.GetIKSolver().IKPositionWeight > 0) return true;
}
return false;
}
}
// Stored the current pose of the character
private void StoreLocalState() {
foreach (Child c in children) c.StoreLocalState();
}
// Interpolate the character to the last stored pose (see StoreLocalState)
private void FixTransforms(float weight) {
foreach (Child c in children) c.FixTransform(weight);
}
// Cleaning up the delegates
void OnDestroy() {
if (ik != null) {
ik.GetIKSolver().OnPostUpdate -= AfterLastIK;
}
}
}
}