[csharp] Porting of major 4.0-beta runtime changes. Remaining Unity assets will be updated in separate commit. See #1796.

This commit is contained in:
Harald Csaszar 2020-10-22 14:40:34 +02:00
parent 3ab0b72033
commit d2529d410b
30 changed files with 1940 additions and 1904 deletions

File diff suppressed because it is too large Load Diff

View File

@ -112,7 +112,7 @@ namespace Spine {
// end of difference // end of difference
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<string> propertyIds = new HashSet<string>();
private bool animationsChanged; private bool animationsChanged;
private float timeScale = 1; private float timeScale = 1;
private int unkeyedState; private int unkeyedState;
@ -244,7 +244,13 @@ namespace Spine {
mix = 0; // Set to setup pose the last time the entry will be applied. mix = 0; // Set to setup pose the last time the entry will be applied.
// Apply current entry. // Apply current entry.
float animationLast = current.animationLast, animationTime = current.AnimationTime; float animationLast = current.animationLast, animationTime = current.AnimationTime, applyTime = animationTime;
ExposedList<Event> applyEvents = events;
if (current.reverse) {
applyTime = current.animation.duration - applyTime;
applyEvents = null;
}
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;
@ -252,9 +258,9 @@ namespace Spine {
for (int ii = 0; ii < timelineCount; ii++) { for (int ii = 0; ii < timelineCount; ii++) {
var timeline = timelinesItems[ii]; var timeline = timelinesItems[ii];
if (timeline is AttachmentTimeline) if (timeline is AttachmentTimeline)
ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, blend, true); ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true);
else else
timeline.Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In); timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, blend, MixDirection.In);
} }
} else { } else {
var timelineMode = current.timelineMode.Items; var timelineMode = current.timelineMode.Items;
@ -268,12 +274,12 @@ namespace Spine {
MixBlend timelineBlend = timelineMode[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, ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, mix, timelineBlend, timelinesRotation,
ii << 1, firstFrame); ii << 1, firstFrame);
else if (timeline is AttachmentTimeline) else if (timeline is AttachmentTimeline)
ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, blend, true); ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true);
else else
timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelineBlend, MixDirection.In); timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.In);
} }
} }
QueueEvents(current, animationTime); QueueEvents(current, animationTime);
@ -314,17 +320,23 @@ namespace Spine {
if (blend != MixBlend.First) blend = from.mixBlend; // Track 0 ignores track mix blend. if (blend != MixBlend.First) blend = from.mixBlend; // Track 0 ignores track mix blend.
} }
var eventBuffer = mix < from.eventThreshold ? this.events : null;
bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold;
float animationLast = from.animationLast, animationTime = from.AnimationTime;
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 alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix); float alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
float animationLast = from.animationLast, animationTime = from.AnimationTime, applyTime = animationTime;
ExposedList<Event> events = null;
if (from.reverse)
applyTime = from.animation.duration - applyTime;
else {
if (mix < from.eventThreshold) events = this.events;
}
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, applyTime, events, alphaMix, blend, MixDirection.Out);
} else { } else {
var timelineMode = from.timelineMode.Items; var timelineMode = from.timelineMode.Items;
var timelineHoldMix = from.timelineHoldMix.Items; var timelineHoldMix = from.timelineHoldMix.Items;
@ -366,14 +378,14 @@ namespace Spine {
from.totalAlpha += alpha; from.totalAlpha += alpha;
var rotateTimeline = timeline as RotateTimeline; var rotateTimeline = timeline as RotateTimeline;
if (rotateTimeline != null) { if (rotateTimeline != null) {
ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation, ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, alpha, timelineBlend, timelinesRotation, i << 1,
i << 1, firstFrame); firstFrame);
} else if (timeline is AttachmentTimeline) { } else if (timeline is AttachmentTimeline) {
ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, timelineBlend, attachments); ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, timelineBlend, attachments);
} else { } else {
if (drawOrder && timeline is DrawOrderTimeline && timelineBlend == MixBlend.Setup) if (drawOrder && timeline is DrawOrderTimeline && timelineBlend == MixBlend.Setup)
direction = MixDirection.In; direction = MixDirection.In;
timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, timelineBlend, direction); timeline.Apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction);
} }
} }
} }
@ -393,7 +405,7 @@ namespace Spine {
private void ApplyAttachmentTimeline (AttachmentTimeline timeline, Skeleton skeleton, float time, MixBlend blend, private void ApplyAttachmentTimeline (AttachmentTimeline timeline, Skeleton skeleton, float time, MixBlend blend,
bool attachments) { bool attachments) {
Slot slot = skeleton.slots.Items[timeline.slotIndex]; Slot slot = skeleton.slots.Items[timeline.SlotIndex];
if (!slot.bone.active) return; if (!slot.bone.active) return;
float[] frames = timeline.frames; float[] frames = timeline.frames;
@ -402,12 +414,7 @@ namespace Spine {
SetAttachment(skeleton, slot, slot.data.attachmentName, attachments); SetAttachment(skeleton, slot, slot.data.attachmentName, attachments);
} }
else { else {
int frameIndex; SetAttachment(skeleton, slot, timeline.AttachmentNames[Animation.Search(frames, time)], attachments);
if (time >= frames[frames.Length - 1]) // Time is after last frame.
frameIndex = frames.Length - 1;
else
frameIndex = Animation.BinarySearch(frames, time) - 1;
SetAttachment(skeleton, slot, timeline.attachmentNames[frameIndex], attachments);
} }
// If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later. // If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later.
@ -432,7 +439,7 @@ namespace Spine {
return; return;
} }
Bone bone = skeleton.bones.Items[timeline.boneIndex]; Bone bone = skeleton.bones.Items[timeline.BoneIndex];
if (!bone.active) return; if (!bone.active) return;
float[] frames = timeline.frames; float[] frames = timeline.frames;
@ -441,7 +448,7 @@ namespace Spine {
switch (blend) { switch (blend) {
case MixBlend.Setup: case MixBlend.Setup:
bone.rotation = bone.data.rotation; bone.rotation = bone.data.rotation;
return; goto default; // Fall through.
default: default:
return; return;
case MixBlend.First: case MixBlend.First:
@ -451,21 +458,7 @@ namespace Spine {
} }
} else { } else {
r1 = blend == MixBlend.Setup ? bone.data.rotation : bone.rotation; r1 = blend == MixBlend.Setup ? bone.data.rotation : bone.rotation;
if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame. r2 = bone.data.rotation + timeline.GetCurveValue(time);
r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION];
else {
// Interpolate between the previous frame and the current frame.
int frame = Animation.BinarySearch(frames, time, RotateTimeline.ENTRIES);
float prevRotation = frames[frame + RotateTimeline.PREV_ROTATION];
float frameTime = frames[frame];
float percent = timeline.GetCurvePercent((frame >> 1) - 1,
1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime));
r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation;
r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360;
r2 = prevRotation + r2 * percent + bone.data.rotation;
r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360;
}
} }
// Mix between rotations using the direction of the shortest route on the first frame. // Mix between rotations using the direction of the shortest route on the first frame.
@ -494,8 +487,7 @@ namespace Spine {
timelinesRotation[i] = total; timelinesRotation[i] = total;
} }
timelinesRotation[i + 1] = diff; timelinesRotation[i + 1] = diff;
r1 += total * alpha; bone.rotation = r1 + total * alpha;
bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360;
} }
private void QueueEvents (TrackEntry entry, float animationTime) { private void QueueEvents (TrackEntry entry, float animationTime) {
@ -577,10 +569,17 @@ namespace Spine {
queue.Drain(); queue.Drain();
} }
/// <summary>
/// Removes the <see cref="TrackEntry.Next">next entry</see> and all entries after it for the specified entry.</summary>
public void ClearNext (TrackEntry entry) {
DisposeNext(entry.next);
}
/// <summary>Sets the active TrackEntry for a given track number.</summary> /// <summary>Sets the active TrackEntry for a given track number.</summary>
private void SetCurrent (int index, TrackEntry current, bool interrupt) { private void SetCurrent (int index, TrackEntry current, bool interrupt) {
TrackEntry from = ExpandToIndex(index); TrackEntry from = ExpandToIndex(index);
tracks.Items[index] = current; tracks.Items[index] = current;
current.previous = null;
if (from != null) { if (from != null) {
if (interrupt) queue.Interrupt(from); if (interrupt) queue.Interrupt(from);
@ -647,7 +646,7 @@ namespace Spine {
/// equivalent to calling <see cref="SetAnimation(int, Animation, bool)"/>.</summary> /// equivalent to calling <see cref="SetAnimation(int, Animation, bool)"/>.</summary>
/// <param name="delay"> /// <param name="delay">
/// If &gt; 0, sets <see cref="TrackEntry.Delay"/>. If &lt;= 0, the delay set is the duration of the previous track entry /// If &gt; 0, sets <see cref="TrackEntry.Delay"/>. If &lt;= 0, the delay set is the duration of the previous track entry
/// minus any mix duration (from the {@link AnimationStateData}) plus the specified <code>Delay</code> (ie the mix /// minus any mix duration (from the <see cref="AnimationStateData"/> plus the specified <code>Delay</code> (ie the mix
/// ends at (<code>Delay</code> = 0) or before (<code>Delay</code> &lt; 0) the previous track entry duration). If the /// ends at (<code>Delay</code> = 0) or before (<code>Delay</code> &lt; 0) the previous track entry duration). If the
/// previous entry is looping, its next loop completion is used instead of its duration. /// previous entry is looping, its next loop completion is used instead of its duration.
/// </param> /// </param>
@ -669,18 +668,8 @@ namespace Spine {
queue.Drain(); queue.Drain();
} else { } else {
last.next = entry; last.next = entry;
if (delay <= 0) { entry.previous = last;
float duration = last.animationEnd - last.animationStart; if (delay <= 0) delay += last.TrackComplete - entry.mixDuration;
if (duration != 0) {
if (last.loop) {
delay += duration * (1 + (int)(last.trackTime / duration)); // Completion of next loop.
} else {
delay += Math.Max(duration, last.trackTime); // After duration, else next update.
}
delay -= data.GetMix(last.animation, animation);
} else
delay = last.trackTime; // Next update.
}
} }
entry.delay = delay; entry.delay = delay;
@ -698,11 +687,11 @@ namespace Spine {
/// 0 still mixes out over one frame.</para> /// 0 still mixes out over one frame.</para>
/// <para> /// <para>
/// Mixing in is done by first setting an empty animation, then adding an animation using /// Mixing in is done by first setting an empty animation, then adding an animation using
/// <see cref="AnimationState.AddAnimation(int, Animation, boolean, float)"/> and on the returned track entry, set the /// <see cref="AnimationState.AddAnimation(int, Animation, bool, float)"/> with the desired delay (an empty animation has a duration of 0) and on
/// <see cref="TrackEntry.SetMixDuration(float)"/>. Mixing from an empty animation causes the new animation to be applied more and /// the returned track entry, set the <see cref="TrackEntry.SetMixDuration(float)"/>. Mixing from an empty animation causes the new
/// more over the mix duration. Properties keyed in the new animation transition from the value from lower tracks or from the /// animation to be applied more and more over the mix duration. Properties keyed in the new animation transition from the value
/// setup pose value if no lower tracks key the property to the value keyed in the new animation.</para> /// from lower tracks or from the setup pose value if no lower tracks key the property to the value keyed in the new
/// </summary> /// animation.</para></summary>
public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) { public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) {
TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false);
entry.mixDuration = mixDuration; entry.mixDuration = mixDuration;
@ -725,10 +714,10 @@ namespace Spine {
/// after the <see cref="AnimationState.Dispose"/> event occurs. /// after the <see cref="AnimationState.Dispose"/> event occurs.
/// </returns> /// </returns>
public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) { public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) {
if (delay <= 0) delay -= mixDuration; TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay <= 0 ? 1 : delay);
TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay);
entry.mixDuration = mixDuration; entry.mixDuration = mixDuration;
entry.trackEnd = mixDuration; entry.trackEnd = mixDuration;
if (delay <= 0 && entry.previous != null) entry.delay = entry.previous.TrackComplete - entry.mixDuration;
return entry; return entry;
} }
@ -738,8 +727,9 @@ namespace Spine {
public void SetEmptyAnimations (float mixDuration) { public void SetEmptyAnimations (float mixDuration) {
bool oldDrainDisabled = queue.drainDisabled; bool oldDrainDisabled = queue.drainDisabled;
queue.drainDisabled = true; queue.drainDisabled = true;
var tracksItems = tracks.Items;
for (int i = 0, n = tracks.Count; i < n; i++) { for (int i = 0, n = tracks.Count; i < n; i++) {
TrackEntry current = tracks.Items[i]; TrackEntry current = tracksItems[i];
if (current != null) SetEmptyAnimation(current.trackIndex, mixDuration); if (current != null) SetEmptyAnimation(current.trackIndex, mixDuration);
} }
queue.drainDisabled = oldDrainDisabled; queue.drainDisabled = oldDrainDisabled;
@ -798,10 +788,10 @@ namespace Spine {
animationsChanged = false; animationsChanged = false;
// Process in the order that animations are applied. // Process in the order that animations are applied.
propertyIDs.Clear(); propertyIds.Clear();
int n = tracks.Count;
var tracksItems = tracks.Items; var tracksItems = tracks.Items;
for (int i = 0, n = tracks.Count; i < n; i++) { for (int i = 0; i < n; i++) {
TrackEntry entry = tracksItems[i]; TrackEntry entry = tracksItems[i];
if (entry == null) continue; if (entry == null) continue;
while (entry.mixingFrom != null) // Move to last entry, then iterate in reverse. while (entry.mixingFrom != null) // Move to last entry, then iterate in reverse.
@ -814,6 +804,8 @@ namespace Spine {
} }
} }
private void ComputeHold (TrackEntry entry) { private void ComputeHold (TrackEntry entry) {
TrackEntry to = entry.mixingTo; TrackEntry to = entry.mixingTo;
var timelines = entry.animation.timelines.Items; var timelines = entry.animation.timelines.Items;
@ -821,11 +813,11 @@ namespace Spine {
var timelineMode = entry.timelineMode.Resize(timelinesCount).Items; //timelineMode.setSize(timelinesCount); var timelineMode = entry.timelineMode.Resize(timelinesCount).Items; //timelineMode.setSize(timelinesCount);
entry.timelineHoldMix.Clear(); entry.timelineHoldMix.Clear();
var timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; //timelineHoldMix.setSize(timelinesCount); var timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; //timelineHoldMix.setSize(timelinesCount);
var propertyIDs = this.propertyIDs; var propertyIds = this.propertyIds;
if (to != null && to.holdPrevious) { if (to != null && to.holdPrevious) {
for (int i = 0; i < timelinesCount; i++) for (int i = 0; i < timelinesCount; i++)
timelineMode[i] = propertyIDs.Add(timelines[i].PropertyId) ? AnimationState.HoldFirst : AnimationState.HoldSubsequent; timelineMode[i] = propertyIds.AddAll(timelines[i].PropertyIds) ? AnimationState.HoldFirst : AnimationState.HoldSubsequent;
return; return;
} }
@ -833,15 +825,15 @@ namespace Spine {
// outer: // outer:
for (int i = 0; i < timelinesCount; i++) { for (int i = 0; i < timelinesCount; i++) {
Timeline timeline = timelines[i]; Timeline timeline = timelines[i];
int id = timeline.PropertyId; String[] ids = timeline.PropertyIds;
if (!propertyIDs.Add(id)) if (!propertyIds.AddAll(ids))
timelineMode[i] = AnimationState.Subsequent; timelineMode[i] = AnimationState.Subsequent;
else if (to == null || timeline is AttachmentTimeline || timeline is DrawOrderTimeline else if (to == null || timeline is AttachmentTimeline || timeline is DrawOrderTimeline
|| timeline is EventTimeline || !to.animation.HasTimeline(id)) { || timeline is EventTimeline || !to.animation.HasTimeline(ids)) {
timelineMode[i] = AnimationState.First; timelineMode[i] = AnimationState.First;
} else { } else {
for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) { for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) {
if (next.animation.HasTimeline(id)) continue; if (next.animation.HasTimeline(ids)) continue;
if (next.mixDuration > 0) { if (next.mixDuration > 0) {
timelineMode[i] = AnimationState.HoldMix; timelineMode[i] = AnimationState.HoldMix;
timelineHoldMix[i] = next; timelineHoldMix[i] = next;
@ -892,8 +884,9 @@ namespace Spine {
override public string ToString () { override public string ToString () {
var buffer = new System.Text.StringBuilder(); var buffer = new System.Text.StringBuilder();
var tracksItems = tracks.Items;
for (int i = 0, n = tracks.Count; i < n; i++) { for (int i = 0, n = tracks.Count; i < n; i++) {
TrackEntry entry = tracks.Items[i]; TrackEntry entry = tracksItems[i];
if (entry == null) continue; if (entry == null) continue;
if (buffer.Length > 0) buffer.Append(", "); if (buffer.Length > 0) buffer.Append(", ");
buffer.Append(entry.ToString()); buffer.Append(entry.ToString());
@ -912,7 +905,7 @@ namespace Spine {
public class TrackEntry : Pool<TrackEntry>.IPoolable { public class TrackEntry : Pool<TrackEntry>.IPoolable {
internal Animation animation; internal Animation animation;
internal TrackEntry next, mixingFrom, mixingTo; internal TrackEntry previous, next, mixingFrom, mixingTo;
// difference to libgdx reference: delegates are used for event callbacks instead of 'AnimationStateListener listener'. // difference to libgdx reference: delegates are used for event callbacks instead of 'AnimationStateListener listener'.
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;
@ -925,7 +918,7 @@ namespace Spine {
internal int trackIndex; internal int trackIndex;
internal bool loop, holdPrevious; internal bool loop, holdPrevious, reverse;
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;
@ -937,6 +930,7 @@ namespace Spine {
// IPoolable.Reset() // IPoolable.Reset()
public void Reset () { public void Reset () {
previous = null;
next = null; next = null;
mixingFrom = null; mixingFrom = null;
mixingTo = null; mixingTo = null;
@ -973,7 +967,10 @@ namespace Spine {
/// track entry <see cref="TrackEntry.TrackTime"/> &gt;= this track entry's <code>Delay</code>).</para> /// track entry <see cref="TrackEntry.TrackTime"/> &gt;= this track entry's <code>Delay</code>).</para>
/// <para> /// <para>
/// <see cref="TrackEntry.TimeScale"/> affects the delay.</para> /// <see cref="TrackEntry.TimeScale"/> affects the delay.</para>
/// </summary> /// <para>
/// When using <see cref="AnimationState.AddAnimation(int, Animation, bool, float)"/> with a <code>delay</code> <= 0, the delay
/// is set using the mix duration from the <see cref="AnimationStateData"/>. If <see cref="mixDuration"/> is set afterward, the delay
/// may need to be adjusted.</summary>
public float Delay { get { return delay; } set { delay = value; } } public float Delay { get { return delay; } set { delay = value; } }
/// <summary> /// <summary>
@ -994,6 +991,21 @@ namespace Spine {
/// </summary> /// </summary>
public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } }
/// <summary>
/// If this track entry is non-looping, the track time in seconds when <see cref="AnimationEnd"/> is reached, or the current
/// <see cref="TrackTime"/> if it has already been reached. If this track entry is looping, the track time when this
/// animation will reach its next <see cref="AnimationEnd"/> (the next loop completion).</summary>
public float TrackComplete {
get {
float duration = animationEnd - animationStart;
if (duration != 0) {
if (loop) return duration * (1 + (int)(trackTime / duration)); // Completion of next loop.
if (trackTime < duration) return duration; // Before duration.
}
return trackTime; // Next update.
}
}
/// <summary> /// <summary>
/// <para> /// <para>
/// Seconds when this animation starts, both initially and after looping. Defaults to 0.</para> /// Seconds when this animation starts, both initially and after looping. Defaults to 0.</para>
@ -1043,11 +1055,13 @@ namespace Spine {
/// Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or /// Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or
/// faster. Defaults to 1.</para> /// faster. Defaults to 1.</para>
/// <para> /// <para>
/// Values < 0 are not supported. To play an animation in reverse, use <see cref="Reverse"/>.
/// <para>
/// <see cref="TrackEntry.MixTime"/> is not affected by track entry time scale, so <see cref="TrackEntry.MixDuration"/> may need to be adjusted to /// <see cref="TrackEntry.MixTime"/> is not affected by track entry time scale, so <see cref="TrackEntry.MixDuration"/> may need to be adjusted to
/// match the animation speed.</para> /// match the animation speed.</para>
/// <para> /// <para>
/// When using <see cref="AnimationState.AddAnimation(int, Animation, boolean, float)"> with a <code>Delay</code> <= 0, note the /// When using <see cref="AnimationState.AddAnimation(int, Animation, bool, float)"> with a <code>Delay</code> <= 0, the
/// {<see cref="TrackEntry.Delay"/> is set using the mix duration from the <see cref="AnimationStateData"/>, assuming time scale to be 1. If /// <see cref="TrackEntry.Delay"/> is set using the mix duration from the <see cref="AnimationStateData"/>, assuming time scale to be 1. If
/// the time scale is not 1, the delay may need to be adjusted.</para> /// the time scale is not 1, the delay may need to be adjusted.</para>
/// <para> /// <para>
/// See AnimationState <see cref="AnimationState.TimeScale"/> for affecting all animations.</para> /// See AnimationState <see cref="AnimationState.TimeScale"/> for affecting all animations.</para>
@ -1086,9 +1100,16 @@ namespace Spine {
public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } }
/// <summary> /// <summary>
/// The animation queued to start after this animation, or null. <code>Next</code> makes up a linked list. </summary> /// The animation queued to start after this animation, or null if there is none. <code>next</code> makes up a doubly linked
/// list.
/// <para>
/// See <see cref="AnimationState.ClearNext(TrackEntry)"/> to truncate the list.</para></summary>
public TrackEntry Next { get { return next; } } public TrackEntry Next { get { return next; } }
/// <summary>
/// The animation queued to play before this animation, or null. <code>previous</code> makes up a doubly linked list.</summary>
public TrackEntry Previous { get { return previous; } }
/// <summary> /// <summary>
/// Returns true if at least one loop has been completed.</summary> /// Returns true if at least one loop has been completed.</summary>
/// <seealso cref="TrackEntry.Complete"/> /// <seealso cref="TrackEntry.Complete"/>
@ -1108,20 +1129,21 @@ namespace Spine {
/// <para> /// <para>
/// The <code>MixDuration</code> can be set manually rather than use the value from /// The <code>MixDuration</code> can be set manually rather than use the value from
/// <see cref="AnimationStateData.GetMix(Animation, Animation)"/>. In that case, the <code>MixDuration</code> can be set for a new /// <see cref="AnimationStateData.GetMix(Animation, Animation)"/>. In that case, the <code>MixDuration</code> can be set for a new
/// track entry only before <see cref="AnimationState.Update(float)"/> is first called.</para> /// track entry only before <see cref="AnimationState.Update(float)"/> is first called.</para>
/// <para> /// <para>
/// When using <seealso cref="AnimationState.AddAnimation(int, Animation, bool, float)"/> with a <code>Delay</code> &lt;= 0, note the /// When using <seealso cref="AnimationState.AddAnimation(int, Animation, bool, float)"/> with a <code>Delay</code> &lt;= 0, the
/// <see cref="TrackEntry.Delay"/> is set using the mix duration from the <see cref=" AnimationStateData"/>, not a mix duration set /// <see cref="TrackEntry.Delay"/> is set using the mix duration from the <see cref=" AnimationStateData"/>. If <code>mixDuration</code> is set
/// afterward.</para> /// afterward, the delay may need to be adjusted. For example:
/// </summary> /// <code>entry.Delay = entry.previous.TrackComplete - entry.MixDuration;</code>
/// </para></summary>
public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } public float MixDuration { get { return mixDuration; } set { mixDuration = value; } }
/// <summary> /// <summary>
/// <para> /// <para>
/// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to <see cref="MixBlend.Replace"/>, which /// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to <see cref="MixBlend.Replace"/>.
/// replaces the values from the lower tracks with the animation values. <see cref="MixBlend.Add"/> adds the animation values to /// </para><para>
/// the values from the lower tracks.</para> /// Track entries on track 0 ignore this setting and always use <see cref="MixBlend.First"/>.
/// <para> /// </para><para>
/// The <code>MixBlend</code> can be set for a new track entry only before <see cref="AnimationState.Apply(Skeleton)"/> is first /// The <code>MixBlend</code> can be set for a new track entry only before <see cref="AnimationState.Apply(Skeleton)"/> is first
/// called.</para> /// called.</para>
/// </summary> /// </summary>
@ -1153,6 +1175,10 @@ namespace Spine {
/// </summary> /// </summary>
public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } } public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } }
/// <summary>
/// If true, the animation will be applied in reverse. Events are not fired when an animation is applied in reverse.</summary>
public bool Reverse { get { return reverse; } set { reverse = value; } }
/// <summary> /// <summary>
/// <para> /// <para>
/// Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the /// Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the
@ -1331,4 +1357,14 @@ namespace Spine {
} }
} }
public static class HashSetExtensions {
public static bool AddAll<T> (this HashSet<T> set, T[] addSet) {
bool anyItemAdded = false;
for (int i = 0, n = addSet.Length; i < n; ++i) {
var item = addSet[i];
anyItemAdded |= set.Add(item);
}
return anyItemAdded;
}
}
} }

View File

@ -39,7 +39,7 @@ namespace Spine {
private Atlas[] atlasArray; private Atlas[] atlasArray;
public AtlasAttachmentLoader (params Atlas[] atlasArray) { public AtlasAttachmentLoader (params Atlas[] atlasArray) {
if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); if (atlasArray == null) throw new ArgumentNullException("atlas", "atlas array cannot be null.");
this.atlasArray = atlasArray; this.atlasArray = atlasArray;
} }

View File

@ -56,7 +56,7 @@ namespace Spine {
deformAttachment = this; deformAttachment = this;
lock (VertexAttachment.nextIdLock) { lock (VertexAttachment.nextIdLock) {
id = (VertexAttachment.nextID++ & 65535) << 11; id = VertexAttachment.nextID++;
} }
} }

View File

@ -116,6 +116,7 @@ namespace Spine {
/// <summary>Returns the magnitide (always positive) of the world scale Y.</summary> /// <summary>Returns the magnitide (always positive) of the world scale Y.</summary>
public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } }
/// <summary>Copy constructor. Does not copy the <see cref="Children"/> bones.</summary>
/// <param name="parent">May be null.</param> /// <param name="parent">May be null.</param>
public Bone (BoneData data, Skeleton skeleton, Bone parent) { public Bone (BoneData data, Skeleton skeleton, Bone parent) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null."); if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
@ -305,10 +306,10 @@ namespace Spine {
public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) { public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) {
float a = this.a, b = this.b, c = this.c, d = this.d; float a = this.a, b = this.b, c = this.c, d = this.d;
float invDet = 1 / (a * d - b * c); float det = a * d - b * c;
float x = worldX - this.worldX, y = worldY - this.worldY; float x = worldX - this.worldX, y = worldY - this.worldY;
localX = (x * d * invDet - y * b * invDet); localX = (x * d - y * b) / det;
localY = (y * a * invDet - x * c * invDet); localY = (y * a - x * c) / det;
} }
public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) { public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) {

View File

@ -79,20 +79,16 @@ namespace Spine {
stretch = constraint.stretch; stretch = constraint.stretch;
} }
/// <summary>Applies the constraint to the constrained bones.</summary>
public void Apply () {
Update();
}
public void Update () { public void Update () {
if (mix == 0) return;
Bone target = this.target; Bone target = this.target;
ExposedList<Bone> bones = this.bones; var bones = this.bones.Items;
switch (bones.Count) { switch (this.bones.Count) {
case 1: case 1:
Apply(bones.Items[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix); Apply(bones[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix);
break; break;
case 2: case 2:
Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, stretch, softness, mix); Apply(bones[0], bones[1], target.worldX, target.worldY, bendDirection, stretch, softness, mix);
break; break;
} }
} }
@ -157,6 +153,7 @@ namespace Spine {
/// <summary>Applies 1 bone IK. The target is specified in the world coordinate system.</summary> /// <summary>Applies 1 bone IK. The target is specified in the world coordinate system.</summary>
static public void Apply (Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, static public void Apply (Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform,
float alpha) { float alpha) {
if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
if (!bone.appliedValid) bone.UpdateAppliedTransform(); if (!bone.appliedValid) bone.UpdateAppliedTransform();
Bone p = bone.parent; Bone p = bone.parent;
@ -175,16 +172,16 @@ namespace Spine {
float sc = pc / bone.skeleton.ScaleY; float sc = pc / bone.skeleton.ScaleY;
pb = -sc * s * bone.skeleton.ScaleX; pb = -sc * s * bone.skeleton.ScaleX;
pd = sa * s * bone.skeleton.ScaleY; pd = sa * s * bone.skeleton.ScaleY;
rotationIK += (float)Math.Atan2(pc, pa) * MathUtils.RadDeg; rotationIK += (float)Math.Atan2(sc, sa) * MathUtils.RadDeg;
goto default; // Fall through. goto default; // Fall through.
} }
default: { default: {
float x = targetX - p.worldX, y = targetY - p.worldY; float x = targetX - p.worldX, y = targetY - p.worldY;
float d = pa * pd - pb * pc; float d = pa * pd - pb * pc;
tx = (x * pd - y * pb) / d - bone.ax; tx = (x * pd - y * pb) / d - bone.ax;
ty = (y * pa - x * pc) / d - bone.ay; ty = (y * pa - x * pc) / d - bone.ay;
break; break;
} }
} }
rotationIK += (float)Math.Atan2(ty, tx) * MathUtils.RadDeg; rotationIK += (float)Math.Atan2(ty, tx) * MathUtils.RadDeg;
@ -198,13 +195,10 @@ namespace Spine {
if (compress || stretch) { if (compress || stretch) {
switch (bone.data.transformMode) { switch (bone.data.transformMode) {
case TransformMode.NoScale: case TransformMode.NoScale:
tx = targetX - bone.worldX; case TransformMode.NoScaleOrReflection:
ty = targetY - bone.worldY;
break;
case TransformMode.NoScaleOrReflection:
tx = targetX - bone.worldX; tx = targetX - bone.worldX;
ty = targetY - bone.worldY; ty = targetY - bone.worldY;
break; break;
} }
float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty); float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty);
if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) { if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) {
@ -220,10 +214,8 @@ namespace Spine {
/// <param name="child">A direct descendant of the parent bone.</param> /// <param name="child">A direct descendant of the parent bone.</param>
static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, float softness, static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, float softness,
float alpha) { float alpha) {
if (alpha == 0) { if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null.");
child.UpdateWorldTransform(); if (child == null) throw new ArgumentNullException("child", "child cannot be null.");
return;
}
if (!parent.appliedValid) parent.UpdateAppliedTransform(); if (!parent.appliedValid) parent.UpdateAppliedTransform();
if (!child.appliedValid) child.UpdateAppliedTransform(); if (!child.appliedValid) child.UpdateAppliedTransform();
float px = parent.ax, py = parent.ay, psx = parent.ascaleX, sx = psx, psy = parent.ascaleY, csx = child.ascaleX; float px = parent.ax, py = parent.ay, psx = parent.ascaleX, sx = psx, psy = parent.ascaleY, csx = child.ascaleX;

View File

@ -34,7 +34,7 @@ namespace Spine {
/// <summary> /// <summary>
/// <para> /// <para>
/// Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the /// Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the
/// constrained bones so they follow a {@link PathAttachment}.</para> /// constrained bones so they follow a <see cref="PathAttachment"/>.</para>
/// <para> /// <para>
/// See <a href="http://esotericsoftware.com/spine-path-constraints">Path constraints</a> in the Spine User Guide.</para> /// See <a href="http://esotericsoftware.com/spine-path-constraints">Path constraints</a> in the Spine User Guide.</para>
/// </summary> /// </summary>
@ -82,11 +82,6 @@ namespace Spine {
translateMix = constraint.translateMix; translateMix = constraint.translateMix;
} }
/// <summary>Applies the constraint to the constrained bones.</summary>
public void Apply () {
Update();
}
public void Update () { public void Update () {
PathAttachment attachment = target.Attachment as PathAttachment; PathAttachment attachment = target.Attachment as PathAttachment;
if (attachment == null) return; if (attachment == null) return;

View File

@ -40,7 +40,6 @@ namespace Spine {
internal ExposedList<TransformConstraint> transformConstraints; internal ExposedList<TransformConstraint> transformConstraints;
internal ExposedList<PathConstraint> pathConstraints; internal ExposedList<PathConstraint> pathConstraints;
internal ExposedList<IUpdatable> updateCache = new ExposedList<IUpdatable>(); internal ExposedList<IUpdatable> updateCache = new ExposedList<IUpdatable>();
internal ExposedList<Bone> updateCacheReset = new ExposedList<Bone>();
internal Skin skin; internal Skin skin;
internal float r = 1, g = 1, b = 1, a = 1; internal float r = 1, g = 1, b = 1, a = 1;
internal float time; internal float time;
@ -55,7 +54,13 @@ namespace Spine {
public ExposedList<IkConstraint> IkConstraints { get { return ikConstraints; } } public ExposedList<IkConstraint> IkConstraints { get { return ikConstraints; } }
public ExposedList<PathConstraint> PathConstraints { get { return pathConstraints; } } public ExposedList<PathConstraint> PathConstraints { get { return pathConstraints; } }
public ExposedList<TransformConstraint> TransformConstraints { get { return transformConstraints; } } public ExposedList<TransformConstraint> TransformConstraints { get { return transformConstraints; } }
public Skin Skin { get { return skin; } set { SetSkin(value); } }
public Skin Skin {
/// <summary>The skeleton's current skin. May be null.</summary>
get { return skin; }
/// <summary>Sets a skin, <see cref="SetSkin(Skin)"/>.</summary>
set { SetSkin(value); }
}
public float R { get { return r; } set { r = value; } } public float R { get { return r; } set { r = value; } }
public float G { get { return g; } set { g = value; } } public float G { get { return g; } set { g = value; } }
public float B { get { return b; } set { b = value; } } public float B { get { return b; } set { b = value; } }
@ -72,6 +77,7 @@ namespace Spine {
[Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")] [Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")]
public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } } public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } }
/// <summary>Returns the root bone, or null if the skeleton has no bones.</summary>
public Bone RootBone { public Bone RootBone {
get { return bones.Count == 0 ? null : bones.Items[0]; } get { return bones.Count == 0 ? null : bones.Items[0]; }
} }
@ -81,22 +87,23 @@ namespace Spine {
this.data = data; this.data = data;
bones = new ExposedList<Bone>(data.bones.Count); bones = new ExposedList<Bone>(data.bones.Count);
var bonesItems = this.bones.Items;
foreach (BoneData boneData in data.bones) { foreach (BoneData boneData in data.bones) {
Bone bone; Bone bone;
if (boneData.parent == null) { if (boneData.parent == null) {
bone = new Bone(boneData, this, null); bone = new Bone(boneData, this, null);
} else { } else {
Bone parent = bones.Items[boneData.parent.index]; Bone parent = bonesItems[boneData.parent.index];
bone = new Bone(boneData, this, parent); bone = new Bone(boneData, this, parent);
parent.children.Add(bone); parent.children.Add(bone);
} }
bones.Add(bone); this.bones.Add(bone);
} }
slots = new ExposedList<Slot>(data.slots.Count); slots = new ExposedList<Slot>(data.slots.Count);
drawOrder = new ExposedList<Slot>(data.slots.Count); drawOrder = new ExposedList<Slot>(data.slots.Count);
foreach (SlotData slotData in data.slots) { foreach (SlotData slotData in data.slots) {
Bone bone = bones.Items[slotData.boneData.index]; Bone bone = bonesItems[slotData.boneData.index];
Slot slot = new Slot(slotData, bone); Slot slot = new Slot(slotData, bone);
slots.Add(slot); slots.Add(slot);
drawOrder.Add(slot); drawOrder.Add(slot);
@ -115,7 +122,7 @@ namespace Spine {
pathConstraints.Add(new PathConstraint(pathConstraintData, this)); pathConstraints.Add(new PathConstraint(pathConstraintData, this));
UpdateCache(); UpdateCache();
UpdateWorldTransform(); //UpdateWorldTransform();
} }
/// <summary>Caches information about bones and constraints. Must be called if the <see cref="Skin"/> is modified or if bones, constraints, or /// <summary>Caches information about bones and constraints. Must be called if the <see cref="Skin"/> is modified or if bones, constraints, or
@ -123,7 +130,6 @@ namespace Spine {
public void UpdateCache () { public void UpdateCache () {
var updateCache = this.updateCache; var updateCache = this.updateCache;
updateCache.Clear(); updateCache.Clear();
this.updateCacheReset.Clear();
int boneCount = this.bones.Items.Length; int boneCount = this.bones.Items.Length;
var bones = this.bones; var bones = this.bones;
@ -191,16 +197,19 @@ namespace Spine {
Bone parent = constrained.Items[0]; Bone parent = constrained.Items[0];
SortBone(parent); SortBone(parent);
if (constrained.Count > 1) { if (constrained.Count == 1) {
Bone child = constrained.Items[constrained.Count - 1]; updateCache.Add(constraint);
if (!updateCache.Contains(child)) SortReset(parent.children);
updateCacheReset.Add(child);
} }
else {
Bone child = constrained.Items[constrained.Count - 1];
SortBone(child);
updateCache.Add(constraint); updateCache.Add(constraint);
SortReset(parent.children); SortReset(parent.children);
constrained.Items[constrained.Count - 1].sorted = true; child.sorted = true;
}
} }
private void SortPathConstraint (PathConstraint constraint) { private void SortPathConstraint (PathConstraint constraint) {
@ -218,17 +227,17 @@ namespace Spine {
Attachment attachment = slot.attachment; Attachment attachment = slot.attachment;
if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone);
var constrained = constraint.bones; var constrained = constraint.bones.Items;
int boneCount = constrained.Count; int boneCount = constraint.bones.Count;
for (int i = 0; i < boneCount; i++) for (int i = 0; i < boneCount; i++)
SortBone(constrained.Items[i]); SortBone(constrained[i]);
updateCache.Add(constraint); updateCache.Add(constraint);
for (int i = 0; i < boneCount; i++) for (int i = 0; i < boneCount; i++)
SortReset(constrained.Items[i].children); SortReset(constrained[i].children);
for (int i = 0; i < boneCount; i++) for (int i = 0; i < boneCount; i++)
constrained.Items[i].sorted = true; constrained[i].sorted = true;
} }
private void SortTransformConstraint (TransformConstraint constraint) { private void SortTransformConstraint (TransformConstraint constraint) {
@ -238,25 +247,25 @@ namespace Spine {
SortBone(constraint.target); SortBone(constraint.target);
var constrained = constraint.bones; var constrained = constraint.bones.Items;
int boneCount = constrained.Count; int boneCount = constraint.bones.Count;
if (constraint.data.local) { if (constraint.data.local) {
for (int i = 0; i < boneCount; i++) { for (int i = 0; i < boneCount; i++) {
Bone child = constrained.Items[i]; Bone child = constrained[i];
SortBone(child.parent); SortBone(child.parent);
if (!updateCache.Contains(child)) updateCacheReset.Add(child); SortBone(child);
} }
} else { } else {
for (int i = 0; i < boneCount; i++) for (int i = 0; i < boneCount; i++)
SortBone(constrained.Items[i]); SortBone(constrained[i]);
} }
updateCache.Add(constraint); updateCache.Add(constraint);
for (int i = 0; i < boneCount; i++) for (int i = 0; i < boneCount; i++)
SortReset(constrained.Items[i].children); SortReset(constrained[i].children);
for (int i = 0; i < boneCount; i++) for (int i = 0; i < boneCount; i++)
constrained.Items[i].sorted = true; constrained[i].sorted = true;
} }
private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
@ -271,12 +280,12 @@ namespace Spine {
if (pathBones == null) if (pathBones == null)
SortBone(slotBone); SortBone(slotBone);
else { else {
var bones = this.bones; var bones = this.bones.Items;
for (int i = 0, n = pathBones.Length; i < n;) { for (int i = 0, n = pathBones.Length; i < n;) {
int nn = pathBones[i++]; int nn = pathBones[i++];
nn += i; nn += i;
while (i < nn) while (i < nn)
SortBone(bones.Items[pathBones[i++]]); SortBone(bones[pathBones[i++]]);
} }
} }
} }
@ -299,24 +308,17 @@ namespace Spine {
} }
} }
/// <summary>Updates the world transform for each bone and applies constraints.</summary>
/// <summary>
/// Updates the world transform for each bone and applies all constraints.
/// <para>
/// See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
/// Runtimes Guide.</para>
/// </summary>
public void UpdateWorldTransform () { public void UpdateWorldTransform () {
var updateCacheReset = this.updateCacheReset; var updateCache = this.updateCache.Items;
var updateCacheResetItems = updateCacheReset.Items; for (int i = 0, n = this.updateCache.Count; i < n; i++)
for (int i = 0, n = updateCacheReset.Count; i < n; i++) { updateCache[i].Update();
Bone bone = updateCacheResetItems[i];
bone.ax = bone.x;
bone.ay = bone.y;
bone.arotation = bone.rotation;
bone.ascaleX = bone.scaleX;
bone.ascaleY = bone.scaleY;
bone.ashearX = bone.shearX;
bone.ashearY = bone.shearY;
bone.appliedValid = true;
}
var updateItems = this.updateCache.Items;
for (int i = 0, n = updateCache.Count; i < n; i++)
updateItems[i].Update();
} }
/// <summary> /// <summary>
@ -324,22 +326,7 @@ namespace Spine {
/// all constraints. /// all constraints.
/// </summary> /// </summary>
public void UpdateWorldTransform (Bone parent) { public void UpdateWorldTransform (Bone parent) {
// This partial update avoids computing the world transform for constrained bones when 1) the bone is not updated if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null.");
// before the constraint, 2) the constraint only needs to access the applied local transform, and 3) the constraint calls
// updateWorldTransform.
var updateCacheReset = this.updateCacheReset;
var updateCacheResetItems = updateCacheReset.Items;
for (int i = 0, n = updateCacheReset.Count; i < n; i++) {
Bone bone = updateCacheResetItems[i];
bone.ax = bone.x;
bone.ay = bone.y;
bone.arotation = bone.rotation;
bone.ascaleX = bone.scaleX;
bone.ascaleY = bone.scaleY;
bone.ashearX = bone.shearX;
bone.ashearY = bone.shearY;
bone.appliedValid = true;
}
// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection. // Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
Bone rootBone = this.RootBone; Bone rootBone = this.RootBone;
@ -358,10 +345,9 @@ namespace Spine {
rootBone.d = (pc * lb + pd * ld) * scaleY; rootBone.d = (pc * lb + pd * ld) * scaleY;
// Update everything except root bone. // Update everything except root bone.
var updateCache = this.updateCache; var updateCache = this.updateCache.Items;
var updateCacheItems = updateCache.Items; for (int i = 0, n = this.updateCache.Count; i < n; i++) {
for (int i = 0, n = updateCache.Count; i < n; i++) { var updatable = updateCache[i];
var updatable = updateCacheItems[i];
if (updatable != rootBone) if (updatable != rootBone)
updatable.Update(); updatable.Update();
} }
@ -375,13 +361,13 @@ namespace Spine {
/// <summary>Sets the bones and constraints to their setup pose values.</summary> /// <summary>Sets the bones and constraints to their setup pose values.</summary>
public void SetBonesToSetupPose () { public void SetBonesToSetupPose () {
var bonesItems = this.bones.Items; var bones = this.bones.Items;
for (int i = 0, n = bones.Count; i < n; i++) for (int i = 0, n = this.bones.Count; i < n; i++)
bonesItems[i].SetToSetupPose(); bones[i].SetToSetupPose();
var ikConstraintsItems = this.ikConstraints.Items; var ikConstraints = this.ikConstraints.Items;
for (int i = 0, n = ikConstraints.Count; i < n; i++) { for (int i = 0, n = this.ikConstraints.Count; i < n; i++) {
IkConstraint constraint = ikConstraintsItems[i]; IkConstraint constraint = ikConstraints[i];
constraint.mix = constraint.data.mix; constraint.mix = constraint.data.mix;
constraint.softness = constraint.data.softness; constraint.softness = constraint.data.softness;
constraint.bendDirection = constraint.data.bendDirection; constraint.bendDirection = constraint.data.bendDirection;
@ -389,9 +375,9 @@ namespace Spine {
constraint.stretch = constraint.data.stretch; constraint.stretch = constraint.data.stretch;
} }
var transformConstraintsItems = this.transformConstraints.Items; var transformConstraints = this.transformConstraints.Items;
for (int i = 0, n = transformConstraints.Count; i < n; i++) { for (int i = 0, n = this.transformConstraints.Count; i < n; i++) {
TransformConstraint constraint = transformConstraintsItems[i]; TransformConstraint constraint = transformConstraints[i];
TransformConstraintData constraintData = constraint.data; TransformConstraintData constraintData = constraint.data;
constraint.rotateMix = constraintData.rotateMix; constraint.rotateMix = constraintData.rotateMix;
constraint.translateMix = constraintData.translateMix; constraint.translateMix = constraintData.translateMix;
@ -399,9 +385,9 @@ namespace Spine {
constraint.shearMix = constraintData.shearMix; constraint.shearMix = constraintData.shearMix;
} }
var pathConstraintItems = this.pathConstraints.Items; var pathConstraints = this.pathConstraints.Items;
for (int i = 0, n = pathConstraints.Count; i < n; i++) { for (int i = 0, n = this.pathConstraints.Count; i < n; i++) {
PathConstraint constraint = pathConstraintItems[i]; PathConstraint constraint = pathConstraints[i];
PathConstraintData constraintData = constraint.data; PathConstraintData constraintData = constraint.data;
constraint.position = constraintData.position; constraint.position = constraintData.position;
constraint.spacing = constraintData.spacing; constraint.spacing = constraintData.spacing;
@ -411,23 +397,21 @@ namespace Spine {
} }
public void SetSlotsToSetupPose () { public void SetSlotsToSetupPose () {
var slots = this.slots; var slots = this.slots.Items;
var slotsItems = slots.Items; int n = this.slots.Count;
drawOrder.Clear(); Array.Copy(slots, 0, drawOrder.Items, 0, n);
for (int i = 0, n = slots.Count; i < n; i++) for (int i = 0; i < n; i++)
drawOrder.Add(slotsItems[i]); slots[i].SetToSetupPose();
for (int i = 0, n = slots.Count; i < n; i++)
slotsItems[i].SetToSetupPose();
} }
/// <summary>Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it
/// repeatedly.</summary>
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public Bone FindBone (string boneName) { public Bone FindBone (string boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
var bones = this.bones; var bones = this.bones.Items;
var bonesItems = bones.Items; for (int i = 0, n = this.bones.Count; i < n; i++) {
for (int i = 0, n = bones.Count; i < n; i++) { Bone bone = bones[i];
Bone bone = bonesItems[i];
if (bone.data.name == boneName) return bone; if (bone.data.name == boneName) return bone;
} }
return null; return null;
@ -443,13 +427,14 @@ namespace Spine {
return -1; return -1;
} }
/// <summary>Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it
/// repeatedly.</summary>
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public Slot FindSlot (string slotName) { public Slot FindSlot (string slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
var slots = this.slots; var slots = this.slots.Items;
var slotsItems = slots.Items; for (int i = 0, n = this.slots.Count; i < n; i++) {
for (int i = 0, n = slots.Count; i < n; i++) { Slot slot = slots[i];
Slot slot = slotsItems[i];
if (slot.data.name == slotName) return slot; if (slot.data.name == slotName) return slot;
} }
return null; return null;
@ -461,11 +446,11 @@ namespace Spine {
var slots = this.slots; var slots = this.slots;
var slotsItems = slots.Items; var slotsItems = slots.Items;
for (int i = 0, n = slots.Count; i < n; i++) for (int i = 0, n = slots.Count; i < n; i++)
if (slotsItems[i].data.name.Equals(slotName)) return i; if (slotsItems[i].data.name == slotName) return i;
return -1; return -1;
} }
/// <summary>Sets a skin by name (see SetSkin).</summary> /// <summary>Sets a skin by name (<see cref="SetSkin(Skin)"/>).</summary>
public void SetSkin (string skinName) { public void SetSkin (string skinName) {
Skin foundSkin = data.FindSkin(skinName); Skin foundSkin = data.FindSkin(skinName);
if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName");
@ -506,7 +491,7 @@ namespace Spine {
UpdateCache(); UpdateCache();
} }
/// <summary>Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot name and attachment name.</summary> /// <summary>Finds an attachment by looking in the <see cref="Skeleton.Skin"/> and <see cref="SkeletonData.DefaultSkin"/> using the slot name and attachment name.</summary>
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public Attachment GetAttachment (string slotName, string attachmentName) { public Attachment GetAttachment (string slotName, string attachmentName) {
return GetAttachment(data.FindSlotIndex(slotName), attachmentName); return GetAttachment(data.FindSlotIndex(slotName), attachmentName);
@ -543,34 +528,40 @@ namespace Spine {
throw new Exception("Slot not found: " + slotName); throw new Exception("Slot not found: " + slotName);
} }
/// <summary>Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method
/// than to call it repeatedly.</summary>
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public IkConstraint FindIkConstraint (string constraintName) { public IkConstraint FindIkConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<IkConstraint> ikConstraints = this.ikConstraints; var ikConstraints = this.ikConstraints.Items;
for (int i = 0, n = ikConstraints.Count; i < n; i++) { for (int i = 0, n = this.ikConstraints.Count; i < n; i++) {
IkConstraint ikConstraint = ikConstraints.Items[i]; IkConstraint ikConstraint = ikConstraints[i];
if (ikConstraint.data.name == constraintName) return ikConstraint; if (ikConstraint.data.name == constraintName) return ikConstraint;
} }
return null; return null;
} }
/// <summary>Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of
/// this method than to call it repeatedly.</summary>
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public TransformConstraint FindTransformConstraint (string constraintName) { public TransformConstraint FindTransformConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<TransformConstraint> transformConstraints = this.transformConstraints; var transformConstraints = this.transformConstraints.Items;
for (int i = 0, n = transformConstraints.Count; i < n; i++) { for (int i = 0, n = this.transformConstraints.Count; i < n; i++) {
TransformConstraint transformConstraint = transformConstraints.Items[i]; TransformConstraint transformConstraint = transformConstraints[i];
if (transformConstraint.data.Name == constraintName) return transformConstraint; if (transformConstraint.data.Name == constraintName) return transformConstraint;
} }
return null; return null;
} }
/// <summary>Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method
/// than to call it repeatedly.</summary>
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public PathConstraint FindPathConstraint (string constraintName) { public PathConstraint FindPathConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<PathConstraint> pathConstraints = this.pathConstraints; var pathConstraints = this.pathConstraints.Items;
for (int i = 0, n = pathConstraints.Count; i < n; i++) { for (int i = 0, n = this.pathConstraints.Count; i < n; i++) {
PathConstraint constraint = pathConstraints.Items[i]; PathConstraint constraint = pathConstraints[i];
if (constraint.data.Name.Equals(constraintName)) return constraint; if (constraint.data.Name.Equals(constraintName)) return constraint;
} }
return null; return null;
@ -589,10 +580,10 @@ namespace Spine {
public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer) { public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer) {
float[] temp = vertexBuffer; float[] temp = vertexBuffer;
temp = temp ?? new float[8]; temp = temp ?? new float[8];
var drawOrderItems = this.drawOrder.Items; var drawOrder = this.drawOrder.Items;
float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
for (int i = 0, n = drawOrderItems.Length; i < n; i++) { for (int i = 0, n = this.drawOrder.Count; i < n; i++) {
Slot slot = drawOrderItems[i]; Slot slot = drawOrder[i];
if (!slot.bone.active) continue; if (!slot.bone.active) continue;
int verticesLength = 0; int verticesLength = 0;
float[] vertices = null; float[] vertices = null;

View File

@ -34,6 +34,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization;
#if WINDOWS_STOREAPP #if WINDOWS_STOREAPP
using System.Threading.Tasks; using System.Threading.Tasks;
@ -41,7 +42,7 @@ using Windows.Storage;
#endif #endif
namespace Spine { namespace Spine {
public class SkeletonBinary { public class SkeletonBinary : SkeletonLoader {
public const int BONE_ROTATE = 0; public const int BONE_ROTATE = 0;
public const int BONE_TRANSLATE = 1; public const int BONE_TRANSLATE = 1;
public const int BONE_SCALE = 2; public const int BONE_SCALE = 2;
@ -59,22 +60,15 @@ namespace Spine {
public const int CURVE_STEPPED = 1; public const int CURVE_STEPPED = 1;
public const int CURVE_BEZIER = 2; public const int CURVE_BEZIER = 2;
public float Scale { get; set; } public SkeletonBinary (AttachmentLoader attachmentLoader)
:base(attachmentLoader) {
private AttachmentLoader attachmentLoader; }
private List<SkeletonJson.LinkedMesh> linkedMeshes = new List<SkeletonJson.LinkedMesh>();
public SkeletonBinary (params Atlas[] atlasArray) public SkeletonBinary (params Atlas[] atlasArray)
: this(new AtlasAttachmentLoader(atlasArray)) { : base(atlasArray) {
} }
public SkeletonBinary (AttachmentLoader attachmentLoader) { #if !ISUNITY && WINDOWS_STOREAPP
if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader");
this.attachmentLoader = attachmentLoader;
Scale = 1;
}
#if !ISUNITY && WINDOWS_STOREAPP
private async Task<SkeletonData> ReadFile(string path) { private async Task<SkeletonData> ReadFile(string path) {
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) { using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) {
@ -84,11 +78,11 @@ namespace Spine {
} }
} }
public SkeletonData ReadSkeletonData (String path) { public override SkeletonData ReadSkeletonData (string path) {
return this.ReadFile(path).Result; return this.ReadFile(path).Result;
} }
#else #else
public SkeletonData ReadSkeletonData (String path) { public override SkeletonData ReadSkeletonData (string path) {
#if WINDOWS_PHONE #if WINDOWS_PHONE
using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) {
#else #else
@ -119,13 +113,13 @@ namespace Spine {
public SkeletonData ReadSkeletonData (Stream file) { public SkeletonData ReadSkeletonData (Stream file) {
if (file == null) throw new ArgumentNullException("file"); if (file == null) throw new ArgumentNullException("file");
float scale = Scale; float scale = this.scale;
var skeletonData = new SkeletonData(); var skeletonData = new SkeletonData();
SkeletonInput input = new SkeletonInput(file); SkeletonInput input = new SkeletonInput(file);
skeletonData.hash = input.ReadString(); long hash = input.ReadLong();
if (skeletonData.hash.Length == 0) skeletonData.hash = null; skeletonData.hash = hash == 0 ? null : hash.ToString();
skeletonData.version = input.ReadString(); skeletonData.version = input.ReadString();
if (skeletonData.version.Length == 0) skeletonData.version = null; if (skeletonData.version.Length == 0) skeletonData.version = null;
if ("3.8.75" == skeletonData.version) if ("3.8.75" == skeletonData.version)
@ -151,16 +145,15 @@ namespace Spine {
Object[] o; Object[] o;
// Strings. // Strings.
input.strings = new ExposedList<string>(n = input.ReadInt(true)); o = input.strings = new String[n = input.ReadInt(true)];
o = input.strings.Resize(n).Items;
for (int i = 0; i < n; i++) for (int i = 0; i < n; i++)
o[i] = input.ReadString(); o[i] = input.ReadString();
// Bones. // Bones.
o = skeletonData.bones.Resize(n = input.ReadInt(true)).Items; var bones = skeletonData.bones.Resize(n = input.ReadInt(true)).Items;
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
String name = input.ReadString(); String name = input.ReadString();
BoneData parent = i == 0 ? null : skeletonData.bones.Items[input.ReadInt(true)]; BoneData parent = i == 0 ? null : bones[input.ReadInt(true)];
BoneData data = new BoneData(i, name, parent); BoneData data = new BoneData(i, name, parent);
data.rotation = input.ReadFloat(); data.rotation = input.ReadFloat();
data.x = input.ReadFloat() * scale; data.x = input.ReadFloat() * scale;
@ -169,18 +162,18 @@ namespace Spine {
data.scaleY = input.ReadFloat(); data.scaleY = input.ReadFloat();
data.shearX = input.ReadFloat(); data.shearX = input.ReadFloat();
data.shearY = input.ReadFloat(); data.shearY = input.ReadFloat();
data.length = input.ReadFloat() * scale; data.Length = input.ReadFloat() * scale;
data.transformMode = TransformModeValues[input.ReadInt(true)]; data.transformMode = TransformModeValues[input.ReadInt(true)];
data.skinRequired = input.ReadBoolean(); data.skinRequired = input.ReadBoolean();
if (nonessential) input.ReadInt(); // Skip bone color. if (nonessential) input.ReadInt(); // Skip bone color.
o[i] = data; bones[i] = data;
} }
// Slots. // Slots.
o = skeletonData.slots.Resize(n = input.ReadInt(true)).Items; var slots = skeletonData.slots.Resize(n = input.ReadInt(true)).Items;
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
String slotName = input.ReadString(); String slotName = input.ReadString();
BoneData boneData = skeletonData.bones.Items[input.ReadInt(true)]; BoneData boneData = bones[input.ReadInt(true)];
SlotData slotData = new SlotData(i, slotName, boneData); SlotData slotData = new SlotData(i, slotName, boneData);
int color = input.ReadInt(); int color = input.ReadInt();
slotData.r = ((color & 0xff000000) >> 24) / 255f; slotData.r = ((color & 0xff000000) >> 24) / 255f;
@ -198,7 +191,7 @@ namespace Spine {
slotData.attachmentName = input.ReadStringRef(); slotData.attachmentName = input.ReadStringRef();
slotData.blendMode = (BlendMode)input.ReadInt(true); slotData.blendMode = (BlendMode)input.ReadInt(true);
o[i] = slotData; slots[i] = slotData;
} }
// IK constraints. // IK constraints.
@ -207,10 +200,10 @@ namespace Spine {
IkConstraintData data = new IkConstraintData(input.ReadString()); IkConstraintData data = new IkConstraintData(input.ReadString());
data.order = input.ReadInt(true); data.order = input.ReadInt(true);
data.skinRequired = input.ReadBoolean(); data.skinRequired = input.ReadBoolean();
Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items; var constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items;
for (int ii = 0; ii < nn; ii++) for (int ii = 0; ii < nn; ii++)
bones[ii] = skeletonData.bones.Items[input.ReadInt(true)]; constraintBones[ii] = bones[input.ReadInt(true)];
data.target = skeletonData.bones.Items[input.ReadInt(true)]; data.target = bones[input.ReadInt(true)];
data.mix = input.ReadFloat(); data.mix = input.ReadFloat();
data.softness = input.ReadFloat() * scale; data.softness = input.ReadFloat() * scale;
data.bendDirection = input.ReadSByte(); data.bendDirection = input.ReadSByte();
@ -226,10 +219,10 @@ namespace Spine {
TransformConstraintData data = new TransformConstraintData(input.ReadString()); TransformConstraintData data = new TransformConstraintData(input.ReadString());
data.order = input.ReadInt(true); data.order = input.ReadInt(true);
data.skinRequired = input.ReadBoolean(); data.skinRequired = input.ReadBoolean();
Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items; var constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items;
for (int ii = 0; ii < nn; ii++) for (int ii = 0; ii < nn; ii++)
bones[ii] = skeletonData.bones.Items[input.ReadInt(true)]; constraintBones[ii] = bones[input.ReadInt(true)];
data.target = skeletonData.bones.Items[input.ReadInt(true)]; data.target = bones[input.ReadInt(true)];
data.local = input.ReadBoolean(); data.local = input.ReadBoolean();
data.relative = input.ReadBoolean(); data.relative = input.ReadBoolean();
data.offsetRotation = input.ReadFloat(); data.offsetRotation = input.ReadFloat();
@ -251,10 +244,10 @@ namespace Spine {
PathConstraintData data = new PathConstraintData(input.ReadString()); PathConstraintData data = new PathConstraintData(input.ReadString());
data.order = input.ReadInt(true); data.order = input.ReadInt(true);
data.skinRequired = input.ReadBoolean(); data.skinRequired = input.ReadBoolean();
Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items; Object[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items;
for (int ii = 0; ii < nn; ii++) for (int ii = 0; ii < nn; ii++)
bones[ii] = skeletonData.bones.Items[input.ReadInt(true)]; constraintBones[ii] = bones[input.ReadInt(true)];
data.target = skeletonData.slots.Items[input.ReadInt(true)]; data.target = slots[input.ReadInt(true)];
data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(input.ReadInt(true)); data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(input.ReadInt(true));
data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(input.ReadInt(true)); data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(input.ReadInt(true));
data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(input.ReadInt(true)); data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(input.ReadInt(true));
@ -286,7 +279,7 @@ namespace Spine {
// Linked meshes. // Linked meshes.
n = linkedMeshes.Count; n = linkedMeshes.Count;
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; LinkedMesh linkedMesh = linkedMeshes[i];
Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin);
if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin);
Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
@ -334,16 +327,21 @@ namespace Spine {
} else { } else {
skin = new Skin(input.ReadStringRef()); skin = new Skin(input.ReadStringRef());
Object[] bones = skin.bones.Resize(input.ReadInt(true)).Items; Object[] bones = skin.bones.Resize(input.ReadInt(true)).Items;
var bonesItems = skeletonData.bones.Items;
for (int i = 0, n = skin.bones.Count; i < n; i++) for (int i = 0, n = skin.bones.Count; i < n; i++)
bones[i] = skeletonData.bones.Items[input.ReadInt(true)]; bones[i] = bonesItems[input.ReadInt(true)];
var ikConstraintsItems = skeletonData.ikConstraints.Items;
for (int i = 0, n = input.ReadInt(true); i < n; i++) for (int i = 0, n = input.ReadInt(true); i < n; i++)
skin.constraints.Add(skeletonData.ikConstraints.Items[input.ReadInt(true)]); skin.constraints.Add(ikConstraintsItems[input.ReadInt(true)]);
var transformConstraintsItems = skeletonData.transformConstraints.Items;
for (int i = 0, n = input.ReadInt(true); i < n; i++) for (int i = 0, n = input.ReadInt(true); i < n; i++)
skin.constraints.Add(skeletonData.transformConstraints.Items[input.ReadInt(true)]); skin.constraints.Add(transformConstraintsItems[input.ReadInt(true)]);
var pathConstraintsItems = skeletonData.pathConstraints.Items;
for (int i = 0, n = input.ReadInt(true); i < n; i++) for (int i = 0, n = input.ReadInt(true); i < n; i++)
skin.constraints.Add(skeletonData.pathConstraints.Items[input.ReadInt(true)]); skin.constraints.Add(pathConstraintsItems[input.ReadInt(true)]);
skin.constraints.TrimExcess(); skin.constraints.TrimExcess();
slotCount = input.ReadInt(true); slotCount = input.ReadInt(true);
} }
for (int i = 0; i < slotCount; i++) { for (int i = 0; i < slotCount; i++) {
@ -359,14 +357,12 @@ namespace Spine {
private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonData, Skin skin, int slotIndex, private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonData, Skin skin, int slotIndex,
String attachmentName, bool nonessential) { String attachmentName, bool nonessential) {
float scale = this.scale;
float scale = Scale;
String name = input.ReadStringRef(); String name = input.ReadStringRef();
if (name == null) name = attachmentName; if (name == null) name = attachmentName;
AttachmentType type = (AttachmentType)input.ReadByte(); switch ((AttachmentType)input.ReadByte()) {
switch (type) {
case AttachmentType.Region: { case AttachmentType.Region: {
String path = input.ReadStringRef(); String path = input.ReadStringRef();
float rotation = input.ReadFloat(); float rotation = input.ReadFloat();
@ -529,7 +525,7 @@ namespace Spine {
} }
private Vertices ReadVertices (SkeletonInput input, int vertexCount) { private Vertices ReadVertices (SkeletonInput input, int vertexCount) {
float scale = Scale; float scale = this.scale;
int verticesLength = vertexCount << 1; int verticesLength = vertexCount << 1;
Vertices vertices = new Vertices(); Vertices vertices = new Vertices();
if(!input.ReadBoolean()) { if(!input.ReadBoolean()) {
@ -574,66 +570,97 @@ namespace Spine {
return array; return array;
} }
/// <exception cref="SerializationException">SerializationException will be thrown when a Vertex attachment is not found.</exception>
/// <exception cref="IOException">Throws IOException when a read operation fails.</exception>
private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData skeletonData) { private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData skeletonData) {
var timelines = new ExposedList<Timeline>(32); var timelines = new ExposedList<Timeline>(input.ReadInt(true));
float scale = Scale; float scale = this.scale;
float duration = 0;
// Slot timelines. // Slot timelines.
for (int i = 0, n = input.ReadInt(true); i < n; i++) { for (int i = 0, n = input.ReadInt(true); i < n; i++) {
int slotIndex = input.ReadInt(true); int slotIndex = input.ReadInt(true);
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
int timelineType = input.ReadByte(); int timelineType = input.ReadByte(), frameCount = input.ReadInt(true), frameLast = frameCount - 1;
int frameCount = input.ReadInt(true);
switch (timelineType) { switch (timelineType) {
case SLOT_ATTACHMENT: { case SLOT_ATTACHMENT: {
AttachmentTimeline timeline = new AttachmentTimeline(frameCount); AttachmentTimeline timeline = new AttachmentTimeline(frameCount, slotIndex);
timeline.slotIndex = slotIndex; for (int frame = 0; frame < frameCount; frame++)
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) timeline.SetFrame(frame, input.ReadFloat(), input.ReadStringRef());
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadStringRef()); timelines.Add(timeline);
timelines.Add(timeline); break;
duration = Math.Max(duration, timeline.frames[frameCount - 1]);
break;
}
case SLOT_COLOR: {
ColorTimeline timeline = new ColorTimeline(frameCount);
timeline.slotIndex = slotIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
float time = input.ReadFloat();
int color = input.ReadInt();
float r = ((color & 0xff000000) >> 24) / 255f;
float g = ((color & 0x00ff0000) >> 16) / 255f;
float b = ((color & 0x0000ff00) >> 8) / 255f;
float a = ((color & 0x000000ff)) / 255f;
timeline.SetFrame(frameIndex, time, r, g, b, a);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
} }
timelines.Add(timeline); case SLOT_COLOR: {
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * ColorTimeline.ENTRIES]); ColorTimeline timeline = new ColorTimeline(frameCount, input.ReadInt(true), slotIndex);
break;
}
case SLOT_TWO_COLOR: {
TwoColorTimeline timeline = new TwoColorTimeline(frameCount);
timeline.slotIndex = slotIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
float time = input.ReadFloat(); float time = input.ReadFloat();
int color = input.ReadInt(); float r = input.Read() / 255f, g = input.Read() / 255f;
float r = ((color & 0xff000000) >> 24) / 255f; float b = input.Read() / 255f, a = input.Read() / 255f;
float g = ((color & 0x00ff0000) >> 16) / 255f; for (int frame = 0, bezier = 0; ; frame++) {
float b = ((color & 0x0000ff00) >> 8) / 255f; timeline.SetFrame(frame, time, r, g, b, a);
float a = ((color & 0x000000ff)) / 255f; if (frame == frameLast) break;
int color2 = input.ReadInt(); // 0x00rrggbb float time2 = input.ReadFloat();
float r2 = ((color2 & 0x00ff0000) >> 16) / 255f; float r2 = input.Read() / 255f, g2 = input.Read() / 255f;
float g2 = ((color2 & 0x0000ff00) >> 8) / 255f; float b2 = input.Read() / 255f, a2 = input.Read() / 255f;
float b2 = ((color2 & 0x000000ff)) / 255f; switch (input.ReadByte()) {
case CURVE_STEPPED:
timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2); timeline.SetStepped(frame);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); break;
case CURVE_BEZIER:
SetBezier(input, timeline, bezier++, frame, 0, time, time2, r, r2, 1);
SetBezier(input, timeline, bezier++, frame, 1, time, time2, g, g2, 1);
SetBezier(input, timeline, bezier++, frame, 2, time, time2, b, b2, 1);
SetBezier(input, timeline, bezier++, frame, 3, time, time2, a, a2, 1);
break;
}
time = time2;
r = r2;
g = g2;
b = b2;
a = a2;
}
timelines.Add(timeline);
break;
}
case SLOT_TWO_COLOR: {
TwoColorTimeline timeline = new TwoColorTimeline(frameCount, input.ReadInt(true), slotIndex);
float time = input.ReadFloat();
float r = input.Read() / 255f, g = input.Read() / 255f;
float b = input.Read() / 255f, a = input.Read() / 255f;
float r2 = input.Read() / 255f, g2 = input.Read() / 255f;
float b2 = input.Read() / 255f;
for (int frame = 0, bezier = 0; ; frame++) {
timeline.SetFrame(frame, time, r, g, b, a, r2, g2, b2);
if (frame == frameLast) break;
float time2 = input.ReadFloat();
float nr = input.Read() / 255f, ng = input.Read() / 255f;
float nb = input.Read() / 255f, na = input.Read() / 255f;
float nr2 = input.Read() / 255f, ng2 = input.Read() / 255f;
float nb2 = input.Read() / 255f;
switch (input.ReadByte()) {
case CURVE_STEPPED:
timeline.SetStepped(frame);
break;
case CURVE_BEZIER:
SetBezier(input, timeline, bezier++, frame, 0, time, time2, r, nr, 1);
SetBezier(input, timeline, bezier++, frame, 1, time, time2, g, ng, 1);
SetBezier(input, timeline, bezier++, frame, 2, time, time2, b, nb, 1);
SetBezier(input, timeline, bezier++, frame, 3, time, time2, a, na, 1);
SetBezier(input, timeline, bezier++, frame, 4, time, time2, r2, nr2, 1);
SetBezier(input, timeline, bezier++, frame, 5, time, time2, g2, ng2, 1);
SetBezier(input, timeline, bezier++, frame, 6, time, time2, b2, nb2, 1);
break;
}
time = time2;
r = nr;
g = ng;
b = nb;
a = na;
r2 = nr2;
g2 = ng2;
b2 = nb2;
}
timelines.Add(timeline);
break;
} }
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TwoColorTimeline.ENTRIES]);
break;
}
} }
} }
} }
@ -642,76 +669,78 @@ namespace Spine {
for (int i = 0, n = input.ReadInt(true); i < n; i++) { for (int i = 0, n = input.ReadInt(true); i < n; i++) {
int boneIndex = input.ReadInt(true); int boneIndex = input.ReadInt(true);
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
int timelineType = input.ReadByte(); switch (input.ReadByte()) {
int frameCount = input.ReadInt(true); case BONE_ROTATE:
switch (timelineType) { timelines.Add(ReadTimeline(input, new RotateTimeline(input.ReadInt(true), input.ReadInt(true), boneIndex), 1));
case BONE_ROTATE: {
RotateTimeline timeline = new RotateTimeline(frameCount);
timeline.boneIndex = boneIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat());
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]);
break; break;
} case BONE_TRANSLATE:
case BONE_TRANSLATE: timelines
case BONE_SCALE: .Add(ReadTimeline(input, new TranslateTimeline(input.ReadInt(true), input.ReadInt(true), boneIndex), scale));
case BONE_SHEAR: { break;
TranslateTimeline timeline; case BONE_SCALE:
float timelineScale = 1; timelines.Add(ReadTimeline(input, new ScaleTimeline(input.ReadInt(true), input.ReadInt(true), boneIndex), 1));
if (timelineType == BONE_SCALE) break;
timeline = new ScaleTimeline(frameCount); case BONE_SHEAR:
else if (timelineType == BONE_SHEAR) timelines.Add(ReadTimeline(input, new ShearTimeline(input.ReadInt(true), input.ReadInt(true), boneIndex), 1));
timeline = new ShearTimeline(frameCount);
else {
timeline = new TranslateTimeline(frameCount);
timelineScale = scale;
}
timeline.boneIndex = boneIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat() * timelineScale,
input.ReadFloat() * timelineScale);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]);
break; break;
}
} }
} }
} }
// IK constraint timelines. // IK constraint timelines.
for (int i = 0, n = input.ReadInt(true); i < n; i++) { for (int i = 0, n = input.ReadInt(true); i < n; i++) {
int index = input.ReadInt(true); int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1;
int frameCount = input.ReadInt(true); IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount, input.ReadInt(true), index);
IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount) { float time = input.ReadFloat(), mix = input.ReadFloat(), softness = input.ReadFloat() * scale;
ikConstraintIndex = index for (int frame = 0, bezier = 0; ; frame++) {
}; timeline.SetFrame(frame, time, mix, softness, input.ReadSByte(), input.ReadBoolean(), input.ReadBoolean());
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { if (frame == frameLast) break;
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat() * scale, input.ReadSByte(), input.ReadBoolean(), float time2 = input.ReadFloat(), mix2 = input.ReadFloat(), softness2 = input.ReadFloat() * scale;
input.ReadBoolean()); switch (input.ReadByte()) {
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); case CURVE_STEPPED:
timeline.SetStepped(frame);
break;
case CURVE_BEZIER:
SetBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2, 1);
SetBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2, scale);
break;
}
time = time2;
mix = mix2;
softness = softness2;
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]);
} }
// Transform constraint timelines. // Transform constraint timelines.
for (int i = 0, n = input.ReadInt(true); i < n; i++) { for (int i = 0, n = input.ReadInt(true); i < n; i++) {
int index = input.ReadInt(true); int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1;
int frameCount = input.ReadInt(true); TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount, input.ReadInt(true), index);
TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); float time = input.ReadFloat(), rotateMix = input.ReadFloat(), translateMix = input.ReadFloat(),
timeline.transformConstraintIndex = index; scaleMix = input.ReadFloat(), shearMix = input.ReadFloat();
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { for (int frame = 0, bezier = 0; ; frame++) {
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat(), input.ReadFloat(), timeline.SetFrame(frame, time, rotateMix, translateMix, scaleMix, shearMix);
input.ReadFloat()); if (frame == frameLast) break;
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); float time2 = input.ReadFloat(), rotateMix2 = input.ReadFloat(), translateMix2 = input.ReadFloat(),
scaleMix2 = input.ReadFloat(), shearMix2 = input.ReadFloat();
switch (input.ReadByte()) {
case CURVE_STEPPED:
timeline.SetStepped(frame);
break;
case CURVE_BEZIER:
SetBezier(input, timeline, bezier++, frame, 0, time, time2, rotateMix, rotateMix2, 1);
SetBezier(input, timeline, bezier++, frame, 1, time, time2, translateMix, translateMix2, 1);
SetBezier(input, timeline, bezier++, frame, 2, time, time2, scaleMix, scaleMix2, 1);
SetBezier(input, timeline, bezier++, frame, 3, time, time2, shearMix, shearMix2, 1);
break;
}
time = time2;
rotateMix = rotateMix2;
translateMix = translateMix2;
scaleMix = scaleMix2;
shearMix = shearMix2;
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]);
} }
// Path constraint timelines. // Path constraint timelines.
@ -719,40 +748,21 @@ namespace Spine {
int index = input.ReadInt(true); int index = input.ReadInt(true);
PathConstraintData data = skeletonData.pathConstraints.Items[index]; PathConstraintData data = skeletonData.pathConstraints.Items[index];
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
int timelineType = input.ReadSByte(); switch (input.ReadByte()) {
int frameCount = input.ReadInt(true);
switch(timelineType) {
case PATH_POSITION: case PATH_POSITION:
case PATH_SPACING: { timelines
PathConstraintPositionTimeline timeline; .Add(ReadTimeline(input, new PathConstraintPositionTimeline(input.ReadInt(true), input.ReadInt(true), index),
float timelineScale = 1; data.positionMode == PositionMode.Fixed ? scale : 1));
if (timelineType == PATH_SPACING) { break;
timeline = new PathConstraintSpacingTimeline(frameCount); case PATH_SPACING:
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; timelines
} else { .Add(ReadTimeline(input, new PathConstraintSpacingTimeline(input.ReadInt(true), input.ReadInt(true), index),
timeline = new PathConstraintPositionTimeline(frameCount); data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed ? scale : 1));
if (data.positionMode == PositionMode.Fixed) timelineScale = scale; break;
} case PATH_MIX:
timeline.pathConstraintIndex = index; timelines
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { .Add(ReadTimeline(input, new PathConstraintMixTimeline(input.ReadInt(true), input.ReadInt(true), index), 1));
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat() * timelineScale); break;
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
break;
}
case PATH_MIX: {
PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount);
timeline.pathConstraintIndex = index;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat());
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
break;
}
} }
} }
} }
@ -763,18 +773,18 @@ namespace Spine {
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
int slotIndex = input.ReadInt(true); int slotIndex = input.ReadInt(true);
for (int iii = 0, nnn = input.ReadInt(true); iii < nnn; iii++) { for (int iii = 0, nnn = input.ReadInt(true); iii < nnn; iii++) {
VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, input.ReadStringRef()); String attachmentName = input.ReadStringRef();
bool weighted = attachment.bones != null; VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, attachmentName);
float[] vertices = attachment.vertices; if (attachment == null) throw new SerializationException("Vertex attachment not found: " + attachmentName);
int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; bool weighted = attachment.Bones != null;
float[] vertices = attachment.Vertices;
int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length;
int frameCount = input.ReadInt(true); int frameCount = input.ReadInt(true), frameLast = frameCount - 1;
DeformTimeline timeline = new DeformTimeline(frameCount); DeformTimeline timeline = new DeformTimeline(frameCount, input.ReadInt(true), slotIndex, attachment);
timeline.slotIndex = slotIndex;
timeline.attachment = attachment;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { float time = input.ReadFloat();
float time = input.ReadFloat(); for (int frame = 0, bezier = 0; ; frame++) {
float[] deform; float[] deform;
int end = input.ReadInt(true); int end = input.ReadInt(true);
if (end == 0) if (end == 0)
@ -786,7 +796,8 @@ namespace Spine {
if (scale == 1) { if (scale == 1) {
for (int v = start; v < end; v++) for (int v = start; v < end; v++)
deform[v] = input.ReadFloat(); deform[v] = input.ReadFloat();
} else { }
else {
for (int v = start; v < end; v++) for (int v = start; v < end; v++)
deform[v] = input.ReadFloat() * scale; deform[v] = input.ReadFloat() * scale;
} }
@ -795,12 +806,20 @@ namespace Spine {
deform[v] += vertices[v]; deform[v] += vertices[v];
} }
} }
timeline.SetFrame(frame, time, deform);
timeline.SetFrame(frameIndex, time, deform); if (frame == frameLast) break;
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); float time2 = input.ReadFloat();
switch (input.ReadByte()) {
case CURVE_STEPPED:
timeline.SetStepped(frame);
break;
case CURVE_BEZIER:
SetBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1);
break;
}
time = time2;
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount - 1]);
} }
} }
} }
@ -835,7 +854,6 @@ namespace Spine {
timeline.SetFrame(i, time, drawOrder); timeline.SetFrame(i, time, drawOrder);
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]);
} }
// Event timeline. // Event timeline.
@ -845,34 +863,75 @@ namespace Spine {
for (int i = 0; i < eventCount; i++) { for (int i = 0; i < eventCount; i++) {
float time = input.ReadFloat(); float time = input.ReadFloat();
EventData eventData = skeletonData.events.Items[input.ReadInt(true)]; EventData eventData = skeletonData.events.Items[input.ReadInt(true)];
Event e = new Event(time, eventData) { Event e = new Event(time, eventData);
Int = input.ReadInt(false), e.intValue = input.ReadInt(false);
Float = input.ReadFloat(), e.floatValue = input.ReadFloat();
String = input.ReadBoolean() ? input.ReadString() : eventData.String e.stringValue = input.ReadBoolean() ? input.ReadString() : eventData.String;
}; if (e.Data.AudioPath != null) {
if (e.data.AudioPath != null) {
e.volume = input.ReadFloat(); e.volume = input.ReadFloat();
e.balance = input.ReadFloat(); e.balance = input.ReadFloat();
} }
timeline.SetFrame(i, e); timeline.SetFrame(i, e);
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[eventCount - 1]);
} }
timelines.TrimExcess(); float duration = 0;
var items = timelines.Items;
for (int i = 0, n = timelines.Count; i < n; i++)
duration = Math.Max(duration, items[i].Duration);
return new Animation(name, timelines, duration); return new Animation(name, timelines, duration);
} }
private void ReadCurve (SkeletonInput input, int frameIndex, CurveTimeline timeline) { /// <exception cref="IOException">Throws IOException when a read operation fails.</exception>
switch (input.ReadByte()) { private Timeline ReadTimeline (SkeletonInput input, CurveTimeline1 timeline, float scale) {
case CURVE_STEPPED: float time = input.ReadFloat(), value = input.ReadFloat() * scale;
timeline.SetStepped(frameIndex); for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1;; frame++) {
break; timeline.SetFrame(frame, time, value);
case CURVE_BEZIER: if (frame == frameLast) break;
timeline.SetCurve(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat(), input.ReadFloat()); float time2 = input.ReadFloat(), value2 = input.ReadFloat() * scale;
break; switch (input.ReadByte()) {
case CURVE_STEPPED:
timeline.SetStepped(frame);
break;
case CURVE_BEZIER:
SetBezier (input, timeline, bezier++, frame, 0, time, time2, value, value2, 1);
break;
}
time = time2;
value = value2;
} }
return timeline;
}
/// <exception cref="IOException">Throws IOException when a read operation fails.</exception>
private Timeline ReadTimeline (SkeletonInput input, CurveTimeline2 timeline, float scale) {
float time = input.ReadFloat(), value1 = input.ReadFloat() * scale, value2 = input.ReadFloat() * scale;
for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1;; frame++) {
timeline.SetFrame(frame, time, value1, value2);
if (frame == frameLast) break;
float time2 = input.ReadFloat(), nvalue1 = input.ReadFloat() * scale, nvalue2 = input.ReadFloat() * scale;
switch (input.ReadByte()) {
case CURVE_STEPPED:
timeline.SetStepped(frame);
break;
case CURVE_BEZIER:
SetBezier(input, timeline, bezier++, frame, 0, time, time2, value1, nvalue1, scale);
SetBezier(input, timeline, bezier++, frame, 1, time, time2, value2, nvalue2, scale);
break;
}
time = time2;
value1 = nvalue1;
value2 = nvalue2;
}
return timeline;
}
/// <exception cref="IOException">Throws IOException when a read operation fails.</exception>
void SetBezier (SkeletonInput input, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2,
float value1, float value2, float scale) {
timeline.SetBezier(bezier, frame, value, time1, value1, input.ReadFloat(), input.ReadFloat() * scale, input.ReadFloat(),
input.ReadFloat() * scale, time2, value2);
} }
internal class Vertices internal class Vertices
@ -883,14 +942,18 @@ namespace Spine {
internal class SkeletonInput { internal class SkeletonInput {
private byte[] chars = new byte[32]; private byte[] chars = new byte[32];
private byte[] bytesBigEndian = new byte[4]; private byte[] bytesBigEndian = new byte[8];
internal ExposedList<String> strings; internal string[] strings;
Stream input; Stream input;
public SkeletonInput (Stream input) { public SkeletonInput (Stream input) {
this.input = input; this.input = input;
} }
public int Read () {
return input.ReadByte();
}
public byte ReadByte () { public byte ReadByte () {
return (byte)input.ReadByte(); return (byte)input.ReadByte();
} }
@ -922,6 +985,18 @@ namespace Spine {
+ bytesBigEndian[3]; + bytesBigEndian[3];
} }
public long ReadLong () {
input.Read(bytesBigEndian, 0, 8);
return ((long)(bytesBigEndian[0]) << 56)
+ ((long)(bytesBigEndian[1]) << 48)
+ ((long)(bytesBigEndian[2]) << 40)
+ ((long)(bytesBigEndian[3]) << 32)
+ ((long)(bytesBigEndian[4]) << 24)
+ ((long)(bytesBigEndian[5]) << 16)
+ ((long)(bytesBigEndian[6]) << 8)
+ (long)(bytesBigEndian[7]);
}
public int ReadInt (bool optimizePositive) { public int ReadInt (bool optimizePositive) {
int b = input.ReadByte(); int b = input.ReadByte();
int result = b & 0x7F; int result = b & 0x7F;
@ -959,7 +1034,7 @@ namespace Spine {
///<return>May be null.</return> ///<return>May be null.</return>
public String ReadStringRef () { public String ReadStringRef () {
int index = ReadInt(true); int index = ReadInt(true);
return index == 0 ? null : strings.Items[index - 1]; return index == 0 ? null : strings[index - 1];
} }
public void ReadFully (byte[] buffer, int offset, int length) { public void ReadFully (byte[] buffer, int offset, int length) {
@ -974,20 +1049,9 @@ namespace Spine {
/// <summary>Returns the version string of binary skeleton data.</summary> /// <summary>Returns the version string of binary skeleton data.</summary>
public string GetVersionString () { public string GetVersionString () {
try { try {
// Hash. ReadLong(); // long hash
int byteCount = ReadInt(true); string version = ReadString();
if (byteCount > 1) input.Position += byteCount - 1; return version;
// Version.
byteCount = ReadInt(true);
if (byteCount > 1) {
byteCount--;
var buffer = new byte[byteCount];
ReadFully(buffer, 0, byteCount);
return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount);
}
throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input");
} catch (Exception e) { } catch (Exception e) {
throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input"); throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input");
} }

View File

@ -176,7 +176,7 @@ namespace Spine {
} }
/// <summary>Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more /// <summary>Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more
/// efficient to only call this method if {@link #aabbContainsPoint(float, float)} returns true.</summary> /// efficient to only call this method if <see cref="AabbContainsPoint(float, float)"/> returns true.</summary>
public BoundingBoxAttachment ContainsPoint (float x, float y) { public BoundingBoxAttachment ContainsPoint (float x, float y) {
ExposedList<Polygon> polygons = Polygons; ExposedList<Polygon> polygons = Polygons;
for (int i = 0, n = polygons.Count; i < n; i++) for (int i = 0, n = polygons.Count; i < n; i++)
@ -185,7 +185,7 @@ namespace Spine {
} }
/// <summary>Returns the first bounding box attachment that contains the line segment, or null. When doing many checks, it is usually /// <summary>Returns the first bounding box attachment that contains the line segment, or null. When doing many checks, it is usually
/// more efficient to only call this method if {@link #aabbIntersectsSegment(float, float, float, float)} returns true.</summary> /// more efficient to only call this method if <see cref="aabbIntersectsSegment(float, float, float, float)"/> returns true.</summary>
public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) { public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) {
ExposedList<Polygon> polygons = Polygons; ExposedList<Polygon> polygons = Polygons;
for (int i = 0, n = polygons.Count; i < n; i++) for (int i = 0, n = polygons.Count; i < n; i++)

View File

@ -50,6 +50,8 @@ namespace Spine {
internal float fps; internal float fps;
internal string imagesPath, audioPath; internal string imagesPath, audioPath;
///<summary>The skeleton's name, which by default is the name of the skeleton data file when possible, or null when a name hasn't been
///set.</summary>
public string Name { get { return name; } set { name = value; } } public string Name { get { return name; } set { name = value; } }
/// <summary>The skeleton's bones, sorted parent first. The root bone is always the first bone.</summary> /// <summary>The skeleton's bones, sorted parent first. The root bone is always the first bone.</summary>
@ -79,16 +81,18 @@ namespace Spine {
public float Height { get { return height; } set { height = value; } } public float Height { get { return height; } set { height = value; } }
/// <summary>The Spine version used to export this data, or null.</summary> /// <summary>The Spine version used to export this data, or null.</summary>
public string Version { get { return version; } set { version = value; } } public string Version { get { return version; } set { version = value; } }
///<summary>The skeleton data hash. This value will change if any of the skeleton data has changed.
///May be null.</summary>
public string Hash { get { return hash; } set { hash = value; } } public string Hash { get { return hash; } set { hash = value; } }
/// <summary>The path to the images directory as defined in Spine. Available only when nonessential data was exported. May be null</summary>
public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } }
/// <summary>The path to the audio directory defined in Spine. Available only when nonessential data was exported. May be null.</summary> /// <summary> The path to the audio directory as defined in Spine. Available only when nonessential data was exported.
/// May be null.</summary>
public string AudioPath { get { return audioPath; } set { audioPath = value; } } public string AudioPath { get { return audioPath; } set { audioPath = value; } }
/// <summary> /// <summary>The dopesheet FPS in Spine, or zero if nonessential data was not exported.</summary>
/// The dopesheet FPS in Spine. Available only when nonessential data was exported.</summary>
public float Fps { get { return fps; } set { fps = value; } } public float Fps { get { return fps; } set { fps = value; } }
// --- Bones. // --- Bones.
@ -99,10 +103,9 @@ namespace Spine {
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public BoneData FindBone (string boneName) { public BoneData FindBone (string boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
var bones = this.bones; var bones = this.bones.Items;
var bonesItems = bones.Items; for (int i = 0, n = this.bones.Count; i < n; i++) {
for (int i = 0, n = bones.Count; i < n; i++) { BoneData bone = bones[i];
BoneData bone = bonesItems[i];
if (bone.name == boneName) return bone; if (bone.name == boneName) return bone;
} }
return null; return null;
@ -111,10 +114,9 @@ namespace Spine {
/// <returns>-1 if the bone was not found.</returns> /// <returns>-1 if the bone was not found.</returns>
public int FindBoneIndex (string boneName) { public int FindBoneIndex (string boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
var bones = this.bones; var bones = this.bones.Items;
var bonesItems = bones.Items; for (int i = 0, n = this.bones.Count; i < n; i++)
for (int i = 0, n = bones.Count; i < n; i++) if (bones[i].name == boneName) return i;
if (bonesItems[i].name == boneName) return i;
return -1; return -1;
} }
@ -123,9 +125,9 @@ namespace Spine {
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public SlotData FindSlot (string slotName) { public SlotData FindSlot (string slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
ExposedList<SlotData> slots = this.slots; var slots = this.slots.Items;
for (int i = 0, n = slots.Count; i < n; i++) { for (int i = 0, n = this.slots.Count; i < n; i++) {
SlotData slot = slots.Items[i]; SlotData slot = slots[i];
if (slot.name == slotName) return slot; if (slot.name == slotName) return slot;
} }
return null; return null;
@ -165,9 +167,9 @@ namespace Spine {
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public Animation FindAnimation (string animationName) { public Animation FindAnimation (string animationName) {
if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null.");
ExposedList<Animation> animations = this.animations; var animations = this.animations.Items;
for (int i = 0, n = animations.Count; i < n; i++) { for (int i = 0, n = this.animations.Count; i < n; i++) {
Animation animation = animations.Items[i]; Animation animation = animations[i];
if (animation.name == animationName) return animation; if (animation.name == animationName) return animation;
} }
return null; return null;
@ -178,9 +180,9 @@ namespace Spine {
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public IkConstraintData FindIkConstraint (string constraintName) { public IkConstraintData FindIkConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<IkConstraintData> ikConstraints = this.ikConstraints; var ikConstraints = this.ikConstraints.Items;
for (int i = 0, n = ikConstraints.Count; i < n; i++) { for (int i = 0, n = this.ikConstraints.Count; i < n; i++) {
IkConstraintData ikConstraint = ikConstraints.Items[i]; IkConstraintData ikConstraint = ikConstraints[i];
if (ikConstraint.name == constraintName) return ikConstraint; if (ikConstraint.name == constraintName) return ikConstraint;
} }
return null; return null;
@ -191,9 +193,9 @@ namespace Spine {
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public TransformConstraintData FindTransformConstraint (string constraintName) { public TransformConstraintData FindTransformConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<TransformConstraintData> transformConstraints = this.transformConstraints; var transformConstraints = this.transformConstraints.Items;
for (int i = 0, n = transformConstraints.Count; i < n; i++) { for (int i = 0, n = this.transformConstraints.Count; i < n; i++) {
TransformConstraintData transformConstraint = transformConstraints.Items[i]; TransformConstraintData transformConstraint = transformConstraints[i];
if (transformConstraint.name == constraintName) return transformConstraint; if (transformConstraint.name == constraintName) return transformConstraint;
} }
return null; return null;
@ -204,9 +206,9 @@ namespace Spine {
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public PathConstraintData FindPathConstraint (string constraintName) { public PathConstraintData FindPathConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<PathConstraintData> pathConstraints = this.pathConstraints; var pathConstraints = this.pathConstraints.Items;
for (int i = 0, n = pathConstraints.Count; i < n; i++) { for (int i = 0, n = this.pathConstraints.Count; i < n; i++) {
PathConstraintData constraint = pathConstraints.Items[i]; PathConstraintData constraint = pathConstraints[i];
if (constraint.name.Equals(constraintName)) return constraint; if (constraint.name.Equals(constraintName)) return constraint;
} }
return null; return null;

View File

@ -41,23 +41,27 @@ using Windows.Storage;
#endif #endif
namespace Spine { namespace Spine {
public class SkeletonJson {
public float Scale { get; set; }
private AttachmentLoader attachmentLoader; /// <summary>
private List<LinkedMesh> linkedMeshes = new List<LinkedMesh>(); /// Loads skeleton data in the Spine JSON format.
/// <para>
/// JSON is human readable but the binary format is much smaller on disk and faster to load. See <see cref="SkeletonBinary"/>.</para>
/// <para>
/// See <a href="http://esotericsoftware.com/spine-json-format">Spine JSON format</a> and
/// <a href = "http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data" > JSON and binary data</a> in the Spine
/// Runtimes Guide.</para>
/// </summary>
public class SkeletonJson : SkeletonLoader {
public SkeletonJson (AttachmentLoader attachmentLoader)
: base(attachmentLoader) {
}
public SkeletonJson (params Atlas[] atlasArray) public SkeletonJson (params Atlas[] atlasArray)
: this(new AtlasAttachmentLoader(atlasArray)) { : base(atlasArray) {
} }
public SkeletonJson (AttachmentLoader attachmentLoader) { #if !IS_UNITY && WINDOWS_STOREAPP
if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null.");
this.attachmentLoader = attachmentLoader;
Scale = 1;
}
#if !IS_UNITY && WINDOWS_STOREAPP
private async Task<SkeletonData> ReadFile(string path) { private async Task<SkeletonData> ReadFile(string path) {
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false);
@ -68,11 +72,11 @@ namespace Spine {
} }
} }
public SkeletonData ReadSkeletonData (string path) { public override SkeletonData ReadSkeletonData (string path) {
return this.ReadFile(path).Result; return this.ReadFile(path).Result;
} }
#else #else
public SkeletonData ReadSkeletonData (string path) { public override SkeletonData ReadSkeletonData (string path) {
#if WINDOWS_PHONE #if WINDOWS_PHONE
using (var reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { using (var reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) {
#else #else
@ -88,7 +92,7 @@ namespace Spine {
public SkeletonData ReadSkeletonData (TextReader reader) { public SkeletonData ReadSkeletonData (TextReader reader) {
if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null.");
float scale = this.Scale; float scale = this.scale;
var skeletonData = new SkeletonData(); var skeletonData = new SkeletonData();
var root = Json.Deserialize(reader) as Dictionary<string, Object>; var root = Json.Deserialize(reader) as Dictionary<string, Object>;
@ -99,8 +103,6 @@ namespace Spine {
var skeletonMap = (Dictionary<string, Object>)root["skeleton"]; var skeletonMap = (Dictionary<string, Object>)root["skeleton"];
skeletonData.hash = (string)skeletonMap["hash"]; skeletonData.hash = (string)skeletonMap["hash"];
skeletonData.version = (string)skeletonMap["spine"]; skeletonData.version = (string)skeletonMap["spine"];
if ("3.8.75" == skeletonData.version)
throw new Exception("Unsupported skeleton data, please export with a newer version of Spine.");
skeletonData.x = GetFloat(skeletonMap, "x", 0); skeletonData.x = GetFloat(skeletonMap, "x", 0);
skeletonData.y = GetFloat(skeletonMap, "y", 0); skeletonData.y = GetFloat(skeletonMap, "y", 0);
skeletonData.width = GetFloat(skeletonMap, "width", 0); skeletonData.width = GetFloat(skeletonMap, "width", 0);
@ -283,6 +285,7 @@ namespace Spine {
skin.bones.Add(bone); skin.bones.Add(bone);
} }
} }
skin.bones.TrimExcess();
if (skinMap.ContainsKey("ik")) { if (skinMap.ContainsKey("ik")) {
foreach (string entryName in (List<Object>)skinMap["ik"]) { foreach (string entryName in (List<Object>)skinMap["ik"]) {
IkConstraintData constraint = skeletonData.FindIkConstraint(entryName); IkConstraintData constraint = skeletonData.FindIkConstraint(entryName);
@ -304,6 +307,7 @@ namespace Spine {
skin.constraints.Add(constraint); skin.constraints.Add(constraint);
} }
} }
skin.constraints.TrimExcess();
if (skinMap.ContainsKey("attachments")) { if (skinMap.ContainsKey("attachments")) {
foreach (KeyValuePair<string, Object> slotEntry in (Dictionary<string, Object>)skinMap["attachments"]) { foreach (KeyValuePair<string, Object> slotEntry in (Dictionary<string, Object>)skinMap["attachments"]) {
int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key);
@ -358,7 +362,7 @@ namespace Spine {
try { try {
ReadAnimation((Dictionary<string, Object>)entry.Value, entry.Key, skeletonData); ReadAnimation((Dictionary<string, Object>)entry.Value, entry.Key, skeletonData);
} catch (Exception e) { } catch (Exception e) {
throw new Exception("Error reading animation: " + entry.Key, e); throw new Exception("Error reading animation: " + entry.Key + "\n" + e.Message, e);
} }
} }
} }
@ -373,7 +377,7 @@ namespace Spine {
} }
private Attachment ReadAttachment (Dictionary<string, Object> map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) { private Attachment ReadAttachment (Dictionary<string, Object> map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) {
float scale = this.Scale; float scale = this.scale;
name = GetString(map, "name", name); name = GetString(map, "name", name);
var typeName = GetString(map, "type", "region"); var typeName = GetString(map, "type", "region");
@ -438,7 +442,7 @@ namespace Spine {
mesh.regionUVs = uvs; mesh.regionUVs = uvs;
mesh.UpdateUVs(); mesh.UpdateUVs();
if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2; if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) << 1;
if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges");
return mesh; return mesh;
} }
@ -505,7 +509,7 @@ namespace Spine {
for (int i = 0, n = vertices.Length; i < n;) { for (int i = 0, n = vertices.Length; i < n;) {
int boneCount = (int)vertices[i++]; int boneCount = (int)vertices[i++];
bones.Add(boneCount); bones.Add(boneCount);
for (int nn = i + boneCount * 4; i < nn; i += 4) { for (int nn = i + (boneCount << 2); i < nn; i += 4) {
bones.Add((int)vertices[i]); bones.Add((int)vertices[i]);
weights.Add(vertices[i + 1] * this.Scale); weights.Add(vertices[i + 1] * this.Scale);
weights.Add(vertices[i + 2] * this.Scale); weights.Add(vertices[i + 2] * this.Scale);
@ -517,9 +521,8 @@ namespace Spine {
} }
private void ReadAnimation (Dictionary<string, Object> map, string name, SkeletonData skeletonData) { private void ReadAnimation (Dictionary<string, Object> map, string name, SkeletonData skeletonData) {
var scale = this.Scale; var scale = this.scale;
var timelines = new ExposedList<Timeline>(); var timelines = new ExposedList<Timeline>();
float duration = 0;
// Slot timelines. // Slot timelines.
if (map.ContainsKey("slots")) { if (map.ContainsKey("slots")) {
@ -529,50 +532,117 @@ namespace Spine {
var timelineMap = (Dictionary<string, Object>)entry.Value; var timelineMap = (Dictionary<string, Object>)entry.Value;
foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) { foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
var values = (List<Object>)timelineEntry.Value; var values = (List<Object>)timelineEntry.Value;
if (values.Count == 0) continue;
var timelineName = (string)timelineEntry.Key; var timelineName = (string)timelineEntry.Key;
if (timelineName == "attachment") { if (timelineName == "attachment") {
var timeline = new AttachmentTimeline(values.Count); var timeline = new AttachmentTimeline(values.Count, slotIndex);
timeline.slotIndex = slotIndex; int frame = 0;
foreach (Dictionary<string, Object> keyMap in values) {
int frameIndex = 0; timeline.SetFrame(frame++, GetFloat(keyMap, "time", 0), (string)keyMap["name"]);
foreach (Dictionary<string, Object> valueMap in values) {
float time = GetFloat(valueMap, "time", 0);
timeline.SetFrame(frameIndex++, time, (string)valueMap["name"]);
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
} else if (timelineName == "color") { } else if (timelineName == "color") {
var timeline = new ColorTimeline(values.Count); var timeline = new ColorTimeline(values.Count, values.Count << 2, slotIndex);
timeline.slotIndex = slotIndex;
int frameIndex = 0; var keyMapEnumerator = values.GetEnumerator();
foreach (Dictionary<string, Object> valueMap in values) { keyMapEnumerator.MoveNext();
float time = GetFloat(valueMap, "time", 0); var keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
string c = (string)valueMap["color"]; float time = GetFloat(keyMap, "time", 0);
timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); string color = (string)keyMap["color"];
ReadCurve(valueMap, timeline, frameIndex); float r = ToColor(color, 0);
frameIndex++; float g = ToColor(color, 1);
float b = ToColor(color, 2);
float a = ToColor(color, 3);
for (int frame = 0, bezier = 0;; frame++) {
timeline.SetFrame(frame, time, r, g, b, a);
bool hasNext = keyMapEnumerator.MoveNext();
if (!hasNext) {
timeline.Shrink(bezier);
break;
}
var nextMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
float time2 = GetFloat(nextMap, "time", 0);
color = (string)nextMap["color"];
float nr = ToColor(color, 0);
float ng = ToColor(color, 1);
float nb = ToColor(color, 2);
float na = ToColor(color, 3);
if (keyMap.ContainsKey("curve")) {
object curve = keyMap["curve"];
bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1);
bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1);
bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1);
bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1);
}
time = time2;
r = nr;
g = ng;
b = nb;
a = na;
keyMap = nextMap;
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]);
} else if (timelineName == "twoColor") { } else if (timelineName == "twoColor") {
var timeline = new TwoColorTimeline(values.Count); var timeline = new TwoColorTimeline(values.Count, values.Count * 7, slotIndex);
timeline.slotIndex = slotIndex;
int frameIndex = 0; var keyMapEnumerator = values.GetEnumerator();
foreach (Dictionary<string, Object> valueMap in values) { keyMapEnumerator.MoveNext();
float time = GetFloat(valueMap, "time", 0); var keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
string light = (string)valueMap["light"]; float time = GetFloat(keyMap, "time", 0);
string dark = (string)valueMap["dark"]; string color = (string)keyMap["light"];
timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3), float r = ToColor(color, 0);
ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6)); float g = ToColor(color, 1);
ReadCurve(valueMap, timeline, frameIndex); float b = ToColor(color, 2);
frameIndex++; float a = ToColor(color, 3);
color = (string)keyMap["dark"];
float r2 = ToColor(color, 0);
float g2 = ToColor(color, 1);
float b2 = ToColor(color, 2);
for (int frame = 0, bezier = 0; ; frame++) {
timeline.SetFrame(frame, time, r, g, b, a, r2, g2, b2);
bool hasNext = keyMapEnumerator.MoveNext();
if (!hasNext) {
timeline.Shrink(bezier);
break;
}
var nextMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
float time2 = GetFloat(nextMap, "time", 0);
color = (string)nextMap["light"];
float nr = ToColor(color, 0);
float ng = ToColor(color, 1);
float nb = ToColor(color, 2);
float na = ToColor(color, 3);
color = (string)nextMap["dark"];
float nr2 = ToColor(color, 0);
float ng2 = ToColor(color, 1);
float nb2 = ToColor(color, 2);
if (keyMap.ContainsKey("curve")) {
object curve = keyMap["curve"];
bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1);
bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1);
bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1);
bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1);
bezier = ReadCurve(curve, timeline, bezier, frame, 4, time, time2, r2, nr2, 1);
bezier = ReadCurve(curve, timeline, bezier, frame, 5, time, time2, g2, ng2, 1);
bezier = ReadCurve(curve, timeline, bezier, frame, 6, time, time2, b2, nb2, 1);
}
time = time2;
r = nr;
g = ng;
b = nb;
a = na;
r2 = nr2;
g2 = ng2;
b2 = nb2;
keyMap = nextMap;
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]);
} else } else
throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");
@ -589,47 +659,23 @@ namespace Spine {
var timelineMap = (Dictionary<string, Object>)entry.Value; var timelineMap = (Dictionary<string, Object>)entry.Value;
foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) { foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
var values = (List<Object>)timelineEntry.Value; var values = (List<Object>)timelineEntry.Value;
var keyMapEnumerator = values.GetEnumerator();
bool hasNext = keyMapEnumerator.MoveNext();
if (!hasNext) continue;
var timelineName = (string)timelineEntry.Key; var timelineName = (string)timelineEntry.Key;
if (timelineName == "rotate") { if (timelineName == "rotate")
var timeline = new RotateTimeline(values.Count); timelines.Add(ReadTimeline(ref keyMapEnumerator, new RotateTimeline(values.Count, values.Count, boneIndex), 0, 1));
timeline.boneIndex = boneIndex; else if (timelineName == "translate") {
TranslateTimeline timeline = new TranslateTimeline(values.Count, values.Count << 1, boneIndex);
int frameIndex = 0; timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, scale));
foreach (Dictionary<string, Object> valueMap in values) { }
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "angle", 0)); else if (timelineName == "scale") {
ReadCurve(valueMap, timeline, frameIndex); ScaleTimeline timeline = new ScaleTimeline(values.Count, values.Count << 1, boneIndex);
frameIndex++; timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 1, 1));
} }
timelines.Add(timeline); else if (timelineName == "shear") {
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]); ShearTimeline timeline = new ShearTimeline(values.Count, values.Count << 1, boneIndex);
timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, 1));
} else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") {
TranslateTimeline timeline;
float timelineScale = 1, defaultValue = 0;
if (timelineName == "scale") {
timeline = new ScaleTimeline(values.Count);
defaultValue = 1;
}
else if (timelineName == "shear")
timeline = new ShearTimeline(values.Count);
else {
timeline = new TranslateTimeline(values.Count);
timelineScale = scale;
}
timeline.boneIndex = boneIndex;
int frameIndex = 0;
foreach (Dictionary<string, Object> valueMap in values) {
float time = GetFloat(valueMap, "time", 0);
float x = GetFloat(valueMap, "x", defaultValue);
float y = GetFloat(valueMap, "y", defaultValue);
timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale);
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]);
} else } else
throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
} }
@ -638,40 +684,82 @@ namespace Spine {
// IK constraint timelines. // IK constraint timelines.
if (map.ContainsKey("ik")) { if (map.ContainsKey("ik")) {
foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["ik"]) { foreach (KeyValuePair<string, Object> timelineMap in (Dictionary<string, Object>)map["ik"]) {
IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); var timelineMapValues = (List<Object>)timelineMap.Value;
var values = (List<Object>)constraintMap.Value; var keyMapEnumerator = timelineMapValues.GetEnumerator();
var timeline = new IkConstraintTimeline(values.Count); bool hasNext = keyMapEnumerator.MoveNext();
timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); if (!hasNext) continue;
int frameIndex = 0; var keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
foreach (Dictionary<string, Object> valueMap in values) { IkConstraintData constraint = skeletonData.FindIkConstraint(timelineMap.Key);
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "mix", 1), IkConstraintTimeline timeline = new IkConstraintTimeline(timelineMapValues.Count, timelineMapValues.Count << 1,
GetFloat(valueMap, "softness", 0) * scale, GetBoolean(valueMap, "bendPositive", true) ? 1 : -1, skeletonData.IkConstraints.IndexOf(constraint));
GetBoolean(valueMap, "compress", false), GetBoolean(valueMap, "stretch", false)); float time = GetFloat(keyMap, "time", 0);
ReadCurve(valueMap, timeline, frameIndex); float mix = GetFloat(keyMap, "mix", 1), softness = GetFloat(keyMap, "softness", 0) * scale;
frameIndex++; for (int frame = 0, bezier = 0; ; frame++) {
timeline.SetFrame(frame, time, mix, softness, GetBoolean(keyMap, "bendPositive", true) ? 1 : -1,
GetBoolean(keyMap, "compress", false), GetBoolean(keyMap, "stretch", false));
hasNext = keyMapEnumerator.MoveNext();
if (!hasNext) {
timeline.Shrink(bezier);
break;
}
var nextMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
float time2 = GetFloat(nextMap, "time", 0);
float mix2 = GetFloat(nextMap, "mix", 1), softness2 = GetFloat(nextMap, "softness", 0) * scale;
if (keyMap.ContainsKey("curve")) {
object curve = keyMap["curve"];
bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, mix, mix2, 1);
bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, softness, softness2, scale);
}
time = time2;
mix = mix2;
softness = softness2;
keyMap = nextMap;
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]);
} }
} }
// Transform constraint timelines. // Transform constraint timelines.
if (map.ContainsKey("transform")) { if (map.ContainsKey("transform")) {
foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["transform"]) { foreach (KeyValuePair<string, Object> timelineMap in (Dictionary<string, Object>)map["transform"]) {
TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); var timelineMapValues = (List<Object>)timelineMap.Value;
var values = (List<Object>)constraintMap.Value; var keyMapEnumerator = timelineMapValues.GetEnumerator();
var timeline = new TransformConstraintTimeline(values.Count); bool hasNext = keyMapEnumerator.MoveNext();
timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); if (!hasNext) continue;
int frameIndex = 0; var keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
foreach (Dictionary<string, Object> valueMap in values) { TransformConstraintData constraint = skeletonData.FindTransformConstraint(timelineMap.Key);
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "rotateMix", 1), TransformConstraintTimeline timeline = new TransformConstraintTimeline(timelineMapValues.Count, timelineMapValues.Count << 2,
GetFloat(valueMap, "translateMix", 1), GetFloat(valueMap, "scaleMix", 1), GetFloat(valueMap, "shearMix", 1)); skeletonData.TransformConstraints.IndexOf(constraint));
ReadCurve(valueMap, timeline, frameIndex); float time = GetFloat(keyMap, "time", 0);
frameIndex++; float rotateMix = GetFloat(keyMap, "rotateMix", 1), translateMix = GetFloat(keyMap, "translateMix", 1);
float scaleMix = GetFloat(keyMap, "scaleMix", 1), shearMix = GetFloat(keyMap, "shearMix", 1);
for (int frame = 0, bezier = 0; ; frame++) {
timeline.SetFrame(frame, time, rotateMix, translateMix, scaleMix, shearMix);
hasNext = keyMapEnumerator.MoveNext();
if (!hasNext) {
timeline.Shrink(bezier);
break;
}
var nextMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
float time2 = GetFloat(nextMap, "time", 0);
float rotateMix2 = GetFloat(nextMap, "rotateMix", 1), translateMix2 = GetFloat(nextMap, "translateMix", 1);
float scaleMix2 = GetFloat(nextMap, "scaleMix", 1), shearMix2 = GetFloat(nextMap, "shearMix", 1);
if (keyMap.ContainsKey("curve")) {
object curve = keyMap["curve"];
bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, rotateMix, rotateMix2, 1);
bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, translateMix, translateMix2, 1);
bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, scaleMix, scaleMix2, 1);
bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, shearMix, shearMix2, 1);
}
time = time2;
rotateMix = rotateMix2;
translateMix = translateMix2;
scaleMix = scaleMix2;
shearMix = shearMix2;
keyMap = nextMap;
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]);
} }
} }
@ -684,40 +772,22 @@ namespace Spine {
var timelineMap = (Dictionary<string, Object>)constraintMap.Value; var timelineMap = (Dictionary<string, Object>)constraintMap.Value;
foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) { foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
var values = (List<Object>)timelineEntry.Value; var values = (List<Object>)timelineEntry.Value;
var keyMapEnumerator = values.GetEnumerator();
bool hasNext = keyMapEnumerator.MoveNext();
if (!hasNext) continue;
var timelineName = (string)timelineEntry.Key; var timelineName = (string)timelineEntry.Key;
if (timelineName == "position" || timelineName == "spacing") { if (timelineName == "position") {
PathConstraintPositionTimeline timeline; CurveTimeline1 timeline = new PathConstraintPositionTimeline(values.Count, values.Count, index);
float timelineScale = 1; timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, data.positionMode == PositionMode.Fixed ? scale : 1));
if (timelineName == "spacing") { }
timeline = new PathConstraintSpacingTimeline(values.Count); else if (timelineName == "spacing") {
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; CurveTimeline1 timeline = new PathConstraintSpacingTimeline(values.Count, values.Count, index);
} timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0,
else { data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed ? scale : 1));
timeline = new PathConstraintPositionTimeline(values.Count);
if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
}
timeline.pathConstraintIndex = index;
int frameIndex = 0;
foreach (Dictionary<string, Object> valueMap in values) {
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, timelineName, 0) * timelineScale);
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
} }
else if (timelineName == "mix") { else if (timelineName == "mix") {
PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count); CurveTimeline2 timeline = new PathConstraintMixTimeline(values.Count, values.Count << 1, index);
timeline.pathConstraintIndex = index; timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "rotateMix", "translateMix", 1, 1));
int frameIndex = 0;
foreach (Dictionary<string, Object> valueMap in values) {
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "rotateMix", 1),
GetFloat(valueMap, "translateMix", 1));
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
} }
} }
} }
@ -731,26 +801,26 @@ namespace Spine {
int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); int slotIndex = skeletonData.FindSlotIndex(slotMap.Key);
if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key); if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key);
foreach (KeyValuePair<string, Object> timelineMap in (Dictionary<string, Object>)slotMap.Value) { foreach (KeyValuePair<string, Object> timelineMap in (Dictionary<string, Object>)slotMap.Value) {
var values = (List<Object>)timelineMap.Value; var timelineMapValues = (List<Object>)timelineMap.Value;
var keyMapEnumerator = timelineMapValues.GetEnumerator();
bool hasNext = keyMapEnumerator.MoveNext();
if (!hasNext) continue;
var keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key);
if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key);
bool weighted = attachment.bones != null; bool weighted = attachment.bones != null;
float[] vertices = attachment.vertices; float[] vertices = attachment.vertices;
int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length;
DeformTimeline timeline = new DeformTimeline(timelineMapValues.Count, timelineMapValues.Count, slotIndex, attachment);
var timeline = new DeformTimeline(values.Count); float time = GetFloat(keyMap, "time", 0);
timeline.slotIndex = slotIndex; for (int frame = 0, bezier = 0; ; frame++) {
timeline.attachment = attachment;
int frameIndex = 0;
foreach (Dictionary<string, Object> valueMap in values) {
float[] deform; float[] deform;
if (!valueMap.ContainsKey("vertices")) { if (!keyMap.ContainsKey("vertices")) {
deform = weighted ? new float[deformLength] : vertices; deform = weighted ? new float[deformLength] : vertices;
} else { } else {
deform = new float[deformLength]; deform = new float[deformLength];
int start = GetInt(valueMap, "offset", 0); int start = GetInt(keyMap, "offset", 0);
float[] verticesValue = GetFloatArray(valueMap, "vertices", 1); float[] verticesValue = GetFloatArray(keyMap, "vertices", 1);
Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); Array.Copy(verticesValue, 0, deform, start, verticesValue.Length);
if (scale != 1) { if (scale != 1) {
for (int i = start, n = i + verticesValue.Length; i < n; i++) for (int i = start, n = i + verticesValue.Length; i < n; i++)
@ -763,12 +833,22 @@ namespace Spine {
} }
} }
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), deform); timeline.SetFrame(frame, time, deform);
ReadCurve(valueMap, timeline, frameIndex); hasNext = keyMapEnumerator.MoveNext();
frameIndex++; if (!hasNext) {
timeline.Shrink(bezier);
break;
}
var nextMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
float time2 = GetFloat(nextMap, "time", 0);
if (keyMap.ContainsKey("curve")) {
object curve = keyMap["curve"];
bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1);
}
time = time2;
keyMap = nextMap;
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
} }
} }
} }
@ -779,7 +859,7 @@ namespace Spine {
var values = (List<Object>)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; var values = (List<Object>)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"];
var timeline = new DrawOrderTimeline(values.Count); var timeline = new DrawOrderTimeline(values.Count);
int slotCount = skeletonData.slots.Count; int slotCount = skeletonData.slots.Count;
int frameIndex = 0; int frame = 0;
foreach (Dictionary<string, Object> drawOrderMap in values) { foreach (Dictionary<string, Object> drawOrderMap in values) {
int[] drawOrder = null; int[] drawOrder = null;
if (drawOrderMap.ContainsKey("offsets")) { if (drawOrderMap.ContainsKey("offsets")) {
@ -806,17 +886,17 @@ namespace Spine {
for (int i = slotCount - 1; i >= 0; i--) for (int i = slotCount - 1; i >= 0; i--)
if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex];
} }
timeline.SetFrame(frameIndex++, GetFloat(drawOrderMap, "time", 0), drawOrder); timeline.SetFrame(frame, GetFloat(drawOrderMap, "time", 0), drawOrder);
++frame;
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
} }
// Event timeline. // Event timeline.
if (map.ContainsKey("events")) { if (map.ContainsKey("events")) {
var eventsMap = (List<Object>)map["events"]; var eventsMap = (List<Object>)map["events"];
var timeline = new EventTimeline(eventsMap.Count); var timeline = new EventTimeline(eventsMap.Count);
int frameIndex = 0; int frame = 0;
foreach (Dictionary<string, Object> eventMap in eventsMap) { foreach (Dictionary<string, Object> eventMap in eventsMap) {
EventData eventData = skeletonData.FindEvent((string)eventMap["name"]); EventData eventData = skeletonData.FindEvent((string)eventMap["name"]);
if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]);
@ -829,39 +909,97 @@ namespace Spine {
e.volume = GetFloat(eventMap, "volume", eventData.Volume); e.volume = GetFloat(eventMap, "volume", eventData.Volume);
e.balance = GetFloat(eventMap, "balance", eventData.Balance); e.balance = GetFloat(eventMap, "balance", eventData.Balance);
} }
timeline.SetFrame(frameIndex++, e); timeline.SetFrame(frame, e);
++frame;
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
} }
timelines.TrimExcess(); timelines.TrimExcess();
float duration = 0;
var items = timelines.Items;
for (int i = 0, n = timelines.Count; i < n; i++)
duration = Math.Max(duration, items[i].Duration);
skeletonData.animations.Add(new Animation(name, timelines, duration)); skeletonData.animations.Add(new Animation(name, timelines, duration));
} }
static void ReadCurve (Dictionary<string, Object> valueMap, CurveTimeline timeline, int frameIndex) { static Timeline ReadTimeline (ref List<object>.Enumerator keyMapEnumerator, CurveTimeline1 timeline, float defaultValue, float scale) {
if (!valueMap.ContainsKey("curve")) var keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
return; float time = GetFloat(keyMap, "time", 0);
Object curveObject = valueMap["curve"]; float value = GetFloat(keyMap, "value", defaultValue) * scale;
if (curveObject is string) int bezier = 0;
timeline.SetStepped(frameIndex); for (int frame = 0; ; frame++) {
else timeline.SetFrame(frame, time, value);
timeline.SetCurve(frameIndex, (float)curveObject, GetFloat(valueMap, "c2", 0), GetFloat(valueMap, "c3", 1), GetFloat(valueMap, "c4", 1)); bool hasNext = keyMapEnumerator.MoveNext();
if (!hasNext)
break;
var nextMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
float time2 = GetFloat(nextMap, "time", 0);
float value2 = GetFloat(nextMap, "value", defaultValue) * scale;
if (keyMap.ContainsKey("curve")) {
object curve = keyMap["curve"];
bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2, scale);
}
time = time2;
value = value2;
keyMap = nextMap;
}
timeline.Shrink(bezier);
return timeline;
} }
internal class LinkedMesh { static Timeline ReadTimeline (ref List<object>.Enumerator keyMapEnumerator, CurveTimeline2 timeline, String name1, String name2, float defaultValue,
internal string parent, skin; float scale) {
internal int slotIndex;
internal MeshAttachment mesh;
internal bool inheritDeform;
public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritDeform) { var keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
this.mesh = mesh; float time = GetFloat(keyMap, "time", 0);
this.skin = skin; float value1 = GetFloat(keyMap, name1, defaultValue) * scale, value2 = GetFloat(keyMap, name2, defaultValue) * scale;
this.slotIndex = slotIndex; int bezier = 0;
this.parent = parent; for (int frame = 0; ; frame++) {
this.inheritDeform = inheritDeform; timeline.SetFrame(frame, time, value1, value2);
bool hasNext = keyMapEnumerator.MoveNext();
if (!hasNext)
break;
var nextMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
float time2 = GetFloat(nextMap, "time", 0);
float nvalue1 = GetFloat(nextMap, name1, defaultValue) * scale, nvalue2 = GetFloat(nextMap, name2, defaultValue) * scale;
if (keyMap.ContainsKey("curve")) {
object curve = keyMap["curve"];
bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, value1, nvalue1, scale);
bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, value2, nvalue2, scale);
}
time = time2;
value1 = nvalue1;
value2 = nvalue2;
keyMap = nextMap;
} }
timeline.Shrink(bezier);
return timeline;
}
static int ReadCurve (object curve, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2,
float value1, float value2, float scale) {
if (curve is string) {
if (value != 0) timeline.SetStepped(frame);
}
else {
var curveValues = (List<object>)curve;
int index = value << 2;
float cx1 = (float)curveValues[index];
++index;
float cy1 = ((float)curveValues[index]) * scale;
++index;
float cx2 = (float)curveValues[index];
++index;
float cy2 = (float)curveValues[index] * scale;
SetBezier(timeline, frame, value, bezier++, time1, value1, cx1, cy1, cx2, cy2, time2, value2);
}
return bezier;
}
static void SetBezier (CurveTimeline timeline, int frame, int value, int bezier, float time1, float value1, float cx1, float cy1,
float cx2, float cy2, float time2, float value2) {
timeline.SetBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2);
} }
static float[] GetFloatArray(Dictionary<string, Object> map, string name, float scale) { static float[] GetFloatArray(Dictionary<string, Object> map, string name, float scale) {

View File

@ -0,0 +1,92 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated January 1, 2020. Replaces all prior versions.
*
* Copyright (c) 2013-2020, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.IO;
using System.Collections.Generic;
namespace Spine {
/// <summary>
/// Base class for loading skeleton data from a file.
/// <para>
/// See<a href="http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data">JSON and binary data</a> in the
/// Spine Runtimes Guide.</para>
/// </summary>
public abstract class SkeletonLoader {
protected readonly AttachmentLoader attachmentLoader;
protected float scale = 1;
protected readonly List<LinkedMesh> linkedMeshes = new List<LinkedMesh>();
/// <summary>Creates a skeleton loader that loads attachments using an <see cref="AtlasAttachmentLoader"/> with the specified atlas.
/// </summary>
public SkeletonLoader (params Atlas[] atlasArray) {
attachmentLoader = new AtlasAttachmentLoader(atlasArray);
}
/// <summary>Creates a skeleton loader that loads attachments using the specified attachment loader.
/// <para>See <a href='http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data'>Loading skeleton data</a> in the
/// Spine Runtimes Guide.</para></summary>
public SkeletonLoader (AttachmentLoader attachmentLoader) {
if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null.");
this.attachmentLoader = attachmentLoader;
}
/// <summary>Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at
/// runtime than were used in Spine.
/// <para>
/// See <a href="http://esotericsoftware.com/spine-loading-skeleton-data#Scaling">Scaling</a> in the Spine Runtimes Guide.</para>
/// </summary>
public float Scale {
get { return scale; }
set {
if (scale == 0) throw new ArgumentNullException("scale", "scale cannot be 0.");
this.scale = value;
}
}
public abstract SkeletonData ReadSkeletonData (string path);
protected class LinkedMesh {
internal string parent, skin;
internal int slotIndex;
internal MeshAttachment mesh;
internal bool inheritDeform;
public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritDeform) {
this.mesh = mesh;
this.skin = skin;
this.slotIndex = slotIndex;
this.parent = parent;
this.inheritDeform = inheritDeform;
}
}
}
}

View File

@ -39,6 +39,8 @@ namespace Spine {
/// </summary> /// </summary>
public class Skin { public class Skin {
internal string name; internal string name;
// Difference to reference implementation: using Dictionary<SkinKey, SkinEntry> instead of HashSet<SkinEntry>.
// Reason is that there is no efficient way to replace or access an already added element, losing any benefits.
private Dictionary<SkinKey, SkinEntry> attachments = new Dictionary<SkinKey, SkinEntry>(SkinKeyComparer.Instance); private Dictionary<SkinKey, SkinEntry> attachments = new Dictionary<SkinKey, SkinEntry>(SkinKeyComparer.Instance);
internal readonly ExposedList<BoneData> bones = new ExposedList<BoneData>(); internal readonly ExposedList<BoneData> bones = new ExposedList<BoneData>();
internal readonly ExposedList<ConstraintData> constraints = new ExposedList<ConstraintData>(); internal readonly ExposedList<ConstraintData> constraints = new ExposedList<ConstraintData>();
@ -58,7 +60,6 @@ namespace Spine {
/// If the name already exists for the slot, the previous value is replaced.</summary> /// If the name already exists for the slot, the previous value is replaced.</summary>
public void SetAttachment (int slotIndex, string name, Attachment attachment) { public void SetAttachment (int slotIndex, string name, Attachment attachment) {
if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null.");
if (slotIndex < 0) throw new ArgumentNullException("slotIndex", "slotIndex must be >= 0.");
attachments[new SkinKey(slotIndex, name)] = new SkinEntry(slotIndex, name, attachment); attachments[new SkinKey(slotIndex, name)] = new SkinEntry(slotIndex, name, attachment);
} }
@ -104,13 +105,14 @@ namespace Spine {
/// <summary> Removes the attachment in the skin for the specified slot index and name, if any.</summary> /// <summary> Removes the attachment in the skin for the specified slot index and name, if any.</summary>
public void RemoveAttachment (int slotIndex, string name) { public void RemoveAttachment (int slotIndex, string name) {
if (slotIndex < 0) throw new ArgumentOutOfRangeException("slotIndex", "slotIndex must be >= 0");
attachments.Remove(new SkinKey(slotIndex, name)); attachments.Remove(new SkinKey(slotIndex, name));
} }
/// <summary>Returns all attachments in this skin for the specified slot index.</summary> /// <summary>Returns all attachments in this skin for the specified slot index.</summary>
/// <param name="slotIndex">The target slotIndex. To find the slot index, use <see cref="Spine.Skeleton.FindSlotIndex"/> or <see cref="Spine.SkeletonData.FindSlotIndex"/> /// <param name="slotIndex">The target slotIndex. To find the slot index, use <see cref="Spine.Skeleton.FindSlotIndex"/> or <see cref="Spine.SkeletonData.FindSlotIndex"/>
public void GetAttachments (int slotIndex, List<SkinEntry> attachments) { public void GetAttachments (int slotIndex, List<SkinEntry> attachments) {
if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0.");
if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null.");
foreach (var item in this.attachments) { foreach (var item in this.attachments) {
SkinEntry entry = item.Value; SkinEntry entry = item.Value;
if (entry.slotIndex == slotIndex) attachments.Add(entry); if (entry.slotIndex == slotIndex) attachments.Add(entry);
@ -176,10 +178,14 @@ namespace Spine {
private struct SkinKey { private struct SkinKey {
internal readonly int slotIndex; internal readonly int slotIndex;
internal readonly string name; internal readonly string name;
internal readonly int hashCode;
public SkinKey (int slotIndex, string name) { public SkinKey (int slotIndex, string name) {
if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0.");
if (name == null) throw new ArgumentNullException("name", "name cannot be null");
this.slotIndex = slotIndex; this.slotIndex = slotIndex;
this.name = name; this.name = name;
this.hashCode = name.GetHashCode() + slotIndex * 37;
} }
} }
@ -191,7 +197,7 @@ namespace Spine {
} }
int IEqualityComparer<SkinKey>.GetHashCode (SkinKey e) { int IEqualityComparer<SkinKey>.GetHashCode (SkinKey e) {
return e.name.GetHashCode() + e.slotIndex * 37; return e.hashCode;
} }
} }
} }

View File

@ -32,7 +32,7 @@ using System;
namespace Spine { namespace Spine {
/// <summary> /// <summary>
/// Stores a slot's current pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store /// Stores a slot's current pose. Slots organize attachments for <see cref="Skeleton.DrawOrder"/> purposes and provide a place to store
/// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared /// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared
/// across multiple skeletons. /// across multiple skeletons.
/// </summary> /// </summary>

View File

@ -76,12 +76,8 @@ namespace Spine {
shearMix = constraint.shearMix; shearMix = constraint.shearMix;
} }
/// <summary>Applies the constraint to the constrained bones.</summary>
public void Apply () {
Update();
}
public void Update () { public void Update () {
if (rotateMix == 0 && translateMix == 0 && scaleMix == 0 && shearMix == 0) return;
if (data.local) { if (data.local) {
if (data.relative) if (data.relative)
ApplyRelativeLocal(); ApplyRelativeLocal();
@ -101,10 +97,9 @@ namespace Spine {
float ta = target.a, tb = target.b, tc = target.c, td = target.d; float ta = target.a, tb = target.b, tc = target.c, td = target.d;
float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect;
var bones = this.bones; var bones = this.bones.Items;
for (int i = 0, n = bones.Count; i < n; i++) { for (int i = 0, n = this.bones.Count; i < n; i++) {
Bone bone = bones.Items[i]; Bone bone = bones[i];
bool modified = false;
if (rotateMix != 0) { if (rotateMix != 0) {
float a = bone.a, b = bone.b, c = bone.c, d = bone.d; float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
@ -118,7 +113,6 @@ namespace Spine {
bone.b = cos * b - sin * d; bone.b = cos * b - sin * d;
bone.c = sin * a + cos * c; bone.c = sin * a + cos * c;
bone.d = sin * b + cos * d; bone.d = sin * b + cos * d;
modified = true;
} }
if (translateMix != 0) { if (translateMix != 0) {
@ -126,7 +120,6 @@ namespace Spine {
target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY));
bone.worldX += (tx - bone.worldX) * translateMix; bone.worldX += (tx - bone.worldX) * translateMix;
bone.worldY += (ty - bone.worldY) * translateMix; bone.worldY += (ty - bone.worldY) * translateMix;
modified = true;
} }
if (scaleMix > 0) { if (scaleMix > 0) {
@ -138,7 +131,6 @@ namespace Spine {
if (s != 0) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * scaleMix) / s; if (s != 0) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * scaleMix) / s;
bone.b *= s; bone.b *= s;
bone.d *= s; bone.d *= s;
modified = true;
} }
if (shearMix > 0) { if (shearMix > 0) {
@ -152,10 +144,9 @@ namespace Spine {
float s = (float)Math.Sqrt(b * b + d * d); float s = (float)Math.Sqrt(b * b + d * d);
bone.b = MathUtils.Cos(r) * s; bone.b = MathUtils.Cos(r) * s;
bone.d = MathUtils.Sin(r) * s; bone.d = MathUtils.Sin(r) * s;
modified = true;
} }
if (modified) bone.appliedValid = false; bone.appliedValid = false;
} }
} }
@ -165,10 +156,9 @@ namespace Spine {
float ta = target.a, tb = target.b, tc = target.c, td = target.d; float ta = target.a, tb = target.b, tc = target.c, td = target.d;
float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect;
var bones = this.bones; var bones = this.bones.Items;
for (int i = 0, n = bones.Count; i < n; i++) { for (int i = 0, n = this.bones.Count; i < n; i++) {
Bone bone = bones.Items[i]; Bone bone = bones[i];
bool modified = false;
if (rotateMix != 0) { if (rotateMix != 0) {
float a = bone.a, b = bone.b, c = bone.c, d = bone.d; float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
@ -182,7 +172,6 @@ namespace Spine {
bone.b = cos * b - sin * d; bone.b = cos * b - sin * d;
bone.c = sin * a + cos * c; bone.c = sin * a + cos * c;
bone.d = sin * b + cos * d; bone.d = sin * b + cos * d;
modified = true;
} }
if (translateMix != 0) { if (translateMix != 0) {
@ -190,7 +179,6 @@ namespace Spine {
target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY));
bone.worldX += tx * translateMix; bone.worldX += tx * translateMix;
bone.worldY += ty * translateMix; bone.worldY += ty * translateMix;
modified = true;
} }
if (scaleMix > 0) { if (scaleMix > 0) {
@ -200,7 +188,6 @@ namespace Spine {
s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1; s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1;
bone.b *= s; bone.b *= s;
bone.d *= s; bone.d *= s;
modified = true;
} }
if (shearMix > 0) { if (shearMix > 0) {
@ -213,10 +200,9 @@ namespace Spine {
float s = (float)Math.Sqrt(b * b + d * d); float s = (float)Math.Sqrt(b * b + d * d);
bone.b = MathUtils.Cos(r) * s; bone.b = MathUtils.Cos(r) * s;
bone.d = MathUtils.Sin(r) * s; bone.d = MathUtils.Sin(r) * s;
modified = true;
} }
if (modified) bone.appliedValid = false; bone.appliedValid = false;
} }
} }
@ -224,9 +210,9 @@ namespace Spine {
float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
Bone target = this.target; Bone target = this.target;
if (!target.appliedValid) target.UpdateAppliedTransform(); if (!target.appliedValid) target.UpdateAppliedTransform();
var bonesItems = this.bones.Items; var bones = this.bones.Items;
for (int i = 0, n = this.bones.Count; i < n; i++) { for (int i = 0, n = this.bones.Count; i < n; i++) {
Bone bone = bonesItems[i]; Bone bone = bones[i];
if (!bone.appliedValid) bone.UpdateAppliedTransform(); if (!bone.appliedValid) bone.UpdateAppliedTransform();
float rotation = bone.arotation; float rotation = bone.arotation;
@ -263,9 +249,9 @@ namespace Spine {
float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
Bone target = this.target; Bone target = this.target;
if (!target.appliedValid) target.UpdateAppliedTransform(); if (!target.appliedValid) target.UpdateAppliedTransform();
var bonesItems = this.bones.Items; var bones = this.bones.Items;
for (int i = 0, n = this.bones.Count; i < n; i++) { for (int i = 0, n = this.bones.Count; i < n; i++) {
Bone bone = bonesItems[i]; Bone bone = bones[i];
if (!bone.appliedValid) bone.UpdateAppliedTransform(); if (!bone.appliedValid) bone.UpdateAppliedTransform();
float rotation = bone.arotation; float rotation = bone.arotation;

View File

@ -61,32 +61,35 @@ namespace Spine.Unity.Examples {
// Build a reference collection of timelines to match // Build a reference collection of timelines to match
// and a collection of dummy timelines that can be used to fill-in missing items. // and a collection of dummy timelines that can be used to fill-in missing items.
var timelineDictionary = new Dictionary<int, Spine.Timeline>(); var timelineDictionary = new Dictionary<string, Spine.Timeline>();
foreach (var animation in animations) { foreach (var animation in animations) {
foreach (var timeline in animation.Timelines) { foreach (var timeline in animation.Timelines) {
if (timeline is EventTimeline) continue; if (timeline is EventTimeline) continue;
int propertyID = timeline.PropertyId; foreach (string propertyId in timeline.PropertyIds) {
if (!timelineDictionary.ContainsKey(propertyID)) { if (!timelineDictionary.ContainsKey(propertyId)) {
timelineDictionary.Add(propertyID, GetFillerTimeline(timeline, skeletonData)); timelineDictionary.Add(propertyId, GetFillerTimeline(timeline, skeletonData));
}
} }
} }
} }
var idsToMatch = new List<int>(timelineDictionary.Keys); var idsToMatch = new List<string>(timelineDictionary.Keys);
// For each animation in the list, check for and add missing timelines. // For each animation in the list, check for and add missing timelines.
var currentAnimationIDs = new HashSet<int>(); var currentAnimationIDs = new HashSet<string>();
foreach (var animation in animations) { foreach (var animation in animations) {
currentAnimationIDs.Clear(); currentAnimationIDs.Clear();
foreach (var timeline in animation.Timelines) { foreach (var timeline in animation.Timelines) {
if (timeline is EventTimeline) continue; if (timeline is EventTimeline) continue;
currentAnimationIDs.Add(timeline.PropertyId); foreach (string propertyId in timeline.PropertyIds) {
currentAnimationIDs.Add(propertyId);
}
} }
var animationTimelines = animation.Timelines; var animationTimelines = animation.Timelines;
foreach (int propertyID in idsToMatch) { foreach (string propertyId in idsToMatch) {
if (!currentAnimationIDs.Contains(propertyID)) if (!currentAnimationIDs.Contains(propertyId))
animationTimelines.Add(timelineDictionary[propertyID]); animationTimelines.Add(timelineDictionary[propertyId]);
} }
} }
@ -132,62 +135,52 @@ namespace Spine.Unity.Examples {
} }
static RotateTimeline GetFillerTimeline (RotateTimeline timeline, SkeletonData skeletonData) { static RotateTimeline GetFillerTimeline (RotateTimeline timeline, SkeletonData skeletonData) {
var t = new RotateTimeline(1); var t = new RotateTimeline(1, 0, timeline.BoneIndex);
t.BoneIndex = timeline.BoneIndex;
t.SetFrame(0, 0, 0); t.SetFrame(0, 0, 0);
return t; return t;
} }
static TranslateTimeline GetFillerTimeline (TranslateTimeline timeline, SkeletonData skeletonData) { static TranslateTimeline GetFillerTimeline (TranslateTimeline timeline, SkeletonData skeletonData) {
var t = new TranslateTimeline(1); var t = new TranslateTimeline(1, 0, timeline.BoneIndex);
t.BoneIndex = timeline.BoneIndex;
t.SetFrame(0, 0, 0, 0); t.SetFrame(0, 0, 0, 0);
return t; return t;
} }
static ScaleTimeline GetFillerTimeline (ScaleTimeline timeline, SkeletonData skeletonData) { static ScaleTimeline GetFillerTimeline (ScaleTimeline timeline, SkeletonData skeletonData) {
var t = new ScaleTimeline(1); var t = new ScaleTimeline(1, 0, timeline.BoneIndex);
t.BoneIndex = timeline.BoneIndex;
t.SetFrame(0, 0, 0, 0); t.SetFrame(0, 0, 0, 0);
return t; return t;
} }
static ShearTimeline GetFillerTimeline (ShearTimeline timeline, SkeletonData skeletonData) { static ShearTimeline GetFillerTimeline (ShearTimeline timeline, SkeletonData skeletonData) {
var t = new ShearTimeline(1); var t = new ShearTimeline(1, 0, timeline.BoneIndex);
t.BoneIndex = timeline.BoneIndex;
t.SetFrame(0, 0, 0, 0); t.SetFrame(0, 0, 0, 0);
return t; return t;
} }
static AttachmentTimeline GetFillerTimeline (AttachmentTimeline timeline, SkeletonData skeletonData) { static AttachmentTimeline GetFillerTimeline (AttachmentTimeline timeline, SkeletonData skeletonData) {
var t = new AttachmentTimeline(1); var t = new AttachmentTimeline(1, timeline.SlotIndex);
t.SlotIndex = timeline.SlotIndex;
var slotData = skeletonData.Slots.Items[t.SlotIndex]; var slotData = skeletonData.Slots.Items[t.SlotIndex];
t.SetFrame(0, 0, slotData.AttachmentName); t.SetFrame(0, 0, slotData.AttachmentName);
return t; return t;
} }
static ColorTimeline GetFillerTimeline (ColorTimeline timeline, SkeletonData skeletonData) { static ColorTimeline GetFillerTimeline (ColorTimeline timeline, SkeletonData skeletonData) {
var t = new ColorTimeline(1); var t = new ColorTimeline(1, 0, timeline.SlotIndex);
t.SlotIndex = timeline.SlotIndex;
var slotData = skeletonData.Slots.Items[t.SlotIndex]; var slotData = skeletonData.Slots.Items[t.SlotIndex];
t.SetFrame(0, 0, slotData.R, slotData.G, slotData.B, slotData.A); t.SetFrame(0, 0, slotData.R, slotData.G, slotData.B, slotData.A);
return t; return t;
} }
static TwoColorTimeline GetFillerTimeline (TwoColorTimeline timeline, SkeletonData skeletonData) { static TwoColorTimeline GetFillerTimeline (TwoColorTimeline timeline, SkeletonData skeletonData) {
var t = new TwoColorTimeline(1); var t = new TwoColorTimeline(1, 0, timeline.SlotIndex);
t.SlotIndex = timeline.SlotIndex;
var slotData = skeletonData.Slots.Items[t.SlotIndex]; var slotData = skeletonData.Slots.Items[t.SlotIndex];
t.SetFrame(0, 0, slotData.R, slotData.G, slotData.B, slotData.A, slotData.R2, slotData.G2, slotData.B2); t.SetFrame(0, 0, slotData.R, slotData.G, slotData.B, slotData.A, slotData.R2, slotData.G2, slotData.B2);
return t; return t;
} }
static DeformTimeline GetFillerTimeline (DeformTimeline timeline, SkeletonData skeletonData) { static DeformTimeline GetFillerTimeline (DeformTimeline timeline, SkeletonData skeletonData) {
var t = new DeformTimeline(1); var t = new DeformTimeline(1, 0, timeline.SlotIndex, timeline.Attachment);
t.SlotIndex = timeline.SlotIndex;
t.Attachment = timeline.Attachment;
if (t.Attachment.IsWeighted()) { if (t.Attachment.IsWeighted()) {
t.SetFrame(0, 0, new float[t.Attachment.Vertices.Length]); t.SetFrame(0, 0, new float[t.Attachment.Vertices.Length]);
} else { } else {
@ -204,35 +197,35 @@ namespace Spine.Unity.Examples {
} }
static IkConstraintTimeline GetFillerTimeline (IkConstraintTimeline timeline, SkeletonData skeletonData) { static IkConstraintTimeline GetFillerTimeline (IkConstraintTimeline timeline, SkeletonData skeletonData) {
var t = new IkConstraintTimeline(1); var t = new IkConstraintTimeline(1, 0, timeline.IkConstraintIndex);
var ikConstraintData = skeletonData.IkConstraints.Items[timeline.IkConstraintIndex]; var ikConstraintData = skeletonData.IkConstraints.Items[timeline.IkConstraintIndex];
t.SetFrame(0, 0, ikConstraintData.Mix, ikConstraintData.Softness, ikConstraintData.BendDirection, ikConstraintData.Compress, ikConstraintData.Stretch); t.SetFrame(0, 0, ikConstraintData.Mix, ikConstraintData.Softness, ikConstraintData.BendDirection, ikConstraintData.Compress, ikConstraintData.Stretch);
return t; return t;
} }
static TransformConstraintTimeline GetFillerTimeline (TransformConstraintTimeline timeline, SkeletonData skeletonData) { static TransformConstraintTimeline GetFillerTimeline (TransformConstraintTimeline timeline, SkeletonData skeletonData) {
var t = new TransformConstraintTimeline(1); var t = new TransformConstraintTimeline(1, 0, timeline.TransformConstraintIndex);
var data = skeletonData.TransformConstraints.Items[timeline.TransformConstraintIndex]; var data = skeletonData.TransformConstraints.Items[timeline.TransformConstraintIndex];
t.SetFrame(0, 0, data.RotateMix, data.TranslateMix, data.ScaleMix, data.ShearMix); t.SetFrame(0, 0, data.RotateMix, data.TranslateMix, data.ScaleMix, data.ShearMix);
return t; return t;
} }
static PathConstraintPositionTimeline GetFillerTimeline (PathConstraintPositionTimeline timeline, SkeletonData skeletonData) { static PathConstraintPositionTimeline GetFillerTimeline (PathConstraintPositionTimeline timeline, SkeletonData skeletonData) {
var t = new PathConstraintPositionTimeline(1); var t = new PathConstraintPositionTimeline(1, 0, timeline.PathConstraintIndex);
var data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex]; var data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex];
t.SetFrame(0, 0, data.Position); t.SetFrame(0, 0, data.Position);
return t; return t;
} }
static PathConstraintSpacingTimeline GetFillerTimeline (PathConstraintSpacingTimeline timeline, SkeletonData skeletonData) { static PathConstraintSpacingTimeline GetFillerTimeline (PathConstraintSpacingTimeline timeline, SkeletonData skeletonData) {
var t = new PathConstraintSpacingTimeline(1); var t = new PathConstraintSpacingTimeline(1, 0, timeline.PathConstraintIndex);
var data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex]; var data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex];
t.SetFrame(0, 0, data.Spacing); t.SetFrame(0, 0, data.Spacing);
return t; return t;
} }
static PathConstraintMixTimeline GetFillerTimeline (PathConstraintMixTimeline timeline, SkeletonData skeletonData) { static PathConstraintMixTimeline GetFillerTimeline (PathConstraintMixTimeline timeline, SkeletonData skeletonData) {
var t = new PathConstraintMixTimeline(1); var t = new PathConstraintMixTimeline(1, 0, timeline.PathConstraintIndex);
var data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex]; var data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex];
t.SetFrame(0, 0, data.RotateMix, data.TranslateMix); t.SetFrame(0, 0, data.RotateMix, data.TranslateMix);
return t; return t;

View File

@ -123,7 +123,6 @@ namespace Spine.Unity.Editor {
EditorGUI.indentLevel = 0; EditorGUI.indentLevel = 0;
var mixMode = layerMixModes.GetArrayElementAtIndex(i); var mixMode = layerMixModes.GetArrayElementAtIndex(i);
var blendMode = layerBlendModes.GetArrayElementAtIndex(i);
rect.position += new Vector2(rect.width, 0); rect.position += new Vector2(rect.width, 0);
rect.width = widthMixColumn; rect.width = widthMixColumn;
EditorGUI.PropertyField(rect, mixMode, GUIContent.none); EditorGUI.PropertyField(rect, mixMode, GUIContent.none);

View File

@ -1,9 +0,0 @@
fileFormatVersion: 2
guid: 18ee2876d53412642bbfa1070a1b947f
folderAsset: yes
timeCreated: 1527569487
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,9 +0,0 @@
fileFormatVersion: 2
guid: 1ad4318c20ec5674a9f4d7f786afd681
folderAsset: yes
timeCreated: 1496449217
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,47 +0,0 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated January 1, 2020. Replaces all prior versions.
*
* Copyright (c) 2013-2020, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using UnityEngine;
using UnityEditor;
using Spine.Unity.Deprecated;
using System;
namespace Spine.Unity.Editor {
using Editor = UnityEditor.Editor;
[Obsolete("The spine-unity 3.7 runtime introduced SkeletonDataModifierAssets BlendModeMaterials which replaced SlotBlendModes. Will be removed in spine-unity 3.9.", false)]
public class SlotBlendModesEditor : Editor {
[MenuItem("CONTEXT/SkeletonRenderer/Add Slot Blend Modes Component")]
static void AddSlotBlendModesComponent (MenuCommand command) {
var skeletonRenderer = (SkeletonRenderer)command.context;
skeletonRenderer.gameObject.AddComponent<SlotBlendModes>();
}
}
}

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: cbec7dc66dca80a419477536c23b7a0d
timeCreated: 1496449255
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -40,8 +40,8 @@ namespace Spine.Unity {
public static class SkeletonDataCompatibility { public static class SkeletonDataCompatibility {
#if UNITY_EDITOR #if UNITY_EDITOR
static readonly int[][] compatibleBinaryVersions = { new[] { 3, 9, 0 }, new[] { 3, 8, 0 } }; static readonly int[][] compatibleBinaryVersions = { new[] { 4, 0, 0 } };
static readonly int[][] compatibleJsonVersions = { new[] { 3, 9, 0 }, new[] { 3, 8, 0 } }; static readonly int[][] compatibleJsonVersions = { new[] { 4, 0, 0 } };
static bool wasVersionDialogShown = false; static bool wasVersionDialogShown = false;
static readonly Regex jsonVersionRegex = new Regex(@"""spine""\s*:\s*""([^""]+)""", RegexOptions.CultureInvariant); static readonly Regex jsonVersionRegex = new Regex(@"""spine""\s*:\s*""([^""]+)""", RegexOptions.CultureInvariant);

View File

@ -254,7 +254,6 @@ namespace Spine.Unity {
private void OnClipAppliedCallback (Spine.Animation clip, AnimatorStateInfo stateInfo, private void OnClipAppliedCallback (Spine.Animation clip, AnimatorStateInfo stateInfo,
int layerIndex, float time, bool isLooping, float weight) { int layerIndex, float time, bool isLooping, float weight) {
float clipDuration = clip.duration == 0 ? 1 : clip.duration;
float speedFactor = stateInfo.speedMultiplier * stateInfo.speed; float speedFactor = stateInfo.speedMultiplier * stateInfo.speed;
float lastTime = time - (Time.deltaTime * speedFactor); float lastTime = time - (Time.deltaTime * speedFactor);
if (isLooping && clip.duration != 0) { if (isLooping && clip.duration != 0) {

View File

@ -1,9 +0,0 @@
fileFormatVersion: 2
guid: 04817e31b917de6489f349dd332d7468
folderAsset: yes
timeCreated: 1563295668
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,9 +0,0 @@
fileFormatVersion: 2
guid: dfdd78a071ca1a04bb64c6cc41e14aa0
folderAsset: yes
timeCreated: 1496447038
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,230 +0,0 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated January 1, 2020. Replaces all prior versions.
*
* Copyright (c) 2013-2020, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System.Collections.Generic;
using UnityEngine;
using System;
namespace Spine.Unity.Deprecated {
/// <summary>
/// Deprecated. The spine-unity 3.7 runtime introduced SkeletonDataModifierAssets BlendModeMaterials which replaced SlotBlendModes. See the
/// <see href="http://esotericsoftware.com/spine-unity-skeletondatamodifierassets#BlendModeMaterials">SkeletonDataModifierAssets BlendModeMaterials documentation page</see> and
/// <see href="http://esotericsoftware.com/forum/Slot-blending-not-work-11281">this forum thread</see> for further information.
/// This class will be removed in the spine-unity 3.9 runtime.
/// </summary>
[Obsolete("The spine-unity 3.7 runtime introduced SkeletonDataModifierAssets BlendModeMaterials which replaced SlotBlendModes. Will be removed in spine-unity 3.9.", false)]
[DisallowMultipleComponent]
public class SlotBlendModes : MonoBehaviour {
#region Internal Material Dictionary
public struct MaterialTexturePair {
public Texture2D texture2D;
public Material material;
}
internal class MaterialWithRefcount {
public Material materialClone;
public int refcount = 1;
public MaterialWithRefcount(Material mat) {
this.materialClone = mat;
}
}
static Dictionary<MaterialTexturePair, MaterialWithRefcount> materialTable;
internal static Dictionary<MaterialTexturePair, MaterialWithRefcount> MaterialTable {
get {
if (materialTable == null) materialTable = new Dictionary<MaterialTexturePair, MaterialWithRefcount>();
return materialTable;
}
}
internal struct SlotMaterialTextureTuple {
public Slot slot;
public Texture2D texture2D;
public Material material;
public SlotMaterialTextureTuple(Slot slot, Material material, Texture2D texture) {
this.slot = slot;
this.material = material;
this.texture2D = texture;
}
}
internal static Material GetOrAddMaterialFor(Material materialSource, Texture2D texture) {
if (materialSource == null || texture == null) return null;
var mt = SlotBlendModes.MaterialTable;
MaterialWithRefcount matWithRefcount;
var key = new MaterialTexturePair { material = materialSource, texture2D = texture };
if (!mt.TryGetValue(key, out matWithRefcount)) {
matWithRefcount = new MaterialWithRefcount(new Material(materialSource));
var m = matWithRefcount.materialClone;
m.name = "(Clone)" + texture.name + "-" + materialSource.name;
m.mainTexture = texture;
mt[key] = matWithRefcount;
}
else {
matWithRefcount.refcount++;
}
return matWithRefcount.materialClone;
}
internal static MaterialWithRefcount GetExistingMaterialFor(Material materialSource, Texture2D texture)
{
if (materialSource == null || texture == null) return null;
var mt = SlotBlendModes.MaterialTable;
MaterialWithRefcount matWithRefcount;
var key = new MaterialTexturePair { material = materialSource, texture2D = texture };
if (!mt.TryGetValue(key, out matWithRefcount)) {
return null;
}
return matWithRefcount;
}
internal static void RemoveMaterialFromTable(Material materialSource, Texture2D texture) {
var mt = SlotBlendModes.MaterialTable;
var key = new MaterialTexturePair { material = materialSource, texture2D = texture };
mt.Remove(key);
}
#endregion
#region Inspector
public Material multiplyMaterialSource;
public Material screenMaterialSource;
Texture2D texture;
#endregion
SlotMaterialTextureTuple[] slotsWithCustomMaterial = new SlotMaterialTextureTuple[0];
public bool Applied { get; private set; }
void Start() {
if (!Applied) Apply();
}
void OnDestroy() {
if (Applied) Remove();
}
public void Apply() {
GetTexture();
if (texture == null) return;
var skeletonRenderer = GetComponent<SkeletonRenderer>();
if (skeletonRenderer == null) return;
var slotMaterials = skeletonRenderer.CustomSlotMaterials;
int numSlotsWithCustomMaterial = 0;
foreach (var s in skeletonRenderer.Skeleton.Slots) {
switch (s.data.blendMode) {
case BlendMode.Multiply:
if (multiplyMaterialSource != null) {
slotMaterials[s] = GetOrAddMaterialFor(multiplyMaterialSource, texture);
++numSlotsWithCustomMaterial;
}
break;
case BlendMode.Screen:
if (screenMaterialSource != null) {
slotMaterials[s] = GetOrAddMaterialFor(screenMaterialSource, texture);
++numSlotsWithCustomMaterial;
}
break;
}
}
slotsWithCustomMaterial = new SlotMaterialTextureTuple[numSlotsWithCustomMaterial];
int storedSlotIndex = 0;
foreach (var s in skeletonRenderer.Skeleton.Slots) {
switch (s.data.blendMode) {
case BlendMode.Multiply:
if (multiplyMaterialSource != null) {
slotsWithCustomMaterial[storedSlotIndex++] = new SlotMaterialTextureTuple(s, multiplyMaterialSource, texture);
}
break;
case BlendMode.Screen:
if (screenMaterialSource != null) {
slotsWithCustomMaterial[storedSlotIndex++] = new SlotMaterialTextureTuple(s, screenMaterialSource, texture);
}
break;
}
}
Applied = true;
skeletonRenderer.LateUpdate();
}
public void Remove() {
GetTexture();
if (texture == null) return;
var skeletonRenderer = GetComponent<SkeletonRenderer>();
if (skeletonRenderer == null) return;
var slotMaterials = skeletonRenderer.CustomSlotMaterials;
foreach (var slotWithCustomMat in slotsWithCustomMaterial) {
Slot s = slotWithCustomMat.slot;
Material storedMaterialSource = slotWithCustomMat.material;
Texture2D storedTexture = slotWithCustomMat.texture2D;
var matWithRefcount = GetExistingMaterialFor(storedMaterialSource, storedTexture);
if (--matWithRefcount.refcount == 0) {
RemoveMaterialFromTable(storedMaterialSource, storedTexture);
}
// we don't want to remove slotMaterials[s] if it has been changed in the meantime.
Material m;
if (slotMaterials.TryGetValue(s, out m)) {
var existingMat = matWithRefcount == null ? null : matWithRefcount.materialClone;
if (Material.ReferenceEquals(m, existingMat)) {
slotMaterials.Remove(s);
}
}
}
slotsWithCustomMaterial = null;
Applied = false;
if (skeletonRenderer.valid) skeletonRenderer.LateUpdate();
}
public void GetTexture() {
if (texture == null) {
var sr = GetComponent<SkeletonRenderer>(); if (sr == null) return;
var sda = sr.skeletonDataAsset; if (sda == null) return;
var aa = sda.atlasAssets[0]; if (aa == null) return;
var am = aa.PrimaryMaterial; if (am == null) return;
texture = am.mainTexture as Texture2D;
}
}
}
}

View File

@ -1,16 +0,0 @@
fileFormatVersion: 2
guid: f1f8243645ba2e74aa3564bd956eed89
timeCreated: 1496794038
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences:
- multiplyMaterialSource: {fileID: 2100000, guid: 53bf0ab317d032d418cf1252d68f51df,
type: 2}
- screenMaterialSource: {fileID: 2100000, guid: 73f0f46d3177c614baf0fa48d646a9be,
type: 2}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -38,28 +38,28 @@ namespace Spine.Unity.AnimationTools {
/// SkeletonData can be accessed from Skeleton.Data or from SkeletonDataAsset.GetSkeletonData. /// SkeletonData can be accessed from Skeleton.Data or from SkeletonDataAsset.GetSkeletonData.
/// If no SkeletonData is given, values are computed relative to setup pose instead of local-absolute.</summary> /// If no SkeletonData is given, values are computed relative to setup pose instead of local-absolute.</summary>
public static Vector2 Evaluate (this TranslateTimeline timeline, float time, SkeletonData skeletonData = null) { public static Vector2 Evaluate (this TranslateTimeline timeline, float time, SkeletonData skeletonData = null) {
const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1;
const int X = 1, Y = 2;
var frames = timeline.frames; var frames = timeline.frames;
if (time < frames[0]) return Vector2.zero; if (time < frames[0]) return Vector2.zero;
float x, y; float x, y;
if (time >= frames[frames.Length - TranslateTimeline.ENTRIES]) { // Time is after last frame. int i = Animation.Search(frames, time, TranslateTimeline.ENTRIES), curveType = (int)timeline.curves[i / TranslateTimeline.ENTRIES];
x = frames[frames.Length + PREV_X]; switch (curveType) {
y = frames[frames.Length + PREV_Y]; case TranslateTimeline.LINEAR:
} float before = frames[i];
else { x = frames[i + TranslateTimeline.VALUE1];
// Interpolate between the previous frame and the current frame. y = frames[i + TranslateTimeline.VALUE2];
int frame = Animation.BinarySearch(frames, time, TranslateTimeline.ENTRIES); float t = (time - before) / (frames[i + TranslateTimeline.ENTRIES] - before);
x = frames[frame + PREV_X]; x += (frames[i + TranslateTimeline.ENTRIES + TranslateTimeline.VALUE1] - x) * t;
y = frames[frame + PREV_Y]; y += (frames[i + TranslateTimeline.ENTRIES + TranslateTimeline.VALUE2] - y) * t;
float frameTime = frames[frame]; break;
float percent = timeline.GetCurvePercent(frame / TranslateTimeline.ENTRIES - 1, case TranslateTimeline.STEPPED:
1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); x = frames[i + TranslateTimeline.VALUE1];
y = frames[i + TranslateTimeline.VALUE2];
x += (frames[frame + X] - x) * percent; break;
y += (frames[frame + Y] - y) * percent; default:
x = timeline.GetBezierValue(time, i, TranslateTimeline.VALUE1, curveType - TranslateTimeline.BEZIER);
y = timeline.GetBezierValue(time, i, TranslateTimeline.VALUE2, curveType + TranslateTimeline.BEZIER_SIZE - TranslateTimeline.BEZIER);
break;
} }
Vector2 xy = new Vector2(x, y); Vector2 xy = new Vector2(x, y);
@ -67,7 +67,7 @@ namespace Spine.Unity.AnimationTools {
return xy; return xy;
} }
else { else {
var boneData = skeletonData.bones.Items[timeline.boneIndex]; var boneData = skeletonData.bones.Items[timeline.BoneIndex];
return xy + new Vector2(boneData.x, boneData.y); return xy + new Vector2(boneData.x, boneData.y);
} }
} }
@ -82,7 +82,7 @@ namespace Spine.Unity.AnimationTools {
continue; continue;
var translateTimeline = timeline as TranslateTimeline; var translateTimeline = timeline as TranslateTimeline;
if (translateTimeline != null && translateTimeline.boneIndex == boneIndex) if (translateTimeline != null && translateTimeline.BoneIndex == boneIndex)
return translateTimeline; return translateTimeline;
} }
return null; return null;

View File

@ -1 +1 @@
This Spine-Unity runtime works with data exported from Spine Editor version: 3.8.xx and 3.9.xx This Spine-Unity runtime works with data exported from Spine Editor version: 4.0.xx