Multi-drag and stretching for keyframes

This commit is contained in:
AbstractConcept 2022-10-14 00:52:07 -05:00
parent f449a9b4e2
commit ab5a2a4c02
151 changed files with 195 additions and 85 deletions

View File

@ -242,6 +242,8 @@ MonoBehaviour:
maxGhosts: 50
actorID: 0
keyframeID: 0
linkedSlider: {fileID: 0}
linkedOffset: 0
--- !u!1 &8359461402257861397
GameObject:
m_ObjectHideFlags: 0

View File

@ -8316,7 +8316,7 @@ MonoBehaviour:
actorCard: {fileID: 3804747680621674853}
animationTimelines: {fileID: 1100016168}
actorBodies: {fileID: 1828035561}
stretchkeyframesToggle: {fileID: 462332576}
stretchKeyframesToggle: {fileID: 462332576}
playBackSpeedField: {fileID: 1579799916}
playToggleButton: {fileID: 79733375}
actorBodyPrefab: {fileID: -4411442180840688308, guid: dc4c8b005322f3b46a2f122a55f38db2,
@ -17458,7 +17458,7 @@ MonoBehaviour:
m_TargetGraphic: {fileID: 922060210}
m_HandleRect: {fileID: 922060209}
m_Direction: 2
m_Value: 1
m_Value: 0
m_Size: 1
m_NumberOfSteps: 0
m_OnValueChanged:

View File

@ -34,15 +34,14 @@ namespace RimWorldAnimationStudio
HeadBob.Clear();
GenitalAngle.Clear();
int keyframePosition = 0;
int duration = 0;
keyframes[keyframes.Count - 1].tickDuration = 1;
foreach (PawnKeyframe frame in keyframes)
{ duration += frame.tickDuration; }
int keyframePosition = 0;
keyframes[keyframes.Count - 1].tickDuration = 1;
for (int i = 0; i < keyframes.Count; i++)
{
PawnKeyframe keyframe = keyframes[i];
@ -82,7 +81,7 @@ namespace RimWorldAnimationStudio
quiver.Add(keyframePosition + keyframe.tickDuration - 1, false);
}
keyframe.atTick = keyframePosition + 1;
keyframe.atTick = keyframePosition + Constants.minTick;
keyframePosition += keyframe.tickDuration;
}
}

View File

@ -3,6 +3,7 @@ using System.Linq;
using System.Xml;
using System.Xml.Serialization;
using UnityEngine;
using UnityEngine.UI;
namespace RimWorldAnimationStudio
{
@ -45,5 +46,10 @@ namespace RimWorldAnimationStudio
public bool HasValidKeyframeID()
{ return keyframeID >= 100000 && keyframeID < 1000000; }
public KeyframeSlider GetKeyframeSlider()
{
return Selectable.allSelectablesArray.FirstOrDefault(x => x.GetComponent<KeyframeSlider>()?.keyframeID == keyframeID)?.GetComponent< KeyframeSlider>();
}
}
}

View File

@ -23,9 +23,15 @@ namespace RimWorldAnimationStudio
private PawnAnimationClip clip;
private PawnKeyframe keyframe;
private float dragTimeStart = -1f;
public void Initialize(AnimationTimeline timeline, int actorID, int keyframeID)
private float dragTimeStart = -1f;
private int dragTickStart = -1;
public KeyframeSlider linkedSlider;
public Keyframe pivotKeyframe;
public int linkedOffset;
public void Initialize(AnimationTimeline timeline, int actorID, int keyframeID)
{
this.timeline = timeline;
this.clip = Workspace.Instance.GetPawnAnimationClip(actorID);
@ -48,8 +54,6 @@ namespace RimWorldAnimationStudio
keyframe.atTick = (int)value;
clip.BuildSimpleCurves();
//AnimationController.Instance.stageTick = keyframe.atTick.Value;
timeline.InitiateUpdateOfGhostFrames();
}
@ -99,52 +103,76 @@ namespace RimWorldAnimationStudio
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.LeftCommand))
{ Workspace.keyframeID.Add(keyframeID); }
else
else if (Workspace.keyframeID.NullOrEmpty() || Workspace.keyframeID.Contains(keyframeID) == false)
{ Workspace.keyframeID = new List<int> { keyframeID }; }
if (eventData.clickCount >= 2)
{ AnimationController.Instance.stageTick = keyframe.atTick.Value; }
//Workspace.Instance.RecordEvent("Keyframe selected");
}
public void OnBeginDrag(PointerEventData eventData)
{
//AnimationController.Instance.stageTick = keyframe.atTick.Value;
Workspace.actorID = actorID;
Workspace.keyframeID = new List<int> { keyframeID };
dragTimeStart = Time.unscaledTime;
dragTickStart = keyframe.atTick.Value;
if (Workspace.keyframeID.NullOrEmpty() || Workspace.keyframeID.Contains(keyframeID) == false)
{ Workspace.keyframeID = new List<int> { keyframeID }; }
List<PawnKeyframe> selectedKeyframes = Workspace.Instance.GetPawnKeyframes(Workspace.keyframeID).Except(new List<PawnKeyframe>() { keyframe })?.ToList();
// Link other slected keyframes to the movement of this one
if (selectedKeyframes.NotNullOrEmpty())
{
foreach (PawnKeyframe selectedKeyframe in selectedKeyframes)
{
KeyframeSlider unlinkedSlider = selectedKeyframe.GetKeyframeSlider();
if (unlinkedSlider != null)
{
unlinkedSlider.linkedSlider = this;
unlinkedSlider.linkedOffset = unlinkedSlider.keyframe.atTick.Value - keyframe.atTick.Value;
}
}
pivotKeyframe = keyframe.atTick < selectedKeyframes[0].atTick ? selectedKeyframes.Last() : selectedKeyframes.First();
}
}
public override void OnDrag(PointerEventData eventData)
{
if (keyframe.atTick == 1)
{ value = 1; return; }
Workspace.actorID = actorID;
// The first keyframe can't be moved
if (keyframe.atTick == Constants.minTick)
{ value = Constants.minTick; return; }
// Sticky drag
if (Time.unscaledTime - dragTimeStart < 0.05f) return;
interactable = true;
base.OnDrag(eventData);
// Snap to nearest keyframe (on another timeline)
int targetTick = Workspace.FindClosestKeyFrameAtTick(keyframe.atTick.Value, Mathf.CeilToInt(Workspace.StageWindowSize * 0.01f), actorID);
if (Input.GetKey(KeyCode.LeftShift) && Workspace.Instance.DoesPawnKeyframeExistAtTick(Workspace.stageID, actorID, targetTick) == false)
{ value = (float)targetTick; }
// Prevent frames from being moved to tick 1
if (value == 1)
{ value = 2; }
//AnimationController.Instance.stageTick = keyframe.atTick.Value;
Workspace.actorID = actorID;
// Prevent other frames from being moved to the first keyframe
if (value == Constants.minTick)
{ value = Constants.minTick + 1; }
}
public void OnEndDrag(PointerEventData eventData)
{
if (keyframe.atTick == 1)
{ value = 1; return; }
if (keyframe.atTick == Constants.minTick)
{ value = Constants.minTick; return; }
foreach (Selectable otherSlider in Selectable.allSelectablesArray)
{
if (otherSlider is KeyframeSlider)
{ Debug.Log("unlinked keyframes"); (otherSlider as KeyframeSlider).linkedSlider = null; }
}
interactable = false;
Workspace.Instance.RecordEvent("Keyframe move");
@ -154,25 +182,60 @@ namespace RimWorldAnimationStudio
{
base.Update();
// Update outdated values
if (Workspace.keyframeID.NullOrEmpty() || Workspace.keyframeID.Contains(keyframeID) == false)
{ linkedSlider = null; }
else if (AnimationController.Instance.stretchKeyframesToggle.isOn && linkedSlider != null && linkedSlider.IsPivotKeyframe(keyframe) == false)
{
//int minTick = linkedSlider.pivotKeyframe.atTick.Value + GetIndexAmongstSelectedKeyframes();
//value = Mathf.Clamp(Mathf.CeilToInt(linkedSlider.keyframe.atTick.Value + linkedOffset * linkedSlider.ScaledOffsetFromPivot()), minTick, Workspace.StageWindowSize);
value = Mathf.CeilToInt(linkedSlider.keyframe.atTick.Value + linkedOffset * linkedSlider.ScaledOffsetFromPivot());
}
else if (AnimationController.Instance.stretchKeyframesToggle.isOn == false && linkedSlider != null)
{ value = Mathf.Clamp(linkedSlider.keyframe.atTick.Value + linkedOffset, Constants.minTick + 1, Workspace.StageWindowSize); }
else if (keyframe.atTick.Value != value)
{ value = keyframe.atTick.Value; }
// Update key color
if (keyframe.atTick.HasValue && Workspace.keyframeID.Contains(keyframeID) && AnimationController.Instance.stageTick == keyframe.atTick.Value)
{ handleImage.color = Constants.ColorPurple; }
else if (Workspace.keyframeID.Contains(keyframeID))
{ handleImage.color = Constants.ColorCyan; }
else if (keyframe.atTick.HasValue && AnimationController.Instance.stageTick == keyframe.atTick.Value)
else if (AnimationController.Instance.stageTick == keyframe.atTick.Value)
{ handleImage.color = Constants.ColorPink; }
else
{ handleImage.color = Constants.ColorGrey; }
// Show sound symbol
string soundDef = Workspace.Instance.GetPawnKeyframe(actorID, keyframeID)?.soundEffect;
soundIcon.SetActive(soundDef != null && soundDef != "" && soundDef != "None");
}
if (soundDef != null && soundDef != "" && soundDef != "None")
{ soundIcon.SetActive(true); }
public float ScaledOffsetFromPivot()
{
//if (IsPivotKeyframe(keyframe)) return 1f;
if (dragTickStart == pivotKeyframe.atTick.Value) return 0f;
else
{ soundIcon.SetActive(false); }
return (float)(keyframe.atTick.Value - pivotKeyframe.atTick.Value) / (dragTickStart - pivotKeyframe.atTick.Value);
}
public bool IsPivotKeyframe(Keyframe otherKeyframe)
{
return pivotKeyframe == otherKeyframe;
}
public int GetIndexAmongstSelectedKeyframes()
{
List<PawnKeyframe> selectedKeyframes = Workspace.Instance.GetPawnKeyframes(Workspace.keyframeID).OrderBy(x => x.atTick)?.ToList();
if (selectedKeyframes.NullOrEmpty() || selectedKeyframes.Contains(keyframe) == false) return -1;
return selectedKeyframes.IndexOf(keyframe);
}
}
}

View File

@ -64,7 +64,7 @@ namespace RimWorldAnimationStudio
if (Workspace.stageID != transform.GetSiblingIndex())
{
AnimationController.Instance.stageTick = 1;
AnimationController.Instance.stageTick = Constants.minTick;
Workspace.Instance.RecordEvent("Stage selected");
}

View File

@ -13,7 +13,7 @@ namespace RimWorldAnimationStudio
{
[Header("Animation settings")]
public bool isAnimating = false;
public int stageTick = 1;
public int stageTick = Constants.minTick;
[Header("Object references")]
public Slider stageTimelineSlider;
@ -25,7 +25,7 @@ namespace RimWorldAnimationStudio
public ActorCard actorCard;
public Transform animationTimelines;
public Transform actorBodies;
public Toggle stretchkeyframesToggle;
public Toggle stretchKeyframesToggle;
public InputField playBackSpeedField;
public Button playToggleButton;
@ -34,7 +34,7 @@ namespace RimWorldAnimationStudio
public GameObject animationTimelinePrefab;
// Private timing variables
private int lastStageTick = 1;
private int lastStageTick = Constants.minTick;
private float timeSinceLastUpdate = 0;
private int cycleIndex = 0;
private bool isDirty = true;
@ -57,7 +57,7 @@ namespace RimWorldAnimationStudio
{ Initialize(); }
// Update tick if animating
stageTick = Mathf.Clamp(stageTick, 1, Workspace.StageWindowSize);
stageTick = Mathf.Clamp(stageTick, Constants.minTick, Workspace.StageWindowSize);
if (isAnimating)
{
@ -72,12 +72,12 @@ namespace RimWorldAnimationStudio
if (stageTick > Workspace.StageWindowSize)
{
if (stageLoopDropdown.value == 1)
{ stageTick = 1; }
{ stageTick = Constants.minTick; }
else if (stageLoopDropdown.value >= 2)
{
++cycleIndex;
stageTick = 1;
stageTick = Constants.minTick;
if ((stageLoopDropdown.value == 2 && cycleIndex >= int.Parse(cyclesNormalField.text)) ||
(stageLoopDropdown.value == 3 && cycleIndex >= int.Parse(cyclesFastField.text)))
@ -148,7 +148,7 @@ namespace RimWorldAnimationStudio
bool requiresGenitals = actor.requiredGenitals.Any(x => x == "Penis") || Workspace.animationDef.actors[actorID].isFucking;
float clipPercent = (float)(stageTick % clip.duration) / clip.duration;
if (stageTick == clip.duration) clipPercent = 1f;
if (stageTick > Constants.minTick && stageTick == clip.duration) clipPercent = 1f;
if (Workspace.animationDef.animationStages[Workspace.stageID].isLooping == false)
{ clipPercent = (float)stageTick / clip.duration; }
@ -401,7 +401,7 @@ namespace RimWorldAnimationStudio
if (tickToPasteAt < 1) continue;
if (tickToPasteAt > Workspace.StageWindowSize)
{
if (stretchkeyframesToggle.isOn)
if (stretchKeyframesToggle.isOn)
{ ResizeStageWindowSize(tickToPasteAt); }
else continue;
@ -487,7 +487,7 @@ namespace RimWorldAnimationStudio
if (keyframe == null || clip == null) return;
if (keyframe.atTick == 1 && force == false)
if (keyframe.atTick == Constants.minTick && force == false)
{ Debug.LogWarning("Cannot delete key frame - the first key frame of an animation clip cannot be deleted"); return; }
if (clip.keyframes.Count <= 2 && force == false)
@ -527,7 +527,7 @@ namespace RimWorldAnimationStudio
if (Workspace.animationDef == null) return;
int.TryParse(animationClipTimeField.text, out int newStageTick);
stageTick = Mathf.Clamp(newStageTick, 1, Workspace.StageWindowSize);
stageTick = Mathf.Clamp(newStageTick, Constants.minTick, Workspace.StageWindowSize);
stageTimelineSlider.value = stageTick;
}
@ -540,8 +540,11 @@ namespace RimWorldAnimationStudio
Debug.Log("Resizing animation clip length to " + newStageWindowSize.ToString() + " ticks.");
if (stretchkeyframesToggle.isOn)
{ StretchKeyframes(newStageWindowSize); }
if (stretchKeyframesToggle.isOn)
{
List<PawnKeyframe> keyframes = Workspace.animationDef.animationStages[Workspace.stageID].animationClips.SelectMany(x => x.keyframes)?.ToList();
StretchKeyframes(keyframes, Workspace.StageWindowSize, newStageWindowSize);
}
else
{
@ -566,7 +569,7 @@ namespace RimWorldAnimationStudio
ResizeStageWindowSize(newStageWindowSize);
}
public void StretchKeyframes(int newStageWindowSize)
/*public void StretchKeyframes(int newStageWindowSize)
{
float scale = (float)newStageWindowSize / Workspace.StageWindowSize;
@ -580,6 +583,39 @@ namespace RimWorldAnimationStudio
clip.BuildSimpleCurves();
}
}*/
public void StretchKeyframes(List<PawnKeyframe> keyframesToStretch, int v1, int v2)
{
int v0 = keyframesToStretch.Min(x => x.atTick.Value);
if (v1 == v0)
{ OffsetKeyframes(keyframesToStretch, v1, v2); return; }
float scaleFactor = (float)(v2 - v0) / (v1 - v0);
foreach (PawnKeyframe keyframe in keyframesToStretch)
{
keyframe.atTick = Mathf.RoundToInt(scaleFactor * (keyframe.atTick.Value - v0) + v0);
}
foreach(PawnAnimationClip clip in Workspace.animationDef.animationStages[Workspace.stageID].animationClips)
{ clip.BuildSimpleCurves(); }
}
public void OffsetKeyframes(List<PawnKeyframe> keyframesToOffset, int v1, int v2)
{
float offset = v2 - v1;
foreach (PawnKeyframe keyframe in keyframesToOffset)
{
keyframe.atTick = Mathf.RoundToInt(keyframe.atTick.Value + offset);
Debug.Log(keyframe.atTick);
Workspace.Instance.GetAnimationClipThatOwnsKeyframe(keyframe.keyframeID, out int clipID).BuildSimpleCurves();
}
foreach (PawnAnimationClip clip in Workspace.animationDef.animationStages[Workspace.stageID].animationClips)
{ clip.BuildSimpleCurves(); }
}
public void ResizeStageWindowSize(int newStageWindowSize)

View File

@ -341,19 +341,19 @@ namespace RimWorldAnimationStudio
public void ToPreviousTick()
{
if (Workspace.animationDef == null) return;
AnimationController.Instance.stageTick = Mathf.Clamp(AnimationController.Instance.stageTick - 1, 1, Workspace.StageWindowSize);
AnimationController.Instance.stageTick = Mathf.Clamp(AnimationController.Instance.stageTick - 1, Constants.minTick, Workspace.StageWindowSize);
}
public void ToNextTick()
{
if (Workspace.animationDef == null) return;
AnimationController.Instance.stageTick = Mathf.Clamp(AnimationController.Instance.stageTick + 1, 1, Workspace.StageWindowSize);
AnimationController.Instance.stageTick = Mathf.Clamp(AnimationController.Instance.stageTick + 1, Constants.minTick, Workspace.StageWindowSize);
}
public void ToFirstTick()
{
if (Workspace.animationDef == null) return;
AnimationController.Instance.stageTick = 1;
AnimationController.Instance.stageTick = Constants.minTick;
}
public void ToLastTick()

View File

@ -8,6 +8,7 @@ namespace RimWorldAnimationStudio
public static class Constants
{
public static int defaultAnimationClipLength = 600;
public static int minTick = 1;
public static int minAnimationClipLength = 2;
public static int maxAnimationClipLength = 9999;

View File

@ -37,6 +37,9 @@ namespace RimWorldAnimationStudio
if (animationDef.animationStages[stageID].stageWindowSize < 0)
{ animationDef.animationStages[stageID].stageWindowSize = animationDef.animationStages[stageID].animationClips.Select(x => x.duration).Max(); }
Debug.Log(animationDef.animationStages[stageID].stageWindowSize);
return animationDef.animationStages[stageID].stageWindowSize;
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More