This commit is contained in:
badlogic 2018-09-06 15:45:27 +02:00
commit ab3dcb0823
2 changed files with 91 additions and 66 deletions

View File

@ -34,17 +34,16 @@ using System.Collections.Generic;
namespace Spine { namespace Spine {
public class AnimationState { public class AnimationState {
static readonly Animation EmptyAnimation = new Animation("<empty>", new ExposedList<Timeline>(), 0); static readonly Animation EmptyAnimation = new Animation("<empty>", new ExposedList<Timeline>(), 0);
internal const int Subsequent = 0, First = 1, Dip = 2, DipMix = 3; internal const int Subsequent = 0, First = 1, Hold = 2, HoldMix = 3;
private AnimationStateData data; private AnimationStateData data;
Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>(); private readonly Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
private readonly ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>(); private readonly ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>();
private readonly ExposedList<Event> events = new ExposedList<Event>(); private readonly ExposedList<Event> events = new ExposedList<Event>();
private readonly EventQueue queue; // Initialized by constructor. private readonly EventQueue queue; // Initialized by constructor.
private readonly HashSet<int> propertyIDs = new HashSet<int>(); private readonly HashSet<int> propertyIDs = new HashSet<int>();
private readonly ExposedList<TrackEntry> mixingTo = new ExposedList<TrackEntry>();
private bool animationsChanged; private bool animationsChanged;
private float timeScale = 1; private float timeScale = 1;
@ -119,6 +118,7 @@ namespace Spine {
// End mixing from entries once all have completed. // End mixing from entries once all have completed.
var from = current.mixingFrom; var from = current.mixingFrom;
current.mixingFrom = null; current.mixingFrom = null;
if (from != null) from.mixingTo = null;
while (from != null) { while (from != null) {
queue.End(from); queue.End(from);
from = from.mixingFrom; from = from.mixingFrom;
@ -146,6 +146,7 @@ namespace Spine {
// Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame).
if (from.totalAlpha == 0 || to.mixDuration == 0) { if (from.totalAlpha == 0 || to.mixDuration == 0) {
to.mixingFrom = from.mixingFrom; to.mixingFrom = from.mixingFrom;
if (from.mixingFrom != null) from.mixingFrom.mixingTo = to;
to.interruptAlpha = from.interruptAlpha; to.interruptAlpha = from.interruptAlpha;
queue.End(from); queue.End(from);
} }
@ -187,11 +188,11 @@ namespace Spine {
int timelineCount = current.animation.timelines.Count; int timelineCount = current.animation.timelines.Count;
var timelines = current.animation.timelines; var timelines = current.animation.timelines;
var timelinesItems = timelines.Items; var timelinesItems = timelines.Items;
if (mix == 1 || blend == MixBlend.Add) { if (i == 0 && (mix == 1 || blend == MixBlend.Add)) {
for (int ii = 0; ii < timelineCount; ii++) for (int ii = 0; ii < timelineCount; ii++)
timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In); timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In);
} else { } else {
var timelineData = current.timelineData.Items; var timelineMode = current.timelineMode.Items;
bool firstFrame = current.timelinesRotation.Count == 0; bool firstFrame = current.timelinesRotation.Count == 0;
if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1); if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1);
@ -199,7 +200,7 @@ namespace Spine {
for (int ii = 0; ii < timelineCount; ii++) { for (int ii = 0; ii < timelineCount; ii++) {
Timeline timeline = timelinesItems[ii]; Timeline timeline = timelinesItems[ii];
MixBlend timelineBlend = timelineData[ii] >= AnimationState.Subsequent ? blend : MixBlend.Setup; MixBlend timelineBlend = timelineMode[ii] >= AnimationState.Subsequent ? blend : MixBlend.Setup;
var rotateTimeline = timeline as RotateTimeline; var rotateTimeline = timeline as RotateTimeline;
if (rotateTimeline != null) if (rotateTimeline != null)
ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame); ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);
@ -237,14 +238,14 @@ namespace Spine {
var timelines = from.animation.timelines; var timelines = from.animation.timelines;
int timelineCount = timelines.Count; int timelineCount = timelines.Count;
var timelinesItems = timelines.Items; var timelinesItems = timelines.Items;
float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix); float alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
if (blend == MixBlend.Add) { if (blend == MixBlend.Add) {
for (int i = 0; i < timelineCount; i++) for (int i = 0; i < timelineCount; i++)
(timelinesItems[i]).Apply(skeleton, animationLast, animationTime, eventBuffer, alphaMix, blend, MixDirection.Out); (timelinesItems[i]).Apply(skeleton, animationLast, animationTime, eventBuffer, alphaMix, blend, MixDirection.Out);
} else { } else {
var timelineData = from.timelineData.Items; var timelineMode = from.timelineMode.Items;
var timelineDipMix = from.timelineDipMix.Items; var timelineHoldMix = from.timelineHoldMix.Items;
bool firstFrame = from.timelinesRotation.Count == 0; bool firstFrame = from.timelinesRotation.Count == 0;
if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize
@ -255,7 +256,7 @@ namespace Spine {
Timeline timeline = timelinesItems[i]; Timeline timeline = timelinesItems[i];
MixBlend timelineBlend; MixBlend timelineBlend;
float alpha; float alpha;
switch (timelineData[i]) { switch (timelineMode[i]) {
case AnimationState.Subsequent: case AnimationState.Subsequent:
if (!attachments && timeline is AttachmentTimeline) if (!attachments && timeline is AttachmentTimeline)
continue; continue;
@ -268,14 +269,14 @@ namespace Spine {
timelineBlend = MixBlend.Setup; timelineBlend = MixBlend.Setup;
alpha = alphaMix; alpha = alphaMix;
break; break;
case AnimationState.Dip: case AnimationState.Hold:
timelineBlend = MixBlend.Setup; timelineBlend = MixBlend.Setup;
alpha = alphaDip; alpha = alphaHold;
break; break;
default: default:
timelineBlend = MixBlend.Setup; timelineBlend = MixBlend.Setup;
TrackEntry dipMix = timelineDipMix[i]; TrackEntry holdMix = timelineHoldMix[i];
alpha = alphaDip * Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration); alpha = alphaHold * Math.Max(0, 1 - holdMix.mixTime / holdMix.mixDuration);
break; break;
} }
from.totalAlpha += alpha; from.totalAlpha += alpha;
@ -428,6 +429,7 @@ namespace Spine {
if (from == null) break; if (from == null) break;
queue.End(from); queue.End(from);
entry.mixingFrom = null; entry.mixingFrom = null;
entry.mixingTo = null;
entry = from; entry = from;
} }
@ -444,6 +446,7 @@ namespace Spine {
if (from != null) { if (from != null) {
if (interrupt) queue.Interrupt(from); if (interrupt) queue.Interrupt(from);
current.mixingFrom = from; current.mixingFrom = from;
current.mixingTo = current;
current.mixTime = 0; current.mixTime = 0;
// Store interrupted mix percentage. // Store interrupted mix percentage.
@ -599,6 +602,7 @@ namespace Spine {
entry.trackIndex = trackIndex; entry.trackIndex = trackIndex;
entry.animation = animation; entry.animation = animation;
entry.loop = loop; entry.loop = loop;
entry.holdPrevious = false;
entry.eventThreshold = 0; entry.eventThreshold = 0;
entry.attachmentThreshold = 0; entry.attachmentThreshold = 0;
@ -636,17 +640,71 @@ namespace Spine {
private void AnimationsChanged () { private void AnimationsChanged () {
animationsChanged = false; animationsChanged = false;
var propertyIDs = this.propertyIDs; this.propertyIDs.Clear();
propertyIDs.Clear();
var mixingTo = this.mixingTo;
var tracksItems = tracks.Items; var tracksItems = tracks.Items;
for (int i = 0, n = tracks.Count; i < n; i++) { for (int i = 0, n = tracks.Count; i < n; i++) {
var entry = tracksItems[i]; var entry = tracksItems[i];
if (entry != null && (i == 0 || entry.mixBlend != MixBlend.Add)) entry.SetTimelineData(null, mixingTo, propertyIDs); if (entry == null) continue;
// Move to last entry, then iterate in reverse (the order animations are applied).
while (entry.mixingFrom != null)
entry = entry.mixingFrom;
do {
if (entry.mixingTo == null || entry.mixBlend != MixBlend.Add) SetTimelineModes(entry);
entry = entry.mixingTo;
} while (entry != null);
} }
} }
private void SetTimelineModes (TrackEntry entry) {
var to = entry.mixingTo;
var timelines = entry.animation.timelines.Items;
int timelinesCount = entry.animation.timelines.Count;
var timelineMode = entry.timelineMode.Resize(timelinesCount).Items; //timelineMode.setSize(timelinesCount);
entry.timelineHoldMix.Clear();
var timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; //timelineHoldMix.setSize(timelinesCount);
var propertyIDs = this.propertyIDs;
if (to != null && to.holdPrevious) {
for (int i = 0; i < timelinesCount; i++) {
propertyIDs.Add(timelines[i].PropertyId);
timelineMode[i] = AnimationState.Hold;
}
return;
}
// outer:
for (int i = 0; i < timelinesCount; i++) {
int id = timelines[i].PropertyId;
if (!propertyIDs.Add(id))
timelineMode[i] = AnimationState.Subsequent;
else if (to == null || !HasTimeline(to, id))
timelineMode[i] = AnimationState.First;
else {
for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) {
if (HasTimeline(next, id)) continue;
if (next.mixDuration > 0) {
timelineMode[i] = AnimationState.HoldMix;
timelineHoldMix[i] = next;
goto continue_outer; // continue outer;
}
break;
}
timelineMode[i] = AnimationState.Hold;
}
continue_outer: {}
}
}
static bool HasTimeline (TrackEntry entry, int id) {
var timelines = entry.animation.timelines.Items;
for (int i = 0, n = entry.animation.timelines.Count; i < n; i++)
if (timelines[i].PropertyId == id) return true;
return false;
}
/// <returns>The track entry for the animation currently playing on the track, or null if no animation is currently playing.</returns> /// <returns>The track entry for the animation currently playing on the track, or null if no animation is currently playing.</returns>
public TrackEntry GetCurrent (int trackIndex) { public TrackEntry GetCurrent (int trackIndex) {
return (trackIndex >= tracks.Count) ? null : tracks.Items[trackIndex]; return (trackIndex >= tracks.Count) ? null : tracks.Items[trackIndex];
@ -675,17 +733,17 @@ namespace Spine {
public class TrackEntry : Pool<TrackEntry>.IPoolable { public class TrackEntry : Pool<TrackEntry>.IPoolable {
internal Animation animation; internal Animation animation;
internal TrackEntry next, mixingFrom; internal TrackEntry next, mixingFrom, mixingTo;
internal int trackIndex; internal int trackIndex;
internal bool loop; internal bool loop, holdPrevious;
internal float eventThreshold, attachmentThreshold, drawOrderThreshold; internal float eventThreshold, attachmentThreshold, drawOrderThreshold;
internal float animationStart, animationEnd, animationLast, nextAnimationLast; internal float animationStart, animationEnd, animationLast, nextAnimationLast;
internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f;
internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha;
internal MixBlend mixBlend = MixBlend.Replace; internal MixBlend mixBlend = MixBlend.Replace;
internal readonly ExposedList<int> timelineData = new ExposedList<int>(); internal readonly ExposedList<int> timelineMode = new ExposedList<int>();
internal readonly ExposedList<TrackEntry> timelineDipMix = new ExposedList<TrackEntry>(); internal readonly ExposedList<TrackEntry> timelineHoldMix = new ExposedList<TrackEntry>();
internal readonly ExposedList<float> timelinesRotation = new ExposedList<float>(); internal readonly ExposedList<float> timelinesRotation = new ExposedList<float>();
// IPoolable.Reset() // IPoolable.Reset()
@ -693,8 +751,8 @@ namespace Spine {
next = null; next = null;
mixingFrom = null; mixingFrom = null;
animation = null; animation = null;
timelineData.Clear(); timelineMode.Clear();
timelineDipMix.Clear(); timelineHoldMix.Clear();
timelinesRotation.Clear(); timelinesRotation.Clear();
Start = null; Start = null;
@ -705,47 +763,6 @@ namespace Spine {
Event = null; Event = null;
} }
/// <summary>Sets the timeline data.</summary>
/// <param name="to">May be null.</param>
internal TrackEntry SetTimelineData (TrackEntry to, ExposedList<TrackEntry> mixingToArray, HashSet<int> propertyIDs) {
if (to != null) mixingToArray.Add(to);
var lastEntry = mixingFrom != null ? mixingFrom.SetTimelineData(this, mixingToArray, propertyIDs) : this;
if (to != null) mixingToArray.Pop();
var mixingTo = mixingToArray.Items;
int mixingToLast = mixingToArray.Count - 1;
var timelines = animation.timelines.Items;
int timelinesCount = animation.timelines.Count;
var timelineDataItems = timelineData.Resize(timelinesCount).Items; // timelineData.setSize(timelinesCount);
timelineDipMix.Clear();
var timelineDipMixItems = timelineDipMix.Resize(timelinesCount).Items; //timelineDipMix.setSize(timelinesCount);
// outer:
for (int i = 0; i < timelinesCount; i++) {
int id = timelines[i].PropertyId;
if (!propertyIDs.Add(id)) {
timelineDataItems[i] = AnimationState.Subsequent;
} else if (to == null || !to.HasTimeline(id)) {
timelineDataItems[i] = AnimationState.First;
} else {
for (int ii = mixingToLast; ii >= 0; ii--) {
var entry = mixingTo[ii];
if (!entry.HasTimeline(id)) {
if (entry.mixDuration > 0) {
timelineDataItems[i] = AnimationState.DipMix;
timelineDipMixItems[i] = entry;
goto continue_outer; // continue outer;
}
break;
}
}
timelineDataItems[i] = AnimationState.Dip;
}
continue_outer: {}
}
return lastEntry;
}
bool HasTimeline (int id) { bool HasTimeline (int id) {
var timelines = animation.timelines.Items; var timelines = animation.timelines.Items;
for (int i = 0, n = animation.timelines.Count; i < n; i++) for (int i = 0, n = animation.timelines.Count; i < n; i++)
@ -903,6 +920,15 @@ namespace Spine {
/// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a linked list.</summary> /// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a linked list.</summary>
public TrackEntry MixingFrom { get { return mixingFrom; } } public TrackEntry MixingFrom { get { return mixingFrom; } }
/// <summary>
/// If true, when mixing from the previous animation to this animation, the previous animation is applied as normal instead of being mixed out.
///
/// When mixing between animations that key the same property, if a lower track also keys that property then the value will briefly dip toward the lower track value during the mix. This happens because the first animation mixes from 100% to 0% while the second animation mixes from 0% to 100%. Setting HoldPrevious to true applies the first animation at 100% during the mix so the lower track value is overwritten. Such dipping does not occur on the lowest track which keys the property, only when a higher track also keys the property.
///
/// Snapping will occur if HoldPrevious is true and this animation does not key all the same properties as the previous animation.
/// </summary>
public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } }
public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete;
public event AnimationState.TrackEntryEventDelegate Event; public event AnimationState.TrackEntryEventDelegate Event;
internal void OnStart () { if (Start != null) Start(this); } internal void OnStart () { if (Start != null) Start(this); }

View File

@ -666,7 +666,6 @@ public class AnimationState {
private void animationsChanged () { private void animationsChanged () {
animationsChanged = false; animationsChanged = false;
IntSet propertyIDs = this.propertyIDs;
propertyIDs.clear(2048); propertyIDs.clear(2048);
for (int i = 0, n = tracks.size; i < n; i++) { for (int i = 0, n = tracks.size; i < n; i++) {