diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs index 8ba2c8c2f..ebf24ed42 100644 --- a/spine-csharp/src/Animation.cs +++ b/spine-csharp/src/Animation.cs @@ -126,7 +126,8 @@ namespace Spine { Attachment, Color, Deform, // Event, DrawOrder, // IkConstraint, TransformConstraint, // - PathConstraintPosition, PathConstraintSpacing, PathConstraintMix + PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, // + TwoColor } /// Base class for frames that use an interpolation bezier curve. @@ -546,6 +547,136 @@ namespace Spine { } } + public class TwoColorTimeline : CurveTimeline { + public const int ENTRIES = 8; + protected const int PREV_TIME = -8, PREV_R = -7, PREV_G = -6, PREV_B = -5, PREV_A = -4; + protected const int PREV_R2 = -3, PREV_G2 = -2, PREV_B2 = -1; + protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; + + internal float[] frames; // time, r, g, b, a, r2, g2, b2, ... + public float[] Frames { get { return frames; } } + + internal int slotIndex; + public int SlotIndex { + get { return slotIndex; } + set { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + slotIndex = value; + } + } + + public TwoColorTimeline (int frameCount) : + base(frameCount) { + frames = new float[frameCount * ENTRIES]; + } + + override public int PropertyId { + get { return ((int)TimelineType.TwoColor << 24) + slotIndex; } + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame (int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2) { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + R] = r; + frames[frameIndex + G] = g; + frames[frameIndex + B] = b; + frames[frameIndex + A] = a; + frames[frameIndex + R2] = r2; + frames[frameIndex + G2] = g2; + frames[frameIndex + B2] = b2; + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) { + Slot slot = skeleton.slots.Items[slotIndex]; + float[] frames = this.frames; + if (time < frames[0]) { // Time is before first frame. + if (setupPose) { +// slot.color.set(slot.data.color); +// slot.darkColor.set(slot.data.darkColor); + var slotData = slot.data; + slot.r = slotData.r; + slot.g = slotData.g; + slot.b = slotData.b; + slot.a = slotData.a; + slot.r2 = slotData.r2; + slot.g2 = slotData.g2; + slot.b2 = slotData.b2; + } + return; + } + + float r, g, b, a, r2, g2, b2; + if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. + int i = frames.Length; + r = frames[i + PREV_R]; + g = frames[i + PREV_G]; + b = frames[i + PREV_B]; + a = frames[i + PREV_A]; + r2 = frames[i + PREV_R2]; + g2 = frames[i + PREV_G2]; + b2 = frames[i + PREV_B2]; + } else { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + r = frames[frame + PREV_R]; + g = frames[frame + PREV_G]; + b = frames[frame + PREV_B]; + a = frames[frame + PREV_A]; + r2 = frames[frame + PREV_R2]; + g2 = frames[frame + PREV_G2]; + b2 = frames[frame + PREV_B2]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + r += (frames[frame + R] - r) * percent; + g += (frames[frame + G] - g) * percent; + b += (frames[frame + B] - b) * percent; + a += (frames[frame + A] - a) * percent; + r2 += (frames[frame + R2] - r2) * percent; + g2 += (frames[frame + G2] - g2) * percent; + b2 += (frames[frame + B2] - b2) * percent; + } + if (alpha == 1) { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + slot.r2 = r2; + slot.g2 = g2; + slot.b2 = b2; + } else { + float br, bg, bb, ba, br2, bg2, bb2; + if (setupPose) { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + br2 = slot.data.r2; + bg2 = slot.data.g2; + bb2 = slot.data.b2; + } else { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + br2 = slot.r2; + bg2 = slot.g2; + bb2 = slot.b2; + } + slot.r = br + ((r - br) * alpha); + slot.g = bg + ((g - bg) * alpha); + slot.b = bb + ((b - bb) * alpha); + slot.a = ba + ((a - ba) * alpha); + slot.r2 = br2 + ((r2 - br2) * alpha); + slot.g2 = bg2 + ((g2 - bg2) * alpha); + slot.b2 = bb2 + ((b2 - bb2) * alpha); + } + } + + } + public class AttachmentTimeline : Timeline { internal int slotIndex; internal float[] frames; diff --git a/spine-csharp/src/AnimationState.cs b/spine-csharp/src/AnimationState.cs index 8ba41b284..0acb79e4a 100644 --- a/spine-csharp/src/AnimationState.cs +++ b/spine-csharp/src/AnimationState.cs @@ -805,6 +805,11 @@ namespace Spine { /// based on the animation before this animation (if any). /// /// The mix duration must be set before is next called. + /// + /// When using with a + /// delay is set using the mix duration from the + /// + /// /// public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } diff --git a/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs b/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs index 345282847..0fbebe13b 100644 --- a/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs +++ b/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs @@ -39,7 +39,7 @@ namespace Spine { 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 + ")"); RegionAttachment attachment = new RegionAttachment(name); @@ -54,7 +54,7 @@ namespace Spine { 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 + ")"); MeshAttachment attachment = new MeshAttachment(name); @@ -73,12 +73,16 @@ namespace Spine { 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 AtlasRegion FindRegion (string name) { diff --git a/spine-csharp/src/Attachments/AttachmentLoader.cs b/spine-csharp/src/Attachments/AttachmentLoader.cs index 297fb1203..d1695c4c9 100644 --- a/spine-csharp/src/Attachments/AttachmentLoader.cs +++ b/spine-csharp/src/Attachments/AttachmentLoader.cs @@ -33,15 +33,17 @@ using System; namespace Spine { public interface AttachmentLoader { /// May be null to not load any attachment. - RegionAttachment NewRegionAttachment (Skin skin, String name, String path); + RegionAttachment NewRegionAttachment (Skin skin, String name, string path); /// May be null to not load any attachment. - MeshAttachment NewMeshAttachment (Skin skin, String name, String path); + MeshAttachment NewMeshAttachment (Skin skin, String name, string path); /// May be null to not load any attachment. - BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name); + BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name); /// May be null to not load any attachment - PathAttachment NewPathAttachment (Skin skin, String name); + PathAttachment NewPathAttachment (Skin skin, string name); + + PointAttachment NewPointAttachment (Skin skin, string name); } } diff --git a/spine-csharp/src/Attachments/AttachmentType.cs b/spine-csharp/src/Attachments/AttachmentType.cs index b7bb988ef..f0ee07054 100644 --- a/spine-csharp/src/Attachments/AttachmentType.cs +++ b/spine-csharp/src/Attachments/AttachmentType.cs @@ -30,6 +30,6 @@ namespace Spine { public enum AttachmentType { - Region, Boundingbox, Mesh, Linkedmesh, Path + Region, Boundingbox, Mesh, Linkedmesh, Path, Point } } diff --git a/spine-csharp/src/Attachments/MeshAttachment.cs b/spine-csharp/src/Attachments/MeshAttachment.cs index 7c2c0371a..301fc1699 100644 --- a/spine-csharp/src/Attachments/MeshAttachment.cs +++ b/spine-csharp/src/Attachments/MeshAttachment.cs @@ -43,6 +43,7 @@ namespace Spine { public int HullLength { get { return hulllength; } set { hulllength = value; } } public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + /// The UV pair for each vertex, normalized within the entire texture. public float[] UVs { get { return uvs; } set { uvs = value; } } public int[] Triangles { get { return triangles; } set { triangles = value; } } diff --git a/spine-csharp/src/Attachments/PointAttachment.cs b/spine-csharp/src/Attachments/PointAttachment.cs new file mode 100644 index 000000000..0fbe8dd6b --- /dev/null +++ b/spine-csharp/src/Attachments/PointAttachment.cs @@ -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 { + /// + /// 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. + ///

+ /// See Point Attachments in the Spine User Guide. + ///

+ 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, float x, float y, out float ox, out float oy) { + bone.LocalToWorld(x, 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; + } + } +} + diff --git a/spine-csharp/src/Attachments/VertexAttachment.cs b/spine-csharp/src/Attachments/VertexAttachment.cs index ffdcd9b28..ff20b04c8 100644 --- a/spine-csharp/src/Attachments/VertexAttachment.cs +++ b/spine-csharp/src/Attachments/VertexAttachment.cs @@ -55,7 +55,8 @@ namespace Spine { /// The number of world vertex values to output. Must be less than or equal to - start. /// The output world vertices. Must have a length greater than or equal to + . /// The index to begin writing values. - public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset) { + /// The number of entries between the value pairs written. + public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) { count += offset; Skeleton skeleton = slot.Skeleton; var deformArray = slot.attachmentVertices; @@ -66,7 +67,7 @@ namespace Spine { 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; @@ -81,7 +82,7 @@ namespace Spine { } Bone[] 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 +97,7 @@ namespace Spine { } } 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; diff --git a/spine-csharp/src/Bone.cs b/spine-csharp/src/Bone.cs index c760ec6ff..b329a3c47 100644 --- a/spine-csharp/src/Bone.cs +++ b/spine-csharp/src/Bone.cs @@ -31,6 +31,14 @@ using System; namespace Spine { + /// + /// Stores a bone's current pose. + /// + /// A bone has a local transform which is used to compute its world transform. A bone also has an applied transform, which is a + /// local transform that can be applied to compute the world transform. The local transform and applied transform may differ if a + /// constraint or application code modifies the world transform after it was computed from the local transform. + /// + /// public class Bone : IUpdatable { static public bool yDown; @@ -55,6 +63,7 @@ namespace Spine { public Skeleton Skeleton { get { return skeleton; } } public Bone Parent { get { return parent; } } public ExposedList Children { get { return children; } } + /// The local X translation. 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; } } @@ -240,34 +249,6 @@ namespace Spine { 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; - } - /// /// 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,6 +309,44 @@ namespace Spine { worldY = localX * c + localY * d + this.worldY; } + 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; + } + + 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; } diff --git a/spine-csharp/src/PathConstraint.cs b/spine-csharp/src/PathConstraint.cs index 240085ef7..462e42b0c 100644 --- a/spine-csharp/src/PathConstraint.cs +++ b/spine-csharp/src/PathConstraint.cs @@ -91,10 +91,10 @@ namespace Spine { 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; + 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++) diff --git a/spine-csharp/src/Skeleton.cs b/spine-csharp/src/Skeleton.cs index 43728e9dc..30ac0291d 100644 --- a/spine-csharp/src/Skeleton.cs +++ b/spine-csharp/src/Skeleton.cs @@ -194,15 +194,15 @@ namespace Spine { 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) { @@ -210,15 +210,25 @@ namespace Spine { var constrained = constraint.bones; int boneCount = constrained.Count; - for (int ii = 0; ii < boneCount; ii++) - SortBone(constrained.Items[ii]); +// 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[constrained.Count - 1]; + 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) { @@ -475,5 +485,53 @@ namespace Spine { public void Update (float delta) { time += delta; } + + /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. + /// The horizontal distance between the skeleton origin and the left side of the AABB. + /// The vertical distance between the skeleton origin and the bottom side of the AABB. + /// The width of the AABB + /// The height of the AABB. + /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. + 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 drawOrder = this.drawOrder; + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + for (int i = 0, n = drawOrder.Count; i < n; i++) { + Slot slot = drawOrder.Items[i]; + int verticesLength = 0; + float[] vertices = null; + Attachment attachment = slot.attachment; + var regionAttachment = attachment as RegionAttachment; + if (regionAttachment != null) { + verticesLength = 8; + if (temp.Length < 8) temp = new float[8]; + regionAttachment.ComputeWorldVertices(slot.bone, temp); + } else { + var meshAttachment = attachment as MeshAttachment; + if (meshAttachment != null) { + MeshAttachment mesh = meshAttachment; + verticesLength = mesh.WorldVerticesLength; + if (temp.Length < verticesLength) 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; + } } } diff --git a/spine-csharp/src/SkeletonBinary.cs b/spine-csharp/src/SkeletonBinary.cs index 14a670988..0134d1fd2 100644 --- a/spine-csharp/src/SkeletonBinary.cs +++ b/spine-csharp/src/SkeletonBinary.cs @@ -50,6 +50,7 @@ namespace Spine { 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 @@ namespace Spine { slotData.g = ((color & 0x00ff0000) >> 16) / 255f; slotData.b = ((color & 0x0000ff00) >> 8) / 255f; slotData.a = ((color & 0x000000ff)) / 255f; + + int darkColor = ReadInt(input); + if (darkColor != -1) { + slotData.hasSecondColor = true; + slotData.r2 = ((darkColor & 0xff000000) >> 24) / 255f; + slotData.g2 = ((darkColor & 0x00ff0000) >> 16) / 255f; + slotData.b2 = ((darkColor & 0x0000ff00) >> 8) / 255f; + } + slotData.attachmentName = ReadString(input); slotData.blendMode = (BlendMode)ReadVarint(input, true); skeletonData.slots.Add(slotData); @@ -425,7 +435,7 @@ namespace Spine { 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 +446,21 @@ namespace Spine { 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; + } } return null; } @@ -499,6 +523,15 @@ namespace Spine { 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 +549,26 @@ namespace Spine { 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); + float r2 = ((color2 & 0xff000000) >> 24) / 255f; + float g2 = ((color2 & 0x00ff0000) >> 16) / 255f; + float b2 = ((color2 & 0x0000ff00) >> 8) / 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; } } diff --git a/spine-csharp/src/SkeletonJson.cs b/spine-csharp/src/SkeletonJson.cs index bbcb973be..272a55932 100644 --- a/spine-csharp/src/SkeletonJson.cs +++ b/spine-csharp/src/SkeletonJson.cs @@ -146,6 +146,14 @@ namespace Spine { data.b = ToColor(color, 2); data.a = ToColor(color, 3); } + + if (slotMap.ContainsKey("dark")) { + var color2 = (String)slotMap["dark"]; + data.r2 = ToColor(color2, 0); + data.g2 = ToColor(color2, 1); + data.b2 = ToColor(color2, 2); + data.hasSecondColor = true; + } data.attachmentName = GetString(slotMap, "attachment", null); if (slotMap.ContainsKey("blend")) @@ -394,6 +402,17 @@ namespace Spine { 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; + } } return null; } @@ -441,7 +460,19 @@ namespace Spine { foreach (KeyValuePair timelineEntry in timelineMap) { var values = (List)timelineEntry.Value; var timelineName = (String)timelineEntry.Key; - if (timelineName == "color") { + if (timelineName == "attachment") { + var timeline = new AttachmentTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) { + float time = (float)valueMap["time"]; + 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; @@ -456,17 +487,22 @@ namespace Spine { 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 valueMap in values) { float time = (float)valueMap["time"]; - timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]); + String c = (String)valueMap["light"]; + String c2 = (String)valueMap["dark"]; + timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3), + ToColor(c2, 0), ToColor(c2, 1), ToColor(c2, 2)); + 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) * ColorTimeline.ENTRIES]); } else throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); diff --git a/spine-csharp/src/Slot.cs b/spine-csharp/src/Slot.cs index e66956f87..c4cff8b0c 100644 --- a/spine-csharp/src/Slot.cs +++ b/spine-csharp/src/Slot.cs @@ -35,6 +35,8 @@ namespace Spine { internal SlotData data; internal Bone bone; internal float r, g, b, a; + internal float r2, g2, b2; + internal bool hasSecondColor; internal Attachment attachment; internal float attachmentTime; internal ExposedList attachmentVertices = new ExposedList(); @@ -47,6 +49,11 @@ namespace Spine { public float B { get { return b; } set { b = value; } } public float A { get { return a; } set { a = value; } } + public float R2 { get { return r2; } set { r2 = value; } } + public float G2 { get { return g2; } set { g2 = value; } } + public float B2 { get { return b2; } set { b2 = value; } } + public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } + /// May be null. public Attachment Attachment { get { return attachment; } diff --git a/spine-csharp/src/SlotData.cs b/spine-csharp/src/SlotData.cs index 4f95c0355..10eb5102d 100644 --- a/spine-csharp/src/SlotData.cs +++ b/spine-csharp/src/SlotData.cs @@ -33,19 +33,27 @@ using System; namespace Spine { public class SlotData { internal int index; - internal String name; + internal string name; internal BoneData boneData; internal float r = 1, g = 1, b = 1, a = 1; - internal String attachmentName; + internal float r2 = 0, g2 = 0, b2 = 0; + internal bool hasSecondColor = false; + internal string attachmentName; internal BlendMode blendMode; public int Index { get { return index; } } - public String Name { get { return name; } } + public string Name { get { return name; } } public BoneData BoneData { get { return boneData; } } public float R { get { return r; } set { r = value; } } public float G { get { return g; } set { g = value; } } public float B { get { return b; } set { b = value; } } public float A { get { return a; } set { a = value; } } + + public float R2 { get { return r2; } set { r2 = value; } } + public float G2 { get { return g2; } set { g2 = value; } } + public float B2 { get { return b2; } set { b2 = value; } } + public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } + /// May be null. public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } diff --git a/spine-csharp/src/TransformConstraint.cs b/spine-csharp/src/TransformConstraint.cs index 3e368f14c..401778cf2 100644 --- a/spine-csharp/src/TransformConstraint.cs +++ b/spine-csharp/src/TransformConstraint.cs @@ -67,15 +67,28 @@ namespace Spine { } public void Update () { + if (data.local) { + if (data.relative) + ApplyRelativeLocal(); + else + ApplyAbsoluteLocal(); + } else { + if (data.relative) + ApplyRelativeWorld(); + else + ApplyAbsoluteWorld(); + } + } + + void ApplyAbsoluteWorld () { float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; Bone target = this.target; float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = (ta * td - tb * tc > 0) ? MathUtils.DegRad : -MathUtils.DegRad; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; var bones = this.bones; - var bonesItems = bones.Items; for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; + Bone bone = bones.Items[i]; bool modified = false; if (rotateMix != 0) { @@ -94,22 +107,20 @@ namespace Spine { } if (translateMix != 0) { - float tempx, tempy; - target.LocalToWorld(data.offsetX, data.offsetY, out tempx, out tempy); - bone.worldX += (tempx - bone.worldX) * translateMix; - bone.worldY += (tempy - bone.worldY) * translateMix; + float tx, ty; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += (tx - bone.worldX) * translateMix; + bone.worldY += (ty - bone.worldY) * translateMix; modified = true; } if (scaleMix > 0) { float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); - float ts = (float)Math.Sqrt(ta * ta + tc * tc); - if (s > 0.00001f) s = (s + (ts - s + data.offsetScaleX) * scaleMix) / s; + if (s > 0.00001f) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * scaleMix) / s; bone.a *= s; bone.c *= s; s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); - ts = (float)Math.Sqrt(tb * tb + td * td); - if (s > 0.00001f) s = (s + (ts - s + data.offsetScaleY) * scaleMix) / s; + if (s > 0.00001f) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * scaleMix) / s; bone.b *= s; bone.d *= s; modified = true; @@ -133,6 +144,137 @@ namespace Spine { } } + void ApplyRelativeWorld () { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + var bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) { + Bone bone = bones.Items[i]; + bool modified = false; + + if (rotateMix != 0) { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r *= rotateMix; + float cos = MathUtils.Cos(r), sin = MathUtils.Sin(r); + bone.a = cos * a - sin * c; + bone.b = cos * b - sin * d; + bone.c = sin * a + cos * c; + bone.d = sin * b + cos * d; + modified = true; + } + + if (translateMix != 0) { + float tx, ty; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += tx * translateMix; + bone.worldY += ty * translateMix; + modified = true; + } + + if (scaleMix > 0) { + float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * scaleMix + 1; + bone.a *= s; + bone.c *= s; + s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1; + bone.b *= s; + bone.d *= s; + modified = true; + } + + if (shearMix > 0) { + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + float b = bone.b, d = bone.d; + r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * shearMix; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + modified = true; + } + + if (modified) bone.appliedValid = false; + } + } + + void ApplyAbsoluteLocal () { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + if (!target.appliedValid) target.UpdateAppliedTransform(); + var bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) { + Bone bone = bones.Items[i]; + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + + float rotation = bone.arotation; + if (rotateMix != 0) { + float r = target.arotation - rotation + data.offsetRotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + rotation += r * rotateMix; + } + + float x = bone.ax, y = bone.ay; + if (translateMix != 0) { + x += (target.ax - x + data.offsetX) * translateMix; + y += (target.ay - y + data.offsetY) * translateMix; + } + + float scaleX = bone.ascaleX, scaleY = bone.ascaleY; + if (scaleMix > 0) { + if (scaleX > 0.00001f) scaleX = (scaleX + (target.ascaleX - scaleX + data.offsetScaleX) * scaleMix) / scaleX; + if (scaleY > 0.00001f) scaleY = (scaleY + (target.ascaleY - scaleY + data.offsetScaleY) * scaleMix) / scaleY; + } + + float shearY = bone.ashearY; + if (shearMix > 0) { + float r = target.ashearY - shearY + data.offsetShearY; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + bone.shearY += r * shearMix; + } + + bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } + + void ApplyRelativeLocal () { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + if (!target.appliedValid) target.UpdateAppliedTransform(); + var bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) { + Bone bone = bones.Items[i]; + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + + float rotation = bone.arotation; + if (rotateMix != 0) rotation += (target.arotation + data.offsetRotation) * rotateMix; + + float x = bone.ax, y = bone.ay; + if (translateMix != 0) { + x += (target.ax + data.offsetX) * translateMix; + y += (target.ay + data.offsetY) * translateMix; + } + + float scaleX = bone.ascaleX, scaleY = bone.ascaleY; + if (scaleMix > 0) { + if (scaleX > 0.00001f) scaleX *= ((target.ascaleX - 1 + data.offsetScaleX) * scaleMix) + 1; + if (scaleY > 0.00001f) scaleY *= ((target.ascaleY - 1 + data.offsetScaleY) * scaleMix) + 1; + } + + float shearY = bone.ashearY; + if (shearMix > 0) shearY += (target.ashearY + data.offsetShearY) * shearMix; + + bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } + override public String ToString () { return data.name; } diff --git a/spine-csharp/src/TransformConstraintData.cs b/spine-csharp/src/TransformConstraintData.cs index 71fbd30f9..248cb4c58 100644 --- a/spine-csharp/src/TransformConstraintData.cs +++ b/spine-csharp/src/TransformConstraintData.cs @@ -38,6 +38,7 @@ namespace Spine { internal BoneData target; internal float rotateMix, translateMix, scaleMix, shearMix; internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; + internal bool relative, local; public String Name { get { return name; } } public int Order { get { return order; } set { order = value; } } @@ -55,6 +56,9 @@ namespace Spine { public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } + public bool Relative { get { return relative; } set { relative = value; } } + public bool Local { get { return local; } set { local = value; } } + public TransformConstraintData (String name) { if (name == null) throw new ArgumentNullException("name", "name cannot be null."); this.name = name;