Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File renamed without changes.

Large diffs are not rendered by default.

File renamed without changes.

Large diffs are not rendered by default.

File renamed without changes.
@@ -32,28 +32,36 @@
using System.Collections.Generic;

namespace Spine {

/// <summary>Stores mix (crossfade) durations to be applied when AnimationState animations are changed.</summary>
public class AnimationStateData {
internal SkeletonData skeletonData;

readonly Dictionary<AnimationPair, float> animationToMixTime = new Dictionary<AnimationPair, float>(AnimationPairComparer.Instance);
internal float defaultMix;

/// <summary>The SkeletonData to look up animations when they are specified by name.</summary>
public SkeletonData SkeletonData { get { return skeletonData; } }

/// <summary>
/// The mix duration to use when no mix duration has been specifically defined between two animations.</summary>
public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } }

public AnimationStateData (SkeletonData skeletonData) {
if (skeletonData == null) throw new ArgumentException ("skeletonData cannot be null.");
this.skeletonData = skeletonData;
}

public void SetMix (String fromName, String toName, float duration) {
/// <summary>Sets a mix duration by animation names.</summary>
public void SetMix (string fromName, string toName, float duration) {
Animation from = skeletonData.FindAnimation(fromName);
if (from == null) throw new ArgumentException("Animation not found: " + fromName);
Animation to = skeletonData.FindAnimation(toName);
if (to == null) throw new ArgumentException("Animation not found: " + toName);
SetMix(from, to, duration);
}

/// <summary>Sets a mix duration when changing from the specified animation to the other.
/// See TrackEntry.MixDuration.</summary>
public void SetMix (Animation from, Animation to, float duration) {
if (from == null) throw new ArgumentNullException("from", "from cannot be null.");
if (to == null) throw new ArgumentNullException("to", "to cannot be null.");
@@ -62,6 +70,10 @@ public class AnimationStateData {
animationToMixTime.Add(key, duration);
}

/// <summary>
/// The mix duration to use when changing from the specified animation to the other,
/// or the DefaultMix if no mix duration has been set.
/// </summary>
public float GetMix (Animation from, Animation to) {
if (from == null) throw new ArgumentNullException("from", "from cannot be null.");
if (to == null) throw new ArgumentNullException("to", "to cannot be null.");
File renamed without changes.
@@ -28,6 +28,10 @@
* 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;
@@ -40,11 +44,11 @@

namespace Spine {
public class Atlas {
List<AtlasPage> pages = new List<AtlasPage>();
readonly List<AtlasPage> pages = new List<AtlasPage>();
List<AtlasRegion> regions = new List<AtlasRegion>();
TextureLoader textureLoader;

#if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) // !UNITY
#if !(IS_UNITY)
#if WINDOWS_STOREAPP
private async Task ReadFile(string path, TextureLoader textureLoader) {
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
@@ -58,12 +62,12 @@ public class Atlas {
}
}

public Atlas(String path, TextureLoader textureLoader) {
public Atlas(string path, TextureLoader textureLoader) {
this.ReadFile(path, textureLoader).Wait();
}
#else

public Atlas (String path, TextureLoader textureLoader) {
public Atlas (string path, TextureLoader textureLoader) {

#if WINDOWS_PHONE
Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path);
@@ -82,9 +86,9 @@ public class Atlas {
}
#endif // WINDOWS_STOREAPP

#endif // !(UNITY)
#endif

public Atlas (TextReader reader, String dir, TextureLoader textureLoader) {
public Atlas (TextReader reader, string dir, TextureLoader textureLoader) {
Load(reader, dir, textureLoader);
}

@@ -94,14 +98,14 @@ public class Atlas {
this.textureLoader = null;
}

private void Load (TextReader reader, String imagesDir, TextureLoader textureLoader) {
private void Load (TextReader reader, string imagesDir, TextureLoader textureLoader) {
if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null.");
this.textureLoader = textureLoader;

String[] tuple = new String[4];
string[] tuple = new string[4];
AtlasPage page = null;
while (true) {
String line = reader.ReadLine();
string line = reader.ReadLine();
if (line == null) break;
if (line.Trim().Length == 0)
page = null;
@@ -120,7 +124,7 @@ public class Atlas {
page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false);
page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false);

String direction = ReadValue(reader);
string direction = ReadValue(reader);
page.uWrap = TextureWrap.ClampToEdge;
page.vWrap = TextureWrap.ClampToEdge;
if (direction == "x")
@@ -189,16 +193,16 @@ public class Atlas {
}
}

static String ReadValue (TextReader reader) {
String line = reader.ReadLine();
static string ReadValue (TextReader reader) {
string line = reader.ReadLine();
int colon = line.IndexOf(':');
if (colon == -1) throw new Exception("Invalid line: " + line);
return line.Substring(colon + 1).Trim();
}

/// <summary>Returns the number of tuple values read (1, 2 or 4).</summary>
static int ReadTuple (TextReader reader, String[] tuple) {
String line = reader.ReadLine();
static int ReadTuple (TextReader 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;
@@ -223,7 +227,7 @@ public class Atlas {
/// <summary>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.</summary>
/// <returns>The region, or null.</returns>
public AtlasRegion FindRegion (String name) {
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;
@@ -263,7 +267,7 @@ public enum TextureWrap {
}

public class AtlasPage {
public String name;
public string name;
public Format format;
public TextureFilter minFilter;
public TextureFilter magFilter;
@@ -275,7 +279,7 @@ public class AtlasPage {

public class AtlasRegion {
public AtlasPage page;
public String name;
public string name;
public int x, y, width, height;
public float u, v, u2, v2;
public float offsetX, offsetY;
@@ -287,7 +291,7 @@ public class AtlasRegion {
}

public interface TextureLoader {
void Load (AtlasPage page, String path);
void Load (AtlasPage page, string path);
void Unload (Object texture);
}
}
File renamed without changes.
File renamed without changes.
@@ -31,6 +31,11 @@
using System;

namespace Spine {

/// <summary>
/// An AttachmentLoader that configures attachments using texture regions from an Atlas.
/// See <a href='http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data'>Loading Skeleton Data</a> in the Spine Runtimes Guide.
/// </summary>
public class AtlasAttachmentLoader : AttachmentLoader {
private Atlas[] atlasArray;

@@ -39,9 +44,9 @@ public class AtlasAttachmentLoader : AttachmentLoader {
this.atlasArray = atlasArray;
}

public RegionAttachment NewRegionAttachment (Skin skin, String name, String path) {
public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) {
AtlasRegion region = FindRegion(path);
if (region == null) throw new Exception("Region not found in atlas: " + path + " (region attachment: " + name + ")");
if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name));
RegionAttachment attachment = new RegionAttachment(name);
attachment.RendererObject = region;
attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate);
@@ -54,9 +59,9 @@ public class AtlasAttachmentLoader : AttachmentLoader {
return attachment;
}

public MeshAttachment NewMeshAttachment (Skin skin, String name, String path) {
public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) {
AtlasRegion region = FindRegion(path);
if (region == null) throw new Exception("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name));
MeshAttachment attachment = new MeshAttachment(name);
attachment.RendererObject = region;
attachment.RegionU = region.u;
@@ -73,12 +78,20 @@ public class AtlasAttachmentLoader : AttachmentLoader {
return attachment;
}

public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name) {
public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) {
return new BoundingBoxAttachment(name);
}

public PathAttachment NewPathAttachment (Skin skin, String name) {
return new PathAttachment (name);
public PathAttachment NewPathAttachment (Skin skin, string name) {
return new PathAttachment(name);
}

public PointAttachment NewPointAttachment (Skin skin, string name) {
return new PointAttachment(name);
}

public ClippingAttachment NewClippingAttachment(Skin skin, string name) {
return new ClippingAttachment(name);
}

public AtlasRegion FindRegion (string name) {
File renamed without changes.
@@ -32,14 +32,14 @@

namespace Spine {
abstract public class Attachment {
public String Name { get; private set; }
public string Name { get; private set; }

public Attachment (String name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null");
Name = name;
}

override public String ToString () {
override public string ToString () {
return Name;
}
}
File renamed without changes.
@@ -28,20 +28,22 @@
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/

using System;

namespace Spine {
public interface AttachmentLoader {
/// <return>May be null to not load any attachment.</return>
RegionAttachment NewRegionAttachment (Skin skin, String name, String path);
RegionAttachment NewRegionAttachment (Skin skin, string name, string path);

/// <return>May be null to not load any attachment.</return>
MeshAttachment NewMeshAttachment (Skin skin, String name, String path);
MeshAttachment NewMeshAttachment (Skin skin, string name, string path);

/// <return>May be null to not load any attachment.</return>
BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name);
BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name);

/// <returns>May be null to not load any attachment</returns>
PathAttachment NewPathAttachment (Skin skin, String name);
PathAttachment NewPathAttachment (Skin skin, string name);

PointAttachment NewPointAttachment (Skin skin, string name);

ClippingAttachment NewClippingAttachment (Skin skin, string name);
}
}
File renamed without changes.
@@ -30,6 +30,6 @@

namespace Spine {
public enum AttachmentType {
Region, Boundingbox, Mesh, Linkedmesh, Path
Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,4 +1,4 @@
/******************************************************************************
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
@@ -28,20 +28,15 @@
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/

namespace Spine.Unity.MeshGeneration {
// Typically, each ISpineMeshGenerator implementation will handle double-buffering meshes, handling any other optimization behavior
// and operating on assumptions (eg, only handling one skeleton, not updating triangles all the time).
// The Scale property allows generated mesh to match external systems like Canvas referencePixelsPerUnit
using System;

public interface ISimpleMeshGenerator {
UnityEngine.Mesh GenerateMesh (Spine.Skeleton skeleton);
UnityEngine.Mesh LastGeneratedMesh { get; }
namespace Spine {
public class ClippingAttachment : VertexAttachment {
internal SlotData endSlot;

float Scale { set; }
float ZSpacing { get; set; }
bool PremultiplyVertexColors { get; set; }
public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } }

bool AddNormals { get; set; }
bool AddTangents { get; set; }
}
public ClippingAttachment(string name) : base(name) {
}
}
}
@@ -34,15 +34,16 @@ namespace Spine {
/// <summary>Attachment that displays a texture region using a mesh.</summary>
public class MeshAttachment : VertexAttachment {
internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight;
private MeshAttachment parentMesh;
internal float[] uvs, regionUVs;
internal int[] triangles;
internal float r = 1, g = 1, b = 1, a = 1;
internal int hulllength;
internal MeshAttachment parentMesh;
internal bool inheritDeform;

public int HullLength { get { return hulllength; } set { hulllength = value; } }
public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } }
/// <summary>The UV pair for each vertex, normalized within the entire texture. <seealso cref="MeshAttachment.UpdateUVs"/></summary>
public float[] UVs { get { return uvs; } set { uvs = value; } }
public int[] Triangles { get { return triangles; } set { triangles = value; } }

@@ -51,8 +52,8 @@ public class MeshAttachment : VertexAttachment {
public float B { get { return b; } set { b = value; } }
public float A { get { return a; } set { a = value; } }

public String Path { get; set; }
public Object RendererObject { get; set; }
public string Path { get; set; }
public object RendererObject; //public Object RendererObject { get; set; }
public float RegionU { get; set; }
public float RegionV { get; set; }
public float RegionU2 { get; set; }
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,61 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/

namespace Spine {
/// <summary>
/// An attachment which is a single point and a rotation. This can be used to spawn projectiles, particles, etc. A bone can be
/// used in similar ways, but a PointAttachment is slightly less expensive to compute and can be hidden, shown, and placed in a
/// skin.
/// <p>
/// See <a href="http://esotericsoftware.com/spine-point-attachments">Point Attachments</a> in the Spine User Guide.
/// </summary>
public class PointAttachment : Attachment {
internal float x, y, rotation;
public float X { get { return x; } set { x = value; } }
public float Y { get { return y; } set { y = value; } }
public float Rotation { get { return rotation; } set { rotation = value; } }

public PointAttachment (string name)
: base(name) {
}

public void ComputeWorldPosition (Bone bone, out float ox, out float oy) {
bone.LocalToWorld(this.x, this.y, out ox, out oy);
}

public float ComputeWorldRotation (Bone bone) {
float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation);
float ix = cos * bone.a + sin * bone.b;
float iy = cos * bone.c + sin * bone.d;
return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg;
}
}
}

@@ -33,14 +33,14 @@
namespace Spine {
/// <summary>Attachment that displays a texture region.</summary>
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 const int BLX = 0;
public const int BLY = 1;
public const int ULX = 2;
public const int ULY = 3;
public const int URX = 4;
public const int URY = 5;
public const int BRX = 6;
public const int BRY = 7;

internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height;
internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight;
@@ -60,8 +60,8 @@ public class RegionAttachment : Attachment {
public float B { get { return b; } set { b = value; } }
public float A { get { return a; } set { a = value; } }

public String Path { get; set; }
public Object RendererObject { get; set; }
public string Path { get; set; }
public object RendererObject; //public object RendererObject { get; set; }
public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } }
public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated.
public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } }
@@ -76,29 +76,6 @@ public RegionAttachment (string name)
: base(name) {
}

public void SetUVs (float u, float v, float u2, float v2, bool rotate) {
float[] uvs = this.uvs;
if (rotate) {
uvs[X2] = u;
uvs[Y2] = v2;
uvs[X3] = u;
uvs[Y3] = v;
uvs[X4] = u2;
uvs[Y4] = v;
uvs[X1] = u2;
uvs[Y1] = v2;
} else {
uvs[X1] = u;
uvs[Y1] = v2;
uvs[X2] = u;
uvs[Y2] = v;
uvs[X3] = u2;
uvs[Y3] = v;
uvs[X4] = u2;
uvs[Y4] = v2;
}
}

public void UpdateOffset () {
float width = this.width;
float height = this.height;
@@ -124,28 +101,75 @@ public RegionAttachment (string name)
float localY2Cos = localY2 * cos + y;
float localY2Sin = localY2 * sin;
float[] offset = this.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;
offset[BLX] = localXCos - localYSin;
offset[BLY] = localYCos + localXSin;
offset[ULX] = localXCos - localY2Sin;
offset[ULY] = localY2Cos + localXSin;
offset[URX] = localX2Cos - localY2Sin;
offset[URY] = localY2Cos + localX2Sin;
offset[BRX] = localX2Cos - localYSin;
offset[BRY] = localYCos + localX2Sin;
}

public void ComputeWorldVertices (Bone bone, float[] worldVertices) {
float x = bone.worldX, y = bone.worldY;
public void SetUVs (float u, float v, float u2, float v2, bool rotate) {
float[] uvs = this.uvs;
// UV values differ from RegionAttachment.java
if (rotate) {
uvs[URX] = u;
uvs[URY] = v2;
uvs[BRX] = u;
uvs[BRY] = v;
uvs[BLX] = u2;
uvs[BLY] = v;
uvs[ULX] = u2;
uvs[ULY] = v2;
} else {
uvs[ULX] = u;
uvs[ULY] = v2;
uvs[URX] = u;
uvs[URY] = v;
uvs[BRX] = u2;
uvs[BRY] = v;
uvs[BLX] = u2;
uvs[BLY] = v2;
}
}

/// <summary>Transforms the attachment's four vertices to world coordinates.</summary>
/// <param name="bone">The parent bone.</param>
/// <param name="worldVertices">The output world vertices. Must have a length greater than or equal to offset + 8.</param>
/// <param name="offset">The worldVertices index to begin writing values.</param>
/// <param name="stride">The number of worldVertices entries between the value pairs written.</param>
public void ComputeWorldVertices (Bone bone, float[] worldVertices, int offset, int stride = 2) {
float[] vertexOffset = this.offset;
float bwx = bone.worldX, bwy = bone.worldY;
float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
float[] offset = this.offset;
worldVertices[X1] = offset[X1] * a + offset[Y1] * b + x;
worldVertices[Y1] = offset[X1] * c + offset[Y1] * d + y;
worldVertices[X2] = offset[X2] * a + offset[Y2] * b + x;
worldVertices[Y2] = offset[X2] * c + offset[Y2] * d + y;
worldVertices[X3] = offset[X3] * a + offset[Y3] * b + x;
worldVertices[Y3] = offset[X3] * c + offset[Y3] * d + y;
worldVertices[X4] = offset[X4] * a + offset[Y4] * b + x;
worldVertices[Y4] = offset[X4] * c + offset[Y4] * d + y;
float offsetX, offsetY;

// Vertex order is different from RegionAttachment.java
offsetX = vertexOffset[BRX]; // 0
offsetY = vertexOffset[BRY]; // 1
worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl
worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
offset += stride;

offsetX = vertexOffset[BLX]; // 2
offsetY = vertexOffset[BLY]; // 3
worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul
worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
offset += stride;

offsetX = vertexOffset[ULX]; // 4
offsetY = vertexOffset[ULY]; // 5
worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur
worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
offset += stride;

offsetX = vertexOffset[URX]; // 6
offsetY = vertexOffset[URY]; // 7
worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br
worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
//offset += stride;
}
}
}
File renamed without changes.
@@ -29,21 +29,30 @@
*****************************************************************************/

using System;
using System.Collections.Generic;

namespace Spine {
/// <summary>>An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices.</summary>
public class VertexAttachment : Attachment {
static int nextID = 0;
static readonly Object nextIdLock = new Object();

internal readonly int id;
internal int[] bones;
internal float[] vertices;
internal int worldVerticesLength;

/// <summary>Gets a unique ID for this attachment.</summary>
public int Id { get { return id; } }
public int[] Bones { get { return bones; } set { bones = value; } }
public float[] Vertices { get { return vertices; } set { vertices = value; } }
public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } }

public VertexAttachment (String name)
public VertexAttachment (string name)
: base(name) {

lock (VertexAttachment.nextIdLock) {
id = (VertexAttachment.nextID++ & 65535) << 11;
}
}

public void ComputeWorldVertices (Slot slot, float[] worldVertices) {
@@ -55,9 +64,10 @@ public VertexAttachment (String name)
/// <param name="count">The number of world vertex values to output. Must be less than or equal to <see cref="WorldVerticesLength"/> - start.</param>
/// <param name="worldVertices">The output world vertices. Must have a length greater than or equal to <paramref name="offset"/> + <paramref name="count"/>.</param>
/// <param name="offset">The <paramref name="worldVertices"/> index to begin writing values.</param>
public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset) {
count += offset;
Skeleton skeleton = slot.Skeleton;
/// <param name="stride">The number of <paramref name="worldVertices"/> entries between the value pairs written.</param>
public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) {
count = offset + (count >> 1) * stride;
Skeleton skeleton = slot.bone.skeleton;
var deformArray = slot.attachmentVertices;
float[] vertices = this.vertices;
int[] bones = this.bones;
@@ -66,7 +76,7 @@ public VertexAttachment (String name)
Bone bone = slot.bone;
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 += 2) {
for (int vv = start, w = offset; w < count; vv += 2, w += stride) {
float vx = vertices[vv], vy = vertices[vv + 1];
worldVertices[w] = vx * a + vy * b + x;
worldVertices[w + 1] = vx * c + vy * d + y;
@@ -79,9 +89,9 @@ public VertexAttachment (String name)
v += n + 1;
skip += n;
}
Bone[] skeletonBones = skeleton.Bones.Items;
var skeletonBones = skeleton.bones.Items;
if (deformArray.Count == 0) {
for (int w = offset, b = skip * 3; w < count; w += 2) {
for (int w = offset, b = skip * 3; w < count; w += stride) {
float wx = 0, wy = 0;
int n = bones[v++];
n += v;
@@ -96,7 +106,7 @@ public VertexAttachment (String name)
}
} else {
float[] deform = deformArray.Items;
for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += 2) {
for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) {
float wx = 0, wy = 0;
int n = bones[v++];
n += v;
File renamed without changes.
@@ -30,6 +30,6 @@

namespace Spine {
public enum BlendMode {
normal, additive, multiply, screen
Normal, Additive, Multiply, Screen
}
}
File renamed without changes.
@@ -31,6 +31,14 @@
using System;

namespace Spine {
/// <summary>
/// Stores a bone's current pose.
/// <para>
/// 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.
/// </para>
/// </summary>
public class Bone : IUpdatable {
static public bool yDown;

@@ -55,20 +63,51 @@ public class Bone : IUpdatable {
public Skeleton Skeleton { get { return skeleton; } }
public Bone Parent { get { return parent; } }
public ExposedList<Bone> Children { get { return children; } }
/// <summary>The local X translation.</summary>
public float X { get { return x; } set { x = value; } }
/// <summary>The local Y translation.</summary>
public float Y { get { return y; } set { y = value; } }
/// <summary>The local rotation.</summary>
public float Rotation { get { return rotation; } set { rotation = value; } }
/// <summary>The rotation, as calculated by any constraints.</summary>
public float AppliedRotation { get { return arotation; } set { arotation = value; } }

/// <summary>The local scaleX.</summary>
public float ScaleX { get { return scaleX; } set { scaleX = value; } }

/// <summary>The local scaleY.</summary>
public float ScaleY { get { return scaleY; } set { scaleY = value; } }

/// <summary>The local shearX.</summary>
public float ShearX { get { return shearX; } set { shearX = value; } }

/// <summary>The local shearY.</summary>
public float ShearY { get { return shearY; } set { shearY = value; } }

/// <summary>The rotation, as calculated by any constraints.</summary>
public float AppliedRotation { get { return arotation; } set { arotation = value; } }

/// <summary>The applied local x translation.</summary>
public float AX { get { return ax; } set { ax = value; } }

/// <summary>The applied local y translation.</summary>
public float AY { get { return ay; } set { ay = value; } }

/// <summary>The applied local scaleX.</summary>
public float AScaleX { get { return ascaleX; } set { ascaleX = value; } }

/// <summary>The applied local scaleY.</summary>
public float AScaleY { get { return ascaleY; } set { ascaleY = value; } }

/// <summary>The applied local shearX.</summary>
public float AShearX { get { return ashearX; } set { ashearX = value; } }

/// <summary>The applied local shearY.</summary>
public float AShearY { get { return ashearY; } set { ashearY = value; } }

public float A { get { return a; } }
public float B { get { return b; } }
public float C { get { return c; } }
public float D { get { return d; } }

public float WorldX { get { return worldX; } }
public float WorldY { get { return worldY; } }
public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } }
@@ -207,14 +246,14 @@ public class Bone : IUpdatable {
float lb = MathUtils.CosDeg(90 + shearY) * scaleY;
float lc = MathUtils.SinDeg(shearX) * scaleX;
float ld = MathUtils.SinDeg(90 + shearY) * scaleY;
if (data.transformMode != TransformMode.NoScaleOrReflection? pa * pd - pb* pc< 0 : skeleton.flipX != skeleton.flipY) {
zb = -zb;
zd = -zd;
}
a = za * la + zb * lc;
b = za * lb + zb * ld;
c = zc * la + zd * lc;
d = zc * lb + zd * ld;
if (data.transformMode != TransformMode.NoScaleOrReflection ? pa * pd - pb * pc < 0 : skeleton.flipX != skeleton.flipY) {
b = -b;
d = -d;
}
d = zc * lb + zd * ld;
return;
}
}
@@ -223,7 +262,7 @@ public class Bone : IUpdatable {
a = -a;
b = -b;
}
if (skeleton.flipY) {
if (skeleton.flipY != Bone.yDown) {
c = -c;
d = -d;
}
@@ -240,34 +279,6 @@ public class Bone : IUpdatable {
shearY = data.shearY;
}

public float WorldToLocalRotationX {
get {
Bone parent = this.parent;
if (parent == null) return arotation;
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c;
return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg;
}
}

public float WorldToLocalRotationY {
get {
Bone parent = this.parent;
if (parent == null) return arotation;
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d;
return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg;
}
}

public void RotateWorld (float degrees) {
float a = this.a, b = this.b, c = this.c, d = this.d;
float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees);
this.a = cos * a - sin * c;
this.b = cos * b - sin * d;
this.c = sin * a + cos * c;
this.d = sin * b + cos * d;
appliedValid = false;
}

/// <summary>
/// Computes the individual applied transform values from the world transform. This can be useful to perform processing using
/// the applied transform after the world transform has been modified directly (eg, by a constraint)..
@@ -328,7 +339,49 @@ public class Bone : IUpdatable {
worldY = localX * c + localY * d + this.worldY;
}

override public String ToString () {
public float WorldToLocalRotationX {
get {
Bone parent = this.parent;
if (parent == null) return arotation;
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c;
return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg;
}
}

public float WorldToLocalRotationY {
get {
Bone parent = this.parent;
if (parent == null) return arotation;
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d;
return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg;
}
}

public float WorldToLocalRotation (float worldRotation) {
float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation);
return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg;
}

public float LocalToWorldRotation (float localRotation) {
float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation);
return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg;
}

/// <summary>
/// Rotates the world transform the specified amount and sets isAppliedValid to false.
/// </summary>
/// <param name="degrees">Degrees.</param>
public void RotateWorld (float degrees) {
float a = this.a, b = this.b, c = this.c, d = this.d;
float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees);
this.a = cos * a - sin * c;
this.b = cos * b - sin * d;
this.c = sin * a + cos * c;
this.d = sin * b + cos * d;
appliedValid = false;
}

override public string ToString () {
return data.name;
}
}
File renamed without changes.
@@ -33,47 +33,64 @@
namespace Spine {
public class BoneData {
internal int index;
internal String name;
internal string name;
internal BoneData parent;
internal float length;
internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY;
internal TransformMode transformMode = TransformMode.Normal;
//internal bool inheritRotation = true, inheritScale = true;

/// <summary>May be null.</summary>
/// <summary>The index of the bone in Skeleton.Bones</summary>
public int Index { get { return index; } }
public String Name { get { return name; } }

/// <summary>The name of the bone, which is unique within the skeleton.</summary>
public string Name { get { return name; } }

/// <summary>May be null.</summary>
public BoneData Parent { get { return parent; } }

public float Length { get { return length; } set { length = value; } }

/// <summary>Local X translation.</summary>
public float X { get { return x; } set { x = value; } }

/// <summary>Local Y translation.</summary>
public float Y { get { return y; } set { y = value; } }

/// <summary>Local rotation.</summary>
public float Rotation { get { return rotation; } set { rotation = value; } }

/// <summary>Local scaleX.</summary>
public float ScaleX { get { return scaleX; } set { scaleX = value; } }

/// <summary>Local scaleY.</summary>
public float ScaleY { get { return scaleY; } set { scaleY = value; } }

/// <summary>Local shearX.</summary>
public float ShearX { get { return shearX; } set { shearX = value; } }

/// <summary>Local shearY.</summary>
public float ShearY { get { return shearY; } set { shearY = value; } }
public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } }
// public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } }
// public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } }

/// <summary>The transform mode for how parent world transforms affect this bone.</summary>
public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } }

/// <param name="parent">May be null.</param>
public BoneData (int index, String name, BoneData parent) {
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 () {
override public string ToString () {
return name;
}
}

[Flags]
public enum TransformMode {
//0000 0FSR
//0000 0 Flip Scale Rotation
Normal = 0, // 0000
OnlyTranslation = 7, // 0111
NoRotationOrReflection = 1, // 0001
File renamed without changes.
@@ -31,6 +31,7 @@
using System;

namespace Spine {
/// <summary>Stores the current pose values for an Event.</summary>
public class Event {
internal readonly EventData data;
internal readonly float time;
@@ -39,10 +40,12 @@ public class Event {
internal string stringValue;

public EventData Data { get { return data; } }
/// <summary>The animation time this event was keyed.</summary>
public float Time { get { return time; } }

public int Int { get { return intValue; } set { intValue = value; } }
public float Float { get { return floatValue; } set { floatValue = value; } }
public String String { get { return stringValue; } set { stringValue = value; } }
public string String { get { return stringValue; } set { stringValue = value; } }

public Event (float time, EventData data) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
File renamed without changes.
@@ -31,20 +31,22 @@
using System;

namespace Spine {
/// <summary>Stores the setup pose values for an Event.</summary>
public class EventData {
internal String name;
internal string name;

public String Name { get { return name; } }
/// <summary>The name of the event, which is unique within the skeleton.</summary>
public string Name { get { return name; } }
public int Int { get; set; }
public float Float { get; set; }
public String String { get; set; }
public string String { get; set; }

public EventData (String name) {
public EventData (string name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.name = name;
}

override public String ToString () {
override public string ToString () {
return Name;
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,4 +1,4 @@
/******************************************************************************
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
@@ -28,18 +28,13 @@
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/

using UnityEngine;
using System.Collections;
namespace Spine {

/// <summary>The interface for all constraints.</summary>
public interface IConstraint : IUpdatable {
/// <summary>The ordinal for the order a skeleton's constraints will be applied.</summary>
int Order { get; }

namespace Spine.Unity {
public class DoubleBufferedMesh {
readonly Mesh mesh1 = SpineMesh.NewMesh();
readonly Mesh mesh2 = SpineMesh.NewMesh();
bool usingMesh1;

public Mesh GetNextMesh () {
usingMesh1 = !usingMesh1;
return usingMesh1 ? mesh1 : mesh2;
}
}
}

}
File renamed without changes.
@@ -28,8 +28,6 @@
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/

using System;

namespace Spine {
public interface IUpdatable {
void Update ();
File renamed without changes.
@@ -58,11 +58,12 @@ public class IkConstraint : IConstraint {
target = skeleton.FindBone(data.target.name);
}

public void Update () {
Apply();
/// <summary>Applies the constraint to the constrained bones.</summary>
public void Apply () {
Update();
}

public void Apply () {
public void Update () {
Bone target = this.target;
ExposedList<Bone> bones = this.bones;
switch (bones.Count) {
@@ -75,7 +76,7 @@ public class IkConstraint : IConstraint {
}
}

override public String ToString () {
override public string ToString () {
return data.name;
}

@@ -87,12 +88,12 @@ public class IkConstraint : IConstraint {
float id = 1 / (p.a * p.d - p.b * p.c);
float x = targetX - p.worldX, y = targetY - p.worldY;
float tx = (x * p.d - y * p.b) * id - bone.ax, ty = (y * p.a - x * p.c) * id - bone.ay;
float rotationIK = MathUtils.Atan2(ty, tx) * MathUtils.RadDeg - bone.ashearX - bone.arotation;
float rotationIK = (float)Math.Atan2(ty, tx) * MathUtils.RadDeg - bone.ashearX - bone.arotation;
if (bone.ascaleX < 0) rotationIK += 180;
if (rotationIK > 180)
rotationIK -= 360;
else if (rotationIK < -180) rotationIK += 360;
bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, bone.ascaleX, bone.ascaleY, bone.ashearX,
bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, bone.ascaleX, bone.ascaleY, bone.ashearX,
bone.ashearY);
}

@@ -156,12 +157,12 @@ public class IkConstraint : IConstraint {
else if (cos > 1) cos = 1;
a2 = (float)Math.Acos(cos) * bendDir;
a = l1 + l2 * cos;
b = l2 * MathUtils.Sin(a2);
a1 = MathUtils.Atan2(ty * a - tx * b, tx * a + ty * b);
b = l2 * (float)Math.Sin(a2);
a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b);
} else {
a = psx * l2;
b = psy * l2;
float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = MathUtils.Atan2(ty, tx);
float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = (float)Math.Atan2(ty, tx);
c = bb * l1 * l1 + aa * dd - aa * bb;
float c1 = -2 * bb * l1, c2 = bb - aa;
d = c1 * c1 - 4 * c2 * c;
@@ -173,53 +174,42 @@ public class IkConstraint : IConstraint {
float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1;
if (r * r <= dd) {
y = (float)Math.Sqrt(dd - r * r) * bendDir;
a1 = ta - MathUtils.Atan2(y, r);
a2 = MathUtils.Atan2(y / psy, (r - l1) / psx);
goto outer;
a1 = ta - (float)Math.Atan2(y, r);
a2 = (float)Math.Atan2(y / psy, (r - l1) / psx);
goto outer; // break outer;
}
}
float minAngle = 0, minDist = float.MaxValue, minX = 0, minY = 0;
float maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0;
x = l1 + a;
d = x * x;
if (d > maxDist) {
maxAngle = 0;
maxDist = d;
maxX = x;
}
x = l1 - a;
d = x * x;
if (d < minDist) {
minAngle = MathUtils.PI;
minDist = d;
minX = x;
}
float angle = (float)Math.Acos(-a * l1 / (aa - bb));
x = a * MathUtils.Cos(angle) + l1;
y = b * MathUtils.Sin(angle);
d = x * x + y * y;
if (d < minDist) {
minAngle = angle;
minDist = d;
minX = x;
minY = y;
}
if (d > maxDist) {
maxAngle = angle;
maxDist = d;
maxX = x;
maxY = y;
float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0;
float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0;
c = -a * l1 / (aa - bb);
if (c >= -1 && c <= 1) {
c = (float)Math.Acos(c);
x = a * (float)Math.Cos(c) + l1;
y = b * (float)Math.Sin(c);
d = x * x + y * y;
if (d < minDist) {
minAngle = c;
minDist = d;
minX = x;
minY = y;
}
if (d > maxDist) {
maxAngle = c;
maxDist = d;
maxX = x;
maxY = y;
}
}
if (dd <= (minDist + maxDist) / 2) {
a1 = ta - MathUtils.Atan2(minY * bendDir, minX);
a1 = ta - (float)Math.Atan2(minY * bendDir, minX);
a2 = minAngle * bendDir;
} else {
a1 = ta - MathUtils.Atan2(maxY * bendDir, maxX);
a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX);
a2 = maxAngle * bendDir;
}
}
outer:
float os = MathUtils.Atan2(cy, cx) * s2;
float os = (float)Math.Atan2(cy, cx) * s2;
float rotation = parent.arotation;
a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation;
if (a1 > 180)
File renamed without changes.
@@ -32,27 +32,50 @@
using System.Collections.Generic;

namespace Spine {
/// <summary>Stores the setup pose for an IkConstraint.</summary>
public class IkConstraintData {
internal String name;
internal string name;
internal int order;
internal List<BoneData> bones = new List<BoneData>();
internal BoneData target;
internal int bendDirection = 1;
internal float mix = 1;

public String Name { get { return name; } }
public int Order { get { return order; } set { order = value; } }
public List<BoneData> Bones { get { return bones; } }
public BoneData Target { get { return target; } set { target = value; } }
public int BendDirection { get { return bendDirection; } set { bendDirection = value; } }
/// <summary>The IK constraint's name, which is unique within the skeleton.</summary>
public string Name {
get { return name; }
}

public int Order {
get { return order; }
set { order = value; }
}

/// <summary>The bones that are constrained by this IK Constraint.</summary>
public List<BoneData> Bones {
get { return bones; }
}

/// <summary>The bone that is the IK target.</summary>
public BoneData Target {
get { return target; }
set { target = value; }
}

/// <summary>Controls the bend direction of the IK bones, either 1 or -1.</summary>
public int BendDirection {
get { return bendDirection; }
set { bendDirection = value; }
}

public float Mix { get { return mix; } set { mix = value; } }

public IkConstraintData (String name) {
public IkConstraintData (string name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.name = name;
}

override public String ToString () {
override public string ToString () {
return name;
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -32,7 +32,7 @@

namespace Spine {
public class PathConstraint : IConstraint {
private const int NONE = -1, BEFORE = -2, AFTER = -3;
const int NONE = -1, BEFORE = -2, AFTER = -3;

internal PathConstraintData data;
internal ExposedList<Bone> bones;
@@ -84,17 +84,17 @@ public class PathConstraint : IConstraint {
RotateMode rotateMode = data.rotateMode;
bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale;
int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1;
Bone[] bones = this.bones.Items;
Bone[] bonesItems = this.bones.Items;
ExposedList<float> spaces = this.spaces.Resize(spacesCount), lengths = null;
float spacing = this.spacing;
if (scale || lengthSpacing) {
if (scale) lengths = this.lengths.Resize(boneCount);
for (int i = 0, n = spacesCount - 1; i < n;) {
Bone bone = bones[i];
float length = bone.data.length, x = length * bone.a, y = length * bone.c;
length = (float)Math.Sqrt(x * x + y * y);
if (scale) lengths.Items[i] = length;
spaces.Items[++i] = lengthSpacing ? Math.Max(0, length + spacing) : spacing;
Bone bone = bonesItems[i];
float setupLength = bone.data.length, x = setupLength * bone.a, y = setupLength * bone.c;
float length = (float)Math.Sqrt(x * x + y * y);
if (scale) lengths.Items[i] = setupLength;
spaces.Items[++i] = (lengthSpacing ? Math.Max(0, setupLength + spacing) : spacing) * length / setupLength;
}
} else {
for (int i = 1; i < spacesCount; i++)
@@ -113,7 +113,7 @@ public class PathConstraint : IConstraint {
offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
}
for (int i = 0, p = 3; i < boneCount; i++, p += 3) {
Bone bone = (Bone)bones[i];
Bone bone = bonesItems[i];
bone.worldX += (boneX - bone.worldX) * translateMix;
bone.worldY += (boneY - bone.worldY) * translateMix;
float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
@@ -166,7 +166,7 @@ public class PathConstraint : IConstraint {

Slot target = this.target;
float position = this.position;
float[] spaces = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world;
float[] spacesItems = 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;

@@ -178,11 +178,11 @@ public class PathConstraint : IConstraint {
if (percentPosition) position *= pathLength;
if (percentSpacing) {
for (int i = 0; i < spacesCount; i++)
spaces[i] *= pathLength;
spacesItems[i] *= pathLength;
}
world = this.world.Resize(8).Items;
for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
float space = spaces[i];
float space = spacesItems[i];
position += space;
float p = position;

@@ -286,13 +286,13 @@ public class PathConstraint : IConstraint {
if (percentPosition) position *= pathLength;
if (percentSpacing) {
for (int i = 0; i < spacesCount; i++)
spaces[i] *= pathLength;
spacesItems[i] *= pathLength;
}

float[] segments = this.segments;
float curveLength = 0;
for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) {
float space = spaces[i];
float space = spacesItems[i];
position += space;
float p = position;

@@ -380,21 +380,21 @@ public class PathConstraint : IConstraint {
return output;
}

private void AddBeforePosition (float p, float[] temp, int i, float[] output, int o) {
static void AddBeforePosition (float p, float[] temp, int i, float[] output, int o) {
float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx);
output[o] = x1 + p * MathUtils.Cos(r);
output[o + 1] = y1 + p * MathUtils.Sin(r);
output[o + 2] = r;
}

private void AddAfterPosition (float p, float[] temp, int i, float[] output, int o) {
static void AddAfterPosition (float p, float[] temp, int i, float[] output, int o) {
float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx);
output[o] = x1 + p * MathUtils.Cos(r);
output[o + 1] = y1 + p * MathUtils.Sin(r);
output[o + 2] = r;
}

private void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
static void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
float[] output, int o, bool tangents) {
if (p == 0 || float.IsNaN(p)) p = 0.0001f;
float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -194,31 +194,39 @@ public class Skeleton {

var constrained = constraint.bones;
int boneCount = constrained.Count;
for (int ii = 0; ii < boneCount; ii++)
SortBone(constrained.Items[ii]);
for (int i = 0; i < boneCount; i++)
SortBone(constrained.Items[i]);

updateCache.Add(constraint);

for (int ii = 0; ii < boneCount; ii++)
SortReset(constrained.Items[ii].children);
for (int ii = 0; ii < boneCount; ii++)
constrained.Items[ii].sorted = true;
for (int i = 0; i < boneCount; i++)
SortReset(constrained.Items[i].children);
for (int i = 0; i < boneCount; i++)
constrained.Items[i].sorted = true;
}

private void SortTransformConstraint (TransformConstraint constraint) {
SortBone(constraint.target);

var constrained = constraint.bones;
int boneCount = constrained.Count;
for (int ii = 0; ii < boneCount; ii++)
SortBone(constrained.Items[ii]);
if (constraint.data.local) {
for (int i = 0; i < boneCount; i++) {
Bone child = constrained.Items[i];
SortBone(child.parent);
if (!updateCache.Contains(child)) updateCacheReset.Add(child);
}
} else {
for (int i = 0; i < boneCount; i++)
SortBone(constrained.Items[i]);
}

updateCache.Add(constraint);

for (int ii = 0; ii < boneCount; ii++)
SortReset(constrained.Items[ii].children);
for (int ii = 0; ii < boneCount; ii++)
constrained.Items[ii].sorted = true;
for (int i = 0; i < boneCount; i++)
SortReset(constrained.Items[i].children);
for (int i = 0; i < boneCount; i++)
constrained.Items[i].sorted = true;
}

private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
@@ -250,7 +258,7 @@ public class Skeleton {
updateCache.Add(bone);
}

private void SortReset (ExposedList<Bone> bones) {
private static void SortReset (ExposedList<Bone> bones) {
var bonesItems = bones.Items;
for (int i = 0, n = bones.Count; i < n; i++) {
Bone bone = bonesItems[i];
@@ -301,21 +309,21 @@ public class Skeleton {
var transformConstraintsItems = this.transformConstraints.Items;
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
TransformConstraint constraint = transformConstraintsItems[i];
TransformConstraintData data = constraint.data;
constraint.rotateMix = data.rotateMix;
constraint.translateMix = data.translateMix;
constraint.scaleMix = data.scaleMix;
constraint.shearMix = data.shearMix;
TransformConstraintData constraintData = constraint.data;
constraint.rotateMix = constraintData.rotateMix;
constraint.translateMix = constraintData.translateMix;
constraint.scaleMix = constraintData.scaleMix;
constraint.shearMix = constraintData.shearMix;
}

var pathConstraintItems = this.pathConstraints.Items;
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
PathConstraint constraint = pathConstraintItems[i];
PathConstraintData data = constraint.data;
constraint.position = data.position;
constraint.spacing = data.spacing;
constraint.rotateMix = data.rotateMix;
constraint.translateMix = data.translateMix;
PathConstraintData constraintData = constraint.data;
constraint.position = constraintData.position;
constraint.spacing = constraintData.spacing;
constraint.rotateMix = constraintData.rotateMix;
constraint.translateMix = constraintData.translateMix;
}
}

@@ -331,7 +339,7 @@ public class Skeleton {
}

/// <returns>May be null.</returns>
public Bone FindBone (String boneName) {
public Bone FindBone (string boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
var bones = this.bones;
var bonesItems = bones.Items;
@@ -343,7 +351,7 @@ public class Skeleton {
}

/// <returns>-1 if the bone was not found.</returns>
public int FindBoneIndex (String boneName) {
public int FindBoneIndex (string boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
var bones = this.bones;
var bonesItems = bones.Items;
@@ -353,7 +361,7 @@ public class Skeleton {
}

/// <returns>May be null.</returns>
public Slot FindSlot (String slotName) {
public Slot FindSlot (string slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
var slots = this.slots;
var slotsItems = slots.Items;
@@ -365,7 +373,7 @@ public class Skeleton {
}

/// <returns>-1 if the bone was not found.</returns>
public int FindSlotIndex (String slotName) {
public int FindSlotIndex (string slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
var slots = this.slots;
var slotsItems = slots.Items;
@@ -375,10 +383,10 @@ public class Skeleton {
}

/// <summary>Sets a skin by name (see SetSkin).</summary>
public void SetSkin (String skinName) {
Skin skin = data.FindSkin(skinName);
if (skin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName");
SetSkin(skin);
public void SetSkin (string skinName) {
Skin foundSkin = data.FindSkin(skinName);
if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName");
SetSkin(foundSkin);
}

/// <summary>Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default
@@ -393,7 +401,7 @@ public class Skeleton {
ExposedList<Slot> slots = this.slots;
for (int i = 0, n = slots.Count; i < n; i++) {
Slot slot = slots.Items[i];
String name = slot.data.attachmentName;
string name = slot.data.attachmentName;
if (name != null) {
Attachment attachment = newSkin.GetAttachment(i, name);
if (attachment != null) slot.Attachment = attachment;
@@ -405,23 +413,22 @@ public class Skeleton {
}

/// <returns>May be null.</returns>
public Attachment GetAttachment (String slotName, String attachmentName) {
public Attachment GetAttachment (string slotName, string attachmentName) {
return GetAttachment(data.FindSlotIndex(slotName), attachmentName);
}

/// <returns>May be null.</returns>
public Attachment GetAttachment (int slotIndex, String attachmentName) {
public Attachment GetAttachment (int slotIndex, string attachmentName) {
if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null.");
if (skin != null) {
Attachment attachment = skin.GetAttachment(slotIndex, attachmentName);
if (attachment != null) return attachment;
}
if (data.defaultSkin != null) return data.defaultSkin.GetAttachment(slotIndex, attachmentName);
return null;
return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null;
}

/// <param name="attachmentName">May be null.</param>
public void SetAttachment (String slotName, String attachmentName) {
public void SetAttachment (string slotName, string attachmentName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
ExposedList<Slot> slots = this.slots;
for (int i = 0, n = slots.Count; i < n; i++) {
@@ -440,7 +447,7 @@ public class Skeleton {
}

/// <returns>May be null.</returns>
public IkConstraint FindIkConstraint (String constraintName) {
public IkConstraint FindIkConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<IkConstraint> ikConstraints = this.ikConstraints;
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
@@ -451,7 +458,7 @@ public class Skeleton {
}

/// <returns>May be null.</returns>
public TransformConstraint FindTransformConstraint (String constraintName) {
public TransformConstraint FindTransformConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
@@ -462,7 +469,7 @@ public class Skeleton {
}

/// <returns>May be null.</returns>
public PathConstraint FindPathConstraint (String constraintName) {
public PathConstraint FindPathConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<PathConstraint> pathConstraints = this.pathConstraints;
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
@@ -475,5 +482,55 @@ public class Skeleton {
public void Update (float delta) {
time += delta;
}

/// <summary>Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose.</summary>
/// <param name="x">The horizontal distance between the skeleton origin and the left side of the AABB.</param>
/// <param name="y">The vertical distance between the skeleton origin and the bottom side of the AABB.</param>
/// <param name="width">The width of the AABB</param>
/// <param name="height">The height of the AABB.</param>
/// <param name="vertexBuffer">Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed.</param>
public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer) {
float[] temp = vertexBuffer;
temp = temp ?? new float[8];
var drawOrderItems = this.drawOrder.Items;
float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
for (int i = 0, n = this.drawOrder.Count; i < n; i++) {
Slot slot = drawOrderItems[i];
int verticesLength = 0;
float[] vertices = null;
Attachment attachment = slot.attachment;
var regionAttachment = attachment as RegionAttachment;
if (regionAttachment != null) {
verticesLength = 8;
vertices = temp;
if (vertices.Length < 8) vertices = temp = new float[8];
regionAttachment.ComputeWorldVertices(slot.bone, temp, 0);
} else {
var meshAttachment = attachment as MeshAttachment;
if (meshAttachment != null) {
MeshAttachment mesh = meshAttachment;
verticesLength = mesh.WorldVerticesLength;
vertices = temp;
if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength];
mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0);
}
}

if (vertices != null) {
for (int ii = 0; ii < verticesLength; ii += 2) {
float vx = vertices[ii], vy = vertices[ii + 1];
minX = Math.Min(minX, vx);
minY = Math.Min(minY, vy);
maxX = Math.Max(maxX, vx);
maxY = Math.Max(maxY, vy);
}
}
}
x = minX;
y = minY;
width = maxX - minX;
height = maxY - minY;
vertexBuffer = temp;
}
}
}
File renamed without changes.
@@ -28,7 +28,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/

#if (UNITY_5 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
#define IS_UNITY
#endif

@@ -50,6 +50,7 @@ public class SkeletonBinary {

public const int SLOT_ATTACHMENT = 0;
public const int SLOT_COLOR = 1;
public const int SLOT_TWO_COLOR = 2;

public const int PATH_POSITION = 0;
public const int PATH_SPACING = 1;
@@ -182,6 +183,15 @@ public SkeletonBinary (params Atlas[] atlasArray)
slotData.g = ((color & 0x00ff0000) >> 16) / 255f;
slotData.b = ((color & 0x0000ff00) >> 8) / 255f;
slotData.a = ((color & 0x000000ff)) / 255f;

int darkColor = ReadInt(input); // 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 = ReadString(input);
slotData.blendMode = (BlendMode)ReadVarint(input, true);
skeletonData.slots.Add(slotData);
@@ -206,6 +216,8 @@ public SkeletonBinary (params Atlas[] atlasArray)
for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
data.target = skeletonData.bones.Items[ReadVarint(input, true)];
data.local = ReadBoolean(input);
data.relative = ReadBoolean(input);
data.offsetRotation = ReadFloat(input);
data.offsetX = ReadFloat(input) * scale;
data.offsetY = ReadFloat(input) * scale;
@@ -240,15 +252,15 @@ public SkeletonBinary (params Atlas[] atlasArray)
}

// Default skin.
Skin defaultSkin = ReadSkin(input, "default", nonessential);
Skin defaultSkin = ReadSkin(input, skeletonData, "default", nonessential);
if (defaultSkin != null) {
skeletonData.defaultSkin = defaultSkin;
skeletonData.skins.Add(defaultSkin);
}

// Skins.
for (int i = 0, n = ReadVarint(input, true); i < n; i++)
skeletonData.skins.Add(ReadSkin(input, ReadString(input), nonessential));
skeletonData.skins.Add(ReadSkin(input, skeletonData, ReadString(input), nonessential));

// Linked meshes.
for (int i = 0, n = linkedMeshes.Count; i < n; i++) {
@@ -287,22 +299,22 @@ public SkeletonBinary (params Atlas[] atlasArray)


/// <returns>May be null.</returns>
private Skin ReadSkin (Stream input, String skinName, bool nonessential) {
private Skin ReadSkin (Stream input, SkeletonData skeletonData, String skinName, bool nonessential) {
int slotCount = ReadVarint(input, true);
if (slotCount == 0) return null;
Skin skin = new Skin(skinName);
for (int i = 0; i < slotCount; i++) {
int slotIndex = ReadVarint(input, true);
for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
String name = ReadString(input);
Attachment attachment = ReadAttachment(input, skin, slotIndex, name, nonessential);
Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential);
if (attachment != null) skin.AddAttachment(slotIndex, name, attachment);
}
}
return skin;
}

private Attachment ReadAttachment (Stream input, Skin skin, int slotIndex, String attachmentName, bool nonessential) {
private Attachment ReadAttachment (Stream input, SkeletonData skeletonData, Skin skin, int slotIndex, String attachmentName, bool nonessential) {
float scale = Scale;

String name = ReadString(input);
@@ -425,7 +437,7 @@ public SkeletonBinary (params Atlas[] atlasArray)
float[] lengths = new float[vertexCount / 3];
for (int i = 0, n = lengths.Length; i < n; i++)
lengths[i] = ReadFloat(input) * scale;
if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning.
if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0;

PathAttachment path = attachmentLoader.NewPathAttachment(skin, name);
if (path == null) return null;
@@ -436,7 +448,35 @@ public SkeletonBinary (params Atlas[] atlasArray)
path.bones = vertices.bones;
path.lengths = lengths;
return path;
}
}
case AttachmentType.Point: {
float rotation = ReadFloat(input);
float x = ReadFloat(input);
float y = ReadFloat(input);
if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0;

PointAttachment point = attachmentLoader.NewPointAttachment(skin, name);
if (point == null) return null;
point.x = x * scale;
point.y = y * scale;
point.rotation = rotation;
//if (nonessential) point.color = color;
return point;
}
case AttachmentType.Clipping: {
int endSlotIndex = ReadVarint(input, true);
int vertexCount = ReadVarint(input, true);
Vertices vertices = ReadVertices(input, vertexCount);
if (nonessential) ReadInt(input);

ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name);
if (clip == null) return null;
clip.EndSlot = skeletonData.slots.Items[endSlotIndex];
clip.worldVerticesLength = vertexCount << 1;
clip.vertices = vertices.vertices;
clip.bones = vertices.bones;
return clip;
}
}
return null;
}
@@ -499,6 +539,15 @@ public SkeletonBinary (params Atlas[] atlasArray)
int timelineType = input.ReadByte();
int frameCount = ReadVarint(input, true);
switch (timelineType) {
case SLOT_ATTACHMENT: {
AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
timeline.slotIndex = slotIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input));
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount - 1]);
break;
}
case SLOT_COLOR: {
ColorTimeline timeline = new ColorTimeline(frameCount);
timeline.slotIndex = slotIndex;
@@ -516,13 +565,26 @@ public SkeletonBinary (params Atlas[] atlasArray)
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]);
break;
}
case SLOT_ATTACHMENT: {
AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
case SLOT_TWO_COLOR: {
TwoColorTimeline timeline = new TwoColorTimeline(frameCount);
timeline.slotIndex = slotIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input));
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
float time = ReadFloat(input);
int color = ReadInt(input);
float r = ((color & 0xff000000) >> 24) / 255f;
float g = ((color & 0x00ff0000) >> 16) / 255f;
float b = ((color & 0x0000ff00) >> 8) / 255f;
float a = ((color & 0x000000ff)) / 255f;
int color2 = ReadInt(input); // 0x00rrggbb
float r2 = ((color2 & 0x00ff0000) >> 16) / 255f;
float g2 = ((color2 & 0x0000ff00) >> 8) / 255f;
float b2 = ((color2 & 0x000000ff)) / 255f;

timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount - 1]);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]);
break;
}
}
File renamed without changes.
@@ -31,6 +31,11 @@
using System;

namespace Spine {

/// <summary>
/// Collects each BoundingBoxAttachment that is visible and computes the world vertices for its polygon.
/// The polygon vertices are provided along with convenience methods for doing hit detection.
/// </summary>
public class SkeletonBounds {
private ExposedList<Polygon> polygonPool = new ExposedList<Polygon>();
private float minX, minY, maxX, maxY;
@@ -49,6 +54,14 @@ public class SkeletonBounds {
Polygons = new ExposedList<Polygon>();
}

/// <summary>
/// Clears any previous polygons, finds all visible bounding box attachments,
/// and computes the world vertices for each bounding box's polygon.</summary>
/// <param name="skeleton">The skeleton.</param>
/// <param name="updateAabb">
/// If true, the axis aligned bounding box containing all the polygons is computed.
/// If false, the SkeletonBounds AABB methods will always return true.
/// </param>
public void Update (Skeleton skeleton, bool updateAabb) {
ExposedList<BoundingBoxAttachment> boundingBoxes = BoundingBoxes;
ExposedList<Polygon> polygons = Polygons;
@@ -75,7 +88,7 @@ public class SkeletonBounds {
polygon = new Polygon();
polygons.Add(polygon);

int count = boundingBox.Vertices.Length;
int count = boundingBox.worldVerticesLength;
polygon.Count = count;
if (polygon.Vertices.Length < count) polygon.Vertices = new float[count];
boundingBox.ComputeWorldVertices(slot, polygon.Vertices);
File renamed without changes.
@@ -0,0 +1,285 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/

using System;

namespace Spine {
public class SkeletonClipping {
internal readonly Triangulator triangulator = new Triangulator();
internal readonly ExposedList<float> clippingPolygon = new ExposedList<float>();
internal readonly ExposedList<float> clipOutput = new ExposedList<float>(128);
internal readonly ExposedList<float> clippedVertices = new ExposedList<float>(128);
internal readonly ExposedList<int> clippedTriangles = new ExposedList<int>(128);
internal readonly ExposedList<float> clippedUVs = new ExposedList<float>(128);
internal readonly ExposedList<float> scratch = new ExposedList<float>();

internal ClippingAttachment clipAttachment;
internal ExposedList<ExposedList<float>> clippingPolygons;

public ExposedList<float> ClippedVertices { get { return clippedVertices; } }
public ExposedList<int> ClippedTriangles { get { return clippedTriangles; } }
public ExposedList<float> ClippedUVs { get { return clippedUVs; } }

public bool IsClipping () { return clipAttachment != null; }

public int ClipStart (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);
MakeClockwise(clippingPolygon);
clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon));
foreach (var polygon in clippingPolygons) {
MakeClockwise(polygon);
polygon.Add(polygon.Items[0]);
polygon.Add(polygon.Items[1]);
}
return clippingPolygons.Count;
}

public void ClipEnd (Slot slot) {
if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd();
}

public void ClipEnd () {
if (clipAttachment == null) return;
clipAttachment = null;
clippingPolygons = null;
clippedVertices.Clear();
clippedTriangles.Clear();
clippingPolygon.Clear();
}

public void ClipTriangles (float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) {
ExposedList<float> clipOutput = this.clipOutput, clippedVertices = this.clippedVertices;
var clippedTriangles = this.clippedTriangles;
var polygons = clippingPolygons.Items;
int polygonsCount = clippingPolygons.Count;

int index = 0;
clippedVertices.Clear();
clippedUVs.Clear();
clippedTriangles.Clear();
//outer: // libgdx
for (int i = 0; i < trianglesLength; i += 3) {
int vertexOffset = triangles[i] << 1;
float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1];
float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1];

vertexOffset = triangles[i + 1] << 1;
float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1];
float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1];

vertexOffset = triangles[i + 2] << 1;
float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1];
float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1];

for (int p = 0; p < polygonsCount; p++) {
int s = clippedVertices.Count;
if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) {
int clipOutputLength = clipOutput.Count;
if (clipOutputLength == 0) continue;
float d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1;
float d = 1 / (d0 * d2 + d1 * (y1 - y3));

int clipOutputCount = clipOutputLength >> 1;
float[] clipOutputItems = clipOutput.Items;
float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items;
float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items;
for (int ii = 0; ii < clipOutputLength; ii += 2) {
float x = clipOutputItems[ii], y = clipOutputItems[ii + 1];
clippedVerticesItems[s] = x;
clippedVerticesItems[s + 1] = y;
float c0 = x - x3, c1 = y - y3;
float a = (d0 * c0 + d1 * c1) * d;
float b = (d4 * c0 + d2 * c1) * d;
float c = 1 - a - b;
clippedUVsItems[s] = u1 * a + u2 * b + u3 * c;
clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c;
s += 2;
}

s = clippedTriangles.Count;
int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items;
clipOutputCount--;
for (int ii = 1; ii < clipOutputCount; ii++) {
clippedTrianglesItems[s] = index;
clippedTrianglesItems[s + 1] = index + ii;
clippedTrianglesItems[s + 2] = index + ii + 1;
s += 3;
}
index += clipOutputCount + 1;
}
else {
float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items;
float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items;
clippedVerticesItems[s] = x1;
clippedVerticesItems[s + 1] = y1;
clippedVerticesItems[s + 2] = x2;
clippedVerticesItems[s + 3] = y2;
clippedVerticesItems[s + 4] = x3;
clippedVerticesItems[s + 5] = y3;

clippedUVsItems[s] = u1;
clippedUVsItems[s + 1] = v1;
clippedUVsItems[s + 2] = u2;
clippedUVsItems[s + 3] = v2;
clippedUVsItems[s + 4] = u3;
clippedUVsItems[s + 5] = v3;

s = clippedTriangles.Count;
int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items;
clippedTrianglesItems[s] = index;
clippedTrianglesItems[s + 1] = index + 1;
clippedTrianglesItems[s + 2] = index + 2;
index += 3;
break; //continue outer;
}
}
}

}

/** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping
* area, false is returned. The clipping area must duplicate the first vertex at the end of the vertices list. */
internal bool Clip (float x1, float y1, float x2, float y2, float x3, float y3, ExposedList<float> clippingArea, ExposedList<float> output) {
var originalOutput = output;
var clipped = false;

// Avoid copy at the end.
ExposedList<float> input = null;
if (clippingArea.Count % 4 >= 2) {
input = output;
output = scratch;
} else {
input = scratch;
}

input.Clear();
input.Add(x1);
input.Add(y1);
input.Add(x2);
input.Add(y2);
input.Add(x3);
input.Add(y3);
input.Add(x1);
input.Add(y1);
output.Clear();

float[] clippingVertices = clippingArea.Items;
int clippingVerticesLast = clippingArea.Count - 4;
for (int i = 0; ; i += 2) {
float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1];
float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3];
float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2;

float[] inputVertices = input.Items;
int inputVerticesLength = input.Count - 2, outputStart = output.Count;
for (int ii = 0; ii < inputVerticesLength; ii += 2) {
float inputX = inputVertices[ii], inputY = inputVertices[ii + 1];
float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3];
bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0;
if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) {
if (side2) { // v1 inside, v2 inside
output.Add(inputX2);
output.Add(inputY2);
continue;
}
// v1 inside, v2 outside
float c0 = inputY2 - inputY, c2 = inputX2 - inputX;
float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / (c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY));
output.Add(edgeX + (edgeX2 - edgeX) * ua);
output.Add(edgeY + (edgeY2 - edgeY) * ua);
}
else if (side2) { // v1 outside, v2 inside
float c0 = inputY2 - inputY, c2 = inputX2 - inputX;
float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / (c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY));
output.Add(edgeX + (edgeX2 - edgeX) * ua);
output.Add(edgeY + (edgeY2 - edgeY) * ua);
output.Add(inputX2);
output.Add(inputY2);
}
clipped = true;
}

if (outputStart == output.Count) { // All edges outside.
originalOutput.Clear();
return true;
}

output.Add(output.Items[0]);
output.Add(output.Items[1]);

if (i == clippingVerticesLast) break;
var temp = output;
output = input;
output.Clear();
input = temp;
}

if (originalOutput != output) {
originalOutput.Clear();
for (int i = 0, n = output.Count - 2; i < n; i++) {
originalOutput.Add(output.Items[i]);
}
} else {
originalOutput.Resize(originalOutput.Count - 2);
}

return clipped;
}

static void MakeClockwise (ExposedList<float> polygon) {
float[] vertices = polygon.Items;
int verticeslength = polygon.Count;

float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y;
for (int i = 0, n = verticeslength - 3; i < n; i += 2) {
p1x = vertices[i];
p1y = vertices[i + 1];
p2x = vertices[i + 2];
p2y = vertices[i + 3];
area += p1x * p2y - p2x * p1y;
}
if (area < 0) return;

for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) {
float x = vertices[i], y = vertices[i + 1];
int other = lastX - i;
vertices[i] = vertices[other];
vertices[i + 1] = vertices[other + 1];
vertices[other] = x;
vertices[other + 1] = y;
}
}
}
}
@@ -31,10 +31,12 @@
using System;

namespace Spine {

/// <summary>Stores the setup pose and all of the stateless data for a skeleton.</summary>
public class SkeletonData {
internal string name;
internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
internal ExposedList<SlotData> slots = new ExposedList<SlotData>();
internal ExposedList<BoneData> bones = new ExposedList<BoneData>(); // Ordered parents first
internal ExposedList<SlotData> slots = new ExposedList<SlotData>(); // Setup pose draw order.
internal ExposedList<Skin> skins = new ExposedList<Skin>();
internal Skin defaultSkin;
internal ExposedList<EventData> events = new ExposedList<EventData>();
@@ -49,12 +51,23 @@ public class SkeletonData {
internal float fps;
internal string imagesPath;

public String Name { get { return name; } set { name = value; } }
public ExposedList<BoneData> Bones { get { return bones; } } // Ordered parents first.
public ExposedList<SlotData> Slots { get { return slots; } } // Setup pose draw order.
public string Name { get { return name; } set { name = value; } }

/// <summary>The skeleton's bones, sorted parent first. The root bone is always the first bone.</summary>
public ExposedList<BoneData> Bones { get { return bones; } }

public ExposedList<SlotData> Slots { get { return slots; } }

/// <summary>All skins, including the default skin.</summary>
public ExposedList<Skin> Skins { get { return skins; } set { skins = value; } }
/// <summary>May be null.</summary>

/// <summary>
/// The skeleton's default skin.
/// By default this skin contains all attachments that were not in a skin in Spine.
/// </summary>
/// <return>May be null.</return>
public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } }

public ExposedList<EventData> Events { get { return events; } set { events = value; } }
public ExposedList<Animation> Animations { get { return animations; } set { animations = value; } }
public ExposedList<IkConstraintData> IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } }
@@ -67,34 +80,42 @@ public class SkeletonData {
public string Version { get { return version; } set { version = value; } }
public string Hash { get { return hash; } set { hash = value; } }
public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } }

/// <summary>
/// The dopesheet FPS in Spine. Available only when nonessential data was exported.</summary>
public float Fps { get { return fps; } set { fps = value; } }

// --- Bones.

/// <summary>
/// Finds a bone by comparing each bone's name.
/// It is more efficient to cache the results of this method than to call it multiple times.</summary>
/// <returns>May be null.</returns>
public BoneData FindBone (String boneName) {
public BoneData FindBone (string boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
ExposedList<BoneData> bones = this.bones;
var bones = this.bones;
var bonesItems = bones.Items;
for (int i = 0, n = bones.Count; i < n; i++) {
BoneData bone = bones.Items[i];
BoneData bone = bonesItems[i];
if (bone.name == boneName) return bone;
}
return null;
}

/// <returns>-1 if the bone was not found.</returns>
public int FindBoneIndex (String boneName) {
public int FindBoneIndex (string boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
ExposedList<BoneData> bones = this.bones;
var bones = this.bones;
var bonesItems = bones.Items;
for (int i = 0, n = bones.Count; i < n; i++)
if (bones.Items[i].name == boneName) return i;
if (bonesItems[i].name == boneName) return i;
return -1;
}

// --- Slots.

/// <returns>May be null.</returns>
public SlotData FindSlot (String slotName) {
public SlotData FindSlot (string slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
ExposedList<SlotData> slots = this.slots;
for (int i = 0, n = slots.Count; i < n; i++) {
@@ -105,7 +126,7 @@ public class SkeletonData {
}

/// <returns>-1 if the slot was not found.</returns>
public int FindSlotIndex (String slotName) {
public int FindSlotIndex (string slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
ExposedList<SlotData> slots = this.slots;
for (int i = 0, n = slots.Count; i < n; i++)
@@ -116,7 +137,7 @@ public class SkeletonData {
// --- Skins.

/// <returns>May be null.</returns>
public Skin FindSkin (String skinName) {
public Skin FindSkin (string skinName) {
if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null.");
foreach (Skin skin in skins)
if (skin.name == skinName) return skin;
@@ -126,7 +147,7 @@ public class SkeletonData {
// --- Events.

/// <returns>May be null.</returns>
public EventData FindEvent (String eventDataName) {
public EventData FindEvent (string eventDataName) {
if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null.");
foreach (EventData eventData in events)
if (eventData.name == eventDataName) return eventData;
@@ -136,7 +157,7 @@ public class SkeletonData {
// --- Animations.

/// <returns>May be null.</returns>
public Animation FindAnimation (String animationName) {
public Animation FindAnimation (string animationName) {
if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null.");
ExposedList<Animation> animations = this.animations;
for (int i = 0, n = animations.Count; i < n; i++) {
@@ -149,7 +170,7 @@ public class SkeletonData {
// --- IK constraints.

/// <returns>May be null.</returns>
public IkConstraintData FindIkConstraint (String constraintName) {
public IkConstraintData FindIkConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<IkConstraintData> ikConstraints = this.ikConstraints;
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
@@ -162,7 +183,7 @@ public class SkeletonData {
// --- Transform constraints.

/// <returns>May be null.</returns>
public TransformConstraintData FindTransformConstraint (String constraintName) {
public TransformConstraintData FindTransformConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<TransformConstraintData> transformConstraints = this.transformConstraints;
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
@@ -175,7 +196,7 @@ public class SkeletonData {
// --- Path constraints.

/// <returns>May be null.</returns>
public PathConstraintData FindPathConstraint (String constraintName) {
public PathConstraintData FindPathConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<PathConstraintData> pathConstraints = this.pathConstraints;
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
@@ -186,7 +207,7 @@ public class SkeletonData {
}

/// <returns>-1 if the path constraint was not found.</returns>
public int FindPathConstraintIndex (String pathConstraintName) {
public int FindPathConstraintIndex (string pathConstraintName) {
if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null.");
ExposedList<PathConstraintData> pathConstraints = this.pathConstraints;
for (int i = 0, n = pathConstraints.Count; i < n; i++)
@@ -196,7 +217,7 @@ public class SkeletonData {

// ---

override public String ToString () {
override public string ToString () {
return name ?? base.ToString();
}
}
File renamed without changes.
@@ -28,7 +28,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/

#if (UNITY_5 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
#define IS_UNITY
#endif

@@ -146,12 +146,20 @@ public SkeletonJson (params Atlas[] atlasArray)
data.b = ToColor(color, 2);
data.a = ToColor(color, 3);
}

if (slotMap.ContainsKey("dark")) {
var 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"], false);
data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], true);
else
data.blendMode = BlendMode.normal;
data.blendMode = BlendMode.Normal;
skeletonData.slots.Add(data);
}
}
@@ -195,6 +203,9 @@ public SkeletonJson (params Atlas[] atlasArray)
data.target = skeletonData.FindBone(targetName);
if (data.target == null) throw new Exception("Target bone not found: " + targetName);

data.local = GetBoolean(constraintMap, "local", false);
data.relative = GetBoolean(constraintMap, "relative", false);

data.offsetRotation = GetFloat(constraintMap, "rotation", 0);
data.offsetX = GetFloat(constraintMap, "x", 0) * scale;
data.offsetY = GetFloat(constraintMap, "y", 0) * scale;
@@ -250,7 +261,7 @@ public SkeletonJson (params Atlas[] atlasArray)
int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key);
foreach (KeyValuePair<String, Object> entry in ((Dictionary<String, Object>)slotEntry.Value)) {
try {
Attachment attachment = ReadAttachment((Dictionary<String, Object>)entry.Value, skin, slotIndex, entry.Key);
Attachment attachment = ReadAttachment((Dictionary<String, Object>)entry.Value, skin, slotIndex, entry.Key, skeletonData);
if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment);
} catch (Exception e) {
throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e);
@@ -306,7 +317,7 @@ public SkeletonJson (params Atlas[] atlasArray)
return skeletonData;
}

private Attachment ReadAttachment (Dictionary<String, Object> map, Skin skin, int slotIndex, String name) {
private Attachment ReadAttachment (Dictionary<String, Object> map, Skin skin, int slotIndex, String name, SkeletonData skeletonData) {
var scale = this.Scale;
name = GetString(map, "name", name);

@@ -394,6 +405,34 @@ public SkeletonJson (params Atlas[] atlasArray)
pathAttachment.lengths = GetFloatArray(map, "lengths", scale);
return pathAttachment;
}
case AttachmentType.Point: {
PointAttachment point = attachmentLoader.NewPointAttachment(skin, name);
if (point == null) return null;
point.x = GetFloat(map, "x", 0) * scale;
point.y = GetFloat(map, "y", 0) * scale;
point.rotation = GetFloat(map, "rotation", 0);

//string color = GetString(map, "color", null);
//if (color != null) point.color = color;
return point;
}
case AttachmentType.Clipping: {
ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name);
if (clip == null) return null;

string end = GetString(map, "end", null);
if (end != null) {
SlotData slot = skeletonData.FindSlot(end);
if (slot == null) throw new Exception("Clipping end slot not found: " + end);
clip.EndSlot = slot;
}

ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1);

//string color = GetString(map, "color", null);
// if (color != null) clip.color = color;
return clip;
}
}
return null;
}
@@ -441,32 +480,49 @@ public SkeletonJson (params Atlas[] atlasArray)
foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) {
var values = (List<Object>)timelineEntry.Value;
var timelineName = (String)timelineEntry.Key;
if (timelineName == "color") {
var timeline = new ColorTimeline(values.Count);
if (timelineName == "attachment") {
var timeline = new AttachmentTimeline(values.Count);
timeline.slotIndex = slotIndex;

int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) {
float time = (float)valueMap["time"];
String c = (String)valueMap["color"];
timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);

} else if (timelineName == "color") {
var timeline = new ColorTimeline(values.Count);
timeline.slotIndex = slotIndex;

int frameIndex = 0;
foreach (Dictionary<string, Object> valueMap in values) {
float time = (float)valueMap["time"];
string c = (string)valueMap["color"];
timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3));
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]);

} else if (timelineName == "attachment") {
var timeline = new AttachmentTimeline(values.Count);
} else if (timelineName == "twoColor") {
var timeline = new TwoColorTimeline(values.Count);
timeline.slotIndex = slotIndex;

int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) {
foreach (Dictionary<string, Object> valueMap in values) {
float time = (float)valueMap["time"];
timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]);
string light = (string)valueMap["light"];
string dark = (string)valueMap["dark"];
timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3),
ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6));
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]);

} else
throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");
@@ -801,9 +857,9 @@ internal class LinkedMesh {
return (String)map[name];
}

static float ToColor(String hexString, int colorIndex) {
if (hexString.Length != 8)
throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString, "hexString");
static float ToColor(String hexString, int colorIndex, int expectedLength = 8) {
if (hexString.Length != expectedLength)
throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString");
return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255;
}
}
File renamed without changes.