SanAndreasUnity/Assets/Scripts/Behaviours/PedModel.cs
2021-01-07 15:26:11 +01:00

765 lines
No EOL
22 KiB
C#

using System.Collections.Generic;
using SanAndreasUnity.Importing.Conversion;
using SanAndreasUnity.Importing.Items;
using SanAndreasUnity.Importing.Items.Definitions;
using SanAndreasUnity.Importing.Animation;
using UnityEngine;
using System.Linq;
using SanAndreasUnity.Behaviours.Peds;
using SanAndreasUnity.Utilities;
namespace SanAndreasUnity.Behaviours
{
using Anim = SanAndreasUnity.Importing.Conversion.Animation;
public class PedModel : MonoBehaviour
{
/// <summary> State of last played anim. </summary>
public AnimationState LastAnimState { get; private set; }
public AnimationState LastSecondaryAnimState { get; private set; }
/// <summary> Last played anim. </summary>
public AnimId LastAnimId { get; private set; }
public AnimId LastSecondaryAnimId { get; private set; }
/// <summary> Did anims changed when played them last time ? </summary>
public bool AnimsChanged { get { return this.FirstAnimChanged || this.SecondAnimChanged; } }
public bool FirstAnimChanged { get; private set; }
public bool SecondAnimChanged { get; private set; }
private readonly AnimId m_invalidAnimId = new AnimId ();
private UnityEngine.Animation _anim { get; set; }
public UnityEngine.Animation AnimComponent { get { return _anim; } }
private FrameContainer _frames;
public FrameContainer Frames { get { return _frames; } }
private Frame _root;
public Frame RootFrame { get { return _root; } }
public Frame UnnamedFrame { get; private set; }
private readonly Dictionary<string, Anim> _loadedAnims
= new Dictionary<string, Anim>();
public PedestrianDef Definition { get; private set; }
[SerializeField] private int m_startingPedId = 167;
public int StartingPedId { get { return m_startingPedId; } set { m_startingPedId = value; } }
public int PedestrianId { get; private set; }
// have we loaded model since the Loader has finished loading
private bool loadedModelOnStartup = false;
public bool IsInVehicle { get; set; }
public Vector3 VehicleParentOffset { get; set; }
private Transform m_leftFinger = null;
public Transform LeftFinger { get { return m_leftFinger; } }
private Transform m_rightFinger = null;
public Transform RightFinger { get { return m_rightFinger; } }
private Transform m_leftHand;
public Transform LeftHand { get { return m_leftHand; } }
private Transform m_rightHand;
public Transform RightHand { get { return m_rightHand; } }
public Transform LeftUpperArm { get; private set; }
public Transform RightUpperArm { get; private set; }
public Transform LeftForeArm { get; private set; }
public Transform RightForeArm { get; private set; }
public Transform LeftClavicle { get; private set; }
public Transform RightClavicle { get; private set; }
public Transform Head { get; private set; }
public Transform Jaw { get; private set; }
public Transform Neck { get; private set; }
public Transform LBreast { get; private set; }
public Transform RBreast { get; private set; }
public Transform UpperSpine { get; private set; }
public Transform Belly { get; private set; }
public Transform Spine { get; private set; }
public Transform R_Thigh { get; private set; }
public Transform L_Thigh { get; private set; }
public Transform R_Calf { get; private set; }
public Transform L_Calf { get; private set; }
public Transform R_Foot { get; private set; }
public Transform L_Foot { get; private set; }
public Transform Pelvis { get; private set; }
readonly Dictionary<Transform, int> m_damageLevelPerBones = new Dictionary<Transform, int>();
private RagdollBuilder m_ragdollBuilder;
public RagdollBuilder RagdollBuilder => m_ragdollBuilder;
public class FrameAnimData
{
public Vector3 pos;
public Quaternion rot;
public Vector3 velocity;
public Frame frame;
public FrameAnimData (Vector3 pos, Quaternion rot, Vector3 velocity, Frame frame)
{
this.pos = pos;
this.rot = rot;
this.velocity = velocity;
this.frame = frame;
}
}
private List<FrameAnimData> m_originalFrameDatas = new List<FrameAnimData> ();
public List<FrameAnimData> OriginalFrameDatas { get { return m_originalFrameDatas; } }
private Ped m_ped;
/// <summary> Velocity of the model extracted from animation. </summary>
public Vector3 Velocity { get; private set; }
/// <summary> Velocity axis to use. </summary>
public int VelocityAxis { get; set; }
private Dictionary<AnimationState, List<Transform>> m_mixedTransforms = new Dictionary<AnimationState, List<Transform>>();
public event System.Action onLateUpdate = delegate {};
private void Awake()
{
m_ped = this.GetComponentInParent<Ped> ();
}
private void LateUpdate()
{
if (_root == null) return;
var trans = _root.transform;
if (IsInVehicle)
{
// 'Anchor' pedestrian model into the vehicle
this.Velocity = Vector3.zero;
trans.parent.localPosition = VehicleParentOffset;
}
else
{
// Store movement defined by animation for pedestrian model
this.Velocity = _root.LocalVelocity;
trans.parent.localPosition = new Vector3(0f, -trans.localPosition.y * .5f, -trans.localPosition.z);
}
this.onLateUpdate ();
}
private void Update()
{
if (Loader.HasLoaded)
{
// FIXME: should not be done like this
if (!loadedModelOnStartup)
{
loadedModelOnStartup = true;
// load model on startup
// only load it if it wasn't loaded so far
if (null == this.Definition)
{
Load (m_startingPedId);
// and play animation
PlayAnim(AnimGroup.WalkCycle, AnimIndex.Idle);
}
}
}
}
private void OnDrawGizmosSelected()
{
float size = 0.1f;
Gizmos.color = Color.red;
SkinnedMeshRenderer[] renderers = GetComponentsInChildren<SkinnedMeshRenderer>();
foreach (SkinnedMeshRenderer smr in renderers)
{
foreach (Transform tr in smr.bones)
{
Gizmos.DrawWireCube(tr.position, new Vector3(size, size, size));
}
}
}
public void Load(int id)
{
var newDefinition = Item.GetDefinition<PedestrianDef>(id);
if (null == newDefinition)
return;
PedestrianId = id;
Definition = newDefinition;
LastAnimId = m_invalidAnimId;
LastSecondaryAnimId = m_invalidAnimId;
LastAnimState = null;
LastSecondaryAnimState = null;
m_originalFrameDatas.Clear ();
_anim = this.gameObject.GetOrAddComponent<UnityEngine.Animation> ();
// remove mixing transforms for all anim states
this.RemoveAllMixingTransformsForAllAnimStates();
LoadModel(Definition.ModelName, Definition.TextureDictionaryName);
// save original model state
// TODO: we should first reset all anim parameters (eg mixing transforms) ?
var state = PlayAnim(AnimGroup.WalkCycle, AnimIndex.Idle);
if (state != null) {
state.time = 0f;
this.AnimComponent.Sample ();
}
this.SaveModelState ();
// load some anims
LoadAnim(AnimGroup.WalkCycle, AnimIndex.Walk);
LoadAnim(AnimGroup.WalkCycle, AnimIndex.Run);
LoadAnim(AnimGroup.WalkCycle, AnimIndex.Panicked);
LoadAnim(AnimGroup.WalkCycle, AnimIndex.Idle);
LoadAnim(AnimGroup.WalkCycle, AnimIndex.RoadCross);
LoadAnim(AnimGroup.WalkCycle, AnimIndex.WalkStart);
LoadAnim(AnimGroup.Car, AnimIndex.Sit);
LoadAnim(AnimGroup.Car, AnimIndex.DriveLeft);
LoadAnim(AnimGroup.Car, AnimIndex.DriveRight);
LoadAnim(AnimGroup.Car, AnimIndex.GetInLeft);
LoadAnim(AnimGroup.Car, AnimIndex.GetInRight);
LoadAnim(AnimGroup.Car, AnimIndex.GetOutLeft);
LoadAnim(AnimGroup.Car, AnimIndex.GetOutRight);
LoadAnim(AnimGroup.MyWalkCycle, AnimIndex.IdleArmed);
LoadAnim(AnimGroup.MyWalkCycle, AnimIndex.GUN_STAND);
// LoadAllAnimations ();
}
private void LoadModel(string modelName, params string[] txds)
{
if (_frames != null)
{
Destroy(_frames.Root.gameObject);
Destroy(_frames);
_frames = null;
}
var geoms = Geometry.Load(modelName, txds);
_frames = geoms.AttachFrames(transform, MaterialFlags.Default);
// we have to remove white spaces from frame names and their game objects, because some models
// have white spaces, and some don't - and we need to have the same frame names for all models,
// otherwise animations will not work on all of them
foreach (var frame in _frames)
{
frame.Name = frame.Name.Replace (" ", "");
frame.gameObject.name = frame.Name;
}
_root = _frames.GetByName("Root");
UnnamedFrame = _frames.GetByName("unnamed");
System.Func<string, Transform> getFrame = (string frameName) => {
Frame frame = _frames.GetByName (frameName, true);
if(null == frame) {
Debug.LogErrorFormat("Failed to find frame by name '{0}' on model '{1}'", frameName, modelName);
return null;
}
return frame.transform;
};
m_rightFinger = getFrame(" R Finger");
m_leftFinger = getFrame(" L Finger");
m_rightHand = getFrame (" R Hand");
m_leftHand = getFrame (" L Hand");
RightUpperArm = getFrame (" R UpperArm");
LeftUpperArm = getFrame (" L UpperArm");
RightForeArm = getFrame (" R ForeArm");
LeftForeArm = getFrame (" L ForeArm");
RightClavicle = getFrame ("Bip01 R Clavicle");
LeftClavicle = getFrame ("Bip01 L Clavicle");
Head = getFrame (" Head");
Jaw = getFrame(" Jaw");
Neck = getFrame (" Neck");
LBreast = getFrame ("L breast");
RBreast = getFrame ("R breast");
UpperSpine = getFrame (" Spine1");
Belly = getFrame ("Belly");
Spine = getFrame(" Spine");
R_Thigh = getFrame(" R Thigh");
L_Thigh = getFrame(" L Thigh");
R_Calf = getFrame(" R Calf");
L_Calf = getFrame(" L Calf");
R_Foot = getFrame(" R Foot");
L_Foot = getFrame(" L Foot");
Pelvis = getFrame(" Pelvis");
F.RunExceptionSafe(() => this.SetupRagdoll());
}
void SetupRagdoll()
{
m_damageLevelPerBones.Clear();
RagdollBuilder rb = m_ragdollBuilder = new RagdollBuilder
{
pelvis = this.RootFrame.transform,
leftHips = this.L_Thigh.transform,
leftKnee = this.L_Calf.transform,
leftFoot = this.L_Foot.transform,
rightHips = this.R_Thigh.transform,
rightKnee = this.R_Calf.transform,
rightFoot = this.R_Foot.transform,
leftArm = this.LeftUpperArm.transform,
leftElbow = this.LeftForeArm.transform,
rightArm = this.RightUpperArm.transform,
rightElbow = this.RightForeArm.transform,
middleSpine = this.UpperSpine.transform,
head = this.Head.transform,
jaw = this.Jaw.transform,
};
rb.totalMass = PedManager.Instance.ragdollMass;
rb.PrepareBones();
rb.OnWizardCreate();
// set layer
this.RootFrame.gameObject.SetLayerRecursive(Ped.PedBoneLayerName);
// assign damage level for bones
Transform[] stomachAndChest = new Transform[] { this.RootFrame.transform, this.UpperSpine.transform };
Transform[] legsAndHands = new Transform[] { this.L_Thigh, this.R_Thigh, this.L_Calf, this.R_Calf,
this.L_Foot, this.R_Foot, this.LeftUpperArm, this.RightUpperArm, this.LeftForeArm, this.RightForeArm };
Transform[] headAndJaw = new Transform[] { this.Head.transform, this.Jaw.transform };
foreach (var bone in stomachAndChest)
{
m_damageLevelPerBones[bone] = 2;
}
foreach (var bone in legsAndHands)
{
m_damageLevelPerBones[bone] = 1;
}
foreach (var bone in headAndJaw)
{
m_damageLevelPerBones[bone] = 3;
}
}
public DeadBody DetachRagdoll(DamageInfo damageInfo)
{
if (null == m_ragdollBuilder)
return null;
if (null == this.RootFrame)
return null;
Transform ragdollTransform = this.RootFrame.transform;
// remove colliders which are not part of ragdoll
foreach (var c in m_ragdollBuilder.AdditionalColliders)
{
Object.Destroy(c);
}
m_ragdollBuilder.BuildBodies();
m_ragdollBuilder.BuildJoints();
m_ragdollBuilder.CalculateMass();
m_ragdollBuilder = null;
ragdollTransform.SetParent(null);
// setup rigid bodies
foreach (var rb in ragdollTransform.GetComponentsInChildren<Rigidbody>())
{
rb.drag = PedManager.Instance.ragdollDrag;
if (PedManager.Instance.ragdollMaxDepenetrationVelocity >= 0)
rb.maxDepenetrationVelocity = PedManager.Instance.ragdollMaxDepenetrationVelocity;
rb.collisionDetectionMode = PedManager.Instance.ragdollCollisionDetectionMode;
// add velocity to ragdoll based on current ped's velocity
rb.velocity = m_ped.Velocity;
}
// apply force to a collider that was hit by a weapon
if (damageInfo != null && damageInfo.raycastHitTransform != null && damageInfo.damageType == DamageType.Bullet)
{
var c = damageInfo.raycastHitTransform.GetComponent<Collider>();
if (c != null && c.attachedRigidbody != null)
{
c.attachedRigidbody.AddForceAtPosition(
damageInfo.hitDirection * damageInfo.amount.SqrtOrZero() * PedManager.Instance.ragdollDamageForce,
damageInfo.hitPoint,
ForceMode.Impulse);
}
}
// change layer
ragdollTransform.gameObject.SetLayerRecursive(GameManager.DefaultLayerIndex);
return DeadBody.Create(ragdollTransform, m_ped);
}
public float GetAmountOfDamageForBone(Transform boneTransform, float baseDamageValue)
{
if (m_damageLevelPerBones.TryGetValue(boneTransform, out int damageLevel))
{
if (1 == damageLevel)
return baseDamageValue * PedManager.Instance.legAndArmDamageMultiplier;
if (2 == damageLevel)
return baseDamageValue * PedManager.Instance.stomachAndChestDamageMultiplier;
if (3 == damageLevel)
return baseDamageValue * PedManager.Instance.headDamageMultiplier;
}
return 0f;
}
/// <summary>
/// Resets the state of the model. This includes position, rotation, and velocity of every frame (bone).
/// It does it by playing idle anim, setting it's time to 0, and sampling from it.
/// </summary>
public void ResetModelState ()
{
//var state = PlayAnim (AnimGroup.WalkCycle, AnimIndex.Idle);
//state.normalizedTime = 0;
//AnimComponent.Sample ();
foreach (var frameData in m_originalFrameDatas) {
if (null == frameData.frame)
continue;
ResetFrameState (frameData);
}
}
private void SaveModelState ()
{
m_originalFrameDatas.Clear ();
foreach (var frame in this.Frames) {
m_originalFrameDatas.Add (new FrameAnimData (frame.transform.localPosition, frame.transform.localRotation,
frame.LocalVelocity, frame));
}
}
private void ResetFrameState (FrameAnimData frameData)
{
frameData.frame.transform.localPosition = frameData.pos;
frameData.frame.transform.localRotation = frameData.rot;
frameData.frame.LocalVelocity = frameData.velocity;
}
public void ResetFrameState (Transform frameTransform)
{
Frame frame = frameTransform.GetComponent<Frame> ();
if (null == frame)
return;
var frameData = m_originalFrameDatas.FirstOrDefault( f => f.frame == frame );
if (frameData != null) {
ResetFrameState (frameData);
}
}
public AnimationState PlayAnim (AnimId animId, PlayMode playMode = PlayMode.StopAll)
{
this.FirstAnimChanged = this.SecondAnimChanged = false;
var animState = LoadAnim (animId);
if (null == animState)
return null;
this.FirstAnimChanged = !animId.Equals (LastAnimId);
this.SecondAnimChanged = !m_invalidAnimId.Equals (LastSecondaryAnimId);
LastAnimId = animId;
LastSecondaryAnimId = m_invalidAnimId;
LastAnimState = animState;
LastSecondaryAnimState = null;
// reset velocity axis
this.VelocityAxis = 2;
//animState.layer = 0;
RemoveAllMixingTransforms (animState);
_anim.Play (animState.name, playMode);
return animState;
}
public AnimationState PlayAnim (AnimGroup animGroup, AnimIndex animIndex, PlayMode playMode = PlayMode.StopAll)
{
return PlayAnim (new AnimId (animGroup, animIndex), playMode);
}
public AnimationState PlayAnim (string fileName, string animName, PlayMode playMode = PlayMode.StopAll)
{
return PlayAnim (new AnimId (fileName, animName), playMode);
}
public bool Play2Anims (AnimId animIdA, AnimId animIdB)
{
this.FirstAnimChanged = this.SecondAnimChanged = false;
// load anims
var stateA = LoadAnim (animIdA);
var stateB = LoadAnim (animIdB);
if (null == stateA || null == stateB)
return false;
this.FirstAnimChanged = !animIdA.Equals (LastAnimId);
this.SecondAnimChanged = !animIdB.Equals (LastSecondaryAnimId);
// reset velocity axis
this.VelocityAxis = 2;
// play anims
// are mixing transforms and layers preserved ? - probably
RemoveAllMixingTransforms (stateA);
RemoveAllMixingTransforms (stateB);
AddMixingTransform (stateA, this.Spine, true);
AddMixingTransform (stateB, _root.transform, false);
AddMixingTransform (stateB, this.L_Thigh, true);
AddMixingTransform (stateB, this.R_Thigh, true);
stateA.layer = 0;
stateB.layer = 1;
this.AnimComponent.Play( stateA.clip.name, PlayMode.StopSameLayer);
this.AnimComponent.Play( stateB.clip.name, PlayMode.StopSameLayer);
// assign last anims
LastAnimId = animIdA;
LastSecondaryAnimId = animIdB;
LastAnimState = stateA;
LastSecondaryAnimState = stateB;
return true;
}
public bool AddMixingTransform (AnimationState state, Transform tr, bool recursive)
{
List<Transform> list;
if (m_mixedTransforms.TryGetValue (state, out list)) {
if (list.Contains (tr))
return false;
state.AddMixingTransform (tr, recursive);
list.Add (tr);
return true;
} else {
state.AddMixingTransform (tr, recursive);
list = new List<Transform> (){ tr };
m_mixedTransforms.Add (state, list);
return true;
}
}
public void AddMixingTransforms (AnimationState state, params Transform[] transforms)
{
foreach (var tr in transforms)
AddMixingTransform (state, tr, false);
}
public bool RemoveMixingTransform (AnimationState state, Transform tr)
{
List<Transform> list;
if (m_mixedTransforms.TryGetValue (state, out list)) {
if (!list.Contains (tr))
return false;
state.RemoveMixingTransform (tr);
list.Remove (tr);
return true;
} else {
return false;
}
}
public int RemoveAllMixingTransforms (AnimationState state)
{
List<Transform> list;
if (m_mixedTransforms.TryGetValue (state, out list)) {
int count = list.Count;
foreach (Transform mix in list) {
if (mix != null)
state.RemoveMixingTransform (mix);
}
list.Clear ();
return count;
} else {
return 0;
}
}
public void RemoveAllMixingTransformsForAllAnimStates()
{
foreach (AnimationState s in this.AnimComponent)
{
if (s != null)
this.RemoveAllMixingTransforms(s);
}
m_mixedTransforms.Clear();
}
public Anim GetAnim (string animName)
{
Anim result;
return _loadedAnims.TryGetValue (animName, out result) ? result : null;
}
public Anim GetAnim (AnimGroup group, AnimIndex anim)
{
string animName = GetAnimName (group, anim);
if (string.IsNullOrEmpty (animName))
return null;
return GetAnim(animName);
}
public string GetAnimName (AnimGroup group, AnimIndex anim)
{
string animName = null, fileName = null;
if (GetAnimNameAndFile (group, anim, ref animName, ref fileName))
return animName;
return null;
}
public bool GetAnimNameAndFile (AnimGroup group, AnimIndex anim, ref string animName, ref string fileName)
{
if (anim == AnimIndex.None)
return false;
if (group == AnimGroup.None)
return false;
if (null == this.Definition || string.IsNullOrEmpty (this.Definition.AnimGroupName))
return false;
var animGroup = AnimationGroup.Get (this.Definition.AnimGroupName, group);
if (null == animGroup)
return false;
animName = animGroup [anim];
fileName = animGroup.FileName;
return true;
}
private AnimationState LoadAnim (AnimGroup group, AnimIndex anim)
{
string animName = null, fileName = null;
if (GetAnimNameAndFile (group, anim, ref animName, ref fileName))
return LoadAnim (animName, fileName);
return null;
}
private AnimationState LoadAnim (string animName, string fileName)
{
AnimationState state = null;
if (!_loadedAnims.ContainsKey(animName))
{
var importedAnim = Anim.Load(fileName, animName, _frames);
if (importedAnim != null && importedAnim.Clip != null)
{
_loadedAnims.Add(animName, importedAnim);
_anim.AddClip(importedAnim.Clip, animName);
state = _anim[animName];
}
else
{
Debug.LogErrorFormat ("Failed to load anim - file: {0}, anim name: {1}", fileName, animName);
}
}
else
{
state = _anim[animName];
}
return state;
}
private AnimationState LoadAnim (AnimId animId)
{
return animId.UsesAnimGroup ? LoadAnim (animId.AnimGroup, animId.AnimIndex) : LoadAnim (animId.AnimName, animId.FileName);
}
private void LoadAllAnimations()
{
foreach (var dict in AnimationGroup._sGroups.Values)
{
foreach (AnimationGroup animGroup in dict.Values)
{
foreach (var animName in animGroup.Animations)
{
if (!_loadedAnims.ContainsKey(animName))
{
var clip = Importing.Conversion.Animation.Load(animGroup.FileName, animName, _frames);
_loadedAnims.Add(animName, clip);
_anim.AddClip(clip.Clip, animName);
// state = _anim [animName];
}
}
}
}
}
}
}