diff --git a/CHANGELOG.md b/CHANGELOG.md index 12872185e..0f7590cf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,22 @@ ## C# ## +* **Additions** + * Full support for sequences. + * `RegionAttachment` and `MeshAttachment` now provide a `Region` property. Use this property instead of the removed `RendererObject` property (see section *Breaking Changes* below). + +* **Breaking changes** + * Removed `RendererObject` property from `RegionAttachment` and `MeshAttachment`. Use `attachment.Region` property instead. Removed removed `IHasRendererObject` interface. Use `IHasTextureRegion` instead. + * Replaced `RegionAttachment.UpdateOffset` and `MeshAttachment.UpdateUVs` with `Attachment.UpdateRegion`. The caller must ensure that the attachment's region is not `null`. + * Removed `AttachmentRegionExtensions` methods `Attachment.SetRegion`, `MeshAttachment.SetRegion` and `RegionAttachment.SetRegion(region, update)`. Use `attachment.Region = region; if (update) attachment.UpdateRegion()` instead. + * `AttachmentLoader.NewRegionAttachment()` and `AttachmentLoader.NewMeshAttachment()` take an additional `Sequence` parameter. + * `VertexAttachment.DeformAttachment` property has been replaced with `VertexAttachment.TimelineAttachment`. + * `RegionAttachment.ComputeWorldVertices()` takes a `Slot` instead of a `Bone` as the first argument. + * Removed `Skeleton.Update(float deltaTime)` method. + * Removed `Slot.AttachmentTime` property. + * Removed extension method `AtlasRegion.GetSpineAtlasRect()` parameter `includeRotate` (with default value `true`). Most likely this method was never used with `includeRotate=false` in user code so no changes are required. + * `AtlasRegion.PackedWidth` and `AtlasRegion.PackedHeight` are swapped compared to 4.0 when packing rotation is equal to 90 degrees. Most likely this property was never accessed in user code so no changes are required. + ### Unity * **Officially supported Unity versions are 2017.1-2021.1**. @@ -34,7 +50,7 @@ * **Breaking change**: `AttachmentLoader#newRegionAttachment()` and `AttachmentLoader#newMeshAttachment()` take an additional `Sequence` parameter. * **Breaking change**: `Slot#setAttachmentTime()` and `Slot#getAttachmentTime()` have been removed. * **Breaking change**: `VertexAttachment#setDeformAttachment()` and `VertexAttachment#getDeformAttachment()` have been replaced with `VertexAttachment#setTimelineAttachment()` and `VertexAttachment#getTimelineAttachment()`. -* **Breaking change**: `RegionAttachment#updateOffset()` has been renamed to `RegionAttachment#updateRegion()`. The called must ensure that the attachment's region is not `null`. +* **Breaking change**: `RegionAttachment#updateOffset()` has been renamed to `RegionAttachment#updateRegion()`. The caller must ensure that the attachment's region is not `null`. * **Breaking change**: `RegionAttachment#computeWorldVertices()` takes a `Slot` instead of a `Bone` as the first argument. * **Addition**: full support for sequences. @@ -45,7 +61,7 @@ * **Breaking change**: `AttachmentLoader#newRegionAttachment()` and `AttachmentLoader#newMeshAttachment()` take an additional `Sequence` parameter. * **Breaking change**: `Slot#attachmentTime` and has been removed. * **Breaking change**: `VertexAttachment#deformAttachment` has been replaced with `VertexAttachment#timelineAttachment`. -* **Breaking change**: `RegionAttachment#updateOffset()` has been renamed to `RegionAttachment#updateRegion()`. The called must ensure that the attachment's region is not `null`. +* **Breaking change**: `RegionAttachment#updateOffset()` has been renamed to `RegionAttachment#updateRegion()`. The caller must ensure that the attachment's region is not `null`. * **Breaking change**: `RegionAttachment#computeWorldVertices()` takes a `Slot` instead of a `Bone` as the first argument. * **Addition**: full support for sequences. @@ -122,7 +138,7 @@ * Generated normals are now correctly flipped for back faces. * Modifying parent materials updates material instances accordingly. * Only `.json` files that are actually encoding Spine skeletons will be loaded. Other `.json` files will be left to other importers. -* Updated example project to UE 4.25. +* Updated example project to UE 4.27. ## C# ## diff --git a/spine-cpp/spine-cpp/src/spine/SkeletonBounds.cpp b/spine-cpp/spine-cpp/src/spine/SkeletonBounds.cpp index 768868f6f..3df73dbc9 100644 --- a/spine-cpp/spine-cpp/src/spine/SkeletonBounds.cpp +++ b/spine-cpp/spine-cpp/src/spine/SkeletonBounds.cpp @@ -198,10 +198,10 @@ float SkeletonBounds::getHeight() { } void SkeletonBounds::aabbCompute() { - float minX = FLT_MIN; - float minY = FLT_MIN; - float maxX = FLT_MAX; - float maxY = FLT_MAX; + float minX = FLT_MAX; + float minY = FLT_MAX; + float maxX = FLT_MIN; + float maxY = FLT_MIN; for (size_t i = 0, n = _polygons.size(); i < n; ++i) { spine::Polygon *polygon = _polygons[i]; diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs index fe4f3d8d2..c2c520b4d 100644 --- a/spine-csharp/src/Animation.cs +++ b/spine-csharp/src/Animation.cs @@ -181,7 +181,8 @@ namespace Spine { Attachment, Deform, // Event, DrawOrder, // IkConstraint, TransformConstraint, // - PathConstraintPosition, PathConstraintSpacing, PathConstraintMix + PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, // + Sequence } /// @@ -1786,7 +1787,7 @@ namespace Spine { } } /// The attachment that will be deformed. - /// + /// public VertexAttachment Attachment { get { return attachment; @@ -1869,9 +1870,9 @@ namespace Spine { Slot slot = skeleton.slots.Items[slotIndex]; if (!slot.bone.active) return; var vertexAttachment = slot.attachment as VertexAttachment; - if (vertexAttachment == null || vertexAttachment.DeformAttachment != attachment) return; + if (vertexAttachment == null || vertexAttachment.TimelineAttachment != attachment) return; - var deformArray = slot.Deform; + var deformArray = slot.deform; if (deformArray.Count == 0) blend = MixBlend.Setup; float[][] vertices = this.vertices; @@ -2583,4 +2584,94 @@ namespace Spine { } } } + + /// Changes a slot's for an attachment's . + public class SequenceTimeline : Timeline, ISlotTimeline { + public const int ENTRIES = 3; + private const int MODE = 1, DELAY = 2; + + readonly int slotIndex; + readonly IHasTextureRegion attachment; + + public SequenceTimeline (int frameCount, int slotIndex, Attachment attachment) + : base(frameCount, (int)Property.Sequence + "|" + slotIndex + "|" + ((IHasTextureRegion)attachment).Sequence.Id) { + this.slotIndex = slotIndex; + this.attachment = (IHasTextureRegion)attachment; + } + + public override int FrameEntries { + get { return ENTRIES; } + } + + public int SlotIndex { + get { + return slotIndex; + } + } + public Attachment Attachment { + get { + return (Attachment)attachment; + } + } + + /// Sets the time, mode, index, and frame time for the specified frame. + /// Between 0 and frameCount, inclusive. + /// Seconds between frames. + public void SetFrame (int frame, float time, SequenceMode mode, int index, float delay) { + frame *= ENTRIES; + frames[frame] = time; + frames[frame + MODE] = (int)mode | (index << 4); + frames[frame + DELAY] = delay; + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + Attachment slotAttachment = slot.attachment; + if (slotAttachment != attachment) { + VertexAttachment vertexAttachment = slotAttachment as VertexAttachment; + if ((vertexAttachment == null) + || vertexAttachment.TimelineAttachment != attachment) return; + } + + float[] frames = this.frames; + if (time < frames[0]) { // Time is before first frame. + if (blend == MixBlend.Setup || blend == MixBlend.First) slot.SequenceIndex = -1; + return; + } + + int i = Search(frames, time, ENTRIES); + float before = frames[i]; + int modeAndIndex = (int)frames[i + MODE]; + float delay = frames[i + DELAY]; + + int index = modeAndIndex >> 4, count = attachment.Sequence.Regions.Length; + SequenceMode mode = (SequenceMode)(modeAndIndex & 0xf); + if (mode != SequenceMode.Hold) { + index += (int)((time - before) / delay + 0.00001f); + switch (mode) { + case SequenceMode.Once: + index = Math.Min(count - 1, index); + break; + case SequenceMode.Loop: + index %= count; + break; + case SequenceMode.Pingpong: + int n = (count << 1) - 2; + index %= n; + if (index >= count) index = n - index; + break; + case SequenceMode.OnceReverse: + index = Math.Max(count - 1 - index, 0); + break; + case SequenceMode.LoopReverse: + index = count - 1 - (index % count); + break; + } + } + slot.SequenceIndex = index; + } + } } diff --git a/spine-csharp/src/AnimationState.cs b/spine-csharp/src/AnimationState.cs index 4988d73e3..c480494ce 100644 --- a/spine-csharp/src/AnimationState.cs +++ b/spine-csharp/src/AnimationState.cs @@ -1107,9 +1107,12 @@ namespace Spine { } /// - /// Uses to compute the AnimationTime, which is between - /// and . When the TrackTime is 0, the AnimationTime is equal to the - /// AnimationStart time. + /// Uses to compute the AnimationTime. When the TrackTime is 0, the + /// AnimationTime is equal to the AnimationStart time. + /// + /// The animationTime is between and , except if this + /// track entry is non-looping and is >= to the animation , then + /// animationTime continues to increase past . /// public float AnimationTime { get { @@ -1118,7 +1121,8 @@ namespace Spine { if (duration == 0) return animationStart; return (trackTime % duration) + animationStart; } - return Math.Min(trackTime + animationStart, animationEnd); + float animationTime = trackTime + animationStart; + return animationEnd >= animation.duration ? animationTime : Math.Min(animationTime, animationEnd); } } diff --git a/spine-csharp/src/Atlas.cs b/spine-csharp/src/Atlas.cs index 7fe319a87..c72cf5541 100644 --- a/spine-csharp/src/Atlas.cs +++ b/spine-csharp/src/Atlas.cs @@ -245,6 +245,10 @@ namespace Spine { if (region.degrees == 90) { region.u2 = (region.x + region.height) / (float)page.width; region.v2 = (region.y + region.width) / (float)page.height; + + var tempSwap = region.packedWidth; + region.packedWidth = region.packedHeight; + region.packedHeight = tempSwap; } else { region.u2 = (region.x + region.width) / (float)page.width; region.v2 = (region.y + region.height) / (float)page.height; @@ -339,19 +343,23 @@ namespace Spine { } } - public class AtlasRegion { + public class AtlasRegion : TextureRegion { public AtlasPage page; public string name; - public int x, y, width, height; - public float u, v, u2, v2; + public int x, y; public float offsetX, offsetY; public int originalWidth, originalHeight; + public int packedWidth { get { return width; } set { width = value; } } + public int packedHeight { get { return height; } set { height = value; } } public int degrees; public bool rotate; public int index; public string[] names; public int[][] values; + override public int OriginalWidth { get { return originalWidth; } } + override public int OriginalHeight { get { return originalHeight; } } + public AtlasRegion Clone () { return MemberwiseClone() as AtlasRegion; } diff --git a/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs b/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs index 360ffc7d2..09662b0cf 100644 --- a/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs +++ b/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs @@ -43,37 +43,38 @@ namespace Spine { this.atlasArray = atlasArray; } - public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + private void LoadSequence (string name, string basePath, Sequence sequence) { + TextureRegion[] regions = sequence.Regions; + for (int i = 0, n = regions.Length; i < n; i++) { + string path = sequence.GetPath(basePath, i); + regions[i] = FindRegion(path); + if (regions[i] == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + } + } + + public RegionAttachment NewRegionAttachment (Skin skin, string name, string path, Sequence sequence) { RegionAttachment attachment = new RegionAttachment(name); - attachment.RendererObject = region; - attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.degrees); - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; + if (sequence != null) + LoadSequence(name, path, sequence); + else { + AtlasRegion region = FindRegion(path); + if (region == null) + throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + attachment.Region = region; + } return attachment; } - public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + public MeshAttachment NewMeshAttachment (Skin skin, string name, string path, Sequence sequence) { MeshAttachment attachment = new MeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionDegrees = region.degrees; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; + if (sequence != null) + LoadSequence(name, path, sequence); + else { + AtlasRegion region = FindRegion(path); + if (region == null) + throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + attachment.Region = region; + } return attachment; } diff --git a/spine-csharp/src/Attachments/Attachment.cs b/spine-csharp/src/Attachments/Attachment.cs index 12d99db4a..c8b8f8830 100644 --- a/spine-csharp/src/Attachments/Attachment.cs +++ b/spine-csharp/src/Attachments/Attachment.cs @@ -30,12 +30,20 @@ using System; namespace Spine { + + /// The base class for all attachments. abstract public class Attachment { - public string Name { get; private set; } + /// The attachment's name. + public string Name { get; } protected Attachment (string name) { if (name == null) throw new ArgumentNullException("name", "name cannot be null"); - Name = name; + this.Name = name; + } + + /// Copy constructor. + protected Attachment (Attachment other) { + Name = other.Name; } override public string ToString () { @@ -45,8 +53,4 @@ namespace Spine { ///Returns a copy of the attachment. public abstract Attachment Copy (); } - - public interface IHasRendererObject { - object RendererObject { get; set; } - } } diff --git a/spine-csharp/src/Attachments/AttachmentLoader.cs b/spine-csharp/src/Attachments/AttachmentLoader.cs index bcb7eebc7..87ced4a6c 100644 --- a/spine-csharp/src/Attachments/AttachmentLoader.cs +++ b/spine-csharp/src/Attachments/AttachmentLoader.cs @@ -30,10 +30,10 @@ 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, Sequence sequence); /// May be null to not load any attachment. - MeshAttachment NewMeshAttachment (Skin skin, string name, string path); + MeshAttachment NewMeshAttachment (Skin skin, string name, string path, Sequence sequence); /// May be null to not load any attachment. BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name); diff --git a/spine-csharp/src/Attachments/AttachmentType.cs b/spine-csharp/src/Attachments/AttachmentType.cs index c5d8a9caa..81d90cfaa 100644 --- a/spine-csharp/src/Attachments/AttachmentType.cs +++ b/spine-csharp/src/Attachments/AttachmentType.cs @@ -29,6 +29,6 @@ namespace Spine { public enum AttachmentType { - Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping + Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping, Sequence } } diff --git a/spine-csharp/src/Attachments/BoundingBoxAttachment.cs b/spine-csharp/src/Attachments/BoundingBoxAttachment.cs index 398e7b435..47f9d8b05 100644 --- a/spine-csharp/src/Attachments/BoundingBoxAttachment.cs +++ b/spine-csharp/src/Attachments/BoundingBoxAttachment.cs @@ -36,10 +36,13 @@ namespace Spine { : base(name) { } + /// Copy constructor. + protected BoundingBoxAttachment (BoundingBoxAttachment other) + : base(other) { + } + public override Attachment Copy () { - BoundingBoxAttachment copy = new BoundingBoxAttachment(this.Name); - CopyTo(copy); - return copy; + return new BoundingBoxAttachment(this); } } } diff --git a/spine-csharp/src/Attachments/ClippingAttachment.cs b/spine-csharp/src/Attachments/ClippingAttachment.cs index a555ed8f1..fec2925e1 100644 --- a/spine-csharp/src/Attachments/ClippingAttachment.cs +++ b/spine-csharp/src/Attachments/ClippingAttachment.cs @@ -38,11 +38,14 @@ namespace Spine { public ClippingAttachment (string name) : base(name) { } + /// Copy constructor. + protected ClippingAttachment (ClippingAttachment other) + : base(other) { + endSlot = other.endSlot; + } + public override Attachment Copy () { - ClippingAttachment copy = new ClippingAttachment(this.Name); - CopyTo(copy); - copy.endSlot = endSlot; - return copy; + return new ClippingAttachment(this); } } } diff --git a/spine-csharp/src/Attachments/IHasTextureRegion.cs b/spine-csharp/src/Attachments/IHasTextureRegion.cs new file mode 100644 index 000000000..a8092d63a --- /dev/null +++ b/spine-csharp/src/Attachments/IHasTextureRegion.cs @@ -0,0 +1,56 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated September 24, 2021. Replaces all prior versions. + * + * Copyright (c) 2013-2021, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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 + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using System; +using System.Text; + +namespace Spine { + public interface IHasTextureRegion { + /// The name used to find the + string Path { get; set; } + /// + /// Sets the region used to draw the attachment. After setting the region or if the region's properties are changed, + /// must be called. + /// + TextureRegion Region { get; set; } + + /// + /// Updates any values the attachment calculates using the . Must be called after setting the + /// or if the region's properties are changed. + /// + void UpdateRegion (); + + float R { get; set; } + float G { get; set; } + float B { get; set; } + float A { get; set; } + + Sequence Sequence { get; set; } + } +} diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/CustomSkin.cs.meta b/spine-csharp/src/Attachments/IHasTextureRegion.cs.meta similarity index 74% rename from spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/CustomSkin.cs.meta rename to spine-csharp/src/Attachments/IHasTextureRegion.cs.meta index ade04ec0d..f6588e100 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/CustomSkin.cs.meta +++ b/spine-csharp/src/Attachments/IHasTextureRegion.cs.meta @@ -1,6 +1,7 @@ fileFormatVersion: 2 -guid: 6e55c8477eccddc4cb5c3551a3945ca7 +guid: d0e8b0a33cae75d498aa8c328787cafb MonoImporter: + externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 diff --git a/spine-csharp/src/Attachments/MeshAttachment.cs b/spine-csharp/src/Attachments/MeshAttachment.cs index c346e20b6..833e83aec 100644 --- a/spine-csharp/src/Attachments/MeshAttachment.cs +++ b/spine-csharp/src/Attachments/MeshAttachment.cs @@ -31,17 +31,27 @@ using System; namespace Spine { /// Attachment that displays a texture region using a mesh. - public class MeshAttachment : VertexAttachment, IHasRendererObject { - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - private MeshAttachment parentMesh; - internal float[] uvs, regionUVs; + public class MeshAttachment : VertexAttachment, IHasTextureRegion { + internal TextureRegion region; + internal string path; + internal float[] regionUVs, uvs; internal int[] triangles; internal float r = 1, g = 1, b = 1, a = 1; - internal int hulllength; + internal int hullLength; + private MeshAttachment parentMesh; + private Sequence sequence; - public int HullLength { get { return hulllength; } set { hulllength = value; } } + public TextureRegion Region { + get { return region; } + set { + if (value == null) throw new ArgumentNullException("region", "region cannot be null."); + region = value; + } + } + 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. + /// 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; } } @@ -50,19 +60,8 @@ namespace Spine { public float B { get { return b; } set { b = value; } } public float A { get { return a; } set { a = value; } } - public string Path { get; set; } - public object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public int RegionDegrees { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public string Path { get { return path; } set { path = value; } } + public Sequence Sequence { get { return sequence; } set { sequence = value; } } public MeshAttachment ParentMesh { get { return parentMesh; } @@ -91,131 +90,131 @@ namespace Spine { : base(name) { } - public void UpdateUVs () { + /// Copy constructor. Use if the other mesh is a linked mesh. + protected MeshAttachment (MeshAttachment other) + : base(other) { + + if (parentMesh != null) throw new ArgumentException("Use newLinkedMesh to copy a linked mesh."); + + region = other.region; + path = other.path; + r = other.r; + g = other.g; + b = other.b; + a = other.a; + + regionUVs = new float[other.regionUVs.Length]; + Array.Copy(other.regionUVs, 0, regionUVs, 0, regionUVs.Length); + + uvs = new float[other.uvs.Length]; + Array.Copy(other.uvs, 0, uvs, 0, uvs.Length); + + triangles = new int[other.triangles.Length]; + Array.Copy(other.triangles, 0, triangles, 0, triangles.Length); + + hullLength = other.hullLength; + sequence = other.sequence == null ? null : new Sequence(other.sequence); + + // Nonessential. + if (other.Edges != null) { + Edges = new int[other.Edges.Length]; + Array.Copy(other.Edges, 0, Edges, 0, Edges.Length); + } + Width = other.Width; + Height = other.Height; + } + + + public void UpdateRegion () { float[] regionUVs = this.regionUVs; if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; float[] uvs = this.uvs; - float u = RegionU, v = RegionV, width = 0, height = 0; + int n = uvs.Length; + float u, v, width, height; - if (RegionDegrees == 90) { - float textureHeight = this.regionWidth / (RegionV2 - RegionV); - float textureWidth = this.regionHeight / (RegionU2 - RegionU); - u -= (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureWidth; - v -= (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureHeight; - width = RegionOriginalHeight / textureWidth; - height = RegionOriginalWidth / textureHeight; - - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + (1 - regionUVs[i]) * height; - } - } else if (RegionDegrees == 180) { - float textureWidth = this.regionWidth / (RegionU2 - RegionU); - float textureHeight = this.regionHeight / (RegionV2 - RegionV); - u -= (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureWidth; - v -= RegionOffsetY / textureHeight; - width = RegionOriginalWidth / textureWidth; - height = RegionOriginalHeight / textureHeight; - - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + (1 - regionUVs[i]) * width; - uvs[i + 1] = v + (1 - regionUVs[i + 1]) * height; - } - } else if (RegionDegrees == 270) { - float textureWidth = this.regionWidth / (RegionU2 - RegionU); - float textureHeight = this.regionHeight / (RegionV2 - RegionV); - u -= RegionOffsetY / textureWidth; - v -= RegionOffsetX / textureHeight; - width = RegionOriginalHeight / textureWidth; - height = RegionOriginalWidth / textureHeight; - - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + (1 - regionUVs[i + 1]) * width; - uvs[i + 1] = v + regionUVs[i] * height; + if (region is AtlasRegion) { + u = this.region.u; + v = this.region.v; + AtlasRegion region = (AtlasRegion)this.region; + // Note: difference from reference implementation. + // Covers rotation since region.width and height are already setup accordingly. + float textureWidth = this.region.width / (region.u2 - region.u); + float textureHeight = this.region.height / (region.v2 - region.v); + switch (region.degrees) { + case 90: + u -= (region.originalHeight - region.offsetY - region.packedWidth) / textureWidth; + v -= (region.originalWidth - region.offsetX - region.packedHeight) / textureHeight; + width = region.originalHeight / textureWidth; + height = region.originalWidth / textureHeight; + for (int i = 0; i < n; i += 2) { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + (1 - regionUVs[i]) * height; + } + return; + case 180: + u -= (region.originalWidth - region.offsetX - region.packedWidth) / textureWidth; + v -= region.offsetY / textureHeight; + width = region.originalWidth / textureWidth; + height = region.originalHeight / textureHeight; + for (int i = 0; i < n; i += 2) { + uvs[i] = u + (1 - regionUVs[i]) * width; + uvs[i + 1] = v + (1 - regionUVs[i + 1]) * height; + } + return; + case 270: + u -= region.offsetY / textureWidth; + v -= region.offsetX / textureHeight; + width = region.originalHeight / textureWidth; + height = region.originalWidth / textureHeight; + for (int i = 0; i < n; i += 2) { + uvs[i] = u + (1 - regionUVs[i + 1]) * width; + uvs[i + 1] = v + regionUVs[i] * height; + } + return; } + u -= region.offsetX / textureWidth; + v -= (region.originalHeight - region.offsetY - region.packedHeight) / textureHeight; + width = region.originalWidth / textureWidth; + height = region.originalHeight / textureHeight; + } else if (region == null) { + u = v = 0; + width = height = 1; } else { - float textureWidth = this.regionWidth / (RegionU2 - RegionU); - float textureHeight = this.regionHeight / (RegionV2 - RegionV); - u -= RegionOffsetX / textureWidth; - v -= (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureHeight; - width = RegionOriginalWidth / textureWidth; - height = RegionOriginalHeight / textureHeight; - - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } + u = region.u; + v = region.v; + width = region.u2 - u; + height = region.v2 - v; + } + for (int i = 0; i < n; i += 2) { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; } } - public override Attachment Copy () { - if (parentMesh != null) return NewLinkedMesh(); - - MeshAttachment copy = new MeshAttachment(this.Name); - copy.RendererObject = RendererObject; - copy.regionOffsetX = regionOffsetX; - copy.regionOffsetY = regionOffsetY; - copy.regionWidth = regionWidth; - copy.regionHeight = regionHeight; - copy.regionOriginalWidth = regionOriginalWidth; - copy.regionOriginalHeight = regionOriginalHeight; - copy.RegionDegrees = RegionDegrees; - copy.RegionU = RegionU; - copy.RegionV = RegionV; - copy.RegionU2 = RegionU2; - copy.RegionV2 = RegionV2; - - copy.Path = Path; - copy.r = r; - copy.g = g; - copy.b = b; - copy.a = a; - - CopyTo(copy); - copy.regionUVs = new float[regionUVs.Length]; - Array.Copy(regionUVs, 0, copy.regionUVs, 0, regionUVs.Length); - copy.uvs = new float[uvs.Length]; - Array.Copy(uvs, 0, copy.uvs, 0, uvs.Length); - copy.triangles = new int[triangles.Length]; - Array.Copy(triangles, 0, copy.triangles, 0, triangles.Length); - copy.HullLength = HullLength; - - // Nonessential. - if (Edges != null) { - copy.Edges = new int[Edges.Length]; - Array.Copy(Edges, 0, copy.Edges, 0, Edges.Length); - } - copy.Width = Width; - copy.Height = Height; - return copy; + /// If the attachment has a , the region may be changed. + override public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) { + if (sequence != null) sequence.Apply(slot, this); + base.ComputeWorldVertices(slot, start, count, worldVertices, offset, stride); } ///Returns a new mesh with this mesh set as the . public MeshAttachment NewLinkedMesh () { MeshAttachment mesh = new MeshAttachment(Name); - mesh.RendererObject = RendererObject; - mesh.regionOffsetX = regionOffsetX; - mesh.regionOffsetY = regionOffsetY; - mesh.regionWidth = regionWidth; - mesh.regionHeight = regionHeight; - mesh.regionOriginalWidth = regionOriginalWidth; - mesh.regionOriginalHeight = regionOriginalHeight; - mesh.RegionDegrees = RegionDegrees; - mesh.RegionU = RegionU; - mesh.RegionV = RegionV; - mesh.RegionU2 = RegionU2; - mesh.RegionV2 = RegionV2; - mesh.Path = Path; + mesh.timelineAttachment = timelineAttachment; + mesh.region = region; + mesh.path = path; mesh.r = r; mesh.g = g; mesh.b = b; mesh.a = a; - - mesh.deformAttachment = deformAttachment; mesh.ParentMesh = parentMesh != null ? parentMesh : this; - mesh.UpdateUVs(); + if (mesh.Region != null) mesh.UpdateRegion(); return mesh; } + + public override Attachment Copy () { + return parentMesh != null ? NewLinkedMesh() : new MeshAttachment(this); + } } } diff --git a/spine-csharp/src/Attachments/PathAttachment.cs b/spine-csharp/src/Attachments/PathAttachment.cs index 286fb88aa..fb944b922 100644 --- a/spine-csharp/src/Attachments/PathAttachment.cs +++ b/spine-csharp/src/Attachments/PathAttachment.cs @@ -47,14 +47,19 @@ namespace Spine { : base(name) { } + /// Copy constructor. + protected PathAttachment (PathAttachment other) + : base(other) { + + lengths = new float[other.lengths.Length]; + Array.Copy(other.lengths, 0, lengths, 0, lengths.Length); + + closed = other.closed; + constantSpeed = other.constantSpeed; + } + public override Attachment Copy () { - PathAttachment copy = new PathAttachment(this.Name); - CopyTo(copy); - copy.lengths = new float[lengths.Length]; - Array.Copy(lengths, 0, copy.lengths, 0, lengths.Length); - copy.closed = closed; - copy.constantSpeed = constantSpeed; - return copy; + return new PathAttachment(this); } } } diff --git a/spine-csharp/src/Attachments/PointAttachment.cs b/spine-csharp/src/Attachments/PointAttachment.cs index 2b6ad680c..10cd99c47 100644 --- a/spine-csharp/src/Attachments/PointAttachment.cs +++ b/spine-csharp/src/Attachments/PointAttachment.cs @@ -45,6 +45,14 @@ namespace Spine { : base(name) { } + /** Copy constructor. */ + protected PointAttachment (PointAttachment other) + : base(other) { + x = other.x; + y = other.y; + rotation = other.rotation; + } + public void ComputeWorldPosition (Bone bone, out float ox, out float oy) { bone.LocalToWorld(this.x, this.y, out ox, out oy); } @@ -57,11 +65,7 @@ namespace Spine { } public override Attachment Copy () { - PointAttachment copy = new PointAttachment(this.Name); - copy.x = x; - copy.y = y; - copy.rotation = rotation; - return copy; + return new PointAttachment(this); } } } diff --git a/spine-csharp/src/Attachments/RegionAttachment.cs b/spine-csharp/src/Attachments/RegionAttachment.cs index ec4e796d2..b0642a13c 100644 --- a/spine-csharp/src/Attachments/RegionAttachment.cs +++ b/spine-csharp/src/Attachments/RegionAttachment.cs @@ -31,20 +31,17 @@ using System; namespace Spine { /// Attachment that displays a texture region. - public class RegionAttachment : Attachment, IHasRendererObject { - public const int BLX = 0; - public const int BLY = 1; - public const int ULX = 2; - public const int ULY = 3; - public const int URX = 4; - public const int URY = 5; - public const int BRX = 6; - public const int BRY = 7; + public class RegionAttachment : Attachment, IHasTextureRegion { + public const int BLX = 0, BLY = 1; + public const int ULX = 2, ULY = 3; + public const int URX = 4, URY = 5; + public const int BRX = 6, BRY = 7; + internal TextureRegion region; internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; internal float[] offset = new float[8], uvs = new float[8]; internal float r = 1, g = 1, b = 1, a = 1; + internal Sequence sequence; public float X { get { return x; } set { x = value; } } public float Y { get { return y; } set { y = value; } } @@ -60,31 +57,73 @@ namespace Spine { public float A { get { return a; } set { a = value; } } public string Path { get; set; } - public object RendererObject { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public TextureRegion Region { get { return region; } set { region = value; } } + /// For each of the 4 vertices, a pair of x,y values that is the local position of the vertex. + /// public float[] Offset { get { return offset; } } public float[] UVs { get { return uvs; } } + public Sequence Sequence { get { return sequence; } set { sequence = value; } } public RegionAttachment (string name) : base(name) { } - public void UpdateOffset () { - float regionScaleX = width / regionOriginalWidth * scaleX; - float regionScaleY = height / regionOriginalHeight * scaleY; - float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; - float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; - float localX2 = localX + regionWidth * regionScaleX; - float localY2 = localY + regionHeight * regionScaleY; + /// Copy constructor. + public RegionAttachment (RegionAttachment other) + : base(other) { + region = other.region; + Path = other.Path; + x = other.x; + y = other.y; + scaleX = other.scaleX; + scaleY = other.scaleY; + rotation = other.rotation; + width = other.width; + height = other.height; + Array.Copy(other.uvs, 0, uvs, 0, 8); + Array.Copy(other.offset, 0, offset, 0, 8); + r = other.r; + g = other.g; + b = other.b; + a = other.a; + sequence = other.sequence == null ? null : new Sequence(other.sequence); + } + + /// Calculates the and using the region and the attachment's transform. Must be called if the + /// region, the region's properties, or the transform are changed. + public void UpdateRegion () { + float width = Width; + float height = Height; + float localX2 = width / 2; + float localY2 = height / 2; + float localX = -localX2; + float localY = -localY2; + bool rotated = false; + if (region is AtlasRegion) { + AtlasRegion region = (AtlasRegion)this.region; + localX += region.offsetX / region.originalWidth * width; + localY += region.offsetY / region.originalHeight * height; + if (region.degrees == 90) { + rotated = true; + localX2 -= (region.originalWidth - region.offsetX - region.packedHeight) / region.originalWidth * width; + localY2 -= (region.originalHeight - region.offsetY - region.packedWidth) / region.originalHeight * height; + } else { + localX2 -= (region.originalWidth - region.offsetX - region.packedWidth) / region.originalWidth * width; + localY2 -= (region.originalHeight - region.offsetY - region.packedHeight) / region.originalHeight * height; + } + } + float scaleX = ScaleX; + float scaleY = ScaleY; + localX *= scaleX; + localY *= scaleY; + localX2 *= scaleX; + localY2 *= scaleY; + float rotation = Rotation; float cos = MathUtils.CosDeg(this.rotation); float sin = MathUtils.SinDeg(this.rotation); - float x = this.x, y = this.y; + float x = X; + float y = Y; float localXCos = localX * cos + x; float localXSin = localX * sin; float localYCos = localY * cos + y; @@ -102,39 +141,41 @@ namespace Spine { offset[URY] = localY2Cos + localX2Sin; offset[BRX] = localX2Cos - localYSin; offset[BRY] = localYCos + localX2Sin; - } - public void SetUVs (float u, float v, float u2, float v2, int degrees) { float[] uvs = this.uvs; - // UV values differ from spine-libgdx. - if (degrees == 90) { - uvs[URX] = u; - uvs[URY] = v2; - uvs[BRX] = u; - uvs[BRY] = v; - uvs[BLX] = u2; - uvs[BLY] = v; - uvs[ULX] = u2; - uvs[ULY] = v2; + if (rotated) { + uvs[URX] = region.u; + uvs[URY] = region.v2; + uvs[BRX] = region.u; + uvs[BRY] = region.v; + uvs[BLX] = region.u2; + uvs[BLY] = region.v; + uvs[ULX] = region.u2; + uvs[ULY] = region.v2; } else { - uvs[ULX] = u; - uvs[ULY] = v2; - uvs[URX] = u; - uvs[URY] = v; - uvs[BRX] = u2; - uvs[BRY] = v; - uvs[BLX] = u2; - uvs[BLY] = v2; + uvs[ULX] = region.u; + uvs[ULY] = region.v2; + uvs[URX] = region.u; + uvs[URY] = region.v; + uvs[BRX] = region.u2; + uvs[BRY] = region.v; + uvs[BLX] = region.u2; + uvs[BLY] = region.v2; } } - /// Transforms the attachment's four vertices to world coordinates. + /// + /// Transforms the attachment's four vertices to world coordinates. If the attachment has a the region may + /// be changed. /// The parent bone. /// The output world vertices. Must have a length greater than or equal to offset + 8. /// The worldVertices index to begin writing values. /// The number of worldVertices entries between the value pairs written. - public void ComputeWorldVertices (Bone bone, float[] worldVertices, int offset, int stride = 2) { + public void ComputeWorldVertices (Slot slot, float[] worldVertices, int offset, int stride = 2) { + if (sequence != null) sequence.Apply(slot, this); + float[] vertexOffset = this.offset; + Bone bone = slot.Bone; float bwx = bone.worldX, bwy = bone.worldY; float a = bone.a, b = bone.b, c = bone.c, d = bone.d; float offsetX, offsetY; @@ -166,29 +207,7 @@ namespace Spine { } public override Attachment Copy () { - RegionAttachment copy = new RegionAttachment(this.Name); - copy.RendererObject = RendererObject; - copy.regionOffsetX = regionOffsetX; - copy.regionOffsetY = regionOffsetY; - copy.regionWidth = regionWidth; - copy.regionHeight = regionHeight; - copy.regionOriginalWidth = regionOriginalWidth; - copy.regionOriginalHeight = regionOriginalHeight; - copy.Path = Path; - copy.x = x; - copy.y = y; - copy.scaleX = scaleX; - copy.scaleY = scaleY; - copy.rotation = rotation; - copy.width = width; - copy.height = height; - Array.Copy(uvs, 0, copy.uvs, 0, 8); - Array.Copy(offset, 0, copy.offset, 0, 8); - copy.r = r; - copy.g = g; - copy.b = b; - copy.a = a; - return copy; + return new RegionAttachment(this); } } } diff --git a/spine-csharp/src/Attachments/Sequence.cs b/spine-csharp/src/Attachments/Sequence.cs new file mode 100644 index 000000000..47a459c6f --- /dev/null +++ b/spine-csharp/src/Attachments/Sequence.cs @@ -0,0 +1,95 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated September 24, 2021. Replaces all prior versions. + * + * Copyright (c) 2013-2021, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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 + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using System; +using System.Text; + +namespace Spine { + public class Sequence { + static int nextID = 0; + static readonly Object nextIdLock = new Object(); + + internal readonly int id; + internal readonly TextureRegion[] regions; + internal int start, digits, setupIndex; + + public int Start { get { return start; } set { start = value; } } + public int Digits { get { return digits; } set { digits = value; } } + /// The index of the region to show for the setup pose. + public int SetupIndex { get { return setupIndex; } set { setupIndex = value; } } + public TextureRegion[] Regions { get { return regions; } } + /// Returns a unique ID for this attachment. + public int Id { get { return id; } } + + public Sequence (int count) { + lock (Sequence.nextIdLock) { + id = Sequence.nextID++; + } + regions = new TextureRegion[count]; + } + + /// Copy constructor. + public Sequence (Sequence other) { + lock (Sequence.nextIdLock) { + id = Sequence.nextID++; + } + regions = new TextureRegion[other.regions.Length]; + Array.Copy(other.regions, 0, regions, 0, regions.Length); + + start = other.start; + digits = other.digits; + setupIndex = other.setupIndex; + } + + public void Apply (Slot slot, IHasTextureRegion attachment) { + int index = slot.SequenceIndex; + if (index == -1) index = setupIndex; + if (index >= regions.Length) index = regions.Length - 1; + TextureRegion region = regions[index]; + if (attachment.Region != region) { + attachment.Region = region; + attachment.UpdateRegion(); + } + } + + public string GetPath (string basePath, int index) { + StringBuilder buffer = new StringBuilder(basePath.Length + digits); + buffer.Append(basePath); + string frame = (start + index).ToString(); + for (int i = digits - frame.Length; i > 0; i--) + buffer.Append('0'); + buffer.Append(frame); + return buffer.ToString(); + } + } + + public enum SequenceMode { + Hold, Once, Loop, Pingpong, OnceReverse, LoopReverse, PingpongReverse + } +} diff --git a/spine-csharp/src/Attachments/Sequence.cs.meta b/spine-csharp/src/Attachments/Sequence.cs.meta new file mode 100644 index 000000000..3376e99fd --- /dev/null +++ b/spine-csharp/src/Attachments/Sequence.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 522632bd4e297fe47acf78100bfd8689 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-csharp/src/Attachments/VertexAttachment.cs b/spine-csharp/src/Attachments/VertexAttachment.cs index ccd5352fa..4e66c160a 100644 --- a/spine-csharp/src/Attachments/VertexAttachment.cs +++ b/spine-csharp/src/Attachments/VertexAttachment.cs @@ -37,27 +37,50 @@ namespace Spine { static readonly Object nextIdLock = new Object(); internal readonly int id; + internal VertexAttachment timelineAttachment; internal int[] bones; internal float[] vertices; internal int worldVerticesLength; - internal VertexAttachment deformAttachment; /// Gets a unique ID for this attachment. public int Id { get { return id; } } public int[] Bones { get { return bones; } set { bones = value; } } public float[] Vertices { get { return vertices; } set { vertices = value; } } public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } - ///Deform keys for the deform attachment are also applied to this attachment. - /// May be null if no deform keys should be applied. - public VertexAttachment DeformAttachment { get { return deformAttachment; } set { deformAttachment = value; } } + ///Timelines for the timeline attachment are also applied to this attachment. + /// May be null if no attachment-specific timelines should be applied. + public VertexAttachment TimelineAttachment { get { return timelineAttachment; } set { timelineAttachment = value; } } public VertexAttachment (string name) : base(name) { - deformAttachment = this; lock (VertexAttachment.nextIdLock) { id = VertexAttachment.nextID++; } + timelineAttachment = this; + } + + /// Copy constructor. + public VertexAttachment (VertexAttachment other) + : base(other) { + + lock (VertexAttachment.nextIdLock) { + id = VertexAttachment.nextID++; + } + timelineAttachment = other.timelineAttachment; + if (other.bones != null) { + bones = new int[other.bones.Length]; + Array.Copy(other.bones, 0, bones, 0, bones.Length); + } else + bones = null; + + if (other.vertices != null) { + vertices = new float[other.vertices.Length]; + Array.Copy(other.vertices, 0, vertices, 0, vertices.Length); + } else + vertices = null; + + worldVerticesLength = other.worldVerticesLength; } public void ComputeWorldVertices (Slot slot, float[] worldVertices) { @@ -76,7 +99,7 @@ namespace Spine { /// The output world vertices. Must have a length greater than or equal to + . /// The index to begin writing values. /// 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) { + public virtual void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) { count = offset + (count >> 1) * stride; ExposedList deformArray = slot.deform; float[] vertices = this.vertices; @@ -131,23 +154,5 @@ namespace Spine { } } } - - ///Does not copy id (generated) or name (set on construction). - internal void CopyTo (VertexAttachment attachment) { - if (bones != null) { - attachment.bones = new int[bones.Length]; - Array.Copy(bones, 0, attachment.bones, 0, bones.Length); - } else - attachment.bones = null; - - if (vertices != null) { - attachment.vertices = new float[vertices.Length]; - Array.Copy(vertices, 0, attachment.vertices, 0, vertices.Length); - } else - attachment.vertices = null; - - attachment.worldVerticesLength = worldVerticesLength; - attachment.deformAttachment = deformAttachment; - } } } diff --git a/spine-csharp/src/Bone.cs b/spine-csharp/src/Bone.cs index 82e1bd2c5..dec5c2133 100644 --- a/spine-csharp/src/Bone.cs +++ b/spine-csharp/src/Bone.cs @@ -121,8 +121,6 @@ namespace Spine { /// Returns the magnitide (always positive) of the world scale Y. public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } - /// Copy constructor. Does not copy the bones. - /// May be null. public Bone (BoneData data, Skeleton skeleton, Bone parent) { if (data == null) throw new ArgumentNullException("data", "data cannot be null."); if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); @@ -132,6 +130,23 @@ namespace Spine { SetToSetupPose(); } + /// Copy constructor. Does not copy the bones. + /// May be null. + public Bone (Bone bone, Skeleton skeleton, Bone parent) { + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.skeleton = skeleton; + this.parent = parent; + data = bone.data; + x = bone.x; + y = bone.y; + rotation = bone.rotation; + scaleX = bone.scaleX; + scaleY = bone.scaleY; + shearX = bone.shearX; + shearY = bone.shearY; + } + /// Computes the world transform using the parent bone and this bone's local applied transform. public void Update () { UpdateWorldTransform(ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY); diff --git a/spine-csharp/src/IkConstraint.cs b/spine-csharp/src/IkConstraint.cs index 87437d4dc..40f07baa3 100644 --- a/spine-csharp/src/IkConstraint.cs +++ b/spine-csharp/src/IkConstraint.cs @@ -38,8 +38,8 @@ namespace Spine { /// See IK constraints in the Spine User Guide. /// public class IkConstraint : IUpdatable { - internal IkConstraintData data; - internal ExposedList bones = new ExposedList(); + internal readonly IkConstraintData data; + internal readonly ExposedList bones = new ExposedList(); internal Bone target; internal int bendDirection; internal bool compress, stretch; @@ -59,8 +59,8 @@ namespace Spine { bones = new ExposedList(data.bones.Count); foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindBone(data.target.name); + bones.Add(skeleton.bones.Items[boneData.index]); + target = skeleton.bones.Items[data.target.index]; } /// Copy constructor. diff --git a/spine-csharp/src/PathConstraint.cs b/spine-csharp/src/PathConstraint.cs index 88ff80e37..ce0617122 100644 --- a/spine-csharp/src/PathConstraint.cs +++ b/spine-csharp/src/PathConstraint.cs @@ -42,16 +42,16 @@ namespace Spine { const int NONE = -1, BEFORE = -2, AFTER = -3; const float Epsilon = 0.00001f; - internal PathConstraintData data; - internal ExposedList bones; + internal readonly PathConstraintData data; + internal readonly ExposedList bones; internal Slot target; internal float position, spacing, mixRotate, mixX, mixY; internal bool active; - internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); - internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); - internal float[] segments = new float[10]; + internal readonly ExposedList spaces = new ExposedList(), positions = new ExposedList(); + internal readonly ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); + internal readonly float[] segments = new float[10]; public PathConstraint (PathConstraintData data, Skeleton skeleton) { if (data == null) throw new ArgumentNullException("data", "data cannot be null."); @@ -59,8 +59,8 @@ namespace Spine { this.data = data; bones = new ExposedList(data.Bones.Count); foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindSlot(data.target.name); + bones.Add(skeleton.bones.Items[boneData.index]); + target = skeleton.slots.Items[data.target.index]; position = data.position; spacing = data.spacing; mixRotate = data.mixRotate; @@ -73,9 +73,9 @@ namespace Spine { if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); data = constraint.data; - bones = new ExposedList(constraint.Bones.Count); - foreach (Bone bone in constraint.Bones) - bones.Add(skeleton.Bones.Items[bone.data.index]); + bones = new ExposedList(constraint.bones.Count); + foreach (Bone bone in constraint.bones) + bones.Add(skeleton.bones.Items[bone.data.index]); target = skeleton.slots.Items[constraint.target.data.index]; position = constraint.position; spacing = constraint.spacing; @@ -506,5 +506,9 @@ namespace Spine { public bool Active { get { return active; } } /// The path constraint's setup pose data. public PathConstraintData Data { get { return data; } } + + override public string ToString () { + return data.name; + } } } diff --git a/spine-csharp/src/Skeleton.cs b/spine-csharp/src/Skeleton.cs index 66a27e837..b7a454416 100644 --- a/spine-csharp/src/Skeleton.cs +++ b/spine-csharp/src/Skeleton.cs @@ -39,10 +39,10 @@ namespace Spine { internal ExposedList ikConstraints; internal ExposedList transformConstraints; internal ExposedList pathConstraints; + internal ExposedList springConstraints; internal ExposedList updateCache = new ExposedList(); internal Skin skin; internal float r = 1, g = 1, b = 1, a = 1; - internal float time; private float scaleX = 1, scaleY = 1; internal float x, y; @@ -53,6 +53,7 @@ namespace Spine { public ExposedList DrawOrder { get { return drawOrder; } } public ExposedList IkConstraints { get { return ikConstraints; } } public ExposedList PathConstraints { get { return pathConstraints; } } + public ExposedList SpringConstraints { get { return SpringConstraints; } } public ExposedList TransformConstraints { get { return transformConstraints; } } public Skin Skin { @@ -65,7 +66,6 @@ namespace Spine { 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 Time { get { return time; } set { time = value; } } public float X { get { return x; } set { x = value; } } public float Y { get { return y; } set { y = value; } } public float ScaleX { get { return scaleX; } set { scaleX = value; } } @@ -121,8 +121,68 @@ namespace Spine { foreach (PathConstraintData pathConstraintData in data.pathConstraints) pathConstraints.Add(new PathConstraint(pathConstraintData, this)); + springConstraints = new ExposedList(data.springConstraints.Count); + foreach (SpringConstraintData springConstraintData in data.springConstraints) + springConstraints.Add(new SpringConstraint(springConstraintData, this)); + + UpdateCache(); + } + + /// Copy constructor. + public Skeleton (Skeleton skeleton) { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + data = skeleton.data; + + bones = new ExposedList(skeleton.bones.Count); + foreach (Bone bone in skeleton.bones) { + Bone newBone; + if (bone.parent == null) + newBone = new Bone(bone, this, null); + else { + Bone parent = bones.Items[bone.parent.data.index]; + newBone = new Bone(bone, this, parent); + parent.children.Add(newBone); + } + bones.Add(newBone); + } + + slots = new ExposedList(skeleton.slots.Count); + Bone[] bonesItems = bones.Items; + foreach (Slot slot in skeleton.slots) { + Bone bone = bonesItems[slot.bone.data.index]; + slots.Add(new Slot(slot, bone)); + } + + drawOrder = new ExposedList(slots.Count); + Slot[] slotsItems = slots.Items; + foreach (Slot slot in skeleton.drawOrder) + drawOrder.Add(slotsItems[slot.data.index]); + + ikConstraints = new ExposedList(skeleton.ikConstraints.Count); + foreach (IkConstraint ikConstraint in skeleton.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraint, this)); + + transformConstraints = new ExposedList(skeleton.transformConstraints.Count); + foreach (TransformConstraint transformConstraint in skeleton.transformConstraints) + transformConstraints.Add(new TransformConstraint(transformConstraint, this)); + + pathConstraints = new ExposedList(skeleton.pathConstraints.Count); + foreach (PathConstraint pathConstraint in skeleton.pathConstraints) + pathConstraints.Add(new PathConstraint(pathConstraint, this)); + + springConstraints = new ExposedList(skeleton.springConstraints.Count); + foreach (SpringConstraint springConstraint in skeleton.springConstraints) + springConstraints.Add(new SpringConstraint(springConstraint, this)); + + skin = skeleton.skin; + r = skeleton.r; + g = skeleton.g; + b = skeleton.b; + a = skeleton.a; + scaleX = skeleton.scaleX; + scaleY = skeleton.scaleY; + UpdateCache(); - //UpdateWorldTransform(); } /// Caches information about bones and constraints. Must be called if the is modified or if bones, constraints, or @@ -150,11 +210,13 @@ namespace Spine { } } - int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count; + int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count, + springCount = this.springConstraints.Count; IkConstraint[] ikConstraints = this.ikConstraints.Items; TransformConstraint[] transformConstraints = this.transformConstraints.Items; PathConstraint[] pathConstraints = this.pathConstraints.Items; - int constraintCount = ikCount + transformCount + pathCount; + SpringConstraint[] springConstraints = this.springConstraints.Items; + int constraintCount = ikCount + transformCount + pathCount + springCount; for (int i = 0; i < constraintCount; i++) { for (int ii = 0; ii < ikCount; ii++) { IkConstraint constraint = ikConstraints[ii]; @@ -177,6 +239,13 @@ namespace Spine { goto continue_outer; } } + for (int ii = 0; ii < springCount; ii++) { + SpringConstraint constraint = springConstraints[ii]; + if (constraint.data.order == i) { + SortSpringConstraint(constraint); + goto continue_outer; + } + } continue_outer: { } } @@ -210,34 +279,6 @@ namespace Spine { } } - private void SortPathConstraint (PathConstraint constraint) { - constraint.active = constraint.target.bone.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); - if (!constraint.active) return; - - Slot slot = constraint.target; - int slotIndex = slot.data.index; - Bone slotBone = slot.bone; - if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); - if (data.defaultSkin != null && data.defaultSkin != skin) - SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); - - Attachment attachment = slot.attachment; - if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); - - var constrained = constraint.bones.Items; - int boneCount = constraint.bones.Count; - for (int i = 0; i < boneCount; i++) - SortBone(constrained[i]); - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained[i].children); - for (int i = 0; i < boneCount; i++) - constrained[i].sorted = true; - } - private void SortTransformConstraint (TransformConstraint constraint) { constraint.active = constraint.target.active && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); @@ -266,6 +307,34 @@ namespace Spine { constrained[i].sorted = true; } + private void SortPathConstraint (PathConstraint constraint) { + constraint.active = constraint.target.bone.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); + if (!constraint.active) return; + + Slot slot = constraint.target; + int slotIndex = slot.data.index; + Bone slotBone = slot.bone; + if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); + if (data.defaultSkin != null && data.defaultSkin != skin) + SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); + + Attachment attachment = slot.attachment; + if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); + + var constrained = constraint.bones.Items; + int boneCount = constraint.bones.Count; + for (int i = 0; i < boneCount; i++) + SortBone(constrained[i]); + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained[i].children); + for (int i = 0; i < boneCount; i++) + constrained[i].sorted = true; + } + private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { foreach (var entry in skin.Attachments) if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone); @@ -287,6 +356,23 @@ namespace Spine { } } + private void SortSpringConstraint (SpringConstraint constraint) { + constraint.active = !constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)); + if (!constraint.active) return; + + Object[] constrained = constraint.bones.Items; + int boneCount = constraint.bones.Count; + for (int i = 0; i < boneCount; i++) + SortBone((Bone)constrained[i]); + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(((Bone)constrained[i]).children); + for (int i = 0; i < boneCount; i++) + ((Bone)constrained[i]).sorted = true; + } + private void SortBone (Bone bone) { if (bone.sorted) return; Bone parent = bone.parent; @@ -372,7 +458,7 @@ namespace Spine { for (int i = 0, n = this.bones.Count; i < n; i++) bones[i].SetToSetupPose(); - var ikConstraints = this.ikConstraints.Items; + IkConstraint[] ikConstraints = this.ikConstraints.Items; for (int i = 0, n = this.ikConstraints.Count; i < n; i++) { IkConstraint constraint = ikConstraints[i]; IkConstraintData data = constraint.data; @@ -383,7 +469,7 @@ namespace Spine { constraint.stretch = data.stretch; } - var transformConstraints = this.transformConstraints.Items; + TransformConstraint[] transformConstraints = this.transformConstraints.Items; for (int i = 0, n = this.transformConstraints.Count; i < n; i++) { TransformConstraint constraint = transformConstraints[i]; TransformConstraintData data = constraint.data; @@ -395,7 +481,7 @@ namespace Spine { constraint.mixShearY = data.mixShearY; } - var pathConstraints = this.pathConstraints.Items; + PathConstraint[] pathConstraints = this.pathConstraints.Items; for (int i = 0, n = this.pathConstraints.Count; i < n; i++) { PathConstraint constraint = pathConstraints[i]; PathConstraintData data = constraint.data; @@ -405,6 +491,20 @@ namespace Spine { constraint.mixX = data.mixX; constraint.mixY = data.mixY; } + + SpringConstraint[] springConstraints = this.springConstraints.Items; + for (int i = 0, n = this.springConstraints.Count; i < n; i++) { + SpringConstraint constraint = springConstraints[i]; + SpringConstraintData data = constraint.data; + constraint.mix = data.mix; + constraint.friction = data.friction; + constraint.gravity = data.gravity; + constraint.wind = data.wind; + constraint.stiffness = data.stiffness; + constraint.damping = data.damping; + constraint.rope = data.rope; + constraint.stretch = data.stretch; + } } public void SetSlotsToSetupPose () { @@ -558,8 +658,17 @@ namespace Spine { return null; } - public void Update (float delta) { - time += delta; + /// Finds a spring constraint by comparing each spring constraint's name. It is more efficient to cache the results of this + /// method than to call it repeatedly. + /// May be null. + public SpringConstraint FindSpringConstraint (String constraintName) { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + SpringConstraint[] springConstraints = this.springConstraints.Items; + for (int i = 0, n = this.springConstraints.Count; i < n; i++) { + SpringConstraint constraint = springConstraints[i]; + if (constraint.data.name.Equals(constraintName)) return constraint; + } + return null; } /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. @@ -579,12 +688,12 @@ namespace Spine { int verticesLength = 0; float[] vertices = null; Attachment attachment = slot.attachment; - var regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { + RegionAttachment region = attachment as RegionAttachment; + if (region != null) { verticesLength = 8; vertices = temp; if (vertices.Length < 8) vertices = temp = new float[8]; - regionAttachment.ComputeWorldVertices(slot.bone, temp, 0); + region.ComputeWorldVertices(slot, temp, 0, 2); } else { var meshAttachment = attachment as MeshAttachment; if (meshAttachment != null) { @@ -592,7 +701,7 @@ namespace Spine { verticesLength = mesh.WorldVerticesLength; vertices = temp; if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength]; - mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0); + mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0, 2); } } diff --git a/spine-csharp/src/SkeletonBinary.cs b/spine-csharp/src/SkeletonBinary.cs index 2583cf9ac..fe535a8a6 100644 --- a/spine-csharp/src/SkeletonBinary.cs +++ b/spine-csharp/src/SkeletonBinary.cs @@ -61,6 +61,9 @@ namespace Spine { public const int SLOT_RGB2 = 4; public const int SLOT_ALPHA = 5; + public const int ATTACHMENT_DEFORM = 0; + public const int ATTACHMENT_SEQUENCE = 1; + public const int PATH_POSITION = 0; public const int PATH_SPACING = 1; public const int PATH_MIX = 2; @@ -296,9 +299,9 @@ namespace Spine { if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh; + linkedMesh.mesh.TimelineAttachment = linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh; linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); + if (linkedMesh.mesh.Sequence == null) linkedMesh.mesh.UpdateRegion(); } linkedMeshes.Clear(); @@ -384,9 +387,10 @@ namespace Spine { float width = input.ReadFloat(); float height = input.ReadFloat(); int color = input.ReadInt(); + Sequence sequence = ReadSequence(input); if (path == null) path = name; - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path, sequence); if (region == null) return null; region.Path = path; region.x = x * scale; @@ -400,7 +404,8 @@ namespace Spine { region.g = ((color & 0x00ff0000) >> 16) / 255f; region.b = ((color & 0x0000ff00) >> 8) / 255f; region.a = ((color & 0x000000ff)) / 255f; - region.UpdateOffset(); + region.sequence = sequence; + if (sequence == null) region.UpdateRegion(); return region; } case AttachmentType.Boundingbox: { @@ -424,6 +429,7 @@ namespace Spine { int[] triangles = ReadShortArray(input); Vertices vertices = ReadVertices(input, vertexCount); int hullLength = input.ReadInt(true); + Sequence sequence = ReadSequence(input); int[] edges = null; float width = 0, height = 0; if (nonessential) { @@ -433,7 +439,7 @@ namespace Spine { } if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence); if (mesh == null) return null; mesh.Path = path; mesh.r = ((color & 0xff000000) >> 24) / 255f; @@ -445,8 +451,9 @@ namespace Spine { mesh.WorldVerticesLength = vertexCount << 1; mesh.triangles = triangles; mesh.regionUVs = uvs; - mesh.UpdateUVs(); + if (sequence == null) mesh.UpdateRegion(); mesh.HullLength = hullLength << 1; + mesh.Sequence = sequence; if (nonessential) { mesh.Edges = edges; mesh.Width = width * scale; @@ -459,7 +466,8 @@ namespace Spine { int color = input.ReadInt(); String skinName = input.ReadStringRef(); String parent = input.ReadStringRef(); - bool inheritDeform = input.ReadBoolean(); + bool inheritTimelines = input.ReadBoolean(); + Sequence sequence = ReadSequence(input); float width = 0, height = 0; if (nonessential) { width = input.ReadFloat(); @@ -467,18 +475,19 @@ namespace Spine { } if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence); if (mesh == null) return null; mesh.Path = path; mesh.r = ((color & 0xff000000) >> 24) / 255f; mesh.g = ((color & 0x00ff0000) >> 16) / 255f; mesh.b = ((color & 0x0000ff00) >> 8) / 255f; mesh.a = ((color & 0x000000ff)) / 255f; + mesh.Sequence = sequence; if (nonessential) { mesh.Width = width * scale; mesh.Height = height * scale; } - linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent, inheritDeform)); + linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent, inheritTimelines)); return mesh; } case AttachmentType.Path: { @@ -535,6 +544,15 @@ namespace Spine { return null; } + private Sequence ReadSequence (SkeletonInput input) { + if (!input.ReadBoolean()) return null; + Sequence sequence = new Sequence(input.ReadInt(true)); + sequence.Start = input.ReadInt(true); + sequence.Digits = input.ReadInt(true); + sequence.SetupIndex = input.ReadInt(true); + return sequence; + } + private Vertices ReadVertices (SkeletonInput input, int vertexCount) { float scale = this.scale; int verticesLength = vertexCount << 1; @@ -904,58 +922,76 @@ namespace Spine { } } - // Deform timelines. + // Attachment timelines. for (int i = 0, n = input.ReadInt(true); i < n; i++) { Skin skin = skeletonData.skins.Items[input.ReadInt(true)]; for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { int slotIndex = input.ReadInt(true); for (int iii = 0, nnn = input.ReadInt(true); iii < nnn; iii++) { String attachmentName = input.ReadStringRef(); - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, attachmentName); - if (attachment == null) throw new SerializationException("Vertex attachment not found: " + attachmentName); - bool weighted = attachment.Bones != null; - float[] vertices = attachment.Vertices; - int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment == null) throw new SerializationException("Timeline attachment not found: " + attachmentName); - int frameCount = input.ReadInt(true), frameLast = frameCount - 1; - DeformTimeline timeline = new DeformTimeline(frameCount, input.ReadInt(true), slotIndex, attachment); + int timelineType = input.ReadByte(), frameCount = input.ReadInt(true), frameLast = frameCount - 1; + switch (timelineType) { + case ATTACHMENT_DEFORM: { + VertexAttachment vertexAttachment = (VertexAttachment)attachment; + bool weighted = vertexAttachment.Bones != null; + float[] vertices = vertexAttachment.Vertices; + int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; - float time = input.ReadFloat(); - for (int frame = 0, bezier = 0; ; frame++) { - float[] deform; - int end = input.ReadInt(true); - if (end == 0) - deform = weighted ? new float[deformLength] : vertices; - else { - deform = new float[deformLength]; - int start = input.ReadInt(true); - end += start; - if (scale == 1) { - for (int v = start; v < end; v++) - deform[v] = input.ReadFloat(); - } else { - for (int v = start; v < end; v++) - deform[v] = input.ReadFloat() * scale; + DeformTimeline timeline = new DeformTimeline(frameCount, input.ReadInt(true), slotIndex, vertexAttachment); + + float time = input.ReadFloat(); + for (int frame = 0, bezier = 0; ; frame++) { + float[] deform; + int end = input.ReadInt(true); + if (end == 0) + deform = weighted ? new float[deformLength] : vertices; + else { + deform = new float[deformLength]; + int start = input.ReadInt(true); + end += start; + if (scale == 1) { + for (int v = start; v < end; v++) + deform[v] = input.ReadFloat(); + } else { + for (int v = start; v < end; v++) + deform[v] = input.ReadFloat() * scale; + } + if (!weighted) { + for (int v = 0, vn = deform.Length; v < vn; v++) + deform[v] += vertices[v]; + } } - if (!weighted) { - for (int v = 0, vn = deform.Length; v < vn; v++) - deform[v] += vertices[v]; + timeline.SetFrame(frame, time, deform); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + switch (input.ReadByte()) { + case CURVE_STEPPED: + timeline.SetStepped(frame); + break; + case CURVE_BEZIER: + SetBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1); + break; } + time = time2; } - timeline.SetFrame(frame, time, deform); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frame); - break; - case CURVE_BEZIER: - SetBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1); - break; - } - time = time2; + timelines.Add(timeline); + break; } - timelines.Add(timeline); + case ATTACHMENT_SEQUENCE: { + SequenceTimeline timeline = new SequenceTimeline(frameCount, slotIndex, attachment); + for (int frame = 0; frame < frameCount; frame++) { + float time = input.ReadFloat(); + int modeAndIndex = input.ReadInt(); + timeline.SetFrame(frame, time, (SequenceMode)(modeAndIndex & 0xf), modeAndIndex >> 4, + input.ReadFloat()); + } + timelines.Add(timeline); + break; + } // end case + } // end switch } } } diff --git a/spine-csharp/src/SkeletonData.cs b/spine-csharp/src/SkeletonData.cs index d864af997..7e7ce1c61 100644 --- a/spine-csharp/src/SkeletonData.cs +++ b/spine-csharp/src/SkeletonData.cs @@ -43,6 +43,7 @@ namespace Spine { internal ExposedList ikConstraints = new ExposedList(); internal ExposedList transformConstraints = new ExposedList(); internal ExposedList pathConstraints = new ExposedList(); + internal ExposedList springConstraints = new ExposedList(); internal float x, y, width, height; internal string version, hash; @@ -95,7 +96,7 @@ namespace Spine { /// The dopesheet FPS in Spine, or zero if nonessential data was not exported. public float Fps { get { return fps; } set { fps = value; } } - // --- Bones. + // --- Bones /// /// Finds a bone by comparing each bone's name. @@ -111,7 +112,7 @@ namespace Spine { return null; } - // --- Slots. + // --- Slots /// May be null. public SlotData FindSlot (string slotName) { @@ -124,7 +125,7 @@ namespace Spine { return null; } - // --- Skins. + // --- Skins /// May be null. public Skin FindSkin (string skinName) { @@ -134,7 +135,7 @@ namespace Spine { return null; } - // --- Events. + // --- Events /// May be null. public EventData FindEvent (string eventDataName) { @@ -144,7 +145,7 @@ namespace Spine { return null; } - // --- Animations. + // --- Animations /// May be null. public Animation FindAnimation (string animationName) { @@ -157,7 +158,7 @@ namespace Spine { return null; } - // --- IK constraints. + // --- IK constraints /// May be null. public IkConstraintData FindIkConstraint (string constraintName) { @@ -170,7 +171,7 @@ namespace Spine { return null; } - // --- Transform constraints. + // --- Transform constraints /// May be null. public TransformConstraintData FindTransformConstraint (string constraintName) { @@ -183,8 +184,12 @@ namespace Spine { return null; } - // --- Path constraints. + // --- Path constraints + /// + /// Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method + /// than to call it multiple times. + /// /// May be null. public PathConstraintData FindPathConstraint (string constraintName) { if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); @@ -196,6 +201,23 @@ namespace Spine { return null; } + // --- Spring constraints + + /// + /// Finds a spring constraint by comparing each spring constraint's name. It is more efficient to cache the results of this + /// method than to call it multiple times. + /// + /// May be null. + public SpringConstraintData FindSpringConstraint (String constraintName) { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + Object[] springConstraints = this.springConstraints.Items; + for (int i = 0, n = this.springConstraints.Count; i < n; i++) { + SpringConstraintData constraint = (SpringConstraintData)springConstraints[i]; + if (constraint.name.Equals(constraintName)) return constraint; + } + return null; + } + // --- override public string ToString () { diff --git a/spine-csharp/src/SkeletonJson.cs b/spine-csharp/src/SkeletonJson.cs index 3253b4899..beb2bea2f 100644 --- a/spine-csharp/src/SkeletonJson.cs +++ b/spine-csharp/src/SkeletonJson.cs @@ -336,9 +336,9 @@ namespace Spine { if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh; + linkedMesh.mesh.TimelineAttachment = linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh; linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); + if (linkedMesh.mesh.Region != null) linkedMesh.mesh.UpdateRegion(); } linkedMeshes.Clear(); @@ -386,11 +386,13 @@ namespace Spine { var typeName = GetString(map, "type", "region"); var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); - string path = GetString(map, "path", name); - switch (type) { - case AttachmentType.Region: - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + case AttachmentType.Region: { + string path = GetString(map, "path", name); + object sequenceJson; + map.TryGetValue("sequence", out sequenceJson); + Sequence sequence = ReadSequence(sequenceJson); + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path, sequence); if (region == null) return null; region.Path = path; region.x = GetFloat(map, "x", 0) * scale; @@ -400,6 +402,7 @@ namespace Spine { region.rotation = GetFloat(map, "rotation", 0); region.width = GetFloat(map, "width", 32) * scale; region.height = GetFloat(map, "height", 32) * scale; + region.sequence = sequence; if (map.ContainsKey("color")) { var color = (string)map["color"]; @@ -409,8 +412,9 @@ namespace Spine { region.a = ToColor(color, 3); } - region.UpdateOffset(); + if (region.Region != null) region.UpdateRegion(); return region; + } case AttachmentType.Boundingbox: BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); if (box == null) return null; @@ -418,7 +422,11 @@ namespace Spine { return box; case AttachmentType.Mesh: case AttachmentType.Linkedmesh: { - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + string path = GetString(map, "path", name); + object sequenceJson; + map.TryGetValue("sequence", out sequenceJson); + Sequence sequence = ReadSequence(sequenceJson); + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence); if (mesh == null) return null; mesh.Path = path; @@ -432,10 +440,11 @@ namespace Spine { mesh.Width = GetFloat(map, "width", 0) * scale; mesh.Height = GetFloat(map, "height", 0) * scale; + mesh.Sequence = sequence; string parent = GetString(map, "parent", null); if (parent != null) { - linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent, GetBoolean(map, "deform", true))); + linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent, GetBoolean(map, "timelines", true))); return mesh; } @@ -443,7 +452,7 @@ namespace Spine { ReadVertices(map, mesh, uvs.Length); mesh.triangles = GetIntArray(map, "triangles"); mesh.regionUVs = uvs; - mesh.UpdateUVs(); + if (mesh.Region != null) mesh.UpdateRegion(); if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) << 1; if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); @@ -494,6 +503,16 @@ namespace Spine { return null; } + public static Sequence ReadSequence (object sequenceJson) { + var map = sequenceJson as Dictionary; + if (map == null) return null; + Sequence sequence = new Sequence(GetInt(map, "count")); + sequence.start = GetInt(map, "start", 1); + sequence.digits = GetInt(map, "digits", 0); + sequence.setupIndex = GetInt(map, "setup", 0); + return sequence; + } + private void ReadVertices (Dictionary map, VertexAttachment attachment, int verticesLength) { attachment.WorldVerticesLength = verticesLength; float[] vertices = GetFloatArray(map, "vertices", 1); @@ -549,7 +568,7 @@ namespace Spine { var timeline = new AttachmentTimeline(frames, slotIndex); int frame = 0; foreach (Dictionary keyMap in values) { - timeline.SetFrame(frame++, GetFloat(keyMap, "time", 0), (string)keyMap["name"]); + timeline.SetFrame(frame++, GetFloat(keyMap, "time", 0), GetString(keyMap, "name", null)); } timelines.Add(timeline); @@ -945,59 +964,82 @@ namespace Spine { } } - // Deform timelines. - if (map.ContainsKey("deform")) { - foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) { - Skin skin = skeletonData.FindSkin(deformMap.Key); - foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) { - int slotIndex = FindSlotIndex(skeletonData, slotMap.Key); - foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) { - var values = (List)timelineMap.Value; - var keyMapEnumerator = values.GetEnumerator(); - if (!keyMapEnumerator.MoveNext()) continue; - var keyMap = (Dictionary)keyMapEnumerator.Current; - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); - if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); - bool weighted = attachment.bones != null; - float[] vertices = attachment.vertices; - int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; - DeformTimeline timeline = new DeformTimeline(values.Count, values.Count, slotIndex, attachment); - float time = GetFloat(keyMap, "time", 0); - for (int frame = 0, bezier = 0; ; frame++) { - float[] deform; - if (!keyMap.ContainsKey("vertices")) { - deform = weighted ? new float[deformLength] : vertices; - } else { - deform = new float[deformLength]; - int start = GetInt(keyMap, "offset", 0); - float[] verticesValue = GetFloatArray(keyMap, "vertices", 1); - Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); - if (scale != 1) { - for (int i = start, n = i + verticesValue.Length; i < n; i++) - deform[i] *= scale; - } + // Attachment timelines. + if (map.ContainsKey("attachments")) { + foreach (KeyValuePair attachmentsMap in (Dictionary)map["attachments"]) { + Skin skin = skeletonData.FindSkin(attachmentsMap.Key); + foreach (KeyValuePair slotMap in (Dictionary)attachmentsMap.Value) { + SlotData slot = skeletonData.FindSlot(slotMap.Key); + if (slot == null) throw new Exception("Slot not found: " + slotMap.Key); + foreach (KeyValuePair attachmentMap in (Dictionary)slotMap.Value) { + Attachment attachment = skin.GetAttachment(slot.index, attachmentMap.Key); + if (attachment == null) throw new Exception("Timeline attachment not found: " + attachmentMap.Key); + foreach (KeyValuePair timelineMap in (Dictionary)attachmentMap.Value) { + var values = (List)timelineMap.Value; + var keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + var keyMap = (Dictionary)keyMapEnumerator.Current; + int frames = values.Count; + string timelineName = timelineMap.Key; + if (timelineName == "deform") { + VertexAttachment vertexAttachment = (VertexAttachment)attachment; + bool weighted = vertexAttachment.bones != null; + float[] vertices = vertexAttachment.vertices; + int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; - if (!weighted) { - for (int i = 0; i < deformLength; i++) - deform[i] += vertices[i]; - } - } + DeformTimeline timeline = new DeformTimeline(frames, frames, slot.Index, vertexAttachment); + float time = GetFloat(keyMap, "time", 0); + for (int frame = 0, bezier = 0; ; frame++) { + float[] deform; + if (!keyMap.ContainsKey("vertices")) { + deform = weighted ? new float[deformLength] : vertices; + } else { + deform = new float[deformLength]; + int start = GetInt(keyMap, "offset", 0); + float[] verticesValue = GetFloatArray(keyMap, "vertices", 1); + Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); + if (scale != 1) { + for (int i = start, n = i + verticesValue.Length; i < n; i++) + deform[i] *= scale; + } - timeline.SetFrame(frame, time, deform); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; + if (!weighted) { + for (int i = 0; i < deformLength; i++) + deform[i] += vertices[i]; + } + } + + timeline.SetFrame(frame, time, deform); + if (!keyMapEnumerator.MoveNext()) { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1); + } + time = time2; + keyMap = nextMap; + } + timelines.Add(timeline); + } else if (timelineName == "sequence") { + SequenceTimeline timeline = new SequenceTimeline(frames, slot.index, attachment); + float lastDelay = 0; + for (int frame = 0; keyMap != null; + keyMapEnumerator.MoveNext(), keyMap = (Dictionary)keyMapEnumerator.Current, frame++) { + + float delay = GetFloat(keyMap, "delay", lastDelay); + SequenceMode sequenceMode = (SequenceMode)Enum.Parse(typeof(SequenceMode), + GetString(keyMap, "mode", "hold"), true); + timeline.SetFrame(frame, GetFloat(keyMap, "time", 0), + sequenceMode, GetInt(keyMap, "index", 0), delay); + lastDelay = delay; + } + timelines.Add(timeline); } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1); - } - time = time2; - keyMap = nextMap; } - timelines.Add(timeline); } } } @@ -1174,6 +1216,11 @@ namespace Spine { return (int)(float)map[name]; } + static int GetInt (Dictionary map, string name) { + if (!map.ContainsKey(name)) throw new ArgumentException("Named value not found: " + name); + return (int)(float)map[name]; + } + static bool GetBoolean (Dictionary map, string name, bool defaultValue) { if (!map.ContainsKey(name)) return defaultValue; return (bool)map[name]; diff --git a/spine-csharp/src/SkeletonLoader.cs b/spine-csharp/src/SkeletonLoader.cs index 6b6b7e40e..bbd0ae9cd 100644 --- a/spine-csharp/src/SkeletonLoader.cs +++ b/spine-csharp/src/SkeletonLoader.cs @@ -77,14 +77,14 @@ namespace Spine { internal string parent, skin; internal int slotIndex; internal MeshAttachment mesh; - internal bool inheritDeform; + internal bool inheritTimelines; - public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritDeform) { + public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritTimelines) { this.mesh = mesh; this.skin = skin; this.slotIndex = slotIndex; this.parent = parent; - this.inheritDeform = inheritDeform; + this.inheritTimelines = inheritTimelines; } } diff --git a/spine-csharp/src/Slot.cs b/spine-csharp/src/Slot.cs index d504a7314..3ac1650d6 100644 --- a/spine-csharp/src/Slot.cs +++ b/spine-csharp/src/Slot.cs @@ -43,7 +43,7 @@ namespace Spine { internal float r2, g2, b2; internal bool hasSecondColor; internal Attachment attachment; - internal float attachmentTime; + internal int sequenceIndex; internal ExposedList deform = new ExposedList(); internal int attachmentState; @@ -83,7 +83,7 @@ namespace Spine { hasSecondColor = slot.hasSecondColor; attachment = slot.attachment; - attachmentTime = slot.attachmentTime; + sequenceIndex = slot.sequenceIndex; deform.AddRange(slot.deform); } @@ -135,27 +135,26 @@ namespace Spine { /// The current attachment for the slot, or null if the slot has no attachment. get { return attachment; } /// - /// Sets the slot's attachment and, if the attachment changed, resets and clears the . - /// The deform is not cleared if the old attachment has the same as the specified - /// attachment. + /// Sets the slot's attachment and, if the attachment changed, resets and clears the . + /// The deform is not cleared if the old attachment has the same as the + /// specified attachment. /// May be null. set { if (attachment == value) return; if (!(value is VertexAttachment) || !(this.attachment is VertexAttachment) - || ((VertexAttachment)value).DeformAttachment != ((VertexAttachment)this.attachment).DeformAttachment) { + || ((VertexAttachment)value).TimelineAttachment != ((VertexAttachment)this.attachment).TimelineAttachment) { deform.Clear(); } this.attachment = value; - attachmentTime = bone.skeleton.time; + sequenceIndex = -1; } } - /// The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton - /// - public float AttachmentTime { - get { return bone.skeleton.time - attachmentTime; } - set { attachmentTime = bone.skeleton.time - value; } - } + /// + /// The index of the texture region to display when the slot's attachment has a . -1 represents the + /// . + /// + public int SequenceIndex { get { return sequenceIndex; } set { sequenceIndex = value; } } /// Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a /// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. diff --git a/spine-csharp/src/SpringConstraint.cs b/spine-csharp/src/SpringConstraint.cs new file mode 100644 index 000000000..a775af5c1 --- /dev/null +++ b/spine-csharp/src/SpringConstraint.cs @@ -0,0 +1,105 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated September 24, 2021. Replaces all prior versions. + * + * Copyright (c) 2013-2021, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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 + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using System; + +namespace Spine { + /// + /// Stores the current pose for a spring constraint. A spring constraint applies physics to bones. + /// + /// See Spring constraints in the Spine User Guide. + /// + public class SpringConstraint : IUpdatable { + internal readonly SpringConstraintData data; + internal readonly ExposedList bones; + // BOZO! - stiffness -> strength. stiffness, damping, rope, stretch -> move to spring. + internal float mix, friction, gravity, wind, stiffness, damping; + internal bool rope, stretch; + + internal bool active; + + public SpringConstraint (SpringConstraintData data, Skeleton skeleton) { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + mix = data.mix; + friction = data.friction; + gravity = data.gravity; + wind = data.wind; + stiffness = data.stiffness; + damping = data.damping; + rope = data.rope; + stretch = data.stretch; + + bones = new ExposedList(data.Bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.bones.Items[boneData.index]); + } + + /// Copy constructor. + public SpringConstraint (SpringConstraint constraint, Skeleton skeleton) { + if (constraint == null) throw new ArgumentNullException("constraint", "constraint cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + data = constraint.data; + bones = new ExposedList(constraint.bones.Count); + foreach (Bone bone in constraint.bones) + bones.Add(skeleton.bones.Items[bone.data.index]); + mix = constraint.mix; + friction = constraint.friction; + gravity = constraint.gravity; + wind = constraint.wind; + stiffness = constraint.stiffness; + damping = constraint.damping; + rope = constraint.rope; + stretch = constraint.stretch; + } + + /// Applies the constraint to the constrained bones. + public void Update () { + + } + + /// A percentage (0-1) that controls the mix between the constrained and unconstrained poses. + public float Mix { get { return mix; } set { mix = value; } } + public float Friction { get { return friction; } set { friction = value; } } + public float Gravity { get { return gravity; } set { gravity = value; } } + public float Wind { get { return wind; } set { wind = value; } } + public float Stiffness { get { return stiffness; } set { stiffness = value; } } + public float Damping { get { return damping; } set { damping = value; } } + public bool Rope { get { return rope; } set { rope = value; } } + public bool Stretch { get { return stretch; } set { stretch = value; } } + public bool Active { get { return active; } } + /// The spring constraint's setup pose data. + public SpringConstraintData Data { get { return data; } } + + override public string ToString () { + return data.name; + } + } +} diff --git a/spine-csharp/src/SpringConstraint.cs.meta b/spine-csharp/src/SpringConstraint.cs.meta new file mode 100644 index 000000000..e06a49dfe --- /dev/null +++ b/spine-csharp/src/SpringConstraint.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e2816491d178b3b4986920107586ce55 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-csharp/src/SpringConstraintData.cs b/spine-csharp/src/SpringConstraintData.cs new file mode 100644 index 000000000..787159aa1 --- /dev/null +++ b/spine-csharp/src/SpringConstraintData.cs @@ -0,0 +1,59 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated September 24, 2021. Replaces all prior versions. + * + * Copyright (c) 2013-2021, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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 + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using System; + +namespace Spine { + /// + /// Stores the setup pose for a . + /// + /// See Spring constraints in the Spine User Guide. + /// + public class SpringConstraintData : ConstraintData { + internal ExposedList bones = new ExposedList(); + internal float mix, friction, gravity, wind, stiffness, damping; + internal bool rope, stretch; + + public SpringConstraintData (string name) : base(name) { + } + + /// The bones that are constrained by this spring constraint. + public ExposedList Bones { get { return bones; } } + + /// A percentage (0-1) that controls the mix between the constrained and unconstrained poses. + public float Mix { get { return mix; } set { mix = value; } } + public float Friction { get { return friction; } set { friction = value; } } + public float Gravity { get { return gravity; } set { gravity = value; } } + public float Wind { get { return wind; } set { wind = value; } } + public float Stiffness { get { return stiffness; } set { stiffness = value; } } + public float Damping { get { return damping; } set { damping = value; } } + public bool Rope { get { return rope; } set { rope = value; } } + public bool Stretch { get { return stretch; } set { stretch = value; } } + } +} diff --git a/spine-csharp/src/SpringConstraintData.cs.meta b/spine-csharp/src/SpringConstraintData.cs.meta new file mode 100644 index 000000000..d883a6d05 --- /dev/null +++ b/spine-csharp/src/SpringConstraintData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 438688f6194e6dc40953a23d05d48e1a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-csharp/src/TextureRegion.cs b/spine-csharp/src/TextureRegion.cs new file mode 100644 index 000000000..9f056c8c8 --- /dev/null +++ b/spine-csharp/src/TextureRegion.cs @@ -0,0 +1,49 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated September 24, 2021. Replaces all prior versions. + * + * Copyright (c) 2013-2021, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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 + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) +#define IS_UNITY +#endif + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Reflection; + + +namespace Spine { + public class TextureRegion { + public int width, height; + public float u, v, u2, v2; + + virtual public int OriginalWidth { get { return width; } } + virtual public int OriginalHeight { get { return height; } } + } +} diff --git a/spine-csharp/src/TransformConstraint.cs b/spine-csharp/src/TransformConstraint.cs index 1c57d53e3..b80804429 100644 --- a/spine-csharp/src/TransformConstraint.cs +++ b/spine-csharp/src/TransformConstraint.cs @@ -38,8 +38,8 @@ namespace Spine { /// See Transform constraints in the Spine User Guide. /// public class TransformConstraint : IUpdatable { - internal TransformConstraintData data; - internal ExposedList bones; + internal readonly TransformConstraintData data; + internal readonly ExposedList bones; internal Bone target; internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; @@ -57,9 +57,9 @@ namespace Spine { mixShearY = data.mixShearY; bones = new ExposedList(); foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); + bones.Add(skeleton.bones.Items[boneData.index]); - target = skeleton.FindBone(data.target.name); + target = skeleton.bones.Items[data.target.index]; } /// Copy constructor. diff --git a/spine-csharp/src/package.json b/spine-csharp/src/package.json index 106d9ffe8..b0013ab40 100644 --- a/spine-csharp/src/package.json +++ b/spine-csharp/src/package.json @@ -2,7 +2,7 @@ "name": "com.esotericsoftware.spine.spine-csharp", "displayName": "spine-csharp Runtime", "description": "This plugin provides the spine-csharp core runtime.", - "version": "4.0.0", + "version": "4.1.0", "unity": "2018.3", "author": { "name": "Esoteric Software", diff --git a/spine-csharp/tests/src/AnimationStateTests.cs b/spine-csharp/tests/src/AnimationStateTests.cs index 8ac46e332..7a6555f8c 100644 --- a/spine-csharp/tests/src/AnimationStateTests.cs +++ b/spine-csharp/tests/src/AnimationStateTests.cs @@ -44,11 +44,11 @@ namespace Spine { } class NullAttachmentLoader : AttachmentLoader { - public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) { + public RegionAttachment NewRegionAttachment (Skin skin, string name, string path, Sequence sequence) { return null; } - public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) { + public MeshAttachment NewMeshAttachment (Skin skin, string name, string path, Sequence sequence) { return null; } @@ -851,7 +851,6 @@ namespace Spine { state.Apply(skeleton); while (time < endTime) { time += incr; - skeleton.Update(incr); state.Update(incr); // Reduce float discrepancies for tests. diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java index 6b239bebc..6efc7e482 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java @@ -80,7 +80,7 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion arraycopy(other.triangles, 0, triangles, 0, triangles.length); hullLength = other.hullLength; - sequence = new Sequence(other.sequence); + sequence = other.sequence != null ? new Sequence(other.sequence) : null; // Nonessential. if (other.edges != null) { diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java index ae760ca2f..8bfa883d1 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java @@ -75,7 +75,7 @@ public class RegionAttachment extends Attachment implements HasTextureRegion { arraycopy(other.uvs, 0, uvs, 0, 8); arraycopy(other.offset, 0, offset, 0, 8); color.set(other.color); - sequence = new Sequence(other.sequence); + sequence = other.sequence != null ? new Sequence(other.sequence) : null; } /** Calculates the {@link #offset} and {@link #uvs} using the region and the attachment's transform. Must be called if the diff --git a/spine-ts/README.md b/spine-ts/README.md index 21cf01506..ed7be5faa 100644 --- a/spine-ts/README.md +++ b/spine-ts/README.md @@ -33,7 +33,7 @@ The spine-ts WebGL and Player backends support all Spine features. spine-ts Canvas does not support mesh attachments, clipping attachments, or color tinting. Only the alpha channel from tint colors is applied. Experimental support for mesh attachments can be enabled by setting `spine.SkeletonRenderer.useTriangleRendering` to true. Note that this experimental mesh rendering is slow and render with artifacts on some browsers. -spine-ts THREE.JS does not support two color tinting or blend modes. The THREE.JS backend provides `SkeletonMesh.zOffset` to avoid z-fighting. Adjust to your near/far plane settings. +spine-ts THREE.JS does not support two color tinting. The THREE.JS backend provides `SkeletonMesh.zOffset` to avoid z-fighting. Adjust to your near/far plane settings. ## Usage diff --git a/spine-ts/package-lock.json b/spine-ts/package-lock.json index a8204a6b1..9994a1738 100644 --- a/spine-ts/package-lock.json +++ b/spine-ts/package-lock.json @@ -1,12 +1,12 @@ { "name": "@esotericsoftware/spine-ts", - "version": "4.1.1", + "version": "4.1.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@esotericsoftware/spine-ts", - "version": "4.1.1", + "version": "4.1.4", "license": "LicenseRef-LICENSE", "workspaces": [ "spine-core", @@ -53,9 +53,9 @@ "dev": true }, "node_modules/@types/three": { - "version": "0.131.1", - "resolved": "https://registry.npmjs.org/@types/three/-/three-0.131.1.tgz", - "integrity": "sha512-unnjsolcm7R90e4XK9qMq4JYEzly0XQNa0pG8RAOMZeVzj3FLIFPymAYUx4Osz0gY9jFZz8omIQplqiieEE7gw==" + "version": "0.133.1", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.133.1.tgz", + "integrity": "sha512-XqBrP/+kbs+o0CYRhCVVE95v7FaL2bO5Z7+3VQJE0nEyjo+9LoLfeNgZITOnndKHxM+7ltEciAIR7uE0SZlsOg==" }, "node_modules/accepts": { "version": "1.3.7", @@ -7551,9 +7551,9 @@ } }, "node_modules/three": { - "version": "0.132.2", - "resolved": "https://registry.npmjs.org/three/-/three-0.132.2.tgz", - "integrity": "sha512-0wcR7LxxkXMn6Gi58gEs3QvY8WpTVXA31L2VOvpjm4ZPYFRHCZC13UqynheFoS5OXDYgtBneN0dhbaNBE8iLhQ==" + "version": "0.133.1", + "resolved": "https://registry.npmjs.org/three/-/three-0.133.1.tgz", + "integrity": "sha512-WydohO8ll949B0FTD6MGz59Yv2Lwj8hvObg/0Heh2r42S6+tQC1WByfCNRdmG4D7+odfGod+n8JPV1I2xrboWw==" }, "node_modules/through": { "version": "2.3.8", @@ -7949,41 +7949,41 @@ }, "spine-canvas": { "name": "@esotericsoftware/spine-canvas", - "version": "4.1.1", + "version": "4.1.4", "license": "LicenseRef-LICENSE", "dependencies": { - "@esotericsoftware/spine-core": "^4.1.1" + "@esotericsoftware/spine-core": "^4.1.3" } }, "spine-core": { "name": "@esotericsoftware/spine-core", - "version": "4.1.1", + "version": "4.1.4", "license": "LicenseRef-LICENSE" }, "spine-player": { "name": "@esotericsoftware/spine-player", - "version": "4.1.1", + "version": "4.1.4", "license": "LicenseRef-LICENSE", "dependencies": { - "@esotericsoftware/spine-webgl": "^4.1.1" + "@esotericsoftware/spine-webgl": "^4.1.3" } }, "spine-threejs": { "name": "@esotericsoftware/spine-threejs", - "version": "4.1.1", + "version": "4.1.4", "license": "LicenseRef-LICENSE", "dependencies": { - "@esotericsoftware/spine-core": "^4.1.1", - "@types/three": "^0.131.0", - "three": "^0.132.0" + "@esotericsoftware/spine-core": "^4.1.3", + "@types/three": "^0.133.1", + "three": "^0.133.1" } }, "spine-webgl": { "name": "@esotericsoftware/spine-webgl", - "version": "4.1.1", + "version": "4.1.4", "license": "LicenseRef-LICENSE", "dependencies": { - "@esotericsoftware/spine-core": "^4.1.1" + "@esotericsoftware/spine-core": "^4.1.4" } } }, @@ -7991,7 +7991,7 @@ "@esotericsoftware/spine-canvas": { "version": "file:spine-canvas", "requires": { - "@esotericsoftware/spine-core": "^4.1.1" + "@esotericsoftware/spine-core": "^4.1.3" } }, "@esotericsoftware/spine-core": { @@ -8000,21 +8000,21 @@ "@esotericsoftware/spine-player": { "version": "file:spine-player", "requires": { - "@esotericsoftware/spine-webgl": "^4.1.1" + "@esotericsoftware/spine-webgl": "^4.1.3" } }, "@esotericsoftware/spine-threejs": { "version": "file:spine-threejs", "requires": { - "@esotericsoftware/spine-core": "^4.1.1", - "@types/three": "^0.131.0", - "three": "^0.132.0" + "@esotericsoftware/spine-core": "^4.1.3", + "@types/three": "^0.133.1", + "three": "^0.133.1" } }, "@esotericsoftware/spine-webgl": { "version": "file:spine-webgl", "requires": { - "@esotericsoftware/spine-core": "^4.1.1" + "@esotericsoftware/spine-core": "^4.1.4" } }, "@types/offscreencanvas": { @@ -8024,9 +8024,9 @@ "dev": true }, "@types/three": { - "version": "0.131.1", - "resolved": "https://registry.npmjs.org/@types/three/-/three-0.131.1.tgz", - "integrity": "sha512-unnjsolcm7R90e4XK9qMq4JYEzly0XQNa0pG8RAOMZeVzj3FLIFPymAYUx4Osz0gY9jFZz8omIQplqiieEE7gw==" + "version": "0.133.1", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.133.1.tgz", + "integrity": "sha512-XqBrP/+kbs+o0CYRhCVVE95v7FaL2bO5Z7+3VQJE0nEyjo+9LoLfeNgZITOnndKHxM+7ltEciAIR7uE0SZlsOg==" }, "accepts": { "version": "1.3.7", @@ -13936,9 +13936,9 @@ } }, "three": { - "version": "0.132.2", - "resolved": "https://registry.npmjs.org/three/-/three-0.132.2.tgz", - "integrity": "sha512-0wcR7LxxkXMn6Gi58gEs3QvY8WpTVXA31L2VOvpjm4ZPYFRHCZC13UqynheFoS5OXDYgtBneN0dhbaNBE8iLhQ==" + "version": "0.133.1", + "resolved": "https://registry.npmjs.org/three/-/three-0.133.1.tgz", + "integrity": "sha512-WydohO8ll949B0FTD6MGz59Yv2Lwj8hvObg/0Heh2r42S6+tQC1WByfCNRdmG4D7+odfGod+n8JPV1I2xrboWw==" }, "through": { "version": "2.3.8", diff --git a/spine-ts/package.json b/spine-ts/package.json index f59bf693f..34f8ca92f 100644 --- a/spine-ts/package.json +++ b/spine-ts/package.json @@ -1,6 +1,6 @@ { "name": "@esotericsoftware/spine-ts", - "version": "4.1.1", + "version": "4.1.4", "description": "The official Spine Runtimes for the web.", "files": [ "README.md" @@ -60,4 +60,4 @@ "typescript": "^4.3.5", "@types/offscreencanvas": "^2019.6.4" } -} \ No newline at end of file +} diff --git a/spine-ts/spine-canvas/package.json b/spine-ts/spine-canvas/package.json index e6836fc4e..5bb1b9fa6 100644 --- a/spine-ts/spine-canvas/package.json +++ b/spine-ts/spine-canvas/package.json @@ -1,6 +1,6 @@ { "name": "@esotericsoftware/spine-canvas", - "version": "4.1.1", + "version": "4.1.4", "description": "The official Spine Runtimes for the web.", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -30,6 +30,6 @@ }, "homepage": "https://github.com/esotericsoftware/spine-runtimes#readme", "dependencies": { - "@esotericsoftware/spine-core": "^4.1.1" + "@esotericsoftware/spine-core": "^4.1.3" } -} \ No newline at end of file +} diff --git a/spine-ts/spine-core/package.json b/spine-ts/spine-core/package.json index db2beb67d..53b2528a8 100644 --- a/spine-ts/spine-core/package.json +++ b/spine-ts/spine-core/package.json @@ -1,6 +1,6 @@ { "name": "@esotericsoftware/spine-core", - "version": "4.1.1", + "version": "4.1.4", "description": "The official Spine Runtimes for the web.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/spine-ts/spine-core/src/AssetManagerBase.ts b/spine-ts/spine-core/src/AssetManagerBase.ts index a17edbeae..f52ba8e26 100644 --- a/spine-ts/spine-core/src/AssetManagerBase.ts +++ b/spine-ts/spine-core/src/AssetManagerBase.ts @@ -308,7 +308,7 @@ export class Downloader { this.finish(url, request.status, request.response); }; request.onload = () => { - if (request.status == 200) + if (request.status == 200 || request.status == 0) this.finish(url, 200, new Uint8Array(request.response as ArrayBuffer)); else onerror(); @@ -330,7 +330,7 @@ export class Downloader { private finish (url: string, status: number, data: any) { let callbacks = this.callbacks[url]; delete this.callbacks[url]; - let args = status == 200 ? [data] : [status, data]; + let args = status == 200 || status == 0 ? [data] : [status, data]; for (let i = args.length - 1, n = callbacks.length; i < n; i += 2) callbacks[i].apply(null, args); } diff --git a/spine-ts/spine-core/src/attachments/MeshAttachment.ts b/spine-ts/spine-core/src/attachments/MeshAttachment.ts index 0da79f8f5..11694f899 100644 --- a/spine-ts/spine-core/src/attachments/MeshAttachment.ts +++ b/spine-ts/spine-core/src/attachments/MeshAttachment.ts @@ -181,7 +181,7 @@ export class MeshAttachment extends VertexAttachment implements HasTextureRegion Utils.arrayCopy(this.triangles, 0, copy.triangles, 0, this.triangles.length); copy.hullLength = this.hullLength; - copy.sequence = this.sequence.copy(); + copy.sequence = this.sequence != null ? this.sequence.copy() : null; // Nonessential. if (this.edges) { diff --git a/spine-ts/spine-core/src/attachments/RegionAttachment.ts b/spine-ts/spine-core/src/attachments/RegionAttachment.ts index 70d9598a1..eabec9ef8 100644 --- a/spine-ts/spine-core/src/attachments/RegionAttachment.ts +++ b/spine-ts/spine-core/src/attachments/RegionAttachment.ts @@ -193,7 +193,7 @@ export class RegionAttachment extends Attachment implements HasTextureRegion { Utils.arrayCopy(this.uvs, 0, copy.uvs, 0, 8); Utils.arrayCopy(this.offset, 0, copy.offset, 0, 8); copy.color.setFromColor(this.color); - copy.sequence = this.sequence.copy(); + copy.sequence = this.sequence != null ? this.sequence.copy() : null; return copy; } diff --git a/spine-ts/spine-player/package.json b/spine-ts/spine-player/package.json index 0ee1b10dc..453f858e2 100644 --- a/spine-ts/spine-player/package.json +++ b/spine-ts/spine-player/package.json @@ -1,6 +1,6 @@ { "name": "@esotericsoftware/spine-player", - "version": "4.1.1", + "version": "4.1.4", "description": "The official Spine Runtimes for the web.", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -30,6 +30,6 @@ }, "homepage": "https://github.com/esotericsoftware/spine-runtimes#readme", "dependencies": { - "@esotericsoftware/spine-webgl": "^4.1.1" + "@esotericsoftware/spine-webgl": "^4.1.3" } -} \ No newline at end of file +} diff --git a/spine-ts/spine-threejs/example/index.html b/spine-ts/spine-threejs/example/index.html index 63d760a1b..ca632b15d 100644 --- a/spine-ts/spine-threejs/example/index.html +++ b/spine-ts/spine-threejs/example/index.html @@ -5,7 +5,6 @@ spine-threejs -