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.Utilities;
namespace SanAndreasUnity.Behaviours
{
using Anim = SanAndreasUnity.Importing.Conversion.Animation;
public class PedModel : MonoBehaviour
{
/// State of last played anim.
public AnimationState LastAnimState { get; private set; }
public AnimationState LastSecondaryAnimState { get; private set; }
/// Last played anim.
public AnimId LastAnimId { get; private set; }
public AnimId LastSecondaryAnimId { get; private set; }
/// Did anims changed when played them last time ?
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 _loadedAnims
= new Dictionary();
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 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 Pelvis { get; private set; }
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 m_originalFrameDatas = new List ();
public List OriginalFrameDatas { get { return m_originalFrameDatas; } }
private Ped m_ped;
/// Velocity of the model extracted from animation.
public Vector3 Velocity { get; private set; }
/// Velocity axis to use.
public int VelocityAxis { get; set; }
private Dictionary> m_mixedTransforms = new Dictionary>();
public event System.Action onLateUpdate = delegate {};
private void Awake()
{
m_ped = this.GetComponentInParent ();
}
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();
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(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 ();
// 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 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");
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");
Pelvis = getFrame(" Pelvis");
}
///
/// 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.
///
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 ();
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 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 (){ 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 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 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 (AnimGroup group, AnimIndex anim)
{
string animName = GetAnimName (group, anim);
if (string.IsNullOrEmpty (animName))
return null;
Anim result;
return _loadedAnims.TryGetValue (animName, out result) ? result : null;
}
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];
}
}
}
}
}
}
}