diff --git a/spine-csharp/spine-csharp_xna.csproj b/spine-csharp/spine-csharp_xna.csproj
index e7c596053..373273e0d 100644
--- a/spine-csharp/spine-csharp_xna.csproj
+++ b/spine-csharp/spine-csharp_xna.csproj
@@ -101,11 +101,13 @@
+
+
diff --git a/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs b/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs
index 5af5e897e..9fca7fa24 100644
--- a/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs
+++ b/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs
@@ -49,6 +49,8 @@ namespace Spine {
attachment.RegionOriginalWidth = region.originalWidth;
attachment.RegionOriginalHeight = region.originalHeight;
return attachment;
+ case AttachmentType.boundingbox:
+ return new BoundingBoxAttachment(name);
}
throw new Exception("Unknown attachment type: " + type);
}
diff --git a/spine-csharp/src/Attachments/AttachmentType.cs b/spine-csharp/src/Attachments/AttachmentType.cs
index 1d768a9aa..824e7ab9f 100644
--- a/spine-csharp/src/Attachments/AttachmentType.cs
+++ b/spine-csharp/src/Attachments/AttachmentType.cs
@@ -25,6 +25,6 @@
namespace Spine {
public enum AttachmentType {
- region, regionSequence
+ region, regionsequence, boundingbox
}
}
diff --git a/spine-csharp/src/Attachments/BoundingBoxAttachment.cs b/spine-csharp/src/Attachments/BoundingBoxAttachment.cs
new file mode 100644
index 000000000..8e87f7da0
--- /dev/null
+++ b/spine-csharp/src/Attachments/BoundingBoxAttachment.cs
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2013, Esoteric Software
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 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 THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ******************************************************************************/
+
+using System;
+
+namespace Spine {
+ /** Attachment that has a polygon for bounds checking. */
+ public class BoundingBoxAttachment : Attachment {
+ public float[] Vertices { get; set; }
+
+ public BoundingBoxAttachment (string name)
+ : base(name) {
+ }
+
+ /** @param worldVertices Must have at least the same as this attachment's vertices. */
+ public void ComputeWorldVertices (float x, float y, Bone bone, float[] worldVertices) {
+ x += bone.WorldX;
+ y += bone.WorldY;
+ float m00 = bone.M00;
+ float m01 = bone.M01;
+ float m10 = bone.M10;
+ float m11 = bone.M11;
+ float[] vertices = Vertices;
+ for (int i = 0, n = vertices.Length; i < n; i += 2) {
+ float px = vertices[i];
+ float py = vertices[i + 1];
+ worldVertices[i] = px * m00 + py * m01 + x;
+ worldVertices[i + 1] = px * m10 + py * m11 + y;
+ }
+ }
+ }
+}
diff --git a/spine-csharp/src/SkeletonBounds.cs b/spine-csharp/src/SkeletonBounds.cs
new file mode 100644
index 000000000..e1f7cd495
--- /dev/null
+++ b/spine-csharp/src/SkeletonBounds.cs
@@ -0,0 +1,275 @@
+/*******************************************************************************
+ * Copyright (c) 2013, Esoteric Software
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 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 THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ******************************************************************************/
+
+using System;
+using System.Collections.Generic;
+
+namespace Spine {
+ public class SkeletonBounds {
+ private bool aabb;
+ private List polygonPool = new List();
+
+ public List BoundingBoxes { get; private set; }
+ public List Polygons { get; private set; }
+
+ private float minX;
+ public float MinX {
+ get {
+ if (!aabb) aabbCompute();
+ return minX;
+ }
+ private set {
+ minX = value;
+ }
+ }
+
+ private float maxX;
+ public float MaxX {
+ get {
+ if (!aabb) aabbCompute();
+ return maxX;
+ }
+ private set {
+ maxX = value;
+ }
+ }
+
+ private float minY;
+ public float MinY {
+ get {
+ if (!aabb) aabbCompute();
+ return minY;
+ }
+ private set {
+ minY = value;
+ }
+ }
+
+ private float maxY;
+ public float MaxY {
+ get {
+ if (!aabb) aabbCompute();
+ return maxY;
+ }
+ private set {
+ maxY = value;
+ }
+ }
+
+ public float Width {
+ get {
+ if (!aabb) aabbCompute();
+ return maxX - minX;
+ }
+ }
+
+ public float Height {
+ get {
+ if (!aabb) aabbCompute();
+ return maxY - minY;
+ }
+ }
+
+ public SkeletonBounds () {
+ BoundingBoxes = new List();
+ Polygons = new List();
+ }
+
+ public void Update (Skeleton skeleton) {
+ aabb = false;
+
+ List boundingBoxes = BoundingBoxes;
+ List polygons = Polygons;
+ List slots = skeleton.Slots;
+ int slotCount = slots.Count;
+ float x = skeleton.X, y = skeleton.Y;
+
+ boundingBoxes.Clear();
+ foreach (Polygon polygon in polygons) {
+ polygonPool.Add(polygon);
+ }
+ polygons.Clear();
+
+ for (int i = 0; i < slotCount; i++) {
+ Slot slot = slots[i];
+ BoundingBoxAttachment boundingBox = slot.Attachment as BoundingBoxAttachment;
+ if (boundingBox == null) continue;
+ boundingBoxes.Add(boundingBox);
+
+ Polygon polygon = null;
+ int poolCount = polygonPool.Count;
+ if (poolCount > 0) {
+ polygon = polygonPool[poolCount - 1];
+ polygonPool.RemoveAt(poolCount - 1);
+ } else
+ polygon = new Polygon();
+ polygons.Add(polygon);
+ polygon.Count = boundingBox.Vertices.Length;
+ if (polygon.Vertices == null || polygon.Vertices.Length < polygon.Count) polygon.Vertices = new float[polygon.Count];
+ boundingBox.ComputeWorldVertices(x, y, slot.Bone, polygon.Vertices);
+ }
+ }
+
+ private void aabbCompute () {
+ float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
+ List polygons = Polygons;
+ for (int i = 0, n = polygons.Count; i < n; i++) {
+ Polygon polygon = polygons[i];
+ float[] vertices = polygon.Vertices;
+ for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) {
+ float x = vertices[ii];
+ float y = vertices[ii + 1];
+ minX = Math.Min(minX, x);
+ minY = Math.Min(minY, y);
+ maxX = Math.Max(maxX, x);
+ maxY = Math.Max(maxY, y);
+ }
+ }
+ this.minX = minX;
+ this.minY = minY;
+ this.maxX = maxX;
+ this.maxY = maxY;
+ aabb = true;
+ }
+
+
+ /** Returns true if the axis aligned bounding box contains the point. */
+ public bool AabbContainsPoint (float x, float y) {
+ if (!aabb) aabbCompute();
+ return x >= minX && x <= maxX && y >= minY && y <= maxY;
+ }
+
+ /** Returns true if the axis aligned bounding box intersects the line segment. */
+ public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) {
+ if (!aabb) aabbCompute();
+ float minX = this.minX;
+ float minY = this.minY;
+ float maxX = this.maxX;
+ float maxY = this.maxY;
+ if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY))
+ return false;
+ float m = (y2 - y1) / (x2 - x1);
+ float y = m * (minX - x1) + y1;
+ if (y > minY && y < maxY) return true;
+ y = m * (maxX - x1) + y1;
+ if (y > minY && y < maxY) return true;
+ float x = (minY - y1) / m + x1;
+ if (x > minX && x < maxX) return true;
+ x = (maxY - y1) / m + x1;
+ if (x > minX && x < maxX) return true;
+ return false;
+ }
+
+ /** Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. */
+ public bool AabbIntersectsSkeleton (SkeletonBounds bounds) {
+ if (!aabb) aabbCompute();
+ if (!bounds.aabb) bounds.aabbCompute();
+ return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY;
+ }
+
+ /** Returns true if the bounding box attachment contains the point. */
+ public bool ContainsPoint (int index, float x, float y) {
+ Polygon polygon = Polygons[index];
+ float[] vertices = polygon.Vertices;
+ int nn = polygon.Count;
+
+ int prevIndex = nn - 2;
+ bool inside = false;
+ for (int ii = 0; ii < nn; ii += 2) {
+ float vertexY = vertices[ii + 1];
+ float prevY = vertices[prevIndex + 1];
+ if (vertexY < y && prevY >= y || prevY < y && vertexY >= y) {
+ float vertexX = vertices[ii];
+ if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) inside = !inside;
+ }
+ prevIndex = ii;
+ }
+ return inside;
+ }
+
+ /** Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more
+ * efficient to only call this method if {@link #aabbContainsPoint(float, float)} return true. */
+ public BoundingBoxAttachment ContainsPoint (float x, float y) {
+ List boundingBoxes = BoundingBoxes;
+ for (int i = 0, n = boundingBoxes.Count; i < n; i++)
+ if (ContainsPoint(i, x, y)) return boundingBoxes[i];
+ return null;
+ }
+
+ /** Returns true if the bounding box attachment contains the point. The bounding box must be in the SkeletonBounds. */
+ public bool containsPoint (BoundingBoxAttachment attachment, float x, float y) {
+ int index = BoundingBoxes.IndexOf(attachment);
+ return index == -1 ? false : ContainsPoint(index, x, y);
+ }
+
+ /** Returns the first bounding box attachment that contains the line segment, or null. When doing many checks, it is usually
+ * more efficient to only call this method if {@link #aabbIntersectsSegment(float, float, float, float)} return true. */
+ public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) {
+ List boundingBoxes = BoundingBoxes;
+ for (int i = 0, n = boundingBoxes.Count; i < n; i++) {
+ BoundingBoxAttachment attachment = boundingBoxes[i];
+ if (IntersectsSegment(attachment, x1, y1, x2, y2)) return attachment;
+ }
+ return null;
+ }
+
+ /** Returns true if the bounding box attachment contains the line segment. */
+ public bool IntersectsSegment (BoundingBoxAttachment attachment, float x1, float y1, float x2, float y2) {
+ int index = BoundingBoxes.IndexOf(attachment);
+ return index == -1 ? false : IntersectsSegment(index, x1, y1, x2, y2);
+ }
+
+ /** Returns true if the bounding box attachment contains the line segment. */
+ public bool IntersectsSegment (int index, float x1, float y1, float x2, float y2) {
+ Polygon polygon = Polygons[index];
+ float[] vertices = polygon.Vertices;
+ int nn = polygon.Count;
+
+ float width12 = x1 - x2, height12 = y1 - y2;
+ float det1 = x1 * y2 - y1 * x2;
+ float x3 = vertices[nn - 2], y3 = vertices[nn - 1];
+ for (int ii = 0; ii < nn; ii += 2) {
+ float x4 = vertices[ii], y4 = vertices[ii + 1];
+ float det2 = x3 * y4 - y3 * x4;
+ float width34 = x3 - x4, height34 = y3 - y4;
+ float det3 = width12 * height34 - height12 * width34;
+ float x = (det1 * width34 - width12 * det2) / det3;
+ if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) {
+ float y = (det1 * height34 - height12 * det2) / det3;
+ if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) return true;
+ }
+ x3 = x4;
+ y3 = y4;
+
+ }
+ return false;
+ }
+ }
+}
+
+public class Polygon {
+ public float[] Vertices { get; set; }
+ public int Count { get; set; }
+}
diff --git a/spine-csharp/src/SkeletonJson.cs b/spine-csharp/src/SkeletonJson.cs
index c24f2faf1..c8f50da6d 100644
--- a/spine-csharp/src/SkeletonJson.cs
+++ b/spine-csharp/src/SkeletonJson.cs
@@ -21,39 +21,40 @@
* 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 THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- ******************************************************************************/
-
-using System;
-using System.IO;
-using System.Collections.Generic;
-
+ ******************************************************************************/
+
+using System;
+using System.IO;
+using System.Collections.Generic;
+
#if WINDOWS_STOREAPP
using System.Threading.Tasks;
using Windows.Storage;
-#endif
-
+#endif
+
namespace Spine {
- public class SkeletonJson {
- static public String TIMELINE_SCALE = "scale";
- static public String TIMELINE_ROTATE = "rotate";
- static public String TIMELINE_TRANSLATE = "translate";
- static public String TIMELINE_ATTACHMENT = "attachment";
- static public String TIMELINE_COLOR = "color";
-
- static public String ATTACHMENT_REGION = "region";
- static public String ATTACHMENT_REGION_SEQUENCE = "regionSequence";
-
- private AttachmentLoader attachmentLoader;
- public float Scale { get; set; }
-
- public SkeletonJson (Atlas atlas) : this(new AtlasAttachmentLoader(atlas)) {
- }
-
- public SkeletonJson (AttachmentLoader attachmentLoader) {
- if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null.");
- this.attachmentLoader = attachmentLoader;
- Scale = 1;
- }
+ public class SkeletonJson {
+ static public String TIMELINE_SCALE = "scale";
+ static public String TIMELINE_ROTATE = "rotate";
+ static public String TIMELINE_TRANSLATE = "translate";
+ static public String TIMELINE_ATTACHMENT = "attachment";
+ static public String TIMELINE_COLOR = "color";
+
+ static public String ATTACHMENT_REGION = "region";
+ static public String ATTACHMENT_REGION_SEQUENCE = "regionSequence";
+
+ private AttachmentLoader attachmentLoader;
+ public float Scale { get; set; }
+
+ public SkeletonJson (Atlas atlas)
+ : this(new AtlasAttachmentLoader(atlas)) {
+ }
+
+ public SkeletonJson (AttachmentLoader attachmentLoader) {
+ if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null.");
+ this.attachmentLoader = attachmentLoader;
+ Scale = 1;
+ }
#if WINDOWS_STOREAPP
private async Task ReadFile(string path) {
@@ -69,248 +70,257 @@ namespace Spine {
public SkeletonData ReadSkeletonData (String path) {
return this.ReadFile(path).Result;
}
-#else
- public SkeletonData ReadSkeletonData (String path) {
- using (StreamReader reader = new StreamReader(path)) {
- SkeletonData skeletonData = ReadSkeletonData(reader);
- skeletonData.Name = Path.GetFileNameWithoutExtension(path);
- return skeletonData;
- }
- }
-#endif
-
- public SkeletonData ReadSkeletonData (TextReader reader) {
- if (reader == null) throw new ArgumentNullException("reader cannot be null.");
-
- SkeletonData skeletonData = new SkeletonData();
-
- var root = Json.Deserialize(reader) as Dictionary;
- if (root == null) throw new Exception("Invalid JSON.");
-
- // Bones.
- foreach (Dictionary boneMap in (List