diff --git a/spine-csharp/spine-csharp.csproj b/spine-csharp/spine-csharp.csproj index 9cbdb5373..c094972ca 100644 --- a/spine-csharp/spine-csharp.csproj +++ b/spine-csharp/spine-csharp.csproj @@ -75,6 +75,7 @@ + diff --git a/spine-csharp/spine-csharp_xna.csproj b/spine-csharp/spine-csharp_xna.csproj index 85f5f33ca..9566f3d61 100644 --- a/spine-csharp/spine-csharp_xna.csproj +++ b/spine-csharp/spine-csharp_xna.csproj @@ -111,6 +111,7 @@ + diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs index 672126e43..33860611f 100644 --- a/spine-csharp/src/Animation.cs +++ b/spine-csharp/src/Animation.cs @@ -365,7 +365,7 @@ namespace Spine { } /// Sets the time and value of the specified keyframe. - public void setFrame (int frameIndex, float time, float r, float g, float b, float a) { + public void SetFrame (int frameIndex, float time, float r, float g, float b, float a) { frameIndex *= 5; frames[frameIndex] = time; frames[frameIndex + 1] = r; @@ -433,7 +433,7 @@ namespace Spine { } /// Sets the time and value of the specified keyframe. - public void setFrame (int frameIndex, float time, String attachmentName) { + public void SetFrame (int frameIndex, float time, String attachmentName) { frames[frameIndex] = time; attachmentNames[frameIndex] = attachmentName; } @@ -469,7 +469,7 @@ namespace Spine { } /// Sets the time and value of the specified keyframe. - public void setFrame (int frameIndex, float time, Event e) { + public void SetFrame (int frameIndex, float time, Event e) { frames[frameIndex] = time; events[frameIndex] = e; } @@ -518,7 +518,7 @@ namespace Spine { /// Sets the time and value of the specified keyframe. /// May be null to use bind pose draw order. - public void setFrame (int frameIndex, float time, int[] drawOrder) { + public void SetFrame (int frameIndex, float time, int[] drawOrder) { frames[frameIndex] = time; drawOrders[frameIndex] = drawOrder; } @@ -564,7 +564,7 @@ namespace Spine { } /// Sets the time and value of the specified keyframe. - public void setFrame (int frameIndex, float time, float[] vertices) { + public void SetFrame (int frameIndex, float time, float[] vertices) { frames[frameIndex] = time; frameVertices[frameIndex] = vertices; } @@ -641,7 +641,7 @@ namespace Spine { } /** Sets the time, mix and bend direction of the specified keyframe. */ - public void setFrame (int frameIndex, float time, float mix, int bendDirection) { + public void SetFrame (int frameIndex, float time, float mix, int bendDirection) { frameIndex *= 3; frames[frameIndex] = time; frames[frameIndex + 1] = mix; diff --git a/spine-csharp/src/SkeletonBinary.cs b/spine-csharp/src/SkeletonBinary.cs new file mode 100644 index 000000000..943778f37 --- /dev/null +++ b/spine-csharp/src/SkeletonBinary.cs @@ -0,0 +1,663 @@ +/****************************************************************************** + * Spine Runtimes Software License + * Version 2.1 + * + * Copyright (c) 2013, Esoteric Software + * All rights reserved. + * + * You are granted a perpetual, non-exclusive, non-sublicensable and + * non-transferable license to install, execute and perform the Spine Runtimes + * Software (the "Software") solely for internal use. Without the written + * permission of Esoteric Software (typically granted by licensing Spine), you + * may not (a) modify, translate, adapt or otherwise create derivative works, + * improvements of the Software or develop new applications using the Software + * 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 SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using System; +using System.IO; +using System.Collections.Generic; + +#if WINDOWS_STOREAPP +using System.Threading.Tasks; +using Windows.Storage; +#endif + +namespace Spine { + public class SkeletonBinary { + public const int TIMELINE_SCALE = 0; + public const int TIMELINE_ROTATE = 1; + public const int TIMELINE_TRANSLATE = 2; + public const int TIMELINE_ATTACHMENT = 3; + public const int TIMELINE_COLOR = 4; + public const int TIMELINE_FLIPX = 5; + public const int TIMELINE_FLIPY = 6; + + public const int CURVE_LINEAR = 0; + public const int CURVE_STEPPED = 1; + public const int CURVE_BEZIER = 2; + + private AttachmentLoader attachmentLoader; + public float Scale { get; set; } + private char[] chars = new char[32]; + private byte[] buffer = new byte[4]; + + public SkeletonBinary (params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) { + } + + public SkeletonBinary (AttachmentLoader attachmentLoader) { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } + +#if WINDOWS_STOREAPP + private async Task ReadFile(string path) { + var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; + using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) { + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.Name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } + + public SkeletonData ReadSkeletonData (String path) { + return this.ReadFile(path).Result; + } +#else + public SkeletonData ReadSkeletonData (String path) { +#if WINDOWS_PHONE + using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) + { +#else + using (var input = new BufferedStream(new FileStream(path, FileMode.Open))) { +#endif + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } +#endif + + public SkeletonData ReadSkeletonData (BufferedStream input) { + if (input == null) throw new ArgumentNullException("input cannot be null."); + float scale = Scale; + + var skeletonData = new SkeletonData(); + skeletonData.hash = ReadString(input); + if (skeletonData.hash.Length == 0) skeletonData.hash = null; + skeletonData.version = ReadString(input); + if (skeletonData.version.Length == 0) skeletonData.version = null; + skeletonData.width = ReadFloat(input); + skeletonData.height = ReadFloat(input); + + bool nonessential = ReadBoolean(input); + + if (nonessential) { + skeletonData.imagesPath = ReadString(input); + if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null; + } + + // Bones. + for (int i = 0, n = ReadInt(input, true); i < n; i++) { + String name = ReadString(input); + BoneData parent = null; + int parentIndex = ReadInt(input, true) - 1; + if (parentIndex != -1) parent = skeletonData.bones[parentIndex]; + BoneData boneData = new BoneData(name, parent); + boneData.x = ReadFloat(input) * scale; + boneData.y = ReadFloat(input) * scale; + boneData.scaleX = ReadFloat(input); + boneData.scaleY = ReadFloat(input); + boneData.rotation = ReadFloat(input); + boneData.length = ReadFloat(input) * scale; + boneData.flipX = ReadBoolean(input); + boneData.flipY = ReadBoolean(input); + boneData.inheritScale = ReadBoolean(input); + boneData.inheritRotation = ReadBoolean(input); + if (nonessential) ReadInt(input); // Skip bone color. + skeletonData.bones.Add(boneData); + } + + // IK constraints. + for (int i = 0, n = ReadInt(input, true); i < n; i++) { + IkConstraintData ikConstraintData = new IkConstraintData(ReadString(input)); + for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) + ikConstraintData.bones.Add(skeletonData.bones[ReadInt(input, true)]); + ikConstraintData.target = skeletonData.bones[ReadInt(input, true)]; + ikConstraintData.mix = ReadFloat(input); + ikConstraintData.bendDirection = ReadSByte(input); + skeletonData.ikConstraints.Add(ikConstraintData); + } + + // Slots. + for (int i = 0, n = ReadInt(input, true); i < n; i++) { + String slotName = ReadString(input); + BoneData boneData = skeletonData.bones[ReadInt(input, true)]; + SlotData slotData = new SlotData(slotName, boneData); + int color = ReadInt(input); + slotData.r = ((color & 0xff000000) >> 24) / 255f; + slotData.g = ((color & 0x00ff0000) >> 16) / 255f; + slotData.b = ((color & 0x0000ff00) >> 8) / 255f; + slotData.a = ((color & 0x000000ff)) / 255f; + slotData.attachmentName = ReadString(input); + slotData.additiveBlending = ReadBoolean(input); + skeletonData.slots.Add(slotData); + } + + // Default skin. + Skin defaultSkin = ReadSkin(input, "default", nonessential); + if (defaultSkin != null) { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.Add(defaultSkin); + } + + // Skins. + for (int i = 0, n = ReadInt(input, true); i < n; i++) + skeletonData.skins.Add(ReadSkin(input, ReadString(input), nonessential)); + + // Events. + for (int i = 0, n = ReadInt(input, true); i < n; i++) { + EventData eventData = new EventData(ReadString(input)); + eventData.Int = ReadInt(input, false); + eventData.Float = ReadFloat(input); + eventData.String = ReadString(input); + skeletonData.events.Add(eventData); + } + + // Animations. + for (int i = 0, n = ReadInt(input, true); i < n; i++) + ReadAnimation(ReadString(input), input, skeletonData); + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + /** @return May be null. */ + private Skin ReadSkin (BufferedStream input, String skinName, bool nonessential) { + int slotCount = ReadInt(input, true); + if (slotCount == 0) return null; + Skin skin = new Skin(skinName); + for (int i = 0; i < slotCount; i++) { + int slotIndex = ReadInt(input, true); + for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) { + String name = ReadString(input); + skin.AddAttachment(slotIndex, name, ReadAttachment(input, skin, name, nonessential)); + } + } + return skin; + } + + private Attachment ReadAttachment (BufferedStream input, Skin skin, String attachmentName, bool nonessential) { + float scale = Scale; + + String name = ReadString(input); + if (name == null) name = attachmentName; + + switch ((AttachmentType)input.ReadByte()) { + case AttachmentType.region: { + String path = ReadString(input); + if (path == null) path = name; + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = ReadFloat(input) * scale; + region.y = ReadFloat(input) * scale; + region.scaleX = ReadFloat(input); + region.scaleY = ReadFloat(input); + region.rotation = ReadFloat(input); + region.width = ReadFloat(input) * scale; + region.height = ReadFloat(input) * scale; + int color = ReadInt(input); + region.r = ((color & 0xff000000) >> 24) / 255f; + region.g = ((color & 0x00ff0000) >> 16) / 255f; + region.b = ((color & 0x0000ff00) >> 8) / 255f; + region.a = ((color & 0x000000ff)) / 255f; + region.UpdateOffset(); + return region; + } + case AttachmentType.boundingbox: { + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.vertices = ReadFloatArray(input, scale); + return box; + } + case AttachmentType.mesh: { + String path = ReadString(input); + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.regionUVs = ReadFloatArray(input, 1); + mesh.triangles = ReadShortArray(input); + mesh.vertices = ReadFloatArray(input, scale); + mesh.UpdateUVs(); + int color = ReadInt(input); + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.HullLength = ReadInt(input, true) * 2; + if (nonessential) { + mesh.Edges = ReadIntArray(input); + mesh.Width = ReadFloat(input) * scale; + mesh.Height = ReadFloat(input) * scale; + } + return mesh; + } + case AttachmentType.skinnedmesh: { + String path = ReadString(input); + if (path == null) path = name; + SkinnedMeshAttachment mesh = attachmentLoader.NewSkinnedMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + float[] uvs = ReadFloatArray(input, 1); + int[] triangles = ReadShortArray(input); + + int vertexCount = ReadInt(input, true); + var weights = new List(uvs.Length * 3 * 3); + var bones = new List(uvs.Length * 3); + for (int i = 0; i < vertexCount; i++) { + int boneCount = (int)ReadFloat(input); + bones.Add(boneCount); + for (int nn = i + boneCount * 4; i < nn; i += 4) { + bones.Add((int)ReadFloat(input)); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input)); + } + } + mesh.bones = bones.ToArray(); + mesh.weights = weights.ToArray(); + mesh.triangles = triangles; + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + int color = ReadInt(input); + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.HullLength = ReadInt(input, true) * 2; + if (nonessential) { + mesh.Edges = ReadIntArray(input); + mesh.Width = ReadFloat(input) * scale; + mesh.Height = ReadFloat(input) * scale; + } + return mesh; + } + } + return null; + } + + private float[] ReadFloatArray (BufferedStream input, float scale) { + int n = ReadInt(input, true); + float[] array = new float[n]; + if (scale == 1) { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input); + } else { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input) * scale; + } + return array; + } + + private int[] ReadShortArray (BufferedStream input) { + int n = ReadInt(input, true); + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = (input.ReadByte() << 8) + input.ReadByte(); + return array; + } + + private int[] ReadIntArray (BufferedStream input) { + int n = ReadInt(input, true); + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = ReadInt(input, true); + return array; + } + + private void ReadAnimation (String name, BufferedStream input, SkeletonData skeletonData) { + var timelines = new List(); + float scale = Scale; + float duration = 0; + + // Slot timelines. + for (int i = 0, n = ReadInt(input, true); i < n; i++) { + int slotIndex = ReadInt(input, true); + for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) { + int timelineType = input.ReadByte(); + int frameCount = ReadInt(input, true); + switch (timelineType) { + case TIMELINE_COLOR: { + ColorTimeline timeline = new ColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + 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; + timeline.SetFrame(frameIndex, time, r, g, b, a); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 5 - 5]); + break; + } + case TIMELINE_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; + } + } + } + } + + // Bone timelines. + for (int i = 0, n = ReadInt(input, true); i < n; i++) { + int boneIndex = ReadInt(input, true); + for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) { + int timelineType = input.ReadByte(); + int frameCount = ReadInt(input, true); + switch (timelineType) { + case TIMELINE_ROTATE: { + RotateTimeline timeline = new RotateTimeline(frameCount); + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 2 - 2]); + break; + } + case TIMELINE_TRANSLATE: + case TIMELINE_SCALE: { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineType == TIMELINE_SCALE) + timeline = new ScaleTimeline(frameCount); + else { + timeline = new TranslateTimeline(frameCount); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) + * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]); + break; + } + case TIMELINE_FLIPX: + case TIMELINE_FLIPY: { + FlipXTimeline timeline = timelineType == TIMELINE_FLIPX ? new FlipXTimeline(frameCount) : new FlipYTimeline( + frameCount); + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + timeline.SetFrame(frameIndex, ReadFloat(input), ReadBoolean(input)); + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 2 - 2]); + break; + } + } + } + } + + // IK timelines. + for (int i = 0, n = ReadInt(input, true); i < n; i++) { + IkConstraintData ikConstraint = skeletonData.ikConstraints[ReadInt(input, true)]; + int frameCount = ReadInt(input, true); + IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); + timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint); + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]); + } + + // FFD timelines. + for (int i = 0, n = ReadInt(input, true); i < n; i++) { + Skin skin = skeletonData.skins[ReadInt(input, true)]; + for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) { + int slotIndex = ReadInt(input, true); + for (int iii = 0, nnn = ReadInt(input, true); iii < nnn; iii++) { + Attachment attachment = skin.GetAttachment(slotIndex, ReadString(input)); + int frameCount = ReadInt(input, true); + FFDTimeline timeline = new FFDTimeline(frameCount); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { + float time = ReadFloat(input); + + float[] vertices; + int vertexCount; + if (attachment is MeshAttachment) + vertexCount = ((MeshAttachment)attachment).vertices.Length; + else + vertexCount = ((SkinnedMeshAttachment)attachment).weights.Length / 3 * 2; + + int end = ReadInt(input, true); + if (end == 0) { + if (attachment is MeshAttachment) + vertices = ((MeshAttachment)attachment).vertices; + else + vertices = new float[vertexCount]; + } else { + vertices = new float[vertexCount]; + int start = ReadInt(input, true); + end += start; + if (scale == 1) { + for (int v = start; v < end; v++) + vertices[v] = ReadFloat(input); + } else { + for (int v = start; v < end; v++) + vertices[v] = ReadFloat(input) * scale; + } + if (attachment is MeshAttachment) { + float[] meshVertices = ((MeshAttachment)attachment).vertices; + for (int v = 0, vn = vertices.Length; v < vn; v++) + vertices[v] += meshVertices[v]; + } + } + + timeline.SetFrame(frameIndex, time, vertices); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + } + } + } + + // Draw order timeline. + int drawOrderCount = ReadInt(input, true); + if (drawOrderCount > 0) { + DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); + int slotCount = skeletonData.slots.Count; + for (int i = 0; i < drawOrderCount; i++) { + int offsetCount = ReadInt(input, true); + int[] drawOrder = new int[slotCount]; + for (int ii = slotCount - 1; ii >= 0; ii--) + drawOrder[ii] = -1; + int[] unchanged = new int[slotCount - offsetCount]; + int originalIndex = 0, unchangedIndex = 0; + for (int ii = 0; ii < offsetCount; ii++) { + int slotIndex = ReadInt(input, true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + ReadInt(input, true)] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int ii = slotCount - 1; ii >= 0; ii--) + if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; + timeline.SetFrame(i, ReadFloat(input), drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); + } + + // Event timeline. + int eventCount = ReadInt(input, true); + if (eventCount > 0) { + EventTimeline timeline = new EventTimeline(eventCount); + for (int i = 0; i < eventCount; i++) { + float time = ReadFloat(input); + EventData eventData = skeletonData.events[ReadInt(input, true)]; + Event e = new Event(eventData); + e.Int = ReadInt(input, false); + e.Float = ReadFloat(input); + e.String = ReadBoolean(input) ? ReadString(input) : eventData.String; + timeline.SetFrame(i, time, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[eventCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + private void ReadCurve (BufferedStream input, int frameIndex, CurveTimeline timeline) { + switch (input.ReadByte()) { + case CURVE_STEPPED: + timeline.SetStepped(frameIndex); + break; + case CURVE_BEZIER: + timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + break; + } + } + + private sbyte ReadSByte (BufferedStream input) { + int value = input.ReadByte(); + if (value == -1) throw new EndOfStreamException(); + return (sbyte)value; + } + + private bool ReadBoolean (BufferedStream input) { + return input.ReadByte() != 0; + } + + private float ReadFloat (BufferedStream input) { + buffer[3] = (byte)input.ReadByte(); + buffer[2] = (byte)input.ReadByte(); + buffer[1] = (byte)input.ReadByte(); + buffer[0] = (byte)input.ReadByte(); + return BitConverter.ToSingle(buffer, 0); + } + + private int ReadInt (BufferedStream input) { + return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); + } + + private int ReadInt (BufferedStream input, bool optimizePositive) { + int b = input.ReadByte(); + int result = b & 0x7F; + if ((b & 0x80) != 0) { + b = input.ReadByte(); + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) { + b = input.ReadByte(); + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) { + b = input.ReadByte(); + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) { + b = input.ReadByte(); + result |= (b & 0x7F) << 28; + } + } + } + } + return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); + } + + private string ReadString (BufferedStream input) { + int charCount = ReadInt(input, true); + switch (charCount) { + case 0: + return null; + case 1: + return ""; + } + charCount--; + char[] chars = this.chars; + if (chars.Length < charCount) this.chars = chars = new char[charCount]; + // Try to read 7 bit ASCII chars. + int charIndex = 0; + int b = 0; + while (charIndex < charCount) { + b = input.ReadByte(); + if (b > 127) break; + chars[charIndex++] = (char)b; + } + // If a char was not ASCII, finish with slow path. + if (charIndex < charCount) ReadUtf8_slow(input, charCount, charIndex, b); + return new String(chars, 0, charCount); + } + + private void ReadUtf8_slow (BufferedStream input, int charCount, int charIndex, int b) { + char[] chars = this.chars; + while (true) { + switch (b >> 4) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + chars[charIndex] = (char)b; + break; + case 12: + case 13: + chars[charIndex] = (char)((b & 0x1F) << 6 | input.ReadByte() & 0x3F); + break; + case 14: + chars[charIndex] = (char)((b & 0x0F) << 12 | (input.ReadByte() & 0x3F) << 6 | input.ReadByte() & 0x3F); + break; + } + if (++charIndex >= charCount) break; + b = input.ReadByte() & 0xFF; + } + } + } +} diff --git a/spine-csharp/src/SkeletonData.cs b/spine-csharp/src/SkeletonData.cs index 48b500363..5d84501e5 100644 --- a/spine-csharp/src/SkeletonData.cs +++ b/spine-csharp/src/SkeletonData.cs @@ -42,7 +42,7 @@ namespace Spine { internal List animations = new List(); internal List ikConstraints = new List(); internal float width, height; - internal String version, hash; + internal String version, hash, imagesPath; public String Name { get { return name; } set { name = value; } } public List Bones { get { return bones; } } // Ordered parents first. diff --git a/spine-csharp/src/SkeletonJson.cs b/spine-csharp/src/SkeletonJson.cs index b83f88a82..4bebe56f5 100644 --- a/spine-csharp/src/SkeletonJson.cs +++ b/spine-csharp/src/SkeletonJson.cs @@ -53,25 +53,24 @@ namespace Spine { } #if WINDOWS_STOREAPP - private async Task ReadFile(string path) { - var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; - var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); - using (var reader = new StreamReader(await file.OpenStreamForReadAsync().ConfigureAwait(false))) { - SkeletonData skeletonData = ReadSkeletonData(reader); - skeletonData.Name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } + private async Task ReadFile(string path) { + var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; + var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); + using (var reader = new StreamReader(await file.OpenStreamForReadAsync().ConfigureAwait(false))) { + SkeletonData skeletonData = ReadSkeletonData(reader); + skeletonData.Name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } public SkeletonData ReadSkeletonData (String path) { - return this.ReadFile(path).Result; + return this.ReadFile(path).Result; } #else public SkeletonData ReadSkeletonData (String path) { #if WINDOWS_PHONE - Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); - using (StreamReader reader = new StreamReader(stream)) - { + Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); + using (StreamReader reader = new StreamReader(stream)) { #else using (StreamReader reader = new StreamReader(path)) { #endif @@ -209,7 +208,9 @@ namespace Spine { skeletonData.bones.TrimExcess(); skeletonData.slots.TrimExcess(); skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); return skeletonData; } @@ -369,7 +370,7 @@ namespace Spine { return (String)map[name]; } - public static float ToColor (String hexString, int colorIndex) { + private float ToColor (String hexString, int colorIndex) { if (hexString.Length != 8) throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString); return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; @@ -397,7 +398,7 @@ namespace Spine { foreach (Dictionary 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)); + timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); ReadCurve(timeline, frameIndex, valueMap); frameIndex++; } @@ -411,7 +412,7 @@ namespace Spine { int frameIndex = 0; foreach (Dictionary valueMap in values) { float time = (float)valueMap["time"]; - timeline.setFrame(frameIndex++, time, (String)valueMap["name"]); + timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]); } timelines.Add(timeline); duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); @@ -502,7 +503,7 @@ namespace Spine { float time = (float)valueMap["time"]; float mix = valueMap.ContainsKey("mix") ? (float)valueMap["mix"] : 1; bool bendPositive = valueMap.ContainsKey("bendPositive") ? (bool)valueMap["bendPositive"] : true; - timeline.setFrame(frameIndex, time, mix, bendPositive ? 1 : -1); + timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); ReadCurve(timeline, frameIndex, valueMap); frameIndex++; } @@ -556,7 +557,7 @@ namespace Spine { } } - timeline.setFrame(frameIndex, (float)valueMap["time"], vertices); + timeline.SetFrame(frameIndex, (float)valueMap["time"], vertices); ReadCurve(timeline, frameIndex, valueMap); frameIndex++; } @@ -598,7 +599,7 @@ namespace Spine { for (int i = slotCount - 1; i >= 0; i--) if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; } - timeline.setFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); + timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); } timelines.Add(timeline); duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); @@ -615,7 +616,7 @@ namespace Spine { e.Int = GetInt(eventMap, "int", eventData.Int); e.Float = GetFloat(eventMap, "float", eventData.Float); e.String = GetString(eventMap, "string", eventData.String); - timeline.setFrame(frameIndex++, (float)eventMap["time"], e); + timeline.SetFrame(frameIndex++, (float)eventMap["time"], e); } timelines.Add(timeline); duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); diff --git a/spine-xna/example/data/raptor.skel b/spine-xna/example/data/raptor.skel new file mode 100644 index 000000000..507d86e2e Binary files /dev/null and b/spine-xna/example/data/raptor.skel differ diff --git a/spine-xna/example/src/ExampleGame.cs b/spine-xna/example/src/ExampleGame.cs index e07434f56..8671f3b10 100644 --- a/spine-xna/example/src/ExampleGame.cs +++ b/spine-xna/example/src/ExampleGame.cs @@ -76,12 +76,25 @@ namespace Spine { // String name = "spineboy"; // String name = "goblins-mesh"; String name = "raptor"; + bool binaryData = true; Atlas atlas = new Atlas(assetsFolder + name + ".atlas", new XnaTextureLoader(GraphicsDevice)); - SkeletonJson json = new SkeletonJson(atlas); - if (name == "spineboy") json.Scale = 0.6f; - if (name == "raptor") json.Scale = 0.5f; - skeleton = new Skeleton(json.ReadSkeletonData(assetsFolder + name + ".json")); + + float scale = 1; + if (name == "spineboy") scale = 0.6f; + if (name == "raptor") scale = 0.5f; + + SkeletonData skeletonData; + if (binaryData) { + SkeletonBinary binary = new SkeletonBinary(atlas); + binary.Scale = scale; + skeletonData = binary.ReadSkeletonData(assetsFolder + name + ".skel"); + } else { + SkeletonJson json = new SkeletonJson(atlas); + json.Scale = scale; + skeletonData = json.ReadSkeletonData(assetsFolder + name + ".json"); + } + skeleton = new Skeleton(skeletonData); if (name == "goblins-mesh") skeleton.SetSkin("goblin"); // Define mixing between animations.