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

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

File diff suppressed because it is too large Load Diff

View File

@ -112,7 +112,7 @@ namespace Spine {
// end of difference
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 &gt; 0, sets <see cref="TrackEntry.Delay"/>. If &lt;= 0, the delay set is the duration of the previous track entry
/// minus any mix duration (from the {@link AnimationStateData}) plus the specified <code>Delay</code> (ie the mix
/// minus any mix duration (from the <see cref="AnimationStateData"/> plus the specified <code>Delay</code> (ie the mix
/// ends at (<code>Delay</code> = 0) or before (<code>Delay</code> &lt; 0) the previous track entry duration). If the
/// 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"/> &gt;= 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> &lt;= 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> &lt;= 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;
}
}
}

View File

@ -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;
}

View File

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

View File

@ -116,6 +116,7 @@ namespace Spine {
/// <summary>Returns the magnitide (always positive) of the world scale Y.</summary>
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) {

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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");
}

View File

@ -176,7 +176,7 @@ namespace Spine {
}
/// <summary>Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more
/// 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++)

View File

@ -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;

View File

@ -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) {

View File

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

View File

@ -39,6 +39,8 @@ namespace Spine {
/// </summary>
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;
}
}
}

View File

@ -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>

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

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

View File

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

View File

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

View File

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

View File

@ -40,8 +40,8 @@ namespace Spine.Unity {
public static class SkeletonDataCompatibility {
#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);

View File

@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

@ -38,28 +38,28 @@ namespace Spine.Unity.AnimationTools {
/// SkeletonData can be accessed from Skeleton.Data or from SkeletonDataAsset.GetSkeletonData.
/// 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;

View File

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