You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
CrowdControl/Assets/BzKovSoft/RagdollHelper/Editor/Ragdoller.cs

515 lines
17 KiB
C#

1 month ago
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace BzKovSoft.RagdollHelper.Editor
{
/// <summary>
/// Class responsible for regdoll and unregdoll character
/// </summary>
sealed class Ragdoller
{
const string _colliderNodeSufix = "_ColliderRotator";
readonly bool _readyToGenerate;
readonly Vector3 _playerDirection;
readonly Transform _rootNode;
readonly RagdollPartBox _pelvis;
readonly RagdollPartCapsule _leftHip;
readonly RagdollPartCapsule _leftKnee;
readonly RagdollPartCapsule _rightHip;
readonly RagdollPartCapsule _rightKnee;
readonly RagdollPartCapsule _leftArm;
readonly RagdollPartCapsule _leftElbow;
readonly RagdollPartCapsule _rightArm;
readonly RagdollPartCapsule _rightElbow;
readonly RagdollPartBox _chest;
readonly RagdollPartSphere _head;
readonly RagdollPartBox _leftFoot;
readonly RagdollPartBox _rightFoot;
readonly RagdollPartBox _leftHand;
readonly RagdollPartBox _rightHand;
public Ragdoller(Transform player, Vector3 playerDirection)
{
_playerDirection = playerDirection;
_readyToGenerate = false;
// find Animator
Animator animator = FindAnimator(player);
if (animator == null)
return;
_rootNode = animator.transform;
// specify all parts of ragdoll
_pelvis = new RagdollPartBox(animator.GetBoneTransform(HumanBodyBones.Hips));
_leftHip = new RagdollPartCapsule(animator.GetBoneTransform(HumanBodyBones.LeftUpperLeg));
_leftKnee = new RagdollPartCapsule(animator.GetBoneTransform(HumanBodyBones.LeftLowerLeg));
_rightHip = new RagdollPartCapsule(animator.GetBoneTransform(HumanBodyBones.RightUpperLeg));
_rightKnee = new RagdollPartCapsule(animator.GetBoneTransform(HumanBodyBones.RightLowerLeg));
_leftArm = new RagdollPartCapsule(animator.GetBoneTransform(HumanBodyBones.LeftUpperArm));
_leftElbow = new RagdollPartCapsule(animator.GetBoneTransform(HumanBodyBones.LeftLowerArm));
_rightArm = new RagdollPartCapsule(animator.GetBoneTransform(HumanBodyBones.RightUpperArm));
_rightElbow = new RagdollPartCapsule(animator.GetBoneTransform(HumanBodyBones.RightLowerArm));
_chest = new RagdollPartBox(animator.GetBoneTransform(HumanBodyBones.Chest));
_head = new RagdollPartSphere(animator.GetBoneTransform(HumanBodyBones.Head));
_leftFoot = new RagdollPartBox(animator.GetBoneTransform(HumanBodyBones.LeftFoot));
_rightFoot = new RagdollPartBox(animator.GetBoneTransform(HumanBodyBones.RightFoot));
_leftHand = new RagdollPartBox(animator.GetBoneTransform(HumanBodyBones.LeftHand));
_rightHand = new RagdollPartBox(animator.GetBoneTransform(HumanBodyBones.RightHand));
if (_chest.transform == null)
_chest = new RagdollPartBox(animator.GetBoneTransform(HumanBodyBones.Spine));
if (!CheckFields())
{
Debug.LogError("Not all nodes was found!");
return;
}
_readyToGenerate = true;
}
/// <summary>
/// Finds animator component in "player" and in parents till it find Animator component. Otherwise returns null
/// </summary>
static Animator FindAnimator(Transform player)
{
Animator animator;
do
{
animator = player.GetComponent<Animator>();
if (animator != null && animator.enabled)
break;
player = player.parent;
}
while (player != null);
if (animator == null | player == null)
{
Debug.LogError("An Animator must be attached to find bones!");
return null;
}
if (!animator.isHuman)
{
Debug.LogError("To auto detect bones, there are has to be humanoid Animator!");
return null;
}
return animator;
}
/// <summary>
/// Some checks before Applying ragdoll
/// </summary>
bool CheckFields()
{
if (_rootNode == null |
_pelvis == null |
_leftHip == null |
_leftKnee == null |
_rightHip == null |
_rightKnee == null |
_leftArm == null |
_leftElbow == null |
_rightArm == null |
_rightElbow == null |
_chest == null |
_head == null)
return false;
return true;
}
/// <summary>
/// Create all ragdoll's components and set their proterties
/// </summary>
public void ApplyRagdoll(float totalMass, RagdollProperties ragdollProperties)
{
if (!_readyToGenerate)
{
Debug.LogError("Initialization failed. Reinstance object!");
return;
}
var weight = new WeightCalculator(totalMass, ragdollProperties.createTips);
bool alreadyRagdolled = _pelvis.transform.gameObject.GetComponent<Rigidbody>() != null;
AddComponentesTo(_pelvis, ragdollProperties, weight.Pelvis, false);
AddComponentesTo(_leftHip, ragdollProperties, weight.Hip, true);
AddComponentesTo(_leftKnee, ragdollProperties, weight.Knee, true);
AddComponentesTo(_rightHip, ragdollProperties, weight.Hip, true);
AddComponentesTo(_rightKnee, ragdollProperties, weight.Knee, true);
AddComponentesTo(_leftArm, ragdollProperties, weight.Arm, true);
AddComponentesTo(_leftElbow, ragdollProperties, weight.Elbow, true);
AddComponentesTo(_rightArm, ragdollProperties, weight.Arm, true);
AddComponentesTo(_rightElbow, ragdollProperties, weight.Elbow, true);
AddComponentesTo(_chest, ragdollProperties, weight.Chest, true);
AddComponentesTo(_head, ragdollProperties, weight.Head, true);
if (ragdollProperties.createTips)
{
AddComponentesTo(_leftFoot, ragdollProperties, weight.Foot, true);
AddComponentesTo(_rightFoot, ragdollProperties, weight.Foot, true);
AddComponentesTo(_leftHand, ragdollProperties, weight.Hand, true);
AddComponentesTo(_rightHand, ragdollProperties, weight.Hand, true);
}
if (alreadyRagdolled)
return;
// Pelvis
Vector3 pelvisSize = new Vector3(0.32f, 0.31f, 0.3f);
Vector3 pelvisCenter = new Vector3(00f, 0.06f, -0.01f);
_pelvis.collider.size = Abs(_pelvis.transform.InverseTransformVector(pelvisSize));
_pelvis.collider.center = _pelvis.transform.InverseTransformVector(pelvisCenter);
ApplySide(true, ragdollProperties.createTips);
ApplySide(false, ragdollProperties.createTips);
// Chest collider
Vector3 chestSize = new Vector3(0.34f, 0.34f, 0.28f);
float y = (pelvisSize.y + chestSize.y) / 2f + pelvisCenter.y;
y -= _chest.transform.position.y - _pelvis.transform.position.y;
_chest.collider.size = Abs(_chest.transform.InverseTransformVector(chestSize));
_chest.collider.center = _chest.transform.InverseTransformVector(new Vector3(0f, y, -0.03f));
// Chest joint
var chestJoint = _chest.joint;
ConfigureJointParams(_chest, _pelvis.rigidbody, _rootNode.right, _rootNode.forward);
ConfigureJointLimits(chestJoint, -45f, 20f, 20f, 20f);
// head
_head.collider.radius = 0.1f;
_head.collider.center = _head.transform.InverseTransformVector(new Vector3(0f, 0.09f, 0.03f));
var headJoint = _head.joint;
ConfigureJointParams(_head, _chest.rigidbody, _rootNode.right, _rootNode.forward);
ConfigureJointLimits(headJoint, -45f, 20f, 20f, 20f);
}
private Vector3 Abs(Vector3 v)
{
return new Vector3(
Mathf.Abs(v.x),
Mathf.Abs(v.y),
Mathf.Abs(v.z)
);
}
static void ConfigureJointParams(RagdollPartBase part, Rigidbody anchor, Vector3 axis, Vector3 swingAxis)
{
part.joint.connectedBody = anchor;
part.joint.axis = part.transform.InverseTransformDirection(axis);
part.joint.swingAxis = part.transform.InverseTransformDirection(swingAxis);
}
static void ConfigureJointLimits(CharacterJoint joint, float lowTwist, float highTwist, float swing1, float swing2)
{
if (lowTwist > highTwist)
throw new ArgumentException("wrong limitation: lowTwist > highTwist");
var twistLimitSpring = joint.twistLimitSpring;
joint.twistLimitSpring = twistLimitSpring;
var swingLimitSpring = joint.swingLimitSpring;
joint.swingLimitSpring = swingLimitSpring;
// configure limits
var lowTwistLimit = joint.lowTwistLimit;
lowTwistLimit.limit = lowTwist;
joint.lowTwistLimit = lowTwistLimit;
var highTwistLimit = joint.highTwistLimit;
highTwistLimit.limit = highTwist;
joint.highTwistLimit = highTwistLimit;
var swing1Limit = joint.swing1Limit;
swing1Limit.limit = swing1;
joint.swing1Limit = swing1Limit;
var swing2Limit = joint.swing2Limit;
swing2Limit.limit = swing2;
joint.swing2Limit = swing2Limit;
}
/// <summary>
/// Configure one hand and one leg
/// </summary>
/// <param name="leftSide">If true, configuration apply to left hand and left leg, otherwise right hand and right leg</param>
void ApplySide(bool leftSide, bool createTips)
{
RagdollPartCapsule hip = (leftSide ? _leftHip : _rightHip);
RagdollPartCapsule knee = (leftSide ? _leftKnee : _rightKnee);
RagdollPartBox foot = (leftSide ? _leftFoot : _rightFoot);
RagdollPartCapsule arm = (leftSide ? _leftArm : _rightArm);
RagdollPartCapsule elbow = (leftSide ? _leftElbow : _rightElbow);
RagdollPartBox hand = (leftSide ? _leftHand : _rightHand);
ConfigureRagdollForLimb(hip, knee, foot, createTips);
ConfigureLegsJoints(hip, knee, foot, createTips);
ConfigureRagdollForLimb(arm, elbow, hand, createTips);
ConfigureHandJoints(arm, elbow, hand, leftSide, createTips);
}
/// <summary>
/// Configer one of 4 body parts: right leg, left leg, right hand or left hand
/// </summary>
static void ConfigureRagdollForLimb(RagdollPartCapsule limbUpper, RagdollPartCapsule limbLower, RagdollPartBox tip, bool createTips)
{
float totalLength = limbUpper.transform.InverseTransformPoint(tip.transform.position).magnitude;
// limbUpper
CapsuleCollider upperCapsule = limbUpper.collider;
var boneEndPos = limbUpper.transform.InverseTransformPoint(limbLower.transform.position);
upperCapsule.direction = GetXyzDirection(limbLower.transform.localPosition);
upperCapsule.radius = totalLength * 0.12f;
upperCapsule.height = boneEndPos.magnitude;
upperCapsule.center = Vector3.Scale(boneEndPos, Vector3.one * 0.5f);
// limbLower
CapsuleCollider endCapsule = limbLower.collider;
boneEndPos = limbLower.transform.InverseTransformPoint(tip.transform.position);
endCapsule.direction = GetXyzDirection(boneEndPos);
endCapsule.radius = totalLength * 0.12f;
endCapsule.height = boneEndPos.magnitude;
endCapsule.center = Vector3.Scale(boneEndPos, Vector3.one * 0.5f);
// tip
if (createTips)
{
boneEndPos = GetLongestTransform(tip.transform).position;
boneEndPos = tip.transform.InverseTransformPoint(boneEndPos);
Vector3 tipDir = GetXyzDirectionV(boneEndPos);
Vector3 tipSides = (tipDir - Vector3.one) * -1;
Vector3 boxSize = tipDir * boneEndPos.magnitude * 1.3f + tipSides * totalLength * 0.2f;
BoxCollider tipBox = tip.collider;
tipBox.size = boxSize;
float halfTipLength = boneEndPos.magnitude / 2f;
tipBox.center = Vector3.Scale(boneEndPos.normalized, Vector3.one * halfTipLength);
}
}
private static Transform GetLongestTransform(Transform limb)
{
float longestF = -1;
Transform longestT = null;
// find the farest object that attached to 'limb'
foreach (Transform t in limb.GetComponentsInChildren<Transform>())
{
float length = (limb.position - t.position).sqrMagnitude;
if (length > longestF)
{
longestF = length;
longestT = t;
}
}
return longestT;
}
static Vector3 GetXyzDirectionV(Vector3 node)
{
var d = GetXyzDirection(node);
switch (d)
{
case 0: return Vector3.right;
case 1: return Vector3.up;
case 2: return Vector3.forward;
}
throw new InvalidOperationException();
}
/// <summary>
/// Get the most appropriate direction in terms of PhysX (0,1,2 directions)
/// </summary>
static int GetXyzDirection(Vector3 node)
{
float x = Mathf.Abs(node.x);
float y = Mathf.Abs(node.y);
float z = Mathf.Abs(node.z);
if (x > y & x > z) // x is the bigest
return 0;
if (y > x & y > z) // y is the bigest
return 1;
// z is the bigest
return 2;
}
void ConfigureHandJoints(RagdollPartCapsule arm, RagdollPartCapsule elbow, RagdollPartBox hand, bool leftHand, bool createTips)
{
var dirUpper = elbow.transform.position - arm.transform.position;
var dirLower = hand.transform.position - elbow.transform.position;
var dirHand = GetLongestTransform(hand.transform).position - hand.transform.position; // TODO: need to find the most longest bone
if (leftHand)
{
ConfigureJointLimits(arm.joint, -100f, 30f, 100f, 45f);
ConfigureJointLimits(elbow.joint, -120f, 0f, 10f, 90f);
if (createTips)
{
ConfigureJointLimits(hand.joint, -90f, 90f, 90f, 45f);
}
dirUpper = -dirUpper;
dirLower = -dirLower;
dirHand = -dirHand;
}
else
{
ConfigureJointLimits(arm.joint, -30f, 100f, 100f, 45f);
ConfigureJointLimits(elbow.joint, 0f, 120f, 10f, 90f);
if (createTips)
{
ConfigureJointLimits(hand.joint, -90f, 90f, 90f, 45f);
}
}
var upU = Vector3.Cross(_playerDirection, dirUpper);
var upL = Vector3.Cross(_playerDirection, dirLower);
var upH = Vector3.Cross(_playerDirection, dirHand);
ConfigureJointParams(arm, _chest.rigidbody, upU, _playerDirection);
ConfigureJointParams(elbow, arm.rigidbody, upL, _playerDirection);
if (createTips)
{
ConfigureJointParams(hand, elbow.rigidbody, upH, _playerDirection);
}
}
void ConfigureLegsJoints(RagdollPartCapsule hip, RagdollPartCapsule knee, RagdollPartBox foot, bool createTips)
{
var hipJoint = hip.joint;
var kneeJoint = knee.joint;
var footJoint = foot.joint;
ConfigureJointParams(hip, _pelvis.rigidbody, _rootNode.right, _rootNode.forward);
ConfigureJointParams(knee, hip.rigidbody, _rootNode.right, _rootNode.forward);
ConfigureJointLimits(hipJoint, -10f, 120f, 90f, 20f);
ConfigureJointLimits(kneeJoint, -120f, 0f, 10f, 20f);
if (createTips)
{
ConfigureJointParams(foot, knee.rigidbody, _rootNode.right, _rootNode.forward);
ConfigureJointLimits(footJoint, -70f, 70f, 45f, 20f);
}
}
static void AddComponentesTo(RagdollPartBox part, RagdollProperties ragdollProperties, float mass, bool addJoint)
{
AddComponentesToBase(part, ragdollProperties, mass, addJoint);
GameObject go = part.transform.gameObject;
part.collider = GetCollider<BoxCollider>(go.transform);
if (part.collider == null)
part.collider = go.AddComponent<BoxCollider>();
part.collider.isTrigger = ragdollProperties.asTrigger;
}
static void AddComponentesTo(RagdollPartCapsule part, RagdollProperties ragdollProperties, float mass, bool addJoint)
{
AddComponentesToBase(part, ragdollProperties, mass, addJoint);
GameObject go = part.transform.gameObject;
part.collider = GetCollider<CapsuleCollider>(go.transform);
if (part.collider == null)
part.collider = go.AddComponent<CapsuleCollider>();
part.collider.isTrigger = ragdollProperties.asTrigger;
}
static void AddComponentesTo(RagdollPartSphere part, RagdollProperties ragdollProperties, float mass, bool addJoint)
{
AddComponentesToBase(part, ragdollProperties, mass, addJoint);
GameObject go = part.transform.gameObject;
part.collider = GetCollider<SphereCollider>(go.transform);
if (part.collider == null)
part.collider = go.AddComponent<SphereCollider>();
part.collider.isTrigger = ragdollProperties.asTrigger;
}
static void AddComponentesToBase(RagdollPartBase part, RagdollProperties ragdollProperties, float mass, bool addJoint)
{
GameObject go = part.transform.gameObject;
part.rigidbody = go.GetComponent<Rigidbody>();
if (part.rigidbody == null)
part.rigidbody = go.AddComponent<Rigidbody>();
part.rigidbody.mass = mass;
part.rigidbody.drag = ragdollProperties.rigidDrag;
part.rigidbody.angularDrag = ragdollProperties.rigidAngularDrag;
part.rigidbody.collisionDetectionMode = ragdollProperties.cdMode;
part.rigidbody.isKinematic = ragdollProperties.isKinematic;
part.rigidbody.useGravity = ragdollProperties.useGravity;
if (addJoint)
{
part.joint = go.GetComponent<CharacterJoint>();
if (part.joint == null)
part.joint = go.AddComponent<CharacterJoint>();
part.joint.enablePreprocessing = false;
part.joint.enableProjection = true;
}
}
static T GetCollider<T>(Transform transform)
where T : Collider
{
for (int i = 0; i < transform.childCount; ++i)
{
Transform child = transform.GetChild(i);
if (child.name.EndsWith(_colliderNodeSufix))
{
transform = child;
break;
}
}
return transform.GetComponent<T>();
}
/// <summary>
/// Remove all colliders, joints, and rigids
/// </summary>
public void ClearRagdoll()
{
foreach (var component in _pelvis.transform.GetComponentsInChildren<Collider>())
GameObject.DestroyImmediate(component);
foreach (var component in _pelvis.transform.GetComponentsInChildren<CharacterJoint>())
GameObject.DestroyImmediate(component);
foreach (var component in _pelvis.transform.GetComponentsInChildren<Rigidbody>())
GameObject.DestroyImmediate(component);
DeleteColliderNodes(_pelvis.transform);
}
/// <summary>
/// Correct deleting collider with collider's separate nodes
/// </summary>
/// <param name="node"></param>
private static void DeleteColliderNodes(Transform node)
{
for (int i = 0; i < node.childCount; ++i)
{
Transform child = node.GetChild(i);
if (child.name.EndsWith(_colliderNodeSufix))
GameObject.DestroyImmediate(child.gameObject);
else
DeleteColliderNodes(child);
}
}
}
}