mirror of
https://gitgud.io/AbstractConcept/rimworld-animation-studio.git
synced 2024-08-15 00:43:27 +00:00
2989d9a72c
- Insert adds a new keyframe to the selected timeline - New stages have frames cloned from the last frame of current stage - Existing key are now replaced when another key is dropped on them - Fixed bug where starting a new animation could result in errors
288 lines
9.9 KiB
C#
288 lines
9.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using UnityEngine;
|
|
|
|
namespace RimWorldAnimationStudio
|
|
{
|
|
public class Workspace : Singleton<Workspace>
|
|
{
|
|
public static AnimationDef animationDef;
|
|
public static int stageID = 0;
|
|
|
|
public static List<int> keyframeID = new List<int>();
|
|
|
|
[SerializeField] private List<WorkspaceRecord> workspaceHistory = new List<WorkspaceRecord>();
|
|
[SerializeField] private int maxHistoryDepth = 100;
|
|
|
|
public static ActorManipulationMode actorManipulationMode = ActorManipulationMode.Pan;
|
|
public static ActorBodyPart selectedBodyPart;
|
|
|
|
public static List<PawnKeyframe> copiedKeyframes = new List<PawnKeyframe>();
|
|
|
|
public static string animationSavePath;
|
|
|
|
private static int _actorID = 0;
|
|
public static int actorID { get { return Mathf.Clamp(_actorID, 0, animationDef.actors.Count - 1); } set { _actorID = value; } }
|
|
|
|
public static int StageWindowSize
|
|
{
|
|
get
|
|
{
|
|
if (animationDef == null)
|
|
{ return -1; }
|
|
|
|
if (animationDef.animationStages[stageID].stageWindowSize < 0)
|
|
{ animationDef.animationStages[stageID].stageWindowSize = animationDef.animationStages[stageID].animationClips.Select(x => x.duration).Max(); }
|
|
|
|
return animationDef.animationStages[stageID].stageWindowSize;
|
|
}
|
|
}
|
|
|
|
public PawnKeyframe GetCurrentPawnKeyframe(bool makeKeyframe = false)
|
|
{
|
|
int stageTick = AnimationController.Instance.stageTick;
|
|
PawnKeyframe keyframe = animationDef?.animationStages[stageID]?.animationClips[actorID]?.keyframes.FirstOrDefault(x => x.atTick == stageTick);
|
|
|
|
if (keyframe != null || makeKeyframe == false)
|
|
{ return keyframe; }
|
|
|
|
AnimationController.Instance.AddPawnKeyframe();
|
|
return animationDef?.animationStages[stageID]?.animationClips[actorID]?.keyframes.FirstOrDefault(x => x.atTick == stageTick);
|
|
}
|
|
|
|
public List<PawnKeyframe> GetPawnKeyframesAtTick(int actorID, int atTick)
|
|
{
|
|
return animationDef?.animationStages[stageID]?.animationClips[actorID]?.keyframes.Where(x => x.atTick == atTick)?.ToList();
|
|
}
|
|
|
|
public PawnAnimationClip GetCurrentPawnAnimationClip()
|
|
{
|
|
return animationDef.animationStages[stageID].animationClips[actorID];
|
|
}
|
|
|
|
public PawnAnimationClip GetPawnAnimationClip(int actorID)
|
|
{
|
|
return animationDef.animationStages[stageID].animationClips[actorID];
|
|
}
|
|
|
|
public PawnKeyframe GetPawnKeyframe(int actorID, int keyframeID)
|
|
{
|
|
if (stageID < 0) return null;
|
|
if (actorID < 0) return null;
|
|
|
|
if (stageID >= animationDef.animationStages.Count) return null;
|
|
if (actorID >= animationDef.animationStages[stageID].animationClips.Count) return null;
|
|
|
|
return animationDef.animationStages[stageID].animationClips[actorID].keyframes.FirstOrDefault(x => x.keyframeID == keyframeID);
|
|
}
|
|
|
|
public List<PawnKeyframe> GetPawnKeyframesByID(List<int> keyframeIDs)
|
|
{
|
|
List<PawnKeyframe> pawnKeyframes = new List<PawnKeyframe>();
|
|
|
|
foreach (PawnAnimationClip clip in animationDef.animationStages[stageID].animationClips)
|
|
{
|
|
foreach (PawnKeyframe keyframe in clip.keyframes)
|
|
{
|
|
if (keyframeIDs.Contains(keyframe.keyframeID))
|
|
{ pawnKeyframes.Add(keyframe); }
|
|
}
|
|
}
|
|
|
|
return pawnKeyframes;
|
|
}
|
|
|
|
public PawnAnimationClip GetAnimationClipThatOwnsKeyframe(int keyframeID, out int clipID)
|
|
{
|
|
clipID = -1;
|
|
|
|
for (int i = 0; i < animationDef.animationStages[stageID].animationClips.Count; i++)
|
|
{
|
|
PawnAnimationClip clip = animationDef.animationStages[stageID].animationClips[i];
|
|
|
|
if (clip.keyframes.Any(x => x.keyframeID == keyframeID))
|
|
{
|
|
clipID = i;
|
|
return clip;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public bool DoesPawnKeyframeExistAtTick(int stageID, int actorID, int atTick)
|
|
{
|
|
return animationDef.animationStages[stageID].animationClips[actorID].keyframes.Any(x => x.atTick == atTick);
|
|
}
|
|
|
|
public PawnKeyframe GetNextKeyframe(int actorID)
|
|
{
|
|
PawnKeyframe pawnKeyframe = null;
|
|
PawnAnimationClip clip = GetPawnAnimationClip(actorID);
|
|
|
|
int stageTick = AnimationController.Instance.stageTick;
|
|
|
|
foreach (PawnKeyframe keyframe in clip.keyframes)
|
|
{
|
|
if (keyframe.atTick > stageTick)
|
|
{ pawnKeyframe = keyframe; break; }
|
|
}
|
|
|
|
return pawnKeyframe;
|
|
}
|
|
|
|
public PawnKeyframe GetPreviousKeyframe(int actorID)
|
|
{
|
|
PawnKeyframe pawnKeyframe = null;
|
|
PawnAnimationClip clip = GetPawnAnimationClip(actorID);
|
|
|
|
int stageTick = AnimationController.Instance.stageTick;
|
|
|
|
foreach (PawnKeyframe keyframe in clip.keyframes)
|
|
{
|
|
if (keyframe.atTick < stageTick)
|
|
{ pawnKeyframe = keyframe; }
|
|
}
|
|
|
|
return pawnKeyframe;
|
|
}
|
|
|
|
public PawnKeyframe GetCurrentOrPreviousKeyframe(int actorID)
|
|
{
|
|
PawnKeyframe pawnKeyframe = null;
|
|
PawnAnimationClip clip = GetPawnAnimationClip(actorID);
|
|
|
|
int stageTick = AnimationController.Instance.stageTick;
|
|
|
|
foreach (PawnKeyframe keyframe in clip.keyframes)
|
|
{
|
|
if (keyframe.atTick <= stageTick)
|
|
{ pawnKeyframe = keyframe; }
|
|
}
|
|
|
|
return pawnKeyframe;
|
|
}
|
|
|
|
public static int FindClosestKeyFrameAtTick(int atTick, int searchDistance = int.MaxValue, int excludedActorID = -1)
|
|
{
|
|
List<PawnKeyframe> keyframesToCheck;
|
|
|
|
if (excludedActorID >= 0)
|
|
{
|
|
keyframesToCheck = animationDef.animationStages[stageID].animationClips.Where(x =>
|
|
animationDef.animationStages[stageID].animationClips.IndexOf(x) != excludedActorID).SelectMany(x => x.keyframes)?.ToList();
|
|
}
|
|
|
|
else
|
|
{ keyframesToCheck = animationDef.animationStages[stageID].animationClips.SelectMany(x => x.keyframes)?.ToList(); }
|
|
|
|
keyframesToCheck = keyframesToCheck.Where(x => Mathf.Abs(atTick - x.atTick.Value) <= searchDistance)?.ToList();
|
|
|
|
if (keyframesToCheck.NullOrEmpty())
|
|
{ return atTick; }
|
|
|
|
int minDist = int.MaxValue;
|
|
int bestAtTick = -1;
|
|
|
|
foreach (PawnKeyframe keyframe_ in keyframesToCheck)
|
|
{
|
|
if (Mathf.Abs(keyframe_.atTick.Value - atTick) < minDist)
|
|
{
|
|
minDist = Mathf.Abs(keyframe_.atTick.Value - atTick);
|
|
bestAtTick = keyframe_.atTick.Value;
|
|
}
|
|
}
|
|
|
|
return bestAtTick;
|
|
}
|
|
|
|
public int GetEarliestAtTickInCopiedKeyframes(int actorID)
|
|
{
|
|
IEnumerable<Keyframe> keyframes = copiedKeyframes.Where(x => x.actorID == actorID);
|
|
if (keyframes == null || keyframes.Any() == false) return -1;
|
|
|
|
return keyframes.Min(x => x.atTick).Value;
|
|
}
|
|
|
|
[SerializeField]
|
|
public LinkedList<WorkspaceRecord> pastSnapshots = new LinkedList<WorkspaceRecord>();
|
|
public LinkedList<WorkspaceRecord> futureSnapshots = new LinkedList<WorkspaceRecord>();
|
|
|
|
|
|
public void Reset()
|
|
{
|
|
actorID = 0;
|
|
stageID = 0;
|
|
keyframeID.Clear();
|
|
selectedBodyPart = null;
|
|
|
|
ClearHistory();
|
|
}
|
|
|
|
public void MakeHistoricRecord(string eventDesc)
|
|
{
|
|
WorkspaceRecord record = new WorkspaceRecord();
|
|
record.recordID = pastSnapshots.Count;
|
|
record.eventDesc = eventDesc;
|
|
record.animationDef = animationDef.Copy();
|
|
record.stageID = stageID;
|
|
|
|
futureSnapshots.Clear();
|
|
pastSnapshots.AddLast(record);
|
|
|
|
if (pastSnapshots.Count > maxHistoryDepth)
|
|
{ pastSnapshots.RemoveFirst(); }
|
|
}
|
|
|
|
public void RestoreToHistoricRecord(WorkspaceRecord record)
|
|
{
|
|
animationDef = record.animationDef.Copy();
|
|
stageID = record.stageID;
|
|
|
|
AnimationController.Instance.MakeTimelineDirty();
|
|
StageCardManager.Instance.Initialize();
|
|
}
|
|
|
|
public void Undo()
|
|
{
|
|
WorkspaceRecord recordToRead = pastSnapshots.Last?.Previous?.Value;
|
|
WorkspaceRecord recordToStore = pastSnapshots.Last?.Value;
|
|
|
|
if (recordToRead == null || recordToStore == null) return;
|
|
|
|
pastSnapshots.RemoveLast();
|
|
futureSnapshots.AddLast(recordToStore);
|
|
|
|
RestoreToHistoricRecord(recordToRead);
|
|
Debug.Log("Undoing : " + recordToStore.eventDesc);
|
|
}
|
|
|
|
public void Redo()
|
|
{
|
|
WorkspaceRecord recordToReadAndStore = futureSnapshots.Last?.Value;
|
|
|
|
if (recordToReadAndStore == null) return;
|
|
|
|
futureSnapshots.RemoveLast();
|
|
pastSnapshots.AddLast(recordToReadAndStore);
|
|
|
|
RestoreToHistoricRecord(recordToReadAndStore);
|
|
Debug.Log("Redoing : " + recordToReadAndStore.eventDesc);
|
|
}
|
|
|
|
public void ClearHistory()
|
|
{
|
|
pastSnapshots.Clear();
|
|
futureSnapshots.Clear();
|
|
}
|
|
|
|
public void RecordEvent(string eventDesc)
|
|
{
|
|
//Debug.Log("Recording event: " + eventDesc + " (record ID: " + pastSnapshots.Count + ")");
|
|
MakeHistoricRecord(eventDesc);
|
|
}
|
|
}
|
|
}
|