diff --git a/.gitignore b/.gitignore
index 3ac6da826..9c2bb7107 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,12 @@ spine-cocos2d-iphone/libs/*
!spine-cocos2d-iphone/libs/cocos2d/Place cocos2d here.txt
!spine-cocos2d-iphone/libs/CocosDenshion/Place CocosDenshion here.txt
!spine-cocos2d-iphone/libs/kazmath/Place kazmath here.txt
+
+*.user
+forums
+spine-csharp/bin
+spine-csharp/obj
+spine-xna/bin
+spine-xna/obj
+spine-xna/example/bin
+spine-xna/example/obj
\ No newline at end of file
diff --git a/spine-csharp/Properties/AssemblyInfo.cs b/spine-csharp/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..871268e17
--- /dev/null
+++ b/spine-csharp/Properties/AssemblyInfo.cs
@@ -0,0 +1,34 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("spine-csharp")]
+[assembly: AssemblyProduct("spine-csharp")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2013")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type. Only Windows
+// assemblies support COM.
+[assembly: ComVisible(false)]
+
+// On Windows, the following GUID is for the ID of the typelib if this
+// project is exposed to COM. On other platforms, it unique identifies the
+// title storage container when deploying this assembly to the device.
+[assembly: Guid("3ac8567e-9ae8-4624-87b9-a84f0101c629")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+[assembly: AssemblyVersion("1.0.0.0")]
diff --git a/spine-csharp/spine-csharp.csproj b/spine-csharp/spine-csharp.csproj
new file mode 100644
index 000000000..79a02999e
--- /dev/null
+++ b/spine-csharp/spine-csharp.csproj
@@ -0,0 +1,89 @@
+
+
+
+ {94144E22-2431-4A8F-AC04-DEC22F7EDD8F}
+ {6D335F3A-9D43-41b4-9D22-F6F17C4BE596};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ Debug
+ x86
+ Library
+ Properties
+ Spine
+ spine-csharp
+ v4.0
+ Client
+ v4.0
+ Windows
+ HiDef
+ 99dfd52d-8beb-4e5c-a68b-365be39e8064
+ Library
+
+
+ true
+ full
+ false
+ bin\x86\Debug
+ DEBUG;TRACE;WINDOWS
+ prompt
+ 4
+ true
+ false
+ x86
+ false
+
+
+ pdbonly
+ true
+ bin\x86\Release
+ TRACE;WINDOWS
+ prompt
+ 4
+ true
+ false
+ x86
+ true
+
+
+
+ False
+
+
+ False
+
+
+ 4.0
+ False
+
+
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs
new file mode 100644
index 000000000..bd027bfff
--- /dev/null
+++ b/spine-csharp/src/Animation.cs
@@ -0,0 +1,390 @@
+using System;
+using System.Collections.Generic;
+
+namespace Spine {
+ public class Animation {
+ public String Name { get; private set; }
+ public List Timelines { get; set; }
+ public float Duration { get; set; }
+
+ public Animation (String name, List timelines, float duration) {
+ if (name == null) throw new ArgumentNullException("name cannot be null.");
+ if (timelines == null) throw new ArgumentNullException("timelines cannot be null.");
+ Name = name;
+ Timelines = timelines;
+ Duration = duration;
+ }
+
+ /** Poses the skeleton at the specified time for this animation. */
+ public void Apply (Skeleton skeleton, float time, bool loop) {
+ if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
+
+ if (loop && Duration != 0) time %= Duration;
+
+ List timelines = Timelines;
+ for (int i = 0, n = timelines.Count; i < n; i++)
+ timelines[i].Apply(skeleton, time, 1);
+ }
+
+ /** Poses the skeleton at the specified time for this animation mixed with the current pose.
+ * @param alpha The amount of this animation that affects the current pose. */
+ public void Mix (Skeleton skeleton, float time, bool loop, float alpha) {
+ if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
+
+ if (loop && Duration != 0) time %= Duration;
+
+ List timelines = Timelines;
+ for (int i = 0, n = timelines.Count; i < n; i++)
+ timelines[i].Apply(skeleton, time, alpha);
+ }
+
+ /** @param target After the first and before the last entry. */
+ internal static int binarySearch (float[] values, float target, int step) {
+ int low = 0;
+ int high = values.Length / step - 2;
+ if (high == 0) return step;
+ int current = (int)((uint)high >> 1);
+ while (true) {
+ if (values[(current + 1) * step] <= target)
+ low = current + 1;
+ else
+ high = current;
+ if (low == high) return (low + 1) * step;
+ current = (int)((uint)(low + high) >> 1);
+ }
+ }
+
+ internal static int linearSearch (float[] values, float target, int step) {
+ for (int i = 0, last = values.Length - step; i <= last; i += step)
+ if (values[i] > target) return i;
+ return -1;
+ }
+ }
+
+ public interface Timeline {
+ /** Sets the value(s) for the specified time. */
+ void Apply (Skeleton skeleton, float time, float alpha);
+ }
+
+ /** Base class for frames that use an interpolation bezier curve. */
+ abstract public class CurveTimeline : Timeline {
+ static protected float LINEAR = 0;
+ static protected float STEPPED = -1;
+ static protected int BEZIER_SEGMENTS = 10;
+
+ private float[] curves; // dfx, dfy, ddfx, ddfy, dddfx, dddfy, ...
+ public int FrameCount {
+ get {
+ return curves.Length / 6 + 1;
+ }
+ }
+
+ public CurveTimeline (int frameCount) {
+ curves = new float[(frameCount - 1) * 6];
+ }
+
+ abstract public void Apply (Skeleton skeleton, float time, float alpha);
+
+ public void SetLinear (int frameIndex) {
+ curves[frameIndex * 6] = LINEAR;
+ }
+
+ public void SetStepped (int frameIndex) {
+ curves[frameIndex * 6] = STEPPED;
+ }
+
+ /** Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next.
+ * cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of
+ * the difference between the keyframe's values. */
+ public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) {
+ float subdiv_step = 1f / BEZIER_SEGMENTS;
+ float subdiv_step2 = subdiv_step * subdiv_step;
+ float subdiv_step3 = subdiv_step2 * subdiv_step;
+ float pre1 = 3 * subdiv_step;
+ float pre2 = 3 * subdiv_step2;
+ float pre4 = 6 * subdiv_step2;
+ float pre5 = 6 * subdiv_step3;
+ float tmp1x = -cx1 * 2 + cx2;
+ float tmp1y = -cy1 * 2 + cy2;
+ float tmp2x = (cx1 - cx2) * 3 + 1;
+ float tmp2y = (cy1 - cy2) * 3 + 1;
+ int i = frameIndex * 6;
+ float[] curves = this.curves;
+ curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3;
+ curves[i + 1] = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3;
+ curves[i + 2] = tmp1x * pre4 + tmp2x * pre5;
+ curves[i + 3] = tmp1y * pre4 + tmp2y * pre5;
+ curves[i + 4] = tmp2x * pre5;
+ curves[i + 5] = tmp2y * pre5;
+ }
+
+ public float GetCurvePercent (int frameIndex, float percent) {
+ int curveIndex = frameIndex * 6;
+ float[] curves = this.curves;
+ float dfx = curves[curveIndex];
+ if (dfx == LINEAR) return percent;
+ if (dfx == STEPPED) return 0;
+ float dfy = curves[curveIndex + 1];
+ float ddfx = curves[curveIndex + 2];
+ float ddfy = curves[curveIndex + 3];
+ float dddfx = curves[curveIndex + 4];
+ float dddfy = curves[curveIndex + 5];
+ float x = dfx, y = dfy;
+ int i = BEZIER_SEGMENTS - 2;
+ while (true) {
+ if (x >= percent) {
+ float lastX = x - dfx;
+ float lastY = y - dfy;
+ return lastY + (y - lastY) * (percent - lastX) / (x - lastX);
+ }
+ if (i == 0) break;
+ i--;
+ dfx += ddfx;
+ dfy += ddfy;
+ ddfx += dddfx;
+ ddfy += dddfy;
+ x += dfx;
+ y += dfy;
+ }
+ return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1.
+ }
+ }
+
+ public class RotateTimeline : CurveTimeline {
+ static protected int LAST_FRAME_TIME = -2;
+ static protected int FRAME_VALUE = 1;
+
+ public int BoneIndex { get; set; }
+ public float[] Frames { get; private set; } // time, value, ...
+
+ public RotateTimeline (int frameCount)
+ : base(frameCount) {
+ Frames = new float[frameCount * 2];
+ }
+
+ /** Sets the time and value of the specified keyframe. */
+ public void SetFrame (int frameIndex, float time, float angle) {
+ frameIndex *= 2;
+ Frames[frameIndex] = time;
+ Frames[frameIndex + 1] = angle;
+ }
+
+ override public void Apply (Skeleton skeleton, float time, float alpha) {
+ float[] frames = Frames;
+ if (time < frames[0]) return; // Time is before first frame.
+
+ Bone bone = skeleton.Bones[BoneIndex];
+
+ float amount;
+
+ if (time >= frames[frames.Length - 2]) { // Time is after last frame.
+ amount = bone.Data.Rotation + frames[frames.Length - 1] - bone.Rotation;
+ while (amount > 180)
+ amount -= 360;
+ while (amount < -180)
+ amount += 360;
+ bone.Rotation += amount * alpha;
+ return;
+ }
+
+ // Interpolate between the last frame and the current frame.
+ int frameIndex = Animation.binarySearch(frames, time, 2);
+ float lastFrameValue = frames[frameIndex - 1];
+ float frameTime = frames[frameIndex];
+ float percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime);
+ percent = GetCurvePercent(frameIndex / 2 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+
+ amount = frames[frameIndex + FRAME_VALUE] - lastFrameValue;
+ while (amount > 180)
+ amount -= 360;
+ while (amount < -180)
+ amount += 360;
+ amount = bone.Data.Rotation + (lastFrameValue + amount * percent) - bone.Rotation;
+ while (amount > 180)
+ amount -= 360;
+ while (amount < -180)
+ amount += 360;
+ bone.Rotation += amount * alpha;
+ }
+ }
+
+ public class TranslateTimeline : CurveTimeline {
+ static protected int LAST_FRAME_TIME = -3;
+ static protected int FRAME_X = 1;
+ static protected int FRAME_Y = 2;
+
+ public int BoneIndex { get; set; }
+ public float[] Frames { get; private set; } // time, value, value, ...
+
+ public TranslateTimeline (int frameCount)
+ : base(frameCount) {
+ Frames = new float[frameCount * 3];
+ }
+
+ /** Sets the time and value of the specified keyframe. */
+ public void SetFrame (int frameIndex, float time, float x, float y) {
+ frameIndex *= 3;
+ Frames[frameIndex] = time;
+ Frames[frameIndex + 1] = x;
+ Frames[frameIndex + 2] = y;
+ }
+
+ override public void Apply (Skeleton skeleton, float time, float alpha) {
+ float[] frames = Frames;
+ if (time < frames[0]) return; // Time is before first frame.
+
+ Bone bone = skeleton.Bones[BoneIndex];
+
+ if (time >= frames[frames.Length - 3]) { // Time is after last frame.
+ bone.X += (bone.Data.X + frames[frames.Length - 2] - bone.X) * alpha;
+ bone.Y += (bone.Data.Y + frames[frames.Length - 1] - bone.Y) * alpha;
+ return;
+ }
+
+ // Interpolate between the last frame and the current frame.
+ int frameIndex = Animation.binarySearch(frames, time, 3);
+ float lastFrameX = frames[frameIndex - 2];
+ float lastFrameY = frames[frameIndex - 1];
+ float frameTime = frames[frameIndex];
+ float percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime);
+ percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+
+ bone.X += (bone.Data.X + lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent - bone.X) * alpha;
+ bone.Y += (bone.Data.Y + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.Y) * alpha;
+ }
+ }
+
+ public class ScaleTimeline : TranslateTimeline {
+ public ScaleTimeline (int frameCount)
+ : base(frameCount) {
+ }
+
+ override public void Apply (Skeleton skeleton, float time, float alpha) {
+ float[] frames = Frames;
+ if (time < frames[0]) return; // Time is before first frame.
+
+ Bone bone = skeleton.Bones[BoneIndex];
+ if (time >= frames[frames.Length - 3]) { // Time is after last frame.
+ bone.ScaleX += (bone.Data.ScaleX - 1 + frames[frames.Length - 2] - bone.ScaleX) * alpha;
+ bone.ScaleY += (bone.Data.ScaleY - 1 + frames[frames.Length - 1] - bone.ScaleY) * alpha;
+ return;
+ }
+
+ // Interpolate between the last frame and the current frame.
+ int frameIndex = Animation.binarySearch(frames, time, 3);
+ float lastFrameX = frames[frameIndex - 2];
+ float lastFrameY = frames[frameIndex - 1];
+ float frameTime = frames[frameIndex];
+ float percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime);
+ percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+
+ bone.ScaleX += (bone.Data.ScaleX - 1 + lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent - bone.ScaleX) * alpha;
+ bone.ScaleY += (bone.Data.ScaleY - 1 + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.ScaleY) * alpha;
+ }
+ }
+
+ public class ColorTimeline : CurveTimeline {
+ static protected int LAST_FRAME_TIME = -5;
+ static protected int FRAME_R = 1;
+ static protected int FRAME_G = 2;
+ static protected int FRAME_B = 3;
+ static protected int FRAME_A = 4;
+
+ public int SlotIndex { get; set; }
+ public float[] Frames { get; private set; } // time, r, g, b, a, ...
+
+ public ColorTimeline (int frameCount)
+ : base(frameCount) {
+ Frames = new float[frameCount * 5];
+ }
+
+ /** Sets the time and value of the specified keyframe. */
+ public void setFrame (int frameIndex, float time, float r, float g, float b, float a) {
+ frameIndex *= 5;
+ Frames[frameIndex] = time;
+ Frames[frameIndex + 1] = r;
+ Frames[frameIndex + 2] = g;
+ Frames[frameIndex + 3] = b;
+ Frames[frameIndex + 4] = a;
+ }
+
+ override public void Apply (Skeleton skeleton, float time, float alpha) {
+ float[] frames = Frames;
+ if (time < frames[0]) return; // Time is before first frame.
+
+ Slot slot = skeleton.Slots[SlotIndex];
+
+ if (time >= frames[frames.Length - 5]) { // Time is after last frame.
+ int i = frames.Length - 1;
+ slot.R = frames[i - 3];
+ slot.G = frames[i - 2];
+ slot.B = frames[i - 1];
+ slot.A = frames[i];
+ return;
+ }
+
+ // Interpolate between the last frame and the current frame.
+ int frameIndex = Animation.binarySearch(frames, time, 5);
+ float lastFrameR = frames[frameIndex - 4];
+ float lastFrameG = frames[frameIndex - 3];
+ float lastFrameB = frames[frameIndex - 2];
+ float lastFrameA = frames[frameIndex - 1];
+ float frameTime = frames[frameIndex];
+ float percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime);
+ percent = GetCurvePercent(frameIndex / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+
+ float r = lastFrameR + (frames[frameIndex + FRAME_R] - lastFrameR) * percent;
+ float g = lastFrameG + (frames[frameIndex + FRAME_G] - lastFrameG) * percent;
+ float b = lastFrameB + (frames[frameIndex + FRAME_B] - lastFrameB) * percent;
+ float a = lastFrameA + (frames[frameIndex + FRAME_A] - lastFrameA) * percent;
+ if (alpha < 1) {
+ slot.R += (r - slot.R) * alpha;
+ slot.G += (g - slot.G) * alpha;
+ slot.B += (b - slot.B) * alpha;
+ slot.A += (a - slot.A) * alpha;
+ } else {
+ slot.R = r;
+ slot.G = g;
+ slot.B = b;
+ slot.A = a;
+ }
+ }
+ }
+
+ public class AttachmentTimeline : Timeline {
+ public int SlotIndex { get; set; }
+ public float[] Frames { get; private set; } // time, ...
+ public String[] AttachmentNames { get; private set; }
+ public int FrameCount {
+ get {
+ return Frames.Length;
+ }
+ }
+
+ public AttachmentTimeline (int frameCount) {
+ Frames = new float[frameCount];
+ AttachmentNames = new String[frameCount];
+ }
+
+ /** Sets the time and value of the specified keyframe. */
+ public void setFrame (int frameIndex, float time, String attachmentName) {
+ Frames[frameIndex] = time;
+ AttachmentNames[frameIndex] = attachmentName;
+ }
+
+ public void Apply (Skeleton skeleton, float time, float alpha) {
+ float[] frames = Frames;
+ if (time < frames[0]) return; // Time is before first frame.
+
+ int frameIndex;
+ if (time >= frames[frames.Length - 1]) // Time is after last frame.
+ frameIndex = frames.Length - 1;
+ else
+ frameIndex = Animation.binarySearch(frames, time, 1) - 1;
+
+ String attachmentName = AttachmentNames[frameIndex];
+ skeleton.Slots[SlotIndex].Attachment =
+ attachmentName == null ? null : skeleton.GetAttachment(SlotIndex, attachmentName);
+ }
+ }
+}
diff --git a/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs b/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs
new file mode 100644
index 000000000..02e5f975a
--- /dev/null
+++ b/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace Spine {
+ public class AtlasAttachmentLoader : AttachmentLoader {
+ private BaseAtlas atlas;
+
+ public AtlasAttachmentLoader (BaseAtlas atlas) {
+ if (atlas == null) throw new ArgumentNullException("atlas cannot be null.");
+ this.atlas = atlas;
+ }
+
+ public Attachment NewAttachment (AttachmentType type, String name) {
+ switch (type) {
+ case AttachmentType.region:
+ AtlasRegion region = atlas.FindRegion(name);
+ if (region == null) throw new Exception("Region not found in atlas: " + name + " (" + type + ")");
+ RegionAttachment attachment = new RegionAttachment(name);
+ attachment.Region = region;
+ return attachment;
+ }
+ throw new Exception("Unknown attachment type: " + type);
+ }
+ }
+}
diff --git a/spine-csharp/src/Attachments/Attachment.cs b/spine-csharp/src/Attachments/Attachment.cs
new file mode 100644
index 000000000..c8c8aab52
--- /dev/null
+++ b/spine-csharp/src/Attachments/Attachment.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Spine {
+ abstract public class Attachment {
+ public String Name { get; private set; }
+
+ public Attachment (String name) {
+ if (name == null) throw new ArgumentNullException("name cannot be null.");
+ Name = name;
+ }
+
+ override public String ToString () {
+ return Name;
+ }
+ }
+}
diff --git a/spine-csharp/src/Attachments/AttachmentLoader.cs b/spine-csharp/src/Attachments/AttachmentLoader.cs
new file mode 100644
index 000000000..28393aef2
--- /dev/null
+++ b/spine-csharp/src/Attachments/AttachmentLoader.cs
@@ -0,0 +1,8 @@
+using System;
+
+namespace Spine {
+ public interface AttachmentLoader {
+ /** @return May be null to not load any attachment. */
+ Attachment NewAttachment (AttachmentType type, String name);
+ }
+}
diff --git a/spine-csharp/src/Attachments/AttachmentType.cs b/spine-csharp/src/Attachments/AttachmentType.cs
new file mode 100644
index 000000000..49d93f775
--- /dev/null
+++ b/spine-csharp/src/Attachments/AttachmentType.cs
@@ -0,0 +1,6 @@
+
+namespace Spine {
+ public enum AttachmentType {
+ region, regionSequence
+ }
+}
diff --git a/spine-csharp/src/Attachments/RegionAttachment.cs b/spine-csharp/src/Attachments/RegionAttachment.cs
new file mode 100644
index 000000000..86d5c291a
--- /dev/null
+++ b/spine-csharp/src/Attachments/RegionAttachment.cs
@@ -0,0 +1,134 @@
+using System;
+
+namespace Spine {
+ /** Attachment that displays a texture region. */
+ public class RegionAttachment : Attachment {
+ public const int X1 = 0;
+ public const int Y1 = 1;
+ public const int X2 = 2;
+ public const int Y2 = 3;
+ public const int X3 = 4;
+ public const int Y3 = 5;
+ public const int X4 = 6;
+ public const int Y4 = 7;
+
+ public float X { get; set; }
+ public float Y { get; set; }
+ public float ScaleX { get; set; }
+ public float ScaleY { get; set; }
+ public float Rotation { get; set; }
+ public float Width { get; set; }
+ public float Height { get; set; }
+
+ public float[] Offset { get; private set; }
+ public float[] Vertices { get; private set; }
+ public float[] UVs { get; private set; }
+
+ private AtlasRegion region;
+ public AtlasRegion Region {
+ get {
+ return region;
+ }
+ set {
+ region = value;
+ float[] uvs = UVs;
+ if (value.Rotate) {
+ uvs[X2] = value.U;
+ uvs[Y2] = value.V2;
+ uvs[X3] = value.U;
+ uvs[Y3] = value.V;
+ uvs[X4] = value.U2;
+ uvs[Y4] = value.V;
+ uvs[X1] = value.U2;
+ uvs[Y1] = value.V2;
+ } else {
+ uvs[X1] = value.U;
+ uvs[Y1] = value.V2;
+ uvs[X2] = value.U;
+ uvs[Y2] = value.V;
+ uvs[X3] = value.U2;
+ uvs[Y3] = value.V;
+ uvs[X4] = value.U2;
+ uvs[Y4] = value.V2;
+ }
+ }
+ }
+
+ public RegionAttachment (string name)
+ : base(name) {
+ Offset = new float[8];
+ Vertices = new float[8];
+ UVs = new float[8];
+ ScaleX = 1;
+ ScaleY = 1;
+ }
+
+ public void UpdateOffset () {
+ float width = Width;
+ float height = Height;
+ float localX2 = width / 2;
+ float localY2 = height / 2;
+ float localX = -localX2;
+ float localY = -localY2;
+ AtlasRegion region = Region;
+ if (region.Rotate) {
+ localX += region.OffsetX / region.OriginalWidth * height;
+ localY += region.OffsetY / region.OriginalHeight * width;
+ localX2 -= (region.OriginalWidth - region.OffsetX - region.Height) / region.OriginalWidth * width;
+ localY2 -= (region.OriginalHeight - region.OffsetY - region.Width) / region.OriginalHeight * height;
+ } else {
+ localX += region.OffsetX / region.OriginalWidth * width;
+ localY += region.OffsetY / region.OriginalHeight * height;
+ localX2 -= (region.OriginalWidth - region.OffsetX - region.Width) / region.OriginalWidth * width;
+ localY2 -= (region.OriginalHeight - region.OffsetY - region.Height) / region.OriginalHeight * height;
+ }
+ float scaleX = ScaleX;
+ float scaleY = ScaleY;
+ localX *= scaleX;
+ localY *= scaleY;
+ localX2 *= scaleX;
+ localY2 *= scaleY;
+ float radians = Rotation * (float)Math.PI / 180;
+ float cos = (float)Math.Cos(radians);
+ float sin = (float)Math.Sin(radians);
+ float x = X;
+ float y = Y;
+ float localXCos = localX * cos + x;
+ float localXSin = localX * sin;
+ float localYCos = localY * cos + y;
+ float localYSin = localY * sin;
+ float localX2Cos = localX2 * cos + x;
+ float localX2Sin = localX2 * sin;
+ float localY2Cos = localY2 * cos + y;
+ float localY2Sin = localY2 * sin;
+ float[] offset = Offset;
+ offset[X1] = localXCos - localYSin;
+ offset[Y1] = localYCos + localXSin;
+ offset[X2] = localXCos - localY2Sin;
+ offset[Y2] = localY2Cos + localXSin;
+ offset[X3] = localX2Cos - localY2Sin;
+ offset[Y3] = localY2Cos + localX2Sin;
+ offset[X4] = localX2Cos - localYSin;
+ offset[Y4] = localYCos + localX2Sin;
+ }
+
+ public void UpdateVertices (Bone bone) {
+ float x = bone.WorldX;
+ float y = bone.WorldY;
+ float m00 = bone.M00;
+ float m01 = bone.M01;
+ float m10 = bone.M10;
+ float m11 = bone.M11;
+ float[] vertices = Vertices;
+ float[] offset = Offset;
+ vertices[X1] = offset[X1] * m00 + offset[Y1] * m01 + x;
+ vertices[Y1] = offset[X1] * m10 + offset[Y1] * m11 + y;
+ vertices[X2] = offset[X2] * m00 + offset[Y2] * m01 + x;
+ vertices[Y2] = offset[X2] * m10 + offset[Y2] * m11 + y;
+ vertices[X3] = offset[X3] * m00 + offset[Y3] * m01 + x;
+ vertices[Y3] = offset[X3] * m10 + offset[Y3] * m11 + y;
+ vertices[X4] = offset[X4] * m00 + offset[Y4] * m01 + x;
+ vertices[Y4] = offset[X4] * m10 + offset[Y4] * m11 + y;
+ }
+ }
+}
diff --git a/spine-csharp/src/BaseAtlas.cs b/spine-csharp/src/BaseAtlas.cs
new file mode 100644
index 000000000..5ebc4ce1f
--- /dev/null
+++ b/spine-csharp/src/BaseAtlas.cs
@@ -0,0 +1,177 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Spine {
+ abstract public class BaseAtlas {
+ List pages = new List();
+ List regions = new List();
+
+ abstract protected AtlasPage NewAtlasPage (String path);
+
+ public void load (StreamReader reader, String imagesDir) {
+ String[] tuple = new String[4];
+ AtlasPage page = null;
+ while (true) {
+ String line = reader.ReadLine();
+ if (line == null) break;
+ if (line.Trim().Length == 0)
+ page = null;
+ else if (page == null) {
+ page = NewAtlasPage(Path.Combine(imagesDir, line));
+
+ page.Format = (Format)Enum.Parse(typeof(Format), readValue(reader), false);
+
+ readTuple(reader, tuple);
+ page.MinFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0]);
+ page.MagFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1]);
+
+ String direction = readValue(reader);
+ page.UWrap = TextureWrap.ClampToEdge;
+ page.VWrap = TextureWrap.ClampToEdge;
+ if (direction == "x")
+ page.UWrap = TextureWrap.Repeat;
+ else if (direction == "y")
+ page.VWrap = TextureWrap.Repeat;
+ else if (direction == "xy")
+ page.UWrap = page.VWrap = TextureWrap.Repeat;
+
+ pages.Add(page);
+
+ } else {
+ AtlasRegion region = new AtlasRegion();
+ region.Name = line;
+ region.Page = page;
+
+ region.Rotate = Boolean.Parse(readValue(reader));
+
+ readTuple(reader, tuple);
+ int x = int.Parse(tuple[0]);
+ int y = int.Parse(tuple[1]);
+
+ readTuple(reader, tuple);
+ int width = int.Parse(tuple[0]);
+ int height = int.Parse(tuple[1]);
+
+ float invTexWidth = 1f / page.GetTextureWidth();
+ float invTexHeight = 1f / page.GetTextureHeight();
+ region.U = x * invTexWidth;
+ region.V = y * invTexHeight;
+ region.U2 = (x + width) * invTexWidth;
+ region.V2 = (y + height) * invTexHeight;
+ region.Width = Math.Abs(width);
+ region.Height = Math.Abs(height);
+
+ if (readTuple(reader, tuple) == 4) { // split is optional
+ region.Splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]),
+ int.Parse(tuple[2]), int.Parse(tuple[3])};
+
+ if (readTuple(reader, tuple) == 4) { // pad is optional, but only present with splits
+ region.Pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]),
+ int.Parse(tuple[2]), int.Parse(tuple[3])};
+
+ readTuple(reader, tuple);
+ }
+ }
+
+ region.OriginalWidth = int.Parse(tuple[0]);
+ region.OriginalHeight = int.Parse(tuple[1]);
+
+ readTuple(reader, tuple);
+ region.OffsetX = int.Parse(tuple[0]);
+ region.OffsetY = int.Parse(tuple[1]);
+
+ region.Index = int.Parse(readValue(reader));
+
+ regions.Add(region);
+ }
+ }
+ }
+
+ static String readValue (StreamReader reader) {
+ String line = reader.ReadLine();
+ int colon = line.IndexOf(':');
+ if (colon == -1) throw new Exception("Invalid line: " + line);
+ return line.Substring(colon + 1).Trim();
+ }
+
+ /** Returns the number of tuple values read (2 or 4). */
+ static int readTuple (StreamReader reader, String[] tuple) {
+ String line = reader.ReadLine();
+ int colon = line.IndexOf(':');
+ if (colon == -1) throw new Exception("Invalid line: " + line);
+ int i = 0, lastMatch = colon + 1;
+ for (i = 0; i < 3; i++) {
+ int comma = line.IndexOf(',', lastMatch);
+ if (comma == -1) {
+ if (i == 0) throw new Exception("Invalid line: " + line);
+ break;
+ }
+ tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim();
+ lastMatch = comma + 1;
+ }
+ tuple[i] = line.Substring(lastMatch).Trim();
+ return i + 1;
+ }
+
+ /** Returns the first region found with the specified name. This method uses string comparison to find the region, so the result
+ * should be cached rather than calling this method multiple times.
+ * @return The region, or null. */
+ public AtlasRegion FindRegion (String name) {
+ for (int i = 0, n = regions.Count; i < n; i++)
+ if (regions[i].Name == name) return regions[i];
+ return null;
+ }
+ }
+
+ public enum Format {
+ Alpha,
+ Intensity,
+ LuminanceAlpha,
+ RGB565,
+ RGBA4444,
+ RGB888,
+ RGBA8888
+ }
+
+ public enum TextureFilter {
+ Nearest,
+ Linear,
+ MipMap,
+ MipMapNearestNearest,
+ MipMapLinearNearest,
+ MipMapNearestLinear,
+ MipMapLinearLinear
+ }
+
+ public enum TextureWrap {
+ MirroredRepeat,
+ ClampToEdge,
+ Repeat
+ }
+
+ abstract public class AtlasPage {
+ public Format Format;
+ public TextureFilter MinFilter;
+ public TextureFilter MagFilter;
+ public TextureWrap UWrap;
+ public TextureWrap VWrap;
+
+ abstract public int GetTextureWidth ();
+ abstract public int GetTextureHeight ();
+ }
+
+ public class AtlasRegion {
+ public AtlasPage Page;
+ public float U, V;
+ public float U2, V2;
+ public int Width, Height;
+ public int Index;
+ public String Name;
+ public float OffsetX, OffsetY;
+ public int OriginalWidth, OriginalHeight;
+ public bool Rotate;
+ public int[] Splits;
+ public int[] Pads;
+ }
+}
diff --git a/spine-csharp/src/Bone.cs b/spine-csharp/src/Bone.cs
new file mode 100644
index 000000000..7bd04fe26
--- /dev/null
+++ b/spine-csharp/src/Bone.cs
@@ -0,0 +1,83 @@
+using System;
+
+namespace Spine {
+ public class Bone {
+ static public bool yDown;
+
+ public BoneData Data { get; private set; }
+ public Bone Parent { get; private set; }
+ public float X { get; set; }
+ public float Y { get; set; }
+ public float Rotation { get; set; }
+ public float ScaleX { get; set; }
+ public float ScaleY { get; set; }
+
+ public float M00 { get; private set; }
+ public float M01 { get; private set; }
+ public float M10 { get; private set; }
+ public float M11 { get; private set; }
+ public float WorldX { get; private set; }
+ public float WorldY { get; private set; }
+ public float WorldRotation { get; private set; }
+ public float WorldScaleX { get; private set; }
+ public float WorldScaleY { get; private set; }
+
+ /** @param parent May be null. */
+ public Bone (BoneData data, Bone parent) {
+ if (data == null) throw new ArgumentNullException("data cannot be null.");
+ Data = data;
+ Parent = parent;
+ SetToBindPose();
+ }
+
+ /** Computes the world SRT using the parent bone and the local SRT. */
+ public void UpdateWorldTransform (bool flipX, bool flipY) {
+ Bone parent = Parent;
+ if (parent != null) {
+ WorldX = X * parent.M00 + Y * parent.M01 + parent.WorldX;
+ WorldY = X * parent.M10 + Y * parent.M11 + parent.WorldY;
+ WorldScaleX = parent.WorldScaleX * ScaleX;
+ WorldScaleY = parent.WorldScaleY * ScaleY;
+ WorldRotation = parent.WorldRotation + Rotation;
+ } else {
+ WorldX = X;
+ WorldY = Y;
+ WorldScaleX = ScaleX;
+ WorldScaleY = ScaleY;
+ WorldRotation = Rotation;
+ }
+ float radians = WorldRotation * (float)Math.PI / 180;
+ float cos = (float)Math.Cos(radians);
+ float sin = (float)Math.Sin(radians);
+ M00 = cos * WorldScaleX;
+ M10 = sin * WorldScaleX;
+ M01 = -sin * WorldScaleY;
+ M11 = cos * WorldScaleY;
+ if (flipX) {
+ M00 = -M00;
+ M01 = -M01;
+ }
+ if (flipY) {
+ M10 = -M10;
+ M11 = -M11;
+ }
+ if (yDown) {
+ M10 = -M10;
+ M11 = -M11;
+ }
+ }
+
+ public void SetToBindPose () {
+ BoneData data = Data;
+ X = data.X;
+ Y = data.Y;
+ Rotation = data.Rotation;
+ ScaleX = data.ScaleX;
+ ScaleY = data.ScaleY;
+ }
+
+ override public String ToString () {
+ return Data.Name;
+ }
+ }
+}
diff --git a/spine-csharp/src/BoneData.cs b/spine-csharp/src/BoneData.cs
new file mode 100644
index 000000000..f3a4add51
--- /dev/null
+++ b/spine-csharp/src/BoneData.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace Spine {
+ public class BoneData {
+ /** May be null. */
+ public BoneData Parent { get; private set; }
+ public String Name { get; private set; }
+ public float Length { get; set; }
+ public float X { get; set; }
+ public float Y { get; set; }
+ public float Rotation { get; set; }
+ public float ScaleX { get; set; }
+ public float ScaleY { get; set; }
+
+ /** @param parent May be null. */
+ public BoneData (String name, BoneData parent) {
+ if (name == null) throw new ArgumentNullException("name cannot be null.");
+ Name = name;
+ Parent = parent;
+ ScaleX = 1;
+ ScaleY = 1;
+ }
+
+ override public String ToString () {
+ return Name;
+ }
+ }
+}
diff --git a/spine-csharp/src/Json.cs b/spine-csharp/src/Json.cs
new file mode 100644
index 000000000..25b67be9d
--- /dev/null
+++ b/spine-csharp/src/Json.cs
@@ -0,0 +1,551 @@
+/*
+ * Copyright (c) 2012 Calvin Rien
+ *
+ * Based on the JSON parser by Patrick van Bergen
+ * http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html
+ *
+ * Simplified it so that it doesn't throw exceptions
+ * and can be used in Unity iPhone with maximum code stripping.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace Spine
+{
+ // Example usage:
+ //
+ // using UnityEngine;
+ // using System.Collections;
+ // using System.Collections.Generic;
+ // using MiniJSON;
+ //
+ // public class MiniJSONTest : MonoBehaviour {
+ // void Start () {
+ // var jsonString = "{ \"array\": [1.44,2,3], " +
+ // "\"object\": {\"key1\":\"value1\", \"key2\":256}, " +
+ // "\"string\": \"The quick brown fox \\\"jumps\\\" over the lazy dog \", " +
+ // "\"unicode\": \"\\u3041 Men\u00fa sesi\u00f3n\", " +
+ // "\"int\": 65536, " +
+ // "\"float\": 3.1415926, " +
+ // "\"bool\": true, " +
+ // "\"null\": null }";
+ //
+ // var dict = Json.Deserialize(jsonString) as Dictionary;
+ //
+ // Debug.Log("deserialized: " + dict.GetType());
+ // Debug.Log("dict['array'][0]: " + ((List