mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 14:24:53 +08:00
[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:
parent
3ab0b72033
commit
d2529d410b
File diff suppressed because it is too large
Load Diff
@ -112,7 +112,7 @@ namespace Spine {
|
||||
|
||||
// end of difference
|
||||
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 float timeScale = 1;
|
||||
private int unkeyedState;
|
||||
@ -244,7 +244,13 @@ namespace Spine {
|
||||
mix = 0; // Set to setup pose the last time the entry will be applied.
|
||||
|
||||
// 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;
|
||||
var timelines = current.animation.timelines;
|
||||
var timelinesItems = timelines.Items;
|
||||
@ -252,9 +258,9 @@ namespace Spine {
|
||||
for (int ii = 0; ii < timelineCount; ii++) {
|
||||
var timeline = timelinesItems[ii];
|
||||
if (timeline is AttachmentTimeline)
|
||||
ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, blend, true);
|
||||
ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true);
|
||||
else
|
||||
timeline.Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In);
|
||||
timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, blend, MixDirection.In);
|
||||
}
|
||||
} else {
|
||||
var timelineMode = current.timelineMode.Items;
|
||||
@ -268,12 +274,12 @@ namespace Spine {
|
||||
MixBlend timelineBlend = timelineMode[ii] == AnimationState.Subsequent ? blend : MixBlend.Setup;
|
||||
var rotateTimeline = timeline as RotateTimeline;
|
||||
if (rotateTimeline != null)
|
||||
ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation,
|
||||
ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, mix, timelineBlend, timelinesRotation,
|
||||
ii << 1, firstFrame);
|
||||
else if (timeline is AttachmentTimeline)
|
||||
ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, blend, true);
|
||||
ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true);
|
||||
else
|
||||
timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelineBlend, MixDirection.In);
|
||||
timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.In);
|
||||
}
|
||||
}
|
||||
QueueEvents(current, animationTime);
|
||||
@ -314,17 +320,23 @@ namespace Spine {
|
||||
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;
|
||||
float animationLast = from.animationLast, animationTime = from.AnimationTime;
|
||||
var timelines = from.animation.timelines;
|
||||
int timelineCount = timelines.Count;
|
||||
var timelinesItems = timelines.Items;
|
||||
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) {
|
||||
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 {
|
||||
var timelineMode = from.timelineMode.Items;
|
||||
var timelineHoldMix = from.timelineHoldMix.Items;
|
||||
@ -366,14 +378,14 @@ namespace Spine {
|
||||
from.totalAlpha += alpha;
|
||||
var rotateTimeline = timeline as RotateTimeline;
|
||||
if (rotateTimeline != null) {
|
||||
ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation,
|
||||
i << 1, firstFrame);
|
||||
ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, alpha, timelineBlend, timelinesRotation, i << 1,
|
||||
firstFrame);
|
||||
} else if (timeline is AttachmentTimeline) {
|
||||
ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, timelineBlend, attachments);
|
||||
ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, timelineBlend, attachments);
|
||||
} else {
|
||||
if (drawOrder && timeline is DrawOrderTimeline && timelineBlend == MixBlend.Setup)
|
||||
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,
|
||||
bool attachments) {
|
||||
|
||||
Slot slot = skeleton.slots.Items[timeline.slotIndex];
|
||||
Slot slot = skeleton.slots.Items[timeline.SlotIndex];
|
||||
if (!slot.bone.active) return;
|
||||
|
||||
float[] frames = timeline.frames;
|
||||
@ -402,12 +414,7 @@ namespace Spine {
|
||||
SetAttachment(skeleton, slot, slot.data.attachmentName, attachments);
|
||||
}
|
||||
else {
|
||||
int frameIndex;
|
||||
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);
|
||||
SetAttachment(skeleton, slot, timeline.AttachmentNames[Animation.Search(frames, time)], attachments);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
Bone bone = skeleton.bones.Items[timeline.boneIndex];
|
||||
Bone bone = skeleton.bones.Items[timeline.BoneIndex];
|
||||
if (!bone.active) return;
|
||||
|
||||
float[] frames = timeline.frames;
|
||||
@ -441,7 +448,7 @@ namespace Spine {
|
||||
switch (blend) {
|
||||
case MixBlend.Setup:
|
||||
bone.rotation = bone.data.rotation;
|
||||
return;
|
||||
goto default; // Fall through.
|
||||
default:
|
||||
return;
|
||||
case MixBlend.First:
|
||||
@ -451,21 +458,7 @@ namespace Spine {
|
||||
}
|
||||
} else {
|
||||
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 + 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;
|
||||
}
|
||||
r2 = bone.data.rotation + timeline.GetCurveValue(time);
|
||||
}
|
||||
|
||||
// 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 + 1] = diff;
|
||||
r1 += total * alpha;
|
||||
bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360;
|
||||
bone.rotation = r1 + total * alpha;
|
||||
}
|
||||
|
||||
private void QueueEvents (TrackEntry entry, float animationTime) {
|
||||
@ -577,10 +569,17 @@ namespace Spine {
|
||||
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>
|
||||
private void SetCurrent (int index, TrackEntry current, bool interrupt) {
|
||||
TrackEntry from = ExpandToIndex(index);
|
||||
tracks.Items[index] = current;
|
||||
current.previous = null;
|
||||
|
||||
if (from != null) {
|
||||
if (interrupt) queue.Interrupt(from);
|
||||
@ -647,7 +646,7 @@ namespace Spine {
|
||||
/// equivalent to calling <see cref="SetAnimation(int, Animation, bool)"/>.</summary>
|
||||
/// <param name="delay">
|
||||
/// If > 0, sets <see cref="TrackEntry.Delay"/>. If <= 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> < 0) the previous track entry duration). If the
|
||||
/// previous entry is looping, its next loop completion is used instead of its duration.
|
||||
/// </param>
|
||||
@ -669,18 +668,8 @@ namespace Spine {
|
||||
queue.Drain();
|
||||
} else {
|
||||
last.next = entry;
|
||||
if (delay <= 0) {
|
||||
float duration = last.animationEnd - last.animationStart;
|
||||
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.previous = last;
|
||||
if (delay <= 0) delay += last.TrackComplete - entry.mixDuration;
|
||||
}
|
||||
|
||||
entry.delay = delay;
|
||||
@ -698,11 +687,11 @@ namespace Spine {
|
||||
/// 0 still mixes out over one frame.</para>
|
||||
/// <para>
|
||||
/// 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="TrackEntry.SetMixDuration(float)"/>. Mixing from an empty animation causes the new animation to be applied more and
|
||||
/// more over the mix duration. Properties keyed in the new animation transition from the value from lower tracks or from the
|
||||
/// setup pose value if no lower tracks key the property to the value keyed in the new animation.</para>
|
||||
/// </summary>
|
||||
/// <see cref="AnimationState.AddAnimation(int, Animation, bool, float)"/> with the desired delay (an empty animation has a duration of 0) and on
|
||||
/// the returned track entry, set the <see cref="TrackEntry.SetMixDuration(float)"/>. Mixing from an empty animation causes the new
|
||||
/// animation to be applied more and more over the mix duration. Properties keyed in the new animation transition from the value
|
||||
/// from lower tracks or from the setup pose value if no lower tracks key the property to the value keyed in the new
|
||||
/// animation.</para></summary>
|
||||
public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) {
|
||||
TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false);
|
||||
entry.mixDuration = mixDuration;
|
||||
@ -725,10 +714,10 @@ namespace Spine {
|
||||
/// after the <see cref="AnimationState.Dispose"/> event occurs.
|
||||
/// </returns>
|
||||
public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) {
|
||||
if (delay <= 0) delay -= mixDuration;
|
||||
TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay);
|
||||
TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay <= 0 ? 1 : delay);
|
||||
entry.mixDuration = mixDuration;
|
||||
entry.trackEnd = mixDuration;
|
||||
if (delay <= 0 && entry.previous != null) entry.delay = entry.previous.TrackComplete - entry.mixDuration;
|
||||
return entry;
|
||||
}
|
||||
|
||||
@ -738,8 +727,9 @@ namespace Spine {
|
||||
public void SetEmptyAnimations (float mixDuration) {
|
||||
bool oldDrainDisabled = queue.drainDisabled;
|
||||
queue.drainDisabled = true;
|
||||
var tracksItems = tracks.Items;
|
||||
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);
|
||||
}
|
||||
queue.drainDisabled = oldDrainDisabled;
|
||||
@ -798,10 +788,10 @@ namespace Spine {
|
||||
animationsChanged = false;
|
||||
|
||||
// Process in the order that animations are applied.
|
||||
propertyIDs.Clear();
|
||||
|
||||
propertyIds.Clear();
|
||||
int n = tracks.Count;
|
||||
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];
|
||||
if (entry == null) continue;
|
||||
while (entry.mixingFrom != null) // Move to last entry, then iterate in reverse.
|
||||
@ -814,6 +804,8 @@ namespace Spine {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void ComputeHold (TrackEntry entry) {
|
||||
TrackEntry to = entry.mixingTo;
|
||||
var timelines = entry.animation.timelines.Items;
|
||||
@ -821,11 +813,11 @@ namespace Spine {
|
||||
var timelineMode = entry.timelineMode.Resize(timelinesCount).Items; //timelineMode.setSize(timelinesCount);
|
||||
entry.timelineHoldMix.Clear();
|
||||
var timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; //timelineHoldMix.setSize(timelinesCount);
|
||||
var propertyIDs = this.propertyIDs;
|
||||
var propertyIds = this.propertyIds;
|
||||
|
||||
if (to != null && to.holdPrevious) {
|
||||
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;
|
||||
}
|
||||
@ -833,15 +825,15 @@ namespace Spine {
|
||||
// outer:
|
||||
for (int i = 0; i < timelinesCount; i++) {
|
||||
Timeline timeline = timelines[i];
|
||||
int id = timeline.PropertyId;
|
||||
if (!propertyIDs.Add(id))
|
||||
String[] ids = timeline.PropertyIds;
|
||||
if (!propertyIds.AddAll(ids))
|
||||
timelineMode[i] = AnimationState.Subsequent;
|
||||
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;
|
||||
} else {
|
||||
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) {
|
||||
timelineMode[i] = AnimationState.HoldMix;
|
||||
timelineHoldMix[i] = next;
|
||||
@ -892,8 +884,9 @@ namespace Spine {
|
||||
|
||||
override public string ToString () {
|
||||
var buffer = new System.Text.StringBuilder();
|
||||
var tracksItems = tracks.Items;
|
||||
for (int i = 0, n = tracks.Count; i < n; i++) {
|
||||
TrackEntry entry = tracks.Items[i];
|
||||
TrackEntry entry = tracksItems[i];
|
||||
if (entry == null) continue;
|
||||
if (buffer.Length > 0) buffer.Append(", ");
|
||||
buffer.Append(entry.ToString());
|
||||
@ -912,7 +905,7 @@ namespace Spine {
|
||||
public class TrackEntry : Pool<TrackEntry>.IPoolable {
|
||||
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'.
|
||||
public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete;
|
||||
public event AnimationState.TrackEntryEventDelegate Event;
|
||||
@ -925,7 +918,7 @@ namespace Spine {
|
||||
|
||||
internal int trackIndex;
|
||||
|
||||
internal bool loop, holdPrevious;
|
||||
internal bool loop, holdPrevious, reverse;
|
||||
internal float eventThreshold, attachmentThreshold, drawOrderThreshold;
|
||||
internal float animationStart, animationEnd, animationLast, nextAnimationLast;
|
||||
internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f;
|
||||
@ -937,6 +930,7 @@ namespace Spine {
|
||||
|
||||
// IPoolable.Reset()
|
||||
public void Reset () {
|
||||
previous = null;
|
||||
next = null;
|
||||
mixingFrom = null;
|
||||
mixingTo = null;
|
||||
@ -973,7 +967,10 @@ namespace Spine {
|
||||
/// track entry <see cref="TrackEntry.TrackTime"/> >= this track entry's <code>Delay</code>).</para>
|
||||
/// <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; } }
|
||||
|
||||
/// <summary>
|
||||
@ -994,6 +991,21 @@ namespace Spine {
|
||||
/// </summary>
|
||||
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>
|
||||
/// <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
|
||||
/// faster. Defaults to 1.</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
|
||||
/// match the animation speed.</para>
|
||||
/// <para>
|
||||
/// When using <see cref="AnimationState.AddAnimation(int, Animation, boolean, float)"> with a <code>Delay</code> <= 0, note the
|
||||
/// {<see cref="TrackEntry.Delay"/> is set using the mix duration from the <see cref="AnimationStateData"/>, assuming time scale to be 1. If
|
||||
/// 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
|
||||
/// the time scale is not 1, the delay may need to be adjusted.</para>
|
||||
/// <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; } }
|
||||
|
||||
/// <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; } }
|
||||
|
||||
/// <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>
|
||||
/// Returns true if at least one loop has been completed.</summary>
|
||||
/// <seealso cref="TrackEntry.Complete"/>
|
||||
@ -1108,20 +1129,21 @@ namespace Spine {
|
||||
/// <para>
|
||||
/// 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
|
||||
/// track entry only before <see cref="AnimationState.Update(float)"/> is first called.</para>
|
||||
/// <para>
|
||||
/// When using <seealso cref="AnimationState.AddAnimation(int, Animation, bool, float)"/> with a <code>Delay</code> <= 0, note the
|
||||
/// <see cref="TrackEntry.Delay"/> is set using the mix duration from the <see cref=" AnimationStateData"/>, not a mix duration set
|
||||
/// afterward.</para>
|
||||
/// </summary>
|
||||
/// track entry only before <see cref="AnimationState.Update(float)"/> is first called.</para>
|
||||
/// <para>
|
||||
/// When using <seealso 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"/>. If <code>mixDuration</code> is set
|
||||
/// afterward, the delay may need to be adjusted. For example:
|
||||
/// <code>entry.Delay = entry.previous.TrackComplete - entry.MixDuration;</code>
|
||||
/// </para></summary>
|
||||
public float MixDuration { get { return mixDuration; } set { mixDuration = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to <see cref="MixBlend.Replace"/>, which
|
||||
/// replaces the values from the lower tracks with the animation values. <see cref="MixBlend.Add"/> adds the animation values to
|
||||
/// the values from the lower tracks.</para>
|
||||
/// <para>
|
||||
/// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to <see cref="MixBlend.Replace"/>.
|
||||
/// </para><para>
|
||||
/// Track entries on track 0 ignore this setting and always use <see cref="MixBlend.First"/>.
|
||||
/// </para><para>
|
||||
/// The <code>MixBlend</code> can be set for a new track entry only before <see cref="AnimationState.Apply(Skeleton)"/> is first
|
||||
/// called.</para>
|
||||
/// </summary>
|
||||
@ -1153,6 +1175,10 @@ namespace Spine {
|
||||
/// </summary>
|
||||
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>
|
||||
/// <para>
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ namespace Spine {
|
||||
private 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;
|
||||
}
|
||||
|
||||
|
||||
@ -56,7 +56,7 @@ namespace Spine {
|
||||
|
||||
deformAttachment = this;
|
||||
lock (VertexAttachment.nextIdLock) {
|
||||
id = (VertexAttachment.nextID++ & 65535) << 11;
|
||||
id = VertexAttachment.nextID++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -116,6 +116,7 @@ namespace Spine {
|
||||
/// <summary>Returns the magnitide (always positive) of the world scale Y.</summary>
|
||||
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>
|
||||
public Bone (BoneData data, Skeleton skeleton, Bone parent) {
|
||||
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) {
|
||||
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;
|
||||
localX = (x * d * invDet - y * b * invDet);
|
||||
localY = (y * a * invDet - x * c * invDet);
|
||||
localX = (x * d - y * b) / det;
|
||||
localY = (y * a - x * c) / det;
|
||||
}
|
||||
|
||||
public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) {
|
||||
|
||||
@ -79,20 +79,16 @@ namespace Spine {
|
||||
stretch = constraint.stretch;
|
||||
}
|
||||
|
||||
/// <summary>Applies the constraint to the constrained bones.</summary>
|
||||
public void Apply () {
|
||||
Update();
|
||||
}
|
||||
|
||||
public void Update () {
|
||||
if (mix == 0) return;
|
||||
Bone target = this.target;
|
||||
ExposedList<Bone> bones = this.bones;
|
||||
switch (bones.Count) {
|
||||
var bones = this.bones.Items;
|
||||
switch (this.bones.Count) {
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -157,6 +153,7 @@ namespace Spine {
|
||||
/// <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,
|
||||
float alpha) {
|
||||
if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
|
||||
if (!bone.appliedValid) bone.UpdateAppliedTransform();
|
||||
Bone p = bone.parent;
|
||||
|
||||
@ -175,16 +172,16 @@ namespace Spine {
|
||||
float sc = pc / bone.skeleton.ScaleY;
|
||||
pb = -sc * s * bone.skeleton.ScaleX;
|
||||
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.
|
||||
}
|
||||
default: {
|
||||
}
|
||||
default: {
|
||||
float x = targetX - p.worldX, y = targetY - p.worldY;
|
||||
float d = pa * pd - pb * pc;
|
||||
tx = (x * pd - y * pb) / d - bone.ax;
|
||||
ty = (y * pa - x * pc) / d - bone.ay;
|
||||
break;
|
||||
}
|
||||
float d = pa * pd - pb * pc;
|
||||
tx = (x * pd - y * pb) / d - bone.ax;
|
||||
ty = (y * pa - x * pc) / d - bone.ay;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rotationIK += (float)Math.Atan2(ty, tx) * MathUtils.RadDeg;
|
||||
@ -198,13 +195,10 @@ namespace Spine {
|
||||
if (compress || stretch) {
|
||||
switch (bone.data.transformMode) {
|
||||
case TransformMode.NoScale:
|
||||
tx = targetX - bone.worldX;
|
||||
ty = targetY - bone.worldY;
|
||||
break;
|
||||
case TransformMode.NoScaleOrReflection:
|
||||
case TransformMode.NoScaleOrReflection:
|
||||
tx = targetX - bone.worldX;
|
||||
ty = targetY - bone.worldY;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty);
|
||||
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>
|
||||
static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, float softness,
|
||||
float alpha) {
|
||||
if (alpha == 0) {
|
||||
child.UpdateWorldTransform();
|
||||
return;
|
||||
}
|
||||
if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null.");
|
||||
if (child == null) throw new ArgumentNullException("child", "child cannot be null.");
|
||||
if (!parent.appliedValid) parent.UpdateAppliedTransform();
|
||||
if (!child.appliedValid) child.UpdateAppliedTransform();
|
||||
float px = parent.ax, py = parent.ay, psx = parent.ascaleX, sx = psx, psy = parent.ascaleY, csx = child.ascaleX;
|
||||
|
||||
@ -34,7 +34,7 @@ namespace Spine {
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// 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>
|
||||
/// See <a href="http://esotericsoftware.com/spine-path-constraints">Path constraints</a> in the Spine User Guide.</para>
|
||||
/// </summary>
|
||||
@ -82,11 +82,6 @@ namespace Spine {
|
||||
translateMix = constraint.translateMix;
|
||||
}
|
||||
|
||||
/// <summary>Applies the constraint to the constrained bones.</summary>
|
||||
public void Apply () {
|
||||
Update();
|
||||
}
|
||||
|
||||
public void Update () {
|
||||
PathAttachment attachment = target.Attachment as PathAttachment;
|
||||
if (attachment == null) return;
|
||||
|
||||
@ -40,7 +40,6 @@ namespace Spine {
|
||||
internal ExposedList<TransformConstraint> transformConstraints;
|
||||
internal ExposedList<PathConstraint> pathConstraints;
|
||||
internal ExposedList<IUpdatable> updateCache = new ExposedList<IUpdatable>();
|
||||
internal ExposedList<Bone> updateCacheReset = new ExposedList<Bone>();
|
||||
internal Skin skin;
|
||||
internal float r = 1, g = 1, b = 1, a = 1;
|
||||
internal float time;
|
||||
@ -55,7 +54,13 @@ namespace Spine {
|
||||
public ExposedList<IkConstraint> IkConstraints { get { return ikConstraints; } }
|
||||
public ExposedList<PathConstraint> PathConstraints { get { return pathConstraints; } }
|
||||
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 G { get { return g; } set { g = 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.")]
|
||||
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 {
|
||||
get { return bones.Count == 0 ? null : bones.Items[0]; }
|
||||
}
|
||||
@ -81,22 +87,23 @@ namespace Spine {
|
||||
this.data = data;
|
||||
|
||||
bones = new ExposedList<Bone>(data.bones.Count);
|
||||
var bonesItems = this.bones.Items;
|
||||
foreach (BoneData boneData in data.bones) {
|
||||
Bone bone;
|
||||
if (boneData.parent == null) {
|
||||
bone = new Bone(boneData, this, null);
|
||||
} else {
|
||||
Bone parent = bones.Items[boneData.parent.index];
|
||||
Bone parent = bonesItems[boneData.parent.index];
|
||||
bone = new Bone(boneData, this, parent);
|
||||
parent.children.Add(bone);
|
||||
}
|
||||
bones.Add(bone);
|
||||
this.bones.Add(bone);
|
||||
}
|
||||
|
||||
slots = new ExposedList<Slot>(data.slots.Count);
|
||||
drawOrder = new ExposedList<Slot>(data.slots.Count);
|
||||
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);
|
||||
slots.Add(slot);
|
||||
drawOrder.Add(slot);
|
||||
@ -115,7 +122,7 @@ namespace Spine {
|
||||
pathConstraints.Add(new PathConstraint(pathConstraintData, this));
|
||||
|
||||
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
|
||||
@ -123,7 +130,6 @@ namespace Spine {
|
||||
public void UpdateCache () {
|
||||
var updateCache = this.updateCache;
|
||||
updateCache.Clear();
|
||||
this.updateCacheReset.Clear();
|
||||
|
||||
int boneCount = this.bones.Items.Length;
|
||||
var bones = this.bones;
|
||||
@ -191,16 +197,19 @@ namespace Spine {
|
||||
Bone parent = constrained.Items[0];
|
||||
SortBone(parent);
|
||||
|
||||
if (constrained.Count > 1) {
|
||||
Bone child = constrained.Items[constrained.Count - 1];
|
||||
if (!updateCache.Contains(child))
|
||||
updateCacheReset.Add(child);
|
||||
if (constrained.Count == 1) {
|
||||
updateCache.Add(constraint);
|
||||
SortReset(parent.children);
|
||||
}
|
||||
else {
|
||||
Bone child = constrained.Items[constrained.Count - 1];
|
||||
SortBone(child);
|
||||
|
||||
updateCache.Add(constraint);
|
||||
updateCache.Add(constraint);
|
||||
|
||||
SortReset(parent.children);
|
||||
constrained.Items[constrained.Count - 1].sorted = true;
|
||||
SortReset(parent.children);
|
||||
child.sorted = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void SortPathConstraint (PathConstraint constraint) {
|
||||
@ -218,17 +227,17 @@ namespace Spine {
|
||||
Attachment attachment = slot.attachment;
|
||||
if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone);
|
||||
|
||||
var constrained = constraint.bones;
|
||||
int boneCount = constrained.Count;
|
||||
var constrained = constraint.bones.Items;
|
||||
int boneCount = constraint.bones.Count;
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
SortBone(constrained.Items[i]);
|
||||
SortBone(constrained[i]);
|
||||
|
||||
updateCache.Add(constraint);
|
||||
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
SortReset(constrained.Items[i].children);
|
||||
SortReset(constrained[i].children);
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
constrained.Items[i].sorted = true;
|
||||
constrained[i].sorted = true;
|
||||
}
|
||||
|
||||
private void SortTransformConstraint (TransformConstraint constraint) {
|
||||
@ -238,25 +247,25 @@ namespace Spine {
|
||||
|
||||
SortBone(constraint.target);
|
||||
|
||||
var constrained = constraint.bones;
|
||||
int boneCount = constrained.Count;
|
||||
var constrained = constraint.bones.Items;
|
||||
int boneCount = constraint.bones.Count;
|
||||
if (constraint.data.local) {
|
||||
for (int i = 0; i < boneCount; i++) {
|
||||
Bone child = constrained.Items[i];
|
||||
Bone child = constrained[i];
|
||||
SortBone(child.parent);
|
||||
if (!updateCache.Contains(child)) updateCacheReset.Add(child);
|
||||
SortBone(child);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
SortBone(constrained.Items[i]);
|
||||
SortBone(constrained[i]);
|
||||
}
|
||||
|
||||
updateCache.Add(constraint);
|
||||
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
SortReset(constrained.Items[i].children);
|
||||
SortReset(constrained[i].children);
|
||||
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) {
|
||||
@ -271,12 +280,12 @@ namespace Spine {
|
||||
if (pathBones == null)
|
||||
SortBone(slotBone);
|
||||
else {
|
||||
var bones = this.bones;
|
||||
var bones = this.bones.Items;
|
||||
for (int i = 0, n = pathBones.Length; i < n;) {
|
||||
int nn = pathBones[i++];
|
||||
nn += i;
|
||||
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 () {
|
||||
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;
|
||||
}
|
||||
var updateItems = this.updateCache.Items;
|
||||
for (int i = 0, n = updateCache.Count; i < n; i++)
|
||||
updateItems[i].Update();
|
||||
var updateCache = this.updateCache.Items;
|
||||
for (int i = 0, n = this.updateCache.Count; i < n; i++)
|
||||
updateCache[i].Update();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -324,22 +326,7 @@ namespace Spine {
|
||||
/// all constraints.
|
||||
/// </summary>
|
||||
public void UpdateWorldTransform (Bone parent) {
|
||||
// This partial update avoids computing the world transform for constrained bones when 1) the bone is not updated
|
||||
// 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;
|
||||
}
|
||||
if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null.");
|
||||
|
||||
// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
|
||||
Bone rootBone = this.RootBone;
|
||||
@ -358,10 +345,9 @@ namespace Spine {
|
||||
rootBone.d = (pc * lb + pd * ld) * scaleY;
|
||||
|
||||
// Update everything except root bone.
|
||||
var updateCache = this.updateCache;
|
||||
var updateCacheItems = updateCache.Items;
|
||||
for (int i = 0, n = updateCache.Count; i < n; i++) {
|
||||
var updatable = updateCacheItems[i];
|
||||
var updateCache = this.updateCache.Items;
|
||||
for (int i = 0, n = this.updateCache.Count; i < n; i++) {
|
||||
var updatable = updateCache[i];
|
||||
if (updatable != rootBone)
|
||||
updatable.Update();
|
||||
}
|
||||
@ -375,13 +361,13 @@ namespace Spine {
|
||||
|
||||
/// <summary>Sets the bones and constraints to their setup pose values.</summary>
|
||||
public void SetBonesToSetupPose () {
|
||||
var bonesItems = this.bones.Items;
|
||||
for (int i = 0, n = bones.Count; i < n; i++)
|
||||
bonesItems[i].SetToSetupPose();
|
||||
var bones = this.bones.Items;
|
||||
for (int i = 0, n = this.bones.Count; i < n; i++)
|
||||
bones[i].SetToSetupPose();
|
||||
|
||||
var ikConstraintsItems = this.ikConstraints.Items;
|
||||
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
|
||||
IkConstraint constraint = ikConstraintsItems[i];
|
||||
var ikConstraints = this.ikConstraints.Items;
|
||||
for (int i = 0, n = this.ikConstraints.Count; i < n; i++) {
|
||||
IkConstraint constraint = ikConstraints[i];
|
||||
constraint.mix = constraint.data.mix;
|
||||
constraint.softness = constraint.data.softness;
|
||||
constraint.bendDirection = constraint.data.bendDirection;
|
||||
@ -389,9 +375,9 @@ namespace Spine {
|
||||
constraint.stretch = constraint.data.stretch;
|
||||
}
|
||||
|
||||
var transformConstraintsItems = this.transformConstraints.Items;
|
||||
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
|
||||
TransformConstraint constraint = transformConstraintsItems[i];
|
||||
var transformConstraints = this.transformConstraints.Items;
|
||||
for (int i = 0, n = this.transformConstraints.Count; i < n; i++) {
|
||||
TransformConstraint constraint = transformConstraints[i];
|
||||
TransformConstraintData constraintData = constraint.data;
|
||||
constraint.rotateMix = constraintData.rotateMix;
|
||||
constraint.translateMix = constraintData.translateMix;
|
||||
@ -399,9 +385,9 @@ namespace Spine {
|
||||
constraint.shearMix = constraintData.shearMix;
|
||||
}
|
||||
|
||||
var pathConstraintItems = this.pathConstraints.Items;
|
||||
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
|
||||
PathConstraint constraint = pathConstraintItems[i];
|
||||
var pathConstraints = this.pathConstraints.Items;
|
||||
for (int i = 0, n = this.pathConstraints.Count; i < n; i++) {
|
||||
PathConstraint constraint = pathConstraints[i];
|
||||
PathConstraintData constraintData = constraint.data;
|
||||
constraint.position = constraintData.position;
|
||||
constraint.spacing = constraintData.spacing;
|
||||
@ -411,23 +397,21 @@ namespace Spine {
|
||||
}
|
||||
|
||||
public void SetSlotsToSetupPose () {
|
||||
var slots = this.slots;
|
||||
var slotsItems = slots.Items;
|
||||
drawOrder.Clear();
|
||||
for (int i = 0, n = slots.Count; i < n; i++)
|
||||
drawOrder.Add(slotsItems[i]);
|
||||
|
||||
for (int i = 0, n = slots.Count; i < n; i++)
|
||||
slotsItems[i].SetToSetupPose();
|
||||
var slots = this.slots.Items;
|
||||
int n = this.slots.Count;
|
||||
Array.Copy(slots, 0, drawOrder.Items, 0, n);
|
||||
for (int i = 0; i < n; i++)
|
||||
slots[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>
|
||||
public Bone FindBone (string boneName) {
|
||||
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
|
||||
var bones = this.bones;
|
||||
var bonesItems = bones.Items;
|
||||
for (int i = 0, n = bones.Count; i < n; i++) {
|
||||
Bone bone = bonesItems[i];
|
||||
var bones = this.bones.Items;
|
||||
for (int i = 0, n = this.bones.Count; i < n; i++) {
|
||||
Bone bone = bones[i];
|
||||
if (bone.data.name == boneName) return bone;
|
||||
}
|
||||
return null;
|
||||
@ -443,13 +427,14 @@ namespace Spine {
|
||||
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>
|
||||
public Slot FindSlot (string slotName) {
|
||||
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
|
||||
var slots = this.slots;
|
||||
var slotsItems = slots.Items;
|
||||
for (int i = 0, n = slots.Count; i < n; i++) {
|
||||
Slot slot = slotsItems[i];
|
||||
var slots = this.slots.Items;
|
||||
for (int i = 0, n = this.slots.Count; i < n; i++) {
|
||||
Slot slot = slots[i];
|
||||
if (slot.data.name == slotName) return slot;
|
||||
}
|
||||
return null;
|
||||
@ -461,11 +446,11 @@ namespace Spine {
|
||||
var slots = this.slots;
|
||||
var slotsItems = slots.Items;
|
||||
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;
|
||||
}
|
||||
|
||||
/// <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) {
|
||||
Skin foundSkin = data.FindSkin(skinName);
|
||||
if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName");
|
||||
@ -506,7 +491,7 @@ namespace Spine {
|
||||
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>
|
||||
public Attachment GetAttachment (string slotName, string attachmentName) {
|
||||
return GetAttachment(data.FindSlotIndex(slotName), attachmentName);
|
||||
@ -543,34 +528,40 @@ namespace Spine {
|
||||
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>
|
||||
public IkConstraint FindIkConstraint (string constraintName) {
|
||||
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
|
||||
ExposedList<IkConstraint> ikConstraints = this.ikConstraints;
|
||||
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
|
||||
IkConstraint ikConstraint = ikConstraints.Items[i];
|
||||
var ikConstraints = this.ikConstraints.Items;
|
||||
for (int i = 0, n = this.ikConstraints.Count; i < n; i++) {
|
||||
IkConstraint ikConstraint = ikConstraints[i];
|
||||
if (ikConstraint.data.name == constraintName) return ikConstraint;
|
||||
}
|
||||
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>
|
||||
public TransformConstraint FindTransformConstraint (string constraintName) {
|
||||
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
|
||||
ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
|
||||
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
|
||||
TransformConstraint transformConstraint = transformConstraints.Items[i];
|
||||
var transformConstraints = this.transformConstraints.Items;
|
||||
for (int i = 0, n = this.transformConstraints.Count; i < n; i++) {
|
||||
TransformConstraint transformConstraint = transformConstraints[i];
|
||||
if (transformConstraint.data.Name == constraintName) return transformConstraint;
|
||||
}
|
||||
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>
|
||||
public PathConstraint FindPathConstraint (string constraintName) {
|
||||
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
|
||||
ExposedList<PathConstraint> pathConstraints = this.pathConstraints;
|
||||
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
|
||||
PathConstraint constraint = pathConstraints.Items[i];
|
||||
var pathConstraints = this.pathConstraints.Items;
|
||||
for (int i = 0, n = this.pathConstraints.Count; i < n; i++) {
|
||||
PathConstraint constraint = pathConstraints[i];
|
||||
if (constraint.data.Name.Equals(constraintName)) return constraint;
|
||||
}
|
||||
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) {
|
||||
float[] temp = vertexBuffer;
|
||||
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;
|
||||
for (int i = 0, n = drawOrderItems.Length; i < n; i++) {
|
||||
Slot slot = drawOrderItems[i];
|
||||
for (int i = 0, n = this.drawOrder.Count; i < n; i++) {
|
||||
Slot slot = drawOrder[i];
|
||||
if (!slot.bone.active) continue;
|
||||
int verticesLength = 0;
|
||||
float[] vertices = null;
|
||||
|
||||
@ -34,6 +34,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
#if WINDOWS_STOREAPP
|
||||
using System.Threading.Tasks;
|
||||
@ -41,7 +42,7 @@ using Windows.Storage;
|
||||
#endif
|
||||
|
||||
namespace Spine {
|
||||
public class SkeletonBinary {
|
||||
public class SkeletonBinary : SkeletonLoader {
|
||||
public const int BONE_ROTATE = 0;
|
||||
public const int BONE_TRANSLATE = 1;
|
||||
public const int BONE_SCALE = 2;
|
||||
@ -59,22 +60,15 @@ namespace Spine {
|
||||
public const int CURVE_STEPPED = 1;
|
||||
public const int CURVE_BEZIER = 2;
|
||||
|
||||
public float Scale { get; set; }
|
||||
|
||||
private AttachmentLoader attachmentLoader;
|
||||
private List<SkeletonJson.LinkedMesh> linkedMeshes = new List<SkeletonJson.LinkedMesh>();
|
||||
public SkeletonBinary (AttachmentLoader attachmentLoader)
|
||||
:base(attachmentLoader) {
|
||||
}
|
||||
|
||||
public SkeletonBinary (params Atlas[] atlasArray)
|
||||
: this(new AtlasAttachmentLoader(atlasArray)) {
|
||||
: base(atlasArray) {
|
||||
}
|
||||
|
||||
public SkeletonBinary (AttachmentLoader attachmentLoader) {
|
||||
if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader");
|
||||
this.attachmentLoader = attachmentLoader;
|
||||
Scale = 1;
|
||||
}
|
||||
|
||||
#if !ISUNITY && WINDOWS_STOREAPP
|
||||
#if !ISUNITY && WINDOWS_STOREAPP
|
||||
private async Task<SkeletonData> ReadFile(string path) {
|
||||
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
|
||||
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;
|
||||
}
|
||||
#else
|
||||
public SkeletonData ReadSkeletonData (String path) {
|
||||
#else
|
||||
public override SkeletonData ReadSkeletonData (string path) {
|
||||
#if WINDOWS_PHONE
|
||||
using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) {
|
||||
#else
|
||||
@ -119,13 +113,13 @@ namespace Spine {
|
||||
|
||||
public SkeletonData ReadSkeletonData (Stream file) {
|
||||
if (file == null) throw new ArgumentNullException("file");
|
||||
float scale = Scale;
|
||||
float scale = this.scale;
|
||||
|
||||
var skeletonData = new SkeletonData();
|
||||
SkeletonInput input = new SkeletonInput(file);
|
||||
|
||||
skeletonData.hash = input.ReadString();
|
||||
if (skeletonData.hash.Length == 0) skeletonData.hash = null;
|
||||
long hash = input.ReadLong();
|
||||
skeletonData.hash = hash == 0 ? null : hash.ToString();
|
||||
skeletonData.version = input.ReadString();
|
||||
if (skeletonData.version.Length == 0) skeletonData.version = null;
|
||||
if ("3.8.75" == skeletonData.version)
|
||||
@ -151,16 +145,15 @@ namespace Spine {
|
||||
Object[] o;
|
||||
|
||||
// Strings.
|
||||
input.strings = new ExposedList<string>(n = input.ReadInt(true));
|
||||
o = input.strings.Resize(n).Items;
|
||||
o = input.strings = new String[n = input.ReadInt(true)];
|
||||
for (int i = 0; i < n; i++)
|
||||
o[i] = input.ReadString();
|
||||
|
||||
// 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++) {
|
||||
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);
|
||||
data.rotation = input.ReadFloat();
|
||||
data.x = input.ReadFloat() * scale;
|
||||
@ -169,18 +162,18 @@ namespace Spine {
|
||||
data.scaleY = input.ReadFloat();
|
||||
data.shearX = input.ReadFloat();
|
||||
data.shearY = input.ReadFloat();
|
||||
data.length = input.ReadFloat() * scale;
|
||||
data.Length = input.ReadFloat() * scale;
|
||||
data.transformMode = TransformModeValues[input.ReadInt(true)];
|
||||
data.skinRequired = input.ReadBoolean();
|
||||
if (nonessential) input.ReadInt(); // Skip bone color.
|
||||
o[i] = data;
|
||||
bones[i] = data;
|
||||
}
|
||||
|
||||
// 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++) {
|
||||
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);
|
||||
int color = input.ReadInt();
|
||||
slotData.r = ((color & 0xff000000) >> 24) / 255f;
|
||||
@ -198,7 +191,7 @@ namespace Spine {
|
||||
|
||||
slotData.attachmentName = input.ReadStringRef();
|
||||
slotData.blendMode = (BlendMode)input.ReadInt(true);
|
||||
o[i] = slotData;
|
||||
slots[i] = slotData;
|
||||
}
|
||||
|
||||
// IK constraints.
|
||||
@ -207,10 +200,10 @@ namespace Spine {
|
||||
IkConstraintData data = new IkConstraintData(input.ReadString());
|
||||
data.order = input.ReadInt(true);
|
||||
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++)
|
||||
bones[ii] = skeletonData.bones.Items[input.ReadInt(true)];
|
||||
data.target = skeletonData.bones.Items[input.ReadInt(true)];
|
||||
constraintBones[ii] = bones[input.ReadInt(true)];
|
||||
data.target = bones[input.ReadInt(true)];
|
||||
data.mix = input.ReadFloat();
|
||||
data.softness = input.ReadFloat() * scale;
|
||||
data.bendDirection = input.ReadSByte();
|
||||
@ -226,10 +219,10 @@ namespace Spine {
|
||||
TransformConstraintData data = new TransformConstraintData(input.ReadString());
|
||||
data.order = input.ReadInt(true);
|
||||
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++)
|
||||
bones[ii] = skeletonData.bones.Items[input.ReadInt(true)];
|
||||
data.target = skeletonData.bones.Items[input.ReadInt(true)];
|
||||
constraintBones[ii] = bones[input.ReadInt(true)];
|
||||
data.target = bones[input.ReadInt(true)];
|
||||
data.local = input.ReadBoolean();
|
||||
data.relative = input.ReadBoolean();
|
||||
data.offsetRotation = input.ReadFloat();
|
||||
@ -251,10 +244,10 @@ namespace Spine {
|
||||
PathConstraintData data = new PathConstraintData(input.ReadString());
|
||||
data.order = input.ReadInt(true);
|
||||
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++)
|
||||
bones[ii] = skeletonData.bones.Items[input.ReadInt(true)];
|
||||
data.target = skeletonData.slots.Items[input.ReadInt(true)];
|
||||
constraintBones[ii] = bones[input.ReadInt(true)];
|
||||
data.target = slots[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.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(input.ReadInt(true));
|
||||
@ -286,7 +279,7 @@ namespace Spine {
|
||||
// Linked meshes.
|
||||
n = linkedMeshes.Count;
|
||||
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);
|
||||
if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin);
|
||||
Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
|
||||
@ -334,16 +327,21 @@ namespace Spine {
|
||||
} else {
|
||||
skin = new Skin(input.ReadStringRef());
|
||||
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++)
|
||||
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++)
|
||||
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++)
|
||||
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++)
|
||||
skin.constraints.Add(skeletonData.pathConstraints.Items[input.ReadInt(true)]);
|
||||
skin.constraints.Add(pathConstraintsItems[input.ReadInt(true)]);
|
||||
skin.constraints.TrimExcess();
|
||||
|
||||
slotCount = input.ReadInt(true);
|
||||
}
|
||||
for (int i = 0; i < slotCount; i++) {
|
||||
@ -359,14 +357,12 @@ namespace Spine {
|
||||
|
||||
private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonData, Skin skin, int slotIndex,
|
||||
String attachmentName, bool nonessential) {
|
||||
|
||||
float scale = Scale;
|
||||
float scale = this.scale;
|
||||
|
||||
String name = input.ReadStringRef();
|
||||
if (name == null) name = attachmentName;
|
||||
|
||||
AttachmentType type = (AttachmentType)input.ReadByte();
|
||||
switch (type) {
|
||||
switch ((AttachmentType)input.ReadByte()) {
|
||||
case AttachmentType.Region: {
|
||||
String path = input.ReadStringRef();
|
||||
float rotation = input.ReadFloat();
|
||||
@ -529,7 +525,7 @@ namespace Spine {
|
||||
}
|
||||
|
||||
private Vertices ReadVertices (SkeletonInput input, int vertexCount) {
|
||||
float scale = Scale;
|
||||
float scale = this.scale;
|
||||
int verticesLength = vertexCount << 1;
|
||||
Vertices vertices = new Vertices();
|
||||
if(!input.ReadBoolean()) {
|
||||
@ -574,66 +570,97 @@ namespace Spine {
|
||||
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) {
|
||||
var timelines = new ExposedList<Timeline>(32);
|
||||
float scale = Scale;
|
||||
float duration = 0;
|
||||
var timelines = new ExposedList<Timeline>(input.ReadInt(true));
|
||||
float scale = this.scale;
|
||||
|
||||
// Slot timelines.
|
||||
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
|
||||
int slotIndex = input.ReadInt(true);
|
||||
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
|
||||
int timelineType = input.ReadByte();
|
||||
int frameCount = input.ReadInt(true);
|
||||
int timelineType = input.ReadByte(), frameCount = input.ReadInt(true), frameLast = frameCount - 1;
|
||||
switch (timelineType) {
|
||||
case SLOT_ATTACHMENT: {
|
||||
AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
|
||||
timeline.slotIndex = slotIndex;
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
|
||||
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadStringRef());
|
||||
timelines.Add(timeline);
|
||||
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);
|
||||
case SLOT_ATTACHMENT: {
|
||||
AttachmentTimeline timeline = new AttachmentTimeline(frameCount, slotIndex);
|
||||
for (int frame = 0; frame < frameCount; frame++)
|
||||
timeline.SetFrame(frame, input.ReadFloat(), input.ReadStringRef());
|
||||
timelines.Add(timeline);
|
||||
break;
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * ColorTimeline.ENTRIES]);
|
||||
break;
|
||||
}
|
||||
case SLOT_TWO_COLOR: {
|
||||
TwoColorTimeline timeline = new TwoColorTimeline(frameCount);
|
||||
timeline.slotIndex = slotIndex;
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
||||
case SLOT_COLOR: {
|
||||
ColorTimeline timeline = new ColorTimeline(frameCount, input.ReadInt(true), slotIndex);
|
||||
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;
|
||||
int color2 = input.ReadInt(); // 0x00rrggbb
|
||||
float r2 = ((color2 & 0x00ff0000) >> 16) / 255f;
|
||||
float g2 = ((color2 & 0x0000ff00) >> 8) / 255f;
|
||||
float b2 = ((color2 & 0x000000ff)) / 255f;
|
||||
|
||||
timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2);
|
||||
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
|
||||
float r = input.Read() / 255f, g = input.Read() / 255f;
|
||||
float b = input.Read() / 255f, a = input.Read() / 255f;
|
||||
for (int frame = 0, bezier = 0; ; frame++) {
|
||||
timeline.SetFrame(frame, time, r, g, b, a);
|
||||
if (frame == frameLast) break;
|
||||
float time2 = input.ReadFloat();
|
||||
float r2 = input.Read() / 255f, g2 = input.Read() / 255f;
|
||||
float b2 = input.Read() / 255f, a2 = 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, 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++) {
|
||||
int boneIndex = input.ReadInt(true);
|
||||
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
|
||||
int timelineType = input.ReadByte();
|
||||
int frameCount = input.ReadInt(true);
|
||||
switch (timelineType) {
|
||||
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]);
|
||||
switch (input.ReadByte()) {
|
||||
case BONE_ROTATE:
|
||||
timelines.Add(ReadTimeline(input, new RotateTimeline(input.ReadInt(true), input.ReadInt(true), boneIndex), 1));
|
||||
break;
|
||||
}
|
||||
case BONE_TRANSLATE:
|
||||
case BONE_SCALE:
|
||||
case BONE_SHEAR: {
|
||||
TranslateTimeline timeline;
|
||||
float timelineScale = 1;
|
||||
if (timelineType == BONE_SCALE)
|
||||
timeline = new ScaleTimeline(frameCount);
|
||||
else if (timelineType == BONE_SHEAR)
|
||||
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]);
|
||||
case BONE_TRANSLATE:
|
||||
timelines
|
||||
.Add(ReadTimeline(input, new TranslateTimeline(input.ReadInt(true), input.ReadInt(true), boneIndex), scale));
|
||||
break;
|
||||
case BONE_SCALE:
|
||||
timelines.Add(ReadTimeline(input, new ScaleTimeline(input.ReadInt(true), input.ReadInt(true), boneIndex), 1));
|
||||
break;
|
||||
case BONE_SHEAR:
|
||||
timelines.Add(ReadTimeline(input, new ShearTimeline(input.ReadInt(true), input.ReadInt(true), boneIndex), 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IK constraint timelines.
|
||||
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
|
||||
int index = input.ReadInt(true);
|
||||
int frameCount = input.ReadInt(true);
|
||||
IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount) {
|
||||
ikConstraintIndex = index
|
||||
};
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
||||
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat() * scale, input.ReadSByte(), input.ReadBoolean(),
|
||||
input.ReadBoolean());
|
||||
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
|
||||
int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1;
|
||||
IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount, input.ReadInt(true), index);
|
||||
float time = input.ReadFloat(), mix = input.ReadFloat(), softness = input.ReadFloat() * scale;
|
||||
for (int frame = 0, bezier = 0; ; frame++) {
|
||||
timeline.SetFrame(frame, time, mix, softness, input.ReadSByte(), input.ReadBoolean(), input.ReadBoolean());
|
||||
if (frame == frameLast) break;
|
||||
float time2 = input.ReadFloat(), mix2 = input.ReadFloat(), softness2 = input.ReadFloat() * scale;
|
||||
switch (input.ReadByte()) {
|
||||
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);
|
||||
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]);
|
||||
}
|
||||
|
||||
// Transform constraint timelines.
|
||||
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
|
||||
int index = input.ReadInt(true);
|
||||
int frameCount = input.ReadInt(true);
|
||||
TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount);
|
||||
timeline.transformConstraintIndex = index;
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
||||
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat(), input.ReadFloat(),
|
||||
input.ReadFloat());
|
||||
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
|
||||
int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1;
|
||||
TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount, input.ReadInt(true), index);
|
||||
float time = input.ReadFloat(), rotateMix = input.ReadFloat(), translateMix = input.ReadFloat(),
|
||||
scaleMix = input.ReadFloat(), shearMix = input.ReadFloat();
|
||||
for (int frame = 0, bezier = 0; ; frame++) {
|
||||
timeline.SetFrame(frame, time, rotateMix, translateMix, scaleMix, shearMix);
|
||||
if (frame == frameLast) break;
|
||||
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);
|
||||
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]);
|
||||
}
|
||||
|
||||
// Path constraint timelines.
|
||||
@ -719,40 +748,21 @@ namespace Spine {
|
||||
int index = input.ReadInt(true);
|
||||
PathConstraintData data = skeletonData.pathConstraints.Items[index];
|
||||
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
|
||||
int timelineType = input.ReadSByte();
|
||||
int frameCount = input.ReadInt(true);
|
||||
switch(timelineType) {
|
||||
switch (input.ReadByte()) {
|
||||
case PATH_POSITION:
|
||||
case PATH_SPACING: {
|
||||
PathConstraintPositionTimeline timeline;
|
||||
float timelineScale = 1;
|
||||
if (timelineType == PATH_SPACING) {
|
||||
timeline = new PathConstraintSpacingTimeline(frameCount);
|
||||
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale;
|
||||
} else {
|
||||
timeline = new PathConstraintPositionTimeline(frameCount);
|
||||
if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
|
||||
}
|
||||
timeline.pathConstraintIndex = index;
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
||||
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat() * timelineScale);
|
||||
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;
|
||||
}
|
||||
timelines
|
||||
.Add(ReadTimeline(input, new PathConstraintPositionTimeline(input.ReadInt(true), input.ReadInt(true), index),
|
||||
data.positionMode == PositionMode.Fixed ? scale : 1));
|
||||
break;
|
||||
case PATH_SPACING:
|
||||
timelines
|
||||
.Add(ReadTimeline(input, new PathConstraintSpacingTimeline(input.ReadInt(true), input.ReadInt(true), index),
|
||||
data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed ? scale : 1));
|
||||
break;
|
||||
case PATH_MIX:
|
||||
timelines
|
||||
.Add(ReadTimeline(input, new PathConstraintMixTimeline(input.ReadInt(true), input.ReadInt(true), index), 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -763,18 +773,18 @@ namespace Spine {
|
||||
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
|
||||
int slotIndex = input.ReadInt(true);
|
||||
for (int iii = 0, nnn = input.ReadInt(true); iii < nnn; iii++) {
|
||||
VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, input.ReadStringRef());
|
||||
bool weighted = attachment.bones != null;
|
||||
float[] vertices = attachment.vertices;
|
||||
int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length;
|
||||
String attachmentName = input.ReadStringRef();
|
||||
VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, attachmentName);
|
||||
if (attachment == null) throw new SerializationException("Vertex attachment not found: " + attachmentName);
|
||||
bool weighted = attachment.Bones != null;
|
||||
float[] vertices = attachment.Vertices;
|
||||
int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length;
|
||||
|
||||
int frameCount = input.ReadInt(true);
|
||||
DeformTimeline timeline = new DeformTimeline(frameCount);
|
||||
timeline.slotIndex = slotIndex;
|
||||
timeline.attachment = attachment;
|
||||
int frameCount = input.ReadInt(true), frameLast = frameCount - 1;
|
||||
DeformTimeline timeline = new DeformTimeline(frameCount, input.ReadInt(true), slotIndex, 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;
|
||||
int end = input.ReadInt(true);
|
||||
if (end == 0)
|
||||
@ -786,7 +796,8 @@ namespace Spine {
|
||||
if (scale == 1) {
|
||||
for (int v = start; v < end; v++)
|
||||
deform[v] = input.ReadFloat();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
for (int v = start; v < end; v++)
|
||||
deform[v] = input.ReadFloat() * scale;
|
||||
}
|
||||
@ -795,12 +806,20 @@ namespace Spine {
|
||||
deform[v] += vertices[v];
|
||||
}
|
||||
}
|
||||
|
||||
timeline.SetFrame(frameIndex, time, deform);
|
||||
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
|
||||
timeline.SetFrame(frame, time, deform);
|
||||
if (frame == frameLast) break;
|
||||
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);
|
||||
duration = Math.Max(duration, timeline.frames[frameCount - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -835,7 +854,6 @@ namespace Spine {
|
||||
timeline.SetFrame(i, time, drawOrder);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]);
|
||||
}
|
||||
|
||||
// Event timeline.
|
||||
@ -845,34 +863,75 @@ namespace Spine {
|
||||
for (int i = 0; i < eventCount; i++) {
|
||||
float time = input.ReadFloat();
|
||||
EventData eventData = skeletonData.events.Items[input.ReadInt(true)];
|
||||
Event e = new Event(time, eventData) {
|
||||
Int = input.ReadInt(false),
|
||||
Float = input.ReadFloat(),
|
||||
String = input.ReadBoolean() ? input.ReadString() : eventData.String
|
||||
};
|
||||
if (e.data.AudioPath != null) {
|
||||
Event e = new Event(time, eventData);
|
||||
e.intValue = input.ReadInt(false);
|
||||
e.floatValue = input.ReadFloat();
|
||||
e.stringValue = input.ReadBoolean() ? input.ReadString() : eventData.String;
|
||||
if (e.Data.AudioPath != null) {
|
||||
e.volume = input.ReadFloat();
|
||||
e.balance = input.ReadFloat();
|
||||
}
|
||||
timeline.SetFrame(i, e);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
private void ReadCurve (SkeletonInput input, int frameIndex, CurveTimeline timeline) {
|
||||
switch (input.ReadByte()) {
|
||||
case CURVE_STEPPED:
|
||||
timeline.SetStepped(frameIndex);
|
||||
break;
|
||||
case CURVE_BEZIER:
|
||||
timeline.SetCurve(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat(), input.ReadFloat());
|
||||
break;
|
||||
/// <exception cref="IOException">Throws IOException when a read operation fails.</exception>
|
||||
private Timeline ReadTimeline (SkeletonInput input, CurveTimeline1 timeline, float scale) {
|
||||
float time = input.ReadFloat(), value = input.ReadFloat() * scale;
|
||||
for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1;; frame++) {
|
||||
timeline.SetFrame(frame, time, value);
|
||||
if (frame == frameLast) break;
|
||||
float time2 = input.ReadFloat(), value2 = input.ReadFloat() * scale;
|
||||
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
|
||||
@ -883,14 +942,18 @@ namespace Spine {
|
||||
|
||||
internal class SkeletonInput {
|
||||
private byte[] chars = new byte[32];
|
||||
private byte[] bytesBigEndian = new byte[4];
|
||||
internal ExposedList<String> strings;
|
||||
private byte[] bytesBigEndian = new byte[8];
|
||||
internal string[] strings;
|
||||
Stream input;
|
||||
|
||||
public SkeletonInput (Stream input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
public int Read () {
|
||||
return input.ReadByte();
|
||||
}
|
||||
|
||||
public byte ReadByte () {
|
||||
return (byte)input.ReadByte();
|
||||
}
|
||||
@ -922,6 +985,18 @@ namespace Spine {
|
||||
+ 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) {
|
||||
int b = input.ReadByte();
|
||||
int result = b & 0x7F;
|
||||
@ -959,7 +1034,7 @@ namespace Spine {
|
||||
///<return>May be null.</return>
|
||||
public String ReadStringRef () {
|
||||
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) {
|
||||
@ -974,20 +1049,9 @@ namespace Spine {
|
||||
/// <summary>Returns the version string of binary skeleton data.</summary>
|
||||
public string GetVersionString () {
|
||||
try {
|
||||
// Hash.
|
||||
int byteCount = ReadInt(true);
|
||||
if (byteCount > 1) input.Position += byteCount - 1;
|
||||
|
||||
// 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");
|
||||
ReadLong(); // long hash
|
||||
string version = ReadString();
|
||||
return version;
|
||||
} catch (Exception e) {
|
||||
throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input");
|
||||
}
|
||||
|
||||
@ -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
|
||||
/// 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) {
|
||||
ExposedList<Polygon> polygons = Polygons;
|
||||
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
|
||||
/// 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) {
|
||||
ExposedList<Polygon> polygons = Polygons;
|
||||
for (int i = 0, n = polygons.Count; i < n; i++)
|
||||
|
||||
@ -50,6 +50,8 @@ namespace Spine {
|
||||
internal float fps;
|
||||
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; } }
|
||||
|
||||
/// <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; } }
|
||||
/// <summary>The Spine version used to export this data, or null.</summary>
|
||||
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; } }
|
||||
|
||||
/// <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; } }
|
||||
|
||||
/// <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; } }
|
||||
|
||||
/// <summary>
|
||||
/// The dopesheet FPS in Spine. Available only when nonessential data was exported.</summary>
|
||||
/// <summary>The dopesheet FPS in Spine, or zero if nonessential data was not exported.</summary>
|
||||
public float Fps { get { return fps; } set { fps = value; } }
|
||||
|
||||
// --- Bones.
|
||||
@ -99,10 +103,9 @@ namespace Spine {
|
||||
/// <returns>May be null.</returns>
|
||||
public BoneData FindBone (string boneName) {
|
||||
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
|
||||
var bones = this.bones;
|
||||
var bonesItems = bones.Items;
|
||||
for (int i = 0, n = bones.Count; i < n; i++) {
|
||||
BoneData bone = bonesItems[i];
|
||||
var bones = this.bones.Items;
|
||||
for (int i = 0, n = this.bones.Count; i < n; i++) {
|
||||
BoneData bone = bones[i];
|
||||
if (bone.name == boneName) return bone;
|
||||
}
|
||||
return null;
|
||||
@ -111,10 +114,9 @@ namespace Spine {
|
||||
/// <returns>-1 if the bone was not found.</returns>
|
||||
public int FindBoneIndex (string boneName) {
|
||||
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
|
||||
var bones = this.bones;
|
||||
var bonesItems = bones.Items;
|
||||
for (int i = 0, n = bones.Count; i < n; i++)
|
||||
if (bonesItems[i].name == boneName) return i;
|
||||
var bones = this.bones.Items;
|
||||
for (int i = 0, n = this.bones.Count; i < n; i++)
|
||||
if (bones[i].name == boneName) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -123,9 +125,9 @@ namespace Spine {
|
||||
/// <returns>May be null.</returns>
|
||||
public SlotData FindSlot (string slotName) {
|
||||
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
|
||||
ExposedList<SlotData> slots = this.slots;
|
||||
for (int i = 0, n = slots.Count; i < n; i++) {
|
||||
SlotData slot = slots.Items[i];
|
||||
var slots = this.slots.Items;
|
||||
for (int i = 0, n = this.slots.Count; i < n; i++) {
|
||||
SlotData slot = slots[i];
|
||||
if (slot.name == slotName) return slot;
|
||||
}
|
||||
return null;
|
||||
@ -165,9 +167,9 @@ namespace Spine {
|
||||
/// <returns>May be null.</returns>
|
||||
public Animation FindAnimation (string animationName) {
|
||||
if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null.");
|
||||
ExposedList<Animation> animations = this.animations;
|
||||
for (int i = 0, n = animations.Count; i < n; i++) {
|
||||
Animation animation = animations.Items[i];
|
||||
var animations = this.animations.Items;
|
||||
for (int i = 0, n = this.animations.Count; i < n; i++) {
|
||||
Animation animation = animations[i];
|
||||
if (animation.name == animationName) return animation;
|
||||
}
|
||||
return null;
|
||||
@ -178,9 +180,9 @@ namespace Spine {
|
||||
/// <returns>May be null.</returns>
|
||||
public IkConstraintData FindIkConstraint (string constraintName) {
|
||||
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
|
||||
ExposedList<IkConstraintData> ikConstraints = this.ikConstraints;
|
||||
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
|
||||
IkConstraintData ikConstraint = ikConstraints.Items[i];
|
||||
var ikConstraints = this.ikConstraints.Items;
|
||||
for (int i = 0, n = this.ikConstraints.Count; i < n; i++) {
|
||||
IkConstraintData ikConstraint = ikConstraints[i];
|
||||
if (ikConstraint.name == constraintName) return ikConstraint;
|
||||
}
|
||||
return null;
|
||||
@ -191,9 +193,9 @@ namespace Spine {
|
||||
/// <returns>May be null.</returns>
|
||||
public TransformConstraintData FindTransformConstraint (string constraintName) {
|
||||
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
|
||||
ExposedList<TransformConstraintData> transformConstraints = this.transformConstraints;
|
||||
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
|
||||
TransformConstraintData transformConstraint = transformConstraints.Items[i];
|
||||
var transformConstraints = this.transformConstraints.Items;
|
||||
for (int i = 0, n = this.transformConstraints.Count; i < n; i++) {
|
||||
TransformConstraintData transformConstraint = transformConstraints[i];
|
||||
if (transformConstraint.name == constraintName) return transformConstraint;
|
||||
}
|
||||
return null;
|
||||
@ -204,9 +206,9 @@ namespace Spine {
|
||||
/// <returns>May be null.</returns>
|
||||
public PathConstraintData FindPathConstraint (string constraintName) {
|
||||
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
|
||||
ExposedList<PathConstraintData> pathConstraints = this.pathConstraints;
|
||||
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
|
||||
PathConstraintData constraint = pathConstraints.Items[i];
|
||||
var pathConstraints = this.pathConstraints.Items;
|
||||
for (int i = 0, n = this.pathConstraints.Count; i < n; i++) {
|
||||
PathConstraintData constraint = pathConstraints[i];
|
||||
if (constraint.name.Equals(constraintName)) return constraint;
|
||||
}
|
||||
return null;
|
||||
|
||||
@ -41,23 +41,27 @@ using Windows.Storage;
|
||||
#endif
|
||||
|
||||
namespace Spine {
|
||||
public class SkeletonJson {
|
||||
public float Scale { get; set; }
|
||||
|
||||
private AttachmentLoader attachmentLoader;
|
||||
private List<LinkedMesh> linkedMeshes = new List<LinkedMesh>();
|
||||
/// <summary>
|
||||
/// 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)
|
||||
: this(new AtlasAttachmentLoader(atlasArray)) {
|
||||
: base(atlasArray) {
|
||||
}
|
||||
|
||||
public SkeletonJson (AttachmentLoader attachmentLoader) {
|
||||
if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null.");
|
||||
this.attachmentLoader = attachmentLoader;
|
||||
Scale = 1;
|
||||
}
|
||||
|
||||
#if !IS_UNITY && WINDOWS_STOREAPP
|
||||
#if !IS_UNITY && WINDOWS_STOREAPP
|
||||
private async Task<SkeletonData> ReadFile(string path) {
|
||||
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
|
||||
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;
|
||||
}
|
||||
#else
|
||||
public SkeletonData ReadSkeletonData (string path) {
|
||||
#else
|
||||
public override SkeletonData ReadSkeletonData (string path) {
|
||||
#if WINDOWS_PHONE
|
||||
using (var reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) {
|
||||
#else
|
||||
@ -88,7 +92,7 @@ namespace Spine {
|
||||
public SkeletonData ReadSkeletonData (TextReader reader) {
|
||||
if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null.");
|
||||
|
||||
float scale = this.Scale;
|
||||
float scale = this.scale;
|
||||
var skeletonData = new SkeletonData();
|
||||
|
||||
var root = Json.Deserialize(reader) as Dictionary<string, Object>;
|
||||
@ -99,8 +103,6 @@ namespace Spine {
|
||||
var skeletonMap = (Dictionary<string, Object>)root["skeleton"];
|
||||
skeletonData.hash = (string)skeletonMap["hash"];
|
||||
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.y = GetFloat(skeletonMap, "y", 0);
|
||||
skeletonData.width = GetFloat(skeletonMap, "width", 0);
|
||||
@ -283,6 +285,7 @@ namespace Spine {
|
||||
skin.bones.Add(bone);
|
||||
}
|
||||
}
|
||||
skin.bones.TrimExcess();
|
||||
if (skinMap.ContainsKey("ik")) {
|
||||
foreach (string entryName in (List<Object>)skinMap["ik"]) {
|
||||
IkConstraintData constraint = skeletonData.FindIkConstraint(entryName);
|
||||
@ -304,6 +307,7 @@ namespace Spine {
|
||||
skin.constraints.Add(constraint);
|
||||
}
|
||||
}
|
||||
skin.constraints.TrimExcess();
|
||||
if (skinMap.ContainsKey("attachments")) {
|
||||
foreach (KeyValuePair<string, Object> slotEntry in (Dictionary<string, Object>)skinMap["attachments"]) {
|
||||
int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key);
|
||||
@ -358,7 +362,7 @@ namespace Spine {
|
||||
try {
|
||||
ReadAnimation((Dictionary<string, Object>)entry.Value, entry.Key, skeletonData);
|
||||
} 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) {
|
||||
float scale = this.Scale;
|
||||
float scale = this.scale;
|
||||
name = GetString(map, "name", name);
|
||||
|
||||
var typeName = GetString(map, "type", "region");
|
||||
@ -438,7 +442,7 @@ namespace Spine {
|
||||
mesh.regionUVs = uvs;
|
||||
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");
|
||||
return mesh;
|
||||
}
|
||||
@ -505,7 +509,7 @@ namespace Spine {
|
||||
for (int i = 0, n = vertices.Length; i < n;) {
|
||||
int boneCount = (int)vertices[i++];
|
||||
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]);
|
||||
weights.Add(vertices[i + 1] * 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) {
|
||||
var scale = this.Scale;
|
||||
var scale = this.scale;
|
||||
var timelines = new ExposedList<Timeline>();
|
||||
float duration = 0;
|
||||
|
||||
// Slot timelines.
|
||||
if (map.ContainsKey("slots")) {
|
||||
@ -529,50 +532,117 @@ namespace Spine {
|
||||
var timelineMap = (Dictionary<string, Object>)entry.Value;
|
||||
foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
|
||||
var values = (List<Object>)timelineEntry.Value;
|
||||
if (values.Count == 0) continue;
|
||||
var timelineName = (string)timelineEntry.Key;
|
||||
if (timelineName == "attachment") {
|
||||
var timeline = new AttachmentTimeline(values.Count);
|
||||
timeline.slotIndex = slotIndex;
|
||||
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> valueMap in values) {
|
||||
float time = GetFloat(valueMap, "time", 0);
|
||||
timeline.SetFrame(frameIndex++, time, (string)valueMap["name"]);
|
||||
var timeline = new AttachmentTimeline(values.Count, slotIndex);
|
||||
int frame = 0;
|
||||
foreach (Dictionary<string, Object> keyMap in values) {
|
||||
timeline.SetFrame(frame++, GetFloat(keyMap, "time", 0), (string)keyMap["name"]);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
|
||||
|
||||
} else if (timelineName == "color") {
|
||||
var timeline = new ColorTimeline(values.Count);
|
||||
timeline.slotIndex = slotIndex;
|
||||
var timeline = new ColorTimeline(values.Count, values.Count << 2, slotIndex);
|
||||
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> valueMap in values) {
|
||||
float time = GetFloat(valueMap, "time", 0);
|
||||
string c = (string)valueMap["color"];
|
||||
timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3));
|
||||
ReadCurve(valueMap, timeline, frameIndex);
|
||||
frameIndex++;
|
||||
var keyMapEnumerator = values.GetEnumerator();
|
||||
keyMapEnumerator.MoveNext();
|
||||
var keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
|
||||
float time = GetFloat(keyMap, "time", 0);
|
||||
string color = (string)keyMap["color"];
|
||||
float r = ToColor(color, 0);
|
||||
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);
|
||||
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]);
|
||||
|
||||
} else if (timelineName == "twoColor") {
|
||||
var timeline = new TwoColorTimeline(values.Count);
|
||||
timeline.slotIndex = slotIndex;
|
||||
var timeline = new TwoColorTimeline(values.Count, values.Count * 7, slotIndex);
|
||||
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> valueMap in values) {
|
||||
float time = GetFloat(valueMap, "time", 0);
|
||||
string light = (string)valueMap["light"];
|
||||
string dark = (string)valueMap["dark"];
|
||||
timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3),
|
||||
ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6));
|
||||
ReadCurve(valueMap, timeline, frameIndex);
|
||||
frameIndex++;
|
||||
var keyMapEnumerator = values.GetEnumerator();
|
||||
keyMapEnumerator.MoveNext();
|
||||
var keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
|
||||
float time = GetFloat(keyMap, "time", 0);
|
||||
string color = (string)keyMap["light"];
|
||||
float r = ToColor(color, 0);
|
||||
float g = ToColor(color, 1);
|
||||
float b = ToColor(color, 2);
|
||||
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);
|
||||
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]);
|
||||
|
||||
} else
|
||||
throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");
|
||||
@ -589,47 +659,23 @@ namespace Spine {
|
||||
var timelineMap = (Dictionary<string, Object>)entry.Value;
|
||||
foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
|
||||
var values = (List<Object>)timelineEntry.Value;
|
||||
var keyMapEnumerator = values.GetEnumerator();
|
||||
bool hasNext = keyMapEnumerator.MoveNext();
|
||||
if (!hasNext) continue;
|
||||
var timelineName = (string)timelineEntry.Key;
|
||||
if (timelineName == "rotate") {
|
||||
var timeline = new RotateTimeline(values.Count);
|
||||
timeline.boneIndex = boneIndex;
|
||||
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> valueMap in values) {
|
||||
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "angle", 0));
|
||||
ReadCurve(valueMap, timeline, frameIndex);
|
||||
frameIndex++;
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]);
|
||||
|
||||
} 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]);
|
||||
|
||||
if (timelineName == "rotate")
|
||||
timelines.Add(ReadTimeline(ref keyMapEnumerator, new RotateTimeline(values.Count, values.Count, boneIndex), 0, 1));
|
||||
else if (timelineName == "translate") {
|
||||
TranslateTimeline timeline = new TranslateTimeline(values.Count, values.Count << 1, boneIndex);
|
||||
timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, scale));
|
||||
}
|
||||
else if (timelineName == "scale") {
|
||||
ScaleTimeline timeline = new ScaleTimeline(values.Count, values.Count << 1, boneIndex);
|
||||
timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 1, 1));
|
||||
}
|
||||
else if (timelineName == "shear") {
|
||||
ShearTimeline timeline = new ShearTimeline(values.Count, values.Count << 1, boneIndex);
|
||||
timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, 1));
|
||||
} else
|
||||
throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
|
||||
}
|
||||
@ -638,40 +684,82 @@ namespace Spine {
|
||||
|
||||
// IK constraint timelines.
|
||||
if (map.ContainsKey("ik")) {
|
||||
foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["ik"]) {
|
||||
IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key);
|
||||
var values = (List<Object>)constraintMap.Value;
|
||||
var timeline = new IkConstraintTimeline(values.Count);
|
||||
timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint);
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> valueMap in values) {
|
||||
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "mix", 1),
|
||||
GetFloat(valueMap, "softness", 0) * scale, GetBoolean(valueMap, "bendPositive", true) ? 1 : -1,
|
||||
GetBoolean(valueMap, "compress", false), GetBoolean(valueMap, "stretch", false));
|
||||
ReadCurve(valueMap, timeline, frameIndex);
|
||||
frameIndex++;
|
||||
foreach (KeyValuePair<string, Object> timelineMap in (Dictionary<string, Object>)map["ik"]) {
|
||||
var timelineMapValues = (List<Object>)timelineMap.Value;
|
||||
var keyMapEnumerator = timelineMapValues.GetEnumerator();
|
||||
bool hasNext = keyMapEnumerator.MoveNext();
|
||||
if (!hasNext) continue;
|
||||
var keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
|
||||
IkConstraintData constraint = skeletonData.FindIkConstraint(timelineMap.Key);
|
||||
IkConstraintTimeline timeline = new IkConstraintTimeline(timelineMapValues.Count, timelineMapValues.Count << 1,
|
||||
skeletonData.IkConstraints.IndexOf(constraint));
|
||||
float time = GetFloat(keyMap, "time", 0);
|
||||
float mix = GetFloat(keyMap, "mix", 1), softness = GetFloat(keyMap, "softness", 0) * scale;
|
||||
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);
|
||||
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]);
|
||||
}
|
||||
}
|
||||
|
||||
// Transform constraint timelines.
|
||||
if (map.ContainsKey("transform")) {
|
||||
foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["transform"]) {
|
||||
TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key);
|
||||
var values = (List<Object>)constraintMap.Value;
|
||||
var timeline = new TransformConstraintTimeline(values.Count);
|
||||
timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint);
|
||||
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), GetFloat(valueMap, "scaleMix", 1), GetFloat(valueMap, "shearMix", 1));
|
||||
ReadCurve(valueMap, timeline, frameIndex);
|
||||
frameIndex++;
|
||||
foreach (KeyValuePair<string, Object> timelineMap in (Dictionary<string, Object>)map["transform"]) {
|
||||
var timelineMapValues = (List<Object>)timelineMap.Value;
|
||||
var keyMapEnumerator = timelineMapValues.GetEnumerator();
|
||||
bool hasNext = keyMapEnumerator.MoveNext();
|
||||
if (!hasNext) continue;
|
||||
var keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
|
||||
TransformConstraintData constraint = skeletonData.FindTransformConstraint(timelineMap.Key);
|
||||
TransformConstraintTimeline timeline = new TransformConstraintTimeline(timelineMapValues.Count, timelineMapValues.Count << 2,
|
||||
skeletonData.TransformConstraints.IndexOf(constraint));
|
||||
float time = GetFloat(keyMap, "time", 0);
|
||||
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);
|
||||
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -684,40 +772,22 @@ namespace Spine {
|
||||
var timelineMap = (Dictionary<string, Object>)constraintMap.Value;
|
||||
foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
|
||||
var values = (List<Object>)timelineEntry.Value;
|
||||
var keyMapEnumerator = values.GetEnumerator();
|
||||
bool hasNext = keyMapEnumerator.MoveNext();
|
||||
if (!hasNext) continue;
|
||||
var timelineName = (string)timelineEntry.Key;
|
||||
if (timelineName == "position" || timelineName == "spacing") {
|
||||
PathConstraintPositionTimeline timeline;
|
||||
float timelineScale = 1;
|
||||
if (timelineName == "spacing") {
|
||||
timeline = new PathConstraintSpacingTimeline(values.Count);
|
||||
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale;
|
||||
}
|
||||
else {
|
||||
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]);
|
||||
if (timelineName == "position") {
|
||||
CurveTimeline1 timeline = new PathConstraintPositionTimeline(values.Count, values.Count, index);
|
||||
timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, data.positionMode == PositionMode.Fixed ? scale : 1));
|
||||
}
|
||||
else if (timelineName == "spacing") {
|
||||
CurveTimeline1 timeline = new PathConstraintSpacingTimeline(values.Count, values.Count, index);
|
||||
timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0,
|
||||
data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed ? scale : 1));
|
||||
}
|
||||
else if (timelineName == "mix") {
|
||||
PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count);
|
||||
timeline.pathConstraintIndex = index;
|
||||
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]);
|
||||
CurveTimeline2 timeline = new PathConstraintMixTimeline(values.Count, values.Count << 1, index);
|
||||
timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "rotateMix", "translateMix", 1, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -731,26 +801,26 @@ namespace Spine {
|
||||
int slotIndex = skeletonData.FindSlotIndex(slotMap.Key);
|
||||
if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key);
|
||||
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);
|
||||
if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key);
|
||||
bool weighted = attachment.bones != null;
|
||||
float[] vertices = attachment.vertices;
|
||||
int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length;
|
||||
|
||||
var timeline = new DeformTimeline(values.Count);
|
||||
timeline.slotIndex = slotIndex;
|
||||
timeline.attachment = attachment;
|
||||
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> valueMap in values) {
|
||||
int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length;
|
||||
DeformTimeline timeline = new DeformTimeline(timelineMapValues.Count, timelineMapValues.Count, slotIndex, attachment);
|
||||
float time = GetFloat(keyMap, "time", 0);
|
||||
for (int frame = 0, bezier = 0; ; frame++) {
|
||||
float[] deform;
|
||||
if (!valueMap.ContainsKey("vertices")) {
|
||||
if (!keyMap.ContainsKey("vertices")) {
|
||||
deform = weighted ? new float[deformLength] : vertices;
|
||||
} else {
|
||||
deform = new float[deformLength];
|
||||
int start = GetInt(valueMap, "offset", 0);
|
||||
float[] verticesValue = GetFloatArray(valueMap, "vertices", 1);
|
||||
int start = GetInt(keyMap, "offset", 0);
|
||||
float[] verticesValue = GetFloatArray(keyMap, "vertices", 1);
|
||||
Array.Copy(verticesValue, 0, deform, start, verticesValue.Length);
|
||||
if (scale != 1) {
|
||||
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);
|
||||
ReadCurve(valueMap, timeline, frameIndex);
|
||||
frameIndex++;
|
||||
timeline.SetFrame(frame, time, deform);
|
||||
hasNext = keyMapEnumerator.MoveNext();
|
||||
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);
|
||||
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 timeline = new DrawOrderTimeline(values.Count);
|
||||
int slotCount = skeletonData.slots.Count;
|
||||
int frameIndex = 0;
|
||||
int frame = 0;
|
||||
foreach (Dictionary<string, Object> drawOrderMap in values) {
|
||||
int[] drawOrder = null;
|
||||
if (drawOrderMap.ContainsKey("offsets")) {
|
||||
@ -806,17 +886,17 @@ namespace Spine {
|
||||
for (int i = slotCount - 1; i >= 0; i--)
|
||||
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);
|
||||
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
|
||||
}
|
||||
|
||||
// Event timeline.
|
||||
if (map.ContainsKey("events")) {
|
||||
var eventsMap = (List<Object>)map["events"];
|
||||
var timeline = new EventTimeline(eventsMap.Count);
|
||||
int frameIndex = 0;
|
||||
int frame = 0;
|
||||
foreach (Dictionary<string, Object> eventMap in eventsMap) {
|
||||
EventData eventData = skeletonData.FindEvent((string)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.balance = GetFloat(eventMap, "balance", eventData.Balance);
|
||||
}
|
||||
timeline.SetFrame(frameIndex++, e);
|
||||
timeline.SetFrame(frame, e);
|
||||
++frame;
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 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);
|
||||
skeletonData.animations.Add(new Animation(name, timelines, duration));
|
||||
}
|
||||
|
||||
static void ReadCurve (Dictionary<string, Object> valueMap, CurveTimeline timeline, int frameIndex) {
|
||||
if (!valueMap.ContainsKey("curve"))
|
||||
return;
|
||||
Object curveObject = valueMap["curve"];
|
||||
if (curveObject is string)
|
||||
timeline.SetStepped(frameIndex);
|
||||
else
|
||||
timeline.SetCurve(frameIndex, (float)curveObject, GetFloat(valueMap, "c2", 0), GetFloat(valueMap, "c3", 1), GetFloat(valueMap, "c4", 1));
|
||||
static Timeline ReadTimeline (ref List<object>.Enumerator keyMapEnumerator, CurveTimeline1 timeline, float defaultValue, float scale) {
|
||||
var keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
|
||||
float time = GetFloat(keyMap, "time", 0);
|
||||
float value = GetFloat(keyMap, "value", defaultValue) * scale;
|
||||
int bezier = 0;
|
||||
for (int frame = 0; ; frame++) {
|
||||
timeline.SetFrame(frame, time, value);
|
||||
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 {
|
||||
internal string parent, skin;
|
||||
internal int slotIndex;
|
||||
internal MeshAttachment mesh;
|
||||
internal bool inheritDeform;
|
||||
static Timeline ReadTimeline (ref List<object>.Enumerator keyMapEnumerator, CurveTimeline2 timeline, String name1, String name2, float defaultValue,
|
||||
float scale) {
|
||||
|
||||
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;
|
||||
var keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
|
||||
float time = GetFloat(keyMap, "time", 0);
|
||||
float value1 = GetFloat(keyMap, name1, defaultValue) * scale, value2 = GetFloat(keyMap, name2, defaultValue) * scale;
|
||||
int bezier = 0;
|
||||
for (int frame = 0; ; frame++) {
|
||||
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) {
|
||||
|
||||
92
spine-csharp/src/SkeletonLoader.cs
Normal file
92
spine-csharp/src/SkeletonLoader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -39,6 +39,8 @@ namespace Spine {
|
||||
/// </summary>
|
||||
public class Skin {
|
||||
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);
|
||||
internal readonly ExposedList<BoneData> bones = new ExposedList<BoneData>();
|
||||
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>
|
||||
public void SetAttachment (int slotIndex, string name, Attachment attachment) {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -104,13 +105,14 @@ namespace Spine {
|
||||
|
||||
/// <summary> Removes the attachment in the skin for the specified slot index and name, if any.</summary>
|
||||
public void RemoveAttachment (int slotIndex, string name) {
|
||||
if (slotIndex < 0) throw new ArgumentOutOfRangeException("slotIndex", "slotIndex must be >= 0");
|
||||
attachments.Remove(new SkinKey(slotIndex, name));
|
||||
}
|
||||
|
||||
/// <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"/>
|
||||
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) {
|
||||
SkinEntry entry = item.Value;
|
||||
if (entry.slotIndex == slotIndex) attachments.Add(entry);
|
||||
@ -176,10 +178,14 @@ namespace Spine {
|
||||
private struct SkinKey {
|
||||
internal readonly int slotIndex;
|
||||
internal readonly string name;
|
||||
internal readonly int hashCode;
|
||||
|
||||
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.name = name;
|
||||
this.hashCode = name.GetHashCode() + slotIndex * 37;
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,7 +197,7 @@ namespace Spine {
|
||||
}
|
||||
|
||||
int IEqualityComparer<SkinKey>.GetHashCode (SkinKey e) {
|
||||
return e.name.GetHashCode() + e.slotIndex * 37;
|
||||
return e.hashCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ using System;
|
||||
namespace Spine {
|
||||
|
||||
/// <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
|
||||
/// across multiple skeletons.
|
||||
/// </summary>
|
||||
|
||||
@ -76,12 +76,8 @@ namespace Spine {
|
||||
shearMix = constraint.shearMix;
|
||||
}
|
||||
|
||||
/// <summary>Applies the constraint to the constrained bones.</summary>
|
||||
public void Apply () {
|
||||
Update();
|
||||
}
|
||||
|
||||
public void Update () {
|
||||
if (rotateMix == 0 && translateMix == 0 && scaleMix == 0 && shearMix == 0) return;
|
||||
if (data.local) {
|
||||
if (data.relative)
|
||||
ApplyRelativeLocal();
|
||||
@ -101,10 +97,9 @@ namespace Spine {
|
||||
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 offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect;
|
||||
var bones = this.bones;
|
||||
for (int i = 0, n = bones.Count; i < n; i++) {
|
||||
Bone bone = bones.Items[i];
|
||||
bool modified = false;
|
||||
var bones = this.bones.Items;
|
||||
for (int i = 0, n = this.bones.Count; i < n; i++) {
|
||||
Bone bone = bones[i];
|
||||
|
||||
if (rotateMix != 0) {
|
||||
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.c = sin * a + cos * c;
|
||||
bone.d = sin * b + cos * d;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
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));
|
||||
bone.worldX += (tx - bone.worldX) * translateMix;
|
||||
bone.worldY += (ty - bone.worldY) * translateMix;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
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;
|
||||
bone.b *= s;
|
||||
bone.d *= s;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (shearMix > 0) {
|
||||
@ -152,10 +144,9 @@ namespace Spine {
|
||||
float s = (float)Math.Sqrt(b * b + d * d);
|
||||
bone.b = MathUtils.Cos(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 degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
|
||||
float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect;
|
||||
var bones = this.bones;
|
||||
for (int i = 0, n = bones.Count; i < n; i++) {
|
||||
Bone bone = bones.Items[i];
|
||||
bool modified = false;
|
||||
var bones = this.bones.Items;
|
||||
for (int i = 0, n = this.bones.Count; i < n; i++) {
|
||||
Bone bone = bones[i];
|
||||
|
||||
if (rotateMix != 0) {
|
||||
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.c = sin * a + cos * c;
|
||||
bone.d = sin * b + cos * d;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
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));
|
||||
bone.worldX += tx * translateMix;
|
||||
bone.worldY += ty * translateMix;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (scaleMix > 0) {
|
||||
@ -200,7 +188,6 @@ namespace Spine {
|
||||
s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1;
|
||||
bone.b *= s;
|
||||
bone.d *= s;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (shearMix > 0) {
|
||||
@ -213,10 +200,9 @@ namespace Spine {
|
||||
float s = (float)Math.Sqrt(b * b + d * d);
|
||||
bone.b = MathUtils.Cos(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;
|
||||
Bone target = this.target;
|
||||
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++) {
|
||||
Bone bone = bonesItems[i];
|
||||
Bone bone = bones[i];
|
||||
if (!bone.appliedValid) bone.UpdateAppliedTransform();
|
||||
|
||||
float rotation = bone.arotation;
|
||||
@ -263,9 +249,9 @@ namespace Spine {
|
||||
float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
|
||||
Bone target = this.target;
|
||||
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++) {
|
||||
Bone bone = bonesItems[i];
|
||||
Bone bone = bones[i];
|
||||
if (!bone.appliedValid) bone.UpdateAppliedTransform();
|
||||
|
||||
float rotation = bone.arotation;
|
||||
|
||||
@ -61,32 +61,35 @@ namespace Spine.Unity.Examples {
|
||||
|
||||
// Build a reference collection of timelines to match
|
||||
// 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 timeline in animation.Timelines) {
|
||||
if (timeline is EventTimeline) continue;
|
||||
|
||||
int propertyID = timeline.PropertyId;
|
||||
if (!timelineDictionary.ContainsKey(propertyID)) {
|
||||
timelineDictionary.Add(propertyID, GetFillerTimeline(timeline, skeletonData));
|
||||
foreach (string propertyId in timeline.PropertyIds) {
|
||||
if (!timelineDictionary.ContainsKey(propertyId)) {
|
||||
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.
|
||||
var currentAnimationIDs = new HashSet<int>();
|
||||
var currentAnimationIDs = new HashSet<string>();
|
||||
foreach (var animation in animations) {
|
||||
currentAnimationIDs.Clear();
|
||||
foreach (var timeline in animation.Timelines) {
|
||||
if (timeline is EventTimeline) continue;
|
||||
currentAnimationIDs.Add(timeline.PropertyId);
|
||||
foreach (string propertyId in timeline.PropertyIds) {
|
||||
currentAnimationIDs.Add(propertyId);
|
||||
}
|
||||
}
|
||||
|
||||
var animationTimelines = animation.Timelines;
|
||||
foreach (int propertyID in idsToMatch) {
|
||||
if (!currentAnimationIDs.Contains(propertyID))
|
||||
animationTimelines.Add(timelineDictionary[propertyID]);
|
||||
foreach (string propertyId in idsToMatch) {
|
||||
if (!currentAnimationIDs.Contains(propertyId))
|
||||
animationTimelines.Add(timelineDictionary[propertyId]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,62 +135,52 @@ namespace Spine.Unity.Examples {
|
||||
}
|
||||
|
||||
static RotateTimeline GetFillerTimeline (RotateTimeline timeline, SkeletonData skeletonData) {
|
||||
var t = new RotateTimeline(1);
|
||||
t.BoneIndex = timeline.BoneIndex;
|
||||
var t = new RotateTimeline(1, 0, timeline.BoneIndex);
|
||||
t.SetFrame(0, 0, 0);
|
||||
return t;
|
||||
}
|
||||
|
||||
static TranslateTimeline GetFillerTimeline (TranslateTimeline timeline, SkeletonData skeletonData) {
|
||||
var t = new TranslateTimeline(1);
|
||||
t.BoneIndex = timeline.BoneIndex;
|
||||
var t = new TranslateTimeline(1, 0, timeline.BoneIndex);
|
||||
t.SetFrame(0, 0, 0, 0);
|
||||
return t;
|
||||
}
|
||||
|
||||
static ScaleTimeline GetFillerTimeline (ScaleTimeline timeline, SkeletonData skeletonData) {
|
||||
var t = new ScaleTimeline(1);
|
||||
t.BoneIndex = timeline.BoneIndex;
|
||||
var t = new ScaleTimeline(1, 0, timeline.BoneIndex);
|
||||
t.SetFrame(0, 0, 0, 0);
|
||||
return t;
|
||||
}
|
||||
|
||||
static ShearTimeline GetFillerTimeline (ShearTimeline timeline, SkeletonData skeletonData) {
|
||||
var t = new ShearTimeline(1);
|
||||
t.BoneIndex = timeline.BoneIndex;
|
||||
var t = new ShearTimeline(1, 0, timeline.BoneIndex);
|
||||
t.SetFrame(0, 0, 0, 0);
|
||||
return t;
|
||||
}
|
||||
|
||||
static AttachmentTimeline GetFillerTimeline (AttachmentTimeline timeline, SkeletonData skeletonData) {
|
||||
var t = new AttachmentTimeline(1);
|
||||
t.SlotIndex = timeline.SlotIndex;
|
||||
var t = new AttachmentTimeline(1, timeline.SlotIndex);
|
||||
var slotData = skeletonData.Slots.Items[t.SlotIndex];
|
||||
t.SetFrame(0, 0, slotData.AttachmentName);
|
||||
return t;
|
||||
}
|
||||
|
||||
static ColorTimeline GetFillerTimeline (ColorTimeline timeline, SkeletonData skeletonData) {
|
||||
var t = new ColorTimeline(1);
|
||||
t.SlotIndex = timeline.SlotIndex;
|
||||
var t = new ColorTimeline(1, 0, timeline.SlotIndex);
|
||||
var slotData = skeletonData.Slots.Items[t.SlotIndex];
|
||||
t.SetFrame(0, 0, slotData.R, slotData.G, slotData.B, slotData.A);
|
||||
return t;
|
||||
}
|
||||
|
||||
static TwoColorTimeline GetFillerTimeline (TwoColorTimeline timeline, SkeletonData skeletonData) {
|
||||
var t = new TwoColorTimeline(1);
|
||||
t.SlotIndex = timeline.SlotIndex;
|
||||
var t = new TwoColorTimeline(1, 0, timeline.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);
|
||||
return t;
|
||||
}
|
||||
|
||||
static DeformTimeline GetFillerTimeline (DeformTimeline timeline, SkeletonData skeletonData) {
|
||||
var t = new DeformTimeline(1);
|
||||
t.SlotIndex = timeline.SlotIndex;
|
||||
t.Attachment = timeline.Attachment;
|
||||
|
||||
var t = new DeformTimeline(1, 0, timeline.SlotIndex, timeline.Attachment);
|
||||
if (t.Attachment.IsWeighted()) {
|
||||
t.SetFrame(0, 0, new float[t.Attachment.Vertices.Length]);
|
||||
} else {
|
||||
@ -204,35 +197,35 @@ namespace Spine.Unity.Examples {
|
||||
}
|
||||
|
||||
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];
|
||||
t.SetFrame(0, 0, ikConstraintData.Mix, ikConstraintData.Softness, ikConstraintData.BendDirection, ikConstraintData.Compress, ikConstraintData.Stretch);
|
||||
return t;
|
||||
}
|
||||
|
||||
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];
|
||||
t.SetFrame(0, 0, data.RotateMix, data.TranslateMix, data.ScaleMix, data.ShearMix);
|
||||
return t;
|
||||
}
|
||||
|
||||
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];
|
||||
t.SetFrame(0, 0, data.Position);
|
||||
return t;
|
||||
}
|
||||
|
||||
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];
|
||||
t.SetFrame(0, 0, data.Spacing);
|
||||
return t;
|
||||
}
|
||||
|
||||
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];
|
||||
t.SetFrame(0, 0, data.RotateMix, data.TranslateMix);
|
||||
return t;
|
||||
|
||||
@ -123,7 +123,6 @@ namespace Spine.Unity.Editor {
|
||||
EditorGUI.indentLevel = 0;
|
||||
|
||||
var mixMode = layerMixModes.GetArrayElementAtIndex(i);
|
||||
var blendMode = layerBlendModes.GetArrayElementAtIndex(i);
|
||||
rect.position += new Vector2(rect.width, 0);
|
||||
rect.width = widthMixColumn;
|
||||
EditorGUI.PropertyField(rect, mixMode, GUIContent.none);
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18ee2876d53412642bbfa1070a1b947f
|
||||
folderAsset: yes
|
||||
timeCreated: 1527569487
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ad4318c20ec5674a9f4d7f786afd681
|
||||
folderAsset: yes
|
||||
timeCreated: 1496449217
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cbec7dc66dca80a419477536c23b7a0d
|
||||
timeCreated: 1496449255
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -40,8 +40,8 @@ namespace Spine.Unity {
|
||||
public static class SkeletonDataCompatibility {
|
||||
|
||||
#if UNITY_EDITOR
|
||||
static readonly int[][] compatibleBinaryVersions = { new[] { 3, 9, 0 }, new[] { 3, 8, 0 } };
|
||||
static readonly int[][] compatibleJsonVersions = { new[] { 3, 9, 0 }, new[] { 3, 8, 0 } };
|
||||
static readonly int[][] compatibleBinaryVersions = { new[] { 4, 0, 0 } };
|
||||
static readonly int[][] compatibleJsonVersions = { new[] { 4, 0, 0 } };
|
||||
|
||||
static bool wasVersionDialogShown = false;
|
||||
static readonly Regex jsonVersionRegex = new Regex(@"""spine""\s*:\s*""([^""]+)""", RegexOptions.CultureInvariant);
|
||||
|
||||
@ -254,7 +254,6 @@ namespace Spine.Unity {
|
||||
private void OnClipAppliedCallback (Spine.Animation clip, AnimatorStateInfo stateInfo,
|
||||
int layerIndex, float time, bool isLooping, float weight) {
|
||||
|
||||
float clipDuration = clip.duration == 0 ? 1 : clip.duration;
|
||||
float speedFactor = stateInfo.speedMultiplier * stateInfo.speed;
|
||||
float lastTime = time - (Time.deltaTime * speedFactor);
|
||||
if (isLooping && clip.duration != 0) {
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 04817e31b917de6489f349dd332d7468
|
||||
folderAsset: yes
|
||||
timeCreated: 1563295668
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfdd78a071ca1a04bb64c6cc41e14aa0
|
||||
folderAsset: yes
|
||||
timeCreated: 1496447038
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -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:
|
||||
@ -38,28 +38,28 @@ namespace Spine.Unity.AnimationTools {
|
||||
/// 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>
|
||||
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;
|
||||
if (time < frames[0]) return Vector2.zero;
|
||||
|
||||
float x, y;
|
||||
if (time >= frames[frames.Length - TranslateTimeline.ENTRIES]) { // Time is after last frame.
|
||||
x = frames[frames.Length + PREV_X];
|
||||
y = frames[frames.Length + PREV_Y];
|
||||
}
|
||||
else {
|
||||
// Interpolate between the previous frame and the current frame.
|
||||
int frame = Animation.BinarySearch(frames, time, TranslateTimeline.ENTRIES);
|
||||
x = frames[frame + PREV_X];
|
||||
y = frames[frame + PREV_Y];
|
||||
float frameTime = frames[frame];
|
||||
float percent = timeline.GetCurvePercent(frame / TranslateTimeline.ENTRIES - 1,
|
||||
1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
|
||||
|
||||
x += (frames[frame + X] - x) * percent;
|
||||
y += (frames[frame + Y] - y) * percent;
|
||||
int i = Animation.Search(frames, time, TranslateTimeline.ENTRIES), curveType = (int)timeline.curves[i / TranslateTimeline.ENTRIES];
|
||||
switch (curveType) {
|
||||
case TranslateTimeline.LINEAR:
|
||||
float before = frames[i];
|
||||
x = frames[i + TranslateTimeline.VALUE1];
|
||||
y = frames[i + TranslateTimeline.VALUE2];
|
||||
float t = (time - before) / (frames[i + TranslateTimeline.ENTRIES] - before);
|
||||
x += (frames[i + TranslateTimeline.ENTRIES + TranslateTimeline.VALUE1] - x) * t;
|
||||
y += (frames[i + TranslateTimeline.ENTRIES + TranslateTimeline.VALUE2] - y) * t;
|
||||
break;
|
||||
case TranslateTimeline.STEPPED:
|
||||
x = frames[i + TranslateTimeline.VALUE1];
|
||||
y = frames[i + TranslateTimeline.VALUE2];
|
||||
break;
|
||||
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);
|
||||
@ -67,7 +67,7 @@ namespace Spine.Unity.AnimationTools {
|
||||
return xy;
|
||||
}
|
||||
else {
|
||||
var boneData = skeletonData.bones.Items[timeline.boneIndex];
|
||||
var boneData = skeletonData.bones.Items[timeline.BoneIndex];
|
||||
return xy + new Vector2(boneData.x, boneData.y);
|
||||
}
|
||||
}
|
||||
@ -82,7 +82,7 @@ namespace Spine.Unity.AnimationTools {
|
||||
continue;
|
||||
|
||||
var translateTimeline = timeline as TranslateTimeline;
|
||||
if (translateTimeline != null && translateTimeline.boneIndex == boneIndex)
|
||||
if (translateTimeline != null && translateTimeline.BoneIndex == boneIndex)
|
||||
return translateTimeline;
|
||||
}
|
||||
return null;
|
||||
|
||||
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user