diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs index d64474de0..883d066e6 100644 --- a/spine-csharp/src/Animation.cs +++ b/spine-csharp/src/Animation.cs @@ -27,25 +27,36 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +#if UNITY_5_3_OR_NEWER +#define IS_UNITY +#endif + using System; using System.Collections.Generic; namespace Spine { +#if IS_UNITY + using Color = UnityEngine.Color; +#endif /// /// Stores a list of timelines to animate a skeleton's pose over time. public class Animation { - internal String name; + internal string name; + internal float duration; internal ExposedList timelines; internal HashSet timelineIds; - internal float duration; + internal readonly ExposedList bones; public Animation (string name, ExposedList timelines, float duration) { if (name == null) throw new ArgumentNullException("name", "name cannot be null."); this.name = name; - SetTimelines(timelines); this.duration = duration; + int n = timelines.Count << 1; + timelineIds = new HashSet(n); + bones = new ExposedList(n); + SetTimelines(timelines); } public ExposedList Timelines { @@ -56,30 +67,33 @@ namespace Spine { public void SetTimelines (ExposedList timelines) { if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); this.timelines = timelines; + // Note: avoiding reallocations by adding all hash set entries at // once (EnsureCapacity() is only available in newer .Net versions). int idCount = 0; - int timelinesCount = timelines.Count; - Timeline[] timelinesItems = timelines.Items; - for (int t = 0; t < timelinesCount; ++t) - idCount += timelinesItems[t].PropertyIds.Length; + int n = timelines.Count; + // not needed: timelineIds.Clear(n << 1); + bones.Clear(); + var boneSet = new HashSet(); + Timeline[] items = timelines.Items; + for (int i = 0; i < n; ++i) + idCount += items[i].PropertyIds.Length; var propertyIds = new string[idCount]; int currentId = 0; - for (int t = 0; t < timelinesCount; ++t) { - string[] ids = timelinesItems[t].PropertyIds; - for (int i = 0, idsLength = ids.Length; i < idsLength; ++i) - propertyIds[currentId++] = ids[i]; + for (int i = 0; i < n; ++i) { + Timeline timeline = items[i]; + string[] ids = items[i].PropertyIds; + for (int ii = 0, idsLength = ids.Length; ii < idsLength; ++ii) + propertyIds[currentId++] = ids[ii]; + + IBoneTimeline boneTimeline = timeline as IBoneTimeline; + if (boneTimeline != null && boneSet.Add(boneTimeline.BoneIndex)) + bones.Add(boneTimeline.BoneIndex); } this.timelineIds = new HashSet(propertyIds); + bones.TrimExcess(); } - /// The duration of the animation in seconds, which is usually the highest time of all frames in the timeline. The duration is - /// used to know when it has completed and when it should loop back to the start. - public float Duration { get { return duration; } set { duration = value; } } - - /// The animation's name, which is unique across all animations in the skeleton. - public string Name { get { return name; } } - /// Returns true if this animation contains a timeline with any of the specified property IDs. public bool HasTimeline (string[] propertyIds) { foreach (string id in propertyIds) @@ -87,8 +101,13 @@ namespace Spine { return false; } + /// The duration of the animation in seconds, which is usually the highest time of all frames in the timeline. The duration is + /// used to know when it has completed and when it should loop back to the start. + public float Duration { get { return duration; } set { duration = value; } } + + /// Applies the animation's timelines to the specified skeleton. - /// + /// /// The skeleton the animation is being applied to. This provides access to the bones, slots, and other skeleton /// components the timelines may change. /// The last time in seconds this animation was applied. Some timelines trigger only at specific times rather @@ -107,7 +126,7 @@ namespace Spine { /// Indicates whether the timelines are mixing in or out. Used by timelines which perform instant transitions, /// such as or . public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, - MixBlend blend, MixDirection direction) { + MixBlend blend, MixDirection direction, bool appliedPose) { if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); if (loop && duration != 0) { @@ -117,9 +136,12 @@ namespace Spine { Timeline[] timelines = this.timelines.Items; for (int i = 0, n = this.timelines.Count; i < n; i++) - timelines[i].Apply(skeleton, lastTime, time, events, alpha, blend, direction); + timelines[i].Apply(skeleton, lastTime, time, events, alpha, blend, direction, appliedPose); } + /// The animation's name, which is unique across all animations in the skeleton. + public string Name { get { return name; } } + override public string ToString () { return name; } @@ -128,26 +150,28 @@ namespace Spine { /// /// Controls how timeline values are mixed with setup pose values or current pose values when a timeline is applied with /// alpha < 1. - /// + /// public enum MixBlend { - /// Transitions from the setup value to the timeline value (the current value is not used). Before the first frame, the - /// setup value is set. + /// + /// Transitions between the setup and timeline values (the current value is not used). Before the first frame, the setup + /// value is used. + /// + /// Setup is intended to transition to or from the setup pose, not for animations layered on top of others. Setup, /// /// - /// Transitions from the current value to the timeline value. Before the first frame, transitions from the current value to - /// the setup value. Timelines which perform instant transitions, such as or - /// , use the setup value before the first frame. + /// Transitions between the current and timeline values. Before the first frame, transitions between the current and setup + /// values. Timelines which perform instant transitions, such as or , use + /// the setup value before the first frame. /// - /// First is intended for the first animations applied, not for animations layered on top of those. + /// First is intended for the first animations applied, not for animations layered on top of others. /// First, /// /// - /// Transitions from the current value to the timeline value. No change is made before the first frame (the current value is - /// kept until the first frame). + /// Transitions between the current and timeline values. No change is made before the first frame. /// /// Replace is intended for animations layered on top of others, not for the first animations applied. /// @@ -155,13 +179,12 @@ namespace Spine { /// /// - /// Transitions from the current value to the current value plus the timeline value. No change is made before the first frame - /// (the current value is kept until the first frame). + /// Transitions between the current value and the current plus timeline values. No change is made before the first frame. /// - /// Add is intended for animations layered on top of others, not for the first animations applied. Properties - /// set by additive animations must be set manually or by another animation before applying the additive animations, else the - /// property values will increase each time the additive animations are applied. - /// + /// Add is intended for animations layered on top of others, not for the first animations applied. + /// + /// Properties set by additive animations must be set manually or by another animation before applying the additive + /// animations, else the property values will increase each time the additive animations are applied. /// Add } @@ -169,7 +192,7 @@ namespace Spine { /// /// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose value) or /// mixing in toward 1 (the timeline's value). Some timelines use this to decide how values are applied. - /// + /// public enum MixDirection { In, Out @@ -184,7 +207,8 @@ namespace Spine { PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, // PhysicsConstraintInertia, PhysicsConstraintStrength, PhysicsConstraintDamping, PhysicsConstraintMass, // PhysicsConstraintWind, PhysicsConstraintGravity, PhysicsConstraintMix, PhysicsConstraintReset, // - Sequence + Sequence, // + SliderTime, SliderMix } /// @@ -245,10 +269,11 @@ namespace Spine { /// Controls how mixing is applied when alpha < 1. /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions, /// such as or , and other such as . + /// True to modify the applied pose. public abstract void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, - MixBlend blend, MixDirection direction); + MixBlend blend, MixDirection direction, bool appliedPose); - /// Search using a stride of 1. + /// Linear search using a stride of 1. /// Must be >= the first value in frames. /// The index of the first value <= time. internal static int Search (float[] frames, float time) { @@ -269,12 +294,6 @@ namespace Spine { } } - /// An interface for timelines which change the property of a bone. - public interface IBoneTimeline { - /// The index of the bone in that will be changed when this timeline is applied. - int BoneIndex { get; } - } - /// An interface for timelines which change the property of a slot. public interface ISlotTimeline { /// The index of the slot in that will be changed when this timeline is applied. @@ -341,7 +360,7 @@ namespace Spine { /// The value for the second Bezier handle. /// The time for the second key. /// The value for the second key. - public void SetBezier (int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, + public virtual void SetBezier (int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, float cy2, float time2, float value2) { float[] curves = this.curves; @@ -454,6 +473,8 @@ namespace Spine { case MixBlend.Replace: value += setup - current; break; + case MixBlend.Add: + return current + value * alpha; } return current + value * alpha; } @@ -469,8 +490,16 @@ namespace Spine { return current; } float value = GetCurveValue(time); - if (blend == MixBlend.Setup) return setup + (value - setup) * alpha; - return current + (value - current) * alpha; + switch (blend) { + case MixBlend.Setup: + return setup + (value - setup) * alpha; + case MixBlend.First: + case MixBlend.Replace: + default: + return current + (value - current) * alpha; + case MixBlend.Add: + return current + value * alpha; + } } public float GetAbsoluteValue (float time, float alpha, MixBlend blend, float current, float setup, float value) { @@ -483,8 +512,16 @@ namespace Spine { } return current; } - if (blend == MixBlend.Setup) return setup + (value - setup) * alpha; - return current + (value - current) * alpha; + switch (blend) { + case MixBlend.Setup: + return setup + (value - setup) * alpha; + case MixBlend.First: + case MixBlend.Replace: + default: + return current + (value - current) * alpha; + case MixBlend.Add: + return current + value * alpha; + } } public float GetScaleValue (float time, float alpha, MixBlend blend, MixDirection direction, float current, float setup) { @@ -528,15 +565,17 @@ namespace Spine { } } - /// The base class for a which sets two properties. - public abstract class CurveTimeline2 : CurveTimeline { + /// The base class for a that is a and sets two properties. + public abstract class BoneTimeline2 : CurveTimeline, IBoneTimeline { public const int ENTRIES = 3; internal const int VALUE1 = 1, VALUE2 = 2; + readonly int boneIndex; + /// The maximum number of Bezier curves. See . - /// Unique identifiers for the properties the timeline modifies. - public CurveTimeline2 (int frameCount, int bezierCount, string propertyId1, string propertyId2) - : base(frameCount, bezierCount, propertyId1, propertyId2) { + public BoneTimeline2 (int frameCount, int bezierCount, int boneIndex, Property property1, Property property2) + : base(frameCount, bezierCount, (int)property1 + "|" + boneIndex, (int)property2 + "|" + boneIndex) { + this.boneIndex = boneIndex; } public override int FrameEntries { @@ -552,83 +591,109 @@ namespace Spine { frames[frame + VALUE1] = value1; frames[frame + VALUE2] = value2; } + + public int BoneIndex { + get { + return boneIndex; + } + } + + /// May be null. + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction, bool appliedPose) { + + Bone bone = skeleton.bones.Items[boneIndex]; + if (bone.active) Apply(appliedPose ? bone.applied : bone.pose, bone.data.setup, time, alpha, blend, direction); + } + + abstract protected void Apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, + MixDirection direction); } - /// Changes a bone's local . - public class RotateTimeline : CurveTimeline1, IBoneTimeline { + /// An interface for timelines which change the property of a bone. + public interface IBoneTimeline { + /// The index of the bone in that will be changed when this timeline is applied. + int BoneIndex { get; } + } + + public abstract class BoneTimeline1 : CurveTimeline1, IBoneTimeline { + readonly int boneIndex; + + public BoneTimeline1 (int frameCount, int bezierCount, int boneIndex, Property property) + : base(frameCount, bezierCount, (int)property + "|" + boneIndex) { + this.boneIndex = boneIndex; + } + + public int BoneIndex { + get { + return boneIndex; + } + } + + /// May be null. + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction, bool appliedPose) { + + Bone bone = skeleton.bones.Items[boneIndex]; + if (bone.active) Apply(appliedPose ? bone.applied : bone.pose, bone.data.setup, time, alpha, blend, direction); + } + + abstract protected void Apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, + MixDirection direction); + } + + /// Changes a bone's local . + public class RotateTimeline : BoneTimeline1, IBoneTimeline { readonly int boneIndex; public RotateTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.Rotate + "|" + boneIndex) { - this.boneIndex = boneIndex; + : base(frameCount, bezierCount, boneIndex, Property.Rotate) { } - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (bone.active) bone.rotation = GetRelativeValue(time, alpha, blend, bone.rotation, bone.data.rotation); + override protected void Apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { + pose.rotation = GetRelativeValue(time, alpha, blend, pose.Rotation, setup.rotation); } } - /// Changes a bone's local and . - public class TranslateTimeline : CurveTimeline2, IBoneTimeline { - readonly int boneIndex; - + /// Changes a bone's local and . + public class TranslateTimeline : BoneTimeline2 { public TranslateTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, // - (int)Property.X + "|" + boneIndex, // - (int)Property.Y + "|" + boneIndex) { - this.boneIndex = boneIndex; + : base(frameCount, bezierCount, boneIndex, Property.X, Property.Y) { } - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - + override protected void Apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { float[] frames = this.frames; if (time < frames[0]) { switch (blend) { case MixBlend.Setup: - bone.x = bone.data.x; - bone.y = bone.data.y; + pose.x = setup.x; + pose.y = setup.y; return; case MixBlend.First: - bone.x += (bone.data.x - bone.x) * alpha; - bone.y += (bone.data.y - bone.y) * alpha; - return; + pose.x += (setup.x - pose.x) * alpha; + pose.y += (setup.y - pose.y) * alpha; + break; } return; } float x, y; - GetCurveValue(out x, out y, time); // note: reference implementation has code inlined + // note: reference implementation has code inlined, we re-use GetCurveValue code for root motion. + GetCurveValue(out x, out y, time); switch (blend) { case MixBlend.Setup: - bone.x = bone.data.x + x * alpha; - bone.y = bone.data.y + y * alpha; + pose.x = setup.x + x * alpha; + pose.y = setup.y + y * alpha; break; case MixBlend.First: case MixBlend.Replace: - bone.x += (bone.data.x + x - bone.x) * alpha; - bone.y += (bone.data.y + y - bone.y) * alpha; + pose.x += (setup.x + x - pose.x) * alpha; + pose.y += (setup.y + y - pose.y) * alpha; break; case MixBlend.Add: - bone.x += x * alpha; - bone.y += y * alpha; + pose.x += x * alpha; + pose.y += y * alpha; break; } } @@ -656,82 +721,46 @@ namespace Spine { } } - /// Changes a bone's local . - public class TranslateXTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - + /// Changes a bone's local . + public class TranslateXTimeline : BoneTimeline1 { public TranslateXTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.X + "|" + boneIndex) { - this.boneIndex = boneIndex; + : base(frameCount, bezierCount, boneIndex, Property.X) { } - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (bone.active) bone.x = GetRelativeValue(time, alpha, blend, bone.x, bone.data.x); + override protected void Apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { + pose.x = GetRelativeValue(time, alpha, blend, pose.x, setup.x); } } - /// Changes a bone's local . - public class TranslateYTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - + /// Changes a bone's local . + public class TranslateYTimeline : BoneTimeline1 { public TranslateYTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.Y + "|" + boneIndex) { - this.boneIndex = boneIndex; + : base(frameCount, bezierCount, boneIndex, Property.Y) { } - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (bone.active) bone.y = GetRelativeValue(time, alpha, blend, bone.y, bone.data.y); + override protected void Apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { + pose.y = GetRelativeValue(time, alpha, blend, pose.y, setup.y); } } - /// Changes a bone's local and . - public class ScaleTimeline : CurveTimeline2, IBoneTimeline { - readonly int boneIndex; + /// Changes a bone's local and . + public class ScaleTimeline : BoneTimeline2 { public ScaleTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, // - (int)Property.ScaleX + "|" + boneIndex, // - (int)Property.ScaleY + "|" + boneIndex) { - this.boneIndex = boneIndex; + : base(frameCount, bezierCount, boneIndex, Property.ScaleX, Property.ScaleY) { } - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - + override protected void Apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { float[] frames = this.frames; if (time < frames[0]) { switch (blend) { case MixBlend.Setup: - bone.scaleX = bone.data.scaleX; - bone.scaleY = bone.data.scaleY; + pose.scaleX = setup.scaleX; + pose.scaleY = setup.scaleY; return; case MixBlend.First: - bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; + pose.scaleX += (setup.scaleX - pose.scaleX) * alpha; + pose.scaleY += (setup.scaleY - pose.scaleY) * alpha; return; } return; @@ -757,16 +786,16 @@ namespace Spine { y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); break; } - x *= bone.data.scaleX; - y *= bone.data.scaleY; + x *= setup.scaleX; + y *= setup.scaleY; if (alpha == 1) { if (blend == MixBlend.Add) { - bone.scaleX += x - bone.data.scaleX; - bone.scaleY += y - bone.data.scaleY; + pose.scaleX += x - setup.scaleX; + pose.scaleY += y - setup.scaleY; } else { - bone.scaleX = x; - bone.scaleY = y; + pose.scaleX = x; + pose.scaleY = y; } } else { // Mixing out uses sign of setup or current pose, else use sign of key. @@ -774,41 +803,41 @@ namespace Spine { if (direction == MixDirection.Out) { switch (blend) { case MixBlend.Setup: - bx = bone.data.scaleX; - by = bone.data.scaleY; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + bx = setup.scaleX; + by = setup.scaleY; + pose.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + pose.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; break; case MixBlend.First: case MixBlend.Replace: - bx = bone.scaleX; - by = bone.scaleY; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + bx = pose.scaleX; + by = pose.scaleY; + pose.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + pose.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; break; case MixBlend.Add: - bone.scaleX += (x - bone.data.scaleX) * alpha; - bone.scaleY += (y - bone.data.scaleY) * alpha; + pose.scaleX += (x - setup.scaleX) * alpha; + pose.scaleY += (y - setup.scaleY) * alpha; break; } } else { switch (blend) { case MixBlend.Setup: - bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); - by = Math.Abs(bone.data.scaleY) * Math.Sign(y); - bone.scaleX = bx + (x - bx) * alpha; - bone.scaleY = by + (y - by) * alpha; + bx = Math.Abs(setup.scaleX) * Math.Sign(x); + by = Math.Abs(setup.scaleY) * Math.Sign(y); + pose.scaleX = bx + (x - bx) * alpha; + pose.scaleY = by + (y - by) * alpha; break; case MixBlend.First: case MixBlend.Replace: - bx = Math.Abs(bone.scaleX) * Math.Sign(x); - by = Math.Abs(bone.scaleY) * Math.Sign(y); - bone.scaleX = bx + (x - bx) * alpha; - bone.scaleY = by + (y - by) * alpha; + bx = Math.Abs(pose.scaleX) * Math.Sign(x); + by = Math.Abs(pose.scaleY) * Math.Sign(y); + pose.scaleX = bx + (x - bx) * alpha; + pose.scaleY = by + (y - by) * alpha; break; case MixBlend.Add: - bone.scaleX += (x - bone.data.scaleX) * alpha; - bone.scaleY += (y - bone.data.scaleY) * alpha; + pose.scaleX += (x - setup.scaleX) * alpha; + pose.scaleY += (y - setup.scaleY) * alpha; break; } } @@ -816,82 +845,45 @@ namespace Spine { } } - /// Changes a bone's local . - public class ScaleXTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - + /// Changes a bone's local . + public class ScaleXTimeline : BoneTimeline1 { public ScaleXTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.ScaleX + "|" + boneIndex) { - this.boneIndex = boneIndex; + : base(frameCount, bezierCount, boneIndex, Property.ScaleX) { } - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (bone.active) bone.scaleX = GetScaleValue(time, alpha, blend, direction, bone.scaleX, bone.data.scaleX); + override protected void Apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { + pose.scaleX = GetScaleValue(time, alpha, blend, direction, pose.scaleX, setup.scaleX); } } - /// Changes a bone's local . - public class ScaleYTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - + /// Changes a bone's local . + public class ScaleYTimeline : BoneTimeline1 { public ScaleYTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.ScaleY + "|" + boneIndex) { - this.boneIndex = boneIndex; + : base(frameCount, bezierCount, boneIndex, Property.ScaleY) { } - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (bone.active) bone.scaleY = GetScaleValue(time, alpha, blend, direction, bone.scaleY, bone.data.scaleY); + override protected void Apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { + pose.scaleY = GetScaleValue(time, alpha, blend, direction, pose.scaleY, setup.scaleY); } } - /// Changes a bone's local and . - public class ShearTimeline : CurveTimeline2, IBoneTimeline { - readonly int boneIndex; - + /// Changes a bone's local and . + public class ShearTimeline : BoneTimeline2 { public ShearTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, // - (int)Property.ShearX + "|" + boneIndex, // - (int)Property.ShearY + "|" + boneIndex) { - this.boneIndex = boneIndex; + : base(frameCount, bezierCount, boneIndex, Property.ShearX, Property.ShearY) { } - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - + override protected void Apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. + if (time < frames[0]) { switch (blend) { case MixBlend.Setup: - bone.shearX = bone.data.shearX; - bone.shearY = bone.data.shearY; + pose.shearX = setup.shearX; + pose.shearY = setup.shearY; return; case MixBlend.First: - bone.shearX += (bone.data.shearX - bone.shearX) * alpha; - bone.shearY += (bone.data.shearY - bone.shearY) * alpha; + pose.shearX += (setup.shearX - pose.shearX) * alpha; + pose.shearY += (setup.shearY - pose.shearY) * alpha; return; } return; @@ -920,71 +912,48 @@ namespace Spine { switch (blend) { case MixBlend.Setup: - bone.shearX = bone.data.shearX + x * alpha; - bone.shearY = bone.data.shearY + y * alpha; + pose.shearX = setup.shearX + x * alpha; + pose.shearY = setup.shearY + y * alpha; break; case MixBlend.First: case MixBlend.Replace: - bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; - bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; + pose.shearX += (setup.shearX + x - pose.shearX) * alpha; + pose.shearY += (setup.shearY + y - pose.shearY) * alpha; break; case MixBlend.Add: - bone.shearX += x * alpha; - bone.shearY += y * alpha; + pose.shearX += x * alpha; + pose.shearY += y * alpha; break; } } } - /// Changes a bone's local . - public class ShearXTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - + /// Changes a bone's local . + public class ShearXTimeline : BoneTimeline1 { public ShearXTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.ShearX + "|" + boneIndex) { - this.boneIndex = boneIndex; + : base(frameCount, bezierCount, boneIndex, Property.ShearX) { } - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (bone.active) bone.shearX = GetRelativeValue(time, alpha, blend, bone.shearX, bone.data.shearX); + override protected void Apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { + pose.shearX = GetRelativeValue(time, alpha, blend, pose.shearX, setup.shearX); } } - /// Changes a bone's local . - public class ShearYTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - + /// Changes a bone's local . + public class ShearYTimeline : BoneTimeline1 { public ShearYTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.ShearY + "|" + boneIndex) { - this.boneIndex = boneIndex; + : base(frameCount, bezierCount, boneIndex, Property.ShearY) { } - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (bone.active) bone.shearY = GetRelativeValue(time, alpha, blend, bone.shearY, bone.data.shearY); + override protected void Apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { + pose.shearY = GetRelativeValue(time, alpha, blend, pose.shearY, setup.shearY); } } - /// Changes a bone's . - + /// Changes a bone's . public class InheritTimeline : Timeline, IBoneTimeline { public const int ENTRIES = 2; - public const int INHERIT = 1; + private const int INHERIT = 1; readonly int boneIndex; @@ -1003,7 +972,7 @@ namespace Spine { get { return ENTRIES; } } - /// Sets the transform mode for the specified frame. + /// Sets the inherit transform mode for the specified frame. /// Between 0 and frameCount, inclusive. /// The frame time in seconds. public void SetFrame (int frame, float time, Inherit inherit) { @@ -1012,42 +981,34 @@ namespace Spine { frames[frame + INHERIT] = (int)inherit; } - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { + /// May be null. + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction, bool appliedPose) { Bone bone = skeleton.bones.Items[boneIndex]; if (!bone.active) return; + BoneLocal pose = appliedPose ? bone.applied : bone.pose; if (direction == MixDirection.Out) { - if (blend == MixBlend.Setup) bone.inherit = bone.data.inherit; + if (blend == MixBlend.Setup) pose.inherit = bone.data.setup.inherit; return; } float[] frames = this.frames; if (time < frames[0]) { - if (blend == MixBlend.Setup || blend == MixBlend.First) bone.inherit = bone.data.inherit; - return; - } - bone.inherit = InheritEnum.Values[(int)frames[Search(frames, time, ENTRIES) + INHERIT]]; + if (blend == MixBlend.Setup || blend == MixBlend.First) pose.inherit = bone.data.setup.inherit; + } else + pose.inherit = InheritEnum.Values[(int)frames[Search(frames, time, ENTRIES) + INHERIT]]; } } - /// Changes a slot's . - public class RGBATimeline : CurveTimeline, ISlotTimeline { - public const int ENTRIES = 5; - protected const int R = 1, G = 2, B = 3, A = 4; - + public abstract class SlotCurveTimeline : CurveTimeline, ISlotTimeline { readonly int slotIndex; - public RGBATimeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, // - (int)Property.RGB + "|" + slotIndex, // - (int)Property.Alpha + "|" + slotIndex) { + public SlotCurveTimeline (int frameCount, int bezierCount, int slotIndex, params string[] propertyIds) + : base(frameCount, bezierCount, propertyIds) { this.slotIndex = slotIndex; } - public override int FrameEntries { - get { return ENTRIES; } - } public int SlotIndex { get { @@ -1055,6 +1016,31 @@ namespace Spine { } } + /// May be null. + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction, bool appliedPose) { + + Slot slot = skeleton.slots.Items[slotIndex]; + if (slot.bone.active) Apply(slot, appliedPose ? slot.applied : slot.pose, time, alpha, blend); + } + + abstract protected void Apply (Slot slot, SlotPose pose, float time, float alpha, MixBlend blend); + } + + /// Changes a slot's . + public class RGBATimeline : SlotCurveTimeline { + public const int ENTRIES = 5; + private const int R = 1, G = 2, B = 3, A = 4; + + public RGBATimeline (int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, slotIndex, // + (int)Property.RGB + "|" + slotIndex, // + (int)Property.Alpha + "|" + slotIndex) { + } + public override int FrameEntries { + get { return ENTRIES; } + } + /// Sets the time and color for the specified frame. /// Between 0 and frameCount, inclusive. /// The frame time in seconds. @@ -1067,28 +1053,22 @@ namespace Spine { frames[frame + A] = a; } - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - + override protected void Apply (Slot slot, SlotPose pose, float time, float alpha, MixBlend blend) { float[] frames = this.frames; + Color color = pose.GetColor(); if (time < frames[0]) { - SlotData setup = slot.data; + Color setup = slot.data.setup.GetColor(); switch (blend) { case MixBlend.Setup: - slot.r = setup.r; - slot.g = setup.g; - slot.b = setup.b; - slot.a = setup.a; + color = setup; + pose.SetColor(color); // required due to Color being a struct return; case MixBlend.First: - slot.r += (setup.r - slot.r) * alpha; - slot.g += (setup.g - slot.g) * alpha; - slot.b += (setup.b - slot.b) * alpha; - slot.a += (setup.a - slot.a) * alpha; - slot.ClampColor(); - return; + color += new Color((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha, + (setup.a - color.a) * alpha); + color.Clamp(); + pose.SetColor(color); // see above + break; } return; } @@ -1123,55 +1103,30 @@ namespace Spine { } if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; + color = new Color(r, g, b, a); } else { - float br, bg, bb, ba; - if (blend == MixBlend.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - } - slot.r = br + (r - br) * alpha; - slot.g = bg + (g - bg) * alpha; - slot.b = bb + (b - bb) * alpha; - slot.a = ba + (a - ba) * alpha; + if (blend == MixBlend.Setup) color = slot.data.setup.GetColor(); + color += new Color((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha); } - slot.ClampColor(); + color.Clamp(); + pose.SetColor(color); // see above + } } - /// Changes the RGB for a slot's . - public class RGBTimeline : CurveTimeline, ISlotTimeline { + /// Changes the RGB for a slot's . + public class RGBTimeline : SlotCurveTimeline { public const int ENTRIES = 4; - protected const int R = 1, G = 2, B = 3; - - readonly int slotIndex; + private const int R = 1, G = 2, B = 3; public RGBTimeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, // - (int)Property.RGB + "|" + slotIndex) { - this.slotIndex = slotIndex; + : base(frameCount, bezierCount, slotIndex, (int)Property.RGB + "|" + slotIndex) { } public override int FrameEntries { get { return ENTRIES; } } - public int SlotIndex { - get { - return slotIndex; - } - } - /// Sets the time and color for the specified frame. /// Between 0 and frameCount, inclusive. /// The frame time in seconds. @@ -1183,25 +1138,24 @@ namespace Spine { frames[frame + B] = b; } - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - + override protected void Apply (Slot slot, SlotPose pose, float time, float alpha, MixBlend blend) { float[] frames = this.frames; + Color color = pose.GetColor(); if (time < frames[0]) { - SlotData setup = slot.data; + Color setup = slot.data.setup.GetColor(); switch (blend) { case MixBlend.Setup: - slot.r = setup.r; - slot.g = setup.g; - slot.b = setup.b; + color.r = setup.r; + color.g = setup.g; + color.b = setup.b; + pose.SetColor(color); // required due to Color being a struct return; case MixBlend.First: - slot.r += (setup.r - slot.r) * alpha; - slot.g += (setup.g - slot.g) * alpha; - slot.b += (setup.b - slot.b) * alpha; - slot.ClampColor(); + color.r += (setup.r - color.r) * alpha; + color.g += (setup.g - color.g) * alpha; + color.b += (setup.b - color.b) * alpha; + color.Clamp(); + pose.SetColor(color); // see above return; } return; @@ -1233,30 +1187,28 @@ namespace Spine { } if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; + color.r = r; + color.g = g; + color.b = b; + color.Clamp(); + pose.SetColor(color); // see above } else { - float br, bg, bb; if (blend == MixBlend.Setup) { - SlotData setup = slot.data; - br = setup.r; - bg = setup.g; - bb = setup.b; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; + Color setup = slot.data.setup.GetColor(); + color.r = setup.r; + color.g = setup.g; + color.b = setup.b; } - slot.r = br + (r - br) * alpha; - slot.g = bg + (g - bg) * alpha; - slot.b = bb + (b - bb) * alpha; + color.r += (r - color.r) * alpha; + color.g += (g - color.g) * alpha; + color.b += (b - color.b) * alpha; } - slot.ClampColor(); + color.Clamp(); + pose.SetColor(color); // see above } } - /// Changes the alpha for a slot's . + /// Changes the alpha for a slot's . public class AlphaTimeline : CurveTimeline1, ISlotTimeline { readonly int slotIndex; @@ -1271,50 +1223,54 @@ namespace Spine { } } - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { + /// May be null. + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction, bool appliedPose) { Slot slot = skeleton.slots.Items[slotIndex]; if (!slot.bone.active) return; + SlotPose pose = (appliedPose ? slot.applied : slot.pose); + Color color = pose.GetColor(); + float[] frames = this.frames; if (time < frames[0]) { - SlotData setup = slot.data; + Color setup = slot.data.setup.GetColor(); switch (blend) { case MixBlend.Setup: - slot.a = setup.a; + color.a = setup.a; + pose.SetColor(color); // required due to Color being a struct return; case MixBlend.First: - slot.a += (setup.a - slot.a) * alpha; - slot.ClampColor(); - return; + color.a += (setup.a - color.a) * alpha; + color.a = MathUtils.Clamp01(color.a); + pose.SetColor(color); // see above + break; } return; } float a = GetCurveValue(time); if (alpha == 1) - slot.a = a; + color.a = a; else { - if (blend == MixBlend.Setup) slot.a = slot.data.a; - slot.a += (a - slot.a) * alpha; + if (blend == MixBlend.Setup) color.a = slot.data.setup.GetColor().a; + color.a += (a - color.a) * alpha; } - slot.ClampColor(); + color.a = MathUtils.Clamp01(color.a); + pose.SetColor(color); // see above } } - /// Changes a slot's and for two color tinting. - public class RGBA2Timeline : CurveTimeline, ISlotTimeline { + /// Changes a slot's and for two color tinting. + public class RGBA2Timeline : SlotCurveTimeline { public const int ENTRIES = 8; protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; - readonly int slotIndex; - public RGBA2Timeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, // + : base(frameCount, bezierCount, slotIndex, // (int)Property.RGB + "|" + slotIndex, // (int)Property.Alpha + "|" + slotIndex, // (int)Property.RGB2 + "|" + slotIndex) { - this.slotIndex = slotIndex; } public override int FrameEntries { @@ -1323,15 +1279,6 @@ namespace Spine { } } - /// - /// The index of the slot in that will be changed when this timeline is applied. The - /// must have a dark color available. - public int SlotIndex { - get { - return slotIndex; - } - } - /// Sets the time, light color, and dark color for the specified frame. /// Between 0 and frameCount, inclusive. /// The frame time in seconds. @@ -1347,36 +1294,33 @@ namespace Spine { frames[frame + B2] = b2; } - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - + override protected void Apply (Slot slot, SlotPose pose, float time, float alpha, MixBlend blend) { float[] frames = this.frames; + Color light = pose.GetColor(); + Color? dark = pose.GetDarkColor(); if (time < frames[0]) { - SlotData setup = slot.data; + SlotPose setup = slot.data.setup; + Color setupLight = setup.GetColor(); + Color? setupDark = setup.GetDarkColor(); switch (blend) { case MixBlend.Setup: - slot.r = setup.r; - slot.g = setup.g; - slot.b = setup.b; - slot.a = setup.a; - slot.ClampColor(); - slot.r2 = setup.r2; - slot.g2 = setup.g2; - slot.b2 = setup.b2; - slot.ClampSecondColor(); + pose.SetColor(setupLight); // required due to Color being a struct + pose.SetDarkColor(setupDark); return; case MixBlend.First: - slot.r += (slot.r - setup.r) * alpha; - slot.g += (slot.g - setup.g) * alpha; - slot.b += (slot.b - setup.b) * alpha; - slot.a += (slot.a - setup.a) * alpha; - slot.ClampColor(); - slot.r2 += (slot.r2 - setup.r2) * alpha; - slot.g2 += (slot.g2 - setup.g2) * alpha; - slot.b2 += (slot.b2 - setup.b2) * alpha; - slot.ClampSecondColor(); + light += new Color((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha, + (setupLight.a - light.a) * alpha); + light.Clamp(); + + Color darkValue = dark.Value; + Color setupDarkValue = setupDark.Value; + darkValue.r += (setupDarkValue.r - darkValue.r) * alpha; + darkValue.g += (setupDarkValue.g - darkValue.g) * alpha; + darkValue.b += (setupDarkValue.b - darkValue.b) * alpha; + darkValue.Clamp(); + + pose.SetColor(light); // required due to Color being a struct + pose.SetDarkColor(darkValue); return; } return; @@ -1424,57 +1368,51 @@ namespace Spine { } if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - slot.r2 = r2; - slot.g2 = g2; - slot.b2 = b2; + light = new Color(r, g, b, a); + light.Clamp(); + + Color darkValue = dark.Value; + darkValue.r = r2; + darkValue.g = g2; + darkValue.b = b2; + darkValue.Clamp(); + + pose.SetColor(light); // required due to Color being a struct + pose.SetDarkColor(darkValue); } else { - float br, bg, bb, ba, br2, bg2, bb2; + Color darkValue = dark.Value; if (blend == MixBlend.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - br2 = slot.data.r2; - bg2 = slot.data.g2; - bb2 = slot.data.b2; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - br2 = slot.r2; - bg2 = slot.g2; - bb2 = slot.b2; + SlotPose setup = slot.data.setup; + light = setup.GetColor(); + Color? setupDark = setup.GetDarkColor(); + Color setupDarkValue = setupDark.Value; + darkValue.r = setupDarkValue.r; + darkValue.g = setupDarkValue.g; + darkValue.b = setupDarkValue.b; } - slot.r = br + (r - br) * alpha; - slot.g = bg + (g - bg) * alpha; - slot.b = bb + (b - bb) * alpha; - slot.a = ba + (a - ba) * alpha; - slot.r2 = br2 + (r2 - br2) * alpha; - slot.g2 = bg2 + (g2 - bg2) * alpha; - slot.b2 = bb2 + (b2 - bb2) * alpha; + light += new Color((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha); + light.Clamp(); + + darkValue.r += (r2 - darkValue.r) * alpha; + darkValue.g += (g2 - darkValue.g) * alpha; + darkValue.b += (b2 - darkValue.b) * alpha; + darkValue.Clamp(); + + pose.SetColor(light); // see above + pose.SetDarkColor(darkValue); } - slot.ClampColor(); - slot.ClampSecondColor(); } } - /// Changes the RGB for a slot's and for two color tinting. - public class RGB2Timeline : CurveTimeline, ISlotTimeline { + /// Changes the RGB for a slot's and for two color tinting. + public class RGB2Timeline : SlotCurveTimeline { public const int ENTRIES = 7; protected const int R = 1, G = 2, B = 3, R2 = 4, G2 = 5, B2 = 6; - readonly int slotIndex; - public RGB2Timeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, // + : base(frameCount, bezierCount, slotIndex, // (int)Property.RGB + "|" + slotIndex, // (int)Property.RGB2 + "|" + slotIndex) { - this.slotIndex = slotIndex; } public override int FrameEntries { @@ -1483,15 +1421,6 @@ namespace Spine { } } - /// - /// The index of the slot in that will be changed when this timeline is applied. The - /// must have a dark color available. - public int SlotIndex { - get { - return slotIndex; - } - } - /// Sets the time, light color, and dark color for the specified frame. /// Between 0 and frameCount, inclusive. /// The frame time in seconds. @@ -1506,34 +1435,43 @@ namespace Spine { frames[frame + B2] = b2; } - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - + override protected void Apply (Slot slot, SlotPose pose, float time, float alpha, MixBlend blend) { float[] frames = this.frames; + Color light = pose.GetColor(); + Color? dark = pose.GetDarkColor(); if (time < frames[0]) { - SlotData setup = slot.data; + SlotPose setup = slot.data.setup; + Color setupLight = setup.GetColor(); + Color? setupDark = setup.GetDarkColor(); + Color darkValue = dark.Value; + Color setupDarkValue = setupDark.Value; + switch (blend) { case MixBlend.Setup: - slot.r = setup.r; - slot.g = setup.g; - slot.b = setup.b; - slot.ClampColor(); - slot.r2 = setup.r2; - slot.g2 = setup.g2; - slot.b2 = setup.b2; - slot.ClampSecondColor(); + light.r = setupLight.r; + light.g = setupLight.g; + light.b = setupLight.b; + + darkValue.r = setupDarkValue.r; + darkValue.g = setupDarkValue.g; + darkValue.b = setupDarkValue.b; + + pose.SetColor(light); // required due to Color being a struct + pose.SetDarkColor(darkValue); return; case MixBlend.First: - slot.r += (slot.r - setup.r) * alpha; - slot.g += (slot.g - setup.g) * alpha; - slot.b += (slot.b - setup.b) * alpha; - slot.ClampColor(); - slot.r2 += (slot.r2 - setup.r2) * alpha; - slot.g2 += (slot.g2 - setup.g2) * alpha; - slot.b2 += (slot.b2 - setup.b2) * alpha; - slot.ClampSecondColor(); + light.r += (setupLight.r - light.r) * alpha; + light.g += (setupLight.g - light.g) * alpha; + light.b += (setupLight.b - light.b) * alpha; + light.Clamp(); + + darkValue.r += (setupDarkValue.r - darkValue.r) * alpha; + darkValue.g += (setupDarkValue.g - darkValue.g) * alpha; + darkValue.b += (setupDarkValue.b - darkValue.b) * alpha; + darkValue.Clamp(); + + pose.SetColor(light); // see above + pose.SetDarkColor(darkValue); return; } return; @@ -1577,43 +1515,52 @@ namespace Spine { } if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.r2 = r2; - slot.g2 = g2; - slot.b2 = b2; + light.r = r; + light.g = g; + light.b = b; + light.Clamp(); + + Color darkValue = dark.Value; + darkValue.r = r2; + darkValue.g = g2; + darkValue.b = b2; + darkValue.Clamp(); + + pose.SetColor(light); // required due to Color being a struct + pose.SetDarkColor(darkValue); } else { - float br, bg, bb, br2, bg2, bb2; + Color darkValue = dark.Value; if (blend == MixBlend.Setup) { - SlotData setup = slot.data; - br = setup.r; - bg = setup.g; - bb = setup.b; - br2 = setup.r2; - bg2 = setup.g2; - bb2 = setup.b2; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - br2 = slot.r2; - bg2 = slot.g2; - bb2 = slot.b2; + + SlotPose setup = slot.data.setup; + Color setupLight = setup.GetColor(); + Color? setupDark = setup.GetDarkColor(); + Color setupDarkValue = setupDark.Value; + + light.r = setupLight.r; + light.g = setupLight.g; + light.b = setupLight.b; + darkValue.r = setupDarkValue.r; + darkValue.g = setupDarkValue.g; + darkValue.b = setupDarkValue.b; } - slot.r = br + (r - br) * alpha; - slot.g = bg + (g - bg) * alpha; - slot.b = bb + (b - bb) * alpha; - slot.r2 = br2 + (r2 - br2) * alpha; - slot.g2 = bg2 + (g2 - bg2) * alpha; - slot.b2 = bb2 + (b2 - bb2) * alpha; + + light.r += (r - light.r) * alpha; + light.g += (g - light.g) * alpha; + light.b += (b - light.b) * alpha; + light.Clamp(); + darkValue.r += (r2 - darkValue.r) * alpha; + darkValue.g += (g2 - darkValue.g) * alpha; + darkValue.b += (b2 - darkValue.b) * alpha; + darkValue.Clamp(); + + pose.SetColor(light); // see above + pose.SetDarkColor(darkValue); } - slot.ClampColor(); - slot.ClampSecondColor(); } } - /// Changes a slot's . + /// Changes a slot's . public class AttachmentTimeline : Timeline, ISlotTimeline { readonly int slotIndex; readonly string[] attachmentNames; @@ -1624,6 +1571,10 @@ namespace Spine { attachmentNames = new String[frameCount]; } + override public int FrameCount { + get { return frames.Length; } + } + public int SlotIndex { get { return slotIndex; @@ -1645,48 +1596,40 @@ namespace Spine { attachmentNames[frame] = attachmentName; } - public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { + public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction, bool appliedPose) { Slot slot = skeleton.slots.Items[slotIndex]; if (!slot.bone.active) return; + SlotPose pose = appliedPose ? slot.applied : slot.pose; if (direction == MixDirection.Out) { - if (blend == MixBlend.Setup) SetAttachment(skeleton, slot, slot.data.attachmentName); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { - if (blend == MixBlend.Setup || blend == MixBlend.First) SetAttachment(skeleton, slot, slot.data.attachmentName); - return; - } - - SetAttachment(skeleton, slot, attachmentNames[Search(frames, time)]); + if (blend == MixBlend.Setup) SetAttachment(skeleton, pose, slot.data.attachmentName); + } else if (time < this.frames[0]) { + if (blend == MixBlend.Setup || blend == MixBlend.First) SetAttachment(skeleton, pose, slot.data.attachmentName); + } else + SetAttachment(skeleton, pose, attachmentNames[Search(this.frames, time)]); } - private void SetAttachment (Skeleton skeleton, Slot slot, string attachmentName) { - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + private void SetAttachment (Skeleton skeleton, SlotPose pose, string attachmentName) { + pose.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); } } - /// Changes a slot's to deform a . - public class DeformTimeline : CurveTimeline, ISlotTimeline { - readonly int slotIndex; + /// Changes a slot's to deform a . + public class DeformTimeline : SlotCurveTimeline { readonly VertexAttachment attachment; internal float[][] vertices; public DeformTimeline (int frameCount, int bezierCount, int slotIndex, VertexAttachment attachment) - : base(frameCount, bezierCount, (int)Property.Deform + "|" + slotIndex + "|" + attachment.Id) { - this.slotIndex = slotIndex; + : base(frameCount, bezierCount, slotIndex, (int)Property.Deform + "|" + slotIndex + "|" + attachment.Id) { this.attachment = attachment; vertices = new float[frameCount][]; } - public int SlotIndex { - get { - return slotIndex; - } + override public int FrameCount { + get { return frames.Length; } } + /// The attachment that will be deformed. /// public VertexAttachment Attachment { @@ -1713,7 +1656,7 @@ namespace Spine { /// Ignored (0 is used for a deform timeline). /// Ignored (1 is used for a deform timeline). - public void setBezier (int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, + override public void SetBezier (int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, float cy2, float time2, float value2) { float[] curves = this.curves; int i = FrameCount + bezier * BEZIER_SIZE; @@ -1765,15 +1708,11 @@ namespace Spine { } } - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - VertexAttachment vertexAttachment = slot.attachment as VertexAttachment; + override protected void Apply (Slot slot, SlotPose pose, float time, float alpha, MixBlend blend) { + VertexAttachment vertexAttachment = pose.attachment as VertexAttachment; if (vertexAttachment == null || vertexAttachment.TimelineAttachment != attachment) return; - ExposedList deformArray = slot.deform; + ExposedList deformArray = pose.deform; if (deformArray.Count == 0) blend = MixBlend.Setup; float[][] vertices = this.vertices; @@ -1798,13 +1737,11 @@ namespace Spine { deformArray.Count = vertexCount; deform = deformArray.Items; - if (vertexAttachment.bones == null) { - // Unweighted vertex positions. + if (vertexAttachment.bones == null) { // Unweighted vertex positions. float[] setupVertices = vertexAttachment.vertices; for (int i = 0; i < vertexCount; i++) deform[i] += (setupVertices[i] - deform[i]) * alpha; - } else { - // Weighted deform offsets. + } else { // Weighted deform offsets. alpha = 1 - alpha; for (int i = 0; i < vertexCount; i++) deform[i] *= alpha; @@ -1823,51 +1760,43 @@ namespace Spine { float[] lastVertices = vertices[frames.Length - 1]; if (alpha == 1) { if (blend == MixBlend.Add) { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. + if (vertexAttachment.bones == null) { // Unweighted vertex positions, no alpha. float[] setupVertices = vertexAttachment.vertices; for (int i = 0; i < vertexCount; i++) deform[i] += lastVertices[i] - setupVertices[i]; - } else { - // Weighted deform offsets, no alpha. + } else { // Weighted deform offsets, no alpha. for (int i = 0; i < vertexCount; i++) deform[i] += lastVertices[i]; } - } else { - // Vertex positions or deform offsets, no alpha. + } else { // Vertex positions or deform offsets, no alpha. Array.Copy(lastVertices, 0, deform, 0, vertexCount); } } else { switch (blend) { case MixBlend.Setup: { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. + if (vertexAttachment.bones == null) { // Unweighted vertex positions, with alpha. float[] setupVertices = vertexAttachment.vertices; for (int i = 0; i < vertexCount; i++) { float setup = setupVertices[i]; deform[i] = setup + (lastVertices[i] - setup) * alpha; } - } else { - // Weighted deform offsets, with alpha. + } else { // Weighted deform offsets, with alpha. for (int i = 0; i < vertexCount; i++) deform[i] = lastVertices[i] * alpha; } break; } case MixBlend.First: - case MixBlend.Replace: - // Vertex positions or deform offsets, with alpha. + case MixBlend.Replace: // Vertex positions or deform offsets, with alpha. for (int i = 0; i < vertexCount; i++) deform[i] += (lastVertices[i] - deform[i]) * alpha; break; case MixBlend.Add: - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. + if (vertexAttachment.bones == null) { // Unweighted vertex positions, no alpha. float[] setupVertices = vertexAttachment.vertices; for (int i = 0; i < vertexCount; i++) deform[i] += (lastVertices[i] - setupVertices[i]) * alpha; - } else { - // Weighted deform offsets, alpha. + } else { // Weighted deform offsets, alpha. for (int i = 0; i < vertexCount; i++) deform[i] += lastVertices[i] * alpha; } @@ -1884,22 +1813,21 @@ namespace Spine { if (alpha == 1) { if (blend == MixBlend.Add) { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. + if (vertexAttachment.bones == null) { // Unweighted vertex positions, no alpha. float[] setupVertices = vertexAttachment.vertices; for (int i = 0; i < vertexCount; i++) { float prev = prevVertices[i]; deform[i] += prev + (nextVertices[i] - prev) * percent - setupVertices[i]; } - } else { - // Weighted deform offsets, no alpha. + } else { // Weighted deform offsets, no alpha. for (int i = 0; i < vertexCount; i++) { float prev = prevVertices[i]; deform[i] += prev + (nextVertices[i] - prev) * percent; } } - } else { - // Vertex positions or deform offsets, no alpha. + } else if (percent == 0) { + Array.Copy(prevVertices, 0, deform, 0, vertexCount); + } else { // Vertex positions or deform offsets, no alpha. for (int i = 0; i < vertexCount; i++) { float prev = prevVertices[i]; deform[i] = prev + (nextVertices[i] - prev) * percent; @@ -1908,15 +1836,13 @@ namespace Spine { } else { switch (blend) { case MixBlend.Setup: { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. + if (vertexAttachment.bones == null) { // Unweighted vertex positions, with alpha. float[] setupVertices = vertexAttachment.vertices; for (int i = 0; i < vertexCount; i++) { float prev = prevVertices[i], setup = setupVertices[i]; deform[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; } - } else { - // Weighted deform offsets, with alpha. + } else { // Weighted deform offsets, with alpha. for (int i = 0; i < vertexCount; i++) { float prev = prevVertices[i]; deform[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; @@ -1925,8 +1851,7 @@ namespace Spine { break; } case MixBlend.First: - case MixBlend.Replace: { - // Vertex positions or deform offsets, with alpha. + case MixBlend.Replace: { // Vertex positions or deform offsets, with alpha. for (int i = 0; i < vertexCount; i++) { float prev = prevVertices[i]; deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha; @@ -1934,15 +1859,13 @@ namespace Spine { break; } case MixBlend.Add: - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. + if (vertexAttachment.bones == null) { // Unweighted vertex positions, with alpha. float[] setupVertices = vertexAttachment.vertices; for (int i = 0; i < vertexCount; i++) { float prev = prevVertices[i]; deform[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha; } - } else { - // Weighted deform offsets, with alpha. + } else { // Weighted deform offsets, with alpha. for (int i = 0; i < vertexCount; i++) { float prev = prevVertices[i]; deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; @@ -1954,6 +1877,112 @@ namespace Spine { } } + /// Changes a slot's for an attachment's . + public class SequenceTimeline : Timeline, ISlotTimeline { + public const int ENTRIES = 3; + private const int MODE = 1, DELAY = 2; + + readonly int slotIndex; + readonly IHasTextureRegion attachment; + + public SequenceTimeline (int frameCount, int slotIndex, Attachment attachment) + : base(frameCount, (int)Property.Sequence + "|" + slotIndex + "|" + ((IHasTextureRegion)attachment).Sequence.Id) { + this.slotIndex = slotIndex; + this.attachment = (IHasTextureRegion)attachment; + } + + public override int FrameEntries { + get { return ENTRIES; } + } + + public int SlotIndex { + get { + return slotIndex; + } + } + public Attachment Attachment { + get { + return (Attachment)attachment; + } + } + + /// Sets the time, mode, index, and frame time for the specified frame. + /// Between 0 and frameCount, inclusive. + /// Seconds between frames. + public void SetFrame (int frame, float time, SequenceMode mode, int index, float delay) { + frame *= ENTRIES; + frames[frame] = time; + frames[frame + MODE] = (int)mode | (index << 4); + frames[frame + DELAY] = delay; + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction, bool appliedPose) { + + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + SlotPose pose = appliedPose ? slot.applied : slot.pose; + + Attachment slotAttachment = pose.attachment; + if (slotAttachment != attachment) { + VertexAttachment vertexAttachment = slotAttachment as VertexAttachment; + if ((vertexAttachment == null) + || vertexAttachment.TimelineAttachment != attachment) return; + } + Sequence sequence = ((IHasTextureRegion)slotAttachment).Sequence; + if (sequence == null) return; + + if (direction == MixDirection.Out) { + if (blend == MixBlend.Setup) pose.SequenceIndex = -1; + return; + } + + float[] frames = this.frames; + if (time < frames[0]) { + if (blend == MixBlend.Setup || blend == MixBlend.First) pose.SequenceIndex = -1; + return; + } + + int i = Search(frames, time, ENTRIES); + float before = frames[i]; + int modeAndIndex = (int)frames[i + MODE]; + float delay = frames[i + DELAY]; + + int index = modeAndIndex >> 4, count = sequence.Regions.Length; + SequenceMode mode = (SequenceMode)(modeAndIndex & 0xf); + if (mode != SequenceMode.Hold) { + index += (int)((time - before) / delay + 0.0001f); + switch (mode) { + case SequenceMode.Once: + index = Math.Min(count - 1, index); + break; + case SequenceMode.Loop: + index %= count; + break; + case SequenceMode.Pingpong: { + int n = (count << 1) - 2; + index = n == 0 ? 0 : index % n; + if (index >= count) index = n - index; + break; + } + case SequenceMode.OnceReverse: + index = Math.Max(count - 1 - index, 0); + break; + case SequenceMode.LoopReverse: + index = count - 1 - (index % count); + break; + case SequenceMode.PingpongReverse: { + int n = (count << 1) - 2; + index = n == 0 ? 0 : (index + count - 1) % n; + if (index >= count) index = n - index; + break; + } // end case + } + } + pose.SequenceIndex = index; + } + } + /// Fires an when specific animation times are reached. public class EventTimeline : Timeline { readonly static string[] propertyIds = { ((int)Property.Event).ToString() }; @@ -1964,6 +1993,10 @@ namespace Spine { events = new Event[frameCount]; } + override public int FrameCount { + get { return frames.Length; } + } + /// The event for each frame. public Event[] Events { get { @@ -1980,7 +2013,7 @@ namespace Spine { /// Fires events for frames > lastTime and <= time. public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, - MixBlend blend, MixDirection direction) { + MixBlend blend, MixDirection direction, bool appliedPose) { if (firedEvents == null) return; @@ -1988,7 +2021,7 @@ namespace Spine { int frameCount = frames.Length; if (lastTime > time) { // Apply after lastTime for looped animations. - Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction); + Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction, appliedPose); lastTime = -1f; } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. return; @@ -2021,6 +2054,10 @@ namespace Spine { drawOrders = new int[frameCount][]; } + override public int FrameCount { + get { return frames.Length; } + } + /// The draw order for each frame. /// . public int[][] DrawOrders { @@ -2039,15 +2076,15 @@ namespace Spine { drawOrders[frame] = drawOrder; } - public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { + /// May be null. + public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction, bool appliedPose) { if (direction == MixDirection.Out) { if (blend == MixBlend.Setup) Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); return; } - float[] frames = this.frames; if (time < frames[0]) { if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); return; @@ -2065,17 +2102,25 @@ namespace Spine { } } - /// Changes an IK constraint's , , - /// , , and . - public class IkConstraintTimeline : CurveTimeline { + public interface IConstraintTimeline { + /// + /// The index of the constraint in that will be changed when this timeline is applied. + /// + public int ConstraintIndex { get; } + } + + /// Changes an IK constraint's , , + /// , , and + /// . + public class IkConstraintTimeline : CurveTimeline, IConstraintTimeline { public const int ENTRIES = 6; private const int MIX = 1, SOFTNESS = 2, BEND_DIRECTION = 3, COMPRESS = 4, STRETCH = 5; readonly int constraintIndex; - public IkConstraintTimeline (int frameCount, int bezierCount, int ikConstraintIndex) - : base(frameCount, bezierCount, (int)Property.IkConstraint + "|" + ikConstraintIndex) { - this.constraintIndex = ikConstraintIndex; + public IkConstraintTimeline (int frameCount, int bezierCount, int constraintIndex) + : base(frameCount, bezierCount, (int)Property.IkConstraint + "|" + constraintIndex) { + this.constraintIndex = constraintIndex; } public override int FrameEntries { @@ -2084,9 +2129,7 @@ namespace Spine { } } - /// The index of the IK constraint in that will be changed when this timeline is - /// applied. - public int IkConstraintIndex { + public int ConstraintIndex { get { return constraintIndex; } @@ -2107,28 +2150,31 @@ namespace Spine { frames[frame + STRETCH] = stretch ? 1 : 0; } - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - IkConstraint constraint = skeleton.ikConstraints.Items[constraintIndex]; + /// May be null. + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction, bool appliedPose) { + var constraint = (IkConstraint)skeleton.constraints.Items[constraintIndex]; if (!constraint.active) return; + IkConstraintPose pose = appliedPose ? constraint.applied : constraint.pose; float[] frames = this.frames; if (time < frames[0]) { + IkConstraintPose setup = constraint.data.setup; switch (blend) { case MixBlend.Setup: - constraint.mix = constraint.data.mix; - constraint.softness = constraint.data.softness; - constraint.bendDirection = constraint.data.bendDirection; - constraint.compress = constraint.data.compress; - constraint.stretch = constraint.data.stretch; - return; + pose.mix = setup.mix; + pose.softness = setup.softness; + pose.bendDirection = setup.bendDirection; + pose.compress = setup.compress; + pose.stretch = setup.stretch; + break; case MixBlend.First: - constraint.mix += (constraint.data.mix - constraint.mix) * alpha; - constraint.softness += (constraint.data.softness - constraint.softness) * alpha; - constraint.bendDirection = constraint.data.bendDirection; - constraint.compress = constraint.data.compress; - constraint.stretch = constraint.data.stretch; - return; + pose.mix += (setup.mix - pose.mix) * alpha; + pose.softness += (setup.softness - pose.softness) * alpha; + pose.bendDirection = setup.bendDirection; + pose.compress = setup.compress; + pose.stretch = setup.stretch; + break; } return; } @@ -2154,40 +2200,51 @@ namespace Spine { break; } - if (blend == MixBlend.Setup) { - constraint.mix = constraint.data.mix + (mix - constraint.data.mix) * alpha; - constraint.softness = constraint.data.softness + (softness - constraint.data.softness) * alpha; + switch (blend) { + case MixBlend.Setup: { + IkConstraintPose setup = constraint.data.setup; + pose.mix = setup.mix + (mix - setup.mix) * alpha; + pose.softness = setup.softness + (softness - setup.softness) * alpha; if (direction == MixDirection.Out) { - constraint.bendDirection = constraint.data.bendDirection; - constraint.compress = constraint.data.compress; - constraint.stretch = constraint.data.stretch; - } else { - constraint.bendDirection = (int)frames[i + BEND_DIRECTION]; - constraint.compress = frames[i + COMPRESS] != 0; - constraint.stretch = frames[i + STRETCH] != 0; - } - } else { - constraint.mix += (mix - constraint.mix) * alpha; - constraint.softness += (softness - constraint.softness) * alpha; - if (direction == MixDirection.In) { - constraint.bendDirection = (int)frames[i + BEND_DIRECTION]; - constraint.compress = frames[i + COMPRESS] != 0; - constraint.stretch = frames[i + STRETCH] != 0; + pose.bendDirection = setup.bendDirection; + pose.compress = setup.compress; + pose.stretch = setup.stretch; + return; } + break; } + case MixBlend.First: + case MixBlend.Replace: { + pose.mix += (mix - pose.mix) * alpha; + pose.softness += (softness - pose.softness) * alpha; + if (direction == MixDirection.Out) return; + break; + } + case MixBlend.Add: { + pose.mix += mix * alpha; + pose.softness += softness * alpha; + if (direction == MixDirection.Out) return; + break; + } + } + pose.bendDirection = (int)frames[i + BEND_DIRECTION]; + pose.compress = frames[i + COMPRESS] != 0; + pose.stretch = frames[i + STRETCH] != 0; } } - /// Changes a transform constraint's mixes. - public class TransformConstraintTimeline : CurveTimeline { + /// Changes a transform constraint's , , + /// , , + /// , and + public class TransformConstraintTimeline : CurveTimeline, IConstraintTimeline { public const int ENTRIES = 7; private const int ROTATE = 1, X = 2, Y = 3, SCALEX = 4, SCALEY = 5, SHEARY = 6; readonly int constraintIndex; - public TransformConstraintTimeline (int frameCount, int bezierCount, int transformConstraintIndex) - : base(frameCount, bezierCount, (int)Property.TransformConstraint + "|" + transformConstraintIndex) { - constraintIndex = transformConstraintIndex; + public TransformConstraintTimeline (int frameCount, int bezierCount, int constraintIndex) + : base(frameCount, bezierCount, (int)Property.TransformConstraint + "|" + constraintIndex) { + this.constraintIndex = constraintIndex; } public override int FrameEntries { @@ -2196,9 +2253,7 @@ namespace Spine { } } - /// The index of the transform constraint in that will be changed when this - /// timeline is applied. - public int TransformConstraintIndex { + public int ConstraintIndex { get { return constraintIndex; } @@ -2219,53 +2274,71 @@ namespace Spine { frames[frame + SHEARY] = mixShearY; } - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - TransformConstraint constraint = skeleton.transformConstraints.Items[constraintIndex]; + /// May be null. + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction, bool appliedPose) { + var constraint = (TransformConstraint)skeleton.constraints.Items[constraintIndex]; if (!constraint.active) return; + TransformConstraintPose pose = appliedPose ? constraint.applied : constraint.pose; float[] frames = this.frames; if (time < frames[0]) { - TransformConstraintData data = constraint.data; + TransformConstraintPose setup = constraint.data.setup; switch (blend) { case MixBlend.Setup: - constraint.mixRotate = data.mixRotate; - constraint.mixX = data.mixX; - constraint.mixY = data.mixY; - constraint.mixScaleX = data.mixScaleX; - constraint.mixScaleY = data.mixScaleY; - constraint.mixShearY = data.mixShearY; + pose.mixRotate = setup.mixRotate; + pose.mixX = setup.mixX; + pose.mixY = setup.mixY; + pose.mixScaleX = setup.mixScaleX; + pose.mixScaleY = setup.mixScaleY; + pose.mixShearY = setup.mixShearY; return; case MixBlend.First: - constraint.mixRotate += (data.mixRotate - constraint.mixRotate) * alpha; - constraint.mixX += (data.mixX - constraint.mixX) * alpha; - constraint.mixY += (data.mixY - constraint.mixY) * alpha; - constraint.mixScaleX += (data.mixScaleX - constraint.mixScaleX) * alpha; - constraint.mixScaleY += (data.mixScaleY - constraint.mixScaleY) * alpha; - constraint.mixShearY += (data.mixShearY - constraint.mixShearY) * alpha; + pose.mixRotate += (setup.mixRotate - pose.mixRotate) * alpha; + pose.mixX += (setup.mixX - pose.mixX) * alpha; + pose.mixY += (setup.mixY - pose.mixY) * alpha; + pose.mixScaleX += (setup.mixScaleX - pose.mixScaleX) * alpha; + pose.mixScaleY += (setup.mixScaleY - pose.mixScaleY) * alpha; + pose.mixShearY += (setup.mixShearY - pose.mixShearY) * alpha; return; } return; } + // note: reference implementation has code inlined, we re-use GetCurveValue code for root motion. float rotate, x, y, scaleX, scaleY, shearY; GetCurveValue(out rotate, out x, out y, out scaleX, out scaleY, out shearY, time); - if (blend == MixBlend.Setup) { - TransformConstraintData data = constraint.data; - constraint.mixRotate = data.mixRotate + (rotate - data.mixRotate) * alpha; - constraint.mixX = data.mixX + (x - data.mixX) * alpha; - constraint.mixY = data.mixY + (y - data.mixY) * alpha; - constraint.mixScaleX = data.mixScaleX + (scaleX - data.mixScaleX) * alpha; - constraint.mixScaleY = data.mixScaleY + (scaleY - data.mixScaleY) * alpha; - constraint.mixShearY = data.mixShearY + (shearY - data.mixShearY) * alpha; - } else { - constraint.mixRotate += (rotate - constraint.mixRotate) * alpha; - constraint.mixX += (x - constraint.mixX) * alpha; - constraint.mixY += (y - constraint.mixY) * alpha; - constraint.mixScaleX += (scaleX - constraint.mixScaleX) * alpha; - constraint.mixScaleY += (scaleY - constraint.mixScaleY) * alpha; - constraint.mixShearY += (shearY - constraint.mixShearY) * alpha; + switch (blend) { + case MixBlend.Setup: { + TransformConstraintPose setup = constraint.data.setup; + pose.mixRotate = setup.mixRotate + (rotate - setup.mixRotate) * alpha; + pose.mixX = setup.mixX + (x - setup.mixX) * alpha; + pose.mixY = setup.mixY + (y - setup.mixY) * alpha; + pose.mixScaleX = setup.mixScaleX + (scaleX - setup.mixScaleX) * alpha; + pose.mixScaleY = setup.mixScaleY + (scaleY - setup.mixScaleY) * alpha; + pose.mixShearY = setup.mixShearY + (shearY - setup.mixShearY) * alpha; + break; + } + case MixBlend.First: + case MixBlend.Replace: { + pose.mixRotate += (rotate - pose.mixRotate) * alpha; + pose.mixX += (x - pose.mixX) * alpha; + pose.mixY += (y - pose.mixY) * alpha; + pose.mixScaleX += (scaleX - pose.mixScaleX) * alpha; + pose.mixScaleY += (scaleY - pose.mixScaleY) * alpha; + pose.mixShearY += (shearY - pose.mixShearY) * alpha; + break; + } + case MixBlend.Add: { + pose.mixRotate += rotate * alpha; + pose.mixX += x * alpha; + pose.mixY += y * alpha; + pose.mixScaleX += scaleX * alpha; + pose.mixScaleY += scaleY * alpha; + pose.mixShearY += shearY * alpha; + break; + } } } @@ -2311,77 +2384,76 @@ namespace Spine { } } - /// Changes a path constraint's . - public class PathConstraintPositionTimeline : CurveTimeline1 { - readonly int constraintIndex; + abstract public class ConstraintTimeline1 : CurveTimeline1, IConstraintTimeline { + internal readonly int constraintIndex; - public PathConstraintPositionTimeline (int frameCount, int bezierCount, int pathConstraintIndex) - : base(frameCount, bezierCount, (int)Property.PathConstraintPosition + "|" + pathConstraintIndex) { - this.constraintIndex = pathConstraintIndex; + public ConstraintTimeline1 (int frameCount, int bezierCount, int constraintIndex, Property property) + : base(frameCount, bezierCount, (int)property + "|" + constraintIndex) { + this.constraintIndex = constraintIndex; } - /// The index of the path constraint slot in that will be changed when this timeline - /// is applied. - public int PathConstraintIndex { + public int ConstraintIndex { get { return constraintIndex; } } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[constraintIndex]; - if (constraint.active) - constraint.position = GetAbsoluteValue(time, alpha, blend, constraint.position, constraint.data.position); - } } - /// Changes a path constraint's . - public class PathConstraintSpacingTimeline : CurveTimeline1 { - readonly int constraintIndex; + /// Changes a path constraint's . + public class PathConstraintPositionTimeline : ConstraintTimeline1 { - public PathConstraintSpacingTimeline (int frameCount, int bezierCount, int pathConstraintIndex) - : base(frameCount, bezierCount, (int)Property.PathConstraintSpacing + "|" + pathConstraintIndex) { - constraintIndex = pathConstraintIndex; - } - - /// The index of the path constraint in that will be changed when this timeline - /// is applied. - public int PathConstraintIndex { - get { - return constraintIndex; - } + public PathConstraintPositionTimeline (int frameCount, int bezierCount, int constraintIndex) + : base(frameCount, bezierCount, constraintIndex, Property.PathConstraintPosition) { } + /// May be null. override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, - MixDirection direction) { - - PathConstraint constraint = skeleton.pathConstraints.Items[constraintIndex]; - if (constraint.active) - constraint.spacing = GetAbsoluteValue(time, alpha, blend, constraint.spacing, constraint.data.spacing); + MixDirection direction, bool appliedPose) { + var constraint = (PathConstraint)skeleton.constraints.Items[constraintIndex]; + if (constraint.active) { + PathConstraintPose pose = appliedPose ? constraint.applied : constraint.pose; + pose.position = GetAbsoluteValue(time, alpha, blend, pose.position, constraint.data.setup.position); + } } } - /// Changes a path constraint's , , and - /// . - public class PathConstraintMixTimeline : CurveTimeline { + /// Changes a path constraint's . + public class PathConstraintSpacingTimeline : ConstraintTimeline1 { + + public PathConstraintSpacingTimeline (int frameCount, int bezierCount, int constraintIndex) + : base(frameCount, bezierCount, constraintIndex, Property.PathConstraintSpacing) { + } + + /// May be null. + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction, bool appliedPose) { + + var constraint = (PathConstraint)skeleton.constraints.Items[constraintIndex]; + if (constraint.active) { + PathConstraintPose pose = appliedPose ? constraint.applied : constraint.pose; + pose.spacing = GetAbsoluteValue(time, alpha, blend, pose.spacing, constraint.data.setup.spacing); + } + } + } + + /// Changes a path constraint's , , and + /// . + public class PathConstraintMixTimeline : CurveTimeline, IConstraintTimeline { public const int ENTRIES = 4; private const int ROTATE = 1, X = 2, Y = 3; readonly int constraintIndex; - public PathConstraintMixTimeline (int frameCount, int bezierCount, int pathConstraintIndex) - : base(frameCount, bezierCount, (int)Property.PathConstraintMix + "|" + pathConstraintIndex) { - constraintIndex = pathConstraintIndex; + public PathConstraintMixTimeline (int frameCount, int bezierCount, int constraintIndex) + : base(frameCount, bezierCount, (int)Property.PathConstraintMix + "|" + constraintIndex) { + this.constraintIndex = constraintIndex; } public override int FrameEntries { get { return ENTRIES; } } - /// The index of the path constraint slot in that will be changed when this timeline - /// is applied. - public int PathConstraintIndex { + public int ConstraintIndex { get { return constraintIndex; } @@ -2398,23 +2470,26 @@ namespace Spine { frames[frame + Y] = mixY; } - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[constraintIndex]; + /// May be null. + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction, bool appliedPose) { + var constraint = (PathConstraint)skeleton.constraints.Items[constraintIndex]; if (!constraint.active) return; + PathConstraintPose pose = appliedPose ? constraint.applied : constraint.pose; float[] frames = this.frames; if (time < frames[0]) { + PathConstraintPose setup = constraint.data.setup; switch (blend) { case MixBlend.Setup: - constraint.mixRotate = constraint.data.mixRotate; - constraint.mixX = constraint.data.mixX; - constraint.mixY = constraint.data.mixY; + pose.mixRotate = setup.mixRotate; + pose.mixX = setup.mixX; + pose.mixY = setup.mixY; return; case MixBlend.First: - constraint.mixRotate += (constraint.data.mixRotate - constraint.mixRotate) * alpha; - constraint.mixX += (constraint.data.mixX - constraint.mixX) * alpha; - constraint.mixY += (constraint.data.mixY - constraint.mixY) * alpha; + pose.mixRotate += (setup.mixRotate - pose.mixRotate) * alpha; + pose.mixX += (setup.mixX - pose.mixX) * alpha; + pose.mixY += (setup.mixY - pose.mixY) * alpha; return; } return; @@ -2445,81 +2520,81 @@ namespace Spine { break; } - if (blend == MixBlend.Setup) { - PathConstraintData data = constraint.data; - constraint.mixRotate = data.mixRotate + (rotate - data.mixRotate) * alpha; - constraint.mixX = data.mixX + (x - data.mixX) * alpha; - constraint.mixY = data.mixY + (y - data.mixY) * alpha; - } else { - constraint.mixRotate += (rotate - constraint.mixRotate) * alpha; - constraint.mixX += (x - constraint.mixX) * alpha; - constraint.mixY += (y - constraint.mixY) * alpha; + switch (blend) { + case MixBlend.Setup: { + PathConstraintPose setup = constraint.data.setup; + pose.mixRotate = setup.mixRotate + (rotate - setup.mixRotate) * alpha; + pose.mixX = setup.mixX + (x - setup.mixX) * alpha; + pose.mixY = setup.mixY + (y - setup.mixY) * alpha; + break; + } + case MixBlend.First: + case MixBlend.Replace: { + pose.mixRotate += (rotate - pose.mixRotate) * alpha; + pose.mixX += (x - pose.mixX) * alpha; + pose.mixY += (y - pose.mixY) * alpha; + break; + } + case MixBlend.Add: { + pose.mixRotate += rotate * alpha; + pose.mixX += x * alpha; + pose.mixY += y * alpha; + break; + } } } } /// The base class for most timelines. - public abstract class PhysicsConstraintTimeline : CurveTimeline1 { - readonly int constraintIndex; + public abstract class PhysicsConstraintTimeline : ConstraintTimeline1 { - /// -1 for all physics constraints in the skeleton. - public PhysicsConstraintTimeline (int frameCount, int bezierCount, int physicsConstraintIndex, Property property) - : base(frameCount, bezierCount, (int)property + "|" + physicsConstraintIndex) { - - constraintIndex = physicsConstraintIndex; + /// -1 for all physics constraints in the skeleton. + public PhysicsConstraintTimeline (int frameCount, int bezierCount, int constraintIndex, Property property) + : base(frameCount, bezierCount, constraintIndex, property) { } - /// The index of the physics constraint in that will be changed when this timeline - /// is applied, or -1 if all physics constraints in the skeleton will be changed. - public int PhysicsConstraintIndex { - get { - return constraintIndex; - } - } + /// May be null. + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction, bool appliedPose) { - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - PhysicsConstraint constraint; if (constraintIndex == -1) { float value = time >= frames[0] ? GetCurveValue(time) : 0; - - PhysicsConstraint[] constraints = skeleton.physicsConstraints.Items; - for (int i = 0, n = skeleton.physicsConstraints.Count; i < n; i++) { - constraint = (PhysicsConstraint)constraints[i]; - if (constraint.active && Global(constraint.data)) - Set(constraint, GetAbsoluteValue(time, alpha, blend, Get(constraint), Setup(constraint), value)); + PhysicsConstraint[] constraints = skeleton.physics.Items; + for (int i = 0, n = skeleton.physics.Count; i < n; i++) { + PhysicsConstraint constraint = constraints[i]; + if (constraint.active && Global(constraint.data)) { + PhysicsConstraintPose pose = appliedPose ? constraint.applied : constraint.pose; + Set(pose, GetAbsoluteValue(time, alpha, blend, Get(pose), Get(constraint.data.setup), value)); + } } } else { - constraint = skeleton.physicsConstraints.Items[constraintIndex]; - if (constraint.active) Set(constraint, GetAbsoluteValue(time, alpha, blend, Get(constraint), Setup(constraint))); + var constraint = (PhysicsConstraint)skeleton.constraints.Items[constraintIndex]; + if (constraint.active) { + PhysicsConstraintPose pose = appliedPose ? constraint.applied : constraint.pose; + Set(pose, GetAbsoluteValue(time, alpha, blend, Get(pose), Get(constraint.data.setup))); + } } } - abstract protected float Setup (PhysicsConstraint constraint); + abstract protected float Get (PhysicsConstraintPose pose); - abstract protected float Get (PhysicsConstraint constraint); - - abstract protected void Set (PhysicsConstraint constraint, float value); + abstract protected void Set (PhysicsConstraintPose pose, float value); abstract protected bool Global (PhysicsConstraintData constraint); } - /// Changes a physics constraint's . + /// Changes a physics constraint's . public class PhysicsConstraintInertiaTimeline : PhysicsConstraintTimeline { - public PhysicsConstraintInertiaTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) - : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintInertia) { + public PhysicsConstraintInertiaTimeline (int frameCount, int bezierCount, int constraintIndex) + : base(frameCount, bezierCount, constraintIndex, Property.PhysicsConstraintInertia) { } - override protected float Setup (PhysicsConstraint constraint) { - return constraint.data.inertia; + override protected float Get (PhysicsConstraintPose pose) { + return pose.inertia; } - override protected float Get (PhysicsConstraint constraint) { - return constraint.inertia; - } - - override protected void Set (PhysicsConstraint constraint, float value) { - constraint.inertia = value; + override protected void Set (PhysicsConstraintPose pose, float value) { + pose.inertia = value; } override protected bool Global (PhysicsConstraintData constraint) { @@ -2527,22 +2602,18 @@ namespace Spine { } } - /// Changes a physics constraint's . + /// Changes a physics constraint's . public class PhysicsConstraintStrengthTimeline : PhysicsConstraintTimeline { - public PhysicsConstraintStrengthTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) - : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintStrength) { + public PhysicsConstraintStrengthTimeline (int frameCount, int bezierCount, int constraintIndex) + : base(frameCount, bezierCount, constraintIndex, Property.PhysicsConstraintStrength) { } - override protected float Setup (PhysicsConstraint constraint) { - return constraint.data.strength; + override protected float Get (PhysicsConstraintPose pose) { + return pose.strength; } - override protected float Get (PhysicsConstraint constraint) { - return constraint.strength; - } - - override protected void Set (PhysicsConstraint constraint, float value) { - constraint.strength = value; + override protected void Set (PhysicsConstraintPose pose, float value) { + pose.strength = value; } override protected bool Global (PhysicsConstraintData constraint) { @@ -2550,22 +2621,18 @@ namespace Spine { } } - /// Changes a physics constraint's . + /// Changes a physics constraint's . public class PhysicsConstraintDampingTimeline : PhysicsConstraintTimeline { - public PhysicsConstraintDampingTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) - : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintDamping) { + public PhysicsConstraintDampingTimeline (int frameCount, int bezierCount, int constraintIndex) + : base(frameCount, bezierCount, constraintIndex, Property.PhysicsConstraintDamping) { } - override protected float Setup (PhysicsConstraint constraint) { - return constraint.data.damping; + override protected float Get (PhysicsConstraintPose pose) { + return pose.damping; } - override protected float Get (PhysicsConstraint constraint) { - return constraint.damping; - } - - override protected void Set (PhysicsConstraint constraint, float value) { - constraint.damping = value; + override protected void Set (PhysicsConstraintPose pose, float value) { + pose.damping = value; } override protected bool Global (PhysicsConstraintData constraint) { @@ -2573,22 +2640,18 @@ namespace Spine { } } - /// Changes a physics constraint's . The timeline values are not inverted. + /// Changes a physics constraint's . The timeline values are not inverted. public class PhysicsConstraintMassTimeline : PhysicsConstraintTimeline { - public PhysicsConstraintMassTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) - : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintMass) { + public PhysicsConstraintMassTimeline (int frameCount, int bezierCount, int constraintIndex) + : base(frameCount, bezierCount, constraintIndex, Property.PhysicsConstraintMass) { } - override protected float Setup (PhysicsConstraint constraint) { - return 1 / constraint.data.massInverse; + override protected float Get (PhysicsConstraintPose pose) { + return 1 / pose.massInverse; } - override protected float Get (PhysicsConstraint constraint) { - return 1 / constraint.massInverse; - } - - override protected void Set (PhysicsConstraint constraint, float value) { - constraint.massInverse = 1 / value; + override protected void Set (PhysicsConstraintPose pose, float value) { + pose.massInverse = 1 / value; } override protected bool Global (PhysicsConstraintData constraint) { @@ -2596,22 +2659,18 @@ namespace Spine { } } - /// Changes a physics constraint's . + /// Changes a physics constraint's . public class PhysicsConstraintWindTimeline : PhysicsConstraintTimeline { - public PhysicsConstraintWindTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) - : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintWind) { + public PhysicsConstraintWindTimeline (int frameCount, int bezierCount, int constraintIndex) + : base(frameCount, bezierCount, constraintIndex, Property.PhysicsConstraintWind) { } - override protected float Setup (PhysicsConstraint constraint) { - return constraint.data.wind; + override protected float Get (PhysicsConstraintPose pose) { + return pose.wind; } - override protected float Get (PhysicsConstraint constraint) { - return constraint.wind; - } - - override protected void Set (PhysicsConstraint constraint, float value) { - constraint.wind = value; + override protected void Set (PhysicsConstraintPose pose, float value) { + pose.wind = value; } override protected bool Global (PhysicsConstraintData constraint) { @@ -2619,22 +2678,18 @@ namespace Spine { } } - /// Changes a physics constraint's . + /// Changes a physics constraint's . public class PhysicsConstraintGravityTimeline : PhysicsConstraintTimeline { - public PhysicsConstraintGravityTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) - : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintGravity) { + public PhysicsConstraintGravityTimeline (int frameCount, int bezierCount, int constraintIndex) + : base(frameCount, bezierCount, constraintIndex, Property.PhysicsConstraintGravity) { } - override protected float Setup (PhysicsConstraint constraint) { - return constraint.data.gravity; + override protected float Get (PhysicsConstraintPose pose) { + return pose.gravity; } - override protected float Get (PhysicsConstraint constraint) { - return constraint.gravity; - } - - override protected void Set (PhysicsConstraint constraint, float value) { - constraint.gravity = value; + override protected void Set (PhysicsConstraintPose pose, float value) { + pose.gravity = value; } override protected bool Global (PhysicsConstraintData constraint) { @@ -2642,22 +2697,18 @@ namespace Spine { } } - /// Changes a physics constraint's . + /// Changes a physics constraint's . public class PhysicsConstraintMixTimeline : PhysicsConstraintTimeline { - public PhysicsConstraintMixTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) - : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintMix) { + public PhysicsConstraintMixTimeline (int frameCount, int bezierCount, int constraintIndex) + : base(frameCount, bezierCount, constraintIndex, Property.PhysicsConstraintMix) { } - override protected float Setup (PhysicsConstraint constraint) { - return constraint.data.mix; + override protected float Get (PhysicsConstraintPose pose) { + return pose.mix; } - override protected float Get (PhysicsConstraint constraint) { - return constraint.mix; - } - - override protected void Set (PhysicsConstraint constraint, float value) { - constraint.mix = value; + override protected void Set (PhysicsConstraintPose pose, float value) { + pose.mix = value; } override protected bool Global (PhysicsConstraintData constraint) { @@ -2666,20 +2717,20 @@ namespace Spine { } /// Resets a physics constraint when specific animation times are reached. - public class PhysicsConstraintResetTimeline : Timeline { + public class PhysicsConstraintResetTimeline : Timeline, IConstraintTimeline { static readonly string[] propertyIds = { ((int)Property.PhysicsConstraintReset).ToString() }; readonly int constraintIndex; - /// -1 for all physics constraints in the skeleton. - public PhysicsConstraintResetTimeline (int frameCount, int physicsConstraintIndex) + /// -1 for all physics constraints in the skeleton. + public PhysicsConstraintResetTimeline (int frameCount, int constraintIndex) : base(frameCount, propertyIds) { - constraintIndex = physicsConstraintIndex; + this.constraintIndex = constraintIndex; } - /// The index of the physics constraint in that will be reset when this timeline is + /// The index of the physics constraint in that will be reset when this timeline is /// applied, or -1 if all physics constraints in the skeleton will be reset. - public int PhysicsConstraintIndex { + public int ConstraintIndex { get { return constraintIndex; } @@ -2697,18 +2748,18 @@ namespace Spine { /// Resets the physics constraint when frames > lastTime and <= time. override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, bool appliedPose) { PhysicsConstraint constraint = null; if (constraintIndex != -1) { - constraint = skeleton.physicsConstraints.Items[constraintIndex]; + constraint = (PhysicsConstraint)skeleton.constraints.Items[constraintIndex]; if (!constraint.active) return; } float[] frames = this.frames; if (lastTime > time) { // Apply after lastTime for looped animations. - Apply(skeleton, lastTime, int.MaxValue, null, alpha, blend, direction); + Apply(skeleton, lastTime, int.MaxValue, null, alpha, blend, direction, appliedPose); lastTime = -1f; } else if (lastTime >= frames[frames.Length - 1]) // Last time is after last frame. return; @@ -2716,120 +2767,55 @@ namespace Spine { if (lastTime < frames[0] || time >= frames[Search(frames, lastTime) + 1]) { if (constraint != null) - constraint.Reset(); + constraint.Reset(skeleton); else { - PhysicsConstraint[] constraints = skeleton.physicsConstraints.Items; - for (int i = 0, n = skeleton.physicsConstraints.Count; i < n; i++) { - constraint = (PhysicsConstraint)constraints[i]; - if (constraint.active) constraint.Reset(); + PhysicsConstraint[] constraints = skeleton.physics.Items; + for (int i = 0, n = skeleton.physics.Count; i < n; i++) { + constraint = constraints[i]; + if (constraint.active) constraint.Reset(skeleton); } } } } } - - /// Changes a slot's for an attachment's . - public class SequenceTimeline : Timeline, ISlotTimeline { - public const int ENTRIES = 3; - private const int MODE = 1, DELAY = 2; - - readonly int slotIndex; - readonly IHasTextureRegion attachment; - - public SequenceTimeline (int frameCount, int slotIndex, Attachment attachment) - : base(frameCount, (int)Property.Sequence + "|" + slotIndex + "|" + ((IHasTextureRegion)attachment).Sequence.Id) { - this.slotIndex = slotIndex; - this.attachment = (IHasTextureRegion)attachment; + /// + /// Changes a slider's . + /// + public class SliderTimeline : ConstraintTimeline1 { + public SliderTimeline (int frameCount, int bezierCount, int constraintIndex) + : base(frameCount, bezierCount, constraintIndex, Property.SliderTime) { } - public override int FrameEntries { - get { return ENTRIES; } - } + /// May be null. + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction, bool appliedPose) { - public int SlotIndex { - get { - return slotIndex; + var constraint = (Slider)skeleton.constraints.Items[constraintIndex]; + if (constraint.active) { + SliderPose pose = appliedPose ? constraint.applied : constraint.pose; + pose.time = GetAbsoluteValue(time, alpha, blend, pose.time, constraint.data.setup.time); } } - public Attachment Attachment { - get { - return (Attachment)attachment; - } + } + + /// + /// Changes a slider's . + /// + public class SliderMixTimeline : ConstraintTimeline1 { + public SliderMixTimeline (int frameCount, int bezierCount, int constraintIndex) + : base(frameCount, bezierCount, constraintIndex, Property.SliderMix) { } - /// Sets the time, mode, index, and frame time for the specified frame. - /// Between 0 and frameCount, inclusive. - /// Seconds between frames. - public void SetFrame (int frame, float time, SequenceMode mode, int index, float delay) { - frame *= ENTRIES; - frames[frame] = time; - frames[frame + MODE] = (int)mode | (index << 4); - frames[frame + DELAY] = delay; - } + /// May be null. + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction, bool appliedPose) { - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - Attachment slotAttachment = slot.attachment; - if (slotAttachment != attachment) { - VertexAttachment vertexAttachment = slotAttachment as VertexAttachment; - if ((vertexAttachment == null) - || vertexAttachment.TimelineAttachment != attachment) return; + var constraint = (Slider)skeleton.constraints.Items[constraintIndex]; + if (constraint.active) { + SliderPose pose = appliedPose ? constraint.applied : constraint.pose; + pose.mix = GetAbsoluteValue(time, alpha, blend, pose.mix, constraint.data.setup.mix); } - Sequence sequence = ((IHasTextureRegion)slotAttachment).Sequence; - if (sequence == null) return; - - if (direction == MixDirection.Out) { - if (blend == MixBlend.Setup) slot.SequenceIndex = -1; - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { - if (blend == MixBlend.Setup || blend == MixBlend.First) slot.SequenceIndex = -1; - return; - } - - int i = Search(frames, time, ENTRIES); - float before = frames[i]; - int modeAndIndex = (int)frames[i + MODE]; - float delay = frames[i + DELAY]; - - int index = modeAndIndex >> 4, count = sequence.Regions.Length; - SequenceMode mode = (SequenceMode)(modeAndIndex & 0xf); - if (mode != SequenceMode.Hold) { - index += (int)((time - before) / delay + 0.0001f); - switch (mode) { - case SequenceMode.Once: - index = Math.Min(count - 1, index); - break; - case SequenceMode.Loop: - index %= count; - break; - case SequenceMode.Pingpong: { - int n = (count << 1) - 2; - index = n == 0 ? 0 : index % n; - if (index >= count) index = n - index; - break; - } - case SequenceMode.OnceReverse: - index = Math.Max(count - 1 - index, 0); - break; - case SequenceMode.LoopReverse: - index = count - 1 - (index % count); - break; - case SequenceMode.PingpongReverse: { - int n = (count << 1) - 2; - index = n == 0 ? 0 : (index + count - 1) % n; - if (index >= count) index = n - index; - break; - } // end case - } - } - slot.SequenceIndex = index; } } } diff --git a/spine-csharp/src/AnimationState.cs b/spine-csharp/src/AnimationState.cs index 35ef162ff..ab9ac7be0 100644 --- a/spine-csharp/src/AnimationState.cs +++ b/spine-csharp/src/AnimationState.cs @@ -264,8 +264,10 @@ namespace Spine { Timeline timeline = timelines[ii]; if (timeline is AttachmentTimeline) ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, attachments); - else - timeline.Apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection.In); + else { + timeline.Apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection.In, + false); + } } } else { int[] timelineMode = current.timelineMode.Items; @@ -285,7 +287,7 @@ namespace Spine { else if (timeline is AttachmentTimeline) ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, attachments); else - timeline.Apply(skeleton, animationLast, applyTime, applyEvents, alpha, timelineBlend, MixDirection.In); + timeline.Apply(skeleton, animationLast, applyTime, applyEvents, alpha, timelineBlend, MixDirection.In, false); } } QueueEvents(current, animationTime); @@ -303,7 +305,7 @@ namespace Spine { Slot slot = slots[i]; if (slot.attachmentState == setupState) { string attachmentName = slot.data.attachmentName; - slot.Attachment = (attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName)); + slot.pose.Attachment = (attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName)); } } unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot. @@ -339,7 +341,7 @@ namespace Spine { for (int ii = 0; ii < timelineCount; ii++) { Timeline timeline = timelines[ii]; if (timeline is EventTimeline) - timeline.Apply(skeleton, animationLast, animationTime, events, 1.0f, MixBlend.Setup, MixDirection.In); + timeline.Apply(skeleton, animationLast, animationTime, events, 1.0f, MixBlend.Setup, MixDirection.In, false); } QueueEvents(current, animationTime); events.Clear(false); @@ -381,7 +383,7 @@ namespace Spine { if (blend == MixBlend.Add) { for (int i = 0; i < timelineCount; i++) - timelines[i].Apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.Out); + timelines[i].Apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.Out, false); } else { int[] timelineMode = from.timelineMode.Items; TrackEntry[] timelineHoldMix = from.timelineHoldMix.Items; @@ -432,7 +434,7 @@ namespace Spine { } else { if (drawOrder && timeline is DrawOrderTimeline && timelineBlend == MixBlend.Setup) direction = MixDirection.In; - timeline.Apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction); + timeline.Apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction, false); } } } @@ -472,7 +474,7 @@ namespace Spine { for (int i = 0; i < timelineCount; i++) { Timeline timeline = timelines[i]; if (timeline is EventTimeline) - timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, 0, MixBlend.Setup, MixDirection.Out); + timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, 0, MixBlend.Setup, MixDirection.Out, false); } if (to.mixDuration > 0) QueueEvents(from, animationTime); @@ -506,7 +508,7 @@ namespace Spine { } private void SetAttachment (Skeleton skeleton, Slot slot, String attachmentName, bool attachments) { - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName); + slot.pose.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName); if (attachments) slot.attachmentState = unkeyedState + Current; } @@ -519,30 +521,30 @@ namespace Spine { if (firstFrame) timelinesRotation[i] = 0; if (alpha == 1) { - timeline.Apply(skeleton, 0, time, null, 1, blend, MixDirection.In); + timeline.Apply(skeleton, 0, time, null, 1, blend, MixDirection.In, false); return; } Bone bone = skeleton.bones.Items[timeline.BoneIndex]; if (!bone.active) return; - + BoneLocal pose = bone.pose, setup = bone.data.setup; float[] frames = timeline.frames; float r1, r2; if (time < frames[0]) { // Time is before first frame. switch (blend) { case MixBlend.Setup: - bone.rotation = bone.data.rotation; + pose.rotation = setup.rotation; goto default; // Fall through. default: return; case MixBlend.First: - r1 = bone.rotation; - r2 = bone.data.rotation; + r1 = pose.rotation; + r2 = setup.rotation; break; } } else { - r1 = blend == MixBlend.Setup ? bone.data.rotation : bone.rotation; - r2 = bone.data.rotation + timeline.GetCurveValue(time); + r1 = blend == MixBlend.Setup ? setup.rotation : pose.rotation; + r2 = setup.rotation + timeline.GetCurveValue(time); } // Mix between rotations using the direction of the shortest route on the first frame. @@ -575,7 +577,7 @@ namespace Spine { timelinesRotation[i] = total; } timelinesRotation[i + 1] = diff; - bone.rotation = r1 + total * alpha; + pose.rotation = r1 + total * alpha; } private void QueueEvents (TrackEntry entry, float animationTime) { diff --git a/spine-csharp/src/Attachments/IHasTextureRegion.cs b/spine-csharp/src/Attachments/IHasTextureRegion.cs index 8ef943b1c..658f1dfd4 100644 --- a/spine-csharp/src/Attachments/IHasTextureRegion.cs +++ b/spine-csharp/src/Attachments/IHasTextureRegion.cs @@ -27,10 +27,15 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Text; +#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) +#define IS_UNITY +#endif namespace Spine { +#if IS_UNITY + using Color = UnityEngine.Color; +#endif + public interface IHasTextureRegion { /// The name used to find the string Path { get; set; } @@ -46,10 +51,9 @@ namespace Spine { /// void UpdateRegion (); - float R { get; set; } - float G { get; set; } - float B { get; set; } - float A { get; set; } + public abstract Color GetColor (); + public abstract void SetColor (Color color); + public abstract void SetColor (float r, float g, float b, float a); Sequence Sequence { get; set; } } diff --git a/spine-csharp/src/Attachments/MeshAttachment.cs b/spine-csharp/src/Attachments/MeshAttachment.cs index 85a9685f8..2dc5e74fa 100644 --- a/spine-csharp/src/Attachments/MeshAttachment.cs +++ b/spine-csharp/src/Attachments/MeshAttachment.cs @@ -27,16 +27,28 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) +#define IS_UNITY +#endif + using System; namespace Spine { +#if IS_UNITY + using Color = UnityEngine.Color; +#endif + /// Attachment that displays a texture region using a mesh. public class MeshAttachment : VertexAttachment, IHasTextureRegion { internal TextureRegion region; internal string path; internal float[] regionUVs, uvs; internal int[] triangles; - internal float r = 1, g = 1, b = 1, a = 1; + // Color is a struct, set to protected to prevent + // Color color = slot.color; color.a = 0.5; + // modifying just a copy of the struct instead of the original + // object as in reference implementation. + protected Color color = new Color(1, 1, 1, 1); internal int hullLength; private MeshAttachment parentMesh; private Sequence sequence; @@ -55,10 +67,17 @@ namespace Spine { public float[] UVs { get { return uvs; } set { uvs = value; } } public int[] Triangles { get { return triangles; } set { triangles = 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; } } - public float A { get { return a; } set { a = value; } } + public Color GetColor () { + return color; + } + + public void SetColor (Color color) { + this.color = color; + } + + public void SetColor (float r, float g, float b, float a) { + color = new Color(r, g, b, a); + } public string Path { get { return path; } set { path = value; } } public Sequence Sequence { get { return sequence; } set { sequence = value; } } @@ -98,10 +117,7 @@ namespace Spine { region = other.region; path = other.path; - r = other.r; - g = other.g; - b = other.b; - a = other.a; + color = other.color; regionUVs = new float[other.regionUVs.Length]; Array.Copy(other.regionUVs, 0, regionUVs, 0, regionUVs.Length); @@ -192,9 +208,10 @@ namespace Spine { } /// If the attachment has a , the region may be changed. - override public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) { - if (sequence != null) sequence.Apply(slot, this); - base.ComputeWorldVertices(slot, start, count, worldVertices, offset, stride); + override public void ComputeWorldVertices (Skeleton skeleton, Slot slot, int start, int count, float[] worldVertices, int offset, + int stride = 2) { + if (sequence != null) sequence.Apply(slot.AppliedPose, this); + base.ComputeWorldVertices(skeleton, slot, start, count, worldVertices, offset, stride); } /// Returns a new mesh with this mesh set as the . @@ -204,10 +221,7 @@ namespace Spine { mesh.timelineAttachment = timelineAttachment; mesh.region = region; mesh.path = path; - mesh.r = r; - mesh.g = g; - mesh.b = b; - mesh.a = a; + mesh.color = color; mesh.ParentMesh = parentMesh != null ? parentMesh : this; if (mesh.Region != null) mesh.UpdateRegion(); return mesh; diff --git a/spine-csharp/src/Attachments/PointAttachment.cs b/spine-csharp/src/Attachments/PointAttachment.cs index fa8bad636..9473b73a9 100644 --- a/spine-csharp/src/Attachments/PointAttachment.cs +++ b/spine-csharp/src/Attachments/PointAttachment.cs @@ -55,11 +55,11 @@ namespace Spine { rotation = other.rotation; } - public void ComputeWorldPosition (Bone bone, out float ox, out float oy) { + public void ComputeWorldPosition (BonePose bone, out float ox, out float oy) { bone.LocalToWorld(this.x, this.y, out ox, out oy); } - public float ComputeWorldRotation (Bone bone) { + public float ComputeWorldRotation (BonePose bone) { float r = rotation * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r); float x = cos * bone.a + sin * bone.b; float y = cos * bone.c + sin * bone.d; diff --git a/spine-csharp/src/Attachments/RegionAttachment.cs b/spine-csharp/src/Attachments/RegionAttachment.cs index 8d3e89637..caba05296 100644 --- a/spine-csharp/src/Attachments/RegionAttachment.cs +++ b/spine-csharp/src/Attachments/RegionAttachment.cs @@ -27,9 +27,17 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) +#define IS_UNITY +#endif + using System; namespace Spine { +#if IS_UNITY + using Color = UnityEngine.Color; +#endif + /// Attachment that displays a texture region. public class RegionAttachment : Attachment, IHasTextureRegion { public const int BLX = 0, BLY = 1; @@ -40,7 +48,11 @@ namespace Spine { internal TextureRegion region; internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; internal float[] offset = new float[8], uvs = new float[8]; - internal float r = 1, g = 1, b = 1, a = 1; + // Color is a struct, set to protected to prevent + // Color color = slot.color; color.a = 0.5; + // modifying just a copy of the struct instead of the original + // object as in reference implementation. + protected Color color = new Color(1, 1, 1, 1); internal Sequence sequence; public float X { get { return x; } set { x = value; } } @@ -51,10 +63,17 @@ namespace Spine { public float Width { get { return width; } set { width = value; } } public float Height { get { return height; } set { height = 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; } } - public float A { get { return a; } set { a = value; } } + public Color GetColor () { + return color; + } + + public void SetColor (Color color) { + this.color = color; + } + + public void SetColor (float r, float g, float b, float a) { + color = new Color(r, g, b, a); + } public string Path { get; set; } public TextureRegion Region { get { return region; } set { region = value; } } @@ -83,10 +102,7 @@ namespace Spine { height = other.height; Array.Copy(other.uvs, 0, uvs, 0, 8); Array.Copy(other.offset, 0, offset, 0, 8); - r = other.r; - g = other.g; - b = other.b; - a = other.a; + color = other.color; sequence = other.sequence == null ? null : new Sequence(other.sequence); } @@ -179,10 +195,10 @@ namespace Spine { /// The worldVertices index to begin writing values. /// The number of worldVertices entries between the value pairs written. public void ComputeWorldVertices (Slot slot, float[] worldVertices, int offset, int stride = 2) { - if (sequence != null) sequence.Apply(slot, this); + if (sequence != null) sequence.Apply(slot.AppliedPose, this); float[] vertexOffset = this.offset; - Bone bone = slot.Bone; + BonePose bone = slot.Bone.AppliedPose; float bwx = bone.worldX, bwy = bone.worldY; float a = bone.a, b = bone.b, c = bone.c, d = bone.d; float offsetX, offsetY; diff --git a/spine-csharp/src/Attachments/Sequence.cs b/spine-csharp/src/Attachments/Sequence.cs index 81d148588..b4228fe13 100644 --- a/spine-csharp/src/Attachments/Sequence.cs +++ b/spine-csharp/src/Attachments/Sequence.cs @@ -67,7 +67,7 @@ namespace Spine { setupIndex = other.setupIndex; } - public void Apply (Slot slot, IHasTextureRegion attachment) { + public void Apply (SlotPose slot, IHasTextureRegion attachment) { int index = slot.SequenceIndex; if (index == -1) index = setupIndex; if (index >= regions.Length) index = regions.Length - 1; diff --git a/spine-csharp/src/Attachments/VertexAttachment.cs b/spine-csharp/src/Attachments/VertexAttachment.cs index 72879ba2d..3ef87d942 100644 --- a/spine-csharp/src/Attachments/VertexAttachment.cs +++ b/spine-csharp/src/Attachments/VertexAttachment.cs @@ -31,7 +31,7 @@ using System; namespace Spine { /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's - /// . + /// . public abstract class VertexAttachment : Attachment { static int nextID = 0; static readonly Object nextIdLock = new Object(); @@ -83,13 +83,13 @@ namespace Spine { worldVerticesLength = other.worldVerticesLength; } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); + public void ComputeWorldVertices (Skeleton skeleton, Slot slot, float[] worldVertices) { + ComputeWorldVertices(skeleton, slot, 0, worldVerticesLength, worldVertices, 0); } /// - /// Transforms the attachment's local to world coordinates. If the slot's is - /// not empty, it is used to deform the vertices. + /// Transforms the attachment's local to world coordinates. If the slot's + /// is not empty, it is used to deform the vertices. /// /// See World transforms in the Spine /// Runtimes Guide. @@ -99,14 +99,14 @@ namespace Spine { /// The output world vertices. Must have a length greater than or equal to + . /// The index to begin writing values. /// The number of entries between the value pairs written. - public virtual void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) { + public virtual void ComputeWorldVertices (Skeleton skeleton, Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) { count = offset + (count >> 1) * stride; - ExposedList deformArray = slot.deform; + ExposedList deformArray = slot.AppliedPose.deform; float[] vertices = this.vertices; int[] bones = this.bones; if (bones == null) { if (deformArray.Count > 0) vertices = deformArray.Items; - Bone bone = slot.bone; + BonePose bone = slot.bone.AppliedPose; float x = bone.worldX, y = bone.worldY; float a = bone.a, b = bone.b, c = bone.c, d = bone.d; for (int vv = start, w = offset; w < count; vv += 2, w += stride) { @@ -122,14 +122,14 @@ namespace Spine { v += n + 1; skip += n; } - Bone[] skeletonBones = slot.bone.skeleton.bones.Items; + Bone[] skeletonBones = skeleton.bones.Items; if (deformArray.Count == 0) { for (int w = offset, b = skip * 3; w < count; w += stride) { float wx = 0, wy = 0; int n = bones[v++]; n += v; for (; v < n; v++, b += 3) { - Bone bone = skeletonBones[bones[v]]; + BonePose bone = skeletonBones[bones[v]].AppliedPose; float vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2]; wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight; wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight; @@ -144,7 +144,7 @@ namespace Spine { int n = bones[v++]; n += v; for (; v < n; v++, b += 3, f += 2) { - Bone bone = skeletonBones[bones[v]]; + BonePose bone = skeletonBones[bones[v]].AppliedPose; float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2]; wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight; wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight; diff --git a/spine-csharp/src/Bone.cs b/spine-csharp/src/Bone.cs index f838c5f22..2e8265a8f 100644 --- a/spine-csharp/src/Bone.cs +++ b/spine-csharp/src/Bone.cs @@ -30,429 +30,40 @@ using System; namespace Spine { - using Physics = Skeleton.Physics; /// - /// Stores a bone's current pose. + /// The current pose for a bone, before constraints are applied. /// /// A bone has a local transform which is used to compute its world transform. A bone also has an applied transform, which is a /// local transform that can be applied to compute the world transform. The local transform and applied transform may differ if a /// constraint or application code modifies the world transform after it was computed from the local transform. /// /// - public class Bone : IUpdatable { + public class Bone : PosedActive { static public bool yDown; - internal BoneData data; - internal Skeleton skeleton; internal Bone parent; - internal ExposedList children = new ExposedList(); - internal float x, y, rotation, scaleX, scaleY, shearX, shearY; - internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; + internal ExposedList children = new ExposedList(4); + + internal bool sorted; - internal float a, b, worldX; - internal float c, d, worldY; - internal Inherit inherit; + public Bone (BoneData data, Bone parent) + : base(data, new BonePose(), new BonePose()) { + this.parent = parent; + applied.bone = this; + constrained.bone = this; + } - internal bool sorted, active; + /// + /// Copy constructor. Does not copy the bones. + /// + public Bone (Bone bone, Bone parent) + : this(bone.data, parent) { + pose.Set(bone.pose); + } - public BoneData Data { get { return data; } } - public Skeleton Skeleton { get { return skeleton; } } public Bone Parent { get { return parent; } } public ExposedList Children { get { return children; } } - public bool Active { get { return active; } } - /// The local X translation. - public float X { get { return x; } set { x = value; } } - /// The local Y translation. - public float Y { get { return y; } set { y = value; } } - /// The local rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// The local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// The local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// The local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// The local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// Controls how parent world transforms affect this bone. - public Inherit Inherit { get { return inherit; } set { inherit = value; } } - - /// The rotation, as calculated by any constraints. - public float AppliedRotation { get { return arotation; } set { arotation = value; } } - - /// The applied local x translation. - public float AX { get { return ax; } set { ax = value; } } - - /// The applied local y translation. - public float AY { get { return ay; } set { ay = value; } } - - /// The applied local scaleX. - public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } - - /// The applied local scaleY. - public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } - - /// The applied local shearX. - public float AShearX { get { return ashearX; } set { ashearX = value; } } - - /// The applied local shearY. - public float AShearY { get { return ashearY; } set { ashearY = value; } } - - /// Part of the world transform matrix for the X axis. If changed, should be called. - public float A { get { return a; } set { a = value; } } - /// Part of the world transform matrix for the Y axis. If changed, should be called. - public float B { get { return b; } set { b = value; } } - /// Part of the world transform matrix for the X axis. If changed, should be called. - public float C { get { return c; } set { c = value; } } - /// Part of the world transform matrix for the Y axis. If changed, should be called. - public float D { get { return d; } set { d = value; } } - - /// The world X position. If changed, should be called. - public float WorldX { get { return worldX; } set { worldX = value; } } - /// The world Y position. If changed, should be called. - public float WorldY { get { return worldY; } set { worldY = value; } } - /// The world rotation for the X axis, calculated using and . - public float WorldRotationX { get { return MathUtils.Atan2Deg(c, a); } } - /// The world rotation for the Y axis, calculated using and . - public float WorldRotationY { get { return MathUtils.Atan2Deg(d, b); } } - - /// Returns the magnitide (always positive) of the world scale X. - public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } - /// Returns the magnitide (always positive) of the world scale Y. - public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } - - public Bone (BoneData data, Skeleton skeleton, Bone parent) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - this.skeleton = skeleton; - this.parent = parent; - SetToSetupPose(); - } - - /// Copy constructor. Does not copy the bones. - /// May be null. - public Bone (Bone bone, Skeleton skeleton, Bone parent) { - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.skeleton = skeleton; - this.parent = parent; - data = bone.data; - x = bone.x; - y = bone.y; - rotation = bone.rotation; - scaleX = bone.scaleX; - scaleY = bone.scaleY; - shearX = bone.shearX; - shearY = bone.shearY; - inherit = bone.inherit; - } - - /// Computes the world transform using the parent bone and this bone's local applied transform. - public void Update (Physics physics) { - UpdateWorldTransform(ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY); - } - - /// Computes the world transform using the parent bone and this bone's local transform. - public void UpdateWorldTransform () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } - - /// Computes the world transform using the parent bone and the specified local transform. The applied transform is set to the - /// specified local transform. Child bones are not updated. - /// - /// See World transforms in the Spine - /// Runtimes Guide. - public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { - ax = x; - ay = y; - arotation = rotation; - ascaleX = scaleX; - ascaleY = scaleY; - ashearX = shearX; - ashearY = shearY; - - Bone parent = this.parent; - if (parent == null) { // Root bone. - Skeleton skeleton = this.skeleton; - float sx = skeleton.scaleX, sy = skeleton.ScaleY; - float rx = (rotation + shearX) * MathUtils.DegRad; - float ry = (rotation + 90 + shearY) * MathUtils.DegRad; - a = (float)Math.Cos(rx) * scaleX * sx; - b = (float)Math.Cos(ry) * scaleY * sx; - c = (float)Math.Sin(rx) * scaleX * sy; - d = (float)Math.Sin(ry) * scaleY * sy; - worldX = x * sx + skeleton.x; - worldY = y * sy + skeleton.y; - return; - } - - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; - worldX = pa * x + pb * y + parent.worldX; - worldY = pc * x + pd * y + parent.worldY; - - switch (inherit) { - case Inherit.Normal: { - float rx = (rotation + shearX) * MathUtils.DegRad; - float ry = (rotation + 90 + shearY) * MathUtils.DegRad; - float la = (float)Math.Cos(rx) * scaleX; - float lb = (float)Math.Cos(ry) * scaleY; - float lc = (float)Math.Sin(rx) * scaleX; - float ld = (float)Math.Sin(ry) * scaleY; - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - return; - } - case Inherit.OnlyTranslation: { - float rx = (rotation + shearX) * MathUtils.DegRad; - float ry = (rotation + 90 + shearY) * MathUtils.DegRad; - a = (float)Math.Cos(rx) * scaleX; - b = (float)Math.Cos(ry) * scaleY; - c = (float)Math.Sin(rx) * scaleX; - d = (float)Math.Sin(ry) * scaleY; - break; - } - case Inherit.NoRotationOrReflection: { - float sx = 1 / skeleton.scaleX, sy = 1 / skeleton.ScaleY; - pa *= sx; - pc *= sy; - float s = pa * pa + pc * pc, prx; - if (s > 0.0001f) { - s = Math.Abs(pa * pd * sy - pb * sx * pc) / s; - pb = pc * s; - pd = pa * s; - prx = MathUtils.Atan2Deg(pc, pa); - } else { - pa = 0; - pc = 0; - prx = 90 - MathUtils.Atan2Deg(pd, pb); - } - float rx = (rotation + shearX - prx) * MathUtils.DegRad; - float ry = (rotation + shearY - prx + 90) * MathUtils.DegRad; - float la = (float)Math.Cos(rx) * scaleX; - float lb = (float)Math.Cos(ry) * scaleY; - float lc = (float)Math.Sin(rx) * scaleX; - float ld = (float)Math.Sin(ry) * scaleY; - a = pa * la - pb * lc; - b = pa * lb - pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - break; - } - case Inherit.NoScale: - case Inherit.NoScaleOrReflection: { - rotation *= MathUtils.DegRad; - float cos = (float)Math.Cos(rotation), sin = (float)Math.Sin(rotation); - float za = (pa * cos + pb * sin) / skeleton.scaleX; - float zc = (pc * cos + pd * sin) / skeleton.ScaleY; - float s = (float)Math.Sqrt(za * za + zc * zc); - if (s > 0.00001f) s = 1 / s; - za *= s; - zc *= s; - s = (float)Math.Sqrt(za * za + zc * zc); - if (inherit == Inherit.NoScale && (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.ScaleY < 0)) s = -s; - rotation = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); - float zb = (float)Math.Cos(rotation) * s; - float zd = (float)Math.Sin(rotation) * s; - shearX *= MathUtils.DegRad; - shearY = (90 + shearY) * MathUtils.DegRad; - float la = (float)Math.Cos(shearX) * scaleX; - float lb = (float)Math.Cos(shearY) * scaleY; - float lc = (float)Math.Sin(shearX) * scaleX; - float ld = (float)Math.Sin(shearY) * scaleY; - a = za * la + zb * lc; - b = za * lb + zb * ld; - c = zc * la + zd * lc; - d = zc * lb + zd * ld; - break; - } - } - a *= skeleton.scaleX; - b *= skeleton.scaleX; - c *= skeleton.ScaleY; - d *= skeleton.ScaleY; - } - - /// Sets this bone's local transform to the setup pose. - public void SetToSetupPose () { - BoneData data = this.data; - x = data.x; - y = data.y; - rotation = data.rotation; - scaleX = data.scaleX; - scaleY = data.ScaleY; - shearX = data.shearX; - shearY = data.shearY; - inherit = data.inherit; - } - - /// - /// Computes the applied transform values from the world transform. - /// - /// If the world transform is modified (by a constraint, , etc) then this method should be called so - /// the applied transform matches the world transform. The applied transform may be needed by other code (eg to apply another - /// constraint). - /// - /// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after - /// calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. - /// - public void UpdateAppliedTransform () { - Bone parent = this.parent; - if (parent == null) { - ax = worldX - skeleton.x; - ay = worldY - skeleton.y; - float a = this.a, b = this.b, c = this.c, d = this.d; - arotation = MathUtils.Atan2Deg(c, a); - ascaleX = (float)Math.Sqrt(a * a + c * c); - ascaleY = (float)Math.Sqrt(b * b + d * d); - ashearX = 0; - ashearY = MathUtils.Atan2Deg(a * b + c * d, a * d - b * c); - return; - } - - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; - float pid = 1 / (pa * pd - pb * pc); - float ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid; - float dx = worldX - parent.worldX, dy = worldY - parent.worldY; - ax = (dx * ia - dy * ib); - ay = (dy * id - dx * ic); - - float ra, rb, rc, rd; - if (inherit == Inherit.OnlyTranslation) { - ra = a; - rb = b; - rc = c; - rd = d; - } else { - switch (inherit) { - case Inherit.NoRotationOrReflection: { - float s = Math.Abs(pa * pd - pb * pc) / (pa * pa + pc * pc); - float skeletonScaleY = skeleton.ScaleY; - pb = -pc * skeleton.scaleX * s / skeletonScaleY; - pd = pa * skeletonScaleY * s / skeleton.scaleX; - pid = 1 / (pa * pd - pb * pc); - ia = pd * pid; - ib = pb * pid; - break; - } - case Inherit.NoScale: - case Inherit.NoScaleOrReflection: { - float r = rotation * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r); - pa = (pa * cos + pb * sin) / skeleton.scaleX; - pc = (pc * cos + pd * sin) / skeleton.ScaleY; - float s = (float)Math.Sqrt(pa * pa + pc * pc); - if (s > 0.00001f) s = 1 / s; - pa *= s; - pc *= s; - s = (float)Math.Sqrt(pa * pa + pc * pc); - if (inherit == Inherit.NoScale && pid < 0 != (skeleton.scaleX < 0 != skeleton.ScaleY < 0)) s = -s; - r = MathUtils.PI / 2 + MathUtils.Atan2(pc, pa); - pb = (float)Math.Cos(r) * s; - pd = (float)Math.Sin(r) * s; - pid = 1 / (pa * pd - pb * pc); - ia = pd * pid; - ib = pb * pid; - ic = pc * pid; - id = pa * pid; - break; - } - } - ra = ia * a - ib * c; - rb = ia * b - ib * d; - rc = id * c - ic * a; - rd = id * d - ic * b; - } - - ashearX = 0; - ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); - if (ascaleX > 0.0001f) { - float det = ra * rd - rb * rc; - ascaleY = det / ascaleX; - ashearY = -MathUtils.Atan2Deg(ra * rb + rc * rd, det); - arotation = MathUtils.Atan2Deg(rc, ra); - } else { - ascaleX = 0; - ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); - ashearY = 0; - arotation = 90 - MathUtils.Atan2Deg(rd, rb); - } - } - - /// Transforms a point from world coordinates to the bone's local coordinates. - 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 det = a * d - b * c; - float x = worldX - this.worldX, y = worldY - this.worldY; - localX = (x * d - y * b) / det; - localY = (y * a - x * c) / det; - } - - /// Transforms a point from the bone's local coordinates to world coordinates. - public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) { - worldX = localX * a + localY * b + this.worldX; - worldY = localX * c + localY * d + this.worldY; - } - - /// Transforms a point from world coordinates to the parent bone's local coordinates. - public void WorldToParent (float worldX, float worldY, out float parentX, out float parentY) { - if (parent == null) { - parentX = worldX; - parentY = worldY; - } else { - parent.WorldToLocal(worldX, worldY, out parentX, out parentY); - } - } - - /// Transforms a point from the parent bone's coordinates to world coordinates. - public void ParentToWorld (float parentX, float parentY, out float worldX, out float worldY) { - if (parent == null) { - worldX = parentX; - worldY = parentY; - } else { - parent.LocalToWorld(parentX, parentY, out worldX, out worldY); - } - } - - /// Transforms a world rotation to a local rotation. - public float WorldToLocalRotation (float worldRotation) { - worldRotation *= MathUtils.DegRad; - float sin = (float)Math.Sin(worldRotation), cos = (float)Math.Cos(worldRotation); - return MathUtils.Atan2Deg(a * sin - c * cos, d * cos - b * sin) + rotation - shearX; - } - - /// Transforms a local rotation to a world rotation. - public float LocalToWorldRotation (float localRotation) { - localRotation = (localRotation - rotation - shearX) * MathUtils.DegRad; - float sin = (float)Math.Sin(localRotation), cos = (float)Math.Cos(localRotation); - return MathUtils.Atan2Deg(cos * c + sin * d, cos * a + sin * b); - } - - /// - /// Rotates the world transform the specified amount. - /// - /// After changes are made to the world transform, should be called and - /// will need to be called on any child bones, recursively. - /// - public void RotateWorld (float degrees) { - degrees *= MathUtils.DegRad; - float sin = (float)Math.Sin(degrees), cos = (float)Math.Cos(degrees); - float ra = a, rb = b; - a = cos * ra - sin * c; - b = cos * rb - sin * d; - c = sin * ra + cos * c; - d = sin * rb + cos * d; - } - - override public string ToString () { - return data.name; - } + } } diff --git a/spine-csharp/src/BoneData.cs b/spine-csharp/src/BoneData.cs index 556caa02e..6a31cfc2b 100644 --- a/spine-csharp/src/BoneData.cs +++ b/spine-csharp/src/BoneData.cs @@ -30,69 +30,45 @@ using System; namespace Spine { - public class BoneData { + + /// + /// The setup pose for a bone. + /// + public class BoneData : PosedData { internal int index; - internal string name; internal BoneData parent; internal float length; - internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; - internal Inherit inherit = Inherit.Normal; - internal bool skinRequired; + + /// May be null. + public BoneData (int index, string name, BoneData parent) + : base(name, new BoneLocal()) { + + if (index < 0) throw new ArgumentException("index must be >= 0", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.index = index; + this.parent = parent; + } + + /// Copy constructor. + /// May be null. + public BoneData (BoneData data, BoneData parent) + : this(data.index, data.name, parent) { + length = data.length; + setup.Set(data.setup); + } /// The index of the bone in Skeleton.Bones public int Index { get { return index; } } - /// The name of the bone, which is unique across all bones in the skeleton. - public string Name { get { return name; } } - /// May be null. public BoneData Parent { get { return parent; } } public float Length { get { return length; } set { length = value; } } - - /// Local X translation. - public float X { get { return x; } set { x = value; } } - - /// Local Y translation. - public float Y { get { return y; } set { y = value; } } - - /// Local rotation in degrees, counter clockwise. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// Local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// Local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// Local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// Local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// Determines how parent world transforms affect this bone. - public Inherit Inherit { get { return inherit; } set { inherit = value; } } - - /// When true, only updates this bone if the contains - /// this bone. - /// - public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } - - /// May be null. - public BoneData (int index, string name, BoneData parent) { - if (index < 0) throw new ArgumentException("index must be >= 0", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.index = index; - this.name = name; - this.parent = parent; - } - - override public string ToString () { - return name; - } } + /// + /// Determines how a bone inherits world transforms from parent bones. + /// public enum Inherit { Normal, OnlyTranslation, diff --git a/spine-csharp/src/BoneLocal.cs b/spine-csharp/src/BoneLocal.cs new file mode 100644 index 000000000..4569cd982 --- /dev/null +++ b/spine-csharp/src/BoneLocal.cs @@ -0,0 +1,74 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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; + +namespace Spine { + /// + /// Stores a bone's local pose. + /// + public class BoneLocal : IPose { + internal float x, y, rotation, scaleX, scaleY, shearX, shearY; + internal Inherit inherit; + + public void Set (BoneLocal pose) { + if (pose == null) throw new ArgumentNullException("pose", "pose cannot be null."); + x = pose.x; + y = pose.y; + rotation = pose.rotation; + scaleX = pose.scaleX; + scaleY = pose.scaleY; + shearX = pose.shearX; + shearY = pose.shearY; + inherit = pose.inherit; + } + + /// The local X translation. + public float X { get { return x; } set { x = value; } } + /// The local Y translation. + public float Y { get { return y; } set { y = value; } } + /// The local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// The local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// The local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// The local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// The local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// Determines how parent world transforms affect this bone. + public Inherit Inherit { get { return inherit; } set { inherit = value; } } + } +} diff --git a/spine-csharp/src/BoneLocal.cs.meta b/spine-csharp/src/BoneLocal.cs.meta new file mode 100644 index 000000000..6135c9e19 --- /dev/null +++ b/spine-csharp/src/BoneLocal.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 94686e11f12e69248ae7322de74373c9 \ No newline at end of file diff --git a/spine-csharp/src/BonePose.cs b/spine-csharp/src/BonePose.cs new file mode 100644 index 000000000..da421aebd --- /dev/null +++ b/spine-csharp/src/BonePose.cs @@ -0,0 +1,381 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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; + +namespace Spine { + + /// + /// The applied pose for a bone. This is the pose with constraints applied and the world transform computed by + /// . + /// + public class BonePose : BoneLocal, IUpdate { + public Bone bone; + internal float a, b, worldX; + internal float c, d, worldY; + internal int world, local; + + /// + /// Called by to compute the world transform, if needed. + /// + public void Update (Skeleton skeleton, Physics physics) { + if (world != skeleton.update) UpdateWorldTransform(skeleton); + } + + /// Computes the world transform using the parent bone's applied pose and this pose. Child bones are not updated. + /// + /// See World transforms in the Spine + /// Runtimes Guide. + public void UpdateWorldTransform (Skeleton skeleton) { + if (local == skeleton.update) + UpdateLocalTransform(skeleton); + else + world = skeleton.update; + + if (bone.parent == null) { // Root bone. + float sx = skeleton.scaleX, sy = skeleton.ScaleY; + float rx = (rotation + shearX) * MathUtils.DegRad; + float ry = (rotation + 90 + shearY) * MathUtils.DegRad; + a = (float)Math.Cos(rx) * scaleX * sx; + b = (float)Math.Cos(ry) * scaleY * sx; + c = (float)Math.Sin(rx) * scaleX * sy; + d = (float)Math.Sin(ry) * scaleY * sy; + worldX = x * sx + skeleton.x; + worldY = y * sy + skeleton.y; + return; + } + + BonePose parent = bone.parent.applied; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; + worldX = pa * x + pb * y + parent.worldX; + worldY = pc * x + pd * y + parent.worldY; + + switch (inherit) { + case Inherit.Normal: { + float rx = (rotation + shearX) * MathUtils.DegRad; + float ry = (rotation + 90 + shearY) * MathUtils.DegRad; + float la = (float)Math.Cos(rx) * scaleX; + float lb = (float)Math.Cos(ry) * scaleY; + float lc = (float)Math.Sin(rx) * scaleX; + float ld = (float)Math.Sin(ry) * scaleY; + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + return; + } + case Inherit.OnlyTranslation: { + float rx = (rotation + shearX) * MathUtils.DegRad; + float ry = (rotation + 90 + shearY) * MathUtils.DegRad; + a = (float)Math.Cos(rx) * scaleX; + b = (float)Math.Cos(ry) * scaleY; + c = (float)Math.Sin(rx) * scaleX; + d = (float)Math.Sin(ry) * scaleY; + break; + } + case Inherit.NoRotationOrReflection: { + float sx = 1 / skeleton.scaleX, sy = 1 / skeleton.ScaleY; + pa *= sx; + pc *= sy; + float s = pa * pa + pc * pc, prx; + if (s > 0.0001f) { + s = Math.Abs(pa * pd * sy - pb * sx * pc) / s; + pb = pc * s; + pd = pa * s; + prx = MathUtils.Atan2Deg(pc, pa); + } else { + pa = 0; + pc = 0; + prx = 90 - MathUtils.Atan2Deg(pd, pb); + } + float rx = (rotation + shearX - prx) * MathUtils.DegRad; + float ry = (rotation + shearY - prx + 90) * MathUtils.DegRad; + float la = (float)Math.Cos(rx) * scaleX; + float lb = (float)Math.Cos(ry) * scaleY; + float lc = (float)Math.Sin(rx) * scaleX; + float ld = (float)Math.Sin(ry) * scaleY; + a = pa * la - pb * lc; + b = pa * lb - pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + break; + } + case Inherit.NoScale: + case Inherit.NoScaleOrReflection: { + rotation *= MathUtils.DegRad; + float cos = (float)Math.Cos(rotation), sin = (float)Math.Sin(rotation); + float za = (pa * cos + pb * sin) / skeleton.scaleX; + float zc = (pc * cos + pd * sin) / skeleton.ScaleY; + float s = (float)Math.Sqrt(za * za + zc * zc); + if (s > 0.00001f) s = 1 / s; + za *= s; + zc *= s; + s = (float)Math.Sqrt(za * za + zc * zc); + if (inherit == Inherit.NoScale && (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.ScaleY < 0)) s = -s; + rotation = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); + float zb = (float)Math.Cos(rotation) * s; + float zd = (float)Math.Sin(rotation) * s; + shearX *= MathUtils.DegRad; + shearY = (90 + shearY) * MathUtils.DegRad; + float la = (float)Math.Cos(shearX) * scaleX; + float lb = (float)Math.Cos(shearY) * scaleY; + float lc = (float)Math.Sin(shearX) * scaleX; + float ld = (float)Math.Sin(shearY) * scaleY; + a = za * la + zb * lc; + b = za * lb + zb * ld; + c = zc * la + zd * lc; + d = zc * lb + zd * ld; + break; + } + } + a *= skeleton.scaleX; + b *= skeleton.scaleX; + c *= skeleton.ScaleY; + d *= skeleton.ScaleY; + } + + /// + /// Computes the local transform values from the world transform. + /// + /// If the world transform is modified (by a constraint, , etc) then this method should be called so + /// the local transform matches the world transform. The local transform may be needed by other code (eg to apply another + /// constraint). + /// + /// Some information is ambiguous in the world transform, such as - 1,-1 scale versus 180 rotation.The local transform after + /// calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. + /// + public void UpdateLocalTransform (Skeleton skeleton) { + local = 0; + world = skeleton.update; + + if (bone.parent == null) { + x = worldX - skeleton.x; + y = worldY - skeleton.y; + float a = this.a, b = this.b, c = this.c, d = this.d; + rotation = MathUtils.Atan2Deg(c, a); + scaleX = (float)Math.Sqrt(a * a + c * c); + scaleY = (float)Math.Sqrt(b * b + d * d); + shearX = 0; + shearY = MathUtils.Atan2Deg(a * b + c * d, a * d - b * c); + return; + } + + BonePose parent = bone.parent.applied; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; + float pid = 1 / (pa * pd - pb * pc); + float ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid; + float dx = worldX - parent.worldX, dy = worldY - parent.worldY; + x = (dx * ia - dy * ib); + y = (dy * id - dx * ic); + + float ra, rb, rc, rd; + if (inherit == Inherit.OnlyTranslation) { + ra = a; + rb = b; + rc = c; + rd = d; + } else { + switch (inherit) { + case Inherit.NoRotationOrReflection: { + float s = Math.Abs(pa * pd - pb * pc) / (pa * pa + pc * pc); + pb = -pc * skeleton.scaleX * s / skeleton.ScaleY; + pd = pa * skeleton.ScaleY * s / skeleton.scaleX; + pid = 1 / (pa * pd - pb * pc); + ia = pd * pid; + ib = pb * pid; + break; + } + case Inherit.NoScale: + case Inherit.NoScaleOrReflection: { + float r = rotation * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r); + pa = (pa * cos + pb * sin) / skeleton.scaleX; + pc = (pc * cos + pd * sin) / skeleton.ScaleY; + float s = (float)Math.Sqrt(pa * pa + pc * pc); + if (s > 0.00001f) s = 1 / s; + pa *= s; + pc *= s; + s = (float)Math.Sqrt(pa * pa + pc * pc); + if (inherit == Inherit.NoScale && pid < 0 != (skeleton.scaleX < 0 != skeleton.ScaleY < 0)) s = -s; + r = MathUtils.PI / 2 + MathUtils.Atan2(pc, pa); + pb = (float)Math.Cos(r) * s; + pd = (float)Math.Sin(r) * s; + pid = 1 / (pa * pd - pb * pc); + ia = pd * pid; + ib = pb * pid; + ic = pc * pid; + id = pa * pid; + break; + } + } + ra = ia * a - ib * c; + rb = ia * b - ib * d; + rc = id * c - ic * a; + rd = id * d - ic * b; + } + + shearX = 0; + scaleX = (float)Math.Sqrt(ra * ra + rc * rc); + if (scaleX > 0.0001f) { + float det = ra * rd - rb * rc; + scaleY = det / scaleX; + shearY = -MathUtils.Atan2Deg(ra * rb + rc * rd, det); + rotation = MathUtils.Atan2Deg(rc, ra); + } else { + scaleX = 0; + scaleY = (float)Math.Sqrt(rb * rb + rd * rd); + shearY = 0; + rotation = 90 - MathUtils.Atan2Deg(rd, rb); + } + } + + /// + /// If the world transform has been modified and the local transform no longer matches, + /// is called. + /// + public void ValidateLocalTransform (Skeleton skeleton) { + if (local == skeleton.update) UpdateLocalTransform(skeleton); + } + + internal void ModifyLocal (Skeleton skeleton) { + if (local == skeleton.update) UpdateLocalTransform(skeleton); + world = 0; + ResetWorld(skeleton.update); + } + + internal void ModifyWorld (int update) { + local = update; + world = update; + ResetWorld(update); + } + + internal void ResetWorld (int update) { + Bone[] children = bone.children.Items; + for (int i = 0, n = bone.children.Count; i < n; i++) { + BonePose child = children[i].applied; + if (child.world == update) { + child.world = 0; + child.local = 0; + child.ResetWorld(update); + } + } + } + + /// Part of the world transform matrix for the X axis. If changed, should be called. + public float A { get { return a; } set { a = value; } } + /// Part of the world transform matrix for the Y axis. If changed, should be called. + public float B { get { return b; } set { b = value; } } + /// Part of the world transform matrix for the X axis. If changed, should be called. + public float C { get { return c; } set { c = value; } } + /// Part of the world transform matrix for the Y axis. If changed, should be called. + public float D { get { return d; } set { d = value; } } + + /// The world X position. If changed, should be called. + public float WorldX { get { return worldX; } set { worldX = value; } } + /// The world Y position. If changed, should be called. + public float WorldY { get { return worldY; } set { worldY = value; } } + /// The world rotation for the X axis, calculated using and . + public float WorldRotationX { get { return MathUtils.Atan2Deg(c, a); } } + /// The world rotation for the Y axis, calculated using and . + public float WorldRotationY { get { return MathUtils.Atan2Deg(d, b); } } + + /// Returns the magnitude (always positive) of the world scale X, calculated using and . + public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } + /// Returns the magnitude (always positive) of the world scale Y, calculated using and . + public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } + + /// Transforms a point from world coordinates to the bone's local coordinates. + 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 det = a * d - b * c; + float x = worldX - this.worldX, y = worldY - this.worldY; + localX = (x * d - y * b) / det; + localY = (y * a - x * c) / det; + } + + /// Transforms a point from the bone's local coordinates to world coordinates. + public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) { + worldX = localX * a + localY * b + this.worldX; + worldY = localX * c + localY * d + this.worldY; + } + + /// Transforms a point from world coordinates to the parent bone's local coordinates. + public void WorldToParent (float worldX, float worldY, out float parentX, out float parentY) { + if (bone.parent == null) { + parentX = worldX; + parentY = worldY; + } else { + bone.parent.applied.WorldToLocal(worldX, worldY, out parentX, out parentY); + } + } + + /// Transforms a point from the parent bone's coordinates to world coordinates. + public void ParentToWorld (float parentX, float parentY, out float worldX, out float worldY) { + if (bone.parent == null) { + worldX = parentX; + worldY = parentY; + } else { + bone.parent.applied.LocalToWorld(parentX, parentY, out worldX, out worldY); + } + } + + /// Transforms a world rotation to a local rotation. + public float WorldToLocalRotation (float worldRotation) { + worldRotation *= MathUtils.DegRad; + float sin = (float)Math.Sin(worldRotation), cos = (float)Math.Cos(worldRotation); + return MathUtils.Atan2Deg(a * sin - c * cos, d * cos - b * sin) + rotation - shearX; + } + + /// Transforms a local rotation to a world rotation. + public float LocalToWorldRotation (float localRotation) { + localRotation = (localRotation - rotation - shearX) * MathUtils.DegRad; + float sin = (float)Math.Sin(localRotation), cos = (float)Math.Cos(localRotation); + return MathUtils.Atan2Deg(cos * c + sin * d, cos * a + sin * b); + } + + /// + /// Rotates the world transform the specified amount. + /// + /// After changes are made to the world transform, should be called on this bone and any + /// child bones, recursively. + /// + public void RotateWorld (float degrees) { + degrees *= MathUtils.DegRad; + float sin = (float)Math.Sin(degrees), cos = (float)Math.Cos(degrees); + float ra = a, rb = b; + a = cos * ra - sin * c; + b = cos * rb - sin * d; + c = sin * ra + cos * c; + d = sin * rb + cos * d; + } + + override public string ToString () { + return bone.data.name; + } + } +} diff --git a/spine-csharp/src/BonePose.cs.meta b/spine-csharp/src/BonePose.cs.meta new file mode 100644 index 000000000..94693336c --- /dev/null +++ b/spine-csharp/src/BonePose.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 024ad131f32a77e45bc91fc1599a8e0f \ No newline at end of file diff --git a/spine-csharp/src/Color.cs b/spine-csharp/src/Color.cs new file mode 100644 index 000000000..4796fe5dd --- /dev/null +++ b/spine-csharp/src/Color.cs @@ -0,0 +1,86 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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. + *****************************************************************************/ + +#if UNITY_5_3_OR_NEWER +#define IS_UNITY +#endif + +namespace Spine { +#if IS_UNITY + using Color = UnityEngine.Color; +#else + public struct Color { + public float r, g, b, a; + + public Color (float r, float g, float b, float a = 1.0f) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + public override string ToString () { + return string.Format("RGBA({0}, {1}, {2}, {3})", r, g, b, a); + } + + public static Color operator + (Color c1, Color c2) { + return new Color(c1.r + c2.r, c1.g + c2.g, c1.b + c2.b, c1.a + c2.a); + } + + public static Color operator - (Color c1, Color c2) { + return new Color(c1.r - c2.r, c1.g - c2.g, c1.b - c2.b, c1.a - c2.a); + } + } +#endif + + static class ColorExtensions { + public static Color Clamp (this Color color) { + color.r = MathUtils.Clamp(color.r, 0, 1); + color.g = MathUtils.Clamp(color.g, 0, 1); + color.b = MathUtils.Clamp(color.b, 0, 1); + color.a = MathUtils.Clamp(color.a, 0, 1); + return color; + } + + public static Color RGBA8888ToColor(this uint rgba8888) { + float r = ((rgba8888 & 0xff000000) >> 24) / 255f; + float g = ((rgba8888 & 0x00ff0000) >> 16) / 255f; + float b = ((rgba8888 & 0x0000ff00) >> 8) / 255f; + float a = ((rgba8888 & 0x000000ff)) / 255f; + return new Color(r, g, b, a); + } + + public static Color XRGB888ToColor (this uint xrgb888) { + float r = ((xrgb888 & 0x00ff0000) >> 16) / 255f; + float g = ((xrgb888 & 0x0000ff00) >> 8) / 255f; + float b = ((xrgb888 & 0x000000ff)) / 255f; + return new Color(r, g, b); + } + } +} diff --git a/spine-csharp/src/Color.cs.meta b/spine-csharp/src/Color.cs.meta new file mode 100644 index 000000000..213549e4e --- /dev/null +++ b/spine-csharp/src/Color.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 445e5522c6d723a4bb98798781daa7d0 \ No newline at end of file diff --git a/spine-csharp/src/Constraint.cs b/spine-csharp/src/Constraint.cs new file mode 100644 index 000000000..4ef69edea --- /dev/null +++ b/spine-csharp/src/Constraint.cs @@ -0,0 +1,64 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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; + +namespace Spine { + public interface IConstraint : IPosedActive, IPosed { + IConstraintData IData { get; } + IConstraint Copy (Skeleton skeleton); + bool IsSourceActive { get; } + void Sort (Skeleton skeleton); + void Update (Skeleton skeleton, Physics physics); + } + + /// + /// + /// Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of + /// the last bone is as close to the target bone as possible. + /// + /// See IK constraints in the Spine User Guide. + /// + public abstract class Constraint : PosedActive, IUpdate, IConstraint + where T : Constraint + where D : ConstraintData + where P : IPose

{ + + public Constraint (D data, P pose, P constrained) + : base(data, pose, constrained) { + } + + public D Data { get { return data; } } + public IConstraintData IData { get { return data; } } + abstract public IConstraint Copy (Skeleton skeleton); + abstract public void Sort (Skeleton skeleton); + public virtual bool IsSourceActive { get { return true; } } + abstract public void Update (Skeleton skeleton, Physics physics); + } +} diff --git a/spine-csharp/src/Constraint.cs.meta b/spine-csharp/src/Constraint.cs.meta new file mode 100644 index 000000000..94386f870 --- /dev/null +++ b/spine-csharp/src/Constraint.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 66115e19768098240ace7f8ad9cb4d4c \ No newline at end of file diff --git a/spine-csharp/src/ConstraintData.cs b/spine-csharp/src/ConstraintData.cs index 242804c6a..16c42fe58 100644 --- a/spine-csharp/src/ConstraintData.cs +++ b/spine-csharp/src/ConstraintData.cs @@ -31,31 +31,19 @@ using System; using System.Collections.Generic; namespace Spine { - ///

The base class for all constraint datas. - public abstract class ConstraintData { - internal readonly string name; - internal int order; - internal bool skinRequired; - public ConstraintData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; + public interface IConstraintData : IPosedData { + public string Name { get; } + public IConstraint Create (Skeleton skeleton); + } + + public abstract class ConstraintData : PosedData

, IConstraintData + where T : IConstraint + where P : IPose

{ + public ConstraintData (string name, P setup) + : base(name, setup) { } - ///

The constraint's name, which is unique across all constraints in the skeleton of the same type. - public string Name { get { return name; } } - - /// The ordinal of this constraint for the order a skeleton's constraints will be applied by - /// . - public int Order { get { return order; } set { order = value; } } - - /// When true, only updates this constraint if the - /// contains this constraint. - /// - public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } - - override public string ToString () { - return name; - } + abstract public IConstraint Create (Skeleton skeleton); } } diff --git a/spine-csharp/src/Event.cs b/spine-csharp/src/Event.cs index 15f804fa1..f204103f0 100644 --- a/spine-csharp/src/Event.cs +++ b/spine-csharp/src/Event.cs @@ -40,6 +40,7 @@ namespace Spine { internal float volume; internal float balance; + /// The event's setup pose data. public EventData Data { get { return data; } } /// The animation time this event was keyed. public float Time { get { return time; } } diff --git a/spine-csharp/src/IPose.cs b/spine-csharp/src/IPose.cs new file mode 100644 index 000000000..6a652e47e --- /dev/null +++ b/spine-csharp/src/IPose.cs @@ -0,0 +1,36 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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; + +namespace Spine { + public interface IPose

{ + public void Set (P pose); + } +} diff --git a/spine-csharp/src/IPose.cs.meta b/spine-csharp/src/IPose.cs.meta new file mode 100644 index 000000000..24e0d14d2 --- /dev/null +++ b/spine-csharp/src/IPose.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 114be1b77921d6645ac5974d5b33f2eb \ No newline at end of file diff --git a/spine-csharp/src/IUpdatable.cs b/spine-csharp/src/IUpdate.cs similarity index 77% rename from spine-csharp/src/IUpdatable.cs rename to spine-csharp/src/IUpdate.cs index 866f7974e..bc82cca37 100644 --- a/spine-csharp/src/IUpdatable.cs +++ b/spine-csharp/src/IUpdate.cs @@ -28,20 +28,10 @@ *****************************************************************************/ namespace Spine { - using Physics = Skeleton.Physics; ///

The interface for items updated by . - public interface IUpdatable { + public interface IUpdate { /// Determines how physics and other non-deterministic updates are applied. - void Update (Physics physics); - - /// Returns false when this item won't be updated by - /// because a skin is required and the - /// active skin does not contain this item. - /// - /// - /// - /// - bool Active { get; } + void Update (Skeleton skeleton, Physics physics); } } diff --git a/spine-csharp/src/IUpdatable.cs.meta b/spine-csharp/src/IUpdate.cs.meta similarity index 100% rename from spine-csharp/src/IUpdatable.cs.meta rename to spine-csharp/src/IUpdate.cs.meta diff --git a/spine-csharp/src/IkConstraint.cs b/spine-csharp/src/IkConstraint.cs index cbc9af4bf..68bbb8a41 100644 --- a/spine-csharp/src/IkConstraint.cs +++ b/spine-csharp/src/IkConstraint.cs @@ -30,7 +30,6 @@ using System; namespace Spine { - using Physics = Skeleton.Physics; /// /// @@ -39,69 +38,59 @@ namespace Spine { /// /// See IK constraints in the Spine User Guide. /// - public class IkConstraint : IUpdatable { - internal readonly IkConstraintData data; - internal readonly ExposedList bones = new ExposedList(); + public class IkConstraint : Constraint { + internal readonly ExposedList bones; internal Bone target; - internal int bendDirection; - internal bool compress, stretch; - internal float mix = 1, softness; - internal bool active; + public IkConstraint (IkConstraintData data, Skeleton skeleton) + : base(data, new IkConstraintPose(), new IkConstraintPose()) { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - public IkConstraint (IkConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - - bones = new ExposedList(data.bones.Count); + bones = new ExposedList(data.bones.Count); foreach (BoneData boneData in data.bones) - bones.Add(skeleton.bones.Items[boneData.index]); + bones.Add(skeleton.bones.Items[boneData.index].constrained); target = skeleton.bones.Items[data.target.index]; - - mix = data.mix; - softness = data.softness; - bendDirection = data.bendDirection; - compress = data.compress; - stretch = data.stretch; } - /// Copy constructor. - public IkConstraint (IkConstraint constraint, Skeleton skeleton) - : this(constraint.data, skeleton) { - - mix = constraint.mix; - softness = constraint.softness; - bendDirection = constraint.bendDirection; - compress = constraint.compress; - stretch = constraint.stretch; + override public IConstraint Copy (Skeleton skeleton) { + var copy = new IkConstraint(data, skeleton); + copy.pose.Set(pose); + return copy; } - public void SetToSetupPose () { - IkConstraintData data = this.data; - mix = data.mix; - softness = data.softness; - bendDirection = data.bendDirection; - compress = data.compress; - stretch = data.stretch; - } - - public void Update (Physics physics) { - if (mix == 0) return; - Bone target = this.target; - Bone[] bones = this.bones.Items; + /// Applies the constraint to the constrained bones. + override public void Update (Skeleton skeleton, Physics physics) { + IkConstraintPose p = applied; + if (p.mix == 0) return; + BonePose target = this.target.applied; + BonePose[] bones = this.bones.Items; switch (this.bones.Count) { case 1: - Apply(bones[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix); + Apply(skeleton, bones[0], target.worldX, target.worldY, p.compress, p.stretch, data.uniform, p.mix); break; case 2: - Apply(bones[0], bones[1], target.worldX, target.worldY, bendDirection, stretch, data.uniform, softness, mix); + Apply(skeleton, bones[0], bones[1], target.worldX, target.worldY, p.bendDirection, p.stretch, data.uniform, + p.softness, p.mix); break; } } + override public void Sort (Skeleton skeleton) { + skeleton.SortBone(target); + Bone parent = bones.Items[0].bone; + skeleton.SortBone(parent); + skeleton.updateCache.Add(this); + parent.sorted = false; + skeleton.SortReset(parent.children); + skeleton.Constrained(parent); + if (bones.Count > 1) skeleton.Constrained(bones.Items[1].bone); + } + + override public bool IsSourceActive { get { return target.active; } } + /// The bones that will be modified by this IK constraint. - public ExposedList Bones { + public ExposedList Bones { get { return bones; } } @@ -111,79 +100,26 @@ namespace Spine { set { target = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - /// - /// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. - /// - public float Mix { - get { return mix; } - set { mix = value; } - } - - /// For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones - /// will not straighten completely until the target is this far out of range. - public float Softness { - get { return softness; } - set { softness = value; } - } - - /// For two bone IK, controls the bend direction of the IK bones, either 1 or -1. - public int BendDirection { - get { return bendDirection; } - set { bendDirection = value; } - } - - /// For one bone IK, when true and the target is too close, the bone is scaled to reach it. - public bool Compress { - get { return compress; } - set { compress = value; } - } - - /// When true and the target is out of range, the parent bone is scaled to reach it. - /// - /// For two bone IK: 1) the child bone's local Y translation is set to 0, - /// 2) stretch is not applied if is > 0, - /// and 3) if the parent bone has local nonuniform scale, stretch is not applied. - /// - public bool Stretch { - get { return stretch; } - set { stretch = value; } - } - - public bool Active { - get { return active; } - } - - /// The IK constraint's setup pose data. - public IkConstraintData Data { - get { return data; } - } - - override public string ToString () { - return data.name; - } - /// Applies 1 bone IK. The target is specified in the world coordinate system. - static public void Apply (Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, - float alpha) { + static public void Apply (Skeleton skeleton, BonePose bone, float targetX, float targetY, bool compress, bool stretch, + bool uniform, float mix) { if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - Bone p = bone.parent; + bone.ModifyLocal(skeleton); + BonePose p = bone.bone.parent.applied; float pa = p.a, pb = p.b, pc = p.c, pd = p.d; - float rotationIK = -bone.ashearX - bone.arotation; - float tx = 0, ty = 0; - + float rotationIK = -bone.shearX - bone.rotation, tx, ty; switch (bone.inherit) { case Inherit.OnlyTranslation: - tx = (targetX - bone.worldX) * Math.Sign(bone.skeleton.ScaleX); - ty = (targetY - bone.worldY) * Math.Sign(bone.skeleton.ScaleY); + tx = (targetX - bone.worldX) * Math.Sign(skeleton.ScaleX); + ty = (targetY - bone.worldY) * Math.Sign(skeleton.ScaleY); break; case Inherit.NoRotationOrReflection: { float s = Math.Abs(pa * pd - pb * pc) / Math.Max(0.0001f, pa * pa + pc * pc); - float sa = pa / bone.skeleton.scaleX; - float sc = pc / bone.skeleton.ScaleY; - pb = -sc * s * bone.skeleton.scaleX; - pd = sa * s * bone.skeleton.ScaleY; + float sa = pa / skeleton.scaleX; + float sc = pc / skeleton.ScaleY; + pb = -sc * s * skeleton.scaleX; + pd = sa * s * skeleton.ScaleY; rotationIK += MathUtils.Atan2Deg(sc, sa); goto default; // Fall through. } @@ -194,21 +130,20 @@ namespace Spine { tx = 0; ty = 0; } else { - tx = (x * pd - y * pb) / d - bone.ax; - ty = (y * pa - x * pc) / d - bone.ay; + tx = (x * pd - y * pb) / d - bone.x; + ty = (y * pa - x * pc) / d - bone.y; } break; } } rotationIK += MathUtils.Atan2Deg(ty, tx); - if (bone.ascaleX < 0) rotationIK += 180; + if (bone.scaleX < 0) rotationIK += 180; if (rotationIK > 180) rotationIK -= 360; else if (rotationIK < -180) // rotationIK += 360; - - float sx = bone.ascaleX, sy = bone.ascaleY; + bone.rotation += rotationIK * mix; if (compress || stretch) { switch (bone.inherit) { case Inherit.NoScale: @@ -217,27 +152,28 @@ namespace Spine { ty = targetY - bone.worldY; break; } - float b = bone.data.length * sx; + float b = bone.bone.data.length * bone.scaleX; if (b > 0.0001f) { float dd = tx * tx + ty * ty; if ((compress && dd < b * b) || (stretch && dd > b * b)) { - float s = ((float)Math.Sqrt(dd) / b - 1) * alpha + 1; - sx *= s; - if (uniform) sy *= s; + float s = ((float)Math.Sqrt(dd) / b - 1) * mix + 1; + bone.scaleX *= s; + if (uniform) bone.scaleY *= s; } } } - bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX, bone.ashearY); } /// Applies 2 bone IK. The target is specified in the world coordinate system. /// A direct descendant of the parent bone. - static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, bool uniform, - float softness, float alpha) { + static public void Apply (Skeleton skeleton, BonePose parent, BonePose child, float targetX, float targetY, int bendDir, + bool stretch, bool uniform, float softness, float mix) { if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null."); if (child == null) throw new ArgumentNullException("child", "child cannot be null."); if (parent.inherit != Inherit.Normal || child.inherit != Inherit.Normal) return; - float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, sx = psx, sy = psy, csx = child.ascaleX; + parent.ModifyLocal(skeleton); + child.ModifyLocal(skeleton); + float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX; int os1, os2, s2; if (psx < 0) { psx = -psx; @@ -256,18 +192,17 @@ namespace Spine { os2 = 180; } else os2 = 0; - float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; + float cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; bool u = Math.Abs(psx - psy) <= 0.0001f; if (!u || stretch) { - cy = 0; - cwx = a * cx + parent.worldX; - cwy = c * cx + parent.worldY; + child.y = 0; + cwx = a * child.x + parent.worldX; + cwy = c * child.x + parent.worldY; } else { - cy = child.ay; - cwx = a * cx + b * cy + parent.worldX; - cwy = c * cx + d * cy + parent.worldY; + cwx = a * child.x + b * child.y + parent.worldX; + cwy = c * child.x + d * child.y + parent.worldY; } - Bone pp = parent.parent; + BonePose pp = parent.bone.parent.applied; a = pp.a; b = pp.b; c = pp.c; @@ -275,10 +210,10 @@ namespace Spine { float id = a * d - b * c, x = cwx - pp.worldX, y = cwy - pp.worldY; id = Math.Abs(id) <= 0.0001f ? 0 : 1 / id; float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; - float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; + float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.bone.data.length * csx, a1, a2; if (l1 < 0.0001f) { - Apply(parent, targetX, targetY, false, stretch, false, alpha); - child.UpdateWorldTransform(cx, cy, 0, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); + Apply(skeleton, parent, targetX, targetY, false, stretch, false, mix); + child.rotation = 0; return; } x = targetX - pp.worldX; @@ -306,9 +241,9 @@ namespace Spine { cos = 1; a2 = 0; if (stretch) { - a = ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1; - sx *= a; - if (uniform) sy *= a; + a = ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * mix + 1; + parent.scaleX *= a; + if (uniform) parent.scaleY *= a; } } else a2 = (float)Math.Acos(cos) * bendDir; @@ -366,21 +301,19 @@ namespace Spine { } } break_outer: - float os = (float)Math.Atan2(cy, cx) * s2; - float rotation = parent.arotation; - a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; + float os = (float)Math.Atan2(child.y, child.x) * s2; + a1 = (a1 - os) * MathUtils.RadDeg + os1 - parent.rotation; if (a1 > 180) a1 -= 360; else if (a1 < -180) a1 += 360; - parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, sx, sy, 0, 0); - rotation = child.arotation; - a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; + parent.rotation += a1 * mix; + a2 = ((a2 + os) * MathUtils.RadDeg - child.shearX) * s2 + os2 - child.rotation; if (a2 > 180) a2 -= 360; else if (a2 < -180) a2 += 360; - child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); + child.rotation += a2 * mix; } } } diff --git a/spine-csharp/src/IkConstraintData.cs b/spine-csharp/src/IkConstraintData.cs index c0f5be11d..5488d4082 100644 --- a/spine-csharp/src/IkConstraintData.cs +++ b/spine-csharp/src/IkConstraintData.cs @@ -32,14 +32,17 @@ using System.Collections.Generic; namespace Spine { /// Stores the setup pose for an IkConstraint. - public class IkConstraintData : ConstraintData { - internal ExposedList bones = new ExposedList(); + public class IkConstraintData : ConstraintData { + internal ExposedList bones = new ExposedList(2); internal BoneData target; - internal int bendDirection; - internal bool compress, stretch, uniform; - internal float mix, softness; + internal bool uniform; - public IkConstraintData (string name) : base(name) { + public IkConstraintData (string name) + : base(name, new IkConstraintPose()) { + } + + override public IConstraint Create (Skeleton skeleton) { + return new IkConstraint(this, skeleton); } /// The bones that are constrained by this IK Constraint. @@ -54,46 +57,8 @@ namespace Spine { } /// - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - /// - /// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. - /// - public float Mix { - get { return mix; } - set { mix = value; } - } - - /// For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones - /// will not straighten completely until the target is this far out of range. - public float Softness { - get { return softness; } - set { softness = value; } - } - - /// For two bone IK, controls the bend direction of the IK bones, either 1 or -1. - public int BendDirection { - get { return bendDirection; } - set { bendDirection = value; } - } - - /// For one bone IK, when true and the target is too close, the bone is scaled to reach it. - public bool Compress { - get { return compress; } - set { compress = value; } - } - - /// When true and the target is out of range, the parent bone is scaled to reach it. - /// - /// For two bone IK: 1) the child bone's local Y translation is set to 0, - /// 2) stretch is not applied if is > 0, - /// and 3) if the parent bone has local nonuniform scale, stretch is not applied. - public bool Stretch { - get { return stretch; } - set { stretch = value; } - } - - /// - /// When true and or is used, the bone is scaled on both the X and Y axes. + /// When true and or is used, the bone is scaled + /// on both the X and Y axes. /// public bool Uniform { get { return uniform; } diff --git a/spine-csharp/src/IkConstraintPose.cs b/spine-csharp/src/IkConstraintPose.cs new file mode 100644 index 000000000..dba54d8d5 --- /dev/null +++ b/spine-csharp/src/IkConstraintPose.cs @@ -0,0 +1,87 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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; + +namespace Spine { + + /// Stores the current pose for an IK constraint. + public class IkConstraintPose : IPose { + internal int bendDirection; + internal bool compress, stretch; + internal float mix = 1, softness; + + public void Set (IkConstraintPose pose) { + mix = pose.mix; + softness = pose.softness; + bendDirection = pose.bendDirection; + compress = pose.compress; + stretch = pose.stretch; + } + + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + /// + /// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. + /// + public float Mix { + get { return mix; } + set { mix = value; } + } + + /// For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones + /// will not straighten completely until the target is this far out of range. + public float Softness { + get { return softness; } + set { softness = value; } + } + + /// For two bone IK, controls the bend direction of the IK bones, either 1 or -1. + public int BendDirection { + get { return bendDirection; } + set { bendDirection = value; } + } + + /// For one bone IK, when true and the target is too close, the bone is scaled to reach it. + public bool Compress { + get { return compress; } + set { compress = value; } + } + + /// When true and the target is out of range, the parent bone is scaled to reach it. + /// + /// For two bone IK: 1) the child bone's local Y translation is set to 0, + /// 2) stretch is not applied if is > 0, + /// and 3) if the parent bone has local nonuniform scale, stretch is not applied. + /// + public bool Stretch { + get { return stretch; } + set { stretch = value; } + } + } +} diff --git a/spine-csharp/src/IkConstraintPose.cs.meta b/spine-csharp/src/IkConstraintPose.cs.meta new file mode 100644 index 000000000..328d89d0c --- /dev/null +++ b/spine-csharp/src/IkConstraintPose.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f7b8113cda0e502458c2084ec31e0cca \ No newline at end of file diff --git a/spine-csharp/src/MathUtils.cs b/spine-csharp/src/MathUtils.cs index 0c875d3ea..3eb9214d8 100644 --- a/spine-csharp/src/MathUtils.cs +++ b/spine-csharp/src/MathUtils.cs @@ -137,6 +137,12 @@ namespace Spine { return value; } + static public float Clamp01 (float value) { + if (value < 0) return 0; + if (value > 1) return 1; + return value; + } + static public float RandomTriangle (float min, float max) { return RandomTriangle(min, max, (min + max) * 0.5f); } diff --git a/spine-csharp/src/PathConstraint.cs b/spine-csharp/src/PathConstraint.cs index 1f21c3fbb..b13f0390b 100644 --- a/spine-csharp/src/PathConstraint.cs +++ b/spine-csharp/src/PathConstraint.cs @@ -30,7 +30,6 @@ using System; namespace Spine { - using Physics = Skeleton.Physics; /// /// @@ -39,48 +38,32 @@ namespace Spine { /// /// See Path constraints in the Spine User Guide. /// - public class PathConstraint : IUpdatable { + public class PathConstraint : Constraint { const int NONE = -1, BEFORE = -2, AFTER = -3; const float Epsilon = 0.00001f; - internal readonly PathConstraintData data; - internal readonly ExposedList bones; + internal readonly ExposedList bones; internal Slot slot; - internal float position, spacing, mixRotate, mixX, mixY; - - internal bool active; internal readonly ExposedList spaces = new ExposedList(), positions = new ExposedList(); internal readonly ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); internal readonly float[] segments = new float[10]; - public PathConstraint (PathConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + public PathConstraint (PathConstraintData data, Skeleton skeleton) + : base(data, new PathConstraintPose (), new PathConstraintPose ()) { if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - bones = new ExposedList(data.Bones.Count); + bones = new ExposedList(data.Bones.Count); foreach (BoneData boneData in data.bones) - bones.Add(skeleton.bones.Items[boneData.index]); + bones.Add(skeleton.bones.Items[boneData.index].constrained); slot = skeleton.slots.Items[data.slot.index]; - - position = data.position; - spacing = data.spacing; - mixRotate = data.mixRotate; - mixX = data.mixX; - mixY = data.mixY; } - /// Copy constructor. - public PathConstraint (PathConstraint constraint, Skeleton skeleton) - : this(constraint.data, skeleton) { - - position = constraint.position; - spacing = constraint.spacing; - mixRotate = constraint.mixRotate; - mixX = constraint.mixX; - mixY = constraint.mixY; + override public IConstraint Copy (Skeleton skeleton) { + var copy = new PathConstraint(data, skeleton); + copy.pose.Set(pose); + return copy; } public static void ArraysFill (float[] a, int fromIndex, int toIndex, float val) { @@ -88,34 +71,26 @@ namespace Spine { a[i] = val; } - public void SetToSetupPose () { - PathConstraintData data = this.data; - position = data.position; - spacing = data.spacing; - mixRotate = data.mixRotate; - mixX = data.mixX; - mixY = data.mixY; - } + override public void Update (Skeleton skeleton, Physics physics) { + PathAttachment pathAttachment = slot.applied.Attachment as PathAttachment; + if (pathAttachment == null) return; - public void Update (Physics physics) { - PathAttachment attachment = slot.Attachment as PathAttachment; - if (attachment == null) return; - - float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY; + PathConstraintPose p = applied; + float mixRotate = p.mixRotate, mixX = p.mixX, mixY = p.mixY; if (mixRotate == 0 && mixX == 0 && mixY == 0) return; PathConstraintData data = this.data; bool tangents = data.rotateMode == RotateMode.Tangent, scale = data.rotateMode == RotateMode.ChainScale; int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; - Bone[] bonesItems = this.bones.Items; + BonePose[] bonesItems = this.bones.Items; float[] spaces = this.spaces.Resize(spacesCount).Items, lengths = scale ? this.lengths.Resize(boneCount).Items : null; - float spacing = this.spacing; + float spacing = p.spacing; switch (data.spacingMode) { case SpacingMode.Percent: if (scale) { for (int i = 0, n = spacesCount - 1; i < n; i++) { - Bone bone = bonesItems[i]; - float setupLength = bone.data.length; + BonePose bone = bonesItems[i]; + float setupLength = bone.bone.data.length; float x = setupLength * bone.a, y = setupLength * bone.c; lengths[i] = (float)Math.Sqrt(x * x + y * y); } @@ -125,9 +100,9 @@ namespace Spine { case SpacingMode.Proportional: { float sum = 0; for (int i = 0, n = spacesCount - 1; i < n;) { - Bone bone = bonesItems[i]; - float setupLength = bone.data.length; - if (setupLength < PathConstraint.Epsilon) { + BonePose bone = bonesItems[i]; + float setupLength = bone.bone.data.length; + if (setupLength < Epsilon) { if (scale) lengths[i] = 0; spaces[++i] = spacing; } else { @@ -148,8 +123,8 @@ namespace Spine { default: { bool lengthSpacing = data.spacingMode == SpacingMode.Length; for (int i = 0, n = spacesCount - 1; i < n;) { - Bone bone = bonesItems[i]; - float setupLength = bone.data.length; + BonePose bone = bonesItems[i]; + float setupLength = bone.bone.data.length; if (setupLength < PathConstraint.Epsilon) { if (scale) lengths[i] = 0; spaces[++i] = spacing; @@ -157,31 +132,31 @@ namespace Spine { float x = setupLength * bone.a, y = setupLength * bone.c; float length = (float)Math.Sqrt(x * x + y * y); if (scale) lengths[i] = length; - spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; + spaces[++i] = (lengthSpacing ? Math.Max(0, setupLength + spacing) : spacing) * length / setupLength; } } break; } } - float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents); + float[] positions = ComputeWorldPositions(skeleton, pathAttachment, spacesCount, tangents); float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; bool tip; if (offsetRotation == 0) { tip = data.rotateMode == RotateMode.Chain; } else { tip = false; - Bone p = slot.bone; - offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + BonePose bone = slot.bone.applied; + offsetRotation *= bone.a * bone.d - bone.b * bone.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; } - for (int i = 0, p = 3; i < boneCount; i++, p += 3) { - Bone bone = bonesItems[i]; + for (int i = 0, ip = 3, u = skeleton.update; i < boneCount; i++, ip += 3) { + BonePose bone = bonesItems[i]; bone.worldX += (boneX - bone.worldX) * mixX; bone.worldY += (boneY - bone.worldY) * mixY; - float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; + float x = positions[ip], y = positions[ip + 1], dx = x - boneX, dy = y - boneY; if (scale) { float length = lengths[i]; - if (length >= PathConstraint.Epsilon) { + if (length >= Epsilon) { float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1; bone.a *= s; bone.c *= s; @@ -192,16 +167,16 @@ namespace Spine { if (mixRotate > 0) { float a = bone.a, b = bone.b, c = bone.c, d = bone.d, r, cos, sin; if (tangents) - r = positions[p - 1]; - else if (spaces[i + 1] < PathConstraint.Epsilon) - r = positions[p + 2]; + r = positions[ip - 1]; + else if (spaces[i + 1] < Epsilon) + r = positions[ip + 2]; else r = MathUtils.Atan2(dy, dx); r -= MathUtils.Atan2(c, a); if (tip) { cos = MathUtils.Cos(r); sin = MathUtils.Sin(r); - float length = bone.data.length; + float length = bone.bone.data.length; boneX += (length * (cos * a - sin * c) - dx) * mixRotate; boneY += (length * (sin * a + cos * c) - dy) * mixRotate; } else @@ -218,13 +193,13 @@ namespace Spine { bone.c = sin * a + cos * c; bone.d = sin * b + cos * d; } - bone.UpdateAppliedTransform(); + bone.ModifyWorld(u); } } - float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents) { - Slot target = this.slot; - float position = this.position; + float[] ComputeWorldPositions (Skeleton skeleton, PathAttachment path, int spacesCount, bool tangents) { + Slot slot = this.slot; + float position = applied.position; float[] spaces = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; bool closed = path.Closed; int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; @@ -262,14 +237,14 @@ namespace Spine { } else if (p < 0) { if (prevCurve != BEFORE) { prevCurve = BEFORE; - path.ComputeWorldVertices(target, 2, 4, world, 0, 2); + path.ComputeWorldVertices(skeleton, slot, 2, 4, world, 0, 2); } AddBeforePosition(p, world, 0, output, o); continue; } else if (p > pathLength) { if (prevCurve != AFTER) { prevCurve = AFTER; - path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0, 2); + path.ComputeWorldVertices(skeleton, slot, verticesLength - 6, 4, world, 0, 2); } AddAfterPosition(p - pathLength, world, 0, output, o); continue; @@ -290,13 +265,13 @@ namespace Spine { if (curve != prevCurve) { prevCurve = curve; if (closed && curve == curveCount) { - path.ComputeWorldVertices(target, verticesLength - 4, 4, world, 0, 2); - path.ComputeWorldVertices(target, 0, 4, world, 4, 2); + path.ComputeWorldVertices(skeleton, slot, verticesLength - 4, 4, world, 0, 2); + path.ComputeWorldVertices(skeleton, slot, 0, 4, world, 4, 2); } else - path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2); + path.ComputeWorldVertices(skeleton, slot, curve * 6 + 2, 8, world, 0, 2); } AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o, - tangents || (i > 0 && space < PathConstraint.Epsilon)); + tangents || (i > 0 && space < Epsilon)); } return output; } @@ -305,15 +280,15 @@ namespace Spine { if (closed) { verticesLength += 2; world = this.world.Resize(verticesLength).Items; - path.ComputeWorldVertices(target, 2, verticesLength - 4, world, 0, 2); - path.ComputeWorldVertices(target, 0, 2, world, verticesLength - 4, 2); + path.ComputeWorldVertices(skeleton, slot, 2, verticesLength - 4, world, 0, 2); + path.ComputeWorldVertices(skeleton, slot, 0, 2, world, verticesLength - 4, 2); world[verticesLength - 2] = world[0]; world[verticesLength - 1] = world[1]; } else { curveCount--; verticesLength -= 4; world = this.world.Resize(verticesLength).Items; - path.ComputeWorldVertices(target, 2, verticesLength, world, 0, 2); + path.ComputeWorldVertices(skeleton, slot, 2, verticesLength, world, 0, 2); } // Curve lengths. @@ -378,6 +353,7 @@ namespace Spine { p %= pathLength; if (p < 0) p += pathLength; curve = 0; + segment = 0; } else if (p < 0) { AddBeforePosition(p, world, 0, output, o); continue; @@ -493,26 +469,53 @@ namespace Spine { } } - /// The position along the path. - public float Position { get { return position; } set { position = value; } } - /// The spacing between bones. - public float Spacing { get { return spacing; } set { spacing = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. - public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. - public float MixX { get { return mixX; } set { mixX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. - public float MixY { get { return mixY; } set { mixY = value; } } - /// The bones that will be modified by this path constraint. - public ExposedList Bones { get { return bones; } } - /// The slot whose path attachment will be used to constrained the bones. - public Slot Target { get { return slot; } set { slot = value; } } - public bool Active { get { return active; } } - /// The path constraint's setup pose data. - public PathConstraintData Data { get { return data; } } - - override public string ToString () { - return data.name; + override public void Sort (Skeleton skeleton) { + int slotIndex = slot.Data.index; + Bone slotBone = slot.bone; + if (skeleton.skin != null) SortPathSlot(skeleton, skeleton.skin, slotIndex, slotBone); + if (skeleton.data.defaultSkin != null && skeleton.data.defaultSkin != skeleton.skin) + SortPathSlot(skeleton, skeleton.data.defaultSkin, slotIndex, slotBone); + SortPath(skeleton, slot.pose.attachment, slotBone); + BonePose[] bones = this.bones.Items; + int boneCount = this.bones.Count; + for (int i = 0; i < boneCount; i++) { + Bone bone = bones[i].bone; + skeleton.SortBone(bone); + skeleton.Constrained(bone); + } + skeleton.updateCache.Add(this); + for (int i = 0; i < boneCount; i++) + skeleton.SortReset(bones[i].bone.children); + for (int i = 0; i < boneCount; i++) + bones[i].bone.sorted = true; } + + private void SortPathSlot (Skeleton skeleton, Skin skin, int slotIndex, Bone slotBone) { + foreach (Skin.SkinEntry entry in skin.Attachments) + if (entry.SlotIndex == slotIndex) SortPath(skeleton, entry.Attachment, slotBone); + } + + private void SortPath (Skeleton skeleton, Attachment attachment, Bone slotBone) { + if (!(attachment is PathAttachment)) return; + int[] pathBones = ((PathAttachment)attachment).bones; + if (pathBones == null) + skeleton.SortBone(slotBone); + else { + Bone[] bones = skeleton.bones.Items; + for (int i = 0, n = pathBones.Length; i < n;) { + int nn = pathBones[i++]; + nn += i; + while (i < nn) + skeleton.SortBone(bones[pathBones[i++]]); + } + } + } + + override public bool IsSourceActive { get { return slot.bone.active; } } + + /// The bones that will be modified by this path constraint. + public ExposedList Bones { get { return bones; } } + /// The slot whose path attachment will be used to constrained the bones. + public Slot Slot { get { return slot; } set { slot = value; } } } } diff --git a/spine-csharp/src/PathConstraintData.cs b/spine-csharp/src/PathConstraintData.cs index 5025d53d8..2dd130cea 100644 --- a/spine-csharp/src/PathConstraintData.cs +++ b/spine-csharp/src/PathConstraintData.cs @@ -30,16 +30,20 @@ using System; namespace Spine { - public class PathConstraintData : ConstraintData { + public class PathConstraintData : ConstraintData { internal ExposedList bones = new ExposedList(); internal SlotData slot; internal PositionMode positionMode; internal SpacingMode spacingMode; internal RotateMode rotateMode; internal float offsetRotation; - internal float position, spacing, mixRotate, mixX, mixY; - public PathConstraintData (string name) : base(name) { + public PathConstraintData (string name) + : base(name, new PathConstraintPose()) { + } + + override public IConstraint Create (Skeleton skeleton) { + return new PathConstraint(this, skeleton); } public ExposedList Bones { get { return bones; } } @@ -48,14 +52,6 @@ namespace Spine { public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float Position { get { return position; } set { position = value; } } - public float Spacing { get { return spacing; } set { spacing = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - public float RotateMix { get { return mixRotate; } set { mixRotate = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. - public float MixX { get { return mixX; } set { mixX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. - public float MixY { get { return mixY; } set { mixY = value; } } } public enum PositionMode { diff --git a/spine-csharp/src/PathConstraintPose.cs b/spine-csharp/src/PathConstraintPose.cs new file mode 100644 index 000000000..4fec4f98c --- /dev/null +++ b/spine-csharp/src/PathConstraintPose.cs @@ -0,0 +1,59 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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; + +namespace Spine { + + /// + /// Stores a pose for a path constraint. + /// + public class PathConstraintPose : IPose { + internal float position, spacing, mixRotate, mixX, mixY; + + public void Set (PathConstraintPose pose) { + position = pose.position; + spacing = pose.spacing; + mixRotate = pose.mixRotate; + mixX = pose.mixX; + mixY = pose.mixY; + } + + /// The position along the path. + public float Position { get { return position; } set { position = value; } } + /// The spacing between bones. + public float Spacing { get { return spacing; } set { spacing = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. + public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + } +} diff --git a/spine-csharp/src/PathConstraintPose.cs.meta b/spine-csharp/src/PathConstraintPose.cs.meta new file mode 100644 index 000000000..3750d32d5 --- /dev/null +++ b/spine-csharp/src/PathConstraintPose.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 40244ea3e8f247f4fac4363b219d8418 \ No newline at end of file diff --git a/spine-csharp/src/Physics.cs b/spine-csharp/src/Physics.cs new file mode 100644 index 000000000..ef9884193 --- /dev/null +++ b/spine-csharp/src/Physics.cs @@ -0,0 +1,46 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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. + *****************************************************************************/ + +namespace Spine { + + /// Determines how physics and other non-deterministic updates are applied. + public enum Physics { + /// Physics are not updated or applied. + None, + + /// Physics are reset to the current pose. + Reset, + + /// Physics are updated and the pose from physics is applied. + Update, + + /// Physics are not updated but the pose from physics is applied. + Pose + } +} diff --git a/spine-csharp/src/Physics.cs.meta b/spine-csharp/src/Physics.cs.meta new file mode 100644 index 000000000..db447bd88 --- /dev/null +++ b/spine-csharp/src/Physics.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2dab58587078e30468faff31f1ad4fa9 \ No newline at end of file diff --git a/spine-csharp/src/PhysicsConstraint.cs b/spine-csharp/src/PhysicsConstraint.cs index 328a08adc..a5dfc6e0f 100644 --- a/spine-csharp/src/PhysicsConstraint.cs +++ b/spine-csharp/src/PhysicsConstraint.cs @@ -30,17 +30,14 @@ using System; namespace Spine { - using Physics = Skeleton.Physics; /// /// Stores the current pose for a physics constraint. A physics constraint applies physics to bones. /// /// See Physics constraints in the Spine User Guide. /// - public class PhysicsConstraint : IUpdatable { - internal readonly PhysicsConstraintData data; - internal Bone bone; - internal float inertia, strength, damping, massInverse, wind, gravity, mix; + public class PhysicsConstraint : Constraint { + internal BonePose bone; bool reset = true; float ux, uy, cx, cy, tx, ty; @@ -49,42 +46,22 @@ namespace Spine { float rotateOffset, rotateLag, rotateVelocity; float scaleOffset, scaleLag, scaleVelocity; - internal bool active; - - readonly Skeleton skeleton; float remaining, lastTime; - public PhysicsConstraint (PhysicsConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + public PhysicsConstraint (PhysicsConstraintData data, Skeleton skeleton) + : base(data, new PhysicsConstraintPose(), new PhysicsConstraintPose()) { if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - this.skeleton = skeleton; - bone = skeleton.bones.Items[data.bone.index]; - - inertia = data.inertia; - strength = data.strength; - damping = data.damping; - massInverse = data.massInverse; - wind = data.wind; - gravity = data.gravity; - mix = data.mix; + bone = skeleton.bones.Items[data.bone.index].constrained; } - /// Copy constructor. - public PhysicsConstraint (PhysicsConstraint constraint, Skeleton skeleton) - : this(constraint.data, skeleton) { - - inertia = constraint.inertia; - strength = constraint.strength; - damping = constraint.damping; - massInverse = constraint.massInverse; - wind = constraint.wind; - gravity = constraint.gravity; - mix = constraint.mix; + override public IConstraint Copy (Skeleton skeleton) { + var copy = new PhysicsConstraint(data, skeleton); + copy.pose.Set(pose); + return copy; } - public void Reset () { + public void Reset (Skeleton skeleton) { remaining = 0; lastTime = skeleton.time; reset = true; @@ -102,20 +79,9 @@ namespace Spine { scaleVelocity = 0; } - public void SetToSetupPose () { - PhysicsConstraintData data = this.data; - inertia = data.inertia; - strength = data.strength; - damping = data.damping; - massInverse = data.massInverse; - wind = data.wind; - gravity = data.gravity; - mix = data.mix; - } - /// - /// Translates the physics constraint so next forces are applied as if the bone moved an additional - /// amount in world space. + /// Translates the physics constraint so next forces are applied as if the bone moved an + /// additional amount in world space. /// public void Translate (float x, float y) { ux -= x; @@ -125,8 +91,8 @@ namespace Spine { } /// - /// Rotates the physics constraint so next forces are applied as if the bone rotated around the - /// specified point in world space. + /// Rotates the physics constraint so next forces are applied as if the bone rotated around + /// the specified point in world space. /// public void Rotate (float x, float y, float degrees) { float r = degrees * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r); @@ -135,23 +101,23 @@ namespace Spine { } /// Applies the constraint to the constrained bones. - public void Update (Physics physics) { - float mix = this.mix; + override public void Update (Skeleton skeleton, Physics physics) { + PhysicsConstraintPose p = applied; + float mix = p.mix; if (mix == 0) return; bool x = data.x > 0, y = data.y > 0, rotateOrShearX = data.rotate > 0 || data.shearX > 0, scaleX = data.scaleX > 0; - Bone bone = this.bone; - float l = bone.data.length, t = data.step, z = 0; + BonePose bone = this.bone; + float l = bone.bone.data.length, t = data.step, z = 0; switch (physics) { case Physics.None: return; case Physics.Reset: - Reset(); + Reset(skeleton); goto case Physics.Update; // Fall through. case Physics.Update: - Skeleton skeleton = this.skeleton; - float delta = Math.Max(skeleton.time - lastTime, 0); + float delta = Math.Max(skeleton.time - lastTime, 0), aa = remaining; remaining += delta; lastTime = skeleton.time; @@ -161,8 +127,8 @@ namespace Spine { ux = bx; uy = by; } else { - float a = remaining, i = inertia, f = skeleton.data.referenceScale, d = -1, qx = data.limit * delta, - qy = qx * Math.Abs(skeleton.ScaleY); + float a = remaining, i = p.inertia, f = skeleton.data.referenceScale, d = -1, m = 0, e = 0, ax = 0, ay = 0, + qx = data.limit * delta, qy = qx * Math.Abs(skeleton.ScaleY); qx *= Math.Abs(skeleton.ScaleX); if (x || y) { @@ -177,17 +143,21 @@ namespace Spine { uy = by; } if (a >= t) { - d = (float)Math.Pow(damping, 60 * t); - float m = massInverse * t, e = strength, w = wind * f * skeleton.ScaleX, - g = gravity * f * skeleton.ScaleY, xs = xOffset, ys = yOffset; + float xs = xOffset, ys = yOffset; + d = (float)Math.Pow(p.damping, 60 * t); + m = t * p.massInverse; + e = p.strength; + float w = f * p.wind, g = f * p.gravity; + ax = (w * skeleton.windX + g * skeleton.gravityX) * skeleton.scaleX; + ay = (w * skeleton.windY + g * skeleton.gravityY) * skeleton.ScaleY; do { if (x) { - xVelocity += (w - xOffset * e) * m; + xVelocity += (ax - xOffset * e) * m; xOffset += xVelocity * t; xVelocity *= d; } if (y) { - yVelocity -= (g + yOffset * e) * m; + yVelocity -= (ay + yOffset * e) * m; yOffset += yVelocity * t; yVelocity *= d; } @@ -210,12 +180,12 @@ namespace Spine { dy = qy; else if (dy < -qy) dy = -qy; - a = remaining; if (rotateOrShearX) { mr = (data.rotate + data.shearX) * mix; - float rz = rotateLag * Math.Max(0, 1 - a / t), r = (float)Math.Atan2(dy + ty, dx + tx) - ca - (rotateOffset - rz) * mr; + z = rotateLag * Math.Max(0, 1 - aa / t); + float r = (float)Math.Atan2(dy + ty, dx + tx) - ca - (rotateOffset - z) * mr; rotateOffset += (r - (float)Math.Ceiling(r * MathUtils.InvPI2 - 0.5f) * MathUtils.PI2) * i; - r = (rotateOffset - rz) * mr + ca; + r = (rotateOffset - z) * mr + ca; c = (float)Math.Cos(r); s = (float)Math.Sin(r); if (scaleX) { @@ -225,23 +195,30 @@ namespace Spine { } else { c = (float)Math.Cos(ca); s = (float)Math.Sin(ca); - float r = l * bone.WorldScaleX; + float r = l * bone.WorldScaleX - scaleLag * Math.Max(0, 1 - aa / t); if (r > 0) scaleOffset += (dx * c + dy * s) * i / r; } a = remaining; if (a >= t) { - if (d == -1) d = (float)Math.Pow(damping, 60 * t); - float m = massInverse * t, e = strength, w = wind, g = (Bone.yDown ? -gravity : gravity), h = l / f, - rs = rotateOffset, ss = scaleOffset; + if (d == -1) { + d = (float)Math.Pow(p.damping, 60 * t); + m = t * p.massInverse; + e = p.strength; + float w = f * p.wind, g = f * p.gravity; + ax = (w * skeleton.windX + g * skeleton.gravityX) * skeleton.scaleX; + ay = (w * skeleton.windY + g * skeleton.gravityY) * skeleton.ScaleY; + } + float rs = rotateOffset, ss = scaleOffset, h = l / f; + if (Spine.Bone.yDown) ay = -ay; while (true) { a -= t; if (scaleX) { - scaleVelocity += (w * c - g * s - scaleOffset * e) * m; + scaleVelocity += (ax * c - ay * s - scaleOffset * e) * m; scaleOffset += scaleVelocity * t; scaleVelocity *= d; } if (rotateOrShearX) { - rotateVelocity -= ((w * s + g * c) * h + rotateOffset * e) * m; + rotateVelocity -= ((ax * s + ay * c) * h + rotateOffset * e) * m; rotateOffset += rotateVelocity * t; rotateVelocity *= d; if (a < t) break; @@ -307,32 +284,20 @@ namespace Spine { tx = l * bone.a; ty = l * bone.c; } - bone.UpdateAppliedTransform(); + bone.ModifyWorld(skeleton.update); } + override public void Sort (Skeleton skeleton) { + Bone bone = this.bone.bone; + skeleton.SortBone(bone); + skeleton.updateCache.Add(this); + skeleton.SortReset(bone.children); + skeleton.Constrained(bone); + } + + override public bool IsSourceActive { get { return bone.bone.active; } } + /// The bone constrained by this physics constraint. - public Bone Bone { get { return bone; } set { bone = value; } } - public float Inertia { get { return inertia; } set { inertia = value; } } - public float Strength { get { return strength; } set { strength = value; } } - public float Damping { get { return damping; } set { damping = value; } } - public float MassInverse { get { return massInverse; } set { massInverse = value; } } - public float Wind { get { return wind; } set { wind = value; } } - public float Gravity { get { return gravity; } set { gravity = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained poses. - public float Mix { get { return mix; } set { mix = value; } } - public bool Active { get { return active; } } - - - /// The physics constraint's setup pose data. - public PhysicsConstraintData getData () { - return data; - } - - /// The physics constraint's setup pose data. - public PhysicsConstraintData Data { get { return data; } } - - override public string ToString () { - return data.name; - } + public BonePose Bone { get { return bone; } set { bone = value; } } } } diff --git a/spine-csharp/src/PhysicsConstraintData.cs b/spine-csharp/src/PhysicsConstraintData.cs index 875a782ae..dbedd20a5 100644 --- a/spine-csharp/src/PhysicsConstraintData.cs +++ b/spine-csharp/src/PhysicsConstraintData.cs @@ -33,13 +33,17 @@ namespace Spine { /// /// See Physics constraints in the Spine User Guide. ///
- public class PhysicsConstraintData : ConstraintData { + public class PhysicsConstraintData : ConstraintData { internal BoneData bone; - internal float x, y, rotate, scaleX, shearX, limit; - internal float step, inertia, strength, damping, massInverse, wind, gravity, mix; + internal float x, y, rotate, scaleX, shearX, limit, step; internal bool inertiaGlobal, strengthGlobal, dampingGlobal, massGlobal, windGlobal, gravityGlobal, mixGlobal; - public PhysicsConstraintData (string name) : base(name) { + public PhysicsConstraintData (string name) + : base(name, new PhysicsConstraintPose()) { + } + + override public IConstraint Create (Skeleton skeleton) { + return new PhysicsConstraint(this, skeleton); } /// The bone constrained by this physics constraint. @@ -52,14 +56,6 @@ namespace Spine { public float ScaleX { get { return scaleX; } set { scaleX = value; } } public float ShearX { get { return shearX; } set { shearX = value; } } public float Limit { get { return limit; } set { limit = value; } } - public float Inertia { get { return inertia; } set { inertia = value; } } - public float Strength { get { return strength; } set { strength = value; } } - public float Damping { get { return damping; } set { damping = value; } } - public float MassInverse { get { return massInverse; } set { massInverse = value; } } - public float Wind { get { return wind; } set { wind = value; } } - public float Gravity { get { return gravity; } set { gravity = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained poses. - public float Mix { get { return mix; } set { mix = value; } } public bool InertiaGlobal { get { return inertiaGlobal; } set { inertiaGlobal = value; } } public bool StrengthGlobal { get { return strengthGlobal; } set { strengthGlobal = value; } } public bool DampingGlobal { get { return dampingGlobal; } set { dampingGlobal = value; } } diff --git a/spine-csharp/src/PhysicsConstraintPose.cs b/spine-csharp/src/PhysicsConstraintPose.cs new file mode 100644 index 000000000..ca1ef2659 --- /dev/null +++ b/spine-csharp/src/PhysicsConstraintPose.cs @@ -0,0 +1,59 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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; + +namespace Spine { + + /// + /// Stores a pose for a physics constraint. + /// + public class PhysicsConstraintPose : IPose { + internal float inertia, strength, damping, massInverse, wind, gravity, mix; + + public void Set (PhysicsConstraintPose pose) { + inertia = pose.inertia; + strength = pose.strength; + damping = pose.damping; + massInverse = pose.massInverse; + wind = pose.wind; + gravity = pose.gravity; + mix = pose.mix; + } + + public float Inertia { get { return inertia; } set { inertia = value; } } + public float Strength { get { return strength; } set { strength = value; } } + public float Damping { get { return damping; } set { damping = value; } } + public float MassInverse { get { return massInverse; } set { massInverse = value; } } + public float Wind { get { return wind; } set { wind = value; } } + public float Gravity { get { return gravity; } set { gravity = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained poses. + public float Mix { get { return mix; } set { mix = value; } } + } +} diff --git a/spine-csharp/src/PhysicsConstraintPose.cs.meta b/spine-csharp/src/PhysicsConstraintPose.cs.meta new file mode 100644 index 000000000..c0461b9c0 --- /dev/null +++ b/spine-csharp/src/PhysicsConstraintPose.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6974fd8e5e17807499e8fed214659cf9 \ No newline at end of file diff --git a/spine-csharp/src/Posed.cs b/spine-csharp/src/Posed.cs new file mode 100644 index 000000000..cdcc0bdb8 --- /dev/null +++ b/spine-csharp/src/Posed.cs @@ -0,0 +1,95 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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; + +namespace Spine { + + public interface IPosed { + // replaces "object.pose == object.applied" of reference implementation. + bool PoseEqualsApplied { get; } + // replaces "object.applied = object.pose" of reference implementation. + void UsePose (); + // replaces "object.applied = object.constrained" of reference implementation. + void UseConstrained (); + // replaces "object.applied.Set(object.pose)" of reference implementation. + void ResetConstrained (); + void SetupPose (); + } + + public class Posed : IPosed + where D : PosedData

+ where P : IPose

+ where A : P { + + internal readonly D data; + internal readonly A pose; + internal readonly A constrained; + internal A applied; + + public Posed (D data, A pose, A constrained) { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + this.pose = pose; + this.constrained = constrained; + applied = pose; + } + + public virtual void SetupPose () { + pose.Set(data.setup); + } + + public bool PoseEqualsApplied { + get { return (object)pose == (object)applied; } + } + + public void UsePose () { + applied = pose; + } + + public void UseConstrained () { + applied = constrained; + } + + public void ResetConstrained () { + applied.Set(pose); + } + + ///

The constraint's setup pose data. + public D Data { get { return data; } } + + public P Pose { get { return pose; } } + + public A AppliedPose { get { return applied; } } + + override public string ToString () { + return data.name; + } + } +} diff --git a/spine-csharp/src/Posed.cs.meta b/spine-csharp/src/Posed.cs.meta new file mode 100644 index 000000000..d07d651d8 --- /dev/null +++ b/spine-csharp/src/Posed.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 208c5c9065683a84e8166c4ce75c8951 \ No newline at end of file diff --git a/spine-csharp/src/PosedActive.cs b/spine-csharp/src/PosedActive.cs new file mode 100644 index 000000000..51eaa5a2b --- /dev/null +++ b/spine-csharp/src/PosedActive.cs @@ -0,0 +1,58 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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; + +namespace Spine { + public interface IPosedActive { + bool Active { get; set; } + } + + public class PosedActive : Posed, IPosedActive + where D : PosedData

+ where P : IPose

+ where A : P { + + internal bool active; + + public PosedActive (D data, A pose, A constrained) + : base(data, pose, constrained) { + SetupPose(); + } + + ///

Returns false when this constraint won't be updated by + /// because a skin is required and the + /// active skin does not contain this item. + /// + /// + /// + /// + public bool Active { get { return active; } set { active = value; } } + } +} diff --git a/spine-csharp/src/PosedActive.cs.meta b/spine-csharp/src/PosedActive.cs.meta new file mode 100644 index 000000000..8eab15d73 --- /dev/null +++ b/spine-csharp/src/PosedActive.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d3e0989017e310f44900496191b7d268 \ No newline at end of file diff --git a/spine-csharp/src/PosedData.cs b/spine-csharp/src/PosedData.cs new file mode 100644 index 000000000..6da38c671 --- /dev/null +++ b/spine-csharp/src/PosedData.cs @@ -0,0 +1,74 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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; + +namespace Spine { + public interface IPosedData { + public bool SkinRequired { get; } + } + + /// + /// The base class for all constrained datas. + /// + public class PosedData

: IPosedData + where P : IPose

{ + + internal readonly string name; + internal readonly P setup; + internal bool skinRequired; + + public PosedData (string name, P setup) { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + this.setup = setup; + } + + ///

The constraint's name, which is unique across all constraints in the skeleton of the same type. + public string Name { get { return name; } } + + public P GetSetupPose () { + return setup; + } + + /// + /// When true, only updates this constraint if the + /// contains this constraint. + /// + /// + public bool SkinRequired { + get { return skinRequired; } + set { skinRequired = value; } + } + + override public string ToString () { + return name; + } + } +} diff --git a/spine-csharp/src/PosedData.cs.meta b/spine-csharp/src/PosedData.cs.meta new file mode 100644 index 000000000..1863d64ea --- /dev/null +++ b/spine-csharp/src/PosedData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 984c45a4e280725489d63d5bf5cf526e \ No newline at end of file diff --git a/spine-csharp/src/Skeleton.cs b/spine-csharp/src/Skeleton.cs index 44bfa4f1f..feab04dda 100644 --- a/spine-csharp/src/Skeleton.cs +++ b/spine-csharp/src/Skeleton.cs @@ -27,88 +27,36 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +#if UNITY_5_3_OR_NEWER +#define IS_UNITY +#endif + using System; namespace Spine { +#if IS_UNITY + using Color = UnityEngine.Color; +#endif public class Skeleton { static private readonly int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; internal SkeletonData data; internal ExposedList bones; internal ExposedList slots; internal ExposedList drawOrder; - internal ExposedList ikConstraints; - internal ExposedList transformConstraints; - internal ExposedList pathConstraints; - internal ExposedList physicsConstraints; - internal ExposedList updateCache = new ExposedList(); + internal ExposedList constraints; + internal ExposedList physics; + internal ExposedList updateCache = new ExposedList(); + internal ExposedList resetCache = new ExposedList(16); internal Skin skin; - internal float r = 1, g = 1, b = 1, a = 1; - internal float x, y, scaleX = 1, time; + // Color is a struct, set to protected to prevent + // Color color = slot.color; color.a = 0.5; + // modifying just a copy of the struct instead of the original + // object as in reference implementation. + protected Color color; + internal float x, y, scaleX = 1, time, windX = 1, windY = 0, gravityX = 0, gravityY = 1; /// Private to enforce usage of ScaleY getter taking Bone.yDown into account. private float scaleY = 1; - - /// The skeleton's setup pose data. - public SkeletonData Data { get { return data; } } - /// The skeleton's bones, sorted parent first. The root bone is always the first bone. - public ExposedList Bones { get { return bones; } } - /// The list of bones and constraints, sorted in the order they should be updated, - /// as computed by . - public ExposedList UpdateCacheList { get { return updateCache; } } - /// The skeleton's slots. - public ExposedList Slots { get { return slots; } } - /// The skeleton's slots in the order they should be drawn. - /// The returned array may be modified to change the draw order. - public ExposedList DrawOrder { get { return drawOrder; } } - /// The skeleton's IK constraints. - public ExposedList IkConstraints { get { return ikConstraints; } } - /// The skeleton's path constraints. - public ExposedList PathConstraints { get { return pathConstraints; } } - /// The skeleton's physics constraints. - public ExposedList PhysicsConstraints { get { return physicsConstraints; } } - /// The skeleton's transform constraints. - public ExposedList TransformConstraints { get { return transformConstraints; } } - - /// The skeleton's current skin. May be null. See - public Skin Skin { - /// The skeleton's current skin. May be null. - get { return skin; } - /// Sets a skin, . - 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; } } - public float A { get { return a; } set { a = value; } } - /// The skeleton X position, which is added to the root bone worldX position. - /// - /// Bones that do not inherit translation are still affected by this property. - public float X { get { return x; } set { x = value; } } - /// The skeleton Y position, which is added to the root bone worldY position. - /// - /// Bones that do not inherit translation are still affected by this property. - public float Y { get { return y; } set { y = value; } } - /// Scales the entire skeleton on the X axis. - /// - /// Bones that do not inherit scale are still affected by this property. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - /// Scales the entire skeleton on the Y axis. - /// - /// Bones that do not inherit scale are still affected by this property. - public float ScaleY { get { return scaleY * (Bone.yDown ? -1 : 1); } set { scaleY = value; } } - - [Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")] - public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } } - - [Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")] - public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } } - /// Returns the skeleton's time. This is used for time-based manipulations, such as . - /// - public float Time { get { return time; } set { time = value; } } - - /// Returns the root bone, or null if the skeleton has no bones. - public Bone RootBone { - get { return bones.Count == 0 ? null : bones.Items[0]; } - } + internal int update; public Skeleton (SkeletonData data) { if (data == null) throw new ArgumentNullException("data", "data cannot be null."); @@ -119,10 +67,10 @@ namespace Spine { foreach (BoneData boneData in data.bones) { Bone bone; if (boneData.parent == null) { - bone = new Bone(boneData, this, null); + bone = new Bone(boneData, null); } else { Bone parent = bonesItems[boneData.parent.index]; - bone = new Bone(boneData, this, parent); + bone = new Bone(boneData, parent); parent.children.Add(bone); } this.bones.Add(bone); @@ -132,27 +80,23 @@ namespace Spine { drawOrder = new ExposedList(data.slots.Count); foreach (SlotData slotData in data.slots) { Bone bone = bonesItems[slotData.boneData.index]; - Slot slot = new Slot(slotData, bone); + Slot slot = new Slot(slotData, this); slots.Add(slot); drawOrder.Add(slot); } - ikConstraints = new ExposedList(data.ikConstraints.Count); - foreach (IkConstraintData ikConstraintData in data.ikConstraints) - ikConstraints.Add(new IkConstraint(ikConstraintData, this)); - - transformConstraints = new ExposedList(data.transformConstraints.Count); - foreach (TransformConstraintData transformConstraintData in data.transformConstraints) - transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); - - pathConstraints = new ExposedList(data.pathConstraints.Count); - foreach (PathConstraintData pathConstraintData in data.pathConstraints) - pathConstraints.Add(new PathConstraint(pathConstraintData, this)); - - physicsConstraints = new ExposedList(data.physicsConstraints.Count); - foreach (PhysicsConstraintData physicsConstraintData in data.physicsConstraints) - physicsConstraints.Add(new PhysicsConstraint(physicsConstraintData, this)); + physics = new ExposedList(8); + constraints = new ExposedList(data.constraints.Count); + foreach (IConstraintData constraintData in data.constraints) { + IConstraint constraint = constraintData.Create(this); + PhysicsConstraint physicsConstraint = constraint as PhysicsConstraint; + if (physicsConstraint != null) physics.Add(physicsConstraint); + constraints.Add(constraint); + } + physics.TrimExcess(); + color = new Color(1, 1, 1, 1); + UpdateCache(); } @@ -165,10 +109,10 @@ namespace Spine { foreach (Bone bone in skeleton.bones) { Bone newBone; if (bone.parent == null) - newBone = new Bone(bone, this, null); + newBone = new Bone(bone, null); else { Bone parent = bones.Items[bone.parent.data.index]; - newBone = new Bone(bone, this, parent); + newBone = new Bone(bone, parent); parent.children.Add(newBone); } bones.Add(newBone); @@ -176,37 +120,25 @@ namespace Spine { slots = new ExposedList(skeleton.slots.Count); Bone[] bonesItems = bones.Items; - foreach (Slot slot in skeleton.slots) { - Bone bone = bonesItems[slot.bone.data.index]; - slots.Add(new Slot(slot, bone)); - } + foreach (Slot slot in skeleton.slots) + slots.Add(new Slot(slot, bonesItems[slot.bone.data.index], this)); drawOrder = new ExposedList(slots.Count); Slot[] slotsItems = slots.Items; foreach (Slot slot in skeleton.drawOrder) drawOrder.Add(slotsItems[slot.data.index]); - ikConstraints = new ExposedList(skeleton.ikConstraints.Count); - foreach (IkConstraint ikConstraint in skeleton.ikConstraints) - ikConstraints.Add(new IkConstraint(ikConstraint, skeleton)); - - transformConstraints = new ExposedList(skeleton.transformConstraints.Count); - foreach (TransformConstraint transformConstraint in skeleton.transformConstraints) - transformConstraints.Add(new TransformConstraint(transformConstraint, skeleton)); - - pathConstraints = new ExposedList(skeleton.pathConstraints.Count); - foreach (PathConstraint pathConstraint in skeleton.pathConstraints) - pathConstraints.Add(new PathConstraint(pathConstraint, skeleton)); - - physicsConstraints = new ExposedList(skeleton.physicsConstraints.Count); - foreach (PhysicsConstraint physicsConstraint in skeleton.physicsConstraints) - physicsConstraints.Add(new PhysicsConstraint(physicsConstraint, skeleton)); + physics = new ExposedList(skeleton.physics.Count); + constraints = new ExposedList(skeleton.constraints.Count); + foreach (IConstraint other in skeleton.constraints) { + IConstraint constraint = other.Copy(this); + PhysicsConstraint physicsConstraint = constraint as PhysicsConstraint; + if (physicsConstraint != null) physics.Add(physicsConstraint); + constraints.Add(constraint); + } skin = skeleton.skin; - r = skeleton.r; - g = skeleton.g; - b = skeleton.b; - a = skeleton.a; + color = skeleton.color; x = skeleton.x; y = skeleton.y; scaleX = skeleton.scaleX; @@ -219,8 +151,13 @@ namespace Spine { /// Caches information about bones and constraints. Must be called if the is modified or if bones, constraints, or /// constraints, or weighted path attachments are added or removed. public void UpdateCache () { - ExposedList updateCache = this.updateCache; updateCache.Clear(); + resetCache.Clear(); + + Slot[] slots = this.slots.Items; + for (int i = 0, n = this.slots.Count; i < n; i++) { + slots[i].UsePose(); + } int boneCount = this.bones.Count; Bone[] bones = this.bones.Items; @@ -228,6 +165,7 @@ namespace Spine { Bone bone = bones[i]; bone.sorted = bone.data.skinRequired; bone.active = !bone.sorted; + bone.UsePose(); } if (skin != null) { BoneData[] skinBones = skin.bones.Items; @@ -240,180 +178,55 @@ namespace Spine { } while (bone != null); } } + IConstraint[] constraints = this.constraints.Items; - int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count, - physicsCount = this.physicsConstraints.Count; - IkConstraint[] ikConstraints = this.ikConstraints.Items; - TransformConstraint[] transformConstraints = this.transformConstraints.Items; - PathConstraint[] pathConstraints = this.pathConstraints.Items; - PhysicsConstraint[] physicsConstraints = this.physicsConstraints.Items; - int constraintCount = ikCount + transformCount + pathCount + physicsCount; - for (int i = 0; i < constraintCount; i++) { - for (int ii = 0; ii < ikCount; ii++) { - IkConstraint constraint = ikConstraints[ii]; - if (constraint.data.order == i) { - SortIkConstraint(constraint); - goto continue_outer; - } + { // scope added to prevent compile error of n already being declared in enclosing scope + int n = this.constraints.Count; + for (int i = 0; i < n; i++) { + constraints[i].UsePose(); } - for (int ii = 0; ii < transformCount; ii++) { - TransformConstraint constraint = transformConstraints[ii]; - if (constraint.data.order == i) { - SortTransformConstraint(constraint); - goto continue_outer; - } + for (int i = 0; i < n; i++) { + IConstraint constraint = constraints[i]; + constraint.Active = constraint.IsSourceActive + && (!constraint.IData.SkinRequired || (skin != null && skin.constraints.Contains(constraint.IData))); + if (constraint.Active) constraint.Sort(this); } - for (int ii = 0; ii < pathCount; ii++) { - PathConstraint constraint = pathConstraints[ii]; - if (constraint.data.order == i) { - SortPathConstraint(constraint); - goto continue_outer; - } - } - for (int ii = 0; ii < physicsCount; ii++) { - PhysicsConstraint constraint = physicsConstraints[ii]; - if (constraint.data.order == i) { - SortPhysicsConstraint(constraint); - goto continue_outer; - } - } - continue_outer: { } - } - for (int i = 0; i < boneCount; i++) - SortBone(bones[i]); - } - - private void SortIkConstraint (IkConstraint constraint) { - constraint.active = constraint.target.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); - if (!constraint.active) return; - - SortBone(constraint.target); - - ExposedList constrained = constraint.bones; - Bone parent = constrained.Items[0]; - SortBone(parent); - - if (constrained.Count == 1) { - updateCache.Add(constraint); - SortReset(parent.children); - } else { - Bone child = constrained.Items[constrained.Count - 1]; - SortBone(child); - - updateCache.Add(constraint); - - SortReset(parent.children); - child.sorted = true; - } - } - - private void SortTransformConstraint (TransformConstraint constraint) { - constraint.active = constraint.source.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); - if (!constraint.active) return; - - SortBone(constraint.source); - - Bone[] constrained = constraint.bones.Items; - int boneCount = constraint.bones.Count; - if (constraint.data.localSource) { - for (int i = 0; i < boneCount; i++) { - Bone child = constrained[i]; - SortBone(child.parent); - SortBone(child); - } - } else { for (int i = 0; i < boneCount; i++) - SortBone(constrained[i]); - } + SortBone(bones[i]); - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained[i].children); - for (int i = 0; i < boneCount; i++) - constrained[i].sorted = true; - } - - private void SortPathConstraint (PathConstraint constraint) { - constraint.active = constraint.slot.bone.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); - if (!constraint.active) return; - - Slot slot = constraint.slot; - int slotIndex = slot.data.index; - Bone slotBone = slot.bone; - if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); - if (data.defaultSkin != null && data.defaultSkin != skin) - SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); - - SortPathConstraintAttachment(slot.attachment, slotBone); - - Bone[] constrained = constraint.bones.Items; - int boneCount = constraint.bones.Count; - for (int i = 0; i < boneCount; i++) - SortBone(constrained[i]); - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained[i].children); - for (int i = 0; i < boneCount; i++) - constrained[i].sorted = true; - } - - private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { - foreach (Skin.SkinEntry entry in skin.Attachments) - if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone); - } - - private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) { - if (!(attachment is PathAttachment)) return; - int[] pathBones = ((PathAttachment)attachment).bones; - if (pathBones == null) - SortBone(slotBone); - else { - Bone[] bones = this.bones.Items; - for (int i = 0, n = pathBones.Length; i < n;) { - int nn = pathBones[i++]; - nn += i; - while (i < nn) - SortBone(bones[pathBones[i++]]); + object[] updateCache = this.updateCache.Items; + n = this.updateCache.Count; + for (int i = 0; i < n; i++) { + Bone bone = updateCache[i] as Bone; + if (bone != null) updateCache[i] = bone.applied; } } } - private void SortPhysicsConstraint (PhysicsConstraint constraint) { - Bone bone = constraint.bone; - constraint.active = bone.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); - if (!constraint.active) return; - - SortBone(bone); - - updateCache.Add(constraint); - - SortReset(bone.children); - bone.sorted = true; + internal void Constrained (IPosed obj) { + if (obj.PoseEqualsApplied) { // if (obj.pose == obj.applied) { + obj.UseConstrained(); + resetCache.Add(obj); + } } - private void SortBone (Bone bone) { - if (bone.sorted) return; + internal void SortBone (Bone bone) { + if (bone.sorted || !bone.active) return; Bone parent = bone.parent; if (parent != null) SortBone(parent); bone.sorted = true; updateCache.Add(bone); } - private static void SortReset (ExposedList bones) { - Bone[] bonesItems = bones.Items; + internal void SortReset (ExposedList bones) { + Bone[] items = bones.Items; for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (!bone.active) continue; - if (bone.sorted) SortReset(bone.children); - bone.sorted = false; + Bone bone = items[i]; + if (bone.active) { + if (bone.sorted) SortReset(bone.children); + bone.sorted = false; + } } } @@ -424,113 +237,55 @@ namespace Spine { /// Runtimes Guide. /// public void UpdateWorldTransform (Physics physics) { - Bone[] bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bones[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; + update++; + + IPosed[] resetCache = this.resetCache.Items; + for (int i = 0, n = this.resetCache.Count; i < n; i++) { + resetCache[i].ResetConstrained(); } - IUpdatable[] updateCache = this.updateCache.Items; + object[] updateCache = this.updateCache.Items; for (int i = 0, n = this.updateCache.Count; i < n; i++) - updateCache[i].Update(physics); + ((IUpdate)updateCache[i]).Update(this, physics); } - /// - /// Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies - /// all constraints. - /// - public void UpdateWorldTransform (Physics physics, Bone parent) { - 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; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; - rootBone.worldX = pa * x + pb * y + parent.worldX; - rootBone.worldY = pc * x + pd * y + parent.worldY; - - float rx = (rootBone.rotation + rootBone.shearX) * MathUtils.DegRad; - float ry = (rootBone.rotation + 90 + rootBone.shearY) * MathUtils.DegRad; - float la = (float)Math.Cos(rx) * rootBone.scaleX; - float lb = (float)Math.Cos(ry) * rootBone.scaleY; - float lc = (float)Math.Sin(rx) * rootBone.scaleX; - float ld = (float)Math.Sin(ry) * rootBone.scaleY; - rootBone.a = (pa * la + pb * lc) * scaleX; - rootBone.b = (pa * lb + pb * ld) * scaleX; - rootBone.c = (pc * la + pd * lc) * scaleY; - rootBone.d = (pc * lb + pd * ld) * scaleY; - - // Update everything except root bone. - IUpdatable[] updateCache = this.updateCache.Items; - for (int i = 0, n = this.updateCache.Count; i < n; i++) { - IUpdatable updatable = updateCache[i]; - if (updatable != rootBone) updatable.Update(physics); - } - } - - /// - /// Calls for each physics constraint. - /// - public void PhysicsTranslate (float x, float y) { - PhysicsConstraint[] physicsConstraints = this.physicsConstraints.Items; - for (int i = 0, n = this.physicsConstraints.Count; i < n; i++) - physicsConstraints[i].Translate(x, y); - } - - /// - /// Calls for each physics constraint. - /// - public void PhysicsRotate (float x, float y, float degrees) { - PhysicsConstraint[] physicsConstraints = this.physicsConstraints.Items; - for (int i = 0, n = this.physicsConstraints.Count; i < n; i++) - physicsConstraints[i].Rotate(x, y, degrees); - } - - /// Increments the skeleton's . - public void Update (float delta) { - time += delta; - } - - /// Sets the bones, constraints, and slots to their setup pose values. - public void SetToSetupPose () { - SetBonesToSetupPose(); - SetSlotsToSetupPose(); + /// Sets the bones, constraints, slots, and draw order to their setup pose values. + public void SetupPose () { + SetupPoseBones(); + SetupPoseSlots(); } /// Sets the bones and constraints to their setup pose values. - public void SetBonesToSetupPose () { + public void SetupPoseBones () { Bone[] bones = this.bones.Items; for (int i = 0, n = this.bones.Count; i < n; i++) - bones[i].SetToSetupPose(); + bones[i].SetupPose(); - IkConstraint[] ikConstraints = this.ikConstraints.Items; - for (int i = 0, n = this.ikConstraints.Count; i < n; i++) - ikConstraints[i].SetToSetupPose(); - - TransformConstraint[] transformConstraints = this.transformConstraints.Items; - for (int i = 0, n = this.transformConstraints.Count; i < n; i++) - transformConstraints[i].SetToSetupPose(); - - PathConstraint[] pathConstraints = this.pathConstraints.Items; - for (int i = 0, n = this.pathConstraints.Count; i < n; i++) - pathConstraints[i].SetToSetupPose(); - - PhysicsConstraint[] physicsConstraints = this.physicsConstraints.Items; - for (int i = 0, n = this.physicsConstraints.Count; i < n; i++) - physicsConstraints[i].SetToSetupPose(); + IConstraint[] constraints = this.constraints.Items; + for (int i = 0, n = this.constraints.Count; i < n; i++) + constraints[i].SetupPose(); } - public void SetSlotsToSetupPose () { + /// Sets the slots and draw order to their setup pose values. + public void SetupPoseSlots () { Slot[] 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(); + slots[i].SetupPose(); + } + + /// The skeleton's setup pose data. + public SkeletonData Data { get { return data; } } + /// The skeleton's bones, sorted parent first. The root bone is always the first bone. + public ExposedList Bones { get { return bones; } } + /// + /// The list of bones and constraints, sorted in the order they should be updated, as computed by . + /// + public ExposedList UpdateCacheList { get { return updateCache; } } + /// Returns the root bone, or null if the skeleton has no bones. + public Bone RootBone { + get { return bones.Count == 0 ? null : bones.Items[0]; } } /// Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it @@ -546,6 +301,9 @@ namespace Spine { return null; } + /// The skeleton's slots. + public ExposedList Slots { get { return slots; } } + /// 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. /// May be null. @@ -559,6 +317,25 @@ namespace Spine { return null; } + /// + /// The skeleton's slots in the order they should be drawn. The returned array may be modified to change the draw order. + /// + public ExposedList DrawOrder { + get { return drawOrder; } + set { + if (value == null) throw new ArgumentNullException("drawOrder ", "drawOrder cannot be null."); + this.drawOrder = value; + } + } + + /// The skeleton's current skin. May be null. See + public Skin Skin { + /// The skeleton's current skin. May be null. + get { return skin; } + /// Sets a skin, . + set { SetSkin(value); } + } + /// Sets a skin by name (see ). public void SetSkin (string skinName) { Skin foundSkin = data.FindSkin(skinName); @@ -570,13 +347,12 @@ namespace Spine { /// Sets the skin used to look up attachments before looking in the . If the /// skin is changed, is called. /// - /// Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. - /// If there was no old skin, each slot's setup mode attachment is attached from the new skin. + /// Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. If there was no + /// old skin, each slot's setup mode attachment is attached from the new skin. /// /// After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling - /// . - /// Also, often is called before the next time the - /// skeleton is rendered to allow any attachment keys in the current animation(s) to hide or show attachments from the new skin. + /// . Also, often is called before the next time the skeleton is + /// rendered to allow any attachment keys in the current animation(s) to hide or show attachments from the new skin. /// /// May be null. public void SetSkin (Skin newSkin) { @@ -591,7 +367,7 @@ namespace Spine { string name = slot.data.attachmentName; if (name != null) { Attachment attachment = newSkin.GetAttachment(i, name); - if (attachment != null) slot.Attachment = attachment; + if (attachment != null) slot.pose.Attachment = attachment; } } } @@ -600,13 +376,20 @@ namespace Spine { UpdateCache(); } - /// Finds an attachment by looking in the and using the slot name and attachment name. + /// Finds an attachment by looking in the and using the slot name and attachment + /// name. /// May be null. + /// public Attachment GetAttachment (string slotName, string attachmentName) { - return GetAttachment(data.FindSlot(slotName).index, attachmentName); + SlotData slot = data.FindSlot(slotName); + if (slot == null) throw new ArgumentException("Slot not found: " + slotName, "slotName"); + return GetAttachment(slot.index, attachmentName); } - /// Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked. + /// Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and + /// attachment name. First the skin is checked and if the attachment was not found, the default skin is checked. + /// + /// See Runtime skins in the Spine Runtimes Guide. /// May be null. public Attachment GetAttachment (int slotIndex, string attachmentName) { if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); @@ -614,77 +397,40 @@ namespace Spine { Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); if (attachment != null) return attachment; } - return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; + if (data.defaultSkin != null) return data.defaultSkin.GetAttachment(slotIndex, attachmentName); + return null; } - /// A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment. + /// A convenience method to set an attachment by finding the slot with , finding the attachment with + /// , then setting the slot's . /// May be null to clear the slot's attachment. public void SetAttachment (string slotName, string attachmentName) { if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - Slot[] slots = this.slots.Items; - for (int i = 0, n = this.slots.Count; i < n; i++) { - Slot slot = slots[i]; - if (slot.data.name == slotName) { - Attachment attachment = null; - if (attachmentName != null) { - attachment = GetAttachment(i, attachmentName); - if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); - } - slot.Attachment = attachment; - return; - } + + Slot slot = FindSlot(slotName); + if (slot == null) throw new ArgumentException("Slot not found: " + slotName, "slotName"); + Attachment attachment = null; + if (attachmentName != null) { + attachment = GetAttachment(slot.data.index, attachmentName); + if (attachment == null) + throw new ArgumentException("Attachment not found: " + attachmentName + ", for slot: " + slotName, "attachmentName"); } - throw new Exception("Slot not found: " + slotName); + slot.pose.Attachment = attachment; } - /// 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. - /// May be null. - public IkConstraint FindIkConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - IkConstraint[] 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; - } + /// The skeleton's constraints. + public ExposedList Constraints { get { return constraints; } } + /// The skeleton's physics constraints. + public ExposedList PhysicsConstraints { get { return physics; } } - /// 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. /// May be null. - public TransformConstraint FindTransformConstraint (string constraintName) { + public T FindConstraint (string constraintName) where T : class, IConstraint { if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - TransformConstraint[] 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; - } - /// 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. - /// May be null. - public PathConstraint FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - PathConstraint[] 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; - } - - /// Finds a physics constraint by comparing each physics constraint's name. It is more efficient to cache the results of this - /// method than to call it repeatedly. - /// May be null. - public PhysicsConstraint FindPhysicsConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - PhysicsConstraint[] physicsConstraints = this.physicsConstraints.Items; - for (int i = 0, n = this.physicsConstraints.Count; i < n; i++) { - PhysicsConstraint constraint = physicsConstraints[i]; - if (constraint.data.name.Equals(constraintName)) return constraint; + IConstraint[] constraints = this.constraints.Items; + for (int i = 0, n = this.constraints.Count; i < n; i++) { + IConstraint constraint = constraints[i]; + if (constraint is T && constraint.IData.Name == constraintName) return (T)constraint; } return null; } @@ -708,13 +454,13 @@ namespace Spine { int verticesLength = 0; float[] vertices = null; int[] triangles = null; - Attachment attachment = slot.attachment; + Attachment attachment = slot.pose.attachment; RegionAttachment region = attachment as RegionAttachment; if (region != null) { verticesLength = 8; vertices = temp; if (vertices.Length < 8) vertices = temp = new float[8]; - region.ComputeWorldVertices(slot, temp, 0, 2); + region.ComputeWorldVertices(slot, vertices, 0, 2); triangles = quadTriangles; } else { MeshAttachment mesh = attachment as MeshAttachment; @@ -722,12 +468,13 @@ namespace Spine { verticesLength = mesh.WorldVerticesLength; vertices = temp; if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength]; - mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0, 2); + mesh.ComputeWorldVertices(this, slot, 0, verticesLength, temp, 0, 2); triangles = mesh.Triangles; } else if (clipper != null) { ClippingAttachment clip = attachment as ClippingAttachment; if (clip != null) { - clipper.ClipStart(slot, clip); + clipper.ClipEnd(slot); + clipper.ClipStart(this, slot, clip); continue; } } @@ -757,23 +504,95 @@ namespace Spine { vertexBuffer = temp; } + /// A copy of the color to tint all the skeleton's attachments. + public Color GetColor () { + return color; + } + + /// Sets the color to tint all the skeleton's attachments. + public void SetColor (Color color) { + this.color = color; + } + + /// + /// A convenience method for setting the skeleton color. The color can also be set by + /// + /// + public void SetColor (float r, float g, float b, float a) { + color = new Color(r, g, b, a); + } + + /// Scales the entire skeleton on the X axis. + /// + /// Bones that do not inherit scale are still affected by this property. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + /// Scales the entire skeleton on the Y axis. + /// + /// Bones that do not inherit scale are still affected by this property. + public float ScaleY { get { return scaleY * (Bone.yDown ? -1 : 1); } set { scaleY = value; } } + + /// + /// Scales the entire skeleton on the X and Y axes. + /// + /// Bones that do not inherit scale are still affected by this property. + /// + public void SetScale (float scaleX, float scaleY) { + this.scaleX = scaleX; + this.scaleY = scaleY; + } + + /// The skeleton X position, which is added to the root bone worldX position. + /// + /// Bones that do not inherit translation are still affected by this property. + public float X { get { return x; } set { x = value; } } + /// The skeleton Y position, which is added to the root bone worldY position. + /// + /// Bones that do not inherit translation are still affected by this property. + public float Y { get { return y; } set { y = value; } } + + /// + /// Sets the skeleton X and Y position, which is added to the root bone worldX and worldY position. + /// + /// Bones that do not inherit translation are still affected by this property. + public void SetPosition (float x, float y) { + this.x = x; + this.y = y; + } + + public float WindX { get { return windX; } set { windX = value; } } + public float WindY { get { return windY; } set { windY = value; } } + public float GravityX { get { return gravityX; } set { gravityX = value; } } + public float GravityY { get { return gravityY; } set { gravityY = value; } } + + /// + /// Calls for each physics constraint. + /// + public void PhysicsTranslate (float x, float y) { + PhysicsConstraint[] physicsConstraints = this.physics.Items; + for (int i = 0, n = this.physics.Count; i < n; i++) + physicsConstraints[i].Translate(x, y); + } + + /// + /// Calls for each physics constraint. + /// + public void PhysicsRotate (float x, float y, float degrees) { + PhysicsConstraint[] physicsConstraints = this.physics.Items; + for (int i = 0, n = this.physics.Count; i < n; i++) + physicsConstraints[i].Rotate(x, y, degrees); + } + + /// Returns the skeleton's time. This is used for time-based manipulations, such as . + /// + public float Time { get { return time; } set { time = value; } } + + /// Increments the skeleton's . + public void Update (float delta) { + time += delta; + } + override public string ToString () { return data.name; } - - /// Determines how physics and other non-deterministic updates are applied. - public enum Physics { - /// Physics are not updated or applied. - None, - - /// Physics are reset to the current pose. - Reset, - - /// Physics are updated and the pose from physics is applied. - Update, - - /// Physics are not updated but the pose from physics is applied. - Pose - } } } diff --git a/spine-csharp/src/SkeletonBinary.cs b/spine-csharp/src/SkeletonBinary.cs index fde73bbfd..9a6a94825 100644 --- a/spine-csharp/src/SkeletonBinary.cs +++ b/spine-csharp/src/SkeletonBinary.cs @@ -27,10 +27,6 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) -#define IS_UNITY -#endif - using System; using System.Collections.Generic; using System.IO; @@ -42,7 +38,7 @@ using Windows.Storage; #endif namespace Spine { - + using static Spine.TransformConstraintData; using FromProperty = TransformConstraintData.FromProperty; using FromRotate = TransformConstraintData.FromRotate; using FromScaleX = TransformConstraintData.FromScaleX; @@ -78,6 +74,12 @@ namespace Spine { public const int SLOT_RGB2 = 4; public const int SLOT_ALPHA = 5; + public const int CONSTRAINT_IK = 0; + public const int CONSTRAINT_PATH = 1; + public const int CONSTRAINT_TRANSFORM = 2; + public const int CONSTRAINT_PHYSICS = 3; + public const int CONSTRAINT_SLIDER = 4; + public const int ATTACHMENT_DEFORM = 0; public const int ATTACHMENT_SEQUENCE = 1; @@ -94,6 +96,9 @@ namespace Spine { public const int PHYSICS_MIX = 7; public const int PHYSICS_RESET = 8; + public const int SLIDER_TIME = 0; + public const int SLIDER_MIX = 1; + public const int CURVE_LINEAR = 0; public const int CURVE_STEPPED = 1; public const int CURVE_BEZIER = 2; @@ -128,9 +133,13 @@ namespace Spine { #else using (FileStream input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { #endif - SkeletonData skeletonData = ReadSkeletonData(input); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; + try { + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } catch (Exception ex) { + throw new SerializationException("Error reading binary skeleton file: " + path, ex); + } } } #endif // WINDOWS_STOREAPP @@ -146,302 +155,344 @@ namespace Spine { if (file == null) throw new ArgumentNullException("file"); float scale = this.scale; - var skeletonData = new SkeletonData(); var input = new SkeletonInput(file); + var skeletonData = new SkeletonData(); + string version = null; + try { + long hash = input.ReadLong(); + skeletonData.hash = hash == 0 ? null : hash.ToString(); + skeletonData.version = input.ReadString(); + version = skeletonData.version; + if (string.IsNullOrEmpty(skeletonData.version)) skeletonData.version = null; + // early return for old 3.8 format instead of reading past the end + if (skeletonData.version.Length > 13) return null; + skeletonData.x = input.ReadFloat(); + skeletonData.y = input.ReadFloat(); + skeletonData.width = input.ReadFloat(); + skeletonData.height = input.ReadFloat(); + skeletonData.referenceScale = input.ReadFloat() * scale; - long hash = input.ReadLong(); - skeletonData.hash = hash == 0 ? null : hash.ToString(); - skeletonData.version = input.ReadString(); - if (skeletonData.version.Length == 0) skeletonData.version = null; - // early return for old 3.8 format instead of reading past the end - if (skeletonData.version.Length > 13) return null; - skeletonData.x = input.ReadFloat(); - skeletonData.y = input.ReadFloat(); - skeletonData.width = input.ReadFloat(); - skeletonData.height = input.ReadFloat(); - skeletonData.referenceScale = input.ReadFloat() * scale; + bool nonessential = input.ReadBoolean(); - bool nonessential = input.ReadBoolean(); - - if (nonessential) { - skeletonData.fps = input.ReadFloat(); - - skeletonData.imagesPath = input.ReadString(); - if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null; - - skeletonData.audioPath = input.ReadString(); - if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null; - } - - int n; - Object[] o; - - // Strings. - o = input.strings = new String[n = input.ReadInt(true)]; - for (int i = 0; i < n; i++) - o[i] = input.ReadString(); - - // Bones. - BoneData[] 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 : bones[input.ReadInt(true)]; - var data = new BoneData(i, name, parent); - data.rotation = input.ReadFloat(); - data.x = input.ReadFloat() * scale; - data.y = input.ReadFloat() * scale; - data.scaleX = input.ReadFloat(); - data.scaleY = input.ReadFloat(); - data.shearX = input.ReadFloat(); - data.shearY = input.ReadFloat(); - data.Length = input.ReadFloat() * scale; - data.inherit = InheritEnum.Values[input.ReadInt(true)]; - data.skinRequired = input.ReadBoolean(); - if (nonessential) { // discard non-essential data - input.ReadInt(); // Color.rgba8888ToColor(data.color, input.readInt()); - input.ReadString(); // data.icon = input.readString(); - input.ReadBoolean(); // data.visible = input.readBoolean(); - } - bones[i] = data; - } - - // Slots. - SlotData[] slots = skeletonData.slots.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) { - String slotName = input.ReadString(); - - BoneData boneData = bones[input.ReadInt(true)]; - var slotData = new SlotData(i, slotName, boneData); - int color = input.ReadInt(); - slotData.r = ((color & 0xff000000) >> 24) / 255f; - slotData.g = ((color & 0x00ff0000) >> 16) / 255f; - slotData.b = ((color & 0x0000ff00) >> 8) / 255f; - slotData.a = ((color & 0x000000ff)) / 255f; - - int darkColor = input.ReadInt(); // 0x00rrggbb - if (darkColor != -1) { - slotData.hasSecondColor = true; - slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; - slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; - slotData.b2 = ((darkColor & 0x000000ff)) / 255f; - } - - slotData.attachmentName = input.ReadStringRef(); - slotData.blendMode = (BlendMode)input.ReadInt(true); if (nonessential) { - input.ReadBoolean(); // data.visible = input.readBoolean(); data.path = path; + skeletonData.fps = input.ReadFloat(); + + skeletonData.imagesPath = input.ReadString(); + if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null; + + skeletonData.audioPath = input.ReadString(); + if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null; } - slots[i] = slotData; - } - // IK constraints. - o = skeletonData.ikConstraints.Resize(n = input.ReadInt(true)).Items; - for (int i = 0, nn; i < n; i++) { - var data = new IkConstraintData(input.ReadString()); - data.order = input.ReadInt(true); - BoneData[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; - for (int ii = 0; ii < nn; ii++) - constraintBones[ii] = bones[input.ReadInt(true)]; - data.target = bones[input.ReadInt(true)]; - int flags = input.Read(); - data.skinRequired = (flags & 1) != 0; - data.bendDirection = (flags & 2) != 0 ? 1 : -1; - data.compress = (flags & 4) != 0; - data.stretch = (flags & 8) != 0; - data.uniform = (flags & 16) != 0; - if ((flags & 32) != 0) data.mix = (flags & 64) != 0 ? input.ReadFloat() : 1; - if ((flags & 128) != 0) data.softness = input.ReadFloat() * scale; - o[i] = data; - } + int n; + object[] o; - // Transform constraints. - o = skeletonData.transformConstraints.Resize(n = input.ReadInt(true)).Items; - for (int i = 0, nn; i < n; i++) { - var data = new TransformConstraintData(input.ReadString()); - data.order = input.ReadInt(true); - BoneData[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; - for (int ii = 0; ii < nn; ii++) - constraintBones[ii] = bones[input.ReadInt(true)]; - data.source = bones[input.ReadInt(true)]; - int flags = input.Read(); - data.skinRequired = (flags & 1) != 0; - data.localSource = (flags & 2) != 0; - data.localTarget = (flags & 4) != 0; - data.additive = (flags & 8) != 0; - data.clamp = (flags & 16) != 0; - FromProperty[] froms = data.properties.Resize(nn = flags >> 5).Items; - for (int ii = 0, tn; ii < nn; ii++) { - float fromScale = 1; - FromProperty from; - switch (input.ReadSByte()) { - case 0: from = new FromRotate(); break; - case 1: { - from = new FromX(); - fromScale = scale; + // Strings. + o = input.strings = new String[n = input.ReadInt(true)]; + for (int i = 0; i < n; i++) + o[i] = input.ReadString(); + + // Bones. + BoneData[] 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 : bones[input.ReadInt(true)]; + var data = new BoneData(i, name, parent); + BoneLocal setup = data.setup; + setup.rotation = input.ReadFloat(); + setup.x = input.ReadFloat() * scale; + setup.y = input.ReadFloat() * scale; + setup.scaleX = input.ReadFloat(); + setup.scaleY = input.ReadFloat(); + setup.shearX = input.ReadFloat(); + setup.shearY = input.ReadFloat(); + setup.inherit = InheritEnum.Values[input.ReadSByte()]; + data.Length = input.ReadFloat() * scale; + data.skinRequired = input.ReadBoolean(); + if (nonessential) { // discard non-essential data + input.ReadInt(); // Color.rgba8888ToColor(data.color, input.readInt()); + input.ReadString(); // data.icon = input.readString(); + input.ReadBoolean(); // data.visible = input.readBoolean(); + } + bones[i] = data; + } + + // Slots. + SlotData[] slots = skeletonData.slots.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) { + string slotName = input.ReadString(); + + BoneData boneData = bones[input.ReadInt(true)]; + var slotData = new SlotData(i, slotName, boneData); + slotData.setup.SetColor(((uint)input.ReadInt()).RGBA8888ToColor()); + int darkColor = input.ReadInt(); // 0x00rrggbb + if (darkColor != -1) { + slotData.setup.SetDarkColor(((uint)darkColor).XRGB888ToColor()); + } + + slotData.attachmentName = input.ReadStringRef(); + slotData.blendMode = (BlendMode)input.ReadInt(true); + if (nonessential) { + input.ReadBoolean(); // data.visible = input.readBoolean(); data.path = path; + } + slots[i] = slotData; + } + + // Constraints. + int constraintCount = input.ReadInt(true); + IConstraintData[] constraints = skeletonData.constraints.Resize(constraintCount).Items; + for (int i = 0; i < constraintCount; i++) { + string name = input.ReadString(); + int nn; + + switch (input.ReadUByte()) { + case CONSTRAINT_IK: { + var data = new IkConstraintData(name); + BoneData[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + constraintBones[ii] = bones[input.ReadInt(true)]; + data.target = bones[input.ReadInt(true)]; + int flags = input.Read(); + data.skinRequired = (flags & 1) != 0; + data.uniform = (flags & 2) != 0; + IkConstraintPose setup = data.setup; + setup.bendDirection = (flags & 4) != 0 ? 1 : -1; + setup.compress = (flags & 8) != 0; + setup.stretch = (flags & 16) != 0; + if ((flags & 32) != 0) setup.mix = (flags & 64) != 0 ? input.ReadFloat() : 1; + if ((flags & 128) != 0) setup.softness = input.ReadFloat() * scale; + constraints[i] = data; break; } - case 2: { - from = new FromY(); - fromScale = scale; + case CONSTRAINT_TRANSFORM: { + var data = new TransformConstraintData(name); + BoneData[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + constraintBones[ii] = bones[input.ReadInt(true)]; + data.source = bones[input.ReadInt(true)]; + int flags = input.Read(); + data.skinRequired = (flags & 1) != 0; + data.localSource = (flags & 2) != 0; + data.localTarget = (flags & 4) != 0; + data.additive = (flags & 8) != 0; + data.clamp = (flags & 16) != 0; + FromProperty[] froms = data.properties.Resize(nn = flags >> 5).Items; + for (int ii = 0, tn; ii < nn; ii++) { + float fromScale = 1; + FromProperty from; + switch (input.ReadUByte()) { + case 0: from = new FromRotate(); break; + case 1: { + from = new FromX(); + fromScale = scale; + break; + } + case 2: { + from = new FromY(); + fromScale = scale; + break; + } + case 3: from = new FromScaleX(); break; + case 4: from = new FromScaleY(); break; + case 5: from = new FromShearY(); break; + default: from = null; break; + }; + from.offset = input.ReadFloat() * fromScale; + ToProperty[] tos = from.to.Resize(tn = input.ReadSByte()).Items; + for (int t = 0; t < tn; t++) { + float toScale = 1; + ToProperty to; + switch (input.ReadUByte()) { + case 0: to = new ToRotate(); break; + case 1: { + to = new ToX(); + toScale = scale; + break; + } + case 2: { + to = new ToY(); + toScale = scale; + break; + } + case 3: to = new ToScaleX(); break; + case 4: to = new ToScaleY(); break; + case 5: to = new ToShearY(); break; + default: to = null; break; + }; + to.offset = input.ReadFloat() * toScale; + to.max = input.ReadFloat() * toScale; + to.scale = input.ReadFloat() * (toScale / fromScale); + tos[t] = to; + } + froms[ii] = from; + } + flags = input.Read(); + if ((flags & 1) != 0) data.offsets[TransformConstraintData.ROTATION] = input.ReadFloat(); + if ((flags & 2) != 0) data.offsets[TransformConstraintData.X] = input.ReadFloat() * scale; + if ((flags & 4) != 0) data.offsets[TransformConstraintData.Y] = input.ReadFloat() * scale; + if ((flags & 8) != 0) data.offsets[TransformConstraintData.SCALEX] = input.ReadFloat(); + if ((flags & 16) != 0) data.offsets[TransformConstraintData.SCALEY] = input.ReadFloat(); + if ((flags & 32) != 0) data.offsets[TransformConstraintData.SHEARY] = input.ReadFloat(); + flags = input.Read(); + TransformConstraintPose setup = data.setup; + if ((flags & 1) != 0) setup.mixRotate = input.ReadFloat(); + if ((flags & 2) != 0) setup.mixX = input.ReadFloat(); + if ((flags & 4) != 0) setup.mixY = input.ReadFloat(); + if ((flags & 8) != 0) setup.mixScaleX = input.ReadFloat(); + if ((flags & 16) != 0) setup.mixScaleY = input.ReadFloat(); + if ((flags & 32) != 0) setup.mixShearY = input.ReadFloat(); + constraints[i] = data; + break; + } + case CONSTRAINT_PATH: { + var data = new PathConstraintData(name); + BoneData[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + constraintBones[ii] = bones[input.ReadInt(true)]; + data.slot = slots[input.ReadInt(true)]; + int flags = input.Read(); + data.skinRequired = (flags & 1) != 0; + data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue((flags >> 1) & 2); + data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue((flags >> 2) & 3); + data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue((flags >> 4) & 3); + if ((flags & 128) != 0) data.offsetRotation = input.ReadFloat(); + PathConstraintPose setup = data.setup; + setup.position = input.ReadFloat(); + if (data.positionMode == PositionMode.Fixed) setup.position *= scale; + setup.spacing = input.ReadFloat(); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) setup.spacing *= scale; + setup.mixRotate = input.ReadFloat(); + setup.mixX = input.ReadFloat(); + setup.mixY = input.ReadFloat(); + constraints[i] = data; + break; + } + case CONSTRAINT_PHYSICS: { + var data = new PhysicsConstraintData(name); + data.bone = bones[input.ReadInt(true)]; + int flags = input.Read(); + data.skinRequired = (flags & 1) != 0; + if ((flags & 2) != 0) data.x = input.ReadFloat(); + if ((flags & 4) != 0) data.y = input.ReadFloat(); + if ((flags & 8) != 0) data.rotate = input.ReadFloat(); + if ((flags & 16) != 0) data.scaleX = input.ReadFloat(); + if ((flags & 32) != 0) data.shearX = input.ReadFloat(); + data.limit = ((flags & 64) != 0 ? input.ReadFloat() : 5000) * scale; + data.step = 1f / input.ReadUByte(); + PhysicsConstraintPose setup = data.setup; + setup.inertia = input.ReadFloat(); + setup.strength = input.ReadFloat(); + setup.damping = input.ReadFloat(); + setup.massInverse = (flags & 128) != 0 ? input.ReadFloat() : 1; + setup.wind = input.ReadFloat(); + setup.gravity = input.ReadFloat(); + flags = input.Read(); + if ((flags & 1) != 0) data.inertiaGlobal = true; + if ((flags & 2) != 0) data.strengthGlobal = true; + if ((flags & 4) != 0) data.dampingGlobal = true; + if ((flags & 8) != 0) data.massGlobal = true; + if ((flags & 16) != 0) data.windGlobal = true; + if ((flags & 32) != 0) data.gravityGlobal = true; + if ((flags & 64) != 0) data.mixGlobal = true; + setup.mix = (flags & 128) != 0 ? input.ReadFloat() : 1; + constraints[i] = data; + break; + } + case CONSTRAINT_SLIDER: { + var data = new SliderData(name); + int flags = input.Read(); + data.skinRequired = (flags & 1) != 0; + data.loop = (flags & 2) != 0; + data.additive = (flags & 4) != 0; + if ((flags & 8) != 0) data.setup.time = input.ReadFloat(); + if ((flags & 16) != 0) data.setup.mix = (flags & 32) != 0 ? input.ReadFloat() : 1; + if ((flags & 64) != 0) { + data.local = (flags & 128) != 0; + data.bone = bones[input.ReadInt(true)]; + float offset = input.ReadFloat(); + switch (input.ReadUByte()) { + case 0: data.property = new FromRotate(); break; + case 1: { + offset *= scale; + data.property = new FromX(); + break; + } + case 2: { + offset *= scale; + data.property = new FromY(); + break; + } + case 3: data.property = new FromScaleX(); break; + case 4: data.property = new FromScaleY(); break; + case 5: data.property = new FromShearY(); break; + default: data.property = null; break; + }; + data.property.offset = offset; + data.offset = input.ReadFloat(); + data.scale = input.ReadFloat(); + } + constraints[i] = data; break; } - case 3: from = new FromScaleX(); break; - case 4: from = new FromScaleY(); break; - case 5: from = new FromShearY(); break; - default: from = null; break; - }; - from.offset = input.ReadFloat() * fromScale; - ToProperty[] tos = from.to.Resize(tn = input.ReadSByte()).Items; - for (int t = 0; t < tn; t++) { - float toScale = 1; - ToProperty to; - switch (input.ReadSByte()) { - case 0: to = new ToRotate(); break; - case 1: { - to = new ToX(); - toScale = scale; - break; - } - case 2: { - to = new ToY(); - toScale = scale; - break; - } - case 3: to = new ToScaleX(); break; - case 4: to = new ToScaleY(); break; - case 5: to = new ToShearY(); break; - default: to = null; break; - }; - to.offset = input.ReadFloat() * toScale; - to.max = input.ReadFloat() * toScale; - to.scale = input.ReadFloat() * (toScale / fromScale); - tos[t] = to; } - froms[ii] = from; } - flags = input.Read(); - if ((flags & 1) != 0) data.offsetRotation = input.ReadFloat(); - if ((flags & 2) != 0) data.offsetX = input.ReadFloat() * scale; - if ((flags & 4) != 0) data.offsetY = input.ReadFloat() * scale; - if ((flags & 8) != 0) data.offsetScaleX = input.ReadFloat(); - if ((flags & 16) != 0) data.offsetScaleY = input.ReadFloat(); - if ((flags & 32) != 0) data.offsetShearY = input.ReadFloat(); - flags = input.Read(); - if ((flags & 1) != 0) data.mixRotate = input.ReadFloat(); - if ((flags & 2) != 0) data.mixX = input.ReadFloat(); - if ((flags & 4) != 0) data.mixY = input.ReadFloat(); - if ((flags & 8) != 0) data.mixScaleX = input.ReadFloat(); - if ((flags & 16) != 0) data.mixScaleY = input.ReadFloat(); - if ((flags & 32) != 0) data.mixShearY = input.ReadFloat(); - o[i] = data; - } - // Path constraints - o = skeletonData.pathConstraints.Resize(n = input.ReadInt(true)).Items; - for (int i = 0, nn; i < n; i++) { - var data = new PathConstraintData(input.ReadString()); - data.order = input.ReadInt(true); - data.skinRequired = input.ReadBoolean(); - BoneData[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; - for (int ii = 0; ii < nn; ii++) - constraintBones[ii] = bones[input.ReadInt(true)]; - data.slot = slots[input.ReadInt(true)]; - int flags = input.Read(); - data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(flags & 1); - data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue((flags >> 1) & 3); - data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue((flags >> 3) & 3); - if ((flags & 128) != 0) data.offsetRotation = input.ReadFloat(); - - data.position = input.ReadFloat(); - if (data.positionMode == PositionMode.Fixed) data.position *= scale; - data.spacing = input.ReadFloat(); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; - data.mixRotate = input.ReadFloat(); - data.mixX = input.ReadFloat(); - data.mixY = input.ReadFloat(); - o[i] = data; - } - - // Physics constraints. - o = skeletonData.physicsConstraints.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) { - var data = new PhysicsConstraintData(input.ReadString()); - data.order = input.ReadInt(true); - data.bone = bones[input.ReadInt(true)]; - int flags = input.Read(); - data.skinRequired = (flags & 1) != 0; - if ((flags & 2) != 0) data.x = input.ReadFloat(); - if ((flags & 4) != 0) data.y = input.ReadFloat(); - if ((flags & 8) != 0) data.rotate = input.ReadFloat(); - if ((flags & 16) != 0) data.scaleX = input.ReadFloat(); - if ((flags & 32) != 0) data.shearX = input.ReadFloat(); - data.limit = ((flags & 64) != 0 ? input.ReadFloat() : 5000) * scale; - data.step = 1f / input.ReadUByte(); - data.inertia = input.ReadFloat(); - data.strength = input.ReadFloat(); - data.damping = input.ReadFloat(); - data.massInverse = (flags & 128) != 0 ? input.ReadFloat() : 1; - data.wind = input.ReadFloat(); - data.gravity = input.ReadFloat(); - flags = input.Read(); - if ((flags & 1) != 0) data.inertiaGlobal = true; - if ((flags & 2) != 0) data.strengthGlobal = true; - if ((flags & 4) != 0) data.dampingGlobal = true; - if ((flags & 8) != 0) data.massGlobal = true; - if ((flags & 16) != 0) data.windGlobal = true; - if ((flags & 32) != 0) data.gravityGlobal = true; - if ((flags & 64) != 0) data.mixGlobal = true; - data.mix = (flags & 128) != 0 ? input.ReadFloat() : 1; - o[i] = data; - } - - // Default skin. - Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential); - if (defaultSkin != null) { - skeletonData.defaultSkin = defaultSkin; - skeletonData.skins.Add(defaultSkin); - } - - // Skins. - { - int i = skeletonData.skins.Count; - o = skeletonData.skins.Resize(n = i + input.ReadInt(true)).Items; - for (; i < n; i++) - o[i] = ReadSkin(input, skeletonData, false, nonessential); - } - - // Linked meshes. - n = linkedMeshes.Count; - for (int i = 0; i < n; i++) { - LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = skeletonData.skins.Items[linkedMesh.skinIndex]; - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.TimelineAttachment = linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh; - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - if (linkedMesh.mesh.Sequence == null) linkedMesh.mesh.UpdateRegion(); - } - linkedMeshes.Clear(); - - // Events. - o = skeletonData.events.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) { - var data = new EventData(input.ReadString()); - data.Int = input.ReadInt(false); - data.Float = input.ReadFloat(); - data.String = input.ReadString(); - data.AudioPath = input.ReadString(); - if (data.AudioPath != null) { - data.Volume = input.ReadFloat(); - data.Balance = input.ReadFloat(); + // Default skin. + Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential); + if (defaultSkin != null) { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.Add(defaultSkin); } - o[i] = data; - } - // Animations. - o = skeletonData.animations.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) - o[i] = ReadAnimation(input.ReadString(), input, skeletonData); + // Skins. + { + int i = skeletonData.skins.Count; + o = skeletonData.skins.Resize(n = i + input.ReadInt(true)).Items; + for (; i < n; i++) + o[i] = ReadSkin(input, skeletonData, false, nonessential); + } + + // Linked meshes. + n = linkedMeshes.Count; + for (int i = 0; i < n; i++) { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = skeletonData.skins.Items[linkedMesh.skinIndex]; + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.TimelineAttachment = linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh; + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + if (linkedMesh.mesh.Sequence == null) linkedMesh.mesh.UpdateRegion(); + } + linkedMeshes.Clear(); + + // Events. + o = skeletonData.events.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) { + var data = new EventData(input.ReadString()); + data.Int = input.ReadInt(false); + data.Float = input.ReadFloat(); + data.String = input.ReadString(); + data.AudioPath = input.ReadString(); + if (data.AudioPath != null) { + data.Volume = input.ReadFloat(); + data.Balance = input.ReadFloat(); + } + o[i] = data; + } + + // Animations. + Animation[] animations = skeletonData.animations.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + animations[i] = ReadAnimation(input, input.ReadString(), skeletonData); + + for (int i = 0; i < constraintCount; i++) { + SliderData data = constraints[i] as SliderData; + if (data != null) data.animation = animations[input.ReadInt(true)]; + } + } catch (IOException ex) { + if (version != null) throw new SerializationException("Error reading binary skeleton data, version: " + version, ex); + throw new SerializationException("Error binary skeleton data.", ex); + } // note: no need to close Input as in reference implementation. return skeletonData; } @@ -461,24 +512,15 @@ namespace Spine { if (nonessential) input.ReadInt(); // discard, Color.rgba8888ToColor(skin.color, input.readInt()); - Object[] bones = skin.bones.Resize(input.ReadInt(true)).Items; - BoneData[] bonesItems = skeletonData.bones.Items; - for (int i = 0, n = skin.bones.Count; i < n; i++) - bones[i] = bonesItems[input.ReadInt(true)]; + int n; + object[] from = skeletonData.bones.Items, to = skin.bones.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + to[i] = from[input.ReadInt(true)]; - IkConstraintData[] ikConstraintsItems = skeletonData.ikConstraints.Items; - for (int i = 0, n = input.ReadInt(true); i < n; i++) - skin.constraints.Add(ikConstraintsItems[input.ReadInt(true)]); - TransformConstraintData[] transformConstraintsItems = skeletonData.transformConstraints.Items; - for (int i = 0, n = input.ReadInt(true); i < n; i++) - skin.constraints.Add(transformConstraintsItems[input.ReadInt(true)]); - PathConstraintData[] pathConstraintsItems = skeletonData.pathConstraints.Items; - for (int i = 0, n = input.ReadInt(true); i < n; i++) - skin.constraints.Add(pathConstraintsItems[input.ReadInt(true)]); - PhysicsConstraintData[] physicsConstraintsItems = skeletonData.physicsConstraints.Items; - for (int i = 0, n = input.ReadInt(true); i < n; i++) - skin.constraints.Add(physicsConstraintsItems[input.ReadInt(true)]); - skin.constraints.TrimExcess(); + from = skeletonData.constraints.Items; + to = skin.constraints.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + to[i] = from[input.ReadInt(true)]; slotCount = input.ReadInt(true); } @@ -524,10 +566,7 @@ namespace Spine { region.rotation = rotation; region.width = width * scale; region.height = height * scale; - region.r = ((color & 0xff000000) >> 24) / 255f; - region.g = ((color & 0x00ff0000) >> 16) / 255f; - region.b = ((color & 0x0000ff00) >> 8) / 255f; - region.a = ((color & 0x000000ff)) / 255f; + region.SetColor(color.RGBA8888ToColor()); region.sequence = sequence; if (sequence == null) region.UpdateRegion(); return region; @@ -564,10 +603,7 @@ namespace Spine { MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence); if (mesh == null) return null; mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; + mesh.SetColor(color.RGBA8888ToColor()); mesh.bones = vertices.bones; mesh.vertices = vertices.vertices; mesh.WorldVerticesLength = vertices.length; @@ -584,7 +620,7 @@ namespace Spine { return mesh; } case AttachmentType.Linkedmesh: { - String path = (flags & 16) != 0 ? input.ReadStringRef() : name; + string path = (flags & 16) != 0 ? input.ReadStringRef() : name; uint color = (flags & 32) != 0 ? (uint)input.ReadInt() : 0xffffffff; Sequence sequence = (flags & 64) != 0 ? ReadSequence(input) : null; bool inheritTimelines = (flags & 128) != 0; @@ -599,10 +635,7 @@ namespace Spine { MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence); if (mesh == null) return null; mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; + mesh.SetColor(color.RGBA8888ToColor()); mesh.Sequence = sequence; if (nonessential) { mesh.Width = width * scale; @@ -618,7 +651,7 @@ namespace Spine { var lengths = new float[vertices.length / 6]; for (int i = 0, n = lengths.Length; i < n; i++) lengths[i] = input.ReadFloat() * scale; - if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; + if (nonessential) input.ReadInt(); // discard, int color = nonessential ? input.ReadInt() : 0; PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); if (path == null) return null; @@ -635,7 +668,7 @@ namespace Spine { float rotation = input.ReadFloat(); float x = input.ReadFloat(); float y = input.ReadFloat(); - if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; + if (nonessential) input.ReadInt(); // discard, int color = nonessential ? input.ReadInt() : 0; PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); if (point == null) return null; @@ -648,7 +681,7 @@ namespace Spine { case AttachmentType.Clipping: { int endSlotIndex = input.ReadInt(true); Vertices vertices = ReadVertices(input, (flags & 16) != 0); - if (nonessential) input.ReadInt(); + if (nonessential) input.ReadInt(); // discard, int color = nonessential ? input.readInt() : 0; ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); if (clip == null) return null; @@ -719,7 +752,7 @@ namespace Spine { /// SerializationException will be thrown when a Vertex attachment is not found. /// Throws IOException when a read operation fails. - private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData skeletonData) { + private Animation ReadAnimation (SkeletonInput input, string name, SkeletonData skeletonData) { var timelines = new ExposedList(input.ReadInt(true)); float scale = this.scale; @@ -1007,7 +1040,7 @@ namespace Spine { // Path constraint timelines. for (int i = 0, n = input.ReadInt(true); i < n; i++) { int index = input.ReadInt(true); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; + var data = (PathConstraintData)skeletonData.constraints.Items[index]; for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { int type = input.ReadUByte(), frameCount = input.ReadInt(true), bezierCount = input.ReadInt(true); switch (type) { @@ -1094,20 +1127,35 @@ namespace Spine { } } + // Slider timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) { + int index = input.ReadInt(true); + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { + int type = input.ReadSByte(), frameCount = input.ReadInt(true), bezierCount = input.ReadInt(true); + ConstraintTimeline1 newTimeline; + switch (type) { + case SLIDER_TIME: newTimeline = new SliderTimeline(frameCount, bezierCount, index); break; + case SLIDER_MIX: newTimeline = new SliderMixTimeline(frameCount, bezierCount, index); break; + default: throw new SerializationException(); + } + ReadTimeline(input, timelines, newTimeline, 1); + } + } + // Attachment timelines. for (int i = 0, n = input.ReadInt(true); i < n; i++) { Skin skin = skeletonData.skins.Items[input.ReadInt(true)]; 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++) { - String attachmentName = input.ReadStringRef(); + string attachmentName = input.ReadStringRef(); Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); if (attachment == null) throw new SerializationException("Timeline attachment not found: " + attachmentName); int timelineType = input.ReadUByte(), frameCount = input.ReadInt(true), frameLast = frameCount - 1; switch (timelineType) { case ATTACHMENT_DEFORM: { - VertexAttachment vertexAttachment = (VertexAttachment)attachment; + var vertexAttachment = (VertexAttachment)attachment; bool weighted = vertexAttachment.Bones != null; float[] vertices = vertexAttachment.Vertices; int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; @@ -1212,7 +1260,7 @@ namespace Spine { e.floatValue = input.ReadFloat(); e.stringValue = input.ReadString(); if (e.stringValue == null) e.stringValue = eventData.String; - if (e.Data.AudioPath != null) { + if (e.data.AudioPath != null) { e.volume = input.ReadFloat(); e.balance = input.ReadFloat(); } @@ -1250,7 +1298,7 @@ namespace Spine { } /// Throws IOException when a read operation fails. - private void ReadTimeline (SkeletonInput input, ExposedList timelines, CurveTimeline2 timeline, float scale) { + private void ReadTimeline (SkeletonInput input, ExposedList timelines, BoneTimeline2 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); diff --git a/spine-csharp/src/SkeletonBounds.cs b/spine-csharp/src/SkeletonBounds.cs index 6f0ac9f21..d4f688c9c 100644 --- a/spine-csharp/src/SkeletonBounds.cs +++ b/spine-csharp/src/SkeletonBounds.cs @@ -75,7 +75,7 @@ namespace Spine { for (int i = 0; i < slotCount; i++) { Slot slot = slots[i]; if (!slot.bone.active) continue; - BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; + BoundingBoxAttachment boundingBox = slot.applied.attachment as BoundingBoxAttachment; if (boundingBox == null) continue; boundingBoxes.Add(boundingBox); @@ -91,7 +91,7 @@ namespace Spine { int count = boundingBox.worldVerticesLength; polygon.Count = count; if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; - boundingBox.ComputeWorldVertices(slot, polygon.Vertices); + boundingBox.ComputeWorldVertices(skeleton, slot, polygon.Vertices); } if (updateAabb) { diff --git a/spine-csharp/src/SkeletonClipping.cs b/spine-csharp/src/SkeletonClipping.cs index afd60465d..19fc3757b 100644 --- a/spine-csharp/src/SkeletonClipping.cs +++ b/spine-csharp/src/SkeletonClipping.cs @@ -48,13 +48,13 @@ namespace Spine { public bool IsClipping { get { return clipAttachment != null; } } - public int ClipStart (Slot slot, ClippingAttachment clip) { + public int ClipStart (Skeleton skeleton, Slot slot, ClippingAttachment clip) { if (clipAttachment != null) return 0; clipAttachment = clip; int n = clip.worldVerticesLength; float[] vertices = clippingPolygon.Resize(n).Items; - clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); + clip.ComputeWorldVertices(skeleton, slot, 0, n, vertices, 0, 2); MakeClockwise(clippingPolygon); clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); foreach (ExposedList polygon in clippingPolygons) { diff --git a/spine-csharp/src/SkeletonData.cs b/spine-csharp/src/SkeletonData.cs index 885da4542..5744afd18 100644 --- a/spine-csharp/src/SkeletonData.cs +++ b/spine-csharp/src/SkeletonData.cs @@ -40,10 +40,7 @@ namespace Spine { internal Skin defaultSkin; internal ExposedList events = new ExposedList(); internal ExposedList animations = new ExposedList(); - internal ExposedList ikConstraints = new ExposedList(); - internal ExposedList transformConstraints = new ExposedList(); - internal ExposedList pathConstraints = new ExposedList(); - internal ExposedList physicsConstraints = new ExposedList(); + internal ExposedList constraints = new ExposedList(); internal float x, y, width, height, referenceScale = 100; internal string version, hash; @@ -75,14 +72,9 @@ namespace Spine { public ExposedList Events { get { return events; } set { events = value; } } /// The skeleton's animations. public ExposedList Animations { get { return animations; } set { animations = value; } } - /// The skeleton's IK constraints. - public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + /// The skeleton's constraints. + public ExposedList Constraints { get { return constraints; } } /// The skeleton's transform constraints. - public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } - /// The skeleton's path constraints. - public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } - /// The skeleton's physics constraints. - public ExposedList PhysicsConstraints { get { return physicsConstraints; } set { physicsConstraints = value; } } public float X { get { return x; } set { x = value; } } public float Y { get { return y; } set { y = value; } } @@ -110,7 +102,6 @@ namespace Spine { public float Fps { get { return fps; } set { fps = value; } } // --- Bones - /// /// Finds a bone by comparing each bone's name. /// It is more efficient to cache the results of this method than to call it multiple times. @@ -126,7 +117,6 @@ namespace Spine { } // --- Slots - /// May be null. public SlotData FindSlot (string slotName) { if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); @@ -139,7 +129,6 @@ namespace Spine { } // --- Skins - /// May be null. public Skin FindSkin (string skinName) { if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); @@ -149,7 +138,6 @@ namespace Spine { } // --- Events - /// May be null. public EventData FindEvent (string eventDataName) { if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); @@ -159,7 +147,6 @@ namespace Spine { } // --- Animations - /// May be null. public Animation FindAnimation (string animationName) { if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); @@ -171,68 +158,17 @@ namespace Spine { return null; } - // --- IK constraints - - /// May be null. - public IkConstraintData FindIkConstraint (string constraintName) { + // --- Constraints + public T FindConstraint (String constraintName) where T : class, IConstraintData { if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - IkConstraintData[] 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; + IConstraintData[] constraints = this.constraints.Items; + for (int i = 0, n = this.constraints.Count; i < n; i++) { + T constraint = constraints[i] as T; + if (constraint != null && constraint.Name.Equals(constraintName)) return constraint; } return null; } - // --- Transform constraints - - /// May be null. - public TransformConstraintData FindTransformConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - TransformConstraintData[] 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; - } - - // --- Path constraints - - /// - /// 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 multiple times. - /// - /// May be null. - public PathConstraintData FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - PathConstraintData[] 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; - } - - // --- Physics constraints - - /// - /// Finds a physics constraint by comparing each physics constraint's name. It is more efficient to cache the results of this - /// method than to call it multiple times. - /// - /// May be null. - public PhysicsConstraintData FindPhysicsConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - PhysicsConstraintData[] physicsConstraints = this.physicsConstraints.Items; - for (int i = 0, n = this.physicsConstraints.Count; i < n; i++) { - PhysicsConstraintData constraint = (PhysicsConstraintData)physicsConstraints[i]; - if (constraint.name.Equals(constraintName)) return constraint; - } - return null; - } - - // --- - override public string ToString () { return name ?? base.ToString(); } diff --git a/spine-csharp/src/SkeletonJson.cs b/spine-csharp/src/SkeletonJson.cs index 47e59da3c..aff91efd4 100644 --- a/spine-csharp/src/SkeletonJson.cs +++ b/spine-csharp/src/SkeletonJson.cs @@ -34,6 +34,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.Serialization; #if WINDOWS_STOREAPP using System.Threading.Tasks; @@ -41,6 +42,10 @@ using Windows.Storage; #endif namespace Spine { + using static Spine.TransformConstraintData; +#if IS_UNITY + using Color = UnityEngine.Color; +#endif using FromProperty = TransformConstraintData.FromProperty; using FromRotate = TransformConstraintData.FromRotate; @@ -108,418 +113,464 @@ namespace Spine { public SkeletonData ReadSkeletonData (TextReader reader) { if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); - float scale = this.scale; var skeletonData = new SkeletonData(); + try { + float scale = this.scale; + Dictionary root = Json.Deserialize(reader) as Dictionary; + if (root == null) throw new Exception("Invalid JSON."); - Dictionary root = Json.Deserialize(reader) as Dictionary; - if (root == null) throw new Exception("Invalid JSON."); - - // Skeleton. - if (root.ContainsKey("skeleton")) { - Dictionary skeletonMap = (Dictionary)root["skeleton"]; - skeletonData.hash = (string)skeletonMap["hash"]; - skeletonData.version = (string)skeletonMap["spine"]; - skeletonData.x = GetFloat(skeletonMap, "x", 0); - skeletonData.y = GetFloat(skeletonMap, "y", 0); - skeletonData.width = GetFloat(skeletonMap, "width", 0); - skeletonData.height = GetFloat(skeletonMap, "height", 0); - skeletonData.referenceScale = GetFloat(skeletonMap, "referenceScale", 100) * scale; - skeletonData.fps = GetFloat(skeletonMap, "fps", 30); - skeletonData.imagesPath = GetString(skeletonMap, "images", null); - skeletonData.audioPath = GetString(skeletonMap, "audio", null); - } - - // Bones. - if (root.ContainsKey("bones")) { - foreach (Dictionary boneMap in (List)root["bones"]) { - BoneData parent = null; - if (boneMap.ContainsKey("parent")) { - parent = skeletonData.FindBone((string)boneMap["parent"]); - if (parent == null) - throw new Exception("Parent bone not found: " + boneMap["parent"]); - } - var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent); - data.length = GetFloat(boneMap, "length", 0) * scale; - data.x = GetFloat(boneMap, "x", 0) * scale; - data.y = GetFloat(boneMap, "y", 0) * scale; - data.rotation = GetFloat(boneMap, "rotation", 0); - data.scaleX = GetFloat(boneMap, "scaleX", 1); - data.scaleY = GetFloat(boneMap, "scaleY", 1); - data.shearX = GetFloat(boneMap, "shearX", 0); - data.shearY = GetFloat(boneMap, "shearY", 0); - - string inheritString = GetString(boneMap, "inherit", Inherit.Normal.ToString()); - data.inherit = (Inherit)Enum.Parse(typeof(Inherit), inheritString, true); - data.skinRequired = GetBoolean(boneMap, "skin", false); - - skeletonData.bones.Add(data); + // Skeleton. + if (root.ContainsKey("skeleton")) { + Dictionary skeletonMap = (Dictionary)root["skeleton"]; + skeletonData.hash = (string)skeletonMap["hash"]; + skeletonData.version = (string)skeletonMap["spine"]; + skeletonData.x = GetFloat(skeletonMap, "x", 0); + skeletonData.y = GetFloat(skeletonMap, "y", 0); + skeletonData.width = GetFloat(skeletonMap, "width", 0); + skeletonData.height = GetFloat(skeletonMap, "height", 0); + skeletonData.referenceScale = GetFloat(skeletonMap, "referenceScale", 100) * scale; + skeletonData.fps = GetFloat(skeletonMap, "fps", 30); + skeletonData.imagesPath = GetString(skeletonMap, "images", null); + skeletonData.audioPath = GetString(skeletonMap, "audio", null); } - } - // Slots. - if (root.ContainsKey("slots")) { - foreach (Dictionary slotMap in (List)root["slots"]) { - string slotName = (string)slotMap["name"]; - string boneName = (string)slotMap["bone"]; - BoneData boneData = skeletonData.FindBone(boneName); - if (boneData == null) throw new Exception("Slot bone not found: " + boneName); - var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); - - if (slotMap.ContainsKey("color")) { - string color = (string)slotMap["color"]; - data.r = ToColor(color, 0); - data.g = ToColor(color, 1); - data.b = ToColor(color, 2); - data.a = ToColor(color, 3); - } - - if (slotMap.ContainsKey("dark")) { - string color2 = (string)slotMap["dark"]; - data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" - data.g2 = ToColor(color2, 1, 6); - data.b2 = ToColor(color2, 2, 6); - data.hasSecondColor = true; - } - - data.attachmentName = GetString(slotMap, "attachment", null); - if (slotMap.ContainsKey("blend")) - data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true); - else - data.blendMode = BlendMode.Normal; - //data.visible = slotMap.getBoolean("visible", true); - skeletonData.slots.Add(data); - } - } - - // IK constraints. - if (root.ContainsKey("ik")) { - foreach (Dictionary constraintMap in (List)root["ik"]) { - var data = new IkConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - data.skinRequired = GetBoolean(constraintMap, "skin", false); - - if (constraintMap.ContainsKey("bones")) { - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("IK bone not found: " + boneName); - data.bones.Add(bone); + // Bones. + if (root.ContainsKey("bones")) { + foreach (Dictionary boneMap in (List)root["bones"]) { + BoneData parent = null; + if (boneMap.ContainsKey("parent")) { + parent = skeletonData.FindBone((string)boneMap["parent"]); + if (parent == null) + throw new Exception("Parent bone not found: " + boneMap["parent"]); } + var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent); + data.length = GetFloat(boneMap, "length", 0) * scale; + BoneLocal setup = data.setup; + setup.x = GetFloat(boneMap, "x", 0) * scale; + setup.y = GetFloat(boneMap, "y", 0) * scale; + setup.rotation = GetFloat(boneMap, "rotation", 0); + setup.scaleX = GetFloat(boneMap, "scaleX", 1); + setup.scaleY = GetFloat(boneMap, "scaleY", 1); + setup.shearX = GetFloat(boneMap, "shearX", 0); + setup.shearY = GetFloat(boneMap, "shearY", 0); + string inheritString = GetString(boneMap, "inherit", Inherit.Normal.ToString()); + setup.inherit = (Inherit)Enum.Parse(typeof(Inherit), inheritString, true); + data.skinRequired = GetBoolean(boneMap, "skin", false); + + skeletonData.bones.Add(data); } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("IK target bone not found: " + targetName); - data.mix = GetFloat(constraintMap, "mix", 1); - data.softness = GetFloat(constraintMap, "softness", 0) * scale; - data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; - data.compress = GetBoolean(constraintMap, "compress", false); - data.stretch = GetBoolean(constraintMap, "stretch", false); - data.uniform = GetBoolean(constraintMap, "uniform", false); - - skeletonData.ikConstraints.Add(data); } - } - // Transform constraints. - if (root.ContainsKey("transform")) { - foreach (Dictionary constraintMap in (List)root["transform"]) { - var data = new TransformConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - data.skinRequired = GetBoolean(constraintMap, "skin", false); + // Slots. + if (root.ContainsKey("slots")) { + foreach (Dictionary slotMap in (List)root["slots"]) { + string slotName = (string)slotMap["name"]; + string boneName = (string)slotMap["bone"]; + BoneData boneData = skeletonData.FindBone(boneName); + if (boneData == null) throw new Exception("Slot bone not found: " + boneName); + var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); - if (constraintMap.ContainsKey("bones")) { - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); - data.bones.Add(bone); + if (slotMap.ContainsKey("color")) { + string color = (string)slotMap["color"]; + data.setup.SetColor(ToColor32(color, 8)); } + + if (slotMap.ContainsKey("dark")) { + string color2 = (string)slotMap["dark"]; + data.setup.SetDarkColor(ToColor24(color2, 6)); // expectedLength = 6. ie. "RRGGBB" + } + + data.attachmentName = GetString(slotMap, "attachment", null); + if (slotMap.ContainsKey("blend")) + data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true); + else + data.blendMode = BlendMode.Normal; + //data.visible = slotMap.getBoolean("visible", true); + skeletonData.slots.Add(data); } + } - string sourceName = (string)constraintMap["source"]; - data.source = skeletonData.FindBone(sourceName); - if (data.source == null) throw new Exception("Transform constraint source bone not found: " + sourceName); + // Constraints. + if (root.ContainsKey("constraints")) { + foreach (Dictionary constraintMap in (List)root["constraints"]) { + string name = (string)constraintMap["name"]; + bool skinRequired = GetBoolean(constraintMap, "skin", false); - data.localSource = GetBoolean(constraintMap, "localSource", false); - data.localTarget = GetBoolean(constraintMap, "localTarget", false); - data.additive = GetBoolean(constraintMap, "additive", false); - data.clamp = GetBoolean(constraintMap, "clamp", false); + switch ((string)constraintMap["type"]) { + case "ik": { + var data = new IkConstraintData(name); + data.skinRequired = skinRequired; - bool rotate = false, x = false, y = false, scaleX = false, scaleY = false, shearY = false; - if (constraintMap.ContainsKey("properties")) { - foreach (KeyValuePair fromEntryObject in (Dictionary)constraintMap["properties"]) { - var fromEntry = (Dictionary)fromEntryObject.Value; - string fromEntryName = fromEntryObject.Key; - - float fromScale = 1; - FromProperty from; - switch (fromEntryName) { - case "rotate": from = new FromRotate(); break; - case "x": { - from = new FromX(); - fromScale = scale; - break; - } - case "y": { - from = new FromY(); - fromScale = scale; - break; - } - case "scaleX": from = new FromScaleX(); break; - case "scaleY": from = new FromScaleY(); break; - case "shearY": from = new FromShearY(); break; - default: throw new Exception("Invalid transform constraint from property: " + fromEntryName); - }; - - from.offset = GetFloat(fromEntry, "offset", 0) * fromScale; - if (fromEntry.ContainsKey("to")) { - foreach (KeyValuePair toEntryObject in (Dictionary)fromEntry["to"]) { - var toEntry = (Dictionary)toEntryObject.Value; - string toEntryName = toEntryObject.Key; - - float toScale = 1; - ToProperty to; - switch (toEntryName) { - case "rotate": { - rotate = true; - to = new ToRotate(); - break; - } - case "x": { - x = true; - to = new ToX(); - toScale = scale; - break; - } - case "y": { - y = true; - to = new ToY(); - toScale = scale; - break; - } - case "scaleX": { - scaleX = true; - to = new ToScaleX(); - break; - } - case "scaleY": { - scaleY = true; - to = new ToScaleY(); - break; - } - case "shearY": { - shearY = true; - to = new ToShearY(); - break; - } - default: throw new Exception("Invalid transform constraint to property: " + toEntryName); - } - to.offset = GetFloat(toEntry, "offset", 0) * toScale; - to.max = GetFloat(toEntry, "max", 1) * toScale; - to.scale = GetFloat(toEntry, "scale") * (toScale / fromScale); - from.to.Add(to); + if (constraintMap.ContainsKey("bones")) { + foreach (string boneName in (List)constraintMap["bones"]) { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("IK bone not found: " + boneName); + data.bones.Add(bone); } } - if (from.to.Count != 0) data.properties.Add(from); + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("IK target bone not found: " + targetName); + + data.uniform = GetBoolean(constraintMap, "uniform", false); + IkConstraintPose setup = data.setup; + setup.mix = GetFloat(constraintMap, "mix", 1); + setup.softness = GetFloat(constraintMap, "softness", 0) * scale; + setup.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; + setup.compress = GetBoolean(constraintMap, "compress", false); + setup.stretch = GetBoolean(constraintMap, "stretch", false); + + skeletonData.constraints.Add(data); + break; + } + case "transform": { + var data = new TransformConstraintData(name); + data.skinRequired = skinRequired; + + if (constraintMap.ContainsKey("bones")) { + foreach (string boneName in (List)constraintMap["bones"]) { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); + data.bones.Add(bone); + } + } + + string sourceName = (string)constraintMap["source"]; + data.source = skeletonData.FindBone(sourceName); + if (data.source == null) throw new Exception("Transform constraint source bone not found: " + sourceName); + + data.localSource = GetBoolean(constraintMap, "localSource", false); + data.localTarget = GetBoolean(constraintMap, "localTarget", false); + data.additive = GetBoolean(constraintMap, "additive", false); + data.clamp = GetBoolean(constraintMap, "clamp", false); + + bool rotate = false, x = false, y = false, scaleX = false, scaleY = false, shearY = false; + if (constraintMap.ContainsKey("properties")) { + foreach (KeyValuePair fromEntryObject in (Dictionary)constraintMap["properties"]) { + var fromEntry = (Dictionary)fromEntryObject.Value; + string fromEntryName = fromEntryObject.Key; + + FromProperty from = FromProperty(fromEntryName); + float fromScale = PropertyScale(fromEntryName, scale); + from.offset = GetFloat(fromEntry, "offset", 0) * fromScale; + if (fromEntry.ContainsKey("to")) { + foreach (KeyValuePair toEntryObject in (Dictionary)fromEntry["to"]) { + var toEntry = (Dictionary)toEntryObject.Value; + string toEntryName = toEntryObject.Key; + + float toScale = 1; + ToProperty to; + switch (toEntryName) { + case "rotate": { + rotate = true; + to = new ToRotate(); + break; + } + case "x": { + x = true; + to = new ToX(); + toScale = scale; + break; + } + case "y": { + y = true; + to = new ToY(); + toScale = scale; + break; + } + case "scaleX": { + scaleX = true; + to = new ToScaleX(); + break; + } + case "scaleY": { + scaleY = true; + to = new ToScaleY(); + break; + } + case "shearY": { + shearY = true; + to = new ToShearY(); + break; + } + default: throw new Exception("Invalid transform constraint to property: " + toEntryName); + } + to.offset = GetFloat(toEntry, "offset", 0) * toScale; + to.max = GetFloat(toEntry, "max", 1) * toScale; + to.scale = GetFloat(toEntry, "scale") * (toScale / fromScale); + from.to.Add(to); + } + } + if (from.to.Count != 0) data.properties.Add(from); + } + } + + data.offsets[TransformConstraintData.ROTATION] = GetFloat(constraintMap, "rotation", 0); + data.offsets[TransformConstraintData.X] = GetFloat(constraintMap, "x", 0) * scale; + data.offsets[TransformConstraintData.Y] = GetFloat(constraintMap, "y", 0) * scale; + data.offsets[TransformConstraintData.SCALEX] = GetFloat(constraintMap, "scaleX", 0); + data.offsets[TransformConstraintData.SCALEY] = GetFloat(constraintMap, "scaleY", 0); + data.offsets[TransformConstraintData.SHEARY] = GetFloat(constraintMap, "shearY", 0); + + TransformConstraintPose setup = data.setup; + if (rotate) setup.mixRotate = GetFloat(constraintMap, "mixRotate", 1); + if (x) setup.mixX = GetFloat(constraintMap, "mixX", 1); + if (y) setup.mixY = GetFloat(constraintMap, "mixY", setup.mixX); + if (scaleX) setup.mixScaleX = GetFloat(constraintMap, "mixScaleX", 1); + if (scaleY) setup.mixScaleY = GetFloat(constraintMap, "mixScaleY", setup.mixScaleX); + if (shearY) setup.mixShearY = GetFloat(constraintMap, "mixShearY", 1); + + skeletonData.constraints.Add(data); + break; + } + case "path": { + var data = new PathConstraintData(name); + data.skinRequired = skinRequired; + + if (constraintMap.ContainsKey("bones")) { + foreach (string boneName in (List)constraintMap["bones"]) { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Path bone not found: " + boneName); + data.bones.Add(bone); + } + } + + string slotName = (string)constraintMap["slot"]; + data.slot = skeletonData.FindSlot(slotName); + if (data.slot == null) throw new Exception("Path slot not found: " + slotName); + + data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); + data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); + data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + PathConstraintPose setup = data.setup; + setup.position = GetFloat(constraintMap, "position", 0); + if (data.positionMode == PositionMode.Fixed) setup.position *= scale; + setup.spacing = GetFloat(constraintMap, "spacing", 0); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) setup.spacing *= scale; + setup.mixRotate = GetFloat(constraintMap, "mixRotate", 1); + setup.mixX = GetFloat(constraintMap, "mixX", 1); + setup.mixY = GetFloat(constraintMap, "mixY", setup.mixX); + + skeletonData.constraints.Add(data); + break; + } + case "physics": { + var data = new PhysicsConstraintData(name); + data.skinRequired = skinRequired; + + string boneName = (string)constraintMap["bone"]; + data.bone = skeletonData.FindBone(boneName); + if (data.bone == null) throw new Exception("Physics bone not found: " + boneName); + + data.x = GetFloat(constraintMap, "x", 0); + data.y = GetFloat(constraintMap, "y", 0); + data.rotate = GetFloat(constraintMap, "rotate", 0); + data.scaleX = GetFloat(constraintMap, "scaleX", 0); + data.shearX = GetFloat(constraintMap, "shearX", 0); + data.limit = GetFloat(constraintMap, "limit", 5000) * scale; + data.step = 1f / GetInt(constraintMap, "fps", 60); + PhysicsConstraintPose setup = data.setup; + setup.inertia = GetFloat(constraintMap, "inertia", 0.5f); + setup.strength = GetFloat(constraintMap, "strength", 100); + setup.damping = GetFloat(constraintMap, "damping", 0.85f); + setup.massInverse = 1f / GetFloat(constraintMap, "mass", 1); + setup.wind = GetFloat(constraintMap, "wind", 0); + setup.gravity = GetFloat(constraintMap, "gravity", 0); + setup.mix = GetFloat(constraintMap, "mix", 1); + data.inertiaGlobal = GetBoolean(constraintMap, "inertiaGlobal", false); + data.strengthGlobal = GetBoolean(constraintMap, "strengthGlobal", false); + data.dampingGlobal = GetBoolean(constraintMap, "dampingGlobal", false); + data.massGlobal = GetBoolean(constraintMap, "massGlobal", false); + data.windGlobal = GetBoolean(constraintMap, "windGlobal", false); + data.gravityGlobal = GetBoolean(constraintMap, "gravityGlobal", false); + data.mixGlobal = GetBoolean(constraintMap, "mixGlobal", false); + + skeletonData.constraints.Add(data); + break; + } + case "slider": { + var data = new SliderData(name); + data.skinRequired = skinRequired; + + data.additive = GetBoolean(constraintMap, "additive", false); + data.loop = GetBoolean(constraintMap, "loop", false); + data.setup.time = GetFloat(constraintMap, "time", 0); + data.setup.mix = GetFloat(constraintMap, "mix", 1); + + string boneName = GetString(constraintMap, "bone", null); + if (boneName != null) { + data.bone = skeletonData.FindBone(boneName); + if (data.bone == null) throw new Exception("Slider bone not found: " + boneName); + string property = (string)constraintMap["property"]; + data.property = FromProperty(property); + data.property.offset = GetFloat(constraintMap, "from", 0) * PropertyScale(property, scale); + data.offset = GetFloat(constraintMap, "to", 0); + data.scale = GetFloat(constraintMap, "scale", 1); + data.local = GetBoolean(constraintMap, "local", false); + } + + skeletonData.constraints.Add(data); + break; + } } } - - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.offsetX = GetFloat(constraintMap, "x", 0) * scale; - data.offsetY = GetFloat(constraintMap, "y", 0) * scale; - data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); - data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); - data.offsetShearY = GetFloat(constraintMap, "shearY", 0); - - if (rotate) data.mixRotate = GetFloat(constraintMap, "mixRotate", 1); - if (x) data.mixX = GetFloat(constraintMap, "mixX", 1); - if (y) data.mixY = GetFloat(constraintMap, "mixY", data.mixX); - if (scaleX) data.mixScaleX = GetFloat(constraintMap, "mixScaleX", 1); - if (scaleY) data.mixScaleY = GetFloat(constraintMap, "mixScaleY", data.mixScaleX); - if (shearY) data.mixShearY = GetFloat(constraintMap, "mixShearY", 1); - - skeletonData.transformConstraints.Add(data); } - } - // Path constraints. - if (root.ContainsKey("path")) { - foreach (Dictionary constraintMap in (List)root["path"]) { - var data = new PathConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - data.skinRequired = GetBoolean(constraintMap, "skin", false); - - if (constraintMap.ContainsKey("bones")) { - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Path bone not found: " + boneName); - data.bones.Add(bone); + // Skins. + if (root.ContainsKey("skins")) { + foreach (Dictionary skinMap in (List)root["skins"]) { + var skin = new Skin((string)skinMap["name"]); + if (skinMap.ContainsKey("bones")) { + foreach (string entryName in (List)skinMap["bones"]) { + BoneData bone = skeletonData.FindBone(entryName); + if (bone == null) throw new Exception("Skin bone not found: " + entryName); + skin.bones.Add(bone); + } } - } - - string slotName = (string)constraintMap["slot"]; - data.slot = skeletonData.FindSlot(slotName); - if (data.slot == null) throw new Exception("Path slot not found: " + slotName); - - data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); - data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); - data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.position = GetFloat(constraintMap, "position", 0); - if (data.positionMode == PositionMode.Fixed) data.position *= scale; - data.spacing = GetFloat(constraintMap, "spacing", 0); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; - data.mixRotate = GetFloat(constraintMap, "mixRotate", 1); - data.mixX = GetFloat(constraintMap, "mixX", 1); - data.mixY = GetFloat(constraintMap, "mixY", data.mixX); - - skeletonData.pathConstraints.Add(data); - } - } - - // Physics constraints. - if (root.ContainsKey("physics")) { - foreach (Dictionary constraintMap in (List)root["physics"]) { - var data = new PhysicsConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - data.skinRequired = GetBoolean(constraintMap, "skin", false); - - string boneName = (string)constraintMap["bone"]; - data.bone = skeletonData.FindBone(boneName); - if (data.bone == null) throw new Exception("Physics bone not found: " + boneName); - - data.x = GetFloat(constraintMap, "x", 0); - data.y = GetFloat(constraintMap, "y", 0); - data.rotate = GetFloat(constraintMap, "rotate", 0); - data.scaleX = GetFloat(constraintMap, "scaleX", 0); - data.shearX = GetFloat(constraintMap, "shearX", 0); - data.limit = GetFloat(constraintMap, "limit", 5000) * scale; - data.step = 1f / GetInt(constraintMap, "fps", 60); - data.inertia = GetFloat(constraintMap, "inertia", 1); - data.strength = GetFloat(constraintMap, "strength", 100); - data.damping = GetFloat(constraintMap, "damping", 1); - data.massInverse = 1f / GetFloat(constraintMap, "mass", 1); - data.wind = GetFloat(constraintMap, "wind", 0); - data.gravity = GetFloat(constraintMap, "gravity", 0); - data.mix = GetFloat(constraintMap, "mix", 1); - data.inertiaGlobal = GetBoolean(constraintMap, "inertiaGlobal", false); - data.strengthGlobal = GetBoolean(constraintMap, "strengthGlobal", false); - data.dampingGlobal = GetBoolean(constraintMap, "dampingGlobal", false); - data.massGlobal = GetBoolean(constraintMap, "massGlobal", false); - data.windGlobal = GetBoolean(constraintMap, "windGlobal", false); - data.gravityGlobal = GetBoolean(constraintMap, "gravityGlobal", false); - data.mixGlobal = GetBoolean(constraintMap, "mixGlobal", false); - - skeletonData.physicsConstraints.Add(data); - } - } - - // Skins. - if (root.ContainsKey("skins")) { - foreach (Dictionary skinMap in (List)root["skins"]) { - var skin = new Skin((string)skinMap["name"]); - if (skinMap.ContainsKey("bones")) { - foreach (string entryName in (List)skinMap["bones"]) { - BoneData bone = skeletonData.FindBone(entryName); - if (bone == null) throw new Exception("Skin bone not found: " + entryName); - skin.bones.Add(bone); + skin.bones.TrimExcess(); + if (skinMap.ContainsKey("ik")) { + foreach (string entryName in (List)skinMap["ik"]) { + IkConstraintData constraint = skeletonData.FindConstraint(entryName); + if (constraint == null) throw new Exception("Skin IK constraint not found: " + entryName); + skin.constraints.Add(constraint); + } } - } - skin.bones.TrimExcess(); - if (skinMap.ContainsKey("ik")) { - foreach (string entryName in (List)skinMap["ik"]) { - IkConstraintData constraint = skeletonData.FindIkConstraint(entryName); - if (constraint == null) throw new Exception("Skin IK constraint not found: " + entryName); - skin.constraints.Add(constraint); + if (skinMap.ContainsKey("transform")) { + foreach (string entryName in (List)skinMap["transform"]) { + TransformConstraintData constraint = skeletonData.FindConstraint(entryName); + if (constraint == null) throw new Exception("Skin transform constraint not found: " + entryName); + skin.constraints.Add(constraint); + } } - } - if (skinMap.ContainsKey("transform")) { - foreach (string entryName in (List)skinMap["transform"]) { - TransformConstraintData constraint = skeletonData.FindTransformConstraint(entryName); - if (constraint == null) throw new Exception("Skin transform constraint not found: " + entryName); - skin.constraints.Add(constraint); + if (skinMap.ContainsKey("path")) { + foreach (string entryName in (List)skinMap["path"]) { + PathConstraintData constraint = skeletonData.FindConstraint(entryName); + if (constraint == null) throw new Exception("Skin path constraint not found: " + entryName); + skin.constraints.Add(constraint); + } } - } - if (skinMap.ContainsKey("path")) { - foreach (string entryName in (List)skinMap["path"]) { - PathConstraintData constraint = skeletonData.FindPathConstraint(entryName); - if (constraint == null) throw new Exception("Skin path constraint not found: " + entryName); - skin.constraints.Add(constraint); + if (skinMap.ContainsKey("physics")) { + foreach (string entryName in (List)skinMap["physics"]) { + PhysicsConstraintData constraint = skeletonData.FindConstraint(entryName); + if (constraint == null) throw new Exception("Skin physics constraint not found: " + entryName); + skin.constraints.Add(constraint); + } } - } - if (skinMap.ContainsKey("physics")) { - foreach (string entryName in (List)skinMap["physics"]) { - PhysicsConstraintData constraint = skeletonData.FindPhysicsConstraint(entryName); - if (constraint == null) throw new Exception("Skin physics constraint not found: " + entryName); - skin.constraints.Add(constraint); + if (skinMap.ContainsKey("slider")) { + foreach (string entryName in (List)skinMap["slider"]) { + SliderData constraint = skeletonData.FindConstraint(entryName); + if (constraint == null) throw new Exception("Skin slider not found: " + entryName); + skin.constraints.Add(constraint); + } } - } - skin.constraints.TrimExcess(); - if (skinMap.ContainsKey("attachments")) { - foreach (KeyValuePair slotEntry in (Dictionary)skinMap["attachments"]) { - int slotIndex = FindSlotIndex(skeletonData, slotEntry.Key); - foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) { - try { - Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); - if (attachment != null) skin.SetAttachment(slotIndex, entry.Key, attachment); - } catch (Exception e) { - throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); + + skin.constraints.TrimExcess(); + if (skinMap.ContainsKey("attachments")) { + foreach (KeyValuePair slotEntry in (Dictionary)skinMap["attachments"]) { + int slotIndex = FindSlotIndex(skeletonData, slotEntry.Key); + foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) { + try { + Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); + if (attachment != null) skin.SetAttachment(slotIndex, entry.Key, attachment); + } catch (Exception e) { + throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); + } } } } - } - skeletonData.skins.Add(skin); - if (skin.name == "default") skeletonData.defaultSkin = skin; - } - } - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.TimelineAttachment = linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh; - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - if (linkedMesh.mesh.Region != null) linkedMesh.mesh.UpdateRegion(); - } - linkedMeshes.Clear(); - - // Events. - if (root.ContainsKey("events")) { - foreach (KeyValuePair entry in (Dictionary)root["events"]) { - Dictionary entryMap = (Dictionary)entry.Value; - var data = new EventData(entry.Key); - data.Int = GetInt(entryMap, "int", 0); - data.Float = GetFloat(entryMap, "float", 0); - data.String = GetString(entryMap, "string", string.Empty); - data.AudioPath = GetString(entryMap, "audio", null); - if (data.AudioPath != null) { - data.Volume = GetFloat(entryMap, "volume", 1); - data.Balance = GetFloat(entryMap, "balance", 0); - } - skeletonData.events.Add(data); - } - } - - // Animations. - if (root.ContainsKey("animations")) { - foreach (KeyValuePair entry in (Dictionary)root["animations"]) { - try { - ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); - } catch (Exception e) { - throw new Exception("Error reading animation: " + entry.Key + "\n" + e.Message, e); + skeletonData.skins.Add(skin); + if (skin.name == "default") skeletonData.defaultSkin = skin; } } - } - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - return skeletonData; + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.TimelineAttachment = linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh; + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + if (linkedMesh.mesh.Region != null) linkedMesh.mesh.UpdateRegion(); + } + linkedMeshes.Clear(); + + // Events. + if (root.ContainsKey("events")) { + foreach (KeyValuePair entry in (Dictionary)root["events"]) { + Dictionary entryMap = (Dictionary)entry.Value; + var data = new EventData(entry.Key); + data.Int = GetInt(entryMap, "int", 0); + data.Float = GetFloat(entryMap, "float", 0); + data.String = GetString(entryMap, "string", string.Empty); + data.AudioPath = GetString(entryMap, "audio", null); + if (data.AudioPath != null) { + data.Volume = GetFloat(entryMap, "volume", 1); + data.Balance = GetFloat(entryMap, "balance", 0); + } + skeletonData.events.Add(data); + } + } + + // Animations. + if (root.ContainsKey("animations")) { + foreach (KeyValuePair entry in (Dictionary)root["animations"]) { + try { + ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); + } catch (Exception e) { + throw new Exception("Error reading animation: " + entry.Key + "\n" + e.Message, e); + } + } + } + + // Slider animations. + if (root.ContainsKey("constraints")) { + foreach (Dictionary constraintMap in (List)root["constraints"]) { + if (GetString(constraintMap, "type", string.Empty) == "slider") { + SliderData data = skeletonData.FindConstraint((string)constraintMap["name"]); + string animationName = (string)constraintMap["animation"]; + data.animation = skeletonData.FindAnimation(animationName); + if (data.animation == null) throw new Exception("Slider animation not found: " + animationName); + } + } + } + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.constraints.TrimExcess(); + return skeletonData; + } catch (Exception ex) { + if (skeletonData.version != null) + throw new SerializationException("Error reading JSON skeleton data, version: " + skeletonData.version, ex); + throw new SerializationException("Error JSON skeleton data.", ex); + } + } + + private FromProperty FromProperty (String type) { + switch (type) { + case "rotate": return new FromRotate(); + case "x": return new FromX(); + case "y": return new FromY(); + case "scaleX": return new FromScaleX(); + case "scaleY": return new FromScaleY(); + case "shearY": return new FromShearY(); + default: throw new Exception("Invalid from property: " + type); + }; + } + + private float PropertyScale (String type, float scale) { + switch (type) { + case "x": + case "y": + return scale; + default: return 1; + }; } private Attachment ReadAttachment (Dictionary map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) { @@ -543,16 +594,13 @@ namespace Spine { region.scaleX = GetFloat(map, "scaleX", 1); region.scaleY = GetFloat(map, "scaleY", 1); region.rotation = GetFloat(map, "rotation", 0); - region.width = GetFloat(map, "width", 32) * scale; - region.height = GetFloat(map, "height", 32) * scale; + region.width = GetFloat(map, "width") * scale; + region.height = GetFloat(map, "height") * scale; region.sequence = sequence; if (map.ContainsKey("color")) { string color = (string)map["color"]; - region.r = ToColor(color, 0); - region.g = ToColor(color, 1); - region.b = ToColor(color, 2); - region.a = ToColor(color, 3); + region.SetColor(ToColor32(color, 8)); } if (region.Region != null) region.UpdateRegion(); @@ -575,10 +623,7 @@ namespace Spine { if (map.ContainsKey("color")) { string color = (string)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); + mesh.SetColor(ToColor32(color, 0)); } mesh.Width = GetFloat(map, "width", 0) * scale; @@ -662,7 +707,7 @@ namespace Spine { float scale = Scale; if (verticesLength == vertices.Length) { if (scale != 1) { - for (int i = 0; i < vertices.Length; i++) { + for (int i = 0, n = vertices.Length; i < n; i++) { vertices[i] *= scale; } } @@ -676,8 +721,8 @@ namespace Spine { bones.Add(boneCount); 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); + weights.Add(vertices[i + 1] * scale); + weights.Add(vertices[i + 2] * scale); weights.Add(vertices[i + 3]); } } @@ -804,7 +849,7 @@ namespace Spine { case "alpha": { List.Enumerator keyMapEnumerator = values.GetEnumerator(); keyMapEnumerator.MoveNext(); - timelines.Add(ReadTimeline(ref keyMapEnumerator, new AlphaTimeline(frames, frames, slotIndex), 0, 1)); + ReadTimeline(ref timelines, ref keyMapEnumerator, new AlphaTimeline(frames, frames, slotIndex), 0, 1); break; } case "rgba2": { @@ -919,8 +964,6 @@ namespace Spine { timelines.Add(timeline); break; } - default: - throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); } } } @@ -930,15 +973,8 @@ namespace Spine { if (map.ContainsKey("bones")) { foreach (KeyValuePair entry in (Dictionary)map["bones"]) { string boneName = entry.Key; - int boneIndex = -1; - BoneData[] bones = skeletonData.bones.Items; - for (int i = 0, n = skeletonData.bones.Count; i < n; i++) { - if (bones[i].name == boneName) { - boneIndex = i; - break; - } - } - if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Bone not found: " + boneName); Dictionary timelineMap = (Dictionary)entry.Value; foreach (KeyValuePair timelineEntry in timelineMap) { List values = (List)timelineEntry.Value; @@ -948,44 +984,42 @@ namespace Spine { string timelineName = (string)timelineEntry.Key; switch (timelineName) { case "rotate": - timelines.Add(ReadTimeline(ref keyMapEnumerator, new RotateTimeline(frames, frames, boneIndex), 0, 1)); + ReadTimeline(ref timelines, ref keyMapEnumerator, new RotateTimeline(frames, frames, bone.index), 0, 1); break; case "translate": { - timelines.Add(ReadTimeline(ref keyMapEnumerator, new TranslateTimeline(frames, frames << 1, boneIndex), "x", "y", 0, scale)); + ReadTimeline(ref timelines, ref keyMapEnumerator, new TranslateTimeline(frames, frames << 1, bone.index), "x", "y", 0, scale); break; } case "translatex": { - timelines - .Add(ReadTimeline(ref keyMapEnumerator, new TranslateXTimeline(frames, frames, boneIndex), 0, scale)); + ReadTimeline(ref timelines, ref keyMapEnumerator, new TranslateXTimeline(frames, frames, bone.index), 0, scale); break; } case "translatey": { - timelines - .Add(ReadTimeline(ref keyMapEnumerator, new TranslateYTimeline(frames, frames, boneIndex), 0, scale)); + ReadTimeline(ref timelines, ref keyMapEnumerator, new TranslateYTimeline(frames, frames, bone.index), 0, scale); break; } case "scale": { - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleTimeline(frames, frames << 1, boneIndex), "x", "y", 1, 1)); + ReadTimeline(ref timelines, ref keyMapEnumerator, new ScaleTimeline(frames, frames << 1, bone.index), "x", "y", 1, 1); break; } case "scalex": - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleXTimeline(frames, frames, boneIndex), 1, 1)); + ReadTimeline(ref timelines, ref keyMapEnumerator, new ScaleXTimeline(frames, frames, bone.index), 1, 1); break; case "scaley": - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleYTimeline(frames, frames, boneIndex), 1, 1)); + ReadTimeline(ref timelines, ref keyMapEnumerator, new ScaleYTimeline(frames, frames, bone.index), 1, 1); break; case "shear": { - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearTimeline(frames, frames << 1, boneIndex), "x", "y", 0, 1)); + ReadTimeline(ref timelines, ref keyMapEnumerator, new ShearTimeline(frames, frames << 1, bone.index), "x", "y", 0, 1); break; } case "shearx": - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearXTimeline(frames, frames, boneIndex), 0, 1)); + ReadTimeline(ref timelines, ref keyMapEnumerator, new ShearXTimeline(frames, frames, bone.index), 0, 1); break; case "sheary": - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearYTimeline(frames, frames, boneIndex), 0, 1)); + ReadTimeline(ref timelines, ref keyMapEnumerator, new ShearYTimeline(frames, frames, bone.index), 0, 1); break; case "inherit": { - var timeline = new InheritTimeline(frames, boneIndex); + var timeline = new InheritTimeline(frames, bone.index); for (int frame = 0; ; frame++) { Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; float time = GetFloat(keyMap, "time", 0); @@ -998,8 +1032,6 @@ namespace Spine { timelines.Add(timeline); break; } - default: - throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); } } } @@ -1012,9 +1044,10 @@ namespace Spine { List.Enumerator keyMapEnumerator = values.GetEnumerator(); if (!keyMapEnumerator.MoveNext()) continue; Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; - IkConstraintData constraint = skeletonData.FindIkConstraint(timelineMap.Key); + IkConstraintData constraint = skeletonData.FindConstraint(timelineMap.Key); + if (constraint == null) throw new SerializationException("IK constraint not found: " + timelineMap.Key); var timeline = new IkConstraintTimeline(values.Count, values.Count << 1, - skeletonData.IkConstraints.IndexOf(constraint)); + skeletonData.constraints.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++) { @@ -1048,13 +1081,15 @@ namespace Spine { List.Enumerator keyMapEnumerator = values.GetEnumerator(); if (!keyMapEnumerator.MoveNext()) continue; Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; - TransformConstraintData constraint = skeletonData.FindTransformConstraint(timelineMap.Key); + TransformConstraintData constraint = skeletonData.FindConstraint(timelineMap.Key); + if (constraint == null) throw new SerializationException("Transform constraint not found: " + timelineMap.Key); var timeline = new TransformConstraintTimeline(values.Count, values.Count * 6, - skeletonData.TransformConstraints.IndexOf(constraint)); + skeletonData.constraints.IndexOf(constraint)); float time = GetFloat(keyMap, "time", 0); - float mixRotate = GetFloat(keyMap, "mixRotate", 1), mixShearY = GetFloat(keyMap, "mixShearY", 1); + float mixRotate = GetFloat(keyMap, "mixRotate", 1); float mixX = GetFloat(keyMap, "mixX", 1), mixY = GetFloat(keyMap, "mixY", mixX); - float mixScaleX = GetFloat(keyMap, "mixScaleX", 1), mixScaleY = GetFloat(keyMap, "mixScaleY", mixScaleX); + float mixScaleX = GetFloat(keyMap, "mixScaleX", 1), mixScaleY = GetFloat(keyMap, "mixScaleY", 1); + float mixShearY = GetFloat(keyMap, "mixShearY", 1); for (int frame = 0, bezier = 0; ; frame++) { timeline.SetFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); if (!keyMapEnumerator.MoveNext()) { @@ -1063,9 +1098,10 @@ namespace Spine { } Dictionary nextMap = (Dictionary)keyMapEnumerator.Current; float time2 = GetFloat(nextMap, "time", 0); - float mixRotate2 = GetFloat(nextMap, "mixRotate", 1), mixShearY2 = GetFloat(nextMap, "mixShearY", 1); + float mixRotate2 = GetFloat(nextMap, "mixRotate", 1); float mixX2 = GetFloat(nextMap, "mixX", 1), mixY2 = GetFloat(nextMap, "mixY", mixX2); - float mixScaleX2 = GetFloat(nextMap, "mixScaleX", 1), mixScaleY2 = GetFloat(nextMap, "mixScaleY", mixScaleX2); + float mixScaleX2 = GetFloat(nextMap, "mixScaleX", 1), mixScaleY2 = GetFloat(nextMap, "mixScaleY", 1); + float mixShearY2 = GetFloat(nextMap, "mixShearY", 1); if (keyMap.ContainsKey("curve")) { object curve = keyMap["curve"]; bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1); @@ -1091,9 +1127,9 @@ namespace Spine { // Path constraint timelines. if (map.ContainsKey("path")) { foreach (KeyValuePair constraintMap in (Dictionary)map["path"]) { - PathConstraintData constraint = skeletonData.FindPathConstraint(constraintMap.Key); + PathConstraintData constraint = skeletonData.FindConstraint(constraintMap.Key); if (constraint == null) throw new Exception("Path constraint not found: " + constraintMap.Key); - int constraintIndex = skeletonData.pathConstraints.IndexOf(constraint); + int constraintIndex = skeletonData.constraints.IndexOf(constraint); Dictionary timelineMap = (Dictionary)constraintMap.Value; foreach (KeyValuePair timelineEntry in timelineMap) { List values = (List)timelineEntry.Value; @@ -1105,13 +1141,13 @@ namespace Spine { switch (timelineName) { case "position": { CurveTimeline1 timeline = new PathConstraintPositionTimeline(frames, frames, constraintIndex); - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, constraint.positionMode == PositionMode.Fixed ? scale : 1)); + ReadTimeline(ref timelines, ref keyMapEnumerator, timeline, 0, constraint.positionMode == PositionMode.Fixed ? scale : 1); break; } case "spacing": { CurveTimeline1 timeline = new PathConstraintSpacingTimeline(frames, frames, constraintIndex); - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, - constraint.spacingMode == SpacingMode.Length || constraint.spacingMode == SpacingMode.Fixed ? scale : 1)); + ReadTimeline(ref timelines, ref keyMapEnumerator, timeline, 0, + constraint.spacingMode == SpacingMode.Length || constraint.spacingMode == SpacingMode.Fixed ? scale : 1); break; } case "mix": { @@ -1155,9 +1191,9 @@ namespace Spine { foreach (KeyValuePair constraintMap in (Dictionary)map["physics"]) { int index = -1; if (!string.IsNullOrEmpty(constraintMap.Key)) { - PhysicsConstraintData constraint = skeletonData.FindPhysicsConstraint(constraintMap.Key); + PhysicsConstraintData constraint = skeletonData.FindConstraint(constraintMap.Key); if (constraint == null) throw new Exception("Physics constraint not found: " + constraintMap.Key); - index = skeletonData.physicsConstraints.IndexOf(constraint); + index = skeletonData.constraints.IndexOf(constraint); } Dictionary timelineMap = (Dictionary)constraintMap.Value; foreach (KeyValuePair timelineEntry in timelineMap) { @@ -1168,6 +1204,7 @@ namespace Spine { int frames = values.Count; string timelineName = (string)timelineEntry.Key; CurveTimeline1 timeline; + float defaultValue = 0; switch (timelineName) { case "reset": { var resetTimeline = new PhysicsConstraintResetTimeline(frames, index); @@ -1197,12 +1234,40 @@ namespace Spine { timeline = new PhysicsConstraintGravityTimeline(frames, frames, index); break; case "mix": + defaultValue = 1; timeline = new PhysicsConstraintMixTimeline(frames, frames, index); break; default: continue; } - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, 1)); + ReadTimeline(ref timelines, ref keyMapEnumerator, timeline, defaultValue, 1); + } + } + } + + // Slider timelines. + if (map.ContainsKey("slider")) { + foreach (KeyValuePair constraintMap in (Dictionary)map["slider"]) { + SliderData constraint = skeletonData.FindConstraint(constraintMap.Key); + if (constraint == null) throw new Exception("Slider not found: " + constraintMap.Key); + int index = skeletonData.constraints.IndexOf(constraint); + + Dictionary timelineMap = (Dictionary)constraintMap.Value; + foreach (KeyValuePair timelineEntry in timelineMap) { + List values = (List)timelineEntry.Value; + List.Enumerator keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + + int frames = values.Count; + string timelineName = (string)timelineEntry.Key; + switch (timelineName) { + case "time": + ReadTimeline(ref timelines, ref keyMapEnumerator, new SliderTimeline(frames, frames, index), 1, 1); + break; + case "mix": + ReadTimeline(ref timelines, ref keyMapEnumerator, new SliderMixTimeline(frames, frames, index), 1, 1); + break; + } } } } @@ -1226,7 +1291,7 @@ namespace Spine { string timelineName = timelineMap.Key; switch (timelineName) { case "deform": { - VertexAttachment vertexAttachment = (VertexAttachment)attachment; + var vertexAttachment = (VertexAttachment)attachment; bool weighted = vertexAttachment.bones != null; float[] vertices = vertexAttachment.vertices; int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; @@ -1360,17 +1425,20 @@ namespace Spine { skeletonData.animations.Add(new Animation(name, timelines, duration)); } - static Timeline ReadTimeline (ref List.Enumerator keyMapEnumerator, CurveTimeline1 timeline, float defaultValue, float scale) { - Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; + static void ReadTimeline (ref ExposedList timelines, ref List.Enumerator keyMapEnumerator, CurveTimeline1 timeline, float defaultValue, + float scale) { + + Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; float time = GetFloat(keyMap, "time", 0); float value = GetFloat(keyMap, "value", defaultValue) * scale; for (int frame = 0, bezier = 0; ; frame++) { timeline.SetFrame(frame, time, value); if (!keyMapEnumerator.MoveNext()) { timeline.Shrink(bezier); - return timeline; + timelines.Add(timeline); + return; } - Dictionary nextMap = (Dictionary)keyMapEnumerator.Current; + Dictionary nextMap = (Dictionary)keyMapEnumerator.Current; float time2 = GetFloat(nextMap, "time", 0); float value2 = GetFloat(nextMap, "value", defaultValue) * scale; if (keyMap.ContainsKey("curve")) { @@ -1383,17 +1451,18 @@ namespace Spine { } } - static Timeline ReadTimeline (ref List.Enumerator keyMapEnumerator, CurveTimeline2 timeline, String name1, String name2, float defaultValue, - float scale) { + static void ReadTimeline (ref ExposedList timelines, ref List.Enumerator keyMapEnumerator, BoneTimeline2 timeline, string name1, string name2, + float defaultValue, float scale) { - Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; + Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; float time = GetFloat(keyMap, "time", 0); float value1 = GetFloat(keyMap, name1, defaultValue) * scale, value2 = GetFloat(keyMap, name2, defaultValue) * scale; for (int frame = 0, bezier = 0; ; frame++) { timeline.SetFrame(frame, time, value1, value2); if (!keyMapEnumerator.MoveNext()) { timeline.Shrink(bezier); - return timeline; + timelines.Add(timeline); + return; } Dictionary nextMap = (Dictionary)keyMapEnumerator.Current; float time2 = GetFloat(nextMap, "time", 0); @@ -1490,6 +1559,27 @@ namespace Spine { return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; } + static Color ToColor32 (string hexString, int expectedLength = 8) { + if (hexString.Length < expectedLength) + throw new ArgumentException("Color hexadecimal length must be " + expectedLength + ", received: " + hexString, "hexString"); + + float r = Convert.ToInt32(hexString.Substring(0, 2), 16) / (float)255; + float g = Convert.ToInt32(hexString.Substring(2, 2), 16) / (float)255; + float b = Convert.ToInt32(hexString.Substring(4, 2), 16) / (float)255; + float a = Convert.ToInt32(hexString.Substring(6, 2), 16) / (float)255; + return new Color(r, g, b, a); + } + + static Color ToColor24 (string hexString, int expectedLength = 6) { + if (hexString.Length < expectedLength) + throw new ArgumentException("Color hexadecimal length must be " + expectedLength + ", received: " + hexString, "hexString"); + + float r = Convert.ToInt32(hexString.Substring(0, 2), 16) / (float)255; + float g = Convert.ToInt32(hexString.Substring(2, 2), 16) / (float)255; + float b = Convert.ToInt32(hexString.Substring(4, 2), 16) / (float)255; + return new Color(r, g, b); + } + private class LinkedMesh { internal string parent, skin; internal int slotIndex; diff --git a/spine-csharp/src/Skin.cs b/spine-csharp/src/Skin.cs index 645634531..b3e0f0db0 100644 --- a/spine-csharp/src/Skin.cs +++ b/spine-csharp/src/Skin.cs @@ -42,13 +42,13 @@ namespace Spine { // Reason is that there is no efficient way to replace or access an already added element, losing any benefits. private Dictionary attachments = new Dictionary(SkinKeyComparer.Instance); internal readonly ExposedList bones = new ExposedList(); - internal readonly ExposedList constraints = new ExposedList(); + internal readonly ExposedList constraints = new ExposedList(); public string Name { get { return name; } } /// Returns all attachments contained in this skin. public ICollection Attachments { get { return attachments.Values; } } public ExposedList Bones { get { return bones; } } - public ExposedList Constraints { get { return constraints; } } + public ExposedList Constraints { get { return constraints; } } public Skin (string name) { if (name == null) throw new ArgumentNullException("name", "name cannot be null."); @@ -67,7 +67,7 @@ namespace Spine { foreach (BoneData data in skin.bones) if (!bones.Contains(data)) bones.Add(data); - foreach (ConstraintData data in skin.constraints) + foreach (IConstraintData data in skin.constraints) if (!constraints.Contains(data)) constraints.Add(data); foreach (KeyValuePair item in skin.attachments) { @@ -81,7 +81,7 @@ namespace Spine { foreach (BoneData data in skin.bones) if (!bones.Contains(data)) bones.Add(data); - foreach (ConstraintData data in skin.constraints) + foreach (IConstraintData data in skin.constraints) if (!constraints.Contains(data)) constraints.Add(data); foreach (KeyValuePair item in skin.attachments) { @@ -134,10 +134,9 @@ namespace Spine { Slot[] slots = skeleton.slots.Items; foreach (KeyValuePair item in oldSkin.attachments) { SkinEntry entry = item.Value; - int slotIndex = entry.slotIndex; - Slot slot = slots[slotIndex]; + SlotPose slot = slots[entry.slotIndex].pose; if (slot.Attachment == entry.attachment) { - Attachment attachment = GetAttachment(slotIndex, entry.name); + Attachment attachment = GetAttachment(entry.slotIndex, entry.name); if (attachment != null) slot.Attachment = attachment; } } diff --git a/spine-csharp/src/Slider.cs b/spine-csharp/src/Slider.cs new file mode 100644 index 000000000..d0aef0142 --- /dev/null +++ b/spine-csharp/src/Slider.cs @@ -0,0 +1,115 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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; + +namespace Spine { + /// + /// Stores the current pose for a slider. + /// + public class Slider : Constraint { + static private readonly float[] offsets = new float[6]; + internal Bone bone; + + public Slider (SliderData data, Skeleton skeleton) + : base(data, new SliderPose(), new SliderPose()) { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + if (data.bone != null) bone = skeleton.bones.Items[data.bone.index]; + } + + override public IConstraint Copy (Skeleton skeleton) { + var copy = new Slider(data, skeleton); + copy.pose.Set(pose); + return copy; + } + + override public void Update (Skeleton skeleton, Physics physics) { + SliderPose p = applied; + if (p.mix == 0) return; + + Animation animation = data.animation; + if (bone != null) { + if (!bone.active) return; + if (data.local) bone.applied.ValidateLocalTransform(skeleton); + p.time = data.offset + + (data.property.Value(skeleton, bone.applied, data.local, offsets) - data.property.offset) * data.scale; + if (data.loop) + p.time = animation.duration + (p.time % animation.duration); + else + p.time = Math.Max(0, p.time); + } + + Bone[] bones = skeleton.bones.Items; + int[] indices = animation.bones.Items; + for (int i = 0, n = animation.bones.Count; i < n; i++) + bones[indices[i]].applied.ModifyLocal(skeleton); + + animation.Apply(skeleton, p.time, p.time, data.loop, null, p.mix, data.additive ? MixBlend.Add : MixBlend.Replace, + MixDirection.In, true); + } + + override public void Sort (Skeleton skeleton) { + if (bone != null && !data.local) skeleton.SortBone(bone); + skeleton.updateCache.Add(this); + + Timeline[] timelines = data.animation.timelines.Items; + Bone[] bones = skeleton.bones.Items; + Slot[] slots = skeleton.slots.Items; + IConstraint[] constraints = skeleton.constraints.Items; + PhysicsConstraint[] physics = skeleton.physics.Items; + int physicsCount = skeleton.physics.Count; + for (int i = 0, n = data.animation.timelines.Count; i < n; i++) { + Timeline t = timelines[i]; + IBoneTimeline boneTimeline = t as IBoneTimeline; + if (boneTimeline != null) { + Bone bone = bones[boneTimeline.BoneIndex]; + bone.sorted = false; + skeleton.SortReset(bone.children); + skeleton.Constrained(bone); + } else if (t as ISlotTimeline != null) { + ISlotTimeline timeline = (ISlotTimeline)t; + skeleton.Constrained(slots[timeline.SlotIndex]); + } else if (t as PhysicsConstraintTimeline != null) { + PhysicsConstraintTimeline timeline = (PhysicsConstraintTimeline)t; + if (timeline.constraintIndex == -1) { + for (int ii = 0; ii < physicsCount; ii++) + skeleton.Constrained(physics[ii]); + } else + skeleton.Constrained(constraints[timeline.constraintIndex]); + } else if (t as IConstraintTimeline != null) { + IConstraintTimeline timeline = (IConstraintTimeline)t; + skeleton.Constrained(constraints[timeline.ConstraintIndex]); + } + } + } + + public Bone Bone { get { return bone; } set { bone = value; } } + } +} diff --git a/spine-csharp/src/Slider.cs.meta b/spine-csharp/src/Slider.cs.meta new file mode 100644 index 000000000..c11e51366 --- /dev/null +++ b/spine-csharp/src/Slider.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5bdcb9aaed0e86b459d0d79ca58603da \ No newline at end of file diff --git a/spine-csharp/src/SliderData.cs b/spine-csharp/src/SliderData.cs new file mode 100644 index 000000000..7595d5d71 --- /dev/null +++ b/spine-csharp/src/SliderData.cs @@ -0,0 +1,65 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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; + +namespace Spine { + using FromProperty = TransformConstraintData.FromProperty; + + /// + /// Stores the setup pose for a . + /// + public class SliderData : ConstraintData { + internal Animation animation; + internal bool additive, loop; + internal BoneData bone; + internal FromProperty property; + internal float offset, scale; + internal bool local; + + public SliderData (string name) + : base(name, new SliderPose()) { + } + + override public IConstraint Create (Skeleton skeleton) { + return new Slider(this, skeleton); + } + + public Animation Animation { get { return animation; } set { animation = value; } } + public bool Additive { get { return additive; } set { additive = value; } } + public bool Loop { get { return loop; } set { loop = value; } } + /// May be null. + public BoneData Bone { get { return bone; } set { bone = value; } } + /// May be null. + public FromProperty Property { get { return property; } set { property = value; } } + public float Offset { get { return offset; } set { offset = value; } } + public float Scale { get { return scale; } set { scale = value; } } + public bool Local { get { return local; } set { local = value; } } + } +} diff --git a/spine-csharp/src/SliderData.cs.meta b/spine-csharp/src/SliderData.cs.meta new file mode 100644 index 000000000..9e920e5f9 --- /dev/null +++ b/spine-csharp/src/SliderData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cb99a53a43158cd42b8c85a74aa29333 \ No newline at end of file diff --git a/spine-csharp/src/SliderPose.cs b/spine-csharp/src/SliderPose.cs new file mode 100644 index 000000000..2d47d9143 --- /dev/null +++ b/spine-csharp/src/SliderPose.cs @@ -0,0 +1,45 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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. + *****************************************************************************/ + +namespace Spine { + /// + /// Stores a pose for a slider. + /// + public class SliderPose : IPose { + internal float time, mix; + + public void Set (SliderPose pose) { + time = pose.time; + mix = pose.mix; + } + + public float Time { get { return time; } set { time = value; } } + public float Mix { get { return mix; } set { mix = value; } } + } +} diff --git a/spine-csharp/src/SliderPose.cs.meta b/spine-csharp/src/SliderPose.cs.meta new file mode 100644 index 000000000..b004550df --- /dev/null +++ b/spine-csharp/src/SliderPose.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 404ec38f814cc31479b3957079ac6233 \ No newline at end of file diff --git a/spine-csharp/src/Slot.cs b/spine-csharp/src/Slot.cs index 94b27ab3e..3acf1a493 100644 --- a/spine-csharp/src/Slot.cs +++ b/spine-csharp/src/Slot.cs @@ -27,178 +27,67 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +#if UNITY_5_3_OR_NEWER +#define IS_UNITY +#endif + using System; namespace Spine { +#if IS_UNITY + using Color = UnityEngine.Color; +#endif /// /// Stores a slot's current pose. Slots organize attachments for 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. /// - public class Slot { - internal SlotData data; - internal Bone bone; - internal float r, g, b, a; - internal float r2, g2, b2; - internal bool hasSecondColor; - internal Attachment attachment; - internal int sequenceIndex; - internal ExposedList deform = new ExposedList(); + public class Slot : Posed { + internal readonly Skeleton skeleton; + internal readonly Bone bone; internal int attachmentState; - public Slot (SlotData data, Bone bone) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - this.data = data; - this.bone = bone; - - // darkColor = data.darkColor == null ? null : new Color(); - if (data.hasSecondColor) { - r2 = g2 = b2 = 0; + public Slot (SlotData data, Skeleton skeleton) + : base(data, new SlotPose(), new SlotPose()) { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.skeleton = skeleton; + bone = skeleton.bones.Items[data.boneData.index]; + if (data.setup.GetDarkColor().HasValue) { + pose.SetDarkColor(new Color()); + constrained.SetDarkColor(new Color()); } - - SetToSetupPose(); + SetupPose(); } /// Copy constructor. - public Slot (Slot slot, Bone bone) { - if (slot == null) throw new ArgumentNullException("slot", "slot cannot be null."); + public Slot (Slot slot, Bone bone, Skeleton skeleton) + : base(slot.data, new SlotPose(), new SlotPose()) { if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - data = slot.data; + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); this.bone = bone; - r = slot.r; - g = slot.g; - b = slot.b; - a = slot.a; - - // darkColor = slot.darkColor == null ? null : new Color(slot.darkColor); - if (slot.hasSecondColor) { - r2 = slot.r2; - g2 = slot.g2; - b2 = slot.b2; - } else { - r2 = g2 = b2 = 0; + this.skeleton = skeleton; + if (data.setup.GetDarkColor().HasValue) { + pose.SetDarkColor(new Color()); + constrained.SetDarkColor(new Color()); } - hasSecondColor = slot.hasSecondColor; - - attachment = slot.attachment; - sequenceIndex = slot.sequenceIndex; - deform.AddRange(slot.deform); + pose.Set(slot.pose); } - /// The slot's setup pose data. - public SlotData Data { get { return data; } } /// The bone this slot belongs to. public Bone Bone { get { return bone; } } - /// The skeleton this slot belongs to. - public Skeleton Skeleton { get { return bone.skeleton; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float R { get { return r; } set { r = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float G { get { return g; } set { g = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float B { get { return b; } set { b = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float A { get { return a; } set { a = value; } } - - public void ClampColor () { - r = MathUtils.Clamp(r, 0, 1); - g = MathUtils.Clamp(g, 0, 1); - b = MathUtils.Clamp(b, 0, 1); - a = MathUtils.Clamp(a, 0, 1); - } - - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float R2 { get { return r2; } set { r2 = value; } } - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float G2 { get { return g2; } set { g2 = value; } } - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float B2 { get { return b2; } set { b2 = value; } } - /// Whether R2 G2 B2 are used to tint the slot's attachment for two color tinting. False if two color tinting is not used. - public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } - - public void ClampSecondColor () { - r2 = MathUtils.Clamp(r2, 0, 1); - g2 = MathUtils.Clamp(g2, 0, 1); - b2 = MathUtils.Clamp(b2, 0, 1); - } - - /// - /// The current attachment for the slot, or null if the slot has no attachment. - /// If the attachment is changed, resets and clears the . - /// The deform is not cleared if the old attachment has the same as the - /// specified attachment. - public Attachment Attachment { - /// The current attachment for the slot, or null if the slot has no attachment. - get { return attachment; } - /// - /// Sets the slot's attachment and, if the attachment changed, resets and clears the . - /// The deform is not cleared if the old attachment has the same as the - /// specified attachment. - /// May be null. - set { - if (attachment == value) return; - if (!(value is VertexAttachment) || !(this.attachment is VertexAttachment) - || ((VertexAttachment)value).TimelineAttachment != ((VertexAttachment)this.attachment).TimelineAttachment) { - deform.Clear(); - } - this.attachment = value; - sequenceIndex = -1; - } - } - - /// - /// The index of the texture region to display when the slot's attachment has a . -1 represents the - /// . - /// - public int SequenceIndex { get { return sequenceIndex; } set { sequenceIndex = value; } } - - /// Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a - /// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. - /// - /// See and . - public ExposedList Deform { - get { - return deform; - } - set { - if (deform == null) throw new ArgumentNullException("deform", "deform cannot be null."); - deform = value; - } - } /// Sets this slot to the setup pose. - public void SetToSetupPose () { - r = data.r; - g = data.g; - b = data.b; - a = data.a; - - // if (darkColor != null) darkColor.set(data.darkColor); - if (HasSecondColor) { - r2 = data.r2; - g2 = data.g2; - b2 = data.b2; - } - + override public void SetupPose () { + pose.SetColor(data.setup.GetColor()); + if (pose.GetDarkColor().HasValue) pose.SetDarkColor(data.setup.GetDarkColor()); + pose.sequenceIndex = data.setup.sequenceIndex; if (data.attachmentName == null) - Attachment = null; + pose.Attachment = null; else { - attachment = null; - Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); + pose.attachment = null; + pose.Attachment = skeleton.GetAttachment(data.index, data.attachmentName); } } - - override public string ToString () { - return data.name; - } } } diff --git a/spine-csharp/src/SlotData.cs b/spine-csharp/src/SlotData.cs index a44ccaaca..50cc1047c 100644 --- a/spine-csharp/src/SlotData.cs +++ b/spine-csharp/src/SlotData.cs @@ -27,54 +27,43 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +#if UNITY_5_3_OR_NEWER +#define IS_UNITY +#endif + using System; namespace Spine { - public class SlotData { +#if IS_UNITY + using Color = UnityEngine.Color; +#endif + + public class SlotData : PosedData { internal int index; - internal string name; internal BoneData boneData; - internal float r = 1, g = 1, b = 1, a = 1; - internal float r2 = 0, g2 = 0, b2 = 0; - internal bool hasSecondColor = false; internal string attachmentName; internal BlendMode blendMode; - + // Nonessential. // bool visible = true; + public SlotData (int index, String name, BoneData boneData) + : base(name, new SlotPose()) { + if (index < 0) throw new ArgumentException("index must be >= 0.", "index"); + if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); + this.index = index; + this.boneData = boneData; + } + /// The index of the slot in . public int Index { get { return index; } } - /// The name of the slot, which is unique across all slots in the skeleton. - public string Name { get { return name; } } + /// The bone this slot belongs to. public BoneData BoneData { get { return boneData; } } - 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; } } - public float A { get { return a; } set { a = value; } } - - public float R2 { get { return r2; } set { r2 = value; } } - public float G2 { get { return g2; } set { g2 = value; } } - public float B2 { get { return b2; } set { b2 = value; } } - public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } /// The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } /// The blend mode for drawing the slot's attachment. public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } - - public SlotData (int index, String name, BoneData boneData) { - if (index < 0) throw new ArgumentException("index must be >= 0.", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); - this.index = index; - this.name = name; - this.boneData = boneData; - } - - override public string ToString () { - return name; - } } } diff --git a/spine-csharp/src/SlotPose.cs b/spine-csharp/src/SlotPose.cs new file mode 100644 index 000000000..f9f0e9232 --- /dev/null +++ b/spine-csharp/src/SlotPose.cs @@ -0,0 +1,143 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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. + *****************************************************************************/ + +#if UNITY_5_3_OR_NEWER +#define IS_UNITY +#endif + +using System; + +namespace Spine { +#if IS_UNITY + using Color = UnityEngine.Color; +#endif + + /// + /// Stores a slot's pose. Slots organize attachments for 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. + /// + public class SlotPose : IPose { + // Color is a struct, thus set to protected to prevent + // Color color = slot.color; color.a = 0.5; modifying just a copy of the struct instead of the original + // object as in reference implementation. + protected Color color = new Color(1, 1, 1, 1); + protected Color? darkColor = null; + internal Attachment attachment; // Not used in setup pose. + internal int sequenceIndex; + internal readonly ExposedList deform = new ExposedList(); + + internal SlotPose () { + } + + public void Set (SlotPose pose) { + if (pose == null) throw new ArgumentNullException("pose", "pose cannot be null."); + color = pose.color; + if (darkColor.HasValue) darkColor = pose.darkColor; + attachment = pose.attachment; + sequenceIndex = pose.sequenceIndex; + deform.Clear(false); + deform.AddRange(pose.deform); + } + + /// A copy of the color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public Color GetColor () { + return color; + } + + /// Sets the color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public void SetColor (Color color) { + this.color = color; + } + + /// Clamps the color used to tint the slot's attachment to the 0-1 range. + public void ClampColor () { + color.Clamp(); + } + + /// A copy of the dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark + /// color's alpha is not used. + public Color? GetDarkColor () { + return darkColor; + } + + /// Sets the dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark + /// color's alpha is not used. + public void SetDarkColor (Color? darkColor) { + this.darkColor = darkColor; + } + + /// Clamps the dark color used to tint the slot's attachment to the 0-1 range. + public void ClampDarkColor () { + if (darkColor.HasValue) darkColor = darkColor.Value.Clamp(); + } + + /// + /// The current attachment for the slot, or null if the slot has no attachment. + /// If the attachment is changed, resets and clears the . + /// The deform is not cleared if the old attachment has the same as the + /// specified attachment. + public Attachment Attachment { + /// The current attachment for the slot, or null if the slot has no attachment. + get { return attachment; } + /// + /// Sets the slot's attachment and, if the attachment changed, resets and clears the . + /// The deform is not cleared if the old attachment has the same as the + /// specified attachment. + /// May be null. + set { + if (attachment == value) return; + if (!(value is VertexAttachment) || !(this.attachment is VertexAttachment) + || ((VertexAttachment)value).TimelineAttachment != ((VertexAttachment)this.attachment).TimelineAttachment) { + deform.Clear(); + } + this.attachment = value; + sequenceIndex = -1; + } + } + + /// + /// The index of the texture region to display when the slot's attachment has a . -1 represents the + /// . + /// + public int SequenceIndex { get { return sequenceIndex; } set { sequenceIndex = value; } } + + /// Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a + /// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. + /// + /// See and . + public ExposedList Deform { + get { + return deform; + } + } + } +} diff --git a/spine-csharp/src/SlotPose.cs.meta b/spine-csharp/src/SlotPose.cs.meta new file mode 100644 index 000000000..4e0157fa8 --- /dev/null +++ b/spine-csharp/src/SlotPose.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e46a84746dc30a649a4488cec8aac930 \ No newline at end of file diff --git a/spine-csharp/src/TransformConstraint.cs b/spine-csharp/src/TransformConstraint.cs index 2a4e14d49..1a89cbdae 100644 --- a/spine-csharp/src/TransformConstraint.cs +++ b/spine-csharp/src/TransformConstraint.cs @@ -31,7 +31,6 @@ using System; namespace Spine { using FromProperty = TransformConstraintData.FromProperty; - using Physics = Skeleton.Physics; using ToProperty = TransformConstraintData.ToProperty; /// @@ -41,73 +40,53 @@ namespace Spine { /// /// See Transform constraints in the Spine User Guide. /// - public class TransformConstraint : IUpdatable { - internal readonly TransformConstraintData data; - internal readonly ExposedList bones; + public class TransformConstraint : Constraint { + internal readonly ExposedList bones; internal Bone source; - internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; - - internal bool active; - - public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + + public TransformConstraint (TransformConstraintData data, Skeleton skeleton) + : base(data, new TransformConstraintPose(), new TransformConstraintPose()) { if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - bones = new ExposedList(); + bones = new ExposedList(data.bones.Count); foreach (BoneData boneData in data.bones) - bones.Add(skeleton.bones.Items[boneData.index]); + bones.Add(skeleton.bones.Items[boneData.index].constrained); source = skeleton.bones.Items[data.source.index]; - - mixRotate = data.mixRotate; - mixX = data.mixX; - mixY = data.mixY; - mixScaleX = data.mixScaleX; - mixScaleY = data.mixScaleY; - mixShearY = data.mixShearY; } - /// Copy constructor. - public TransformConstraint (TransformConstraint constraint, Skeleton skeleton) - : this(constraint.data, skeleton) { - - mixRotate = constraint.mixRotate; - mixX = constraint.mixX; - mixY = constraint.mixY; - mixScaleX = constraint.mixScaleX; - mixScaleY = constraint.mixScaleY; - mixShearY = constraint.mixShearY; + override public IConstraint Copy (Skeleton skeleton) { + var copy = new TransformConstraint(data, skeleton); + copy.pose.Set(pose); + return copy; } - public void SetToSetupPose () { - TransformConstraintData data = this.data; - mixRotate = data.mixRotate; - mixX = data.mixX; - mixY = data.mixY; - mixScaleX = data.mixScaleX; - mixScaleY = data.mixScaleY; - mixShearY = data.mixShearY; - } - - public void Update (Physics physics) { - if (mixRotate == 0 && mixX == 0 && mixY == 0 && mixScaleX == 0 && mixScaleY == 0 && mixShearY == 0) return; + /// Applies the constraint to the constrained bones. + override public void Update (Skeleton skeleton, Physics physics) { + TransformConstraintPose p = applied; + if (p.mixRotate == 0 && p.mixX == 0 && p.mixY == 0 && p.mixScaleX == 0 && p.mixScaleY == 0 && p.mixShearY == 0) return; TransformConstraintData data = this.data; - bool localFrom = data.localSource, localTarget = data.localTarget, additive = data.additive, clamp = data.clamp; - Bone source = this.source; + bool localSource = data.localSource, localTarget = data.localTarget, additive = data.additive, clamp = data.clamp; + float[] offsets = data.offsets; + BonePose source = this.source.applied; + if (localSource) source.ValidateLocalTransform(skeleton); FromProperty[] fromItems = data.properties.Items; - int fn = data.properties.Count; - Bone[] bones = this.bones.Items; + int fn = data.properties.Count, update = skeleton.update; + BonePose[] bones = this.bones.Items; for (int i = 0, n = this.bones.Count; i < n; i++) { - var bone = bones[i]; + BonePose bone = bones[i]; + if (localTarget) + bone.ModifyLocal(skeleton); + else + bone.ModifyWorld(update); for (int f = 0; f < fn; f++) { FromProperty from = fromItems[f]; - float value = from.Value(data, source, localFrom) - from.offset; + float value = from.Value(skeleton, source, localSource, offsets) - from.offset; ToProperty[] toItems = from.to.Items; for (int t = 0, tn = from.to.Count; t < tn; t++) { var to = (ToProperty)toItems[t]; - if (to.Mix(this) != 0) { + if (to.Mix(p) != 0) { float clamped = to.offset + value * to.scale; if (clamp) { if (to.offset < to.max) @@ -115,39 +94,37 @@ namespace Spine { else clamped = MathUtils.Clamp(clamped, to.max, to.offset); } - to.Apply(this, bone, clamped, localTarget, additive); + to.Apply(p, bone, clamped, localTarget, additive); } } } - if (localTarget) - bone.Update(Skeleton.Physics.None); // note: reference implementation passes null, ignored parameter - else - bone.UpdateAppliedTransform(); } } + override public void Sort (Skeleton skeleton) { + if (!data.localSource) skeleton.SortBone(source); + BonePose[] bones = this.bones.Items; + int boneCount = this.bones.Count; + bool worldTarget = !data.localTarget; + if (worldTarget) { + for (int i = 0; i < boneCount; i++) + skeleton.SortBone(bones[i].bone); + } + skeleton.updateCache.Add(this); + for (int i = 0; i < boneCount; i++) { + Bone bone = bones[i].bone; + skeleton.SortReset(bone.children); + skeleton.Constrained(bone); + } + for (int i = 0; i < boneCount; i++) + bones[i].bone.sorted = worldTarget; + } + + override public bool IsSourceActive { get { return source.active; } } + /// The bones that will be modified by this transform constraint. - public ExposedList Bones { get { return bones; } } + public ExposedList Bones { get { return bones; } } /// The bone whose world transform will be copied to the constrained bones. public Bone Source { get { return source; } set { source = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. - public float MixX { get { return mixX; } set { mixX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. - public float MixY { get { return mixY; } set { mixY = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. - public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. - public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. - public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } - public bool Active { get { return active; } } - /// The transform constraint's setup pose data. - public TransformConstraintData Data { get { return data; } } - - override public string ToString () { - return data.name; - } } } diff --git a/spine-csharp/src/TransformConstraintData.cs b/spine-csharp/src/TransformConstraintData.cs index f1bf1a8e3..b0bdd1c48 100644 --- a/spine-csharp/src/TransformConstraintData.cs +++ b/spine-csharp/src/TransformConstraintData.cs @@ -30,17 +30,24 @@ using System; namespace Spine { - public class TransformConstraintData : ConstraintData { + public class TransformConstraintData : ConstraintData { + public const int ROTATION = 0, X = 1, Y = 2, SCALEX = 3, SCALEY = 4, SHEARY = 5; + internal readonly ExposedList bones = new ExposedList(); internal BoneData source; - internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; - internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; + internal float[] offsets = new float[6]; internal bool localSource, localTarget, additive, clamp; internal readonly ExposedList properties = new ExposedList(); - public TransformConstraintData (string name) : base(name) { + public TransformConstraintData (string name) + : base(name, new TransformConstraintPose()) { } + override public IConstraint Create (Skeleton skeleton) { + return new TransformConstraint(this, skeleton); + } + + /// The bones that will be modified by this transform constraint. public ExposedList Bones { get { return bones; } } /// The bone whose world transform will be copied to the constrained bones. @@ -52,35 +59,18 @@ namespace Spine { } } - /// The mapping of transform properties to other transform properties. - public ExposedList Properties { - get { return properties; } - } - - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. - public float MixX { get { return mixX; } set { mixX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. - public float MixY { get { return mixY; } set { mixY = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. - public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. - public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. - public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } /// An offset added to the constrained bone rotation. - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float OffsetRotation { get { return offsets[ROTATION]; } set { offsets[ROTATION] = value; } } /// An offset added to the constrained bone X translation. - public float OffsetX { get { return offsetX; } set { offsetX = value; } } + public float OffsetX { get { return offsets[X]; } set { offsets[X] = value; } } /// An offset added to the constrained bone Y translation. - public float OffsetY { get { return offsetY; } set { offsetY = value; } } + public float OffsetY { get { return offsets[Y]; } set { offsets[Y] = value; } } /// An offset added to the constrained bone scaleX. - public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } + public float OffsetScaleX { get { return offsets[SCALEX]; } set { offsets[SCALEX] = value; } } /// An offset added to the constrained bone scaleY. - public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } + public float OffsetScaleY { get { return offsets[SCALEY]; } set { offsets[SCALEY] = value; } } /// An offset added to the constrained bone shearY. - public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } + public float OffsetShearY { get { return offsets[SHEARY]; } set { offsets[SHEARY] = value; } } /// Reads the source bone's local transform instead of its world transform. public bool LocalSource { get { return localSource; } set { localSource = value; } } @@ -92,16 +82,21 @@ namespace Spine { /// . public bool Clamp { get { return clamp; } set { clamp = value; } } + /// The mapping of transform properties to other transform properties. + public ExposedList Properties { + get { return properties; } + } + /// Source property for a . abstract public class FromProperty { /// The value of this property that corresponds to . public float offset; /// Constrained properties. - public readonly ExposedList to = new ExposedList(); + public readonly ExposedList to = new ExposedList(1); /// Reads this property from the specified bone. - abstract public float Value (TransformConstraintData data, Bone source, bool local); + abstract public float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets); } ///Constrained property for a . @@ -115,32 +110,32 @@ namespace Spine { /// The scale of the value in relation to this property. public float scale; - /// Reads the mix for this property from the specified constraint. - public abstract float Mix (TransformConstraint constraint); + /// Reads the mix for this property from the specified pose. + public abstract float Mix (TransformConstraintPose pose); /// Applies the value to this property. - public abstract void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive); + public abstract void Apply (TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive); } public class FromRotate : FromProperty { - public override float Value (TransformConstraintData data, Bone source, bool local) { - if (local) return source.arotation + data.offsetRotation; - float value = MathUtils.Atan2(source.c, source.a) * MathUtils.RadDeg - + (source.a * source.d - source.b * source.c > 0 ? data.offsetRotation : -data.offsetRotation); + public override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) { + if (local) return source.rotation + offsets[ROTATION]; + float value = MathUtils.Atan2(source.c / skeleton.ScaleY, source.a / skeleton.scaleX) * MathUtils.RadDeg + + (source.a * source.d - source.b * source.c > 0 ? offsets[ROTATION] : -offsets[ROTATION]); if (value < 0) value += 360; return value; } } public class ToRotate : ToProperty { - public override float Mix (TransformConstraint constraint) { - return constraint.mixRotate; + public override float Mix (TransformConstraintPose pose) { + return pose.mixRotate; } - public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) { + public override void Apply (TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive) { if (local) { - if (!additive) value -= bone.arotation; - bone.arotation += value * constraint.mixRotate; + if (!additive) value -= bone.rotation; + bone.rotation += value * pose.mixRotate; } else { float a = bone.a, b = bone.b, c = bone.c, d = bone.d; value *= MathUtils.DegRad; @@ -149,7 +144,7 @@ namespace Spine { value -= MathUtils.PI2; else if (value < -MathUtils.PI) // value += MathUtils.PI2; - value *= constraint.mixRotate; + value *= pose.mixRotate; float cos = MathUtils.Cos(value), sin = MathUtils.Sin(value); bone.a = cos * a - sin * c; bone.b = cos * b - sin * d; @@ -160,73 +155,75 @@ namespace Spine { } public class FromX : FromProperty { - public override float Value (TransformConstraintData data, Bone source, bool local) { - return local ? source.ax + data.offsetX : data.offsetX * source.a + data.offsetY * source.b + source.worldX; + public override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) { + return local ? source.x + offsets[X] : (offsets[X] * source.a + offsets[Y] * source.b + source.worldX) / skeleton.scaleX; } } public class ToX : ToProperty { - public override float Mix (TransformConstraint constraint) { - return constraint.mixX; + public override float Mix (TransformConstraintPose pose) { + return pose.mixX; } - public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) { + public override void Apply (TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive) { if (local) { - if (!additive) value -= bone.ax; - bone.ax += value * constraint.mixX; + if (!additive) value -= bone.x; + bone.x += value * pose.mixX; } else { if (!additive) value -= bone.worldX; - bone.worldX += value * constraint.mixX; + bone.worldX += value * pose.mixX; } } } public class FromY : FromProperty { - public override float Value (TransformConstraintData data, Bone source, bool local) { - return local ? source.ay + data.offsetY : data.offsetX * source.c + data.offsetY * source.d + source.worldY; + public override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) { + return local ? source.y + offsets[Y] : (offsets[X] * source.c + offsets[Y] * source.d + source.worldY) / skeleton.ScaleY; } } public class ToY : ToProperty { - public override float Mix (TransformConstraint constraint) { - return constraint.mixY; + public override float Mix (TransformConstraintPose pose) { + return pose.mixY; } - public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) { + public override void Apply (TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive) { if (local) { - if (!additive) value -= bone.ay; - bone.ay += value * constraint.mixY; + if (!additive) value -= bone.y; + bone.y += value * pose.mixY; } else { if (!additive) value -= bone.worldY; - bone.worldY += value * constraint.mixY; + bone.worldY += value * pose.mixY; } } } public class FromScaleX : FromProperty { - public override float Value (TransformConstraintData data, Bone source, bool local) { - return (local ? source.ascaleX : (float)Math.Sqrt(source.a * source.a + source.c * source.c)) + data.offsetScaleX; + public override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) { + if (local) return source.scaleX + offsets[SCALEX]; + float a = source.a / skeleton.scaleX, c = source.c / skeleton.ScaleY; + return (float)Math.Sqrt(a * a + c * c) + offsets[SCALEX]; } } public class ToScaleX : ToProperty { - public override float Mix (TransformConstraint constraint) { - return constraint.mixScaleX; + public override float Mix (TransformConstraintPose pose) { + return pose.mixScaleX; } - public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) { + public override void Apply (TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive) { if (local) { if (additive) - bone.ascaleX *= 1 + ((value - 1) * constraint.mixScaleX); - else if (bone.ascaleX != 0) // - bone.ascaleX = 1 + (value / bone.ascaleX - 1) * constraint.mixScaleX; + bone.scaleX *= 1 + ((value - 1) * pose.mixScaleX); + else if (bone.scaleX != 0) // + bone.scaleX = 1 + (value / bone.scaleX - 1) * pose.mixScaleX; } else { float s; if (additive) - s = 1 + (value - 1) * constraint.mixScaleX; + s = 1 + (value - 1) * pose.mixScaleX; else { s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); - if (s != 0) s = 1 + (value / s - 1) * constraint.mixScaleX; + if (s != 0) s = 1 + (value / s - 1) * pose.mixScaleX; } bone.a *= s; bone.c *= s; @@ -235,29 +232,31 @@ namespace Spine { } public class FromScaleY : FromProperty { - public override float Value (TransformConstraintData data, Bone source, bool local) { - return (local ? source.ascaleY : (float)Math.Sqrt(source.b * source.b + source.d * source.d)) + data.offsetScaleY; + public override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) { + if (local) return source.scaleY + offsets[SCALEY]; + float b = source.b / skeleton.scaleX, d = source.d / skeleton.ScaleY; + return (float)Math.Sqrt(b * b + d * d) + offsets[SCALEY]; } } public class ToScaleY : ToProperty { - public override float Mix (TransformConstraint constraint) { - return constraint.mixScaleY; + public override float Mix (TransformConstraintPose pose) { + return pose.mixScaleY; } - public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) { + public override void Apply (TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive) { if (local) { if (additive) - bone.ascaleY *= 1 + ((value - 1) * constraint.mixScaleY); - else if (bone.ascaleY != 0) // - bone.ascaleY = 1 + (value / bone.ascaleY - 1) * constraint.mixScaleY; + bone.scaleY *= 1 + ((value - 1) * pose.mixScaleY); + else if (bone.scaleY != 0) // + bone.scaleY = 1 + (value / bone.scaleY - 1) * pose.mixScaleY; } else { float s; if (additive) - s = 1 + (value - 1) * constraint.mixScaleY; + s = 1 + (value - 1) * pose.mixScaleY; else { s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); - if (s != 0) s = 1 + (value / s - 1) * constraint.mixScaleY; + if (s != 0) s = 1 + (value / s - 1) * pose.mixScaleY; } bone.b *= s; bone.d *= s; @@ -266,21 +265,22 @@ namespace Spine { } public class FromShearY : FromProperty { - public override float Value (TransformConstraintData data, Bone source, bool local) { - return (local ? source.ashearY : (MathUtils.Atan2(source.d, source.b) - MathUtils.Atan2(source.c, source.a)) * MathUtils.RadDeg - 90) - + data.offsetShearY; + public override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) { + if (local) return source.shearY + offsets[SHEARY]; + float sx = 1 / skeleton.scaleX, sy = 1 / skeleton.ScaleY; + return (MathUtils.Atan2(source.d * sy, source.b * sx) - MathUtils.Atan2(source.c * sy, source.a * sx)) * MathUtils.RadDeg - 90 + offsets[SHEARY]; } } public class ToShearY : ToProperty { - public override float Mix (TransformConstraint constraint) { - return constraint.mixShearY; + public override float Mix (TransformConstraintPose pose) { + return pose.mixShearY; } - public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) { + public override void Apply (TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive) { if (local) { - if (!additive) value -= bone.ashearY; - bone.ashearY += value * constraint.mixShearY; + if (!additive) value -= bone.shearY; + bone.shearY += value * pose.mixShearY; } else { float b = bone.b, d = bone.d, by = MathUtils.Atan2(d, b); value = (value + 90) * MathUtils.DegRad; @@ -293,7 +293,7 @@ namespace Spine { else if (value < -MathUtils.PI) // value += MathUtils.PI2; } - value = by + value * constraint.mixShearY; + value = by + value * pose.mixShearY; float s = (float)Math.Sqrt(b * b + d * d); bone.b = MathUtils.Cos(value) * s; bone.d = MathUtils.Sin(value) * s; diff --git a/spine-csharp/src/TransformConstraintPose.cs b/spine-csharp/src/TransformConstraintPose.cs new file mode 100644 index 000000000..924d628d0 --- /dev/null +++ b/spine-csharp/src/TransformConstraintPose.cs @@ -0,0 +1,64 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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; + +namespace Spine { + using FromProperty = TransformConstraintData.FromProperty; + using ToProperty = TransformConstraintData.ToProperty; + + /// + /// Stores a pose for a transform constraint. + /// + public class TransformConstraintPose : IPose { + internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; + + public void Set (TransformConstraintPose pose) { + mixRotate = pose.mixRotate; + mixX = pose.mixX; + mixY = pose.mixY; + mixScaleX = pose.mixScaleX; + mixScaleY = pose.mixScaleY; + mixShearY = pose.mixShearY; + } + + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. + public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. + public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. + public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } + } +} diff --git a/spine-csharp/src/TransformConstraintPose.cs.meta b/spine-csharp/src/TransformConstraintPose.cs.meta new file mode 100644 index 000000000..03fdc3d91 --- /dev/null +++ b/spine-csharp/src/TransformConstraintPose.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8f731f92402be95469edd617a53bd865 \ No newline at end of file diff --git a/spine-unity/Assets/Spine Examples/Scripts/Getting Started Scripts/BasicPlatformerController.cs b/spine-unity/Assets/Spine Examples/Scripts/Getting Started Scripts/BasicPlatformerController.cs index 57bb3a5cf..12df18b8c 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Getting Started Scripts/BasicPlatformerController.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Getting Started Scripts/BasicPlatformerController.cs @@ -115,7 +115,7 @@ namespace Spine.Unity.Examples { } // Dummy physics and controller using UnityEngine.CharacterController. - Vector3 gravityDeltaVelocity = Physics.gravity * gravityScale * dt; + Vector3 gravityDeltaVelocity = UnityEngine.Physics.gravity * gravityScale * dt; if (doJump) { velocity.y = jumpSpeed; diff --git a/spine-unity/Assets/Spine Examples/Scripts/Goblins.cs b/spine-unity/Assets/Spine Examples/Scripts/Goblins.cs index 721a4bcce..c23aac8ba 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Goblins.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Goblins.cs @@ -48,12 +48,12 @@ namespace Spine.Unity.Examples { // This is called after the animation is applied to the skeleton and can be used to adjust the bones dynamically. public void UpdateLocal (ISkeletonAnimation skeletonRenderer) { - headBone.Rotation += extraRotation; + headBone.Pose.Rotation += extraRotation; } public void OnMouseDown () { skeletonAnimation.Skeleton.SetSkin(girlSkin ? "goblin" : "goblingirl"); - skeletonAnimation.Skeleton.SetSlotsToSetupPose(); + skeletonAnimation.Skeleton.SetupPoseSlots(); girlSkin = !girlSkin; diff --git a/spine-unity/Assets/Spine Examples/Scripts/MecanimAnimationMatchModifier/AnimationMatchModifierAsset.cs b/spine-unity/Assets/Spine Examples/Scripts/MecanimAnimationMatchModifier/AnimationMatchModifierAsset.cs index 91a07f996..e66061810 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/MecanimAnimationMatchModifier/AnimationMatchModifierAsset.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/MecanimAnimationMatchModifier/AnimationMatchModifierAsset.cs @@ -167,14 +167,19 @@ namespace Spine.Unity.Examples { static RGBATimeline GetFillerTimeline (RGBATimeline timeline, SkeletonData skeletonData) { RGBATimeline t = new RGBATimeline(1, 0, timeline.SlotIndex); SlotData slotData = skeletonData.Slots.Items[t.SlotIndex]; - t.SetFrame(0, 0, slotData.R, slotData.G, slotData.B, slotData.A); + Color color = slotData.GetSetupPose().GetColor(); + t.SetFrame(0, 0, color.r, color.g, color.b, color.a); return t; } static RGBA2Timeline GetFillerTimeline (RGBA2Timeline timeline, SkeletonData skeletonData) { RGBA2Timeline t = new RGBA2Timeline(1, 0, timeline.SlotIndex); SlotData slotData = skeletonData.Slots.Items[t.SlotIndex]; - t.SetFrame(0, 0, slotData.R, slotData.G, slotData.B, slotData.A, slotData.R2, slotData.G2, slotData.B2); + var setup = slotData.GetSetupPose(); + Color color = setup.GetColor(); + Color? darkColorOptional = setup.GetDarkColor(); + Color darkColor = darkColorOptional.HasValue ? darkColorOptional.Value : Color.black; + t.SetFrame(0, 0, color.r, color.g, color.b, color.a, darkColor.r, darkColor.g, darkColor.b); return t; } @@ -196,37 +201,42 @@ namespace Spine.Unity.Examples { } static IkConstraintTimeline GetFillerTimeline (IkConstraintTimeline timeline, SkeletonData skeletonData) { - IkConstraintTimeline t = new IkConstraintTimeline(1, 0, timeline.IkConstraintIndex); - IkConstraintData ikConstraintData = skeletonData.IkConstraints.Items[timeline.IkConstraintIndex]; - t.SetFrame(0, 0, ikConstraintData.Mix, ikConstraintData.Softness, ikConstraintData.BendDirection, ikConstraintData.Compress, ikConstraintData.Stretch); + IkConstraintTimeline t = new IkConstraintTimeline(1, 0, timeline.ConstraintIndex); + var ikConstraintData = (IkConstraintData)skeletonData.Constraints.Items[timeline.ConstraintIndex]; + var setup = ikConstraintData.GetSetupPose(); + t.SetFrame(0, 0, setup.Mix, setup.Softness, setup.BendDirection, setup.Compress, setup.Stretch); return t; } static TransformConstraintTimeline GetFillerTimeline (TransformConstraintTimeline timeline, SkeletonData skeletonData) { - TransformConstraintTimeline t = new TransformConstraintTimeline(1, 0, timeline.TransformConstraintIndex); - TransformConstraintData data = skeletonData.TransformConstraints.Items[timeline.TransformConstraintIndex]; - t.SetFrame(0, 0, data.MixRotate, data.MixX, data.MixY, data.MixScaleX, data.MixScaleY, data.MixShearY); + TransformConstraintTimeline t = new TransformConstraintTimeline(1, 0, timeline.ConstraintIndex); + var data = (TransformConstraintData)skeletonData.Constraints.Items[timeline.ConstraintIndex]; + var setup = data.GetSetupPose(); + t.SetFrame(0, 0, setup.MixRotate, setup.MixX, setup.MixY, setup.MixScaleX, setup.MixScaleY, setup.MixShearY); return t; } static PathConstraintPositionTimeline GetFillerTimeline (PathConstraintPositionTimeline timeline, SkeletonData skeletonData) { - PathConstraintPositionTimeline t = new PathConstraintPositionTimeline(1, 0, timeline.PathConstraintIndex); - PathConstraintData data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex]; - t.SetFrame(0, 0, data.Position); + PathConstraintPositionTimeline t = new PathConstraintPositionTimeline(1, 0, timeline.ConstraintIndex); + var data = (PathConstraintData)skeletonData.Constraints.Items[timeline.ConstraintIndex]; + var setup = data.GetSetupPose(); + t.SetFrame(0, 0, setup.Position); return t; } static PathConstraintSpacingTimeline GetFillerTimeline (PathConstraintSpacingTimeline timeline, SkeletonData skeletonData) { - PathConstraintSpacingTimeline t = new PathConstraintSpacingTimeline(1, 0, timeline.PathConstraintIndex); - PathConstraintData data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex]; - t.SetFrame(0, 0, data.Spacing); + PathConstraintSpacingTimeline t = new PathConstraintSpacingTimeline(1, 0, timeline.ConstraintIndex); + var data = (PathConstraintData)skeletonData.Constraints.Items[timeline.ConstraintIndex]; + var setup = data.GetSetupPose(); + t.SetFrame(0, 0, setup.Spacing); return t; } static PathConstraintMixTimeline GetFillerTimeline (PathConstraintMixTimeline timeline, SkeletonData skeletonData) { - PathConstraintMixTimeline t = new PathConstraintMixTimeline(1, 0, timeline.PathConstraintIndex); - PathConstraintData data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex]; - t.SetFrame(0, 0, data.RotateMix, data.MixX, data.MixY); + PathConstraintMixTimeline t = new PathConstraintMixTimeline(1, 0, timeline.ConstraintIndex); + PathConstraintData data = (PathConstraintData)skeletonData.Constraints.Items[timeline.ConstraintIndex]; + var setup = data.GetSetupPose(); + t.SetFrame(0, 0, setup.MixRotate, setup.MixX, setup.MixY); return t; } #endregion diff --git a/spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/EquipsVisualsComponentExample.cs b/spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/EquipsVisualsComponentExample.cs index eb83da137..31f7e9fc8 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/EquipsVisualsComponentExample.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/EquipsVisualsComponentExample.cs @@ -91,7 +91,7 @@ namespace Spine.Unity.Examples { } void RefreshSkeletonAttachments () { - skeletonAnimation.Skeleton.SetSlotsToSetupPose(); + skeletonAnimation.Skeleton.SetupPoseSlots(); skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton); //skeletonAnimation.Update(0); } diff --git a/spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/MixAndMatchSkinsExample.cs b/spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/MixAndMatchSkinsExample.cs index b8eb1cb91..9d2e4b39c 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/MixAndMatchSkinsExample.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/MixAndMatchSkinsExample.cs @@ -146,7 +146,7 @@ namespace Spine.Unity.Examples { // Use the repacked skin. skeletonAnimation.Skeleton.Skin = repackedSkin; - skeletonAnimation.Skeleton.SetSlotsToSetupPose(); + skeletonAnimation.Skeleton.SetupPoseSlots(); skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton); // `GetRepackedSkin()` and each call to `GetRemappedClone()` with parameter `premultiplyAlpha` set to `true` @@ -189,7 +189,7 @@ namespace Spine.Unity.Examples { AddEquipmentSkinsTo(resultCombinedSkin); skeleton.SetSkin(resultCombinedSkin); - skeleton.SetSlotsToSetupPose(); + skeleton.SetupPoseSlots(); } } } diff --git a/spine-unity/Assets/Spine Examples/Scripts/MixAndMatch.cs b/spine-unity/Assets/Spine Examples/Scripts/MixAndMatch.cs index 90fba98ff..0291336e3 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/MixAndMatch.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/MixAndMatch.cs @@ -136,7 +136,7 @@ namespace Spine.Unity.Examples { skeleton.SetSkin(customSkin); // Just use the custom skin directly. } - skeleton.SetSlotsToSetupPose(); // Use the pose from setup pose. + skeleton.SetupPoseSlots(); // Use the pose from setup pose. skeletonAnimation.Update(0); // Use the pose in the currently active animation. // `GetRepackedSkin()` and each call to `GetRemappedClone()` with parameter `premultiplyAlpha` set to `true` diff --git a/spine-unity/Assets/Spine Examples/Scripts/MixAndMatchGraphic.cs b/spine-unity/Assets/Spine Examples/Scripts/MixAndMatchGraphic.cs index 0bf230440..a0b87f938 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/MixAndMatchGraphic.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/MixAndMatchGraphic.cs @@ -133,8 +133,8 @@ namespace Spine.Unity.Examples { skeleton.SetSkin(customSkin); } - //skeleton.SetSlotsToSetupPose(); - skeleton.SetToSetupPose(); + //skeleton.SetupPoseSlots(); + skeleton.SetupPose(); skeletonGraphic.Update(0); skeletonGraphic.OverrideTexture = runtimeAtlas; diff --git a/spine-unity/Assets/Spine Examples/Scripts/RuntimeLoadFromExportsExample.cs b/spine-unity/Assets/Spine Examples/Scripts/RuntimeLoadFromExportsExample.cs index 3cbd962bd..0bd549038 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/RuntimeLoadFromExportsExample.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/RuntimeLoadFromExportsExample.cs @@ -89,7 +89,7 @@ namespace Spine.Unity.Examples { runtimeSkeletonAnimation.Initialize(false); if (skinName != "") runtimeSkeletonAnimation.Skeleton.SetSkin(skinName); - runtimeSkeletonAnimation.Skeleton.SetSlotsToSetupPose(); + runtimeSkeletonAnimation.Skeleton.SetupPoseSlots(); if (animationName != "") runtimeSkeletonAnimation.AnimationState.SetAnimation(0, animationName, true); } @@ -112,7 +112,7 @@ namespace Spine.Unity.Examples { runtimeSkeletonGraphic.Initialize(false); if (skinName != "") runtimeSkeletonGraphic.Skeleton.SetSkin(skinName); - runtimeSkeletonGraphic.Skeleton.SetSlotsToSetupPose(); + runtimeSkeletonGraphic.Skeleton.SetupPoseSlots(); if (animationName != "") runtimeSkeletonGraphic.AnimationState.SetAnimation(0, animationName, true); } diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/BoneLocalOverride.cs b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/BoneLocalOverride.cs index 248e490d6..b46001d38 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/BoneLocalOverride.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/BoneLocalOverride.cs @@ -55,7 +55,7 @@ namespace Spine.Unity.Examples { if (Application.isPlaying) return; if (spineComponent == null) spineComponent = GetComponent(); if (spineComponent.IsNullOrDestroyed()) return; - if (bone != null) bone.SetToSetupPose(); + if (bone != null) bone.SetupPose(); OverrideLocal(spineComponent); } #endif @@ -78,13 +78,14 @@ namespace Spine.Unity.Examples { } } + var bonePose = bone.Pose; if (overridePosition) { - bone.X = Mathf.Lerp(bone.X, localPosition.x, alpha); - bone.Y = Mathf.Lerp(bone.Y, localPosition.y, alpha); + bonePose.X = Mathf.Lerp(bonePose.X, localPosition.x, alpha); + bonePose.Y = Mathf.Lerp(bonePose.Y, localPosition.y, alpha); } if (overrideRotation) - bone.Rotation = Mathf.Lerp(bone.Rotation, rotation, alpha); + bonePose.Rotation = Mathf.Lerp(bonePose.Rotation, rotation, alpha); } } diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/CombinedSkin.cs b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/CombinedSkin.cs index 8e447bb99..28588332b 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/CombinedSkin.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/CombinedSkin.cs @@ -53,7 +53,7 @@ namespace Spine.Unity.Examples { } skeleton.SetSkin(combinedSkin); - skeleton.SetToSetupPose(); + skeleton.SetupPose(); IAnimationStateComponent animationStateComponent = skeletonComponent as IAnimationStateComponent; if (animationStateComponent != null) animationStateComponent.AnimationState.Apply(skeleton); } diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/AtlasRegionAttacher.cs b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/AtlasRegionAttacher.cs index e76256df6..d35268680 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/AtlasRegionAttacher.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/AtlasRegionAttacher.cs @@ -64,16 +64,17 @@ namespace Spine.Unity.Examples { foreach (SlotRegionPair entry in attachments) { Slot slot = skeletonRenderer.Skeleton.FindSlot(entry.slot); - Attachment originalAttachment = slot.Attachment; + var slotPose = slot.AppliedPose; + Attachment originalAttachment = slotPose.Attachment; AtlasRegion region = atlas.FindRegion(entry.region); if (region == null) { - slot.Attachment = null; + slotPose.Attachment = null; } else if (inheritProperties && originalAttachment != null) { - slot.Attachment = originalAttachment.GetRemappedClone(region, true, true, scale); + slotPose.Attachment = originalAttachment.GetRemappedClone(region, true, true, scale); } else { RegionAttachment newRegionAttachment = region.ToRegionAttachment(region.name, scale); - slot.Attachment = newRegionAttachment; + slotPose.Attachment = newRegionAttachment; } } } diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs index 243590a8d..bea751ad0 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs @@ -142,7 +142,7 @@ namespace Spine.Unity.Examples { /// Update the slot's attachment to the Attachment generated from the sprite. public void Attach () { if (spineSlot != null) - spineSlot.Attachment = attachment; + spineSlot.AppliedPose.Attachment = attachment; } } @@ -162,7 +162,7 @@ namespace Spine.Unity.Examples { [System.Obsolete] public static RegionAttachment AttachUnitySprite (this Skeleton skeleton, string slotName, Sprite sprite, Shader shader, bool applyPMA, float rotation = 0f) { RegionAttachment att = applyPMA ? sprite.ToRegionAttachmentPMAClone(shader, rotation: rotation) : sprite.ToRegionAttachment(new Material(shader), rotation: rotation); - skeleton.FindSlot(slotName).Attachment = att; + skeleton.FindSlot(slotName).AppliedPose.Attachment = att; return att; } diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonAnimationMulti/SkeletonAnimationMulti.cs b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonAnimationMulti/SkeletonAnimationMulti.cs index 2a63c7247..2d4713e2f 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonAnimationMulti/SkeletonAnimationMulti.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonAnimationMulti/SkeletonAnimationMulti.cs @@ -144,7 +144,7 @@ namespace Spine.Unity { if (skeletonAnimation != null) { SetActiveSkeleton(skeletonAnimation); - skeletonAnimation.skeleton.SetToSetupPose(); + skeletonAnimation.skeleton.SetupPose(); TrackEntry trackEntry = skeletonAnimation.state.SetAnimation(MainTrackIndex, animation, loop); skeletonAnimation.Update(0); return trackEntry; diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonColorInitialize.cs b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonColorInitialize.cs index 6e35909e3..190932d5c 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonColorInitialize.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonColorInitialize.cs @@ -50,7 +50,7 @@ namespace Spine.Unity.Prototyping { void OnValidate () { ISkeletonComponent skeletonComponent = GetComponent(); if (skeletonComponent != null) { - skeletonComponent.Skeleton.SetSlotsToSetupPose(); + skeletonComponent.Skeleton.SetupPoseSlots(); IAnimationStateComponent animationStateComponent = GetComponent(); if (animationStateComponent != null && animationStateComponent.AnimationState != null) { animationStateComponent.AnimationState.Apply(skeletonComponent.Skeleton); diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll.cs b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll.cs index dd83554ce..93e2ed0b9 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll.cs @@ -153,8 +153,9 @@ namespace Spine.Unity.Examples { ragdollRoot.localPosition = new Vector3(skeleton.X, skeleton.Y, 0); ragdollRoot.localRotation = (skeleton.ScaleX < 0) ? Quaternion.Euler(0, 0, 180.0f) : Quaternion.identity; } else { - ragdollRoot.localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0); - ragdollRoot.localRotation = Quaternion.Euler(0, 0, b.Parent.WorldRotationX - b.Parent.ShearX); + var parentPose = b.Parent.AppliedPose; + ragdollRoot.localPosition = new Vector3(parentPose.WorldX, parentPose.WorldY, 0); + ragdollRoot.localRotation = Quaternion.Euler(0, 0, parentPose.WorldRotationX - parentPose.ShearX); } parentTransform = ragdollRoot; rootOffset = t.position - transform.position; @@ -186,7 +187,7 @@ namespace Spine.Unity.Examples { for (int x = 0; x < boneColliders.Count; x++) { for (int y = 0; y < boneColliders.Count; y++) { if (x == y) continue; - Physics.IgnoreCollision(boneColliders[x], boneColliders[y]); + UnityEngine.Physics.IgnoreCollision(boneColliders[x], boneColliders[y]); } } @@ -213,28 +214,30 @@ namespace Spine.Unity.Examples { } // Disable skeleton constraints. - if (disableIK) { - ExposedList ikConstraints = skeleton.IkConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) - ikConstraints.Items[i].Mix = 0; - } - - if (disableOtherConstraints) { - ExposedList transformConstraints = skeleton.TransformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - transformConstraints.Items[i].MixRotate = 0; - transformConstraints.Items[i].MixScaleX = 0; - transformConstraints.Items[i].MixScaleY = 0; - transformConstraints.Items[i].MixShearY = 0; - transformConstraints.Items[i].MixX = 0; - transformConstraints.Items[i].MixY = 0; - } - - ExposedList pathConstraints = skeleton.PathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - pathConstraints.Items[i].MixRotate = 0; - pathConstraints.Items[i].MixX = 0; - pathConstraints.Items[i].MixY = 0; + ExposedList constraints = skeleton.Constraints; + IConstraint[] constraintsItems = constraints.Items; + for (int i = 0, n = constraints.Count; i < n; i++) { + var constraint = constraintsItems[i]; + if (constraint is IkConstraint && disableIK) { + var ikConstraint = ((IkConstraint)constraint); + ikConstraint.Pose.Mix = 0; + } else if (disableOtherConstraints) { + if (constraint is TransformConstraint) { + var transformConstraint = (TransformConstraint)constraint; + var constraintPose = transformConstraint.Pose; + constraintPose.MixRotate = 0; + constraintPose.MixScaleX = 0; + constraintPose.MixScaleY = 0; + constraintPose.MixShearY = 0; + constraintPose.MixX = 0; + constraintPose.MixY = 0; + } else if (constraint is PathConstraint) { + var pathConstraint = (PathConstraint)constraint; + var constraintPose = pathConstraint.Pose; + constraintPose.MixRotate = 0; + constraintPose.MixX = 0; + constraintPose.MixY = 0; + } } } @@ -250,7 +253,7 @@ namespace Spine.Unity.Examples { float startTime = Time.time; float startMix = mix; while (mix > 0) { - skeleton.SetBonesToSetupPose(); + skeleton.SetupPoseBones(); mix = Mathf.SmoothStep(startMix, target, (Time.time - startTime) / duration); yield return null; } @@ -269,7 +272,7 @@ namespace Spine.Unity.Examples { t.position -= offset; UpdateSpineSkeleton(null); - skeleton.UpdateWorldTransform(Skeleton.Physics.Update); + skeleton.UpdateWorldTransform(Physics.Update); } /// Removes the ragdoll instance and effect from the animated skeleton. @@ -301,9 +304,10 @@ namespace Spine.Unity.Examples { boneTable.Add(b, t); t.parent = transform; - t.localPosition = new Vector3(b.WorldX, b.WorldY, 0); - t.localRotation = Quaternion.Euler(0, 0, b.WorldRotationX - b.ShearX); - t.localScale = new Vector3(b.WorldScaleX, b.WorldScaleY, 1); + var bonePose = b.AppliedPose; + t.localPosition = new Vector3(bonePose.WorldX, bonePose.WorldY, 0); + t.localRotation = Quaternion.Euler(0, 0, bonePose.WorldRotationX - bonePose.ShearX); + t.localScale = new Vector3(bonePose.WorldScaleX, bonePose.WorldScaleY, 1); List colliders = AttachBoundingBoxRagdollColliders(b); if (colliders.Count == 0) { @@ -340,8 +344,9 @@ namespace Spine.Unity.Examples { parentFlipX = parentBoneFlip.flipX; parentFlipY = parentBoneFlip.flipY; } - bool flipX = parentFlipX ^ (b.ScaleX < 0); - bool flipY = parentFlipY ^ (b.ScaleY < 0); + var bonePose = b.Pose; + bool flipX = parentFlipX ^ (bonePose.ScaleX < 0); + bool flipY = parentFlipY ^ (bonePose.ScaleY < 0); BoneFlipEntry boneFlip; boneFlipTable.TryGetValue(b, out boneFlip); @@ -354,9 +359,10 @@ namespace Spine.Unity.Examples { if (!oldRagdollBehaviour && isStartingBone) { if (b != skeleton.RootBone) { // RagdollRoot is not skeleton root. - ragdollRoot.localPosition = new Vector3(parentBone.WorldX, parentBone.WorldY, 0); - ragdollRoot.localRotation = Quaternion.Euler(0, 0, parentBone.WorldRotationX - parentBone.ShearX); - ragdollRoot.localScale = new Vector3(parentBone.WorldScaleX, parentBone.WorldScaleY, 1); + var parentPose = parentBone.AppliedPose; + ragdollRoot.localPosition = new Vector3(parentPose.WorldX, parentPose.WorldY, 0); + ragdollRoot.localRotation = Quaternion.Euler(0, 0, parentPose.WorldRotationX - parentPose.ShearX); + ragdollRoot.localScale = new Vector3(parentPose.WorldScaleX, parentPose.WorldScaleY, 1); } } Vector3 parentTransformWorldPosition = parentTransform.position; @@ -368,10 +374,11 @@ namespace Spine.Unity.Examples { if (oldRagdollBehaviour) { if (isStartingBone && b != skeleton.RootBone) { - Vector3 localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0); + var parentPose = b.Parent.AppliedPose; + Vector3 localPosition = new Vector3(parentPose.WorldX, parentPose.WorldY, 0); parentSpaceHelper.position = ragdollRoot.TransformPoint(localPosition); - parentSpaceHelper.localRotation = Quaternion.Euler(0, 0, parentBone.WorldRotationX - parentBone.ShearX); - parentSpaceHelper.localScale = new Vector3(parentBone.WorldScaleX, parentBone.WorldScaleY, 1); + parentSpaceHelper.localRotation = Quaternion.Euler(0, 0, parentPose.WorldRotationX - parentPose.ShearX); + parentSpaceHelper.localScale = new Vector3(parentPose.WorldScaleX, parentPose.WorldScaleY, 1); } } @@ -387,9 +394,9 @@ namespace Spine.Unity.Examples { if (parentFlipXOR) boneLocalRotation *= -1f; if (parentFlipX != flipX) boneLocalRotation += 180; - b.X = Mathf.Lerp(b.X, boneLocalPosition.x, mix); - b.Y = Mathf.Lerp(b.Y, boneLocalPosition.y, mix); - b.Rotation = Mathf.Lerp(b.Rotation, boneLocalRotation, mix); + bonePose.X = Mathf.Lerp(bonePose.X, boneLocalPosition.x, mix); + bonePose.Y = Mathf.Lerp(bonePose.Y, boneLocalPosition.y, mix); + bonePose.Rotation = Mathf.Lerp(bonePose.Rotation, boneLocalRotation, mix); //b.AppliedRotation = Mathf.Lerp(b.AppliedRotation, boneLocalRotation, mix); } } @@ -399,8 +406,9 @@ namespace Spine.Unity.Examples { parentFlipY = skeleton.ScaleY < 0; Bone parent = this.StartingBone == null ? null : this.StartingBone.Parent; while (parent != null) { - parentFlipX ^= parent.ScaleX < 0; - parentFlipY ^= parent.ScaleY < 0; + var parentPose = parent.Pose; + parentFlipX ^= parentPose.ScaleX < 0; + parentFlipY ^= parentPose.ScaleY < 0; parent = parent.Parent; } } diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll2D.cs b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll2D.cs index de07a7851..30576724f 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll2D.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll2D.cs @@ -155,8 +155,9 @@ namespace Spine.Unity.Examples { ragdollRoot.localPosition = new Vector3(skeleton.X, skeleton.Y, 0); ragdollRoot.localRotation = (skeleton.ScaleX < 0) ? Quaternion.Euler(0, 0, 180.0f) : Quaternion.identity; } else { - ragdollRoot.localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0); - ragdollRoot.localRotation = Quaternion.Euler(0, 0, b.Parent.WorldRotationX - b.Parent.ShearX); + var parentPose = b.Parent.AppliedPose; + ragdollRoot.localPosition = new Vector3(parentPose.WorldX, parentPose.WorldY, 0); + ragdollRoot.localRotation = Quaternion.Euler(0, 0, parentPose.WorldRotationX - parentPose.ShearX); } parentTransform = ragdollRoot; rootOffset = t.position - transform.position; @@ -225,28 +226,30 @@ namespace Spine.Unity.Examples { } // Disable skeleton constraints. - if (disableIK) { - ExposedList ikConstraints = skeleton.IkConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) - ikConstraints.Items[i].Mix = 0; - } - - if (disableOtherConstraints) { - ExposedList transformConstraints = skeleton.TransformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - transformConstraints.Items[i].MixRotate = 0; - transformConstraints.Items[i].MixScaleX = 0; - transformConstraints.Items[i].MixScaleY = 0; - transformConstraints.Items[i].MixShearY = 0; - transformConstraints.Items[i].MixX = 0; - transformConstraints.Items[i].MixY = 0; - } - - ExposedList pathConstraints = skeleton.PathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - pathConstraints.Items[i].MixRotate = 0; - pathConstraints.Items[i].MixX = 0; - pathConstraints.Items[i].MixY = 0; + ExposedList constraints = skeleton.Constraints; + IConstraint[] constraintsItems = constraints.Items; + for (int i = 0, n = constraints.Count; i < n; i++) { + var constraint = constraintsItems[i]; + if (constraint is IkConstraint && disableIK) { + var ikConstraint = ((IkConstraint)constraint); + ikConstraint.Pose.Mix = 0; + } else if (disableOtherConstraints) { + if (constraint is TransformConstraint) { + var transformConstraint = (TransformConstraint)constraint; + var constraintPose = transformConstraint.Pose; + constraintPose.MixRotate = 0; + constraintPose.MixScaleX = 0; + constraintPose.MixScaleY = 0; + constraintPose.MixShearY = 0; + constraintPose.MixX = 0; + constraintPose.MixY = 0; + } else if (constraint is PathConstraint) { + var pathConstraint = (PathConstraint)constraint; + var constraintPose = pathConstraint.Pose; + constraintPose.MixRotate = 0; + constraintPose.MixX = 0; + constraintPose.MixY = 0; + } } } @@ -262,7 +265,7 @@ namespace Spine.Unity.Examples { float startTime = Time.time; float startMix = mix; while (mix > 0) { - skeleton.SetBonesToSetupPose(); + skeleton.SetupPoseBones(); mix = Mathf.SmoothStep(startMix, target, (Time.time - startTime) / duration); yield return null; } @@ -281,7 +284,7 @@ namespace Spine.Unity.Examples { t.position -= offset; UpdateSpineSkeleton(null); - skeleton.UpdateWorldTransform(Skeleton.Physics.Update); + skeleton.UpdateWorldTransform(Physics.Update); } /// Removes the ragdoll instance and effect from the animated skeleton. @@ -313,9 +316,10 @@ namespace Spine.Unity.Examples { boneTable.Add(b, t); t.parent = transform; - t.localPosition = new Vector3(b.WorldX, b.WorldY, 0); - t.localRotation = Quaternion.Euler(0, 0, b.WorldRotationX - b.ShearX); - t.localScale = new Vector3(b.WorldScaleX, b.WorldScaleY, 1); + var bonePose = b.AppliedPose; + t.localPosition = new Vector3(bonePose.WorldX, bonePose.WorldY, 0); + t.localRotation = Quaternion.Euler(0, 0, bonePose.WorldRotationX - bonePose.ShearX); + t.localScale = new Vector3(bonePose.WorldScaleX, bonePose.WorldScaleY, 1); List colliders = AttachBoundingBoxRagdollColliders(b, boneGameObject, skeleton, this.gravityScale); if (colliders.Count == 0) { @@ -356,8 +360,9 @@ namespace Spine.Unity.Examples { parentFlipX = parentBoneFlip.flipX; parentFlipY = parentBoneFlip.flipY; } - bool flipX = parentFlipX ^ (b.ScaleX < 0); - bool flipY = parentFlipY ^ (b.ScaleY < 0); + var bonePose = b.Pose; + bool flipX = parentFlipX ^ (bonePose.ScaleX < 0); + bool flipY = parentFlipY ^ (bonePose.ScaleY < 0); BoneFlipEntry boneFlip; boneFlipTable.TryGetValue(b, out boneFlip); @@ -370,9 +375,10 @@ namespace Spine.Unity.Examples { if (!oldRagdollBehaviour && isStartingBone) { if (b != skeleton.RootBone) { // RagdollRoot is not skeleton root. - ragdollRoot.localPosition = new Vector3(parentBone.WorldX, parentBone.WorldY, 0); - ragdollRoot.localRotation = Quaternion.Euler(0, 0, parentBone.WorldRotationX - parentBone.ShearX); - ragdollRoot.localScale = new Vector3(parentBone.WorldScaleX, parentBone.WorldScaleY, 1); + var parentPose = parentBone.AppliedPose; + ragdollRoot.localPosition = new Vector3(parentPose.WorldX, parentPose.WorldY, 0); + ragdollRoot.localRotation = Quaternion.Euler(0, 0, parentPose.WorldRotationX - parentPose.ShearX); + ragdollRoot.localScale = new Vector3(parentPose.WorldScaleX, parentPose.WorldScaleY, 1); } } @@ -385,10 +391,11 @@ namespace Spine.Unity.Examples { if (oldRagdollBehaviour) { if (isStartingBone && b != skeleton.RootBone) { - Vector3 localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0); + var parentPose = b.Parent.AppliedPose; + Vector3 localPosition = new Vector3(parentPose.WorldX, parentPose.WorldY, 0); parentSpaceHelper.position = ragdollRoot.TransformPoint(localPosition); - parentSpaceHelper.localRotation = Quaternion.Euler(0, 0, parentBone.WorldRotationX - parentBone.ShearX); - parentSpaceHelper.localScale = new Vector3(parentBone.WorldScaleX, parentBone.WorldScaleY, 1); + parentSpaceHelper.localRotation = Quaternion.Euler(0, 0, parentPose.WorldRotationX - parentPose.ShearX); + parentSpaceHelper.localScale = new Vector3(parentPose.WorldScaleX, parentPose.WorldScaleY, 1); } } @@ -404,9 +411,9 @@ namespace Spine.Unity.Examples { if (parentFlipXOR) boneLocalRotation *= -1f; if (parentFlipX != flipX) boneLocalRotation += 180; - b.X = Mathf.Lerp(b.X, boneLocalPosition.x, mix); - b.Y = Mathf.Lerp(b.Y, boneLocalPosition.y, mix); - b.Rotation = Mathf.Lerp(b.Rotation, boneLocalRotation, mix); + bonePose.X = Mathf.Lerp(bonePose.X, boneLocalPosition.x, mix); + bonePose.Y = Mathf.Lerp(bonePose.Y, boneLocalPosition.y, mix); + bonePose.Rotation = Mathf.Lerp(bonePose.Rotation, boneLocalRotation, mix); //b.AppliedRotation = Mathf.Lerp(b.AppliedRotation, boneLocalRotation, mix); } } @@ -416,8 +423,9 @@ namespace Spine.Unity.Examples { parentFlipY = skeleton.ScaleY < 0; Bone parent = this.StartingBone == null ? null : this.StartingBone.Parent; while (parent != null) { - parentFlipX ^= parent.ScaleX < 0; - parentFlipY ^= parent.ScaleY < 0; + var parentPose = parent.Pose; + parentFlipX ^= parentPose.ScaleX < 0; + parentFlipY ^= parentPose.ScaleY < 0; parent = parent.Parent; } } @@ -440,7 +448,7 @@ namespace Spine.Unity.Examples { continue; bbAttachmentAdded = true; - PolygonCollider2D bbCollider = SkeletonUtility.AddBoundingBoxAsComponent(bbAttachment, slot, go, isTrigger: false); + PolygonCollider2D bbCollider = SkeletonUtility.AddBoundingBoxAsComponent(bbAttachment, skeleton, slot, go, isTrigger: false); colliders.Add(bbCollider); } } diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonUtilityGroundConstraint.cs b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonUtilityGroundConstraint.cs index 6850606aa..2512defff 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonUtilityGroundConstraint.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonUtilityGroundConstraint.cs @@ -97,9 +97,9 @@ namespace Spine.Unity.Examples { bool validHit = false; if (useRadius) - validHit = Physics.SphereCast(rayOrigin, castRadius, rayDir, out hit, castDistance + groundOffset, groundMask); + validHit = UnityEngine.Physics.SphereCast(rayOrigin, castRadius, rayDir, out hit, castDistance + groundOffset, groundMask); else - validHit = Physics.Raycast(rayOrigin, rayDir, out hit, castDistance + groundOffset, groundMask); + validHit = UnityEngine.Physics.Raycast(rayOrigin, rayDir, out hit, castDistance + groundOffset, groundMask); if (validHit) { hitY = hit.point.y + groundOffset; @@ -116,8 +116,9 @@ namespace Spine.Unity.Examples { v.y = Mathf.Clamp(v.y, Mathf.Min(lastHitY, hitY), float.MaxValue); transform.position = v; - bone.bone.X = transform.localPosition.x / hierarchy.PositionScale; - bone.bone.Y = transform.localPosition.y / hierarchy.PositionScale; + var bonePose = bone.bone.Pose; + bonePose.X = transform.localPosition.x / hierarchy.PositionScale; + bonePose.Y = transform.localPosition.y / hierarchy.PositionScale; lastHitY = hitY; } diff --git a/spine-unity/Assets/Spine Examples/Scripts/SpawnSkeletonGraphicExample.cs b/spine-unity/Assets/Spine Examples/Scripts/SpawnSkeletonGraphicExample.cs index 2e0601c7f..39dd925bc 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/SpawnSkeletonGraphicExample.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/SpawnSkeletonGraphicExample.cs @@ -54,7 +54,7 @@ namespace Spine.Unity.Examples { // Extra Stuff sg.Initialize(false); sg.Skeleton.SetSkin(startingSkin); - sg.Skeleton.SetSlotsToSetupPose(); + sg.Skeleton.SetupPoseSlots(); sg.AnimationState.SetAnimation(0, startingAnimation, true); } } diff --git a/spine-unity/Assets/Spine Examples/Scripts/SpineGauge.cs b/spine-unity/Assets/Spine Examples/Scripts/SpineGauge.cs index 61b3167e8..0e41c3fe4 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/SpineGauge.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/SpineGauge.cs @@ -55,8 +55,8 @@ namespace Spine.Unity.Examples { if (skeletonRenderer == null) return; Skeleton skeleton = skeletonRenderer.skeleton; if (skeleton == null) return; - fillAnimation.Animation.Apply(skeleton, 0, percent, false, null, 1f, MixBlend.Setup, MixDirection.In); - skeleton.UpdateWorldTransform(Skeleton.Physics.Update); + fillAnimation.Animation.Apply(skeleton, 0, percent, false, null, 1f, MixBlend.Setup, MixDirection.In, false); + skeleton.UpdateWorldTransform(Physics.Update); } } diff --git a/spine-unity/Assets/Spine Examples/Scripts/SpineboyBodyTilt.cs b/spine-unity/Assets/Spine Examples/Scripts/SpineboyBodyTilt.cs index ad7ce9391..8becb198e 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/SpineboyBodyTilt.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/SpineboyBodyTilt.cs @@ -56,7 +56,7 @@ namespace Spine.Unity.Examples { hipBone = skeleton.FindBone(hip); headBone = skeleton.FindBone(head); - baseHeadRotation = headBone.Rotation; + baseHeadRotation = headBone.Pose.Rotation; skeletonAnimation.UpdateLocal += UpdateLocal; } @@ -64,8 +64,8 @@ namespace Spine.Unity.Examples { private void UpdateLocal (ISkeletonAnimation animated) { hipRotationTarget = planter.Balance * hipTiltScale; hipRotationSmoothed = Mathf.MoveTowards(hipRotationSmoothed, hipRotationTarget, Time.deltaTime * hipRotationMoveScale * Mathf.Abs(2f * planter.Balance / planter.offBalanceThreshold)); - hipBone.Rotation = hipRotationSmoothed; - headBone.Rotation = baseHeadRotation + (-hipRotationSmoothed * headTiltScale); + hipBone.Pose.Rotation = hipRotationSmoothed; + headBone.Pose.Rotation = baseHeadRotation + (-hipRotationSmoothed * headTiltScale); } } diff --git a/spine-unity/Assets/Spine Examples/Scripts/SpineboyFacialExpression.cs b/spine-unity/Assets/Spine Examples/Scripts/SpineboyFacialExpression.cs index ce48a7ec8..9921162c5 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/SpineboyFacialExpression.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/SpineboyFacialExpression.cs @@ -77,11 +77,11 @@ namespace Spine.Unity.Examples { shockTimer -= Time.deltaTime; if (shockTimer > 0) { - eyeSlot.Attachment = shockEye; - mouthSlot.Attachment = shockMouth; + eyeSlot.AppliedPose.Attachment = shockEye; + mouthSlot.AppliedPose.Attachment = shockMouth; } else { - eyeSlot.Attachment = normalEye; - mouthSlot.Attachment = normalMouth; + eyeSlot.AppliedPose.Attachment = normalEye; + mouthSlot.AppliedPose.Attachment = normalMouth; } } } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs index 2f7869f57..8a28da3db 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs @@ -36,6 +36,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using UnityEditor; using UnityEngine; @@ -340,8 +341,19 @@ namespace Spine.Unity.Editor { EditorGUILayout.LabelField("SkeletonData", EditorStyles.boldLabel); if (targetSkeletonData != null) { SkeletonData sd = targetSkeletonData; - string m = string.Format("{8} - {0} {1}\nBones: {2}\nConstraints: \n {5} IK \n {6} Path \n {7} Transform\n\nSlots: {3}\nSkins: {4}\n\nAnimations: {9}", - sd.Version, string.IsNullOrEmpty(sd.Version) ? "" : "export ", sd.Bones.Count, sd.Slots.Count, sd.Skins.Count, sd.IkConstraints.Count, sd.PathConstraints.Count, sd.TransformConstraints.Count, skeletonJSON.objectReferenceValue.name, sd.Animations.Count); + var ikConstraints = sd.Constraints.OfType(); + var pathConstraints = sd.Constraints.OfType(); + var transformConstraints = sd.Constraints.OfType(); + var physicsConstraints = sd.Constraints.OfType(); + int ikCount = ikConstraints.Count(); + int pathCount = pathConstraints.Count(); + int tcCount = transformConstraints.Count(); + int physicsCount = physicsConstraints.Count(); + + string m = string.Format("{9} - {0} {1}\nBones: {2}\nConstraints: \n {5} IK \n {6} Path \n {7} Transform, {8} Physics, \n\nSlots: {3}\nSkins: {4}\n\nAnimations: {10}", + sd.Version, string.IsNullOrEmpty(sd.Version) ? "" : "export ", + sd.Bones.Count, sd.Slots.Count, sd.Skins.Count, ikCount, pathCount, tcCount, physicsCount, + skeletonJSON.objectReferenceValue.name, sd.Animations.Count); EditorGUILayout.LabelField(GUIContent.none, new GUIContent(Icons.info, m), GUILayout.Width(30f)); } } @@ -552,14 +564,15 @@ namespace Spine.Unity.Editor { for (int a = 0; a < slotAttachments.Count; a++) { Skin.SkinEntry skinEntry = slotAttachments[a]; Attachment attachment = skinEntry.Attachment; + var slotPose = slot.AppliedPose; string attachmentName = skinEntry.Name; bool attachmentIsFromSkin = !defaultSkinAttachments.Contains(skinEntry); Texture2D attachmentTypeIcon = Icons.GetAttachmentIcon(attachment); - bool initialState = slot.Attachment == attachment; + bool initialState = slotPose.Attachment == attachment; Texture2D iconToUse = attachmentIsFromSkin ? Icons.skinPlaceholder : attachmentTypeIcon; - bool toggled = EditorGUILayout.ToggleLeft(SpineInspectorUtility.TempContent(attachmentName, iconToUse), slot.Attachment == attachment, GUILayout.MinWidth(150f)); + bool toggled = EditorGUILayout.ToggleLeft(SpineInspectorUtility.TempContent(attachmentName, iconToUse), slotPose.Attachment == attachment, GUILayout.MinWidth(150f)); if (attachmentIsFromSkin) { Rect extraIconRect = GUILayoutUtility.GetLastRect(); @@ -570,7 +583,7 @@ namespace Spine.Unity.Editor { } if (toggled != initialState) { - slot.Attachment = toggled ? attachment : null; + slotPose.Attachment = toggled ? attachment : null; preview.RefreshOnNextUpdate(); } } @@ -1027,7 +1040,7 @@ namespace Spine.Unity.Editor { } skeletonAnimation.AnimationState.ClearTracks(); - skeletonAnimation.Skeleton.SetToSetupPose(); + skeletonAnimation.Skeleton.SetupPose(); } public void PlayPauseAnimation (string animationName, bool loop) { @@ -1041,7 +1054,7 @@ namespace Spine.Unity.Editor { if (!skeletonAnimation.valid) return; if (string.IsNullOrEmpty(animationName)) { - skeletonAnimation.Skeleton.SetToSetupPose(); + skeletonAnimation.Skeleton.SetupPose(); skeletonAnimation.AnimationState.ClearTracks(); return; } @@ -1056,7 +1069,7 @@ namespace Spine.Unity.Editor { AnimationState animationState = skeletonAnimation.AnimationState; if (isEmpty) { - skeleton.SetToSetupPose(); + skeleton.SetupPose(); animationState.SetAnimation(0, targetAnimation, loop); } else { bool sameAnimation = (currentTrack.Animation == targetAnimation); diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonAnimationInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonAnimationInspector.cs index 5b2f1c2bf..bb75f3c12 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonAnimationInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonAnimationInspector.cs @@ -104,7 +104,7 @@ namespace Spine.Unity.Editor { if (!Application.isPlaying) { if (state != null) state.ClearTrack(0); - skeleton.SetToSetupPose(); + skeleton.SetupPose(); } Spine.Animation animationToUse = skeleton.Data.FindAnimation(animationName.stringValue); diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs index 3718ddebc..c5f60984f 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs @@ -657,7 +657,7 @@ namespace Spine.Unity.Editor { SlotData[] slotsItems = skeletonGraphic.SkeletonData.Slots.Items; for (int i = 0, count = skeletonGraphic.SkeletonData.Slots.Count; i < count; ++i) { SlotData slotData = slotsItems[i]; - if (slotData.HasSecondColor) + if (slotData.GetSetupPose().GetDarkColor().HasValue) return true; } return false; @@ -857,7 +857,7 @@ namespace Spine.Unity.Editor { graphic.Initialize(false); if (skin != null) graphic.Skeleton.SetSkin(skin); graphic.initialSkinName = skin.Name; - graphic.Skeleton.UpdateWorldTransform(Skeleton.Physics.Update); + graphic.Skeleton.UpdateWorldTransform(Physics.Update); graphic.UpdateMesh(); return graphic; } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs index 547c14c2c..e70c92b33 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs @@ -123,10 +123,10 @@ namespace Spine.Unity.Editor { Skeleton skeleton = skeletonRenderer.Skeleton; SkeletonData skeletonData = skeleton.Data; - skeleton.SetToSetupPose(); + skeleton.SetupPose(); if (clip != null) { Spine.Animation animation = skeletonData.FindAnimation(clip.name); - animation.Apply(skeleton, 0, time, false, null, 1.0f, MixBlend.First, MixDirection.In); + animation.Apply(skeleton, 0, time, false, null, 1.0f, MixBlend.First, MixDirection.In, false); } skeletonRenderer.LateUpdate(); } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs index cc7c71a83..c285ab3e7 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs @@ -636,7 +636,7 @@ namespace Spine.Unity.Editor { if (!fieldMatchesSkin) { Skin skinToSet = string.IsNullOrEmpty(componentSkinName) ? null : skeletonRenderer.Skeleton.Data.FindSkin(componentSkinName); skeletonRenderer.Skeleton.SetSkin(skinToSet); - skeletonRenderer.Skeleton.SetSlotsToSetupPose(); + skeletonRenderer.Skeleton.SetupPoseSlots(); // Note: the UpdateIfSkinMismatch concept shall be replaced with e.g. an OnValidate based // solution or in a separate commit. The current solution does not repaint the Game view because diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonUtilityBoneInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonUtilityBoneInspector.cs index e851daaac..aad275784 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonUtilityBoneInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonUtilityBoneInspector.cs @@ -85,7 +85,7 @@ namespace Spine.Unity.Editor { if (multiObject) return; if (utilityBone.bone == null) return; - Skeleton skeleton = utilityBone.bone.Skeleton; + Skeleton skeleton = skeletonUtility.SkeletonComponent.Skeleton; int slotCount = skeleton.Slots.Count; Skin skin = skeleton.Skin; if (skeleton.Skin == null) @@ -208,21 +208,22 @@ namespace Spine.Unity.Editor { EditorGUILayout.LabelField(slot.Data.Name); EditorGUI.indentLevel++; { + Skeleton skeleton = skeletonUtility.SkeletonComponent.Skeleton; foreach (BoundingBoxAttachment box in boundingBoxes) { using (new GUILayout.HorizontalScope()) { GUILayout.Space(30); string buttonLabel = box.IsWeighted() ? box.Name + " (!)" : box.Name; if (GUILayout.Button(buttonLabel, GUILayout.Width(200))) { - utilityBone.bone.Skeleton.UpdateWorldTransform(Skeleton.Physics.Update); + skeleton.UpdateWorldTransform(Physics.Update); Transform bbTransform = utilityBone.transform.Find("[BoundingBox]" + box.Name); // Use FindChild in older versions of Unity. if (bbTransform != null) { PolygonCollider2D originalCollider = bbTransform.GetComponent(); if (originalCollider != null) - SkeletonUtility.SetColliderPointsLocal(originalCollider, slot, box); + SkeletonUtility.SetColliderPointsLocal(originalCollider, skeleton, slot, box); else - SkeletonUtility.AddBoundingBoxAsComponent(box, slot, bbTransform.gameObject); + SkeletonUtility.AddBoundingBoxAsComponent(box, skeleton, slot, bbTransform.gameObject); } else { - PolygonCollider2D newPolygonCollider = SkeletonUtility.AddBoundingBoxGameObject(null, box, slot, utilityBone.transform); + PolygonCollider2D newPolygonCollider = SkeletonUtility.AddBoundingBoxGameObject(null, box, skeleton, slot, utilityBone.transform); bbTransform = newPolygonCollider.transform; } EditorGUIUtility.PingObject(bbTransform); diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonUtilityInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonUtilityInspector.cs index 4a079c838..d6b39344b 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonUtilityInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonUtilityInspector.cs @@ -35,6 +35,7 @@ #define PUBLIC_SET_ICON_FOR_OBJECT #endif +using System.Linq; using System.Reflection; using UnityEditor; using UnityEngine; @@ -144,7 +145,8 @@ namespace Spine.Unity.Editor { Skeleton skeleton = boneComponent.hierarchy.Skeleton; Texture2D icon = boneComponent.bone.Data.Length == 0 ? Icons.nullBone : Icons.boneNib; - foreach (IkConstraint c in skeleton.IkConstraints) + var ikConstraints = skeleton.Constraints.OfType(); + foreach (IkConstraint c in ikConstraints) if (c.Target == boneComponent.bone) { icon = Icons.constraintNib; break; diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs index 0eb25d39e..d81c425d3 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs @@ -463,17 +463,16 @@ namespace Spine.Unity.Editor { protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.constraintIK; } } protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) { - return skeletonData.FindIkConstraint(property.stringValue) != null; + return skeletonData.FindConstraint(property.stringValue) != null; } protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineIkConstraint targetAttribute, SkeletonData data) { - ExposedList constraints = skeletonDataAsset.GetSkeletonData(false).IkConstraints; - if (TargetAttribute.includeNone) menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property)); - for (int i = 0; i < constraints.Count; i++) { - string name = constraints.Items[i].Name; + var ikConstraints = skeletonDataAsset.GetSkeletonData(false).Constraints.OfType(); + foreach (var ikConstraint in ikConstraints) { + string name = ikConstraint.Name; if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal)) menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property)); } @@ -487,17 +486,16 @@ namespace Spine.Unity.Editor { protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.constraintTransform; } } protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) { - return skeletonData.FindTransformConstraint(property.stringValue) != null; + return skeletonData.FindConstraint(property.stringValue) != null; } protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineTransformConstraint targetAttribute, SkeletonData data) { - ExposedList constraints = skeletonDataAsset.GetSkeletonData(false).TransformConstraints; - if (TargetAttribute.includeNone) menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property)); - for (int i = 0; i < constraints.Count; i++) { - string name = constraints.Items[i].Name; + var transformConstraints = skeletonDataAsset.GetSkeletonData(false).Constraints.OfType(); + foreach (var constraint in transformConstraints) { + string name = constraint.Name; if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal)) menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property)); } @@ -510,17 +508,16 @@ namespace Spine.Unity.Editor { protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.constraintPath; } } protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) { - return skeletonData.FindPathConstraint(property.stringValue) != null; + return skeletonData.FindConstraint(property.stringValue) != null; } protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpinePathConstraint targetAttribute, SkeletonData data) { - ExposedList constraints = skeletonDataAsset.GetSkeletonData(false).PathConstraints; - if (TargetAttribute.includeNone) menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property)); - for (int i = 0; i < constraints.Count; i++) { - string name = constraints.Items[i].Name; + var pathConstraints = skeletonDataAsset.GetSkeletonData(false).Constraints.OfType(); + foreach (var constraint in pathConstraints) { + string name = constraint.Name; if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal)) menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property)); } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs index 65fd5e6cf..0b698c8a7 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs @@ -1414,7 +1414,7 @@ namespace Spine.Unity.Editor { newSkeletonAnimation.loop = SpineEditorUtilities.Preferences.defaultInstantiateLoop; newSkeletonAnimation.state.Update(0); newSkeletonAnimation.state.Apply(newSkeletonAnimation.skeleton); - newSkeletonAnimation.skeleton.UpdateWorldTransform(Skeleton.Physics.Update); + newSkeletonAnimation.skeleton.UpdateWorldTransform(Physics.Update); return newSkeletonAnimation; } @@ -1497,7 +1497,7 @@ namespace Spine.Unity.Editor { throw e; } - newSkeletonMecanim.skeleton.UpdateWorldTransform(Skeleton.Physics.Update); + newSkeletonMecanim.skeleton.UpdateWorldTransform(Physics.Update); newSkeletonMecanim.LateUpdate(); return newSkeletonMecanim; diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs index 6b7064fc8..5d76527ef 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs @@ -171,12 +171,13 @@ namespace Spine.Unity.Editor { Vector2 offset = positionOffset == null ? Vector2.zero : positionOffset.Value; GUIStyle style = BoneNameStyle; - foreach (Bone b in skeleton.Bones) { - if (!b.Active) continue; - Vector3 pos = new Vector3(b.WorldX * positionScale + offset.x, b.WorldY * positionScale + offset.y, 0) - + (new Vector3(b.A, b.C) * (b.Data.Length * 0.5f)); + foreach (Bone bone in skeleton.Bones) { + if (!bone.Active) continue; + var bonePose = bone.AppliedPose; + Vector3 pos = new Vector3(bonePose.WorldX * positionScale + offset.x, bonePose.WorldY * positionScale + offset.y, 0) + + (new Vector3(bonePose.A, bonePose.C) * (bone.Data.Length * 0.5f)); pos = transform.TransformPoint(pos); - Handles.Label(pos, b.Data.Name, style); + Handles.Label(pos, bone.Data.Name, style); } } @@ -203,18 +204,19 @@ namespace Spine.Unity.Editor { _boneWireBuffer[4] = _boneWireBuffer[0]; // closed polygon. return _boneWireBuffer; } - public static void DrawBoneWireframe (Transform transform, Bone b, Color color, float skeletonRenderScale = 1f, + public static void DrawBoneWireframe (Transform transform, Bone bone, Color color, float skeletonRenderScale = 1f, Vector2? positionOffset = null) { if (UnityEngine.Event.current.type != EventType.Repaint) return; Vector2 offset = positionOffset == null ? Vector2.zero : positionOffset.Value; Handles.color = color; - Vector3 pos = new Vector3(b.WorldX * skeletonRenderScale + offset.x, b.WorldY * skeletonRenderScale + offset.y, 0); - float length = b.Data.Length; + var bonePose = bone.AppliedPose; + Vector3 pos = new Vector3(bonePose.WorldX * skeletonRenderScale + offset.x, bonePose.WorldY * skeletonRenderScale + offset.y, 0); + float length = bone.Data.Length; if (length > 0) { - Quaternion rot = Quaternion.Euler(0, 0, b.WorldRotationX); - Vector3 scale = Vector3.one * length * b.WorldScaleX * skeletonRenderScale; + Quaternion rot = Quaternion.Euler(0, 0, bonePose.WorldRotationX); + Vector3 scale = Vector3.one * length * bonePose.WorldScaleX * skeletonRenderScale; const float my = 1.5f; scale.y *= (SpineEditorUtilities.Preferences.handleScale + 1) * 0.5f; scale.y = Mathf.Clamp(scale.x, -my * skeletonRenderScale, my * skeletonRenderScale); @@ -227,16 +229,17 @@ namespace Spine.Unity.Editor { } } - public static void DrawBone (Transform transform, Bone b, float boneScale, float skeletonRenderScale = 1f, + public static void DrawBone (Transform transform, Bone bone, float boneScale, float skeletonRenderScale = 1f, Vector2? positionOffset = null) { if (UnityEngine.Event.current.type != EventType.Repaint) return; - + + var bonePose = bone.AppliedPose; Vector2 offset = positionOffset == null ? Vector2.zero : positionOffset.Value; - Vector3 pos = new Vector3(b.WorldX * skeletonRenderScale + offset.x, b.WorldY * skeletonRenderScale + offset.y, 0); - float length = b.Data.Length; + Vector3 pos = new Vector3(bonePose.WorldX * skeletonRenderScale + offset.x, bonePose.WorldY * skeletonRenderScale + offset.y, 0); + float length = bone.Data.Length; if (length > 0) { - Quaternion rot = Quaternion.Euler(0, 0, b.WorldRotationX); - Vector3 scale = Vector3.one * length * b.WorldScaleX * skeletonRenderScale; + Quaternion rot = Quaternion.Euler(0, 0, bonePose.WorldRotationX); + Vector3 scale = Vector3.one * length * bonePose.WorldScaleX * skeletonRenderScale; const float my = 1.5f; scale.y *= (SpineEditorUtilities.Preferences.handleScale + 1f) * 0.5f; scale.y = Mathf.Clamp(scale.x, -my * skeletonRenderScale, my * skeletonRenderScale); @@ -248,16 +251,17 @@ namespace Spine.Unity.Editor { } } - public static void DrawBone (Transform transform, Bone b, float boneScale, Color color, float skeletonRenderScale = 1f, + public static void DrawBone (Transform transform, Bone bone, float boneScale, Color color, float skeletonRenderScale = 1f, Vector2? positionOffset = null) { if (UnityEngine.Event.current.type != EventType.Repaint) return; + var bonePose = bone.AppliedPose; Vector2 offset = positionOffset == null ? Vector2.zero : positionOffset.Value; - Vector3 pos = new Vector3(b.WorldX * skeletonRenderScale + offset.x, b.WorldY * skeletonRenderScale + offset.y, 0); - float length = b.Data.Length; + Vector3 pos = new Vector3(bonePose.WorldX * skeletonRenderScale + offset.x, bonePose.WorldY * skeletonRenderScale + offset.y, 0); + float length = bone.Data.Length; if (length > 0) { - Quaternion rot = Quaternion.Euler(0, 0, b.WorldRotationX); - Vector3 scale = Vector3.one * length * b.WorldScaleX; + Quaternion rot = Quaternion.Euler(0, 0, bonePose.WorldRotationX); + Vector3 scale = Vector3.one * length * bonePose.WorldScaleX; const float my = 1.5f; scale.y *= (SpineEditorUtilities.Preferences.handleScale + 1f) * 0.5f; scale.y = Mathf.Clamp(scale.x, -my, my); @@ -273,13 +277,13 @@ namespace Spine.Unity.Editor { if (UnityEngine.Event.current.type != EventType.Repaint) return; foreach (Slot s in skeleton.DrawOrder) { - PathAttachment p = s.Attachment as PathAttachment; - if (p != null) SpineHandles.DrawPath(s, p, transform, true); + PathAttachment p = s.AppliedPose.Attachment as PathAttachment; + if (p != null) SpineHandles.DrawPath(skeleton, s, p, transform, true); } } static float[] pathVertexBuffer; - public static void DrawPath (Slot s, PathAttachment p, Transform t, bool includeName) { + public static void DrawPath (Skeleton skeleton, Slot s, PathAttachment p, Transform t, bool includeName) { if (UnityEngine.Event.current.type != EventType.Repaint) return; int worldVerticesLength = p.WorldVerticesLength; @@ -288,7 +292,7 @@ namespace Spine.Unity.Editor { pathVertexBuffer = new float[worldVerticesLength]; float[] pv = pathVertexBuffer; - p.ComputeWorldVertices(s, pv); + p.ComputeWorldVertices(skeleton, s, pv); Color ocolor = Handles.color; Handles.color = SpineHandles.PathColor; @@ -334,18 +338,18 @@ namespace Spine.Unity.Editor { if (UnityEngine.Event.current.type != EventType.Repaint) return; foreach (Slot slot in skeleton.Slots) { - BoundingBoxAttachment bba = slot.Attachment as BoundingBoxAttachment; - if (bba != null) SpineHandles.DrawBoundingBox(slot, bba, transform); + BoundingBoxAttachment bba = slot.AppliedPose.Attachment as BoundingBoxAttachment; + if (bba != null) SpineHandles.DrawBoundingBox(skeleton, slot, bba, transform); } } - public static void DrawBoundingBox (Slot slot, BoundingBoxAttachment box, Transform t) { + public static void DrawBoundingBox (Skeleton skeleton, Slot slot, BoundingBoxAttachment box, Transform t) { if (UnityEngine.Event.current.type != EventType.Repaint) return; if (box.Vertices.Length <= 2) return; // Handle cases where user creates a BoundingBoxAttachment but doesn't actually define it. float[] worldVerts = new float[box.WorldVerticesLength]; - box.ComputeWorldVertices(slot, worldVerts); + box.ComputeWorldVertices(skeleton, slot, worldVerts); Handles.color = Color.green; Vector3 lastVert = Vector3.zero; @@ -374,8 +378,9 @@ namespace Spine.Unity.Editor { if (pointAttachment == null) return; Vector2 localPos; - pointAttachment.ComputeWorldPosition(bone, out localPos.x, out localPos.y); - float localRotation = pointAttachment.ComputeWorldRotation(bone); + var bonePose = bone.AppliedPose; + pointAttachment.ComputeWorldPosition(bonePose, out localPos.x, out localPos.y); + float localRotation = pointAttachment.ComputeWorldRotation(bonePose); Matrix4x4 m = Matrix4x4.TRS(localPos, Quaternion.Euler(0, 0, localRotation), Vector3.one) * Matrix4x4.TRS(Vector3.right * 0.25f, Quaternion.identity, Vector3.one); DrawBoneCircle(skeletonTransform.TransformPoint(localPos), SpineHandles.PointColor, Vector3.back, 1.3f); @@ -396,31 +401,41 @@ namespace Spine.Unity.Editor { // Transform Constraints handleColor = SpineHandles.TransformContraintColor; - foreach (TransformConstraint tc in skeleton.TransformConstraints) { - Bone sourceBone = tc.Source; + foreach (IConstraint constraint in skeleton.Constraints) { + var transformConstraint = constraint as TransformConstraint; + if (transformConstraint == null) continue; + + Bone sourceBone = transformConstraint.Source; + var sourcePose = sourceBone.AppliedPose; targetPos = sourceBone.GetWorldPosition(transform, skeletonRenderScale, offset); + var tc = transformConstraint.AppliedPose; if (tc.MixX > 0 || tc.MixY > 0) { if ((tc.MixX > 0 && tc.MixX != 1f) || (tc.MixY > 0 && tc.MixY != 1f)) { Handles.color = handleColor; - foreach (Bone b in tc.Bones) { + foreach (BonePose b in transformConstraint.Bones) { pos = b.GetWorldPosition(transform, skeletonRenderScale, offset); Handles.DrawDottedLine(targetPos, pos, Thickness); } } SpineHandles.DrawBoneCircle(targetPos, handleColor, normal, 1.3f * skeletonRenderScale); Handles.color = handleColor; - SpineHandles.DrawCrosshairs(targetPos, 0.2f, sourceBone.A, sourceBone.B, sourceBone.C, sourceBone.D, transform, skeletonRenderScale); + SpineHandles.DrawCrosshairs(targetPos, 0.2f, sourcePose.A, sourcePose.B, sourcePose.C, sourcePose.D, transform, skeletonRenderScale); } } // IK Constraints handleColor = SpineHandles.IkColor; - foreach (IkConstraint ikc in skeleton.IkConstraints) { - Bone targetBone = ikc.Target; + foreach (IConstraint constraint in skeleton.Constraints) { + var ikConstraint = constraint as IkConstraint; + if (ikConstraint == null) continue; + + var ikc = ikConstraint.AppliedPose; + Bone targetBone = ikConstraint.Target; + BonePose targetBonePose = targetBone.AppliedPose; targetPos = targetBone.GetWorldPosition(transform, skeletonRenderScale, offset); - ExposedList bones = ikc.Bones; + ExposedList bones = ikConstraint.Bones; active = ikc.Mix > 0; if (active) { pos = bones.Items[0].GetWorldPosition(transform, skeletonRenderScale, offset); @@ -430,13 +445,13 @@ namespace Spine.Unity.Editor { Handles.DrawLine(targetPos, pos); SpineHandles.DrawBoneCircle(targetPos, handleColor, normal); Matrix4x4 m = bones.Items[0].GetMatrix4x4(); - m.m03 = targetBone.WorldX * skeletonRenderScale + offset.x; - m.m13 = targetBone.WorldY * skeletonRenderScale + offset.y; + m.m03 = targetBonePose.WorldX * skeletonRenderScale + offset.x; + m.m13 = targetBonePose.WorldY * skeletonRenderScale + offset.y; SpineHandles.DrawArrowhead(transform.localToWorldMatrix * m); break; } case 2: { - Bone childBone = bones.Items[1]; + BonePose childBone = bones.Items[1]; Vector3 child = childBone.GetWorldPosition(transform, skeletonRenderScale, offset); Handles.color = handleColor; Handles.DrawLine(child, pos); @@ -445,8 +460,8 @@ namespace Spine.Unity.Editor { SpineHandles.DrawBoneCircle(child, handleColor, normal, 0.5f); SpineHandles.DrawBoneCircle(targetPos, handleColor, normal); Matrix4x4 m = childBone.GetMatrix4x4(); - m.m03 = targetBone.WorldX * skeletonRenderScale + offset.x; - m.m13 = targetBone.WorldY * skeletonRenderScale + offset.y; + m.m03 = targetBonePose.WorldX * skeletonRenderScale + offset.x; + m.m13 = targetBonePose.WorldY * skeletonRenderScale + offset.y; SpineHandles.DrawArrowhead(transform.localToWorldMatrix * m); break; } @@ -457,10 +472,14 @@ namespace Spine.Unity.Editor { // Path Constraints handleColor = SpineHandles.PathColor; - foreach (PathConstraint pc in skeleton.PathConstraints) { + foreach (IConstraint constraint in skeleton.Constraints) { + var pathConstraint = constraint as PathConstraint; + if (pathConstraint == null) continue; + + var pc = pathConstraint.AppliedPose; active = pc.MixX > 0 || pc.MixY > 0 || pc.MixRotate > 0; if (active) - foreach (Bone b in pc.Bones) + foreach (BonePose b in pathConstraint.Bones) SpineHandles.DrawBoneCircle(b.GetWorldPosition(transform, skeletonRenderScale, offset), handleColor, normal, 1f * skeletonRenderScale); } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonBaker.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonBaker.cs index db9471fa8..e03cee831 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonBaker.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonBaker.cs @@ -323,6 +323,7 @@ namespace Spine.Unity.Editor { for (int i = 0; i < skeletonData.Bones.Count; i++) { BoneData boneData = skeletonData.Bones.Items[i]; + var setup = boneData.GetSetupPose(); Transform boneTransform = boneTable[boneData.Name]; Transform parentTransform = null; if (i > 0) @@ -331,15 +332,15 @@ namespace Spine.Unity.Editor { parentTransform = boneTransform.parent; boneTransform.parent = parentTransform; - boneTransform.localPosition = new Vector3(boneData.X, boneData.Y, 0); - Inherit inherit = boneData.Inherit; + boneTransform.localPosition = new Vector3(setup.X, setup.Y, 0); + Inherit inherit = setup.Inherit; if (inherit.InheritsRotation()) - boneTransform.localRotation = Quaternion.Euler(0, 0, boneData.Rotation); + boneTransform.localRotation = Quaternion.Euler(0, 0, setup.Rotation); else - boneTransform.rotation = Quaternion.Euler(0, 0, boneData.Rotation); + boneTransform.rotation = Quaternion.Euler(0, 0, setup.Rotation); if (inherit.InheritsScale()) - boneTransform.localScale = new Vector3(boneData.ScaleX, boneData.ScaleY, 1); + boneTransform.localScale = new Vector3(setup.ScaleX, setup.ScaleY, 1); } //create slots and attachments @@ -477,31 +478,36 @@ namespace Spine.Unity.Editor { SkeletonData skelData = new SkeletonData(); BoneData data = new BoneData(0, "temp", null) { - ScaleX = 1, - ScaleY = 1, Length = 100 }; + var setup = data.GetSetupPose(); + setup.ScaleX = setup.ScaleY = 1; skelData.Bones.Add(data); Skeleton skeleton = new Skeleton(skelData); - Bone bone = new Bone(data, skeleton, null); - bone.UpdateWorldTransform(); + Bone bone = new Bone(data, null); + bone.AppliedPose.UpdateWorldTransform(skeleton); DummyBone = bone; return DummyBone; } + internal static Skeleton GetDummySkeleton () { + SkeletonData skelData = new SkeletonData(); + return new Skeleton(skelData); + } + internal static Slot GetDummySlot () { if (DummySlot != null) return DummySlot; Bone bone = GetDummyBone(); - + Skeleton skeleton = GetDummySkeleton(); SlotData data = new SlotData(0, "temp", bone.Data); - Slot slot = new Slot(data, bone); + Slot slot = new Slot(data, skeleton); DummySlot = slot; return DummySlot; } @@ -511,11 +517,12 @@ namespace Spine.Unity.Editor { Bone bone = slot.Bone; if (centered) { - bone.X = -attachment.X; - bone.Y = -attachment.Y; + bone.Pose.X = -attachment.X; + bone.Pose.Y = -attachment.Y; } - bone.UpdateWorldTransform(); + Skeleton skeleton = GetDummySkeleton(); + bone.AppliedPose.UpdateWorldTransform(skeleton); float[] floatVerts = new float[8]; attachment.ComputeWorldVertices(slot, floatVerts, 0); @@ -549,13 +556,14 @@ namespace Spine.Unity.Editor { internal static Mesh ExtractMeshAttachment (string name, MeshAttachment attachment, Mesh mesh = null) { Slot slot = GetDummySlot(); + Skeleton skeleton = GetDummySkeleton(); - slot.Bone.X = 0; - slot.Bone.Y = 0; - slot.Bone.UpdateWorldTransform(); + slot.Bone.Pose.X = 0; + slot.Bone.Pose.Y = 0; + slot.Bone.AppliedPose.UpdateWorldTransform(skeleton); float[] floatVerts = new float[attachment.WorldVerticesLength]; - attachment.ComputeWorldVertices(slot, floatVerts); + attachment.ComputeWorldVertices(skeleton, slot, floatVerts); Vector2[] uvs = ExtractUV(attachment.UVs); Vector3[] verts = ExtractVerts(floatVerts); @@ -617,15 +625,15 @@ namespace Spine.Unity.Editor { throw new System.ArgumentException("Mesh is not weighted.", "attachment"); Skeleton skeleton = new Skeleton(skeletonData); - skeleton.UpdateWorldTransform(Skeleton.Physics.Update); + skeleton.UpdateWorldTransform(Physics.Update); float[] floatVerts = new float[attachment.WorldVerticesLength]; - attachment.ComputeWorldVertices(skeleton.Slots.Items[slotIndex], floatVerts); + attachment.ComputeWorldVertices(skeleton, skeleton.Slots.Items[slotIndex], floatVerts); Vector2[] uvs = ExtractUV(attachment.UVs); Vector3[] verts = ExtractVerts(floatVerts); int[] triangles = attachment.Triangles; - Color color = new Color(attachment.R, attachment.G, attachment.B, attachment.A); + Color color = attachment.GetColor(); mesh = (mesh == null) ? new Mesh() : mesh; @@ -768,26 +776,28 @@ namespace Spine.Unity.Editor { List ignoreRotateTimelineIndexes = new List(); if (bakeIK) { - foreach (IkConstraint i in skeleton.IkConstraints) { - foreach (Bone b in i.Bones) { - ignoreRotateTimelineIndexes.Add(b.Data.Index); - BakeBoneConstraints(b, animation, clip); + var ikConstraints = skeleton.Constraints.OfType(); + foreach (IkConstraint i in ikConstraints) { + foreach (BonePose b in i.Bones) { + + ignoreRotateTimelineIndexes.Add(b.bone.Data.Index); + BakeBoneConstraints(skeleton, b.bone, animation, clip); } } } foreach (Bone b in skeleton.Bones) { - if (!b.Data.Inherit.InheritsRotation()) { + if (!b.Pose.Inherit.InheritsRotation()) { int index = b.Data.Index; if (ignoreRotateTimelineIndexes.Contains(index) == false) { ignoreRotateTimelineIndexes.Add(index); - BakeBoneConstraints(b, animation, clip); + BakeBoneConstraints(skeleton, b, animation, clip); } } } foreach (Timeline t in timelines) { - skeleton.SetToSetupPose(); + skeleton.SetupPose(); if (t is ScaleTimeline) { ParseScaleTimeline(skeleton, (ScaleTimeline)t, clip); @@ -833,19 +843,18 @@ namespace Spine.Unity.Editor { return n - 1; } - static void BakeBoneConstraints (Bone bone, Spine.Animation animation, AnimationClip clip) { - Skeleton skeleton = bone.Skeleton; - bool inheritRotation = bone.Data.Inherit.InheritsRotation(); + static void BakeBoneConstraints (Skeleton skeleton, Bone bone, Spine.Animation animation, AnimationClip clip) { + bool inheritRotation = bone.Pose.Inherit.InheritsRotation(); - animation.Apply(skeleton, 0, 0, false, null, 1f, MixBlend.Setup, MixDirection.In); - skeleton.UpdateWorldTransform(Skeleton.Physics.Update); + animation.Apply(skeleton, 0, 0, false, null, 1f, MixBlend.Setup, MixDirection.In, false); + skeleton.UpdateWorldTransform(Physics.Update); float duration = animation.Duration; AnimationCurve curve = new AnimationCurve(); List keys = new List(); - float rotation = bone.AppliedRotation; + float rotation = bone.AppliedPose.Rotation; if (!inheritRotation) rotation = GetUninheritedAppliedRotation(bone); @@ -865,8 +874,8 @@ namespace Spine.Unity.Editor { if (i == steps) currentTime = duration; - animation.Apply(skeleton, 0, currentTime, true, null, 1f, MixBlend.Setup, MixDirection.In); - skeleton.UpdateWorldTransform(Skeleton.Physics.Update); + animation.Apply(skeleton, 0, currentTime, true, null, 1f, MixBlend.Setup, MixDirection.In, false); + skeleton.UpdateWorldTransform(Physics.Update); int pIndex = listIndex; @@ -874,7 +883,7 @@ namespace Spine.Unity.Editor { pk = keys[pIndex]; - rotation = inheritRotation ? bone.AppliedRotation : GetUninheritedAppliedRotation(bone); + rotation = inheritRotation ? bone.AppliedPose.Rotation : GetUninheritedAppliedRotation(bone); angle += Mathf.DeltaAngle(angle, rotation); @@ -906,7 +915,9 @@ namespace Spine.Unity.Editor { static void ParseTranslateTimeline (Skeleton skeleton, TranslateTimeline timeline, AnimationClip clip) { BoneData boneData = skeleton.Data.Bones.Items[timeline.BoneIndex]; + var setup = boneData.GetSetupPose(); Bone bone = skeleton.Bones.Items[timeline.BoneIndex]; + var bonePose = bone.Pose; AnimationCurve xCurve = new AnimationCurve(); AnimationCurve yCurve = new AnimationCurve(); @@ -919,14 +930,14 @@ namespace Spine.Unity.Editor { List xKeys = new List(); List yKeys = new List(); - xKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[1] + boneData.X, 0, 0)); - yKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[2] + boneData.Y, 0, 0)); + xKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[1] + setup.X, 0, 0)); + yKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[2] + setup.Y, 0, 0)); int listIndex = 0; int frameIndex = 0; int f = TranslateTimeline.ENTRIES; float[] frames = timeline.Frames; - skeleton.SetToSetupPose(); + skeleton.SetupPose(); float lastTime = 0; while (currentTime < endTime) { int pIndex = listIndex; @@ -938,8 +949,8 @@ namespace Spine.Unity.Editor { Keyframe py = yKeys[pIndex]; float time = frames[f]; - float x = frames[f + 1] + boneData.X; - float y = frames[f + 2] + boneData.Y; + float x = frames[f + 1] + setup.X; + float y = frames[f + 2] + setup.Y; float xOut = (x - px.value) / (time - px.time); float yOut = (y - py.value) / (time - py.time); @@ -955,7 +966,7 @@ namespace Spine.Unity.Editor { currentTime = time; - timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In); + timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false); lastTime = time; listIndex++; @@ -965,8 +976,8 @@ namespace Spine.Unity.Editor { Keyframe py = yKeys[pIndex]; float time = frames[f]; - float x = frames[f + 1] + boneData.X; - float y = frames[f + 2] + boneData.Y; + float x = frames[f + 1] + setup.X; + float y = frames[f + 2] + setup.Y; float xOut = float.PositiveInfinity; float yOut = float.PositiveInfinity; @@ -982,7 +993,7 @@ namespace Spine.Unity.Editor { currentTime = time; - timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In); + timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false); lastTime = time; listIndex++; @@ -1000,19 +1011,19 @@ namespace Spine.Unity.Editor { if (i == steps) currentTime = time; - timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In); + timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false); px = xKeys[listIndex]; py = yKeys[listIndex]; - float xOut = (bone.X - px.value) / (currentTime - px.time); - float yOut = (bone.Y - py.value) / (currentTime - py.time); + float xOut = (bonePose.X - px.value) / (currentTime - px.time); + float yOut = (bonePose.Y - py.value) / (currentTime - py.time); px.outTangent = xOut; py.outTangent = yOut; - xKeys.Add(new Keyframe(currentTime, bone.X, xOut, 0)); - yKeys.Add(new Keyframe(currentTime, bone.Y, yOut, 0)); + xKeys.Add(new Keyframe(currentTime, bonePose.X, xOut, 0)); + yKeys.Add(new Keyframe(currentTime, bonePose.Y, yOut, 0)); xKeys[listIndex] = px; yKeys[listIndex] = py; @@ -1049,8 +1060,10 @@ namespace Spine.Unity.Editor { IBoneTimeline boneTimeline = isXTimeline ? timelineX : timelineY as IBoneTimeline; BoneData boneData = skeleton.Data.Bones.Items[boneTimeline.BoneIndex]; + var setup = boneData.GetSetupPose(); Bone bone = skeleton.Bones.Items[boneTimeline.BoneIndex]; - float boneDataOffset = isXTimeline ? boneData.X : boneData.Y; + var bonePose = bone.Pose; + float boneDataOffset = isXTimeline ? setup.X : setup.Y; AnimationCurve curve = new AnimationCurve(); float endTime = timeline.Frames[(timeline.FrameCount * TranslateXTimeline.ENTRIES) - TranslateXTimeline.ENTRIES]; @@ -1062,7 +1075,7 @@ namespace Spine.Unity.Editor { int frameIndex = 0; int f = TranslateXTimeline.ENTRIES; float[] frames = timeline.Frames; - skeleton.SetToSetupPose(); + skeleton.SetupPose(); float lastTime = 0; while (currentTime < endTime) { int pIndex = listIndex; @@ -1080,7 +1093,7 @@ namespace Spine.Unity.Editor { keys[pIndex] = p; currentTime = time; - timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In); + timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false); lastTime = time; listIndex++; @@ -1096,7 +1109,7 @@ namespace Spine.Unity.Editor { keys[pIndex] = p; currentTime = time; - timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In); + timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false); lastTime = time; listIndex++; @@ -1113,10 +1126,10 @@ namespace Spine.Unity.Editor { if (i == steps) currentTime = time; - timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In); + timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false); p = keys[listIndex]; - float boneOffset = isXTimeline ? bone.X : bone.Y; + float boneOffset = isXTimeline ? bonePose.X : bonePose.Y; float valueOut = (boneOffset - p.value) / (currentTime - p.time); p.outTangent = valueOut; keys.Add(new Keyframe(currentTime, boneOffset, valueOut, 0)); @@ -1142,7 +1155,9 @@ namespace Spine.Unity.Editor { static void ParseScaleTimeline (Skeleton skeleton, ScaleTimeline timeline, AnimationClip clip) { BoneData boneData = skeleton.Data.Bones.Items[timeline.BoneIndex]; + var setup = boneData.GetSetupPose(); Bone bone = skeleton.Bones.Items[timeline.BoneIndex]; + var bonePose = bone.Pose; AnimationCurve xCurve = new AnimationCurve(); AnimationCurve yCurve = new AnimationCurve(); @@ -1155,14 +1170,14 @@ namespace Spine.Unity.Editor { List xKeys = new List(); List yKeys = new List(); - xKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[1] * boneData.ScaleX, 0, 0)); - yKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[2] * boneData.ScaleY, 0, 0)); + xKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[1] * setup.ScaleX, 0, 0)); + yKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[2] * setup.ScaleY, 0, 0)); int listIndex = 0; int frameIndex = 0; int f = ScaleTimeline.ENTRIES; float[] frames = timeline.Frames; - skeleton.SetToSetupPose(); + skeleton.SetupPose(); float lastTime = 0; while (currentTime < endTime) { int pIndex = listIndex; @@ -1173,8 +1188,8 @@ namespace Spine.Unity.Editor { Keyframe py = yKeys[pIndex]; float time = frames[f]; - float x = frames[f + 1] * boneData.ScaleX; - float y = frames[f + 2] * boneData.ScaleY; + float x = frames[f + 1] * setup.ScaleX; + float y = frames[f + 2] * setup.ScaleY; float xOut = (x - px.value) / (time - px.time); float yOut = (y - py.value) / (time - py.time); @@ -1190,7 +1205,7 @@ namespace Spine.Unity.Editor { currentTime = time; - timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In); + timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false); lastTime = time; listIndex++; @@ -1200,8 +1215,8 @@ namespace Spine.Unity.Editor { Keyframe py = yKeys[pIndex]; float time = frames[f]; - float x = frames[f + 1] * boneData.ScaleX; - float y = frames[f + 2] * boneData.ScaleY; + float x = frames[f + 1] * setup.ScaleX; + float y = frames[f + 2] * setup.ScaleY; float xOut = float.PositiveInfinity; float yOut = float.PositiveInfinity; @@ -1217,7 +1232,7 @@ namespace Spine.Unity.Editor { currentTime = time; - timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In); + timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false); lastTime = time; listIndex++; @@ -1235,19 +1250,19 @@ namespace Spine.Unity.Editor { if (i == steps) currentTime = time; - timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In); + timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false); px = xKeys[listIndex]; py = yKeys[listIndex]; - float xOut = (bone.ScaleX - px.value) / (currentTime - px.time); - float yOut = (bone.ScaleY - py.value) / (currentTime - py.time); + float xOut = (bonePose.ScaleX - px.value) / (currentTime - px.time); + float yOut = (bonePose.ScaleY - py.value) / (currentTime - py.time); px.outTangent = xOut; py.outTangent = yOut; - xKeys.Add(new Keyframe(currentTime, bone.ScaleX, xOut, 0)); - yKeys.Add(new Keyframe(currentTime, bone.ScaleY, yOut, 0)); + xKeys.Add(new Keyframe(currentTime, bonePose.ScaleX, xOut, 0)); + yKeys.Add(new Keyframe(currentTime, bonePose.ScaleY, yOut, 0)); xKeys[listIndex] = px; yKeys[listIndex] = py; @@ -1280,8 +1295,10 @@ namespace Spine.Unity.Editor { IBoneTimeline boneTimeline = isXTimeline ? timelineX : timelineY as IBoneTimeline; BoneData boneData = skeleton.Data.Bones.Items[boneTimeline.BoneIndex]; + var setup = boneData.GetSetupPose(); Bone bone = skeleton.Bones.Items[boneTimeline.BoneIndex]; - float boneDataOffset = isXTimeline ? boneData.ScaleX : boneData.ScaleY; + var bonePose = bone.Pose; + float boneDataOffset = isXTimeline ? setup.ScaleX : setup.ScaleY; AnimationCurve curve = new AnimationCurve(); float endTime = timeline.Frames[(timeline.FrameCount * ScaleXTimeline.ENTRIES) - ScaleXTimeline.ENTRIES]; @@ -1293,7 +1310,7 @@ namespace Spine.Unity.Editor { int frameIndex = 0; int f = ScaleXTimeline.ENTRIES; float[] frames = timeline.Frames; - skeleton.SetToSetupPose(); + skeleton.SetupPose(); float lastTime = 0; while (currentTime < endTime) { int pIndex = listIndex; @@ -1310,7 +1327,7 @@ namespace Spine.Unity.Editor { keys[pIndex] = p; currentTime = time; - timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In); + timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false); lastTime = time; listIndex++; @@ -1326,7 +1343,7 @@ namespace Spine.Unity.Editor { keys[pIndex] = p; currentTime = time; - timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In); + timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false); lastTime = time; listIndex++; @@ -1341,11 +1358,11 @@ namespace Spine.Unity.Editor { if (i == steps) currentTime = time; - timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In); + timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false); p = keys[listIndex]; - float boneScale = isXTimeline ? bone.ScaleX : bone.ScaleY; + float boneScale = isXTimeline ? bonePose.ScaleX : bonePose.ScaleY; float valueOut = (boneScale - p.value) / (currentTime - p.time); p.outTangent = valueOut; keys.Add(new Keyframe(currentTime, boneScale, valueOut, 0)); @@ -1371,7 +1388,9 @@ namespace Spine.Unity.Editor { static void ParseRotateTimeline (Skeleton skeleton, RotateTimeline timeline, AnimationClip clip) { BoneData boneData = skeleton.Data.Bones.Items[timeline.BoneIndex]; + var setup = boneData.GetSetupPose(); Bone bone = skeleton.Bones.Items[timeline.BoneIndex]; + var bonePose = bone.Pose; AnimationCurve curve = new AnimationCurve(); @@ -1381,7 +1400,7 @@ namespace Spine.Unity.Editor { List keys = new List(); - float rotation = timeline.Frames[1] + boneData.Rotation; + float rotation = timeline.Frames[1] + setup.Rotation; keys.Add(new Keyframe(timeline.Frames[0], rotation, 0, 0)); @@ -1389,7 +1408,7 @@ namespace Spine.Unity.Editor { int frameIndex = 0; int f = 2; float[] frames = timeline.Frames; - skeleton.SetToSetupPose(); + skeleton.SetupPose(); float lastTime = 0; float angle = rotation; while (currentTime < endTime) { @@ -1402,7 +1421,7 @@ namespace Spine.Unity.Editor { float time = frames[f]; - rotation = frames[f + 1] + boneData.Rotation; + rotation = frames[f + 1] + setup.Rotation; angle += Mathf.DeltaAngle(angle, rotation); float r = angle; @@ -1416,7 +1435,7 @@ namespace Spine.Unity.Editor { currentTime = time; - timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In); + timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false); lastTime = time; listIndex++; @@ -1427,7 +1446,7 @@ namespace Spine.Unity.Editor { float time = frames[f]; - rotation = frames[f + 1] + boneData.Rotation; + rotation = frames[f + 1] + setup.Rotation; angle += Mathf.DeltaAngle(angle, rotation); float r = angle; @@ -1441,7 +1460,7 @@ namespace Spine.Unity.Editor { currentTime = time; - timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In); + timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false); lastTime = time; listIndex++; @@ -1451,10 +1470,10 @@ namespace Spine.Unity.Editor { float time = frames[f]; - timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In); - skeleton.UpdateWorldTransform(Skeleton.Physics.Update); + timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false); + skeleton.UpdateWorldTransform(Physics.Update); - rotation = frames[f + 1] + boneData.Rotation; + rotation = frames[f + 1] + setup.Rotation; angle += Mathf.DeltaAngle(angle, rotation); float r = angle; @@ -1465,11 +1484,11 @@ namespace Spine.Unity.Editor { if (i == steps) currentTime = time; - timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In); - skeleton.UpdateWorldTransform(Skeleton.Physics.Update); + timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false); + skeleton.UpdateWorldTransform(Physics.Update); pk = keys[listIndex]; - rotation = bone.Rotation; + rotation = bonePose.Rotation; angle += Mathf.DeltaAngle(angle, rotation); r = angle; @@ -1621,10 +1640,10 @@ namespace Spine.Unity.Editor { static float GetUninheritedAppliedRotation (Bone b) { Bone parent = b.Parent; - float angle = b.AppliedRotation; + float angle = b.AppliedPose.Rotation; while (parent != null) { - angle -= parent.AppliedRotation; + angle -= parent.AppliedPose.Rotation; parent = parent.Parent; } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonDebugWindow.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonDebugWindow.cs index 5547bc5dc..aab6dff09 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonDebugWindow.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonDebugWindow.cs @@ -36,6 +36,7 @@ #endif using System.Collections.Generic; +using System.Linq; using UnityEditor; using UnityEditor.AnimatedValues; using UnityEngine; @@ -218,8 +219,8 @@ namespace Spine.Unity.Editor { scrollPos = EditorGUILayout.BeginScrollView(scrollPos); using (new SpineInspectorUtility.BoxScope(false)) { - if (SpineInspectorUtility.CenteredButton(SpineInspectorUtility.TempContent("Skeleton.SetToSetupPose()"))) { - skeleton.SetToSetupPose(); + if (SpineInspectorUtility.CenteredButton(SpineInspectorUtility.TempContent("Skeleton.SetupPose()"))) { + skeleton.SetupPose(); requireRepaint = true; } @@ -277,10 +278,12 @@ namespace Spine.Unity.Editor { using (new EditorGUI.DisabledGroupScope(true)) { bool wm = EditorGUIUtility.wideMode; EditorGUIUtility.wideMode = true; - EditorGUILayout.Slider("Local Rotation", ViewRound(bone.Rotation), -180f, 180f); - EditorGUILayout.Vector2Field("Local Position", RoundVector2(bone.X, bone.Y)); - EditorGUILayout.Vector2Field("Local Scale", RoundVector2(bone.ScaleX, bone.ScaleY)); - EditorGUILayout.Vector2Field("Local Shear", RoundVector2(bone.ShearX, bone.ShearY)); + var bonePose = bone.Pose; + var appliedPose = bone.AppliedPose; + EditorGUILayout.Slider("Local Rotation", ViewRound(bonePose.Rotation), -180f, 180f); + EditorGUILayout.Vector2Field("Local Position", RoundVector2(bonePose.X, bonePose.Y)); + EditorGUILayout.Vector2Field("Local Scale", RoundVector2(bonePose.ScaleX, bonePose.ScaleY)); + EditorGUILayout.Vector2Field("Local Shear", RoundVector2(bonePose.ShearX, bonePose.ShearY)); EditorGUILayout.Space(); @@ -296,21 +299,21 @@ namespace Spine.Unity.Editor { EditorGUILayout.BeginHorizontal(); EditorGUILayout.Space(); - EditorGUILayout.TextField(".A", bone.A.ToString(RoundFormat)); - EditorGUILayout.TextField(".B", bone.B.ToString(RoundFormat)); + EditorGUILayout.TextField(".A", appliedPose.A.ToString(RoundFormat)); + EditorGUILayout.TextField(".B", appliedPose.B.ToString(RoundFormat)); EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); EditorGUILayout.Space(); - EditorGUILayout.TextField(".C", bone.C.ToString(RoundFormat)); - EditorGUILayout.TextField(".D", bone.D.ToString(RoundFormat)); + EditorGUILayout.TextField(".C", appliedPose.C.ToString(RoundFormat)); + EditorGUILayout.TextField(".D", appliedPose.D.ToString(RoundFormat)); EditorGUILayout.EndHorizontal(); EditorGUIUtility.labelWidth = lw * 0.5f; EditorGUILayout.BeginHorizontal(); EditorGUILayout.Space(); EditorGUILayout.Space(); - EditorGUILayout.TextField(".WorldX", bone.WorldX.ToString(RoundFormat)); - EditorGUILayout.TextField(".WorldY", bone.WorldY.ToString(RoundFormat)); + EditorGUILayout.TextField(".WorldX", appliedPose.WorldX.ToString(RoundFormat)); + EditorGUILayout.TextField(".WorldY", appliedPose.WorldY.ToString(RoundFormat)); EditorGUILayout.EndHorizontal(); EditorGUIUtility.labelWidth = lw; @@ -332,8 +335,8 @@ namespace Spine.Unity.Editor { showSlotsTree.target = EditorGUILayout.Foldout(showSlotsTree.target, SlotsRootLabel, BoldFoldoutStyle); if (showSlotsTree.faded > 0) { using (new EditorGUILayout.FadeGroupScope(showSlotsTree.faded)) { - if (SpineInspectorUtility.CenteredButton(SpineInspectorUtility.TempContent("Skeleton.SetSlotsToSetupPose()"))) { - skeleton.SetSlotsToSetupPose(); + if (SpineInspectorUtility.CenteredButton(SpineInspectorUtility.TempContent("Skeleton.SetupPoseSlots()"))) { + skeleton.SetupPose(); requireRepaint = true; } @@ -345,7 +348,7 @@ namespace Spine.Unity.Editor { EditorGUI.indentLevel = baseIndent + 1; EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(slot.Data.Name, Icons.slot), GUILayout.ExpandWidth(false)); EditorGUI.BeginChangeCheck(); - Color c = EditorGUILayout.ColorField(new Color(slot.R, slot.G, slot.B, slot.A), GUILayout.Width(60)); + Color c = EditorGUILayout.ColorField(slot.GetColor(), GUILayout.Width(60)); if (EditorGUI.EndChangeCheck()) { slot.SetColor(c); requireRepaint = true; @@ -354,13 +357,14 @@ namespace Spine.Unity.Editor { foreach (Skin.SkinEntry skinEntry in pair.Value) { Attachment attachment = skinEntry.Attachment; - GUI.contentColor = slot.Attachment == attachment ? Color.white : Color.grey; + var slotPose = slot.AppliedPose; + GUI.contentColor = slotPose.Attachment == attachment ? Color.white : Color.grey; EditorGUI.indentLevel = baseIndent + 2; Texture2D icon = Icons.GetAttachmentIcon(attachment); - bool isAttached = (attachment == slot.Attachment); - bool swap = EditorGUILayout.ToggleLeft(SpineInspectorUtility.TempContent(attachment.Name, icon), attachment == slot.Attachment); + bool isAttached = (attachment == slotPose.Attachment); + bool swap = EditorGUILayout.ToggleLeft(SpineInspectorUtility.TempContent(attachment.Name, icon), attachment == slotPose.Attachment); if (isAttached != swap) { - slot.Attachment = isAttached ? null : attachment; + slotPose.Attachment = isAttached ? null : attachment; requireRepaint = true; } GUI.contentColor = Color.white; @@ -371,6 +375,13 @@ namespace Spine.Unity.Editor { EditorGUI.indentLevel = preSlotsIndent; // Constraints + var ikConstraints = skeleton.Constraints.OfType(); + int ikConstraintsCount = ikConstraints.Count(); + var transformConstraints = skeleton.Constraints.OfType(); + int transformConstraintsCount = transformConstraints.Count(); + var pathConstraints = skeleton.Constraints.OfType(); + int pathConstraintsCount = pathConstraints.Count(); + const string NoneText = ""; showConstraintsTree.target = EditorGUILayout.Foldout(showConstraintsTree.target, SpineInspectorUtility.TempContent("Constraints", Icons.constraintRoot), BoldFoldoutStyle); if (showConstraintsTree.faded > 0) { @@ -384,14 +395,15 @@ namespace Spine.Unity.Editor { EditorGUILayout.Space(); - EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(string.Format("IK Constraints ({0})", skeleton.IkConstraints.Count), Icons.constraintIK), EditorStyles.boldLabel); + EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(string.Format("IK Constraints ({0})", ikConstraintsCount), Icons.constraintIK), EditorStyles.boldLabel); using (new SpineInspectorUtility.IndentScope()) { - if (skeleton.IkConstraints.Count > 0) { - foreach (IkConstraint c in skeleton.IkConstraints) { - EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(c.Data.Name, Icons.constraintIK)); - FalseDropDown("Goal", c.Data.Target.Name, Icons.bone, true); + if (ikConstraintsCount > 0) { + foreach (IkConstraint ikConstraint in ikConstraints) { + var c = ikConstraint.AppliedPose; + EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(ikConstraint.Data.Name, Icons.constraintIK)); + FalseDropDown("Goal", ikConstraint.Target.Data.Name, Icons.bone, true); using (new EditorGUI.DisabledGroupScope(true)) { - EditorGUILayout.Toggle(SpineInspectorUtility.TempContent("Data.Uniform", tooltip: "Uniformly scales a bone when Ik stretches or compresses."), c.Data.Uniform); + EditorGUILayout.Toggle(SpineInspectorUtility.TempContent("Data.Uniform", tooltip: "Uniformly scales a bone when Ik stretches or compresses."), ikConstraint.Data.Uniform); } EditorGUI.BeginChangeCheck(); @@ -409,13 +421,14 @@ namespace Spine.Unity.Editor { } } - EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(string.Format("Transform Constraints ({0})", skeleton.TransformConstraints.Count), Icons.constraintTransform), EditorStyles.boldLabel); + EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(string.Format("Transform Constraints ({0})", transformConstraintsCount), Icons.constraintTransform), EditorStyles.boldLabel); using (new SpineInspectorUtility.IndentScope()) { - if (skeleton.TransformConstraints.Count > 0) { - foreach (TransformConstraint c in skeleton.TransformConstraints) { - EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(c.Data.Name, Icons.constraintTransform)); + if (transformConstraintsCount > 0) { + foreach (TransformConstraint transformConstraint in transformConstraints) { + var c = transformConstraint.AppliedPose; + EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(transformConstraint.Data.Name, Icons.constraintTransform)); EditorGUI.BeginDisabledGroup(true); - FalseDropDown("Source", c.Data.Source.Name, Icons.bone); + FalseDropDown("Source", transformConstraint.Source.Data.Name, Icons.bone); EditorGUI.EndDisabledGroup(); EditorGUI.BeginChangeCheck(); @@ -434,13 +447,14 @@ namespace Spine.Unity.Editor { } } - EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(string.Format("Path Constraints ({0})", skeleton.PathConstraints.Count), Icons.constraintPath), EditorStyles.boldLabel); + EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(string.Format("Path Constraints ({0})", pathConstraintsCount), Icons.constraintPath), EditorStyles.boldLabel); EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(string.Format("Physics Constraints ({0})", skeleton.PhysicsConstraints.Count), Icons.constraintIK), EditorStyles.boldLabel); using (new SpineInspectorUtility.IndentScope()) { if (skeleton.PhysicsConstraints.Count > 0) { - foreach (PhysicsConstraint c in skeleton.PhysicsConstraints) { - EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(c.Data.Name, Icons.constraintIK)); + foreach (PhysicsConstraint physicsConstraint in skeleton.PhysicsConstraints) { + var c = physicsConstraint.AppliedPose; + EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(physicsConstraint.Data.Name, Icons.constraintIK)); EditorGUI.BeginChangeCheck(); c.Mix = EditorGUILayout.Slider("Mix", c.Mix, MixMin, MixMax); @@ -465,16 +479,18 @@ namespace Spine.Unity.Editor { requireRepaint |= EditorGUI.EndChangeCheck(); using (new SpineInspectorUtility.IndentScope()) { - if (skeleton.PathConstraints.Count > 0) { - foreach (PathConstraint c in skeleton.PathConstraints) { - EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(c.Data.Name, Icons.constraintPath)); + if (pathConstraintsCount > 0) { + foreach (PathConstraint pathConstraint in pathConstraints) { + var c = pathConstraint.AppliedPose; + var data = pathConstraint.Data; + EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(pathConstraint.Data.Name, Icons.constraintPath)); EditorGUI.BeginDisabledGroup(true); - FalseDropDown("Path Slot", c.Data.Slot.Name, Icons.slot); - Attachment activeAttachment = c.Target.Attachment; + FalseDropDown("Path Slot", pathConstraint.Slot.Data.Name, Icons.slot); + Attachment activeAttachment = pathConstraint.Slot.AppliedPose.Attachment; FalseDropDown("Active Path", activeAttachment != null ? activeAttachment.Name : "", activeAttachment is PathAttachment ? Icons.path : null); - EditorGUILayout.LabelField("PositionMode." + c.Data.PositionMode); - EditorGUILayout.LabelField("SpacingMode." + c.Data.SpacingMode); - EditorGUILayout.LabelField("RotateMode." + c.Data.RotateMode); + EditorGUILayout.LabelField("PositionMode." + data.PositionMode); + EditorGUILayout.LabelField("SpacingMode." + data.SpacingMode); + EditorGUILayout.LabelField("RotateMode." + data.RotateMode); EditorGUI.EndDisabledGroup(); EditorGUI.BeginChangeCheck(); @@ -555,9 +571,9 @@ namespace Spine.Unity.Editor { EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Slots", Icons.slotRoot, "Skeleton.Data.Slots"), new GUIContent(skeletonData.Slots.Count.ToString())); EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Skins", Icons.skinsRoot, "Skeleton.Data.Skins"), new GUIContent(skeletonData.Skins.Count.ToString())); EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Events", Icons.userEvent, "Skeleton.Data.Events"), new GUIContent(skeletonData.Events.Count.ToString())); - EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("IK Constraints", Icons.constraintIK, "Skeleton.Data.IkConstraints"), new GUIContent(skeletonData.IkConstraints.Count.ToString())); - EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Transform Constraints", Icons.constraintTransform, "Skeleton.Data.TransformConstraints"), new GUIContent(skeletonData.TransformConstraints.Count.ToString())); - EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Path Constraints", Icons.constraintPath, "Skeleton.Data.PathConstraints"), new GUIContent(skeletonData.PathConstraints.Count.ToString())); + EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("IK Constraints", Icons.constraintIK, "Skeleton.Data.IkConstraints"), new GUIContent(ikConstraintsCount.ToString())); + EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Transform Constraints", Icons.constraintTransform, "Skeleton.Data.TransformConstraints"), new GUIContent(transformConstraintsCount.ToString())); + EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Path Constraints", Icons.constraintPath, "Skeleton.Data.PathConstraints"), new GUIContent(pathConstraintsCount.ToString())); } } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollower.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollower.cs index 25c686bf0..ebef9c3a0 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollower.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollower.cs @@ -147,10 +147,10 @@ namespace Spine.Unity { if (!Application.isPlaying) skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent); #endif - + Skeleton skeleton = skeletonRenderer.skeleton; if (bone == null) { if (string.IsNullOrEmpty(boneName)) return; - bone = skeletonRenderer.skeleton.FindBone(boneName); + bone = skeleton.FindBone(boneName); if (!SetBone(boneName)) return; } @@ -167,29 +167,29 @@ namespace Spine.Unity { float cumulativeScaleY = 1.0f; Bone p = parentBone; while (p != null) { - cumulativeScaleX *= p.ScaleX; - cumulativeScaleY *= p.ScaleY; + cumulativeScaleX *= p.AppliedPose.ScaleX; + cumulativeScaleY *= p.AppliedPose.ScaleY; p = p.Parent; }; scaleSignX = Mathf.Sign(cumulativeScaleX); scaleSignY = Mathf.Sign(cumulativeScaleY); - localScale = new Vector3(parentBone.WorldScaleX * scaleSignX, parentBone.WorldScaleY * scaleSignY, 1f); + localScale = new Vector3(parentBone.AppliedPose.WorldScaleX * scaleSignX, parentBone.AppliedPose.WorldScaleY * scaleSignY, 1f); } if (followLocalScale) - localScale.Scale(new Vector3(bone.ScaleX, bone.ScaleY, 1f)); + localScale.Scale(new Vector3(bone.AppliedPose.ScaleX, bone.AppliedPose.ScaleY, 1f)); if (followSkeletonFlip) - localScale.y *= Mathf.Sign(bone.Skeleton.ScaleX * bone.Skeleton.ScaleY) * additionalFlipScale; + localScale.y *= Mathf.Sign(skeleton.ScaleX * skeleton.ScaleY) * additionalFlipScale; thisTransform.localScale = localScale; } if (skeletonTransformIsParent) { // Recommended setup: Use local transform properties if Spine GameObject is the immediate parent - thisTransform.localPosition = new Vector3(followXYPosition ? bone.WorldX : thisTransform.localPosition.x, - followXYPosition ? bone.WorldY : thisTransform.localPosition.y, + thisTransform.localPosition = new Vector3(followXYPosition ? bone.AppliedPose.WorldX : thisTransform.localPosition.x, + followXYPosition ? bone.AppliedPose.WorldY : thisTransform.localPosition.y, followZPosition ? 0f : thisTransform.localPosition.z); if (followBoneRotation) { - float halfRotation = Mathf.Atan2(bone.C, bone.A) * 0.5f; - if (followLocalScale && bone.ScaleX < 0) // Negate rotation from negative scaleX. Don't use negative determinant. local scaleY doesn't factor into used rotation. + float halfRotation = Mathf.Atan2(bone.AppliedPose.C, bone.AppliedPose.A) * 0.5f; + if (followLocalScale && bone.AppliedPose.ScaleX < 0) // Negate rotation from negative scaleX. Don't use negative determinant. local scaleY doesn't factor into used rotation. halfRotation += Mathf.PI * 0.5f; if (followParentWorldScale && scaleSignX < 0) halfRotation += Mathf.PI * 0.5f; @@ -201,7 +201,7 @@ namespace Spine.Unity { } } else { // For special cases: Use transform world properties if transform relationship is complicated - Vector3 targetWorldPosition = skeletonTransform.TransformPoint(new Vector3(bone.WorldX, bone.WorldY, 0f)); + Vector3 targetWorldPosition = skeletonTransform.TransformPoint(new Vector3(bone.AppliedPose.WorldX, bone.AppliedPose.WorldY, 0f)); if (!followZPosition) targetWorldPosition.z = thisTransform.position.z; if (!followXYPosition) { targetWorldPosition.x = thisTransform.position.x; @@ -212,7 +212,7 @@ namespace Spine.Unity { Transform transformParent = thisTransform.parent; Vector3 parentLossyScale = transformParent != null ? transformParent.lossyScale : Vector3.one; if (followBoneRotation) { - float boneWorldRotation = bone.WorldRotationX; + float boneWorldRotation = bone.AppliedPose.WorldRotationX; if ((skeletonLossyScale.x * skeletonLossyScale.y) < 0) boneWorldRotation = -boneWorldRotation; @@ -228,7 +228,7 @@ namespace Spine.Unity { boneWorldRotation += 180f; Vector3 worldRotation = skeletonTransform.rotation.eulerAngles; - if (followLocalScale && bone.ScaleX < 0) boneWorldRotation += 180f; + if (followLocalScale && bone.AppliedPose.ScaleX < 0) boneWorldRotation += 180f; thisTransform.SetPositionAndRotation(targetWorldPosition, Quaternion.Euler(worldRotation.x, worldRotation.y, worldRotation.z + boneWorldRotation)); } else { thisTransform.position = targetWorldPosition; diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollowerGraphic.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollowerGraphic.cs index 3520bc487..448c12476 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollowerGraphic.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollowerGraphic.cs @@ -129,10 +129,10 @@ namespace Spine.Unity { if (!Application.isPlaying) skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent); #endif - + Skeleton skeleton = skeletonGraphic.Skeleton; if (bone == null) { if (string.IsNullOrEmpty(boneName)) return; - bone = skeletonGraphic.Skeleton.FindBone(boneName); + bone = skeleton.FindBone(boneName); if (!SetBone(boneName)) return; } @@ -153,31 +153,31 @@ namespace Spine.Unity { float cumulativeScaleY = 1.0f; Bone p = parentBone; while (p != null) { - cumulativeScaleX *= p.ScaleX; - cumulativeScaleY *= p.ScaleY; + cumulativeScaleX *= p.AppliedPose.ScaleX; + cumulativeScaleY *= p.AppliedPose.ScaleY; p = p.Parent; }; scaleSignX = Mathf.Sign(cumulativeScaleX); scaleSignY = Mathf.Sign(cumulativeScaleY); - localScale = new Vector3(parentBone.WorldScaleX * scaleSignX, parentBone.WorldScaleY * scaleSignY, 1f); + localScale = new Vector3(parentBone.AppliedPose.WorldScaleX * scaleSignX, parentBone.AppliedPose.WorldScaleY * scaleSignY, 1f); } if (followLocalScale) - localScale.Scale(new Vector3(bone.ScaleX, bone.ScaleY, 1f)); + localScale.Scale(new Vector3(bone.AppliedPose.ScaleX, bone.AppliedPose.ScaleY, 1f)); if (followSkeletonFlip) - localScale.y *= Mathf.Sign(bone.Skeleton.ScaleX * bone.Skeleton.ScaleY) * additionalFlipScale; + localScale.y *= Mathf.Sign(skeleton.ScaleX * skeleton.ScaleY) * additionalFlipScale; thisTransform.localScale = localScale; } if (skeletonTransformIsParent) { // Recommended setup: Use local transform properties if Spine GameObject is the immediate parent - thisTransform.localPosition = new Vector3(followXYPosition ? bone.WorldX * scale + offset.x : thisTransform.localPosition.x, - followXYPosition ? bone.WorldY * scale + offset.y : thisTransform.localPosition.y, + thisTransform.localPosition = new Vector3(followXYPosition ? bone.AppliedPose.WorldX * scale + offset.x : thisTransform.localPosition.x, + followXYPosition ? bone.AppliedPose.WorldY * scale + offset.y : thisTransform.localPosition.y, followZPosition ? 0f : thisTransform.localPosition.z); if (followBoneRotation) thisTransform.localRotation = bone.GetQuaternion(); } else { // For special cases: Use transform world properties if transform relationship is complicated Vector3 targetWorldPosition = skeletonTransform.TransformPoint( - new Vector3(bone.WorldX * scale + offset.x, bone.WorldY * scale + offset.y, 0f)); + new Vector3(bone.AppliedPose.WorldX * scale + offset.x, bone.AppliedPose.WorldY * scale + offset.y, 0f)); if (!followZPosition) targetWorldPosition.z = thisTransform.position.z; if (!followXYPosition) { targetWorldPosition.x = thisTransform.position.x; @@ -188,7 +188,7 @@ namespace Spine.Unity { Transform transformParent = thisTransform.parent; Vector3 parentLossyScale = transformParent != null ? transformParent.lossyScale : Vector3.one; if (followBoneRotation) { - float boneWorldRotation = bone.WorldRotationX; + float boneWorldRotation = bone.AppliedPose.WorldRotationX; if ((skeletonLossyScale.x * skeletonLossyScale.y) < 0) boneWorldRotation = -boneWorldRotation; @@ -204,7 +204,7 @@ namespace Spine.Unity { boneWorldRotation += 180f; Vector3 worldRotation = skeletonTransform.rotation.eulerAngles; - if (followLocalScale && bone.ScaleX < 0) boneWorldRotation += 180f; + if (followLocalScale && bone.AppliedPose.ScaleX < 0) boneWorldRotation += 180f; thisTransform.SetPositionAndRotation(targetWorldPosition, Quaternion.Euler(worldRotation.x, worldRotation.y, worldRotation.z + boneWorldRotation)); } else { thisTransform.position = targetWorldPosition; diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoundingBoxFollower.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoundingBoxFollower.cs index b1bc7ddf9..311d69a15 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoundingBoxFollower.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoundingBoxFollower.cs @@ -99,7 +99,6 @@ namespace Spine.Unity { // Don't reinitialize if the setup did not change. if (!overwrite && colliderTable.Count > 0 && slot != null && // Slot is set and colliders already populated. - skeletonRenderer.skeleton == slot.Skeleton && // Skeleton object did not change. slotName == slot.Data.Name // Slot object did not change. ) return; @@ -126,10 +125,10 @@ namespace Spine.Unity { PolygonCollider2D[] colliders = GetComponents(); if (this.gameObject.activeInHierarchy) { foreach (Skin skin in skeleton.Data.Skins) - AddCollidersForSkin(skin, slotIndex, colliders, ref requiredCollidersCount); + AddCollidersForSkin(skin, skeleton, slotIndex, colliders, ref requiredCollidersCount); if (skeleton.Skin != null) - AddCollidersForSkin(skeleton.Skin, slotIndex, colliders, ref requiredCollidersCount); + AddCollidersForSkin(skeleton.Skin, skeleton, slotIndex, colliders, ref requiredCollidersCount); } DisposeExcessCollidersAfter(requiredCollidersCount); skinBoneEnabled = slot.Bone.Active; @@ -145,7 +144,7 @@ namespace Spine.Unity { } } - void AddCollidersForSkin (Skin skin, int slotIndex, PolygonCollider2D[] previousColliders, ref int collidersCount) { + void AddCollidersForSkin (Skin skin, Skeleton skeleton, int slotIndex, PolygonCollider2D[] previousColliders, ref int collidersCount) { if (skin == null) return; List skinEntries = new List(); skin.GetAttachments(slotIndex, skinEntries); @@ -162,7 +161,7 @@ namespace Spine.Unity { PolygonCollider2D bbCollider = collidersCount < previousColliders.Length ? previousColliders[collidersCount] : gameObject.AddComponent(); ++collidersCount; - SkeletonUtility.SetColliderPointsLocal(bbCollider, slot, boundingBoxAttachment); + SkeletonUtility.SetColliderPointsLocal(bbCollider, skeleton, slot, boundingBoxAttachment); bbCollider.isTrigger = isTrigger; bbCollider.usedByEffector = usedByEffector; bbCollider.usedByComposite = usedByComposite; @@ -211,9 +210,10 @@ namespace Spine.Unity { } void LateUpdate () { - if (slot != null && (slot.Attachment != currentAttachment || skinBoneEnabled != slot.Bone.Active)) { + var slotPose = slot.AppliedPose; + if (slot != null && (slotPose.Attachment != currentAttachment || skinBoneEnabled != slot.Bone.Active)) { skinBoneEnabled = slot.Bone.Active; - MatchAttachment(slot.Attachment); + MatchAttachment(slotPose.Attachment); } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoundingBoxFollowerGraphic.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoundingBoxFollowerGraphic.cs index baeb9dc8c..f9e229523 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoundingBoxFollowerGraphic.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoundingBoxFollowerGraphic.cs @@ -99,7 +99,6 @@ namespace Spine.Unity { // Don't reinitialize if the setup did not change. if (!overwrite && colliderTable.Count > 0 && slot != null && // Slot is set and colliders already populated. - skeletonGraphic.Skeleton == slot.Skeleton && // Skeleton object did not change. slotName == slot.Data.Name // Slot object did not change. ) return; @@ -127,10 +126,10 @@ namespace Spine.Unity { if (this.gameObject.activeInHierarchy) { float scale = skeletonGraphic.MeshScale; foreach (Skin skin in skeleton.Data.Skins) - AddCollidersForSkin(skin, slotIndex, colliders, scale, ref requiredCollidersCount); + AddCollidersForSkin(skin, skeleton, slotIndex, colliders, scale, ref requiredCollidersCount); if (skeleton.Skin != null) - AddCollidersForSkin(skeleton.Skin, slotIndex, colliders, scale, ref requiredCollidersCount); + AddCollidersForSkin(skeleton.Skin, skeleton, slotIndex, colliders, scale, ref requiredCollidersCount); } DisposeExcessCollidersAfter(requiredCollidersCount); skinBoneEnabled = slot.Bone.Active; @@ -146,7 +145,7 @@ namespace Spine.Unity { } } - void AddCollidersForSkin (Skin skin, int slotIndex, PolygonCollider2D[] previousColliders, float scale, ref int collidersCount) { + void AddCollidersForSkin (Skin skin, Skeleton skeleton, int slotIndex, PolygonCollider2D[] previousColliders, float scale, ref int collidersCount) { if (skin == null) return; List skinEntries = new List(); skin.GetAttachments(slotIndex, skinEntries); @@ -163,7 +162,7 @@ namespace Spine.Unity { PolygonCollider2D bbCollider = collidersCount < previousColliders.Length ? previousColliders[collidersCount] : gameObject.AddComponent(); ++collidersCount; - SkeletonUtility.SetColliderPointsLocal(bbCollider, slot, boundingBoxAttachment, scale); + SkeletonUtility.SetColliderPointsLocal(bbCollider, skeleton, slot, boundingBoxAttachment, scale); bbCollider.isTrigger = isTrigger; bbCollider.usedByEffector = usedByEffector; bbCollider.usedByComposite = usedByComposite; @@ -212,9 +211,10 @@ namespace Spine.Unity { } void LateUpdate () { - if (slot != null && (slot.Attachment != currentAttachment || skinBoneEnabled != slot.Bone.Active)) { + var slotPose = slot.AppliedPose; + if (slot != null && (slotPose.Attachment != currentAttachment || skinBoneEnabled != slot.Bone.Active)) { skinBoneEnabled = slot.Bone.Active; - MatchAttachment(slot.Attachment); + MatchAttachment(slotPose.Attachment); } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/PointFollower.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/PointFollower.cs index be28b1f4f..3a62ba8c8 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/PointFollower.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/PointFollower.cs @@ -118,8 +118,9 @@ namespace Spine.Unity { } Vector2 worldPos; - point.ComputeWorldPosition(bone, out worldPos.x, out worldPos.y); - float rotation = point.ComputeWorldRotation(bone); + var bonePose = bone.AppliedPose; + point.ComputeWorldPosition(bonePose, out worldPos.x, out worldPos.y); + float rotation = point.ComputeWorldRotation(bonePose); Transform thisTransform = this.transform; if (skeletonTransformIsParent) { @@ -156,7 +157,8 @@ namespace Spine.Unity { if (followSkeletonFlip) { Vector3 localScale = thisTransform.localScale; - localScale.y = Mathf.Abs(localScale.y) * Mathf.Sign(bone.Skeleton.ScaleX * bone.Skeleton.ScaleY); + Skeleton skeleton = skeletonRenderer.Skeleton; + localScale.y = Mathf.Abs(localScale.y) * Mathf.Sign(skeleton.ScaleX * skeleton.ScaleY); thisTransform.localScale = localScale; } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs index e34d4104c..47d7ed3bf 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs @@ -172,8 +172,8 @@ namespace Spine.Unity { GatherTopLevelBones(); SetRootMotionBone(rootMotionBoneName); if (rootMotionBone != null) { - initialOffset = new Vector2(rootMotionBone.X, rootMotionBone.Y); - initialOffsetRotation = rootMotionBone.Rotation; + initialOffset = new Vector2(rootMotionBone.Pose.X, rootMotionBone.Pose.Y); + initialOffsetRotation = rootMotionBone.Pose.Rotation; } ISkeletonAnimation skeletonAnimation = skeletonComponent as ISkeletonAnimation; @@ -243,7 +243,7 @@ namespace Spine.Unity { Vector2 parentBoneScale; GetScaleAffectingRootMotion(out parentBoneScale); ClearEffectiveBoneOffsets(parentBoneScale); - skeletonComponent.Skeleton.UpdateWorldTransform(Skeleton.Physics.Pose); + skeletonComponent.Skeleton.UpdateWorldTransform(Physics.Pose); } ClearRigidbodyTempMovement(); @@ -359,9 +359,11 @@ namespace Spine.Unity { endPos = TimelineExtensions.Evaluate(xTimeline, yTimeline, endTime); startPos = TimelineExtensions.Evaluate(xTimeline, yTimeline, startTime); } - TransformConstraint[] transformConstraintsItems = skeletonComponent.Skeleton.TransformConstraints.Items; + ExposedList constraints = skeletonComponent.Skeleton.Constraints; + IConstraint[] constraintsItems = constraints.Items; foreach (int constraintIndex in this.transformConstraintIndices) { - TransformConstraint constraint = transformConstraintsItems[constraintIndex]; + TransformConstraint constraint = constraintsItems[constraintIndex] as TransformConstraint; + if (constraint == null) continue; ApplyConstraintToPos(animation, constraint, constraintIndex, endTime, false, ref endPos); ApplyConstraintToPos(animation, constraint, constraintIndex, startTime, true, ref startPos); } @@ -379,13 +381,13 @@ namespace Spine.Unity { zeroPos = TimelineExtensions.Evaluate(xTimeline, yTimeline, 0); } foreach (int constraintIndex in this.transformConstraintIndices) { - TransformConstraint constraint = transformConstraintsItems[constraintIndex]; + TransformConstraint constraint = (TransformConstraint)constraintsItems[constraintIndex]; ApplyConstraintToPos(animation, constraint, constraintIndex, animation.Duration, false, ref loopPos); ApplyConstraintToPos(animation, constraint, constraintIndex, 0, false, ref zeroPos); } currentDelta += loopPos - zeroPos; } - UpdateLastConstraintPos(transformConstraintsItems); + UpdateLastConstraintPos(constraintsItems); return currentDelta; } @@ -408,9 +410,12 @@ namespace Spine.Unity { endRotation = rotateTimeline.Evaluate(endTime); startRotation = rotateTimeline.Evaluate(startTime); } - TransformConstraint[] transformConstraintsItems = skeletonComponent.Skeleton.TransformConstraints.Items; + + ExposedList constraints = skeletonComponent.Skeleton.Constraints; + IConstraint[] constraintsItems = constraints.Items; foreach (int constraintIndex in this.transformConstraintIndices) { - TransformConstraint constraint = transformConstraintsItems[constraintIndex]; + TransformConstraint constraint = constraintsItems[constraintIndex] as TransformConstraint; + if (constraint == null) continue; ApplyConstraintToRotation(animation, constraint, constraintIndex, endTime, false, ref endRotation); ApplyConstraintToRotation(animation, constraint, constraintIndex, startTime, true, ref startRotation); } @@ -425,13 +430,13 @@ namespace Spine.Unity { zeroPos = rotateTimeline.Evaluate(0); } foreach (int constraintIndex in this.transformConstraintIndices) { - TransformConstraint constraint = transformConstraintsItems[constraintIndex]; + TransformConstraint constraint = (TransformConstraint)constraintsItems[constraintIndex]; ApplyConstraintToRotation(animation, constraint, constraintIndex, animation.Duration, false, ref loopRotation); ApplyConstraintToRotation(animation, constraint, constraintIndex, 0, false, ref zeroPos); } currentDelta += loopRotation - zeroPos; } - UpdateLastConstraintRotation(transformConstraintsItems); + UpdateLastConstraintRotation(constraintsItems); return currentDelta; } @@ -447,7 +452,7 @@ namespace Spine.Unity { constraintPos = transformConstraintLastPos[GetConstraintLastPosIndex(constraintIndex)]; else { Bone sourceBone = constraint.Source; - constraintPos = new Vector2(sourceBone.X, sourceBone.Y); + constraintPos = new Vector2(sourceBone.Pose.X, sourceBone.Pose.Y); } pos = new Vector2( pos.x * invMixXY.x + constraintPos.x * mixXY.x, @@ -466,24 +471,24 @@ namespace Spine.Unity { constraintRotation = transformConstraintLastRotation[GetConstraintLastPosIndex(constraintIndex)]; else { Bone sourceBone = constraint.Source; - constraintRotation = sourceBone.Rotation; + constraintRotation = sourceBone.Pose.Rotation; } rotation = rotation * invMixRotate + constraintRotation * mixRotate; } - void UpdateLastConstraintPos (TransformConstraint[] transformConstraintsItems) { + void UpdateLastConstraintPos (IConstraint[] constraintsItems) { foreach (int constraintIndex in this.transformConstraintIndices) { - TransformConstraint constraint = transformConstraintsItems[constraintIndex]; + TransformConstraint constraint = (TransformConstraint)constraintsItems[constraintIndex]; Bone sourceBone = constraint.Source; - transformConstraintLastPos[GetConstraintLastPosIndex(constraintIndex)] = new Vector2(sourceBone.X, sourceBone.Y); + transformConstraintLastPos[GetConstraintLastPosIndex(constraintIndex)] = new Vector2(sourceBone.Pose.X, sourceBone.Pose.Y); } } - void UpdateLastConstraintRotation (TransformConstraint[] transformConstraintsItems) { + void UpdateLastConstraintRotation (IConstraint[] constraintsItems) { foreach (int constraintIndex in this.transformConstraintIndices) { - TransformConstraint constraint = transformConstraintsItems[constraintIndex]; + TransformConstraint constraint = (TransformConstraint)constraintsItems[constraintIndex]; Bone sourceBone = constraint.Source; - transformConstraintLastRotation[GetConstraintLastPosIndex(constraintIndex)] = sourceBone.Rotation; + transformConstraintLastRotation[GetConstraintLastPosIndex(constraintIndex)] = sourceBone.Pose.Rotation; } } @@ -513,21 +518,22 @@ namespace Spine.Unity { } int GetConstraintLastPosIndex (int constraintIndex) { - ExposedList constraints = skeletonComponent.Skeleton.TransformConstraints; + ExposedList constraints = skeletonComponent.Skeleton.Constraints; return transformConstraintIndices.FindIndex(addedIndex => addedIndex == constraintIndex); } void FindTransformConstraintsAffectingBone () { - ExposedList constraints = skeletonComponent.Skeleton.TransformConstraints; - TransformConstraint[] constraintsItems = constraints.Items; - for (int i = 0, n = constraints.Count; i < n; ++i) { - TransformConstraint constraint = constraintsItems[i]; - if (constraint.Bones.Contains(rootMotionBone)) { - transformConstraintIndices.Add(i); + ExposedList constraints = skeletonComponent.Skeleton.Constraints; + IConstraint[] constraintsItems = constraints.Items; + for (int constraintIndex = 0, n = constraints.Count; constraintIndex < n; ++constraintIndex) { + TransformConstraint constraint = constraintsItems[constraintIndex] as TransformConstraint; + if (constraint == null) continue; + if (constraint.Bones.Contains(rootMotionBone.AppliedPose)) { + transformConstraintIndices.Add(constraintIndex); Bone sourceBone = constraint.Source; - Vector2 constraintPos = new Vector2(sourceBone.X, sourceBone.Y); + Vector2 constraintPos = new Vector2(sourceBone.Pose.X, sourceBone.Pose.Y); transformConstraintLastPos.Add(constraintPos); - transformConstraintLastRotation.Add(sourceBone.Rotation); + transformConstraintLastRotation.Add(sourceBone.Pose.Rotation); } } } @@ -627,16 +633,16 @@ namespace Spine.Unity { } void ApplyTransformConstraints () { - rootMotionBone.AX = rootMotionBone.X; - rootMotionBone.AY = rootMotionBone.Y; - rootMotionBone.AppliedRotation = rootMotionBone.Rotation; - TransformConstraint[] transformConstraintsItems = skeletonComponent.Skeleton.TransformConstraints.Items; + rootMotionBone.AppliedPose.X = rootMotionBone.Pose.X; + rootMotionBone.AppliedPose.Y = rootMotionBone.Pose.Y; + rootMotionBone.AppliedPose.Rotation = rootMotionBone.Pose.Rotation; + IConstraint[] transformConstraintsItems = skeletonComponent.Skeleton.Constraints.Items; foreach (int constraintIndex in this.transformConstraintIndices) { - TransformConstraint constraint = transformConstraintsItems[constraintIndex]; + TransformConstraint constraint = (TransformConstraint)transformConstraintsItems[constraintIndex]; // apply the constraint and sets Bone.ax, Bone.ay and Bone.arotation values. /// Update is based on Bone.x, Bone.y and Bone.rotation, so skeleton.UpdateWorldTransform() /// can be called afterwards without having a different starting point. - constraint.Update(Skeleton.Physics.None); + constraint.Update(skeletonComponent.Skeleton, Physics.None); } } @@ -655,11 +661,11 @@ namespace Spine.Unity { Bone scaleBone = rootMotionBone; while ((scaleBone = scaleBone.Parent) != null) { #if USE_APPLIED_PARENT_SCALE - parentBoneScale.x *= scaleBone.AScaleX; - parentBoneScale.y *= scaleBone.AScaleY; + parentBoneScale.x *= scaleBone.AppliedPose.ScaleX; + parentBoneScale.y *= scaleBone.AppliedPose.ScaleY; #else - parentBoneScale.x *= scaleBone.ScaleX; - parentBoneScale.y *= scaleBone.ScaleY; + parentBoneScale.x *= scaleBone.Pose.ScaleX; + parentBoneScale.y *= scaleBone.Pose.ScaleY; #endif } totalScale = Vector2.Scale(totalScale, parentBoneScale); @@ -699,31 +705,31 @@ namespace Spine.Unity { Skeleton skeleton = skeletonComponent.Skeleton; foreach (Bone topLevelBone in topLevelBones) { if (topLevelBone == rootMotionBone) { - if (transformPositionX) topLevelBone.X = displacementSkeletonSpace.x / skeleton.ScaleX; - if (transformPositionY) topLevelBone.Y = displacementSkeletonSpace.y / skeleton.ScaleY; + if (transformPositionX) topLevelBone.Pose.X = displacementSkeletonSpace.x / skeleton.ScaleX; + if (transformPositionY) topLevelBone.Pose.Y = displacementSkeletonSpace.y / skeleton.ScaleY; if (transformRotation) { float rotationSign = skeleton.ScaleX * skeleton.ScaleY > 0 ? 1 : -1; - topLevelBone.Rotation = rotationSign * rotationSkeletonSpace; + topLevelBone.Pose.Rotation = rotationSign * rotationSkeletonSpace; } } else { bool useAppliedTransform = transformConstraintIndices.Count > 0; - float rootMotionBoneX = useAppliedTransform ? rootMotionBone.AX : rootMotionBone.X; - float rootMotionBoneY = useAppliedTransform ? rootMotionBone.AY : rootMotionBone.Y; + float rootMotionBoneX = useAppliedTransform ? rootMotionBone.AppliedPose.X : rootMotionBone.Pose.X; + float rootMotionBoneY = useAppliedTransform ? rootMotionBone.AppliedPose.Y : rootMotionBone.Pose.Y; float offsetX = (initialOffset.x - rootMotionBoneX) * parentBoneScale.x; float offsetY = (initialOffset.y - rootMotionBoneY) * parentBoneScale.y; - if (transformPositionX) topLevelBone.X = (displacementSkeletonSpace.x / skeleton.ScaleX) + offsetX; - if (transformPositionY) topLevelBone.Y = (displacementSkeletonSpace.y / skeleton.ScaleY) + offsetY; + if (transformPositionX) topLevelBone.Pose.X = (displacementSkeletonSpace.x / skeleton.ScaleX) + offsetX; + if (transformPositionY) topLevelBone.Pose.Y = (displacementSkeletonSpace.y / skeleton.ScaleY) + offsetY; if (transformRotation) { - float rootMotionBoneRotation = useAppliedTransform ? rootMotionBone.AppliedRotation : rootMotionBone.Rotation; + float rootMotionBoneRotation = useAppliedTransform ? rootMotionBone.AppliedPose.Rotation : rootMotionBone.Pose.Rotation; float parentBoneRotationSign = (parentBoneScale.x * parentBoneScale.y > 0 ? 1 : -1); float offsetRotation = (initialOffsetRotation - rootMotionBoneRotation) * parentBoneRotationSign; float skeletonRotationSign = skeleton.ScaleX * skeleton.ScaleY > 0 ? 1 : -1; - topLevelBone.Rotation = (rotationSkeletonSpace * skeletonRotationSign) + offsetRotation; + topLevelBone.Pose.Rotation = (rotationSkeletonSpace * skeletonRotationSign) + offsetRotation; } } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs index 350045fd5..e49663df5 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs @@ -269,11 +269,11 @@ namespace Spine.Unity { _UpdateLocal(this); if (_UpdateWorld == null) { - UpdateWorldTransform(Skeleton.Physics.Update); + UpdateWorldTransform(Physics.Update); } else { - UpdateWorldTransform(Skeleton.Physics.Pose); + UpdateWorldTransform(Physics.Pose); _UpdateWorld(this); - UpdateWorldTransform(Skeleton.Physics.Update); + UpdateWorldTransform(Physics.Update); } if (_UpdateComplete != null) { diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs index f6845476b..729be057b 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs @@ -465,18 +465,18 @@ namespace Spine.Unity { UpdateLocal(this); if (UpdateWorld == null) { - UpdateWorldTransform(Skeleton.Physics.Update); + UpdateWorldTransform(Physics.Update); } else { - UpdateWorldTransform(Skeleton.Physics.Pose); + UpdateWorldTransform(Physics.Pose); UpdateWorld(this); - UpdateWorldTransform(Skeleton.Physics.Update); + UpdateWorldTransform(Physics.Update); } if (UpdateComplete != null) UpdateComplete(this); } - protected void UpdateWorldTransform (Skeleton.Physics physics) { + protected void UpdateWorldTransform (Physics physics) { skeleton.UpdateWorldTransform(physics); } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs index c918836cc..718aaa061 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs @@ -161,11 +161,11 @@ namespace Spine.Unity { _UpdateLocal(this); if (_UpdateWorld == null) { - UpdateWorldTransform(Skeleton.Physics.Update); + UpdateWorldTransform(Physics.Update); } else { - UpdateWorldTransform(Skeleton.Physics.Pose); + UpdateWorldTransform(Physics.Pose); _UpdateWorld(this); - UpdateWorldTransform(Skeleton.Physics.Update); + UpdateWorldTransform(Physics.Update); } if (_UpdateComplete != null) @@ -289,7 +289,7 @@ namespace Spine.Unity { info.clip.isLooping, stateInfo.speed < 0); weight = useCustomClipWeight ? layerWeight * customClipWeight : weight; clip.Apply(skeleton, 0, time, info.clip.isLooping, null, - weight, layerBlendMode, MixDirection.In); + weight, layerBlendMode, MixDirection.In, false); if (_OnClipApplied != null) OnClipAppliedCallback(clip, stateInfo, layerIndex, time, info.clip.isLooping, weight); return true; @@ -313,7 +313,7 @@ namespace Spine.Unity { info.clip.length, info.clip.isLooping, stateInfo.speed < 0); weight = useCustomClipWeight ? layerWeight * customClipWeight : weight; clip.Apply(skeleton, 0, time, info.clip.isLooping, null, - weight, layerBlendMode, MixDirection.In); + weight, layerBlendMode, MixDirection.In, false); if (_OnClipApplied != null) { OnClipAppliedCallback(clip, stateInfo, layerIndex, time, info.clip.isLooping, weight); } @@ -360,7 +360,7 @@ namespace Spine.Unity { if (autoReset) { List previousAnimations = this.previousAnimations; for (int i = 0, n = previousAnimations.Count; i < n; i++) - previousAnimations[i].Apply(skeleton, 0, 0, false, null, 0, MixBlend.Setup, MixDirection.Out); // SetKeyedItemsToSetupPose + previousAnimations[i].Apply(skeleton, 0, 0, false, null, 0, MixBlend.Setup, MixDirection.Out, false); // SetKeyedItemsToSetupPose previousAnimations.Clear(); for (int layer = 0, n = animator.layerCount; layer < n; layer++) { diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs index c2e6df529..193c547da 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs @@ -439,7 +439,7 @@ namespace Spine.Unity { MeshFilter meshFilter = GetComponent(); if (meshFilter != null) meshFilter.sharedMesh = null; currentInstructions.Clear(); - if (skeleton != null) skeleton.SetToSetupPose(); + if (skeleton != null) skeleton.SetupPose(); } /// @@ -501,7 +501,7 @@ namespace Spine.Unity { // Generate mesh once, required to update mesh bounds for visibility UpdateMode updateModeSaved = updateMode; updateMode = UpdateMode.FullUpdate; - UpdateWorldTransform(Skeleton.Physics.Update); + UpdateWorldTransform(Physics.Update); LateUpdate(); updateMode = updateModeSaved; @@ -565,7 +565,7 @@ namespace Spine.Unity { } } - protected virtual void UpdateWorldTransform (Skeleton.Physics physics) { + protected virtual void UpdateWorldTransform (Physics physics) { skeleton.UpdateWorldTransform(physics); } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/SkeletonUtility.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/SkeletonUtility.cs index 95a842c4b..d0b4cea0d 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/SkeletonUtility.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/SkeletonUtility.cs @@ -62,14 +62,14 @@ namespace Spine.Unity { BoundingBoxAttachment box = attachment as BoundingBoxAttachment; if (box != null) { - return AddBoundingBoxGameObject(box.Name, box, slot, parent, isTrigger); + return AddBoundingBoxGameObject(box.Name, box, skeleton, slot, parent, isTrigger); } else { Debug.LogFormat("Attachment '{0}' was not a Bounding Box.", attachmentName); return null; } } - public static PolygonCollider2D AddBoundingBoxGameObject (string name, BoundingBoxAttachment box, Slot slot, Transform parent, bool isTrigger = true) { + public static PolygonCollider2D AddBoundingBoxGameObject (string name, BoundingBoxAttachment box, Skeleton skeleton, Slot slot, Transform parent, bool isTrigger = true) { GameObject go = new GameObject("[BoundingBox]" + (string.IsNullOrEmpty(name) ? box.Name : name)); #if UNITY_EDITOR if (!Application.isPlaying) @@ -80,21 +80,21 @@ namespace Spine.Unity { got.localPosition = Vector3.zero; got.localRotation = Quaternion.identity; got.localScale = Vector3.one; - return AddBoundingBoxAsComponent(box, slot, go, isTrigger); + return AddBoundingBoxAsComponent(box, skeleton, slot, go, isTrigger); } - public static PolygonCollider2D AddBoundingBoxAsComponent (BoundingBoxAttachment box, Slot slot, GameObject gameObject, bool isTrigger = true) { + public static PolygonCollider2D AddBoundingBoxAsComponent (BoundingBoxAttachment box, Skeleton skeleton, Slot slot, GameObject gameObject, bool isTrigger = true) { if (box == null) return null; PolygonCollider2D collider = gameObject.AddComponent(); collider.isTrigger = isTrigger; - SetColliderPointsLocal(collider, slot, box); + SetColliderPointsLocal(collider, skeleton, slot, box); return collider; } - public static void SetColliderPointsLocal (PolygonCollider2D collider, Slot slot, BoundingBoxAttachment box, float scale = 1.0f) { + public static void SetColliderPointsLocal (PolygonCollider2D collider, Skeleton skeleton, Slot slot, BoundingBoxAttachment box, float scale = 1.0f) { if (box == null) return; if (box.IsWeighted()) Debug.LogWarning("UnityEngine.PolygonCollider2D does not support weighted or animated points. Collider points will not be animated and may have incorrect orientation. If you want to use it as a collider, please remove weights and animations from the bounding box in Spine editor."); - Vector2[] verts = box.GetLocalVertices(slot, null); + Vector2[] verts = box.GetLocalVertices(skeleton, slot, null); if (scale != 1.0f) { for (int i = 0, n = verts.Length; i < n; ++i) verts[i] *= scale; @@ -353,14 +353,17 @@ namespace Spine.Unity { if (skeleton == null) return; if (boneRoot != null) { - List constraintTargets = new List(); - ExposedList ikConstraints = skeleton.IkConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) - constraintTargets.Add(ikConstraints.Items[i].Target); - - ExposedList transformConstraints = skeleton.TransformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) - constraintTargets.Add(transformConstraints.Items[i].Source); + List constraintTargets = new List(); + ExposedList constraints = skeleton.Constraints; + for (int i = 0, n = constraints.Count; i < n; i++) { + IConstraint constraint = constraints.Items[i]; + if (constraint is IkConstraint) + constraintTargets.Add(((IkConstraint)constraint).Bones); + else if (constraint is TransformConstraint) + constraintTargets.Add(((TransformConstraint)constraint).Bones); + else if (constraint is PathConstraint) + constraintTargets.Add(((PathConstraint)constraint).Bones); + } List boneComponents = this.boneComponents; for (int i = 0, n = boneComponents.Count; i < n; i++) { @@ -489,9 +492,11 @@ namespace Spine.Unity { b.valid = true; if (mode == SkeletonUtilityBone.Mode.Override) { - if (rot) goTransform.localRotation = Quaternion.Euler(0, 0, b.bone.AppliedRotation); - if (pos) goTransform.localPosition = new Vector3(b.bone.X * positionScale + positionOffset.x, b.bone.Y * positionScale + positionOffset.y, 0); - goTransform.localScale = new Vector3(b.bone.ScaleX, b.bone.ScaleY, 0); + var bonePose = b.bone.AppliedPose; + if (rot) goTransform.localRotation = Quaternion.Euler(0, 0, bonePose.Rotation); + if (pos) goTransform.localPosition = new Vector3( + bonePose.X * positionScale + positionOffset.x, bonePose.Y * positionScale + positionOffset.y, 0); + goTransform.localScale = new Vector3(bonePose.ScaleX, bonePose.ScaleY, 0); } return go; diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/SkeletonUtilityBone.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/SkeletonUtilityBone.cs index b1602fc4b..36e48ac52 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/SkeletonUtilityBone.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/SkeletonUtilityBone.cs @@ -127,43 +127,45 @@ namespace Spine.Unity { Transform thisTransform = cachedTransform; float skeletonFlipRotation = Mathf.Sign(skeleton.ScaleX * skeleton.ScaleY); if (mode == Mode.Follow) { + var bonePose = bone.Pose; switch (phase) { case UpdatePhase.Local: if (position) - thisTransform.localPosition = new Vector3(bone.X * positionScale, bone.Y * positionScale, + thisTransform.localPosition = new Vector3(bonePose.X * positionScale, bonePose.Y * positionScale, zPosition ? 0 : thisTransform.localPosition.z); if (rotation) { - if (bone.Data.Inherit.InheritsRotation()) { - thisTransform.localRotation = Quaternion.Euler(0, 0, bone.Rotation); + if (bone.Data.GetSetupPose().Inherit.InheritsRotation()) { + thisTransform.localRotation = Quaternion.Euler(0, 0, bonePose.Rotation); } else { Vector3 euler = skeletonTransform.rotation.eulerAngles; - thisTransform.rotation = Quaternion.Euler(euler.x, euler.y, euler.z + (bone.WorldRotationX * skeletonFlipRotation)); + thisTransform.rotation = Quaternion.Euler(euler.x, euler.y, euler.z + (bone.AppliedPose.WorldRotationX * skeletonFlipRotation)); } } if (scale) { - thisTransform.localScale = new Vector3(bone.ScaleX, bone.ScaleY, 1f); + thisTransform.localScale = new Vector3(bonePose.ScaleX, bonePose.ScaleY, 1f); incompatibleTransformMode = BoneTransformModeIncompatible(bone); } break; case UpdatePhase.World: case UpdatePhase.Complete: + var appliedPose = bone.AppliedPose; if (position) - thisTransform.localPosition = new Vector3(bone.AX * positionScale, bone.AY * positionScale, + thisTransform.localPosition = new Vector3(appliedPose.X * positionScale, appliedPose.Y * positionScale, zPosition ? 0 : thisTransform.localPosition.z); if (rotation) { - if (bone.Data.Inherit.InheritsRotation()) { - thisTransform.localRotation = Quaternion.Euler(0, 0, bone.AppliedRotation); + if (bone.Data.GetSetupPose().Inherit.InheritsRotation()) { + thisTransform.localRotation = Quaternion.Euler(0, 0, appliedPose.Rotation); } else { Vector3 euler = skeletonTransform.rotation.eulerAngles; - thisTransform.rotation = Quaternion.Euler(euler.x, euler.y, euler.z + (bone.WorldRotationX * skeletonFlipRotation)); + thisTransform.rotation = Quaternion.Euler(euler.x, euler.y, euler.z + (appliedPose.WorldRotationX * skeletonFlipRotation)); } } if (scale) { - thisTransform.localScale = new Vector3(bone.AScaleX, bone.AScaleY, 1f); + thisTransform.localScale = new Vector3(appliedPose.ScaleX, appliedPose.ScaleY, 1f); incompatibleTransformMode = BoneTransformModeIncompatible(bone); } break; @@ -172,24 +174,24 @@ namespace Spine.Unity { } else if (mode == Mode.Override) { if (transformLerpComplete) return; - + var bonePose = bone.Pose; if (parentReference == null) { if (position) { Vector3 clp = thisTransform.localPosition / positionScale; - bone.X = Mathf.Lerp(bone.X, clp.x, overrideAlpha); - bone.Y = Mathf.Lerp(bone.Y, clp.y, overrideAlpha); + bonePose.X = Mathf.Lerp(bonePose.X, clp.x, overrideAlpha); + bonePose.Y = Mathf.Lerp(bonePose.Y, clp.y, overrideAlpha); } if (rotation) { - float angle = Mathf.LerpAngle(bone.Rotation, thisTransform.localRotation.eulerAngles.z, overrideAlpha); - bone.Rotation = angle; - bone.AppliedRotation = angle; + float angle = Mathf.LerpAngle(bonePose.Rotation, thisTransform.localRotation.eulerAngles.z, overrideAlpha); + bonePose.Rotation = angle; + bone.AppliedPose.Rotation = angle; } if (scale) { Vector3 cls = thisTransform.localScale; - bone.ScaleX = Mathf.Lerp(bone.ScaleX, cls.x, overrideAlpha); - bone.ScaleY = Mathf.Lerp(bone.ScaleY, cls.y, overrideAlpha); + bonePose.ScaleX = Mathf.Lerp(bonePose.ScaleX, cls.x, overrideAlpha); + bonePose.ScaleY = Mathf.Lerp(bonePose.ScaleY, cls.y, overrideAlpha); } } else { @@ -198,20 +200,20 @@ namespace Spine.Unity { if (position) { Vector3 pos = parentReference.InverseTransformPoint(thisTransform.position) / positionScale; - bone.X = Mathf.Lerp(bone.X, pos.x, overrideAlpha); - bone.Y = Mathf.Lerp(bone.Y, pos.y, overrideAlpha); + bonePose.X = Mathf.Lerp(bonePose.X, pos.x, overrideAlpha); + bonePose.Y = Mathf.Lerp(bonePose.Y, pos.y, overrideAlpha); } if (rotation) { - float angle = Mathf.LerpAngle(bone.Rotation, Quaternion.LookRotation(Vector3.forward, parentReference.InverseTransformDirection(thisTransform.up)).eulerAngles.z, overrideAlpha); - bone.Rotation = angle; - bone.AppliedRotation = angle; + float angle = Mathf.LerpAngle(bonePose.Rotation, Quaternion.LookRotation(Vector3.forward, parentReference.InverseTransformDirection(thisTransform.up)).eulerAngles.z, overrideAlpha); + bonePose.Rotation = angle; + bone.AppliedPose.Rotation = angle; } if (scale) { Vector3 cls = thisTransform.localScale; - bone.ScaleX = Mathf.Lerp(bone.ScaleX, cls.x, overrideAlpha); - bone.ScaleY = Mathf.Lerp(bone.ScaleY, cls.y, overrideAlpha); + bonePose.ScaleX = Mathf.Lerp(bonePose.ScaleX, cls.x, overrideAlpha); + bonePose.ScaleY = Mathf.Lerp(bonePose.ScaleY, cls.y, overrideAlpha); } incompatibleTransformMode = BoneTransformModeIncompatible(bone); @@ -222,12 +224,12 @@ namespace Spine.Unity { } public static bool BoneTransformModeIncompatible (Bone bone) { - return !bone.Data.Inherit.InheritsScale(); + return !bone.Data.GetSetupPose().Inherit.InheritsScale(); } - public void AddBoundingBox (string skinName, string slotName, string attachmentName) { + public void AddBoundingBox (Skeleton skeleton, string skinName, string slotName, string attachmentName) { SkeletonUtility.AddBoneRigidbody2D(transform.gameObject); - SkeletonUtility.AddBoundingBoxGameObject(bone.Skeleton, skinName, slotName, attachmentName, transform); + SkeletonUtility.AddBoundingBoxGameObject(skeleton, skinName, slotName, attachmentName, transform); } #if UNITY_EDITOR diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs index 9c35e850e..5d8e13d37 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs @@ -233,14 +233,14 @@ namespace Spine.Unity { Slot slot = drawOrderItems[i]; if (!slot.Bone.Active #if SLOT_ALPHA_DISABLES_ATTACHMENT - || slot.A == 0f + || slot.AppliedPose.GetColor().a == 0f #endif ) { workingAttachmentsItems[i] = null; continue; } if (slot.Data.BlendMode == BlendMode.Additive) current.hasPMAAdditiveSlot = true; - Attachment attachment = slot.Attachment; + Attachment attachment = slot.AppliedPose.Attachment; workingAttachmentsItems[i] = attachment; int attachmentTriangleCount; @@ -248,14 +248,14 @@ namespace Spine.Unity { RegionAttachment regionAttachment = attachment as RegionAttachment; if (regionAttachment != null) { - if (regionAttachment.Sequence != null) regionAttachment.Sequence.Apply(slot, regionAttachment); + if (regionAttachment.Sequence != null) regionAttachment.Sequence.Apply(slot.AppliedPose, regionAttachment); rendererObject = regionAttachment.Region; attachmentVertexCount = 4; attachmentTriangleCount = 6; } else { MeshAttachment meshAttachment = attachment as MeshAttachment; if (meshAttachment != null) { - if (meshAttachment.Sequence != null) meshAttachment.Sequence.Apply(slot, meshAttachment); + if (meshAttachment.Sequence != null) meshAttachment.Sequence.Apply(slot.AppliedPose, meshAttachment); rendererObject = meshAttachment.Region; attachmentVertexCount = meshAttachment.WorldVerticesLength >> 1; attachmentTriangleCount = meshAttachment.Triangles.Length; @@ -313,13 +313,13 @@ namespace Spine.Unity { Slot slot = drawOrderItems[i]; if (!slot.Bone.Active #if SLOT_ALPHA_DISABLES_ATTACHMENT - || slot.A == 0f + || slot.AppliedPose.GetColor().a == 0f #endif ) continue; - Attachment attachment = slot.Attachment; + Attachment attachment = slot.AppliedPose.Attachment; IHasTextureRegion rendererAttachment = attachment as IHasTextureRegion; if (rendererAttachment != null) { - if (rendererAttachment.Sequence != null) rendererAttachment.Sequence.Apply(slot, rendererAttachment); + if (rendererAttachment.Sequence != null) rendererAttachment.Sequence.Apply(slot.AppliedPose, rendererAttachment); AtlasRegion atlasRegion = (AtlasRegion)rendererAttachment.Region; Material material = (Material)atlasRegion.page.rendererObject; if (lastRendererMaterial != material) { @@ -370,7 +370,7 @@ namespace Spine.Unity { Slot slot = drawOrderItems[i]; if (!slot.Bone.Active #if SLOT_ALPHA_DISABLES_ATTACHMENT - || (slot.A == 0f && slot.Data != clippingEndSlot) + || (slot.AppliedPose.GetColor().a == 0f && slot.Data != clippingEndSlot) #endif ) { #if SPINE_TRIANGLECHECK @@ -379,7 +379,7 @@ namespace Spine.Unity { continue; } if (slot.Data.BlendMode == BlendMode.Additive) current.hasPMAAdditiveSlot = true; - Attachment attachment = slot.Attachment; + Attachment attachment = slot.AppliedPose.Attachment; #if SPINE_TRIANGLECHECK workingAttachmentsItems[i] = attachment; int attachmentVertexCount = 0, attachmentTriangleCount = 0; @@ -390,7 +390,7 @@ namespace Spine.Unity { RegionAttachment regionAttachment = attachment as RegionAttachment; if (regionAttachment != null) { - if (regionAttachment.Sequence != null) regionAttachment.Sequence.Apply(slot, regionAttachment); + if (regionAttachment.Sequence != null) regionAttachment.Sequence.Apply(slot.AppliedPose, regionAttachment); region = regionAttachment.Region; #if SPINE_TRIANGLECHECK attachmentVertexCount = 4; @@ -399,7 +399,7 @@ namespace Spine.Unity { } else { MeshAttachment meshAttachment = attachment as MeshAttachment; if (meshAttachment != null) { - if (meshAttachment.Sequence != null) meshAttachment.Sequence.Apply(slot, meshAttachment); + if (meshAttachment.Sequence != null) meshAttachment.Sequence.Apply(slot.AppliedPose, meshAttachment); region = meshAttachment.Region; #if SPINE_TRIANGLECHECK attachmentVertexCount = meshAttachment.WorldVerticesLength >> 1; @@ -576,7 +576,8 @@ namespace Spine.Unity { Slot[] drawOrderItems = skeleton.DrawOrder.Items; Color32 color = default(Color32); - float skeletonA = skeleton.A, skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B; + + Color skeletonC = skeleton.GetColor(); Vector2 meshBoundsMin = this.meshBoundsMin, meshBoundsMax = this.meshBoundsMax; // Settings @@ -597,17 +598,18 @@ namespace Spine.Unity { if (useClipping) { if (instruction.preActiveClippingSlotSource >= 0) { Slot slot = drawOrderItems[instruction.preActiveClippingSlotSource]; - clipper.ClipStart(slot, slot.Attachment as ClippingAttachment); + clipper.ClipStart(skeleton, slot, slot.AppliedPose.Attachment as ClippingAttachment); } } for (int slotIndex = instruction.startSlot; slotIndex < instruction.endSlot; slotIndex++) { Slot slot = drawOrderItems[slotIndex]; + SlotPose slotPose = slot.AppliedPose; if (!slot.Bone.Active) { clipper.ClipEnd(slot); continue; } - Attachment attachment = slot.Attachment; + Attachment attachment = slotPose.Attachment; float z = zSpacing * slotIndex; float[] workingVerts = this.tempVerts; @@ -616,7 +618,7 @@ namespace Spine.Unity { int attachmentVertexCount; int attachmentIndexCount; - Color c = default(Color); + Color regionC; // Identify and prepare values. RegionAttachment region = attachment as RegionAttachment; @@ -624,7 +626,7 @@ namespace Spine.Unity { region.ComputeWorldVertices(slot, workingVerts, 0); uvs = region.UVs; attachmentTriangleIndices = regionTriangles; - c.r = region.R; c.g = region.G; c.b = region.B; c.a = region.A; + regionC = region.GetColor(); attachmentVertexCount = 4; attachmentIndexCount = 6; } else { @@ -635,17 +637,17 @@ namespace Spine.Unity { workingVerts = new float[meshVerticesLength]; this.tempVerts = workingVerts; } - mesh.ComputeWorldVertices(slot, 0, meshVerticesLength, workingVerts, 0); //meshAttachment.ComputeWorldVertices(slot, tempVerts); + mesh.ComputeWorldVertices(skeleton, slot, 0, meshVerticesLength, workingVerts, 0); //meshAttachment.ComputeWorldVertices(slot, tempVerts); uvs = mesh.UVs; attachmentTriangleIndices = mesh.Triangles; - c.r = mesh.R; c.g = mesh.G; c.b = mesh.B; c.a = mesh.A; + regionC = mesh.GetColor(); attachmentVertexCount = meshVerticesLength >> 1; // meshVertexCount / 2; attachmentIndexCount = mesh.Triangles.Length; } else { if (useClipping) { ClippingAttachment clippingAttachment = attachment as ClippingAttachment; if (clippingAttachment != null) { - clipper.ClipStart(slot, clippingAttachment); + clipper.ClipStart(skeleton, slot, clippingAttachment); continue; } } @@ -657,17 +659,19 @@ namespace Spine.Unity { } float tintBlackAlpha = 1.0f; + Color slotC = slotPose.GetColor(); + Color combinedC = skeletonC * slotC * regionC; if (pmaVertexColors) { - float alpha = skeletonA * slot.A * c.a; + float alpha = combinedC.a; bool isAdditiveSlot = slot.Data.BlendMode == BlendMode.Additive; #if LINEAR_COLOR_SPACE_FIX_ADDITIVE_ALPHA if (linearColorSpace && isAdditiveSlot) alpha = Mathf.LinearToGammaSpace(alpha); // compensate GammaToLinear performed in shader #endif color.a = (byte)(alpha * 255); - color.r = (byte)(skeletonR * slot.R * c.r * color.a); - color.g = (byte)(skeletonG * slot.G * c.g * color.a); - color.b = (byte)(skeletonB * slot.B * c.b * color.a); + color.r = (byte)(combinedC.r * color.a); + color.g = (byte)(combinedC.g * color.a); + color.b = (byte)(combinedC.b * color.a); if (canvasGroupTintBlack) { tintBlackAlpha = isAdditiveSlot ? 0 : alpha; color.a = 255; @@ -676,10 +680,10 @@ namespace Spine.Unity { color.a = 0; } } else { - color.a = (byte)(skeletonA * slot.A * c.a * 255); - color.r = (byte)(skeletonR * slot.R * c.r * 255); - color.g = (byte)(skeletonG * slot.G * c.g * 255); - color.b = (byte)(skeletonB * slot.B * c.b * 255); + color.a = (byte)(combinedC.a * 255); + color.r = (byte)(combinedC.r * 255); + color.g = (byte)(combinedC.g * 255); + color.b = (byte)(combinedC.b * 255); } if (useClipping && clipper.IsClipping @@ -694,21 +698,22 @@ namespace Spine.Unity { // Actually add slot/attachment data into buffers. if (attachmentVertexCount != 0 && attachmentIndexCount != 0) { if (tintBlack) { - float r2 = slot.R2; - float g2 = slot.G2; - float b2 = slot.B2; + Color? darkColorOptional = slotPose.GetDarkColor(); + Color slotDarkC; + if (darkColorOptional.HasValue) + slotDarkC = darkColorOptional.Value; + else + slotDarkC = new Color(0, 0, 0); if (pmaVertexColors) { - float alpha = skeletonA * slot.A * c.a; + float alpha = combinedC.a; #if LINEAR_COLOR_SPACE_FIX_ADDITIVE_ALPHA bool isAdditiveSlot = slot.Data.BlendMode == BlendMode.Additive; if (linearColorSpace && isAdditiveSlot) alpha = Mathf.LinearToGammaSpace(alpha); // compensate GammaToLinear performed in shader #endif - r2 *= alpha; - g2 *= alpha; - b2 *= alpha; + slotDarkC *= alpha; } - AddAttachmentTintBlack(r2, g2, b2, tintBlackAlpha, attachmentVertexCount); + AddAttachmentTintBlack(slotDarkC, tintBlackAlpha, attachmentVertexCount); } //AddAttachment(workingVerts, uvs, color, attachmentTriangleIndices, attachmentVertexCount, attachmentIndexCount, ref meshBoundsMin, ref meshBoundsMax, z); @@ -849,7 +854,7 @@ namespace Spine.Unity { SubmeshInstruction submesh = instruction.submeshInstructions.Items[si]; Skeleton skeleton = submesh.skeleton; Slot[] drawOrderItems = skeleton.DrawOrder.Items; - float a = skeleton.A, r = skeleton.R, g = skeleton.G, b = skeleton.B; + Color skeletonC = skeleton.GetColor(); int endSlot = submesh.endSlot; int startSlot = submesh.startSlot; @@ -868,22 +873,31 @@ namespace Spine.Unity { for (int slotIndex = startSlot; slotIndex < endSlot; slotIndex++) { Slot slot = drawOrderItems[slotIndex]; + SlotPose slotPose = slot.AppliedPose; + Color slotC = slotPose.GetColor(); if (!slot.Bone.Active #if SLOT_ALPHA_DISABLES_ATTACHMENT - || slot.A == 0f + || slotC.a == 0f #endif ) continue; - Attachment attachment = slot.Attachment; + Attachment attachment = slotPose.Attachment; - rg.x = slot.R2; //r - rg.y = slot.G2; //g - b2.x = slot.B2; //b + Color? darkColorOptional = slotPose.GetDarkColor(); + Color slotDarkC; + if (darkColorOptional.HasValue) + slotDarkC = darkColorOptional.Value; + else + slotDarkC = new Color(0, 0, 0); + rg.x = slotDarkC.r; + rg.y = slotDarkC.g; + b2.x = slotDarkC.b; b2.y = 1.0f; RegionAttachment regionAttachment = attachment as RegionAttachment; if (regionAttachment != null) { + Color regionC = regionAttachment.GetColor(); if (settings.pmaVertexColors) { - float alpha = a * slot.A * regionAttachment.A; + float alpha = skeletonC.a * slotC.a * regionC.a; bool isAdditiveSlot = slot.Data.BlendMode == BlendMode.Additive; #if LINEAR_COLOR_SPACE_FIX_ADDITIVE_ALPHA if (linearColorSpace && isAdditiveSlot) @@ -900,8 +914,9 @@ namespace Spine.Unity { } else { //} if (settings.renderMeshes) { MeshAttachment meshAttachment = attachment as MeshAttachment; if (meshAttachment != null) { + Color meshC = meshAttachment.GetColor(); if (settings.pmaVertexColors) { - float alpha = a * slot.A * meshAttachment.A; + float alpha = skeletonC.a * slotC.a * meshC.a; bool isAdditiveSlot = slot.Data.BlendMode == BlendMode.Additive; #if LINEAR_COLOR_SPACE_FIX_ADDITIVE_ALPHA if (linearColorSpace && isAdditiveSlot) @@ -925,17 +940,21 @@ namespace Spine.Unity { for (int slotIndex = startSlot; slotIndex < endSlot; slotIndex++) { Slot slot = drawOrderItems[slotIndex]; + SlotPose slotPose = slot.AppliedPose; + Color slotC = slotPose.GetColor(); if (!slot.Bone.Active #if SLOT_ALPHA_DISABLES_ATTACHMENT - || slot.A == 0f + || slotC.a == 0f #endif ) continue; - Attachment attachment = slot.Attachment; + Attachment attachment = slot.AppliedPose.Attachment; float z = slotIndex * settings.zSpacing; RegionAttachment regionAttachment = attachment as RegionAttachment; if (regionAttachment != null) { regionAttachment.ComputeWorldVertices(slot, tempVerts, 0); + Color regionC = regionAttachment.GetColor(); + Color combinedC = skeletonC * slotC * regionC; float x1 = tempVerts[RegionAttachment.BLX], y1 = tempVerts[RegionAttachment.BLY]; float x2 = tempVerts[RegionAttachment.ULX], y2 = tempVerts[RegionAttachment.ULY]; @@ -947,24 +966,24 @@ namespace Spine.Unity { vbi[vertexIndex + 3].x = x3; vbi[vertexIndex + 3].y = y3; vbi[vertexIndex + 3].z = z; if (settings.pmaVertexColors) { - float alpha = a * slot.A * regionAttachment.A; + float alpha = combinedC.a; bool isAdditiveSlot = slot.Data.BlendMode == BlendMode.Additive; #if LINEAR_COLOR_SPACE_FIX_ADDITIVE_ALPHA if (linearColorSpace && isAdditiveSlot) alpha = Mathf.LinearToGammaSpace(alpha); // compensate GammaToLinear performed in shader #endif color.a = (byte)(alpha * 255); - color.r = (byte)(r * slot.R * regionAttachment.R * color.a); - color.g = (byte)(g * slot.G * regionAttachment.G * color.a); - color.b = (byte)(b * slot.B * regionAttachment.B * color.a); + color.r = (byte)(combinedC.r * color.a); + color.g = (byte)(combinedC.g * color.a); + color.b = (byte)(combinedC.b * color.a); if (canvasGroupTintBlack) color.a = 255; else if (isAdditiveSlot) color.a = 0; } else { - color.a = (byte)(a * slot.A * regionAttachment.A * 255); - color.r = (byte)(r * slot.R * regionAttachment.R * 255); - color.g = (byte)(g * slot.G * regionAttachment.G * 255); - color.b = (byte)(b * slot.B * regionAttachment.B * 255); + color.a = (byte)(combinedC.a * 255); + color.r = (byte)(combinedC.r * 255); + color.g = (byte)(combinedC.g * 255); + color.b = (byte)(combinedC.b * 255); } cbi[vertexIndex] = color; cbi[vertexIndex + 1] = color; cbi[vertexIndex + 2] = color; cbi[vertexIndex + 3] = color; @@ -999,26 +1018,28 @@ namespace Spine.Unity { if (meshAttachment != null) { int verticesArrayLength = meshAttachment.WorldVerticesLength; if (tempVerts.Length < verticesArrayLength) this.tempVerts = tempVerts = new float[verticesArrayLength]; - meshAttachment.ComputeWorldVertices(slot, tempVerts); + meshAttachment.ComputeWorldVertices(skeleton, slot, tempVerts); + Color meshC = meshAttachment.GetColor(); + Color combinedC = skeletonC * slotC * meshC; if (settings.pmaVertexColors) { - float alpha = a * slot.A * meshAttachment.A; + float alpha = combinedC.a; bool isAdditiveSlot = slot.Data.BlendMode == BlendMode.Additive; #if LINEAR_COLOR_SPACE_FIX_ADDITIVE_ALPHA if (linearColorSpace && isAdditiveSlot) alpha = Mathf.LinearToGammaSpace(alpha); // compensate GammaToLinear performed in shader #endif color.a = (byte)(alpha * 255); - color.r = (byte)(r * slot.R * meshAttachment.R * color.a); - color.g = (byte)(g * slot.G * meshAttachment.G * color.a); - color.b = (byte)(b * slot.B * meshAttachment.B * color.a); + color.r = (byte)(combinedC.r * color.a); + color.g = (byte)(combinedC.g * color.a); + color.b = (byte)(combinedC.b * color.a); if (canvasGroupTintBlack) color.a = 255; else if (isAdditiveSlot) color.a = 0; } else { - color.a = (byte)(a * slot.A * meshAttachment.A * 255); - color.r = (byte)(r * slot.R * meshAttachment.R * 255); - color.g = (byte)(g * slot.G * meshAttachment.G * 255); - color.b = (byte)(b * slot.B * meshAttachment.B * 255); + color.a = (byte)(combinedC.a * 255); + color.r = (byte)(combinedC.r * 255); + color.g = (byte)(combinedC.g * 255); + color.b = (byte)(combinedC.b * 255); } float[] attachmentUVs = meshAttachment.UVs; @@ -1102,11 +1123,11 @@ namespace Spine.Unity { Slot slot = drawOrderItems[slotIndex]; if (!slot.Bone.Active #if SLOT_ALPHA_DISABLES_ATTACHMENT - || slot.A == 0f + || slot.AppliedPose.GetColor().a == 0f #endif ) continue; - Attachment attachment = drawOrderItems[slotIndex].Attachment; + Attachment attachment = drawOrderItems[slotIndex].AppliedPose.Attachment; if (attachment is RegionAttachment) { tris[triangleIndex] = attachmentFirstVertex; tris[triangleIndex + 1] = attachmentFirstVertex + 2; @@ -1182,9 +1203,9 @@ namespace Spine.Unity { } } - void AddAttachmentTintBlack (float r2, float g2, float b2, float a, int vertexCount) { - Vector2 rg = new Vector2(r2, g2); - Vector2 bo = new Vector2(b2, a); + void AddAttachmentTintBlack (Color darkColor, float a, int vertexCount) { + Vector2 rg = new Vector2(darkColor.r, darkColor.g); + Vector2 bo = new Vector2(darkColor.b, a); int ovc = vertexBuffer.Count; int newVertexCount = ovc + vertexCount; @@ -1465,7 +1486,7 @@ namespace Spine.Unity { AttachmentUVs.Add(new Vector2(uvs[RegionAttachment.BLX], uvs[RegionAttachment.BLY])); AttachmentColors32.Clear(); - Color32 c = (Color32)(new Color(regionAttachment.R, regionAttachment.G, regionAttachment.B, regionAttachment.A)); + Color32 c = (Color32)(regionAttachment.GetColor()); for (int i = 0; i < 4; i++) AttachmentColors32.Add(c); @@ -1523,7 +1544,7 @@ namespace Spine.Unity { float[] uvs = meshAttachment.UVs; Vector2 uv = default(Vector2); - Color32 c = (Color32)(new Color(meshAttachment.R, meshAttachment.G, meshAttachment.B, meshAttachment.A)); + Color32 c = (Color32)(meshAttachment.GetColor()); AttachmentUVs.Clear(); AttachmentColors32.Clear(); for (int i = 0; i < vertexCount; i++) { diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SkeletonRendererInstruction.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SkeletonRendererInstruction.cs index 8c49ec200..2088952f2 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SkeletonRendererInstruction.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SkeletonRendererInstruction.cs @@ -109,13 +109,13 @@ namespace Spine.Unity { Slot slot = drawOrderItems[startSlot + i]; if (!slot.Bone.Active #if SLOT_ALPHA_DISABLES_ATTACHMENT - || slot.A == 0f + || slot.AppliedPose.GetColor().a == 0f #endif ) { attachmentsItems[i] = null; continue; } - attachmentsItems[i] = slot.Attachment; + attachmentsItems[i] = slot.AppliedPose.Attachment; } #endif diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AttachmentRegionExtensions.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AttachmentRegionExtensions.cs index 26b199872..baef1d61a 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AttachmentRegionExtensions.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AttachmentRegionExtensions.cs @@ -81,11 +81,7 @@ namespace Spine.Unity.AttachmentTools { attachment.ScaleX = 1; attachment.ScaleY = 1; attachment.Rotation = rotation; - - attachment.R = 1; - attachment.G = 1; - attachment.B = 1; - attachment.A = 1; + attachment.SetColor(Color.white); // pass OriginalWidth and OriginalHeight because UpdateOffset uses it in its calculation. TextureRegion textreRegion = attachment.Region; diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/SkeletonExtensions.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/SkeletonExtensions.cs index aa2f73f7b..ad62fa775 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/SkeletonExtensions.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/SkeletonExtensions.cs @@ -34,66 +34,19 @@ namespace Spine.Unity { #region Colors const float ByteToFloat = 1f / 255f; - public static Color GetColor (this Skeleton s) { return new Color(s.R, s.G, s.B, s.A); } - public static Color GetColor (this RegionAttachment a) { return new Color(a.R, a.G, a.B, a.A); } - public static Color GetColor (this MeshAttachment a) { return new Color(a.R, a.G, a.B, a.A); } - public static Color GetColor (this Slot s) { return new Color(s.R, s.G, s.B, s.A); } - public static Color GetColorTintBlack (this Slot s) { return new Color(s.R2, s.G2, s.B2, 1f); } - - public static void SetColor (this Skeleton skeleton, Color color) { - skeleton.A = color.a; - skeleton.R = color.r; - skeleton.G = color.g; - skeleton.B = color.b; - } - - public static void SetColor (this Skeleton skeleton, Color32 color) { - skeleton.A = color.a * ByteToFloat; - skeleton.R = color.r * ByteToFloat; - skeleton.G = color.g * ByteToFloat; - skeleton.B = color.b * ByteToFloat; + public static Color GetColor (this Slot s) { return s.AppliedPose.GetColor(); } + public static Color GetColorTintBlack (this Slot s) { + Color? darkColor = s.AppliedPose.GetDarkColor(); + if (!darkColor.HasValue) return Color.black; + return darkColor.Value; } public static void SetColor (this Slot slot, Color color) { - slot.A = color.a; - slot.R = color.r; - slot.G = color.g; - slot.B = color.b; + slot.AppliedPose.SetColor(color); } public static void SetColor (this Slot slot, Color32 color) { - slot.A = color.a * ByteToFloat; - slot.R = color.r * ByteToFloat; - slot.G = color.g * ByteToFloat; - slot.B = color.b * ByteToFloat; - } - - public static void SetColor (this RegionAttachment attachment, Color color) { - attachment.A = color.a; - attachment.R = color.r; - attachment.G = color.g; - attachment.B = color.b; - } - - public static void SetColor (this RegionAttachment attachment, Color32 color) { - attachment.A = color.a * ByteToFloat; - attachment.R = color.r * ByteToFloat; - attachment.G = color.g * ByteToFloat; - attachment.B = color.b * ByteToFloat; - } - - public static void SetColor (this MeshAttachment attachment, Color color) { - attachment.A = color.a; - attachment.R = color.r; - attachment.G = color.g; - attachment.B = color.b; - } - - public static void SetColor (this MeshAttachment attachment, Color32 color) { - attachment.A = color.a * ByteToFloat; - attachment.R = color.r * ByteToFloat; - attachment.G = color.g * ByteToFloat; - attachment.B = color.b * ByteToFloat; + slot.AppliedPose.SetColor(color); } #endregion @@ -106,13 +59,18 @@ namespace Spine.Unity { /// Gets the internal bone matrix as a Unity bonespace-to-skeletonspace transformation matrix. public static Matrix4x4 GetMatrix4x4 (this Bone bone) { + return bone.AppliedPose.GetMatrix4x4(); + } + + /// Gets the internal bone matrix as a Unity bonespace-to-skeletonspace transformation matrix. + public static Matrix4x4 GetMatrix4x4 (this BonePose bonePose) { return new Matrix4x4 { - m00 = bone.A, - m01 = bone.B, - m03 = bone.WorldX, - m10 = bone.C, - m11 = bone.D, - m13 = bone.WorldY, + m00 = bonePose.A, + m01 = bonePose.B, + m03 = bonePose.WorldX, + m10 = bonePose.C, + m11 = bonePose.D, + m13 = bonePose.WorldY, m33 = 1 }; } @@ -121,55 +79,103 @@ namespace Spine.Unity { #region Bone /// Sets the bone's (local) X and Y according to a Vector2 public static void SetLocalPosition (this Bone bone, Vector2 position) { - bone.X = position.x; - bone.Y = position.y; + bone.Pose.SetLocalPosition(position); + } + + /// Sets the bone's (local) X and Y according to a Vector2 + public static void SetLocalPosition (this BoneLocal bonePose, Vector2 position) { + bonePose.X = position.x; + bonePose.Y = position.y; } /// Sets the bone's (local) X and Y according to a Vector3. The z component is ignored. public static void SetLocalPosition (this Bone bone, Vector3 position) { - bone.X = position.x; - bone.Y = position.y; + bone.Pose.SetLocalPosition(position); + } + + /// Sets the bone's (local) X and Y according to a Vector3. The z component is ignored. + public static void SetLocalPosition (this BoneLocal bonePose, Vector3 position) { + bonePose.X = position.x; + bonePose.Y = position.y; } /// Gets the bone's local X and Y as a Vector2. public static Vector2 GetLocalPosition (this Bone bone) { - return new Vector2(bone.X, bone.Y); + return bone.Pose.GetLocalPosition(); + } + + /// Gets the bone's local X and Y as a Vector2. + public static Vector2 GetLocalPosition (this BoneLocal bonePose) { + return new Vector2(bonePose.X, bonePose.Y); } /// Gets the position of the bone in Skeleton-space. public static Vector2 GetSkeletonSpacePosition (this Bone bone) { - return new Vector2(bone.WorldX, bone.WorldY); + return bone.GetSkeletonSpacePosition(); + } + + /// Gets the position of the bone in Skeleton-space. + public static Vector2 GetSkeletonSpacePosition (this BonePose bonePose) { + return new Vector2(bonePose.WorldX, bonePose.WorldY); } /// Gets a local offset from the bone and converts it into Skeleton-space. public static Vector2 GetSkeletonSpacePosition (this Bone bone, Vector2 boneLocal) { Vector2 o; - bone.LocalToWorld(boneLocal.x, boneLocal.y, out o.x, out o.y); + bone.AppliedPose.LocalToWorld(boneLocal.x, boneLocal.y, out o.x, out o.y); return o; } - /// Gets the bone's Unity World position using its Spine GameObject Transform. UpdateWorldTransform needs to have been called for this to return the correct, updated value. + /// Gets the bone's Unity World position using its Spine GameObject Transform. + /// UpdateWorldTransform needs to have been called for this to return the correct, updated value. public static Vector3 GetWorldPosition (this Bone bone, UnityEngine.Transform spineGameObjectTransform) { - return spineGameObjectTransform.TransformPoint(new Vector3(bone.WorldX, bone.WorldY)); + return GetWorldPosition(bone.AppliedPose, spineGameObjectTransform); } public static Vector3 GetWorldPosition (this Bone bone, UnityEngine.Transform spineGameObjectTransform, float positionScale) { - return spineGameObjectTransform.TransformPoint(new Vector3(bone.WorldX * positionScale, bone.WorldY * positionScale)); + return GetWorldPosition(bone.AppliedPose, spineGameObjectTransform, positionScale); } public static Vector3 GetWorldPosition (this Bone bone, UnityEngine.Transform spineGameObjectTransform, float positionScale, Vector2 positionOffset) { - return spineGameObjectTransform.TransformPoint(new Vector3(bone.WorldX * positionScale + positionOffset.x, bone.WorldY * positionScale + positionOffset.y)); + return GetWorldPosition(bone.AppliedPose, spineGameObjectTransform, positionScale, positionOffset); + } + + /// Gets the bone's Unity World position using its Spine GameObject Transform. + /// UpdateWorldTransform needs to have been called for this to return the correct, updated value. + public static Vector3 GetWorldPosition (this BonePose bonePose, UnityEngine.Transform spineGameObjectTransform) { + return spineGameObjectTransform.TransformPoint(new Vector3( + bonePose.WorldX, bonePose.WorldY)); + } + + public static Vector3 GetWorldPosition (this BonePose bonePose, UnityEngine.Transform spineGameObjectTransform, float positionScale) { + return spineGameObjectTransform.TransformPoint(new Vector3( + bonePose.WorldX * positionScale, bonePose.WorldY * positionScale)); + } + + public static Vector3 GetWorldPosition (this BonePose bonePose, UnityEngine.Transform spineGameObjectTransform, float positionScale, Vector2 positionOffset) { + return spineGameObjectTransform.TransformPoint(new Vector3( + bonePose.WorldX * positionScale + positionOffset.x, bonePose.WorldY * positionScale + positionOffset.y)); } /// Gets a skeleton space UnityEngine.Quaternion representation of bone.WorldRotationX. public static Quaternion GetQuaternion (this Bone bone) { - float halfRotation = Mathf.Atan2(bone.C, bone.A) * 0.5f; + return bone.AppliedPose.GetQuaternion(); + } + + /// Gets a skeleton space UnityEngine.Quaternion representation of bone.WorldRotationX. + public static Quaternion GetQuaternion (this BonePose bonePose) { + float halfRotation = Mathf.Atan2(bonePose.C, bonePose.A) * 0.5f; return new Quaternion(0, 0, Mathf.Sin(halfRotation), Mathf.Cos(halfRotation)); } /// Gets a bone-local space UnityEngine.Quaternion representation of bone.rotation. public static Quaternion GetLocalQuaternion (this Bone bone) { - float halfRotation = bone.Rotation * Mathf.Deg2Rad * 0.5f; + return bone.Pose.GetLocalQuaternion(); + } + + /// Gets a bone-local space UnityEngine.Quaternion representation of bone.rotation. + public static Quaternion GetLocalQuaternion (this BoneLocal bonePose) { + float halfRotation = bonePose.Rotation * Mathf.Deg2Rad * 0.5f; return new Quaternion(0, 0, Mathf.Sin(halfRotation), Mathf.Cos(halfRotation)); } @@ -180,7 +186,12 @@ namespace Spine.Unity { /// Calculates a 2x2 Transformation Matrix that can convert a skeleton-space position to a bone-local position. public static void GetWorldToLocalMatrix (this Bone bone, out float ia, out float ib, out float ic, out float id) { - float a = bone.A, b = bone.B, c = bone.C, d = bone.D; + bone.AppliedPose.GetWorldToLocalMatrix(out ia, out ib, out ic, out id); + } + + /// Calculates a 2x2 Transformation Matrix that can convert a skeleton-space position to a bone-local position. + public static void GetWorldToLocalMatrix (this BonePose bonePose, out float ia, out float ib, out float ic, out float id) { + float a = bonePose.A, b = bonePose.B, c = bonePose.C, d = bonePose.D; float invDet = 1 / (a * d - b * c); ia = invDet * d; ib = invDet * -b; @@ -190,8 +201,13 @@ namespace Spine.Unity { /// UnityEngine.Vector2 override of Bone.WorldToLocal. This converts a skeleton-space position into a bone local position. public static Vector2 WorldToLocal (this Bone bone, Vector2 worldPosition) { + return bone.AppliedPose.WorldToLocal(worldPosition); + } + + /// UnityEngine.Vector2 override of Bone.WorldToLocal. This converts a skeleton-space position into a bone local position. + public static Vector2 WorldToLocal (this BonePose bonePose, Vector2 worldPosition) { Vector2 o; - bone.WorldToLocal(worldPosition.x, worldPosition.y, out o.x, out o.y); + bonePose.WorldToLocal(worldPosition.x, worldPosition.y, out o.x, out o.y); return o; } @@ -230,14 +246,17 @@ namespace Spine.Unity { /// Fills a Vector2 buffer with local vertices. /// The VertexAttachment /// Slot where the attachment belongs. - /// Correctly-sized buffer. Use attachment's .WorldVerticesLength to get the correct size. If null, a new Vector2[] of the correct size will be allocated. - public static Vector2[] GetLocalVertices (this VertexAttachment va, Slot slot, Vector2[] buffer) { + /// Correctly-sized buffer. Use attachment's .WorldVerticesLength to get the correct size. + /// If null, a new Vector2[] of the correct size will be allocated. + public static Vector2[] GetLocalVertices (this VertexAttachment va, Skeleton skeleton, Slot slot, Vector2[] buffer) { int floatsCount = va.WorldVerticesLength; int bufferTargetSize = floatsCount >> 1; buffer = buffer ?? new Vector2[bufferTargetSize]; - if (buffer.Length < bufferTargetSize) throw new System.ArgumentException(string.Format("Vector2 buffer too small. {0} requires an array of size {1}. Use the attachment's .WorldVerticesLength to get the correct size.", va.Name, floatsCount), "buffer"); + if (buffer.Length < bufferTargetSize) throw new System.ArgumentException( + string.Format("Vector2 buffer too small. {0} requires an array of size {1}. " + + "Use the attachment's .WorldVerticesLength to get the correct size.", va.Name, floatsCount), "buffer"); - if (va.Bones == null && slot.Deform.Count == 0) { + if (va.Bones == null && slot.Pose.Deform.Count == 0) { float[] localVerts = va.Vertices; for (int i = 0; i < bufferTargetSize; i++) { int j = i * 2; @@ -245,12 +264,12 @@ namespace Spine.Unity { } } else { float[] floats = new float[floatsCount]; - va.ComputeWorldVertices(slot, floats); + va.ComputeWorldVertices(skeleton, slot, floats); Bone sb = slot.Bone; - float ia, ib, ic, id, bwx = sb.WorldX, bwy = sb.WorldY; + BonePose pose = slot.Bone.AppliedPose; + float ia, ib, ic, id, bwx = pose.WorldX, bwy = pose.WorldY; sb.GetWorldToLocalMatrix(out ia, out ib, out ic, out id); - for (int i = 0; i < bufferTargetSize; i++) { int j = i * 2; float x = floats[j] - bwx, y = floats[j + 1] - bwy; @@ -265,14 +284,14 @@ namespace Spine.Unity { /// The VertexAttachment /// Slot where the attachment belongs. /// Correctly-sized buffer. Use attachment's .WorldVerticesLength to get the correct size. If null, a new Vector2[] of the correct size will be allocated. - public static Vector2[] GetWorldVertices (this VertexAttachment a, Slot slot, Vector2[] buffer) { + public static Vector2[] GetWorldVertices (this VertexAttachment a, Skeleton skeleton, Slot slot, Vector2[] buffer) { int worldVertsLength = a.WorldVerticesLength; int bufferTargetSize = worldVertsLength >> 1; buffer = buffer ?? new Vector2[bufferTargetSize]; if (buffer.Length < bufferTargetSize) throw new System.ArgumentException(string.Format("Vector2 buffer too small. {0} requires an array of size {1}. Use the attachment's .WorldVerticesLength to get the correct size.", a.Name, worldVertsLength), "buffer"); float[] floats = new float[worldVertsLength]; - a.ComputeWorldVertices(slot, floats); + a.ComputeWorldVertices(skeleton, slot, floats); for (int i = 0, n = worldVertsLength >> 1; i < n; i++) { int j = i * 2; @@ -286,7 +305,7 @@ namespace Spine.Unity { public static Vector3 GetWorldPosition (this PointAttachment attachment, Slot slot, Transform spineGameObjectTransform) { Vector3 skeletonSpacePosition; skeletonSpacePosition.z = 0; - attachment.ComputeWorldPosition(slot.Bone, out skeletonSpacePosition.x, out skeletonSpacePosition.y); + attachment.ComputeWorldPosition(slot.Bone.AppliedPose, out skeletonSpacePosition.x, out skeletonSpacePosition.y); return spineGameObjectTransform.TransformPoint(skeletonSpacePosition); } @@ -294,7 +313,7 @@ namespace Spine.Unity { public static Vector3 GetWorldPosition (this PointAttachment attachment, Bone bone, Transform spineGameObjectTransform) { Vector3 skeletonSpacePosition; skeletonSpacePosition.z = 0; - attachment.ComputeWorldPosition(bone, out skeletonSpacePosition.x, out skeletonSpacePosition.y); + attachment.ComputeWorldPosition(bone.AppliedPose, out skeletonSpacePosition.x, out skeletonSpacePosition.y); return spineGameObjectTransform.TransformPoint(skeletonSpacePosition); } #endregion @@ -326,16 +345,17 @@ namespace Spine { float pa = parentMatrix.a, pb = parentMatrix.b, pc = parentMatrix.c, pd = parentMatrix.d; BoneMatrix result = default(BoneMatrix); - result.x = pa * boneData.X + pb * boneData.Y + parentMatrix.x; - result.y = pc * boneData.X + pd * boneData.Y + parentMatrix.y; + var setup = boneData.GetSetupPose(); + result.x = pa * setup.X + pb * setup.Y + parentMatrix.x; + result.y = pc * setup.X + pd * setup.Y + parentMatrix.y; - switch (boneData.Inherit) { + switch (setup.Inherit) { case Inherit.Normal: { - float rotationY = boneData.Rotation + 90 + boneData.ShearY; - float la = MathUtils.CosDeg(boneData.Rotation + boneData.ShearX) * boneData.ScaleX; - float lb = MathUtils.CosDeg(rotationY) * boneData.ScaleY; - float lc = MathUtils.SinDeg(boneData.Rotation + boneData.ShearX) * boneData.ScaleX; - float ld = MathUtils.SinDeg(rotationY) * boneData.ScaleY; + float rotationY = setup.Rotation + 90 + setup.ShearY; + float la = MathUtils.CosDeg(setup.Rotation + setup.ShearX) * setup.ScaleX; + float lb = MathUtils.CosDeg(rotationY) * setup.ScaleY; + float lc = MathUtils.SinDeg(setup.Rotation + setup.ShearX) * setup.ScaleX; + float ld = MathUtils.SinDeg(rotationY) * setup.ScaleY; result.a = pa * la + pb * lc; result.b = pa * lb + pb * ld; result.c = pc * la + pd * lc; @@ -343,11 +363,11 @@ namespace Spine { break; } case Inherit.OnlyTranslation: { - float rotationY = boneData.Rotation + 90 + boneData.ShearY; - result.a = MathUtils.CosDeg(boneData.Rotation + boneData.ShearX) * boneData.ScaleX; - result.b = MathUtils.CosDeg(rotationY) * boneData.ScaleY; - result.c = MathUtils.SinDeg(boneData.Rotation + boneData.ShearX) * boneData.ScaleX; - result.d = MathUtils.SinDeg(rotationY) * boneData.ScaleY; + float rotationY = setup.Rotation + 90 + setup.ShearY; + result.a = MathUtils.CosDeg(setup.Rotation + setup.ShearX) * setup.ScaleX; + result.b = MathUtils.CosDeg(rotationY) * setup.ScaleY; + result.c = MathUtils.SinDeg(setup.Rotation + setup.ShearX) * setup.ScaleX; + result.d = MathUtils.SinDeg(rotationY) * setup.ScaleY; break; } case Inherit.NoRotationOrReflection: { @@ -362,12 +382,12 @@ namespace Spine { pc = 0; prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; } - float rx = boneData.Rotation + boneData.ShearX - prx; - float ry = boneData.Rotation + boneData.ShearY - prx + 90; - float la = MathUtils.CosDeg(rx) * boneData.ScaleX; - float lb = MathUtils.CosDeg(ry) * boneData.ScaleY; - float lc = MathUtils.SinDeg(rx) * boneData.ScaleX; - float ld = MathUtils.SinDeg(ry) * boneData.ScaleY; + float rx = setup.Rotation + setup.ShearX - prx; + float ry = setup.Rotation + setup.ShearY - prx + 90; + float la = MathUtils.CosDeg(rx) * setup.ScaleX; + float lb = MathUtils.CosDeg(ry) * setup.ScaleY; + float lc = MathUtils.SinDeg(rx) * setup.ScaleX; + float ld = MathUtils.SinDeg(ry) * setup.ScaleY; result.a = pa * la - pb * lc; result.b = pa * lb - pb * ld; result.c = pc * la + pd * lc; @@ -376,7 +396,7 @@ namespace Spine { } case Inherit.NoScale: case Inherit.NoScaleOrReflection: { - float cos = MathUtils.CosDeg(boneData.Rotation), sin = MathUtils.SinDeg(boneData.Rotation); + float cos = MathUtils.CosDeg(setup.Rotation), sin = MathUtils.SinDeg(setup.Rotation); float za = pa * cos + pb * sin; float zc = pc * cos + pd * sin; float s = (float)Math.Sqrt(za * za + zc * zc); @@ -388,11 +408,11 @@ namespace Spine { float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); float zb = MathUtils.Cos(r) * s; float zd = MathUtils.Sin(r) * s; - float la = MathUtils.CosDeg(boneData.ShearX) * boneData.ScaleX; - float lb = MathUtils.CosDeg(90 + boneData.ShearY) * boneData.ScaleY; - float lc = MathUtils.SinDeg(boneData.ShearX) * boneData.ScaleX; - float ld = MathUtils.SinDeg(90 + boneData.ShearY) * boneData.ScaleY; - if (boneData.Inherit != Inherit.NoScaleOrReflection ? pa * pd - pb * pc < 0 : false) { + float la = MathUtils.CosDeg(setup.ShearX) * setup.ScaleX; + float lb = MathUtils.CosDeg(90 + setup.ShearY) * setup.ScaleY; + float lc = MathUtils.SinDeg(setup.ShearX) * setup.ScaleX; + float ld = MathUtils.SinDeg(90 + setup.ShearY) * setup.ScaleY; + if (setup.Inherit != Inherit.NoScaleOrReflection ? pa * pd - pb * pc < 0 : false) { zb = -zb; zd = -zd; } @@ -409,28 +429,30 @@ namespace Spine { /// Constructor for a local bone matrix based on Setup Pose BoneData. public BoneMatrix (BoneData boneData) { - float rotationY = boneData.Rotation + 90 + boneData.ShearY; - float rotationX = boneData.Rotation + boneData.ShearX; + var setup = boneData.GetSetupPose(); + float rotationY = setup.Rotation + 90 + setup.ShearY; + float rotationX = setup.Rotation + setup.ShearX; - a = MathUtils.CosDeg(rotationX) * boneData.ScaleX; - c = MathUtils.SinDeg(rotationX) * boneData.ScaleX; - b = MathUtils.CosDeg(rotationY) * boneData.ScaleY; - d = MathUtils.SinDeg(rotationY) * boneData.ScaleY; - x = boneData.X; - y = boneData.Y; + a = MathUtils.CosDeg(rotationX) * setup.ScaleX; + c = MathUtils.SinDeg(rotationX) * setup.ScaleX; + b = MathUtils.CosDeg(rotationY) * setup.ScaleY; + d = MathUtils.SinDeg(rotationY) * setup.ScaleY; + x = setup.X; + y = setup.Y; } /// Constructor for a local bone matrix based on a bone instance's current pose. public BoneMatrix (Bone bone) { - float rotationY = bone.Rotation + 90 + bone.ShearY; - float rotationX = bone.Rotation + bone.ShearX; + var bonePose = bone.Pose; + float rotationY = bonePose.Rotation + 90 + bonePose.ShearY; + float rotationX = bonePose.Rotation + bonePose.ShearX; - a = MathUtils.CosDeg(rotationX) * bone.ScaleX; - c = MathUtils.SinDeg(rotationX) * bone.ScaleX; - b = MathUtils.CosDeg(rotationY) * bone.ScaleY; - d = MathUtils.SinDeg(rotationY) * bone.ScaleY; - x = bone.X; - y = bone.Y; + a = MathUtils.CosDeg(rotationX) * bonePose.ScaleX; + c = MathUtils.SinDeg(rotationX) * bonePose.ScaleX; + b = MathUtils.CosDeg(rotationY) * bonePose.ScaleY; + d = MathUtils.SinDeg(rotationY) * bonePose.ScaleY; + x = bonePose.X; + y = bonePose.Y; } public BoneMatrix TransformMatrix (BoneMatrix local) { diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs index a5d8217ff..66944bfc2 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs @@ -48,7 +48,8 @@ namespace Spine.Unity.AnimationTools { return new Vector2(x, y); } else { BoneData boneData = skeletonData.Bones.Items[timeline.BoneIndex]; - return new Vector2(boneData.X + x, boneData.Y + y); + var setup = boneData.GetSetupPose(); + return new Vector2(setup.X + x, setup.Y + y); } } @@ -69,7 +70,7 @@ namespace Spine.Unity.AnimationTools { BoneData[] bonesItems = skeletonData.Bones.Items; BoneData boneDataX = bonesItems[xTimeline.BoneIndex]; BoneData boneDataY = bonesItems[yTimeline.BoneIndex]; - return new Vector2(boneDataX.X + x, boneDataY.Y + y); + return new Vector2(boneDataX.GetSetupPose().X + x, boneDataY.GetSetupPose().Y + y); } } @@ -85,7 +86,7 @@ namespace Spine.Unity.AnimationTools { return rotation; } else { BoneData boneData = skeletonData.Bones.Items[timeline.BoneIndex]; - return (boneData.Rotation + rotation); + return (boneData.GetSetupPose().Rotation + rotation); } } @@ -149,7 +150,7 @@ namespace Spine.Unity.AnimationTools { TransformConstraintTimeline transformConstraintTimeline = timeline as TransformConstraintTimeline; if (transformConstraintTimeline != null && - transformConstraintTimeline.TransformConstraintIndex == transformConstraintIndex) + transformConstraintTimeline.ConstraintIndex == transformConstraintIndex) return transformConstraintTimeline; } return null; diff --git a/spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateMixerBehaviour.cs b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateMixerBehaviour.cs index 046adac3b..edbe75672 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateMixerBehaviour.cs +++ b/spine-unity/Modules/com.esotericsoftware.spine.timeline/Runtime/SpineAnimationState/SpineAnimationStateMixerBehaviour.cs @@ -318,7 +318,7 @@ namespace Spine.Unity.Playables { } if (trackIndex == 0) - skeleton.SetToSetupPose(); + skeleton.SetupPose(); // Approximate what AnimationState might do at runtime. if (fromAnimation != null && mixDuration > 0 && toClipTime < mixDuration) { @@ -353,7 +353,7 @@ namespace Spine.Unity.Playables { dummyAnimationState.Event -= EditorEvent; } else { if (toAnimation != null) { - toAnimation.Apply(skeleton, 0, toClipTime, clipData.loop, editorAnimationEvents, clipData.alpha, MixBlend.Setup, MixDirection.In); + toAnimation.Apply(skeleton, 0, toClipTime, clipData.loop, editorAnimationEvents, clipData.alpha, MixBlend.Setup, MixDirection.In, false); if (EditorEvent != null) { foreach (Spine.Event e in editorAnimationEvents) { EditorEvent(null, e); @@ -362,7 +362,7 @@ namespace Spine.Unity.Playables { } } - skeleton.UpdateWorldTransform(Skeleton.Physics.Update); + skeleton.UpdateWorldTransform(Physics.Update); if (skeletonAnimation) { skeletonAnimation.AfterAnimationApplied(); skeletonAnimation.LateUpdate();