From d0625e06a0dd34e0dec708b0b7c13f322b7cdd37 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sun, 29 Mar 2015 00:49:31 +0100 Subject: [PATCH] Added binary format support for C#. Also fixed up line endings. --- spine-csharp/spine-csharp.csproj | 1 + spine-csharp/spine-csharp_xna.csproj | 1 + spine-csharp/src/Animation.cs | 12 +- spine-csharp/src/SkeletonBinary.cs | 663 +++++++++++++++++++++++++++ spine-csharp/src/SkeletonData.cs | 2 +- spine-csharp/src/SkeletonJson.cs | 41 +- spine-xna/example/data/raptor.skel | Bin 0 -> 41027 bytes spine-xna/example/src/ExampleGame.cs | 21 +- 8 files changed, 710 insertions(+), 31 deletions(-) create mode 100644 spine-csharp/src/SkeletonBinary.cs create mode 100644 spine-xna/example/data/raptor.skel 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 0000000000000000000000000000000000000000..507d86e2e9ae3c329c49f2bc913cf1666980cc98 GIT binary patch literal 41027 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 literal 0 HcmV?d00001 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.