增加spine4.1

This commit is contained in:
ww-rm
2025-03-04 20:16:28 +08:00
parent fc3cffff09
commit 8b6d9b1ebb
47 changed files with 14541 additions and 0 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,114 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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.Collections.Generic;
namespace SpineRuntime41 {
/// <summary>Stores mix (crossfade) durations to be applied when AnimationState animations are changed.</summary>
public class AnimationStateData {
internal SkeletonData skeletonData;
readonly Dictionary<AnimationPair, float> animationToMixTime = new Dictionary<AnimationPair, float>(AnimationPairComparer.Instance);
internal float defaultMix;
/// <summary>The SkeletonData to look up animations when they are specified by name.</summary>
public SkeletonData SkeletonData { get { return skeletonData; } }
/// <summary>
/// The mix duration to use when no mix duration has been specifically defined between two animations.</summary>
public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } }
public AnimationStateData (SkeletonData skeletonData) {
if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null.", "skeletonData");
this.skeletonData = skeletonData;
}
/// <summary>Sets a mix duration by animation names.</summary>
public void SetMix (string fromName, string toName, float duration) {
Animation from = skeletonData.FindAnimation(fromName);
if (from == null) throw new ArgumentException("Animation not found: " + fromName, "fromName");
Animation to = skeletonData.FindAnimation(toName);
if (to == null) throw new ArgumentException("Animation not found: " + toName, "toName");
SetMix(from, to, duration);
}
/// <summary>Sets a mix duration when changing from the specified animation to the other.
/// See TrackEntry.MixDuration.</summary>
public void SetMix (Animation from, Animation to, float duration) {
if (from == null) throw new ArgumentNullException("from", "from cannot be null.");
if (to == null) throw new ArgumentNullException("to", "to cannot be null.");
AnimationPair key = new AnimationPair(from, to);
animationToMixTime.Remove(key);
animationToMixTime.Add(key, duration);
}
/// <summary>
/// The mix duration to use when changing from the specified animation to the other,
/// or the DefaultMix if no mix duration has been set.
/// </summary>
public float GetMix (Animation from, Animation to) {
if (from == null) throw new ArgumentNullException("from", "from cannot be null.");
if (to == null) throw new ArgumentNullException("to", "to cannot be null.");
AnimationPair key = new AnimationPair(from, to);
float duration;
if (animationToMixTime.TryGetValue(key, out duration)) return duration;
return defaultMix;
}
public struct AnimationPair {
public readonly Animation a1;
public readonly Animation a2;
public AnimationPair (Animation a1, Animation a2) {
this.a1 = a1;
this.a2 = a2;
}
public override string ToString () {
return a1.name + "->" + a2.name;
}
}
// Avoids boxing in the dictionary.
public class AnimationPairComparer : IEqualityComparer<AnimationPair> {
public static readonly AnimationPairComparer Instance = new AnimationPairComparer();
bool IEqualityComparer<AnimationPair>.Equals (AnimationPair x, AnimationPair y) {
return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2);
}
int IEqualityComparer<AnimationPair>.GetHashCode (AnimationPair obj) {
// from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2);
int h1 = obj.a1.GetHashCode();
return (((h1 << 5) + h1) ^ obj.a2.GetHashCode());
}
}
}
}

View File

@@ -0,0 +1,372 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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;
#if WINDOWS_STOREAPP
using System.Threading.Tasks;
using Windows.Storage;
#endif
namespace SpineRuntime41 {
public class Atlas : IEnumerable<AtlasRegion> {
readonly List<AtlasPage> pages = new List<AtlasPage>();
List<AtlasRegion> regions = new List<AtlasRegion>();
TextureLoader textureLoader;
#region IEnumerable implementation
public IEnumerator<AtlasRegion> GetEnumerator () {
return regions.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
return regions.GetEnumerator();
}
#endregion
public List<AtlasRegion> Regions { get { return regions; } }
public List<AtlasPage> Pages { get { return pages; } }
#if !(IS_UNITY)
#if WINDOWS_STOREAPP
private async Task ReadFile (string path, TextureLoader textureLoader) {
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false);
using (StreamReader reader = new StreamReader(await file.OpenStreamForReadAsync().ConfigureAwait(false))) {
try {
Atlas atlas = new Atlas(reader, Path.GetDirectoryName(path), textureLoader);
this.pages = atlas.pages;
this.regions = atlas.regions;
this.textureLoader = atlas.textureLoader;
} catch (Exception ex) {
throw new Exception("Error reading atlas file: " + path, ex);
}
}
}
public Atlas (string path, TextureLoader textureLoader) {
this.ReadFile(path, textureLoader).Wait();
}
#else
public Atlas (string path, TextureLoader textureLoader) {
#if WINDOWS_PHONE
Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path);
using (StreamReader reader = new StreamReader(stream)) {
#else
using (StreamReader reader = new StreamReader(path)) {
#endif // WINDOWS_PHONE
try {
Atlas atlas = new Atlas(reader, Path.GetDirectoryName(path), textureLoader);
this.pages = atlas.pages;
this.regions = atlas.regions;
this.textureLoader = atlas.textureLoader;
} catch (Exception ex) {
throw new Exception("Error reading atlas file: " + path, ex);
}
}
}
#endif // WINDOWS_STOREAPP
#endif
public Atlas (List<AtlasPage> pages, List<AtlasRegion> regions) {
if (pages == null) throw new ArgumentNullException("pages", "pages cannot be null.");
if (regions == null) throw new ArgumentNullException("regions", "regions cannot be null.");
this.pages = pages;
this.regions = regions;
this.textureLoader = null;
}
public Atlas (TextReader reader, string imagesDir, TextureLoader textureLoader) {
if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null.");
if (imagesDir == null) throw new ArgumentNullException("imagesDir", "imagesDir cannot be null.");
if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null.");
this.textureLoader = textureLoader;
string[] entry = new string[5];
AtlasPage page = null;
AtlasRegion region = null;
Dictionary<string, Action> pageFields = new Dictionary<string, Action>(5);
pageFields.Add("size", () => {
page.width = int.Parse(entry[1], CultureInfo.InvariantCulture);
page.height = int.Parse(entry[2], CultureInfo.InvariantCulture);
});
pageFields.Add("format", () => {
page.format = (Format)Enum.Parse(typeof(Format), entry[1], false);
});
pageFields.Add("filter", () => {
page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[1], false);
page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[2], false);
});
pageFields.Add("repeat", () => {
if (entry[1].IndexOf('x') != -1) page.uWrap = TextureWrap.Repeat;
if (entry[1].IndexOf('y') != -1) page.vWrap = TextureWrap.Repeat;
});
pageFields.Add("pma", () => {
page.pma = entry[1] == "true";
});
Dictionary<string, Action> regionFields = new Dictionary<string, Action>(8);
regionFields.Add("xy", () => { // Deprecated, use bounds.
region.x = int.Parse(entry[1], CultureInfo.InvariantCulture);
region.y = int.Parse(entry[2], CultureInfo.InvariantCulture);
});
regionFields.Add("size", () => { // Deprecated, use bounds.
region.width = int.Parse(entry[1], CultureInfo.InvariantCulture);
region.height = int.Parse(entry[2], CultureInfo.InvariantCulture);
});
regionFields.Add("bounds", () => {
region.x = int.Parse(entry[1], CultureInfo.InvariantCulture);
region.y = int.Parse(entry[2], CultureInfo.InvariantCulture);
region.width = int.Parse(entry[3], CultureInfo.InvariantCulture);
region.height = int.Parse(entry[4], CultureInfo.InvariantCulture);
});
regionFields.Add("offset", () => { // Deprecated, use offsets.
region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture);
region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture);
});
regionFields.Add("orig", () => { // Deprecated, use offsets.
region.originalWidth = int.Parse(entry[1], CultureInfo.InvariantCulture);
region.originalHeight = int.Parse(entry[2], CultureInfo.InvariantCulture);
});
regionFields.Add("offsets", () => {
region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture);
region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture);
region.originalWidth = int.Parse(entry[3], CultureInfo.InvariantCulture);
region.originalHeight = int.Parse(entry[4], CultureInfo.InvariantCulture);
});
regionFields.Add("rotate", () => {
string value = entry[1];
if (value == "true")
region.degrees = 90;
else if (value != "false")
region.degrees = int.Parse(value, CultureInfo.InvariantCulture);
});
regionFields.Add("index", () => {
region.index = int.Parse(entry[1], CultureInfo.InvariantCulture);
});
string line = reader.ReadLine();
// Ignore empty lines before first entry.
while (line != null && line.Trim().Length == 0)
line = reader.ReadLine();
// Header entries.
while (true) {
if (line == null || line.Trim().Length == 0) break;
if (ReadEntry(entry, line) == 0) break; // Silently ignore all header fields.
line = reader.ReadLine();
}
// Page and region entries.
List<string> names = null;
List<int[]> values = null;
while (true) {
if (line == null) break;
if (line.Trim().Length == 0) {
page = null;
line = reader.ReadLine();
} else if (page == null) {
page = new AtlasPage();
page.name = line.Trim();
while (true) {
if (ReadEntry(entry, line = reader.ReadLine()) == 0) break;
Action field;
if (pageFields.TryGetValue(entry[0], out field)) field(); // Silently ignore unknown page fields.
}
textureLoader.Load(page, Path.Combine(imagesDir, page.name));
pages.Add(page);
} else {
region = new AtlasRegion();
region.page = page;
region.name = line;
while (true) {
int count = ReadEntry(entry, line = reader.ReadLine());
if (count == 0) break;
Action field;
if (regionFields.TryGetValue(entry[0], out field))
field();
else {
if (names == null) {
names = new List<string>(8);
values = new List<int[]>(8);
}
names.Add(entry[0]);
int[] entryValues = new int[count];
for (int i = 0; i < count; i++)
int.TryParse(entry[i + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out entryValues[i]); // Silently ignore non-integer values.
values.Add(entryValues);
}
}
if (region.originalWidth == 0 && region.originalHeight == 0) {
region.originalWidth = region.width;
region.originalHeight = region.height;
}
if (names != null && names.Count > 0) {
region.names = names.ToArray();
region.values = values.ToArray();
names.Clear();
values.Clear();
}
region.u = region.x / (float)page.width;
region.v = region.y / (float)page.height;
if (region.degrees == 90) {
region.u2 = (region.x + region.height) / (float)page.width;
region.v2 = (region.y + region.width) / (float)page.height;
int 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;
}
regions.Add(region);
}
}
}
static private int ReadEntry (string[] entry, string line) {
if (line == null) return 0;
line = line.Trim();
if (line.Length == 0) return 0;
int colon = line.IndexOf(':');
if (colon == -1) return 0;
entry[0] = line.Substring(0, colon).Trim();
for (int i = 1, lastMatch = colon + 1; ; i++) {
int comma = line.IndexOf(',', lastMatch);
if (comma == -1) {
entry[i] = line.Substring(lastMatch).Trim();
return i;
}
entry[i] = line.Substring(lastMatch, comma - lastMatch).Trim();
lastMatch = comma + 1;
if (i == 4) return 4;
}
}
public void FlipV () {
for (int i = 0, n = regions.Count; i < n; i++) {
AtlasRegion region = regions[i];
region.v = 1 - region.v;
region.v2 = 1 - region.v2;
}
}
/// <summary>Returns the first region found with the specified name. This method uses string comparison to find the region, so the result
/// should be cached rather than calling this method multiple times.</summary>
/// <returns>The region, or null.</returns>
public AtlasRegion FindRegion (string name) {
for (int i = 0, n = regions.Count; i < n; i++)
if (regions[i].name == name) return regions[i];
return null;
}
public void Dispose () {
if (textureLoader == null) return;
for (int i = 0, n = pages.Count; i < n; i++)
textureLoader.Unload(pages[i].rendererObject);
}
}
public enum Format {
Alpha,
Intensity,
LuminanceAlpha,
RGB565,
RGBA4444,
RGB888,
RGBA8888
}
public enum TextureFilter {
Nearest,
Linear,
MipMap,
MipMapNearestNearest,
MipMapLinearNearest,
MipMapNearestLinear,
MipMapLinearLinear
}
public enum TextureWrap {
MirroredRepeat,
ClampToEdge,
Repeat
}
public class AtlasPage {
public string name;
public int width, height;
public Format format = Format.RGBA8888;
public TextureFilter minFilter = TextureFilter.Nearest;
public TextureFilter magFilter = TextureFilter.Nearest;
public TextureWrap uWrap = TextureWrap.ClampToEdge;
public TextureWrap vWrap = TextureWrap.ClampToEdge;
public bool pma;
public object rendererObject;
public AtlasPage Clone () {
return MemberwiseClone() as AtlasPage;
}
}
public class AtlasRegion : TextureRegion {
public AtlasPage page;
public string name;
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;
}
}
public interface TextureLoader {
void Load (AtlasPage page, string path);
void Unload (Object texture);
}
}

View File

@@ -0,0 +1,109 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
/// <summary>
/// An AttachmentLoader that configures attachments using texture regions from an Atlas.
/// See <a href='http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data'>Loading Skeleton Data</a> in the Spine Runtimes Guide.
/// </summary>
public class AtlasAttachmentLoader : AttachmentLoader {
private Atlas[] atlasArray;
public AtlasAttachmentLoader (params Atlas[] atlasArray) {
if (atlasArray == null) throw new ArgumentNullException("atlas", "atlas array cannot be null.");
this.atlasArray = atlasArray;
}
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);
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, Sequence sequence) {
MeshAttachment attachment = new MeshAttachment(name);
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 BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) {
return new BoundingBoxAttachment(name);
}
public PathAttachment NewPathAttachment (Skin skin, string name) {
return new PathAttachment(name);
}
public PointAttachment NewPointAttachment (Skin skin, string name) {
return new PointAttachment(name);
}
public ClippingAttachment NewClippingAttachment (Skin skin, string name) {
return new ClippingAttachment(name);
}
public AtlasRegion FindRegion (string name) {
AtlasRegion region;
for (int i = 0; i < atlasArray.Length; i++) {
region = atlasArray[i].FindRegion(name);
if (region != null)
return region;
}
return null;
}
}
}

View File

@@ -0,0 +1,56 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
/// <summary>The base class for all attachments.</summary>
abstract public class Attachment {
/// <summary>The attachment's name.</summary>
public string Name { get; }
protected Attachment (string name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null");
this.Name = name;
}
/// <summary>Copy constructor.</summary>
protected Attachment (Attachment other) {
Name = other.Name;
}
override public string ToString () {
return Name;
}
///<summary>Returns a copy of the attachment.</summary>
public abstract Attachment Copy ();
}
}

View File

@@ -0,0 +1,48 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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.
*****************************************************************************/
namespace SpineRuntime41 {
public interface AttachmentLoader {
/// <return>May be null to not load any attachment.</return>
RegionAttachment NewRegionAttachment (Skin skin, string name, string path, Sequence sequence);
/// <return>May be null to not load any attachment.</return>
MeshAttachment NewMeshAttachment (Skin skin, string name, string path, Sequence sequence);
/// <return>May be null to not load any attachment.</return>
BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name);
/// <returns>May be null to not load any attachment</returns>
PathAttachment NewPathAttachment (Skin skin, string name);
PointAttachment NewPointAttachment (Skin skin, string name);
ClippingAttachment NewClippingAttachment (Skin skin, string name);
}
}

View File

@@ -0,0 +1,34 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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.
*****************************************************************************/
namespace SpineRuntime41 {
public enum AttachmentType {
Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping, Sequence
}
}

View File

@@ -0,0 +1,48 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
/// <summary>Attachment that has a polygon for bounds checking.</summary>
public class BoundingBoxAttachment : VertexAttachment {
public BoundingBoxAttachment (string name)
: base(name) {
}
/// <summary>Copy constructor.</summary>
protected BoundingBoxAttachment (BoundingBoxAttachment other)
: base(other) {
}
public override Attachment Copy () {
return new BoundingBoxAttachment(this);
}
}
}

View File

@@ -0,0 +1,51 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
public class ClippingAttachment : VertexAttachment {
internal SlotData endSlot;
public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } }
public ClippingAttachment (string name) : base(name) {
}
/// <summary>Copy constructor.</summary>
protected ClippingAttachment (ClippingAttachment other)
: base(other) {
endSlot = other.endSlot;
}
public override Attachment Copy () {
return new ClippingAttachment(this);
}
}
}

View File

@@ -0,0 +1,56 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
public interface IHasTextureRegion {
/// <summary>The name used to find the <see cref="Region"/></summary>
string Path { get; set; }
/// <summary>
/// Sets the region used to draw the attachment. After setting the region or if the region's properties are changed,
/// <see cref="UpdateRegion()"/> must be called.
/// </summary>
TextureRegion Region { get; set; }
/// <summary>
/// Updates any values the attachment calculates using the <see cref="Region"/>. Must be called after setting the
/// <see cref="Region"/> or if the region's properties are changed.
/// </summary>
void UpdateRegion ();
float R { get; set; }
float G { get; set; }
float B { get; set; }
float A { get; set; }
Sequence Sequence { get; set; }
}
}

View File

@@ -0,0 +1,220 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
/// <summary>Attachment that displays a texture region using a mesh.</summary>
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;
private MeshAttachment parentMesh;
private Sequence sequence;
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; } }
/// <summary>The UV pair for each vertex, normalized within the entire texture.
/// <seealso cref="MeshAttachment.UpdateRegion"/></summary>
public float[] UVs { get { return uvs; } set { uvs = value; } }
public int[] Triangles { get { return triangles; } set { triangles = value; } }
public float R { get { return r; } set { r = value; } }
public float G { get { return g; } set { g = value; } }
public float B { get { return b; } set { b = value; } }
public float A { get { return a; } set { a = value; } }
public string Path { get { return path; } set { path = value; } }
public Sequence Sequence { get { return sequence; } set { sequence = value; } }
public MeshAttachment ParentMesh {
get { return parentMesh; }
set {
parentMesh = value;
if (value != null) {
bones = value.bones;
vertices = value.vertices;
worldVerticesLength = value.worldVerticesLength;
regionUVs = value.regionUVs;
triangles = value.triangles;
HullLength = value.HullLength;
Edges = value.Edges;
Width = value.Width;
Height = value.Height;
}
}
}
// Nonessential.
public int[] Edges { get; set; }
public float Width { get; set; }
public float Height { get; set; }
public MeshAttachment (string name)
: base(name) {
}
/// <summary>Copy constructor. Use <see cref="NewLinkedMesh"/> if the other mesh is a linked mesh.</summary>
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;
int n = uvs.Length;
float u, v, width, 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 {
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;
}
}
/// <summary>If the attachment has a <see cref="Sequence"/>, the region may be changed.</summary>
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);
}
///<summary>Returns a new mesh with this mesh set as the <see cref="ParentMesh"/>.
public MeshAttachment NewLinkedMesh () {
MeshAttachment mesh = new MeshAttachment(Name);
mesh.timelineAttachment = timelineAttachment;
mesh.region = region;
mesh.path = path;
mesh.r = r;
mesh.g = g;
mesh.b = b;
mesh.a = a;
mesh.ParentMesh = parentMesh != null ? parentMesh : this;
if (mesh.Region != null) mesh.UpdateRegion();
return mesh;
}
public override Attachment Copy () {
return parentMesh != null ? NewLinkedMesh() : new MeshAttachment(this);
}
}
}

View File

@@ -0,0 +1,65 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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.Collections.Generic;
namespace SpineRuntime41 {
public class PathAttachment : VertexAttachment {
internal float[] lengths;
internal bool closed, constantSpeed;
/// <summary>The length in the setup pose from the start of the path to the end of each curve.</summary>
public float[] Lengths { get { return lengths; } set { lengths = value; } }
/// <summary>If true, the start and end knots are connected.</summary>
public bool Closed { get { return closed; } set { closed = value; } }
/// <summary>If true, additional calculations are performed to make computing positions along the path more accurate and movement along
/// the path have a constant speed.</summary>
public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } }
public PathAttachment (String name)
: base(name) {
}
/// <summary>Copy constructor.</summary>
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 () {
return new PathAttachment(this);
}
}
}

View File

@@ -0,0 +1,71 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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.
*****************************************************************************/
namespace SpineRuntime41 {
/// <summary>
/// An attachment which is a single point and a rotation. This can be used to spawn projectiles, particles, etc. A bone can be
/// used in similar ways, but a PointAttachment is slightly less expensive to compute and can be hidden, shown, and placed in a
/// skin.
/// <p>
/// See <a href="http://esotericsoftware.com/spine-point-attachments">Point Attachments</a> in the Spine User Guide.
/// </summary>
public class PointAttachment : Attachment {
internal float x, y, rotation;
public float X { get { return x; } set { x = value; } }
public float Y { get { return y; } set { y = value; } }
public float Rotation { get { return rotation; } set { rotation = value; } }
public PointAttachment (string name)
: base(name) {
}
/** 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);
}
public float ComputeWorldRotation (Bone bone) {
float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation);
float ix = cos * bone.a + sin * bone.b;
float iy = cos * bone.c + sin * bone.d;
return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg;
}
public override Attachment Copy () {
return new PointAttachment(this);
}
}
}

View File

@@ -0,0 +1,225 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
/// <summary>Attachment that displays a texture region.</summary>
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[] 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; } }
public float Rotation { get { return rotation; } set { rotation = value; } }
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
public float ScaleY { get { return scaleY; } set { scaleY = value; } }
public float Width { get { return width; } set { width = value; } }
public float Height { get { return height; } set { height = value; } }
public float R { get { return r; } set { r = value; } }
public float G { get { return g; } set { g = value; } }
public float B { get { return b; } set { b = value; } }
public float A { get { return a; } set { a = value; } }
public string Path { get; set; }
public TextureRegion Region { get { return region; } set { region = value; } }
/// <summary>For each of the 4 vertices, a pair of <code>x,y</code> values that is the local position of the vertex.</summary>
/// <seealso cref="UpdateRegion"/>
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) {
}
/// <summary>Copy constructor.</summary>
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);
}
/// <summary>Calculates the <see cref="Offset"/> and <see cref="UVs"/> using the region and the attachment's transform. Must be called if the
/// region, the region's properties, or the transform are changed.</summary>
public void UpdateRegion () {
float[] uvs = this.uvs;
if (region == null) {
uvs[BLX] = 0;
uvs[BLY] = 0;
uvs[ULX] = 0;
uvs[ULY] = 1;
uvs[URX] = 1;
uvs[URY] = 1;
uvs[BRX] = 1;
uvs[BRY] = 0;
return;
}
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 = X;
float y = Y;
float localXCos = localX * cos + x;
float localXSin = localX * sin;
float localYCos = localY * cos + y;
float localYSin = localY * sin;
float localX2Cos = localX2 * cos + x;
float localX2Sin = localX2 * sin;
float localY2Cos = localY2 * cos + y;
float localY2Sin = localY2 * sin;
float[] offset = this.offset;
offset[BLX] = localXCos - localYSin;
offset[BLY] = localYCos + localXSin;
offset[ULX] = localXCos - localY2Sin;
offset[ULY] = localY2Cos + localXSin;
offset[URX] = localX2Cos - localY2Sin;
offset[URY] = localY2Cos + localX2Sin;
offset[BRX] = localX2Cos - localYSin;
offset[BRY] = localYCos + localX2Sin;
if (rotated) {
uvs[BLX] = region.u2;
uvs[BLY] = region.v;
uvs[ULX] = region.u2;
uvs[ULY] = region.v2;
uvs[URX] = region.u;
uvs[URY] = region.v2;
uvs[BRX] = region.u;
uvs[BRY] = region.v;
} else {
uvs[BLX] = region.u2;
uvs[BLY] = region.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;
}
}
/// <summary>
/// Transforms the attachment's four vertices to world coordinates. If the attachment has a <see cref="Sequence"/> the region may
/// be changed.</summary>
/// <param name="bone">The parent bone.</param>
/// <param name="worldVertices">The output world vertices. Must have a length greater than or equal to offset + 8.</param>
/// <param name="offset">The worldVertices index to begin writing values.</param>
/// <param name="stride">The number of worldVertices entries between the value pairs written.</param>
public void ComputeWorldVertices (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;
// Vertex order is different from RegionAttachment.java
offsetX = vertexOffset[BRX]; // 0
offsetY = vertexOffset[BRY]; // 1
worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl
worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
offset += stride;
offsetX = vertexOffset[BLX]; // 2
offsetY = vertexOffset[BLY]; // 3
worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul
worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
offset += stride;
offsetX = vertexOffset[ULX]; // 4
offsetY = vertexOffset[ULY]; // 5
worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur
worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
offset += stride;
offsetX = vertexOffset[URX]; // 6
offsetY = vertexOffset[URY]; // 7
worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br
worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
//offset += stride;
}
public override Attachment Copy () {
return new RegionAttachment(this);
}
}
}

View File

@@ -0,0 +1,95 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
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; } }
/// <summary>The index of the region to show for the setup pose.</summary>
public int SetupIndex { get { return setupIndex; } set { setupIndex = value; } }
public TextureRegion[] Regions { get { return regions; } }
/// <summary>Returns a unique ID for this attachment.</summary>
public int Id { get { return id; } }
public Sequence (int count) {
lock (Sequence.nextIdLock) {
id = Sequence.nextID++;
}
regions = new TextureRegion[count];
}
/// <summary>Copy constructor.</summary>
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
}
}

View File

@@ -0,0 +1,158 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
/// <summary>>An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's
/// <see cref="Slot.Deform"/>.</summary>
public abstract class VertexAttachment : Attachment {
static int nextID = 0;
static readonly Object nextIdLock = new Object();
internal readonly int id;
internal VertexAttachment timelineAttachment;
internal int[] bones;
internal float[] vertices;
internal int worldVerticesLength;
/// <summary>Gets a unique ID for this attachment.</summary>
public int Id { get { return id; } }
public int[] Bones { get { return bones; } set { bones = value; } }
public float[] Vertices { get { return vertices; } set { vertices = value; } }
public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } }
///<summary>Timelines for the timeline attachment are also applied to this attachment.
/// May be null if no attachment-specific timelines should be applied.</summary>
public VertexAttachment TimelineAttachment { get { return timelineAttachment; } set { timelineAttachment = value; } }
public VertexAttachment (string name)
: base(name) {
lock (VertexAttachment.nextIdLock) {
id = VertexAttachment.nextID++;
}
timelineAttachment = this;
}
/// <summary>Copy constructor.</summary>
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) {
ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0);
}
/// <summary>
/// Transforms the attachment's local <see cref="Vertices"/> to world coordinates. If the slot's <see cref="Slot.Deform"/> is
/// not empty, it is used to deform the vertices.
/// <para />
/// See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
/// Runtimes Guide.
/// </summary>
/// <param name="start">The index of the first <see cref="Vertices"/> value to transform. Each vertex has 2 values, x and y.</param>
/// <param name="count">The number of world vertex values to output. Must be less than or equal to <see cref="WorldVerticesLength"/> - start.</param>
/// <param name="worldVertices">The output world vertices. Must have a length greater than or equal to <paramref name="offset"/> + <paramref name="count"/>.</param>
/// <param name="offset">The <paramref name="worldVertices"/> index to begin writing values.</param>
/// <param name="stride">The number of <paramref name="worldVertices"/> entries between the value pairs written.</param>
public virtual void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) {
count = offset + (count >> 1) * stride;
ExposedList<float> deformArray = slot.deform;
float[] vertices = this.vertices;
int[] bones = this.bones;
if (bones == null) {
if (deformArray.Count > 0) vertices = deformArray.Items;
Bone bone = slot.bone;
float x = bone.worldX, y = bone.worldY;
float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
for (int vv = start, w = offset; w < count; vv += 2, w += stride) {
float vx = vertices[vv], vy = vertices[vv + 1];
worldVertices[w] = vx * a + vy * b + x;
worldVertices[w + 1] = vx * c + vy * d + y;
}
return;
}
int v = 0, skip = 0;
for (int i = 0; i < start; i += 2) {
int n = bones[v];
v += n + 1;
skip += n;
}
Bone[] skeletonBones = slot.bone.skeleton.bones.Items;
if (deformArray.Count == 0) {
for (int w = offset, b = skip * 3; w < count; w += stride) {
float wx = 0, wy = 0;
int n = bones[v++];
n += v;
for (; v < n; v++, b += 3) {
Bone bone = skeletonBones[bones[v]];
float vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2];
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
}
worldVertices[w] = wx;
worldVertices[w + 1] = wy;
}
} else {
float[] deform = deformArray.Items;
for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) {
float wx = 0, wy = 0;
int n = bones[v++];
n += v;
for (; v < n; v++, b += 3, f += 2) {
Bone bone = skeletonBones[bones[v]];
float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2];
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
}
worldVertices[w] = wx;
worldVertices[w + 1] = wy;
}
}
}
}
}

View File

@@ -0,0 +1,34 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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.
*****************************************************************************/
namespace SpineRuntime41 {
public enum BlendMode {
Normal, Additive, Multiply, Screen
}
}

View File

@@ -0,0 +1,393 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
/// <summary>
/// Stores a bone's current pose.
/// <para>
/// A bone has a local transform which is used to compute its world transform. A bone also has an applied transform, which is a
/// local transform that can be applied to compute the world transform. The local transform and applied transform may differ if a
/// constraint or application code modifies the world transform after it was computed from the local transform.
/// </para>
/// </summary>
public class Bone : IUpdatable {
static public bool yDown;
internal BoneData data;
internal Skeleton skeleton;
internal Bone parent;
internal ExposedList<Bone> children = new ExposedList<Bone>();
internal float x, y, rotation, scaleX, scaleY, shearX, shearY;
internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY;
internal float a, b, worldX;
internal float c, d, worldY;
internal bool sorted, active;
public BoneData Data { get { return data; } }
public Skeleton Skeleton { get { return skeleton; } }
public Bone Parent { get { return parent; } }
public ExposedList<Bone> Children { get { return children; } }
/// <summary>Returns false when the bone has not been computed because <see cref="BoneData.SkinRequired"/> is true and the
/// <see cref="Skeleton.Skin">active skin</see> does not <see cref="Skin.Bones">contain</see> this bone.</summary>
public bool Active { get { return active; } }
/// <summary>The local X translation.</summary>
public float X { get { return x; } set { x = value; } }
/// <summary>The local Y translation.</summary>
public float Y { get { return y; } set { y = value; } }
/// <summary>The local rotation.</summary>
public float Rotation { get { return rotation; } set { rotation = value; } }
/// <summary>The local scaleX.</summary>
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
/// <summary>The local scaleY.</summary>
public float ScaleY { get { return scaleY; } set { scaleY = value; } }
/// <summary>The local shearX.</summary>
public float ShearX { get { return shearX; } set { shearX = value; } }
/// <summary>The local shearY.</summary>
public float ShearY { get { return shearY; } set { shearY = value; } }
/// <summary>The rotation, as calculated by any constraints.</summary>
public float AppliedRotation { get { return arotation; } set { arotation = value; } }
/// <summary>The applied local x translation.</summary>
public float AX { get { return ax; } set { ax = value; } }
/// <summary>The applied local y translation.</summary>
public float AY { get { return ay; } set { ay = value; } }
/// <summary>The applied local scaleX.</summary>
public float AScaleX { get { return ascaleX; } set { ascaleX = value; } }
/// <summary>The applied local scaleY.</summary>
public float AScaleY { get { return ascaleY; } set { ascaleY = value; } }
/// <summary>The applied local shearX.</summary>
public float AShearX { get { return ashearX; } set { ashearX = value; } }
/// <summary>The applied local shearY.</summary>
public float AShearY { get { return ashearY; } set { ashearY = value; } }
/// <summary>Part of the world transform matrix for the X axis. If changed, <see cref="UpdateAppliedTransform()"/> should be called.</summary>
public float A { get { return a; } set { a = value; } }
/// <summary>Part of the world transform matrix for the Y axis. If changed, <see cref="UpdateAppliedTransform()"/> should be called.</summary>
public float B { get { return b; } set { b = value; } }
/// <summary>Part of the world transform matrix for the X axis. If changed, <see cref="UpdateAppliedTransform()"/> should be called.</summary>
public float C { get { return c; } set { c = value; } }
/// <summary>Part of the world transform matrix for the Y axis. If changed, <see cref="UpdateAppliedTransform()"/> should be called.</summary>
public float D { get { return d; } set { d = value; } }
/// <summary>The world X position. If changed, <see cref="UpdateAppliedTransform()"/> should be called.</summary>
public float WorldX { get { return worldX; } set { worldX = value; } }
/// <summary>The world Y position. If changed, <see cref="UpdateAppliedTransform()"/> should be called.</summary>
public float WorldY { get { return worldY; } set { worldY = value; } }
public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } }
public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } }
/// <summary>Returns the magnitide (always positive) of the world scale X.</summary>
public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } }
/// <summary>Returns the magnitide (always positive) of the world scale Y.</summary>
public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } }
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.");
this.data = data;
this.skeleton = skeleton;
this.parent = parent;
SetToSetupPose();
}
/// <summary>Copy constructor. Does not copy the <see cref="Children"/> bones.</summary>
/// <param name="parent">May be null.</param>
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;
}
/// <summary>Computes the world transform using the parent bone and this bone's local applied transform.</summary>
public void Update () {
UpdateWorldTransform(ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY);
}
/// <summary>Computes the world transform using the parent bone and this bone's local transform.</summary>
public void UpdateWorldTransform () {
UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY);
}
/// <summary>Computes the world transform using the parent bone and the specified local transform. The applied transform is set to the
/// specified local transform. Child bones are not updated.
/// <para>
/// See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
/// Runtimes Guide.</para></summary>
public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) {
ax = x;
ay = y;
arotation = rotation;
ascaleX = scaleX;
ascaleY = scaleY;
ashearX = shearX;
ashearY = shearY;
Bone parent = this.parent;
if (parent == null) { // Root bone.
float rotationY = rotation + 90 + shearY, sx = skeleton.ScaleX, sy = skeleton.ScaleY;
a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx;
b = MathUtils.CosDeg(rotationY) * scaleY * sx;
c = MathUtils.SinDeg(rotation + shearX) * scaleX * sy;
d = MathUtils.SinDeg(rotationY) * scaleY * sy;
worldX = x * sx + skeleton.x;
worldY = y * sy + skeleton.y;
return;
}
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
worldX = pa * x + pb * y + parent.worldX;
worldY = pc * x + pd * y + parent.worldY;
switch (data.transformMode) {
case TransformMode.Normal: {
float rotationY = rotation + 90 + shearY;
float la = MathUtils.CosDeg(rotation + shearX) * scaleX;
float lb = MathUtils.CosDeg(rotationY) * scaleY;
float lc = MathUtils.SinDeg(rotation + shearX) * scaleX;
float ld = MathUtils.SinDeg(rotationY) * scaleY;
a = pa * la + pb * lc;
b = pa * lb + pb * ld;
c = pc * la + pd * lc;
d = pc * lb + pd * ld;
return;
}
case TransformMode.OnlyTranslation: {
float rotationY = rotation + 90 + shearY;
a = MathUtils.CosDeg(rotation + shearX) * scaleX;
b = MathUtils.CosDeg(rotationY) * scaleY;
c = MathUtils.SinDeg(rotation + shearX) * scaleX;
d = MathUtils.SinDeg(rotationY) * scaleY;
break;
}
case TransformMode.NoRotationOrReflection: {
float s = pa * pa + pc * pc, prx;
if (s > 0.0001f) {
s = Math.Abs(pa * pd - pb * pc) / s;
pa /= skeleton.ScaleX;
pc /= skeleton.ScaleY;
pb = pc * s;
pd = pa * s;
prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg;
} else {
pa = 0;
pc = 0;
prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg;
}
float rx = rotation + shearX - prx;
float ry = rotation + shearY - prx + 90;
float la = MathUtils.CosDeg(rx) * scaleX;
float lb = MathUtils.CosDeg(ry) * scaleY;
float lc = MathUtils.SinDeg(rx) * scaleX;
float ld = MathUtils.SinDeg(ry) * scaleY;
a = pa * la - pb * lc;
b = pa * lb - pb * ld;
c = pc * la + pd * lc;
d = pc * lb + pd * ld;
break;
}
case TransformMode.NoScale:
case TransformMode.NoScaleOrReflection: {
float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation);
float za = (pa * cos + pb * sin) / skeleton.ScaleX;
float zc = (pc * cos + pd * sin) / skeleton.ScaleY;
float s = (float)Math.Sqrt(za * za + zc * zc);
if (s > 0.00001f) s = 1 / s;
za *= s;
zc *= s;
s = (float)Math.Sqrt(za * za + zc * zc);
if (data.transformMode == TransformMode.NoScale
&& (pa * pd - pb * pc < 0) != (skeleton.ScaleX < 0 != skeleton.ScaleY < 0)) s = -s;
float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za);
float zb = MathUtils.Cos(r) * s;
float zd = MathUtils.Sin(r) * s;
float la = MathUtils.CosDeg(shearX) * scaleX;
float lb = MathUtils.CosDeg(90 + shearY) * scaleY;
float lc = MathUtils.SinDeg(shearX) * scaleX;
float ld = MathUtils.SinDeg(90 + shearY) * scaleY;
a = za * la + zb * lc;
b = za * lb + zb * ld;
c = zc * la + zd * lc;
d = zc * lb + zd * ld;
break;
}
}
a *= skeleton.ScaleX;
b *= skeleton.ScaleX;
c *= skeleton.ScaleY;
d *= skeleton.ScaleY;
}
public void SetToSetupPose () {
BoneData data = this.data;
x = data.x;
y = data.y;
rotation = data.rotation;
scaleX = data.scaleX;
scaleY = data.scaleY;
shearX = data.shearX;
shearY = data.shearY;
}
/// <summary>
/// Computes the applied transform values from the world transform.
/// <para>
/// If the world transform is modified (by a constraint, <see cref="RotateWorld(float)"/>, etc) then this method should be called so
/// the applied transform matches the world transform. The applied transform may be needed by other code (eg to apply another
/// constraint).
/// </para><para>
/// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after
/// calling this method is equivalent to the local transform used to compute the world transform, but may not be identical.
/// </para></summary>
public void UpdateAppliedTransform () {
Bone parent = this.parent;
if (parent == null) {
ax = worldX - skeleton.x;
ay = worldY - skeleton.y;
arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg;
ascaleX = (float)Math.Sqrt(a * a + c * c);
ascaleY = (float)Math.Sqrt(b * b + d * d);
ashearX = 0;
ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg;
return;
}
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
float pid = 1 / (pa * pd - pb * pc);
float dx = worldX - parent.worldX, dy = worldY - parent.worldY;
ax = (dx * pd * pid - dy * pb * pid);
ay = (dy * pa * pid - dx * pc * pid);
float ia = pid * pd;
float id = pid * pa;
float ib = pid * pb;
float ic = pid * pc;
float ra = ia * a - ib * c;
float rb = ia * b - ib * d;
float rc = id * c - ic * a;
float rd = id * d - ic * b;
ashearX = 0;
ascaleX = (float)Math.Sqrt(ra * ra + rc * rc);
if (ascaleX > 0.0001f) {
float det = ra * rd - rb * rc;
ascaleY = det / ascaleX;
ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg;
arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg;
} else {
ascaleX = 0;
ascaleY = (float)Math.Sqrt(rb * rb + rd * rd);
ashearY = 0;
arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg;
}
}
public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) {
float a = this.a, b = this.b, c = this.c, d = this.d;
float det = a * d - b * c;
float x = worldX - this.worldX, y = worldY - this.worldY;
localX = (x * d - y * b) / det;
localY = (y * a - x * c) / det;
}
public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) {
worldX = localX * a + localY * b + this.worldX;
worldY = localX * c + localY * d + this.worldY;
}
public float WorldToLocalRotationX {
get {
Bone parent = this.parent;
if (parent == null) return arotation;
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c;
return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg;
}
}
public float WorldToLocalRotationY {
get {
Bone parent = this.parent;
if (parent == null) return arotation;
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d;
return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg;
}
}
public float WorldToLocalRotation (float worldRotation) {
float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation);
return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg + rotation - shearX;
}
public float LocalToWorldRotation (float localRotation) {
localRotation -= rotation - shearX;
float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation);
return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg;
}
/// <summary>
/// Rotates the world transform the specified amount.
/// <para>
/// After changes are made to the world transform, <see cref="UpdateAppliedTransform()"/> should be called and <see cref="Update()"/> will
/// need to be called on any child bones, recursively.
/// </para></summary>
public void RotateWorld (float degrees) {
float a = this.a, b = this.b, c = this.c, d = this.d;
float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees);
this.a = cos * a - sin * c;
this.b = cos * b - sin * d;
this.c = sin * a + cos * c;
this.d = sin * b + cos * d;
}
override public string ToString () {
return data.name;
}
}
}

View File

@@ -0,0 +1,105 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
public class BoneData {
internal int index;
internal string name;
internal BoneData parent;
internal float length;
internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY;
internal TransformMode transformMode = TransformMode.Normal;
internal bool skinRequired;
/// <summary>The index of the bone in Skeleton.Bones</summary>
public int Index { get { return index; } }
/// <summary>The name of the bone, which is unique across all bones in the skeleton.</summary>
public string Name { get { return name; } }
/// <summary>May be null.</summary>
public BoneData Parent { get { return parent; } }
public float Length { get { return length; } set { length = value; } }
/// <summary>Local X translation.</summary>
public float X { get { return x; } set { x = value; } }
/// <summary>Local Y translation.</summary>
public float Y { get { return y; } set { y = value; } }
/// <summary>Local rotation.</summary>
public float Rotation { get { return rotation; } set { rotation = value; } }
/// <summary>Local scaleX.</summary>
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
/// <summary>Local scaleY.</summary>
public float ScaleY { get { return scaleY; } set { scaleY = value; } }
/// <summary>Local shearX.</summary>
public float ShearX { get { return shearX; } set { shearX = value; } }
/// <summary>Local shearY.</summary>
public float ShearY { get { return shearY; } set { shearY = value; } }
/// <summary>The transform mode for how parent world transforms affect this bone.</summary>
public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } }
///<summary>When true, <see cref="Skeleton.UpdateWorldTransform()"/> only updates this bone if the <see cref="Skeleton.Skin"/> contains this
/// bone.</summary>
/// <seealso cref="Skin.Bones"/>
public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } }
/// <param name="parent">May be null.</param>
public BoneData (int index, string name, BoneData parent) {
if (index < 0) throw new ArgumentException("index must be >= 0", "index");
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.index = index;
this.name = name;
this.parent = parent;
}
override public string ToString () {
return name;
}
}
[Flags]
public enum TransformMode {
//0000 0 Flip Scale Rotation
Normal = 0, // 0000
OnlyTranslation = 7, // 0111
NoRotationOrReflection = 1, // 0001
NoScale = 2, // 0010
NoScaleOrReflection = 6, // 0110
}
}

View File

@@ -0,0 +1,61 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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.Collections.Generic;
namespace SpineRuntime41 {
/// <summary>The base class for all constraint datas.</summary>
public abstract class ConstraintData {
internal readonly string name;
internal int order;
internal bool skinRequired;
public ConstraintData (string name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.name = name;
}
/// <summary> The constraint's name, which is unique across all constraints in the skeleton of the same type.</summary>
public string Name { get { return name; } }
///<summary>The ordinal of this constraint for the order a skeleton's constraints will be applied by
/// <see cref="Skeleton.UpdateWorldTransform()"/>.</summary>
public int Order { get { return order; } set { order = value; } }
///<summary>When true, <see cref="Skeleton.UpdateWorldTransform()"/> only updates this constraint if the <see cref="Skeleton.Skin"/> contains
/// this constraint.</summary>
///<seealso cref="Skin.Constraints"/>
public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } }
override public string ToString () {
return name;
}
}
}

View File

@@ -0,0 +1,64 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
/// <summary>Stores the current pose values for an Event.</summary>
public class Event {
internal readonly EventData data;
internal readonly float time;
internal int intValue;
internal float floatValue;
internal string stringValue;
internal float volume;
internal float balance;
public EventData Data { get { return data; } }
/// <summary>The animation time this event was keyed.</summary>
public float Time { get { return time; } }
public int Int { get { return intValue; } set { intValue = value; } }
public float Float { get { return floatValue; } set { floatValue = value; } }
public string String { get { return stringValue; } set { stringValue = value; } }
public float Volume { get { return volume; } set { volume = value; } }
public float Balance { get { return balance; } set { balance = value; } }
public Event (float time, EventData data) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
this.time = time;
this.data = data;
}
override public string ToString () {
return this.data.Name;
}
}
}

View File

@@ -0,0 +1,56 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
/// <summary>Stores the setup pose values for an Event.</summary>
public class EventData {
internal string name;
/// <summary>The name of the event, which is unique across all events in the skeleton.</summary>
public string Name { get { return name; } }
public int Int { get; set; }
public float Float { get; set; }
public string @String { get; set; }
public string AudioPath { get; set; }
public float Volume { get; set; }
public float Balance { get; set; }
public EventData (string name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.name = name;
}
override public string ToString () {
return Name;
}
}
}

View File

@@ -0,0 +1,637 @@
//
// System.Collections.Generic.List
//
// Authors:
// Ben Maurer (bmaurer@ximian.com)
// Martin Baulig (martin@ximian.com)
// Carlos Alberto Cortez (calberto.cortez@gmail.com)
// David Waite (mass@akuma.org)
//
// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
// Copyright (C) 2005 David Waite
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
namespace SpineRuntime41 {
[DebuggerDisplay("Count={Count}")]
public class ExposedList<T> : IEnumerable<T> {
public T[] Items;
public int Count;
private const int DefaultCapacity = 4;
private static readonly T[] EmptyArray = new T[0];
private int version;
public ExposedList () {
Items = EmptyArray;
}
public ExposedList (IEnumerable<T> collection) {
CheckCollection(collection);
// initialize to needed size (if determinable)
ICollection<T> c = collection as ICollection<T>;
if (c == null) {
Items = EmptyArray;
AddEnumerable(collection);
} else {
Items = new T[c.Count];
AddCollection(c);
}
}
public ExposedList (int capacity) {
if (capacity < 0)
throw new ArgumentOutOfRangeException("capacity");
Items = new T[capacity];
}
internal ExposedList (T[] data, int size) {
Items = data;
Count = size;
}
public void Add (T item) {
// If we check to see if we need to grow before trying to grow
// we can speed things up by 25%
if (Count == Items.Length)
GrowIfNeeded(1);
Items[Count++] = item;
version++;
}
public void GrowIfNeeded (int addedCount) {
int minimumSize = Count + addedCount;
if (minimumSize > Items.Length)
Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize);
}
public ExposedList<T> Resize (int newSize) {
int itemsLength = Items.Length;
T[] oldItems = Items;
if (newSize > itemsLength) {
Array.Resize(ref Items, newSize);
} else if (newSize < itemsLength) {
// Allow nulling of T reference type to allow GC.
for (int i = newSize; i < itemsLength; i++)
oldItems[i] = default(T);
}
Count = newSize;
return this;
}
public void EnsureCapacity (int min) {
if (Items.Length < min) {
int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2;
//if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
if (newCapacity < min) newCapacity = min;
Capacity = newCapacity;
}
}
private void CheckRange (int index, int count) {
if (index < 0)
throw new ArgumentOutOfRangeException("index");
if (count < 0)
throw new ArgumentOutOfRangeException("count");
if ((uint)index + (uint)count > (uint)Count)
throw new ArgumentException("index and count exceed length of list");
}
private void AddCollection (ICollection<T> collection) {
int collectionCount = collection.Count;
if (collectionCount == 0)
return;
GrowIfNeeded(collectionCount);
collection.CopyTo(Items, Count);
Count += collectionCount;
}
private void AddEnumerable (IEnumerable<T> enumerable) {
foreach (T t in enumerable) {
Add(t);
}
}
// Additional overload provided because ExposedList<T> only implements IEnumerable<T>,
// leading to sub-optimal behavior: It grows multiple times as it assumes not
// to know the final size ahead of insertion.
public void AddRange (ExposedList<T> list) {
CheckCollection(list);
int collectionCount = list.Count;
if (collectionCount == 0)
return;
GrowIfNeeded(collectionCount);
list.CopyTo(Items, Count);
Count += collectionCount;
version++;
}
public void AddRange (IEnumerable<T> collection) {
CheckCollection(collection);
ICollection<T> c = collection as ICollection<T>;
if (c != null)
AddCollection(c);
else
AddEnumerable(collection);
version++;
}
public int BinarySearch (T item) {
return Array.BinarySearch<T>(Items, 0, Count, item);
}
public int BinarySearch (T item, IComparer<T> comparer) {
return Array.BinarySearch<T>(Items, 0, Count, item, comparer);
}
public int BinarySearch (int index, int count, T item, IComparer<T> comparer) {
CheckRange(index, count);
return Array.BinarySearch<T>(Items, index, count, item, comparer);
}
public void Clear (bool clearArray = true) {
if (clearArray)
Array.Clear(Items, 0, Items.Length);
Count = 0;
version++;
}
public bool Contains (T item) {
return Array.IndexOf<T>(Items, item, 0, Count) != -1;
}
public ExposedList<TOutput> ConvertAll<TOutput> (Converter<T, TOutput> converter) {
if (converter == null)
throw new ArgumentNullException("converter");
ExposedList<TOutput> u = new ExposedList<TOutput>(Count);
u.Count = Count;
T[] items = Items;
TOutput[] uItems = u.Items;
for (int i = 0; i < Count; i++)
uItems[i] = converter(items[i]);
return u;
}
public void CopyTo (T[] array) {
Array.Copy(Items, 0, array, 0, Count);
}
public void CopyTo (T[] array, int arrayIndex) {
Array.Copy(Items, 0, array, arrayIndex, Count);
}
public void CopyTo (int index, T[] array, int arrayIndex, int count) {
CheckRange(index, count);
Array.Copy(Items, index, array, arrayIndex, count);
}
public bool Exists (Predicate<T> match) {
CheckMatch(match);
return GetIndex(0, Count, match) != -1;
}
public T Find (Predicate<T> match) {
CheckMatch(match);
int i = GetIndex(0, Count, match);
return (i != -1) ? Items[i] : default(T);
}
private static void CheckMatch (Predicate<T> match) {
if (match == null)
throw new ArgumentNullException("match");
}
public ExposedList<T> FindAll (Predicate<T> match) {
CheckMatch(match);
return FindAllList(match);
}
private ExposedList<T> FindAllList (Predicate<T> match) {
ExposedList<T> results = new ExposedList<T>();
for (int i = 0; i < Count; i++)
if (match(Items[i]))
results.Add(Items[i]);
return results;
}
public int FindIndex (Predicate<T> match) {
CheckMatch(match);
return GetIndex(0, Count, match);
}
public int FindIndex (int startIndex, Predicate<T> match) {
CheckMatch(match);
CheckIndex(startIndex);
return GetIndex(startIndex, Count - startIndex, match);
}
public int FindIndex (int startIndex, int count, Predicate<T> match) {
CheckMatch(match);
CheckRange(startIndex, count);
return GetIndex(startIndex, count, match);
}
private int GetIndex (int startIndex, int count, Predicate<T> match) {
int end = startIndex + count;
for (int i = startIndex; i < end; i++)
if (match(Items[i]))
return i;
return -1;
}
public T FindLast (Predicate<T> match) {
CheckMatch(match);
int i = GetLastIndex(0, Count, match);
return i == -1 ? default(T) : Items[i];
}
public int FindLastIndex (Predicate<T> match) {
CheckMatch(match);
return GetLastIndex(0, Count, match);
}
public int FindLastIndex (int startIndex, Predicate<T> match) {
CheckMatch(match);
CheckIndex(startIndex);
return GetLastIndex(0, startIndex + 1, match);
}
public int FindLastIndex (int startIndex, int count, Predicate<T> match) {
CheckMatch(match);
int start = startIndex - count + 1;
CheckRange(start, count);
return GetLastIndex(start, count, match);
}
private int GetLastIndex (int startIndex, int count, Predicate<T> match) {
// unlike FindLastIndex, takes regular params for search range
for (int i = startIndex + count; i != startIndex;)
if (match(Items[--i]))
return i;
return -1;
}
public void ForEach (Action<T> action) {
if (action == null)
throw new ArgumentNullException("action");
for (int i = 0; i < Count; i++)
action(Items[i]);
}
public Enumerator GetEnumerator () {
return new Enumerator(this);
}
public ExposedList<T> GetRange (int index, int count) {
CheckRange(index, count);
T[] tmpArray = new T[count];
Array.Copy(Items, index, tmpArray, 0, count);
return new ExposedList<T>(tmpArray, count);
}
public int IndexOf (T item) {
return Array.IndexOf<T>(Items, item, 0, Count);
}
public int IndexOf (T item, int index) {
CheckIndex(index);
return Array.IndexOf<T>(Items, item, index, Count - index);
}
public int IndexOf (T item, int index, int count) {
if (index < 0)
throw new ArgumentOutOfRangeException("index");
if (count < 0)
throw new ArgumentOutOfRangeException("count");
if ((uint)index + (uint)count > (uint)Count)
throw new ArgumentOutOfRangeException("index and count exceed length of list");
return Array.IndexOf<T>(Items, item, index, count);
}
private void Shift (int start, int delta) {
if (delta < 0)
start -= delta;
if (start < Count)
Array.Copy(Items, start, Items, start + delta, Count - start);
Count += delta;
if (delta < 0)
Array.Clear(Items, Count, -delta);
}
private void CheckIndex (int index) {
if (index < 0 || (uint)index > (uint)Count)
throw new ArgumentOutOfRangeException("index");
}
public void Insert (int index, T item) {
CheckIndex(index);
if (Count == Items.Length)
GrowIfNeeded(1);
Shift(index, 1);
Items[index] = item;
version++;
}
private void CheckCollection (IEnumerable<T> collection) {
if (collection == null)
throw new ArgumentNullException("collection");
}
public void InsertRange (int index, IEnumerable<T> collection) {
CheckCollection(collection);
CheckIndex(index);
if (collection == this) {
T[] buffer = new T[Count];
CopyTo(buffer, 0);
GrowIfNeeded(Count);
Shift(index, buffer.Length);
Array.Copy(buffer, 0, Items, index, buffer.Length);
} else {
ICollection<T> c = collection as ICollection<T>;
if (c != null)
InsertCollection(index, c);
else
InsertEnumeration(index, collection);
}
version++;
}
private void InsertCollection (int index, ICollection<T> collection) {
int collectionCount = collection.Count;
GrowIfNeeded(collectionCount);
Shift(index, collectionCount);
collection.CopyTo(Items, index);
}
private void InsertEnumeration (int index, IEnumerable<T> enumerable) {
foreach (T t in enumerable)
Insert(index++, t);
}
public int LastIndexOf (T item) {
return Array.LastIndexOf<T>(Items, item, Count - 1, Count);
}
public int LastIndexOf (T item, int index) {
CheckIndex(index);
return Array.LastIndexOf<T>(Items, item, index, index + 1);
}
public int LastIndexOf (T item, int index, int count) {
if (index < 0)
throw new ArgumentOutOfRangeException("index", index, "index is negative");
if (count < 0)
throw new ArgumentOutOfRangeException("count", count, "count is negative");
if (index - count + 1 < 0)
throw new ArgumentOutOfRangeException("count", count, "count is too large");
return Array.LastIndexOf<T>(Items, item, index, count);
}
public bool Remove (T item) {
int loc = IndexOf(item);
if (loc != -1)
RemoveAt(loc);
return loc != -1;
}
public int RemoveAll (Predicate<T> match) {
CheckMatch(match);
int i = 0;
int j = 0;
// Find the first item to remove
for (i = 0; i < Count; i++)
if (match(Items[i]))
break;
if (i == Count)
return 0;
version++;
// Remove any additional items
for (j = i + 1; j < Count; j++) {
if (!match(Items[j]))
Items[i++] = Items[j];
}
if (j - i > 0)
Array.Clear(Items, i, j - i);
Count = i;
return (j - i);
}
public void RemoveAt (int index) {
if (index < 0 || (uint)index >= (uint)Count)
throw new ArgumentOutOfRangeException("index");
Shift(index, -1);
Array.Clear(Items, Count, 1);
version++;
}
// Spine Added Method
// Based on Stack<T>.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs
/// <summary>Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException.</summary>
public T Pop () {
if (Count == 0)
throw new InvalidOperationException("List is empty. Nothing to pop.");
int i = Count - 1;
T item = Items[i];
Items[i] = default(T);
Count--;
version++;
return item;
}
public void RemoveRange (int index, int count) {
CheckRange(index, count);
if (count > 0) {
Shift(index, -count);
Array.Clear(Items, Count, count);
version++;
}
}
public void Reverse () {
Array.Reverse(Items, 0, Count);
version++;
}
public void Reverse (int index, int count) {
CheckRange(index, count);
Array.Reverse(Items, index, count);
version++;
}
public void Sort () {
Array.Sort<T>(Items, 0, Count, Comparer<T>.Default);
version++;
}
public void Sort (IComparer<T> comparer) {
Array.Sort<T>(Items, 0, Count, comparer);
version++;
}
public void Sort (Comparison<T> comparison) {
Array.Sort<T>(Items, comparison);
version++;
}
public void Sort (int index, int count, IComparer<T> comparer) {
CheckRange(index, count);
Array.Sort<T>(Items, index, count, comparer);
version++;
}
public T[] ToArray () {
T[] t = new T[Count];
Array.Copy(Items, t, Count);
return t;
}
public void TrimExcess () {
Capacity = Count;
}
public bool TrueForAll (Predicate<T> match) {
CheckMatch(match);
for (int i = 0; i < Count; i++)
if (!match(Items[i]))
return false;
return true;
}
public int Capacity {
get {
return Items.Length;
}
set {
if ((uint)value < (uint)Count)
throw new ArgumentOutOfRangeException();
Array.Resize(ref Items, value);
}
}
#region Interface implementations.
IEnumerator<T> IEnumerable<T>.GetEnumerator () {
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator () {
return GetEnumerator();
}
#endregion
public struct Enumerator : IEnumerator<T>, IDisposable {
private ExposedList<T> l;
private int next;
private int ver;
private T current;
internal Enumerator (ExposedList<T> l)
: this() {
this.l = l;
ver = l.version;
}
public void Dispose () {
l = null;
}
private void VerifyState () {
if (l == null)
throw new ObjectDisposedException(GetType().FullName);
if (ver != l.version)
throw new InvalidOperationException(
"Collection was modified; enumeration operation may not execute.");
}
public bool MoveNext () {
VerifyState();
if (next < 0)
return false;
if (next < l.Count) {
current = l.Items[next++];
return true;
}
next = -1;
return false;
}
public T Current {
get {
return current;
}
}
void IEnumerator.Reset () {
VerifyState();
next = 0;
}
object IEnumerator.Current {
get {
VerifyState();
if (next <= 0)
throw new InvalidOperationException();
return current;
}
}
}
}
}

View File

@@ -0,0 +1,42 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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.
*****************************************************************************/
namespace SpineRuntime41 {
///<summary>The interface for items updated by <see cref="Skeleton.UpdateWorldTransform()"/>.</summary>
public interface IUpdatable {
void Update ();
///<summary>Returns false when this item has not been updated because a skin is required and the <see cref="Skeleton.Skin">active
/// skin</see> does not contain this item.</summary>
/// <seealso cref="Skin.Bones"/>
/// <seealso cref="Skin.Constraints"/>
bool Active { get; }
}
}

View File

@@ -0,0 +1,374 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
/// <summary>
/// <para>
/// Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of
/// the last bone is as close to the target bone as possible.</para>
/// <para>
/// See <a href="http://esotericsoftware.com/spine-ik-constraints">IK constraints</a> in the Spine User Guide.</para>
/// </summary>
public class IkConstraint : IUpdatable {
internal readonly IkConstraintData data;
internal readonly ExposedList<Bone> bones = new ExposedList<Bone>();
internal Bone target;
internal int bendDirection;
internal bool compress, stretch;
internal float mix = 1, softness;
internal bool active;
public IkConstraint (IkConstraintData 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;
softness = data.softness;
bendDirection = data.bendDirection;
compress = data.compress;
stretch = data.stretch;
bones = new ExposedList<Bone>(data.bones.Count);
foreach (BoneData boneData in data.bones)
bones.Add(skeleton.bones.Items[boneData.index]);
target = skeleton.bones.Items[data.target.index];
}
/// <summary>Copy constructor.</summary>
public IkConstraint (IkConstraint constraint, Skeleton skeleton) {
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<Bone>(constraint.Bones.Count);
foreach (Bone bone in constraint.Bones)
bones.Add(skeleton.Bones.Items[bone.data.index]);
target = skeleton.Bones.Items[constraint.target.data.index];
mix = constraint.mix;
softness = constraint.softness;
bendDirection = constraint.bendDirection;
compress = constraint.compress;
stretch = constraint.stretch;
}
public void Update () {
if (mix == 0) return;
Bone target = this.target;
Bone[] bones = this.bones.Items;
switch (this.bones.Count) {
case 1:
Apply(bones[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix);
break;
case 2:
Apply(bones[0], bones[1], target.worldX, target.worldY, bendDirection, stretch, data.uniform, softness, mix);
break;
}
}
/// <summary>The bones that will be modified by this IK constraint.</summary>
public ExposedList<Bone> Bones {
get { return bones; }
}
/// <summary>The bone that is the IK target.</summary>
public Bone Target {
get { return target; }
set { target = value; }
}
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained rotation.
/// <para>
/// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0.
/// </para></summary>
public float Mix {
get { return mix; }
set { mix = value; }
}
/// <summary>For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones
/// will not straighten completely until the target is this far out of range.</summary>
public float Softness {
get { return softness; }
set { softness = value; }
}
/// <summary>For two bone IK, controls the bend direction of the IK bones, either 1 or -1.</summary>
public int BendDirection {
get { return bendDirection; }
set { bendDirection = value; }
}
/// <summary>For one bone IK, when true and the target is too close, the bone is scaled to reach it.</summary>
public bool Compress {
get { return compress; }
set { compress = value; }
}
/// <summary>When true and the target is out of range, the parent bone is scaled to reach it.
/// <para>
/// For two bone IK: 1) the child bone's local Y translation is set to 0,
/// 2) stretch is not applied if <see cref="Softness"/> is > 0,
/// and 3) if the parent bone has local nonuniform scale, stretch is not applied.
/// </para></summary>
public bool Stretch {
get { return stretch; }
set { stretch = value; }
}
public bool Active {
get { return active; }
}
/// <summary>The IK constraint's setup pose data.</summary>
public IkConstraintData Data {
get { return data; }
}
override public string ToString () {
return data.name;
}
/// <summary>Applies 1 bone IK. The target is specified in the world coordinate system.</summary>
static public void Apply (Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform,
float alpha) {
if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
Bone p = bone.parent;
float pa = p.a, pb = p.b, pc = p.c, pd = p.d;
float rotationIK = -bone.ashearX - bone.arotation;
float tx = 0, ty = 0;
switch (bone.data.transformMode) {
case TransformMode.OnlyTranslation:
tx = (targetX - bone.worldX) * Math.Sign(bone.skeleton.ScaleX);
ty = (targetY - bone.worldY) * Math.Sign(bone.skeleton.ScaleY);
break;
case TransformMode.NoRotationOrReflection: {
float s = Math.Abs(pa * pd - pb * pc) / Math.Max(0.0001f, pa * pa + pc * pc);
float sa = pa / bone.skeleton.ScaleX;
float sc = pc / bone.skeleton.ScaleY;
pb = -sc * s * bone.skeleton.ScaleX;
pd = sa * s * bone.skeleton.ScaleY;
rotationIK += (float)Math.Atan2(sc, sa) * MathUtils.RadDeg;
goto default; // Fall through.
}
default: {
float x = targetX - p.worldX, y = targetY - p.worldY;
float d = pa * pd - pb * pc;
if (Math.Abs(d) <= 0.0001f) {
tx = 0;
ty = 0;
} else {
tx = (x * pd - y * pb) / d - bone.ax;
ty = (y * pa - x * pc) / d - bone.ay;
}
break;
}
}
rotationIK += (float)Math.Atan2(ty, tx) * MathUtils.RadDeg;
if (bone.ascaleX < 0) rotationIK += 180;
if (rotationIK > 180)
rotationIK -= 360;
else if (rotationIK < -180) //
rotationIK += 360;
float sx = bone.ascaleX, sy = bone.ascaleY;
if (compress || stretch) {
switch (bone.data.transformMode) {
case TransformMode.NoScale:
case TransformMode.NoScaleOrReflection:
tx = targetX - bone.worldX;
ty = targetY - bone.worldY;
break;
}
float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty);
if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) {
float s = (dd / b - 1) * alpha + 1;
sx *= s;
if (uniform) sy *= s;
}
}
bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX, bone.ashearY);
}
/// <summary>Applies 2 bone IK. The target is specified in the world coordinate system.</summary>
/// <param name="child">A direct descendant of the parent bone.</param>
static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, bool uniform,
float softness, float alpha) {
if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null.");
if (child == null) throw new ArgumentNullException("child", "child cannot be null.");
float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, sx = psx, sy = psy, csx = child.ascaleX;
int os1, os2, s2;
if (psx < 0) {
psx = -psx;
os1 = 180;
s2 = -1;
} else {
os1 = 0;
s2 = 1;
}
if (psy < 0) {
psy = -psy;
s2 = -s2;
}
if (csx < 0) {
csx = -csx;
os2 = 180;
} else
os2 = 0;
float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d;
bool u = Math.Abs(psx - psy) <= 0.0001f;
if (!u || stretch) {
cy = 0;
cwx = a * cx + parent.worldX;
cwy = c * cx + parent.worldY;
} else {
cy = child.ay;
cwx = a * cx + b * cy + parent.worldX;
cwy = c * cx + d * cy + parent.worldY;
}
Bone pp = parent.parent;
a = pp.a;
b = pp.b;
c = pp.c;
d = pp.d;
float id = a * d - b * c, x = cwx - pp.worldX, y = cwy - pp.worldY;
id = Math.Abs(id) <= 0.0001f ? 0 : 1 / id;
float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py;
float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2;
if (l1 < 0.0001f) {
Apply(parent, targetX, targetY, false, stretch, false, alpha);
child.UpdateWorldTransform(cx, cy, 0, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
return;
}
x = targetX - pp.worldX;
y = targetY - pp.worldY;
float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py;
float dd = tx * tx + ty * ty;
if (softness != 0) {
softness *= psx * (csx + 1) * 0.5f;
float td = (float)Math.Sqrt(dd), sd = td - l1 - l2 * psx + softness;
if (sd > 0) {
float p = Math.Min(1, sd / (softness * 2)) - 1;
p = (sd - softness * (1 - p * p)) / td;
tx -= p * tx;
ty -= p * ty;
dd = tx * tx + ty * ty;
}
}
if (u) {
l2 *= psx;
float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2);
if (cos < -1) {
cos = -1;
a2 = MathUtils.PI * bendDir;
} else if (cos > 1) {
cos = 1;
a2 = 0;
if (stretch) {
a = ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1;
sx *= a;
if (uniform) sy *= a;
}
} else
a2 = (float)Math.Acos(cos) * bendDir;
a = l1 + l2 * cos;
b = l2 * (float)Math.Sin(a2);
a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b);
} else {
a = psx * l2;
b = psy * l2;
float aa = a * a, bb = b * b, ta = (float)Math.Atan2(ty, tx);
c = bb * l1 * l1 + aa * dd - aa * bb;
float c1 = -2 * bb * l1, c2 = bb - aa;
d = c1 * c1 - 4 * c2 * c;
if (d >= 0) {
float q = (float)Math.Sqrt(d);
if (c1 < 0) q = -q;
q = -(c1 + q) * 0.5f;
float r0 = q / c2, r1 = c / q;
float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1;
if (r * r <= dd) {
y = (float)Math.Sqrt(dd - r * r) * bendDir;
a1 = ta - (float)Math.Atan2(y, r);
a2 = (float)Math.Atan2(y / psy, (r - l1) / psx);
goto break_outer; // break outer;
}
}
float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0;
float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0;
c = -a * l1 / (aa - bb);
if (c >= -1 && c <= 1) {
c = (float)Math.Acos(c);
x = a * (float)Math.Cos(c) + l1;
y = b * (float)Math.Sin(c);
d = x * x + y * y;
if (d < minDist) {
minAngle = c;
minDist = d;
minX = x;
minY = y;
}
if (d > maxDist) {
maxAngle = c;
maxDist = d;
maxX = x;
maxY = y;
}
}
if (dd <= (minDist + maxDist) * 0.5f) {
a1 = ta - (float)Math.Atan2(minY * bendDir, minX);
a2 = minAngle * bendDir;
} else {
a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX);
a2 = maxAngle * bendDir;
}
}
break_outer:
float os = (float)Math.Atan2(cy, cx) * s2;
float rotation = parent.arotation;
a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation;
if (a1 > 180)
a1 -= 360;
else if (a1 < -180)
a1 += 360;
parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, sx, sy, 0, 0);
rotation = child.arotation;
a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation;
if (a2 > 180)
a2 -= 360;
else if (a2 < -180)
a2 += 360;
child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
}
}
}

View File

@@ -0,0 +1,103 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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.Collections.Generic;
namespace SpineRuntime41 {
/// <summary>Stores the setup pose for an IkConstraint.</summary>
public class IkConstraintData : ConstraintData {
internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
internal BoneData target;
internal int bendDirection = 1;
internal bool compress, stretch, uniform;
internal float mix = 1, softness;
public IkConstraintData (string name) : base(name) {
}
/// <summary>The bones that are constrained by this IK Constraint.</summary>
public ExposedList<BoneData> Bones {
get { return bones; }
}
/// <summary>The bone that is the IK target.</summary>
public BoneData Target {
get { return target; }
set { target = value; }
}
/// <summary>
/// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation.
/// <para>
/// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0.
/// </para></summary>
public float Mix {
get { return mix; }
set { mix = value; }
}
/// <summary>For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones
/// will not straighten completely until the target is this far out of range.</summary>
public float Softness {
get { return softness; }
set { softness = value; }
}
/// <summary>For two bone IK, controls the bend direction of the IK bones, either 1 or -1.</summary>
public int BendDirection {
get { return bendDirection; }
set { bendDirection = value; }
}
/// <summary>For one bone IK, when true and the target is too close, the bone is scaled to reach it.</summary>
public bool Compress {
get { return compress; }
set { compress = value; }
}
/// <summary>When true and the target is out of range, the parent bone is scaled to reach it.
/// <para>
/// For two bone IK: 1) the child bone's local Y translation is set to 0,
/// 2) stretch is not applied if <see cref="Softness"/> is > 0,
/// and 3) if the parent bone has local nonuniform scale, stretch is not applied.</para></summary>
public bool Stretch {
get { return stretch; }
set { stretch = value; }
}
/// <summary>
/// When true and <see cref="Compress"/> or <see cref="Stretch"/> is used, the bone is scaled on both the X and Y axes.
/// </summary>
public bool Uniform {
get { return uniform; }
set { uniform = value; }
}
}
}

View File

@@ -0,0 +1,517 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
namespace SpineRuntime41 {
public static class Json {
public static object Deserialize (TextReader text) {
SharpJson.JsonDecoder parser = new SharpJson.JsonDecoder();
parser.parseNumbersAsFloat = true;
return parser.Decode(text.ReadToEnd());
}
}
}
/**
* Copyright (c) 2016 Adriano Tinoco d'Oliveira Rezende
*
* Based on the JSON parser by Patrick van Bergen
* http://techblog.procurios.nl/k/news/view/14605/14863/how-do-i-write-my-own-parser-(for-json).html
*
* Changes made:
*
* - Optimized parser speed (deserialize roughly near 3x faster than original)
* - Added support to handle lexer/parser error messages with line numbers
* - Added more fine grained control over type conversions during the parsing
* - Refactory API (Separate Lexer code from Parser code and the Encoder from Decoder)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace SharpJson {
class Lexer {
public enum Token {
None,
Null,
True,
False,
Colon,
Comma,
String,
Number,
CurlyOpen,
CurlyClose,
SquaredOpen,
SquaredClose,
};
public bool hasError {
get {
return !success;
}
}
public int lineNumber {
get;
private set;
}
public bool parseNumbersAsFloat {
get;
set;
}
char[] json;
int index = 0;
bool success = true;
char[] stringBuffer = new char[4096];
public Lexer (string text) {
Reset();
json = text.ToCharArray();
parseNumbersAsFloat = false;
}
public void Reset () {
index = 0;
lineNumber = 1;
success = true;
}
public string ParseString () {
int idx = 0;
StringBuilder builder = null;
SkipWhiteSpaces();
// "
char c = json[index++];
bool failed = false;
bool complete = false;
while (!complete && !failed) {
if (index == json.Length)
break;
c = json[index++];
if (c == '"') {
complete = true;
break;
} else if (c == '\\') {
if (index == json.Length)
break;
c = json[index++];
switch (c) {
case '"':
stringBuffer[idx++] = '"';
break;
case '\\':
stringBuffer[idx++] = '\\';
break;
case '/':
stringBuffer[idx++] = '/';
break;
case 'b':
stringBuffer[idx++] = '\b';
break;
case 'f':
stringBuffer[idx++] = '\f';
break;
case 'n':
stringBuffer[idx++] = '\n';
break;
case 'r':
stringBuffer[idx++] = '\r';
break;
case 't':
stringBuffer[idx++] = '\t';
break;
case 'u':
int remainingLength = json.Length - index;
if (remainingLength >= 4) {
string hex = new string(json, index, 4);
// XXX: handle UTF
stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16);
// skip 4 chars
index += 4;
} else {
failed = true;
}
break;
}
} else {
stringBuffer[idx++] = c;
}
if (idx >= stringBuffer.Length) {
if (builder == null)
builder = new StringBuilder();
builder.Append(stringBuffer, 0, idx);
idx = 0;
}
}
if (!complete) {
success = false;
return null;
}
if (builder != null)
return builder.ToString();
else
return new string(stringBuffer, 0, idx);
}
string GetNumberString () {
SkipWhiteSpaces();
int lastIndex = GetLastIndexOfNumber(index);
int charLength = (lastIndex - index) + 1;
string result = new string(json, index, charLength);
index = lastIndex + 1;
return result;
}
public float ParseFloatNumber () {
float number;
string str = GetNumberString();
if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number))
return 0;
return number;
}
public double ParseDoubleNumber () {
double number;
string str = GetNumberString();
if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number))
return 0;
return number;
}
int GetLastIndexOfNumber (int index) {
int lastIndex;
for (lastIndex = index; lastIndex < json.Length; lastIndex++) {
char ch = json[lastIndex];
if ((ch < '0' || ch > '9') && ch != '+' && ch != '-'
&& ch != '.' && ch != 'e' && ch != 'E')
break;
}
return lastIndex - 1;
}
void SkipWhiteSpaces () {
for (; index < json.Length; index++) {
char ch = json[index];
if (ch == '\n')
lineNumber++;
if (!char.IsWhiteSpace(json[index]))
break;
}
}
public Token LookAhead () {
SkipWhiteSpaces();
int savedIndex = index;
return NextToken(json, ref savedIndex);
}
public Token NextToken () {
SkipWhiteSpaces();
return NextToken(json, ref index);
}
static Token NextToken (char[] json, ref int index) {
if (index == json.Length)
return Token.None;
char c = json[index++];
switch (c) {
case '{':
return Token.CurlyOpen;
case '}':
return Token.CurlyClose;
case '[':
return Token.SquaredOpen;
case ']':
return Token.SquaredClose;
case ',':
return Token.Comma;
case '"':
return Token.String;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
return Token.Number;
case ':':
return Token.Colon;
}
index--;
int remainingLength = json.Length - index;
// false
if (remainingLength >= 5) {
if (json[index] == 'f' &&
json[index + 1] == 'a' &&
json[index + 2] == 'l' &&
json[index + 3] == 's' &&
json[index + 4] == 'e') {
index += 5;
return Token.False;
}
}
// true
if (remainingLength >= 4) {
if (json[index] == 't' &&
json[index + 1] == 'r' &&
json[index + 2] == 'u' &&
json[index + 3] == 'e') {
index += 4;
return Token.True;
}
}
// null
if (remainingLength >= 4) {
if (json[index] == 'n' &&
json[index + 1] == 'u' &&
json[index + 2] == 'l' &&
json[index + 3] == 'l') {
index += 4;
return Token.Null;
}
}
return Token.None;
}
}
public class JsonDecoder {
public string errorMessage {
get;
private set;
}
public bool parseNumbersAsFloat {
get;
set;
}
Lexer lexer;
public JsonDecoder () {
errorMessage = null;
parseNumbersAsFloat = false;
}
public object Decode (string text) {
errorMessage = null;
lexer = new Lexer(text);
lexer.parseNumbersAsFloat = parseNumbersAsFloat;
return ParseValue();
}
public static object DecodeText (string text) {
JsonDecoder builder = new JsonDecoder();
return builder.Decode(text);
}
IDictionary<string, object> ParseObject () {
Dictionary<string, object> table = new Dictionary<string, object>();
// {
lexer.NextToken();
while (true) {
Lexer.Token token = lexer.LookAhead();
switch (token) {
case Lexer.Token.None:
TriggerError("Invalid token");
return null;
case Lexer.Token.Comma:
lexer.NextToken();
break;
case Lexer.Token.CurlyClose:
lexer.NextToken();
return table;
default:
// name
string name = EvalLexer(lexer.ParseString());
if (errorMessage != null)
return null;
// :
token = lexer.NextToken();
if (token != Lexer.Token.Colon) {
TriggerError("Invalid token; expected ':'");
return null;
}
// value
object value = ParseValue();
if (errorMessage != null)
return null;
table[name] = value;
break;
}
}
//return null; // Unreachable code
}
IList<object> ParseArray () {
List<object> array = new List<object>();
// [
lexer.NextToken();
while (true) {
Lexer.Token token = lexer.LookAhead();
switch (token) {
case Lexer.Token.None:
TriggerError("Invalid token");
return null;
case Lexer.Token.Comma:
lexer.NextToken();
break;
case Lexer.Token.SquaredClose:
lexer.NextToken();
return array;
default:
object value = ParseValue();
if (errorMessage != null)
return null;
array.Add(value);
break;
}
}
//return null; // Unreachable code
}
object ParseValue () {
switch (lexer.LookAhead()) {
case Lexer.Token.String:
return EvalLexer(lexer.ParseString());
case Lexer.Token.Number:
if (parseNumbersAsFloat)
return EvalLexer(lexer.ParseFloatNumber());
else
return EvalLexer(lexer.ParseDoubleNumber());
case Lexer.Token.CurlyOpen:
return ParseObject();
case Lexer.Token.SquaredOpen:
return ParseArray();
case Lexer.Token.True:
lexer.NextToken();
return true;
case Lexer.Token.False:
lexer.NextToken();
return false;
case Lexer.Token.Null:
lexer.NextToken();
return null;
case Lexer.Token.None:
break;
}
TriggerError("Unable to parse value");
return null;
}
void TriggerError (string message) {
errorMessage = string.Format("Error: '{0}' at line {1}",
message, lexer.lineNumber);
}
T EvalLexer<T> (T value) {
if (lexer.hasError)
TriggerError("Lexical error ocurred");
return value;
}
}
}

View File

@@ -0,0 +1,173 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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.
*****************************************************************************/
//#define USE_FAST_SIN_COS_ATAN2_APPROXIMATIONS
using System;
namespace SpineRuntime41 {
public static class MathUtils {
public const float PI = 3.1415927f;
public const float PI2 = PI * 2;
public const float RadDeg = 180f / PI;
public const float DegRad = PI / 180;
static Random random = new Random();
#if USE_FAST_SIN_COS_ATAN2_APPROXIMATIONS
const int SIN_BITS = 14; // 16KB. Adjust for accuracy.
const int SIN_MASK = ~(-1 << SIN_BITS);
const int SIN_COUNT = SIN_MASK + 1;
const float RadFull = PI * 2;
const float DegFull = 360;
const float RadToIndex = SIN_COUNT / RadFull;
const float DegToIndex = SIN_COUNT / DegFull;
static float[] sin = new float[SIN_COUNT];
static MathUtils () {
for (int i = 0; i < SIN_COUNT; i++)
sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * RadFull);
for (int i = 0; i < 360; i += 90)
sin[(int)(i * DegToIndex) & SIN_MASK] = (float)Math.Sin(i * DegRad);
}
/// <summary>Returns the sine of a given angle in radians from a lookup table.</summary>
static public float Sin (float radians) {
return sin[(int)(radians * RadToIndex) & SIN_MASK];
}
/// <summary>Returns the cosine of a given angle in radians from a lookup table.</summary>
static public float Cos (float radians) {
return sin[(int)((radians + PI / 2) * RadToIndex) & SIN_MASK];
}
/// <summary>Returns the sine of a given angle in degrees from a lookup table.</summary>
static public float SinDeg (float degrees) {
return sin[(int)(degrees * DegToIndex) & SIN_MASK];
}
/// <summary>Returns the cosine of a given angle in degrees from a lookup table.</summary>
static public float CosDeg (float degrees) {
return sin[(int)((degrees + 90) * DegToIndex) & SIN_MASK];
}
/// <summary>Returns atan2 in radians, faster but less accurate than Math.Atan2. Average error of 0.00231 radians (0.1323
/// degrees), largest error of 0.00488 radians (0.2796 degrees).</summary>
static public float Atan2 (float y, float x) {
if (x == 0f) {
if (y > 0f) return PI / 2;
if (y == 0f) return 0f;
return -PI / 2;
}
float atan, z = y / x;
if (Math.Abs(z) < 1f) {
atan = z / (1f + 0.28f * z * z);
if (x < 0f) return atan + (y < 0f ? -PI : PI);
return atan;
}
atan = PI / 2 - z / (z * z + 0.28f);
return y < 0f ? atan - PI : atan;
}
#else
/// <summary>Returns the sine of a given angle in radians.</summary>
static public float Sin (float radians) {
return (float)Math.Sin(radians);
}
/// <summary>Returns the cosine of a given angle in radians.</summary>
static public float Cos (float radians) {
return (float)Math.Cos(radians);
}
/// <summary>Returns the sine of a given angle in degrees.</summary>
static public float SinDeg (float degrees) {
return (float)Math.Sin(degrees * DegRad);
}
/// <summary>Returns the cosine of a given angle in degrees.</summary>
static public float CosDeg (float degrees) {
return (float)Math.Cos(degrees * DegRad);
}
/// <summary>Returns the atan2 using Math.Atan2.</summary>
static public float Atan2 (float y, float x) {
return (float)Math.Atan2(y, x);
}
#endif
static public float Clamp (float value, float min, float max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
static public float RandomTriangle (float min, float max) {
return RandomTriangle(min, max, (min + max) * 0.5f);
}
static public float RandomTriangle (float min, float max, float mode) {
float u = (float)random.NextDouble();
float d = max - min;
if (u <= (mode - min) / d) return min + (float)Math.Sqrt(u * d * (mode - min));
return max - (float)Math.Sqrt((1 - u) * d * (max - mode));
}
}
public abstract class IInterpolation {
public static IInterpolation Pow2 = new Pow(2);
public static IInterpolation Pow2Out = new PowOut(2);
protected abstract float Apply (float a);
public float Apply (float start, float end, float a) {
return start + (end - start) * Apply(a);
}
}
public class Pow : IInterpolation {
public float Power { get; set; }
public Pow (float power) {
Power = power;
}
protected override float Apply (float a) {
if (a <= 0.5f) return (float)Math.Pow(a * 2, Power) / 2;
return (float)Math.Pow((a - 1) * 2, Power) / (Power % 2 == 0 ? -2 : 2) + 1;
}
}
public class PowOut : Pow {
public PowOut (float power) : base(power) {
}
protected override float Apply (float a) {
return (float)Math.Pow(a - 1, Power) * (Power % 2 == 0 ? -1 : 1) + 1;
}
}
}

View File

@@ -0,0 +1,514 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
/// <summary>
/// <para>
/// Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the
/// constrained bones so they follow a <see cref="PathAttachment"/>.</para>
/// <para>
/// See <a href="http://esotericsoftware.com/spine-path-constraints">Path constraints</a> in the Spine User Guide.</para>
/// </summary>
public class PathConstraint : IUpdatable {
const int NONE = -1, BEFORE = -2, AFTER = -3;
const float Epsilon = 0.00001f;
internal readonly PathConstraintData data;
internal readonly ExposedList<Bone> bones;
internal Slot target;
internal float position, spacing, mixRotate, mixX, mixY;
internal bool active;
internal readonly ExposedList<float> spaces = new ExposedList<float>(), positions = new ExposedList<float>();
internal readonly ExposedList<float> world = new ExposedList<float>(), curves = new ExposedList<float>(), lengths = new ExposedList<float>();
internal readonly float[] segments = new float[10];
public PathConstraint (PathConstraintData 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;
bones = new ExposedList<Bone>(data.Bones.Count);
foreach (BoneData boneData in data.bones)
bones.Add(skeleton.bones.Items[boneData.index]);
target = skeleton.slots.Items[data.target.index];
position = data.position;
spacing = data.spacing;
mixRotate = data.mixRotate;
mixX = data.mixX;
mixY = data.mixY;
}
/// <summary>Copy constructor.</summary>
public PathConstraint (PathConstraint constraint, Skeleton skeleton) {
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<Bone>(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;
mixRotate = constraint.mixRotate;
mixX = constraint.mixX;
mixY = constraint.mixY;
}
public static void ArraysFill (float[] a, int fromIndex, int toIndex, float val) {
for (int i = fromIndex; i < toIndex; i++)
a[i] = val;
}
public void Update () {
PathAttachment attachment = target.Attachment as PathAttachment;
if (attachment == null) return;
float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY;
if (mixRotate == 0 && mixX == 0 && mixY == 0) return;
PathConstraintData data = this.data;
bool tangents = data.rotateMode == RotateMode.Tangent, scale = data.rotateMode == RotateMode.ChainScale;
int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1;
Bone[] bonesItems = this.bones.Items;
float[] spaces = this.spaces.Resize(spacesCount).Items, lengths = scale ? this.lengths.Resize(boneCount).Items : null;
float spacing = this.spacing;
switch (data.spacingMode) {
case SpacingMode.Percent:
if (scale) {
for (int i = 0, n = spacesCount - 1; i < n; i++) {
Bone bone = bonesItems[i];
float setupLength = bone.data.length;
if (setupLength < PathConstraint.Epsilon)
lengths[i] = 0;
else {
float x = setupLength * bone.a, y = setupLength * bone.c;
lengths[i] = (float)Math.Sqrt(x * x + y * y);
}
}
}
ArraysFill(spaces, 1, spacesCount, spacing);
break;
case SpacingMode.Proportional: {
float sum = 0;
for (int i = 0, n = spacesCount - 1; i < n;) {
Bone bone = bonesItems[i];
float setupLength = bone.data.length;
if (setupLength < PathConstraint.Epsilon) {
if (scale) lengths[i] = 0;
spaces[++i] = spacing;
} else {
float x = setupLength * bone.a, y = setupLength * bone.c;
float length = (float)Math.Sqrt(x * x + y * y);
if (scale) lengths[i] = length;
spaces[++i] = length;
sum += length;
}
}
if (sum > 0) {
sum = spacesCount / sum * spacing;
for (int i = 1; i < spacesCount; i++)
spaces[i] *= sum;
}
break;
}
default: {
bool lengthSpacing = data.spacingMode == SpacingMode.Length;
for (int i = 0, n = spacesCount - 1; i < n;) {
Bone bone = bonesItems[i];
float setupLength = bone.data.length;
if (setupLength < PathConstraint.Epsilon) {
if (scale) lengths[i] = 0;
spaces[++i] = spacing;
} else {
float x = setupLength * bone.a, y = setupLength * bone.c;
float length = (float)Math.Sqrt(x * x + y * y);
if (scale) lengths[i] = length;
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
}
}
break;
}
}
float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents);
float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
bool tip;
if (offsetRotation == 0) {
tip = data.rotateMode == RotateMode.Chain;
} else {
tip = false;
Bone p = target.bone;
offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
}
for (int i = 0, p = 3; i < boneCount; i++, p += 3) {
Bone bone = bonesItems[i];
bone.worldX += (boneX - bone.worldX) * mixX;
bone.worldY += (boneY - bone.worldY) * mixY;
float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
if (scale) {
float length = lengths[i];
if (length >= PathConstraint.Epsilon) {
float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1;
bone.a *= s;
bone.c *= s;
}
}
boneX = x;
boneY = y;
if (mixRotate > 0) {
float a = bone.a, b = bone.b, c = bone.c, d = bone.d, r, cos, sin;
if (tangents)
r = positions[p - 1];
else if (spaces[i + 1] < PathConstraint.Epsilon)
r = positions[p + 2];
else
r = MathUtils.Atan2(dy, dx);
r -= MathUtils.Atan2(c, a);
if (tip) {
cos = MathUtils.Cos(r);
sin = MathUtils.Sin(r);
float length = bone.data.length;
boneX += (length * (cos * a - sin * c) - dx) * mixRotate;
boneY += (length * (sin * a + cos * c) - dy) * mixRotate;
} else
r += offsetRotation;
if (r > MathUtils.PI)
r -= MathUtils.PI2;
else if (r < -MathUtils.PI) //
r += MathUtils.PI2;
r *= mixRotate;
cos = MathUtils.Cos(r);
sin = MathUtils.Sin(r);
bone.a = cos * a - sin * c;
bone.b = cos * b - sin * d;
bone.c = sin * a + cos * c;
bone.d = sin * b + cos * d;
}
bone.UpdateAppliedTransform();
}
}
float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents) {
Slot target = this.target;
float position = this.position;
float[] spaces = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world;
bool closed = path.Closed;
int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE;
float pathLength, multiplier;
if (!path.ConstantSpeed) {
float[] lengths = path.Lengths;
curveCount -= closed ? 1 : 2;
pathLength = lengths[curveCount];
if (data.positionMode == PositionMode.Percent) position *= pathLength;
switch (data.spacingMode) {
case SpacingMode.Percent:
multiplier = pathLength;
break;
case SpacingMode.Proportional:
multiplier = pathLength / spacesCount;
break;
default:
multiplier = 1;
break;
}
world = this.world.Resize(8).Items;
for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
float space = spaces[i] * multiplier;
position += space;
float p = position;
if (closed) {
p %= pathLength;
if (p < 0) p += pathLength;
curve = 0;
} else if (p < 0) {
if (prevCurve != BEFORE) {
prevCurve = BEFORE;
path.ComputeWorldVertices(target, 2, 4, world, 0, 2);
}
AddBeforePosition(p, world, 0, output, o);
continue;
} else if (p > pathLength) {
if (prevCurve != AFTER) {
prevCurve = AFTER;
path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0, 2);
}
AddAfterPosition(p - pathLength, world, 0, output, o);
continue;
}
// Determine curve containing position.
for (; ; curve++) {
float length = lengths[curve];
if (p > length) continue;
if (curve == 0)
p /= length;
else {
float prev = lengths[curve - 1];
p = (p - prev) / (length - prev);
}
break;
}
if (curve != prevCurve) {
prevCurve = curve;
if (closed && curve == curveCount) {
path.ComputeWorldVertices(target, verticesLength - 4, 4, world, 0, 2);
path.ComputeWorldVertices(target, 0, 4, world, 4, 2);
} else
path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2);
}
AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o,
tangents || (i > 0 && space < PathConstraint.Epsilon));
}
return output;
}
// World vertices.
if (closed) {
verticesLength += 2;
world = this.world.Resize(verticesLength).Items;
path.ComputeWorldVertices(target, 2, verticesLength - 4, world, 0, 2);
path.ComputeWorldVertices(target, 0, 2, world, verticesLength - 4, 2);
world[verticesLength - 2] = world[0];
world[verticesLength - 1] = world[1];
} else {
curveCount--;
verticesLength -= 4;
world = this.world.Resize(verticesLength).Items;
path.ComputeWorldVertices(target, 2, verticesLength, world, 0, 2);
}
// Curve lengths.
float[] curves = this.curves.Resize(curveCount).Items;
pathLength = 0;
float x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy;
for (int i = 0, w = 2; i < curveCount; i++, w += 6) {
cx1 = world[w];
cy1 = world[w + 1];
cx2 = world[w + 2];
cy2 = world[w + 3];
x2 = world[w + 4];
y2 = world[w + 5];
tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f;
tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f;
dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f;
dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375f;
ddfx = tmpx * 2 + dddfx;
ddfy = tmpy * 2 + dddfy;
dfx = (cx1 - x1) * 0.75f + tmpx + dddfx * 0.16666667f;
dfy = (cy1 - y1) * 0.75f + tmpy + dddfy * 0.16666667f;
pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
dfx += ddfx;
dfy += ddfy;
ddfx += dddfx;
ddfy += dddfy;
pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
dfx += ddfx;
dfy += ddfy;
pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
dfx += ddfx + dddfx;
dfy += ddfy + dddfy;
pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
curves[i] = pathLength;
x1 = x2;
y1 = y2;
}
if (data.positionMode == PositionMode.Percent) position *= pathLength;
switch (data.spacingMode) {
case SpacingMode.Percent:
multiplier = pathLength;
break;
case SpacingMode.Proportional:
multiplier = pathLength / spacesCount;
break;
default:
multiplier = 1;
break;
}
float[] segments = this.segments;
float curveLength = 0;
for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) {
float space = spaces[i] * multiplier;
position += space;
float p = position;
if (closed) {
p %= pathLength;
if (p < 0) p += pathLength;
curve = 0;
} else if (p < 0) {
AddBeforePosition(p, world, 0, output, o);
continue;
} else if (p > pathLength) {
AddAfterPosition(p - pathLength, world, verticesLength - 4, output, o);
continue;
}
// Determine curve containing position.
for (; ; curve++) {
float length = curves[curve];
if (p > length) continue;
if (curve == 0)
p /= length;
else {
float prev = curves[curve - 1];
p = (p - prev) / (length - prev);
}
break;
}
// Curve segment lengths.
if (curve != prevCurve) {
prevCurve = curve;
int ii = curve * 6;
x1 = world[ii];
y1 = world[ii + 1];
cx1 = world[ii + 2];
cy1 = world[ii + 3];
cx2 = world[ii + 4];
cy2 = world[ii + 5];
x2 = world[ii + 6];
y2 = world[ii + 7];
tmpx = (x1 - cx1 * 2 + cx2) * 0.03f;
tmpy = (y1 - cy1 * 2 + cy2) * 0.03f;
dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f;
dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006f;
ddfx = tmpx * 2 + dddfx;
ddfy = tmpy * 2 + dddfy;
dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f;
dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f;
curveLength = (float)Math.Sqrt(dfx * dfx + dfy * dfy);
segments[0] = curveLength;
for (ii = 1; ii < 8; ii++) {
dfx += ddfx;
dfy += ddfy;
ddfx += dddfx;
ddfy += dddfy;
curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
segments[ii] = curveLength;
}
dfx += ddfx;
dfy += ddfy;
curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
segments[8] = curveLength;
dfx += ddfx + dddfx;
dfy += ddfy + dddfy;
curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
segments[9] = curveLength;
segment = 0;
}
// Weight by segment length.
p *= curveLength;
for (; ; segment++) {
float length = segments[segment];
if (p > length) continue;
if (segment == 0)
p /= length;
else {
float prev = segments[segment - 1];
p = segment + (p - prev) / (length - prev);
}
break;
}
AddCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, output, o, tangents || (i > 0 && space < PathConstraint.Epsilon));
}
return output;
}
static void AddBeforePosition (float p, float[] temp, int i, float[] output, int o) {
float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx);
output[o] = x1 + p * MathUtils.Cos(r);
output[o + 1] = y1 + p * MathUtils.Sin(r);
output[o + 2] = r;
}
static void AddAfterPosition (float p, float[] temp, int i, float[] output, int o) {
float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx);
output[o] = x1 + p * MathUtils.Cos(r);
output[o + 1] = y1 + p * MathUtils.Sin(r);
output[o + 2] = r;
}
static void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
float[] output, int o, bool tangents) {
if (p < PathConstraint.Epsilon || float.IsNaN(p)) {
output[o] = x1;
output[o + 1] = y1;
output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1);
return;
}
float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
output[o] = x;
output[o + 1] = y;
if (tangents) {
if (p < 0.001f)
output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1);
else
output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
}
}
/// <summary>The position along the path.</summary>
public float Position { get { return position; } set { position = value; } }
/// <summary>The spacing between bones.</summary>
public float Spacing { get { return spacing; } set { spacing = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained rotations.</summary>
public float MixRotate { get { return mixRotate; } set { mixRotate = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translation X.</summary>
public float MixX { get { return mixX; } set { mixX = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y.</summary>
public float MixY { get { return mixY; } set { mixY = value; } }
/// <summary>The bones that will be modified by this path constraint.</summary>
public ExposedList<Bone> Bones { get { return bones; } }
/// <summary>The slot whose path attachment will be used to constrained the bones.</summary>
public Slot Target { get { return target; } set { target = value; } }
public bool Active { get { return active; } }
/// <summary>The path constraint's setup pose data.</summary>
public PathConstraintData Data { get { return data; } }
override public string ToString () {
return data.name;
}
}
}

View File

@@ -0,0 +1,72 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
public class PathConstraintData : ConstraintData {
internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
internal SlotData target;
internal PositionMode positionMode;
internal SpacingMode spacingMode;
internal RotateMode rotateMode;
internal float offsetRotation;
internal float position, spacing, mixRotate, mixX, mixY;
public PathConstraintData (string name) : base(name) {
}
public ExposedList<BoneData> Bones { get { return bones; } }
public SlotData Target { get { return target; } set { target = value; } }
public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } }
public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } }
public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } }
public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } }
public float Position { get { return position; } set { position = value; } }
public float Spacing { get { return spacing; } set { spacing = value; } }
/// <summary> A percentage (0-1) that controls the mix between the constrained and unconstrained rotation.</summary>
public float RotateMix { get { return mixRotate; } set { mixRotate = value; } }
/// <summary> A percentage (0-1) that controls the mix between the constrained and unconstrained translation X.</summary>
public float MixX { get { return mixX; } set { mixX = value; } }
/// <summary> A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y.</summary>
public float MixY { get { return mixY; } set { mixY = value; } }
}
public enum PositionMode {
Fixed, Percent
}
public enum SpacingMode {
Length, Fixed, Percent, Proportional
}
public enum RotateMode {
Tangent, Chain, ChainScale
}
}

View File

@@ -0,0 +1,660 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
public class Skeleton {
internal SkeletonData data;
internal ExposedList<Bone> bones;
internal ExposedList<Slot> slots;
internal ExposedList<Slot> drawOrder;
internal ExposedList<IkConstraint> ikConstraints;
internal ExposedList<TransformConstraint> transformConstraints;
internal ExposedList<PathConstraint> pathConstraints;
internal ExposedList<IUpdatable> updateCache = new ExposedList<IUpdatable>();
internal Skin skin;
internal float r = 1, g = 1, b = 1, a = 1;
private float scaleX = 1, scaleY = 1;
internal float x, y;
public SkeletonData Data { get { return data; } }
public ExposedList<Bone> Bones { get { return bones; } }
public ExposedList<IUpdatable> UpdateCacheList { get { return updateCache; } }
public ExposedList<Slot> Slots { get { return slots; } }
public ExposedList<Slot> DrawOrder { get { return drawOrder; } }
public ExposedList<IkConstraint> IkConstraints { get { return ikConstraints; } }
public ExposedList<PathConstraint> PathConstraints { get { return pathConstraints; } }
public ExposedList<TransformConstraint> TransformConstraints { get { return transformConstraints; } }
public Skin Skin {
/// <summary>The skeleton's current skin. May be null.</summary>
get { return skin; }
/// <summary>Sets a skin, <see cref="SetSkin(Skin)"/>.</summary>
set { SetSkin(value); }
}
public float R { get { return r; } set { r = value; } }
public float G { get { return g; } set { g = value; } }
public float B { get { return b; } set { b = value; } }
public float A { get { return a; } set { a = value; } }
public float 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; } }
public float ScaleY { get { return scaleY * (Bone.yDown ? -1 : 1); } set { scaleY = value; } }
[Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")]
public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } }
[Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")]
public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } }
/// <summary>Returns the root bone, or null if the skeleton has no bones.</summary>
public Bone RootBone {
get { return bones.Count == 0 ? null : bones.Items[0]; }
}
public Skeleton (SkeletonData data) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
this.data = data;
bones = new ExposedList<Bone>(data.bones.Count);
Bone[] bonesItems = this.bones.Items;
foreach (BoneData boneData in data.bones) {
Bone bone;
if (boneData.parent == null) {
bone = new Bone(boneData, this, null);
} else {
Bone parent = bonesItems[boneData.parent.index];
bone = new Bone(boneData, this, parent);
parent.children.Add(bone);
}
this.bones.Add(bone);
}
slots = new ExposedList<Slot>(data.slots.Count);
drawOrder = new ExposedList<Slot>(data.slots.Count);
foreach (SlotData slotData in data.slots) {
Bone bone = bonesItems[slotData.boneData.index];
Slot slot = new Slot(slotData, bone);
slots.Add(slot);
drawOrder.Add(slot);
}
ikConstraints = new ExposedList<IkConstraint>(data.ikConstraints.Count);
foreach (IkConstraintData ikConstraintData in data.ikConstraints)
ikConstraints.Add(new IkConstraint(ikConstraintData, this));
transformConstraints = new ExposedList<TransformConstraint>(data.transformConstraints.Count);
foreach (TransformConstraintData transformConstraintData in data.transformConstraints)
transformConstraints.Add(new TransformConstraint(transformConstraintData, this));
pathConstraints = new ExposedList<PathConstraint>(data.pathConstraints.Count);
foreach (PathConstraintData pathConstraintData in data.pathConstraints)
pathConstraints.Add(new PathConstraint(pathConstraintData, this));
UpdateCache();
}
/// <summary>Copy constructor.</summary>
public Skeleton (Skeleton skeleton) {
if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
data = skeleton.data;
bones = new ExposedList<Bone>(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<Slot>(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<Slot>(slots.Count);
Slot[] slotsItems = slots.Items;
foreach (Slot slot in skeleton.drawOrder)
drawOrder.Add(slotsItems[slot.data.index]);
ikConstraints = new ExposedList<IkConstraint>(skeleton.ikConstraints.Count);
foreach (IkConstraint ikConstraint in skeleton.ikConstraints)
ikConstraints.Add(new IkConstraint(ikConstraint, this));
transformConstraints = new ExposedList<TransformConstraint>(skeleton.transformConstraints.Count);
foreach (TransformConstraint transformConstraint in skeleton.transformConstraints)
transformConstraints.Add(new TransformConstraint(transformConstraint, this));
pathConstraints = new ExposedList<PathConstraint>(skeleton.pathConstraints.Count);
foreach (PathConstraint pathConstraint in skeleton.pathConstraints)
pathConstraints.Add(new PathConstraint(pathConstraint, this));
skin = skeleton.skin;
r = skeleton.r;
g = skeleton.g;
b = skeleton.b;
a = skeleton.a;
scaleX = skeleton.scaleX;
scaleY = skeleton.scaleY;
UpdateCache();
}
/// <summary>Caches information about bones and constraints. Must be called if the <see cref="Skin"/> is modified or if bones, constraints, or
/// constraints, or weighted path attachments are added or removed.</summary>
public void UpdateCache () {
ExposedList<IUpdatable> updateCache = this.updateCache;
updateCache.Clear();
int boneCount = this.bones.Count;
Bone[] bones = this.bones.Items;
for (int i = 0; i < boneCount; i++) {
Bone bone = bones[i];
bone.sorted = bone.data.skinRequired;
bone.active = !bone.sorted;
}
if (skin != null) {
BoneData[] skinBones = skin.bones.Items;
for (int i = 0, n = skin.bones.Count; i < n; i++) {
Bone bone = bones[skinBones[i].index];
do {
bone.sorted = false;
bone.active = true;
bone = bone.parent;
} while (bone != null);
}
}
int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count;
IkConstraint[] ikConstraints = this.ikConstraints.Items;
TransformConstraint[] transformConstraints = this.transformConstraints.Items;
PathConstraint[] pathConstraints = this.pathConstraints.Items;
int constraintCount = ikCount + transformCount + pathCount;
for (int i = 0; i < constraintCount; i++) {
for (int ii = 0; ii < ikCount; ii++) {
IkConstraint constraint = ikConstraints[ii];
if (constraint.data.order == i) {
SortIkConstraint(constraint);
goto continue_outer;
}
}
for (int ii = 0; ii < transformCount; ii++) {
TransformConstraint constraint = transformConstraints[ii];
if (constraint.data.order == i) {
SortTransformConstraint(constraint);
goto continue_outer;
}
}
for (int ii = 0; ii < pathCount; ii++) {
PathConstraint constraint = pathConstraints[ii];
if (constraint.data.order == i) {
SortPathConstraint(constraint);
goto continue_outer;
}
}
continue_outer: { }
}
for (int i = 0; i < boneCount; i++)
SortBone(bones[i]);
}
private void SortIkConstraint (IkConstraint constraint) {
constraint.active = constraint.target.active
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)));
if (!constraint.active) return;
Bone target = constraint.target;
SortBone(target);
ExposedList<Bone> constrained = constraint.bones;
Bone parent = constrained.Items[0];
SortBone(parent);
if (constrained.Count == 1) {
updateCache.Add(constraint);
SortReset(parent.children);
} else {
Bone child = constrained.Items[constrained.Count - 1];
SortBone(child);
updateCache.Add(constraint);
SortReset(parent.children);
child.sorted = true;
}
}
private void SortTransformConstraint (TransformConstraint constraint) {
constraint.active = constraint.target.active
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)));
if (!constraint.active) return;
SortBone(constraint.target);
Bone[] constrained = constraint.bones.Items;
int boneCount = constraint.bones.Count;
if (constraint.data.local) {
for (int i = 0; i < boneCount; i++) {
Bone child = constrained[i];
SortBone(child.parent);
SortBone(child);
}
} else {
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 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);
Bone[] 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 (Skin.SkinEntry entry in skin.Attachments)
if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone);
}
private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) {
if (!(attachment is PathAttachment)) return;
int[] pathBones = ((PathAttachment)attachment).bones;
if (pathBones == null)
SortBone(slotBone);
else {
Bone[] bones = this.bones.Items;
for (int i = 0, n = pathBones.Length; i < n;) {
int nn = pathBones[i++];
nn += i;
while (i < nn)
SortBone(bones[pathBones[i++]]);
}
}
}
private void SortBone (Bone bone) {
if (bone.sorted) return;
Bone parent = bone.parent;
if (parent != null) SortBone(parent);
bone.sorted = true;
updateCache.Add(bone);
}
private static void SortReset (ExposedList<Bone> bones) {
Bone[] bonesItems = bones.Items;
for (int i = 0, n = bones.Count; i < n; i++) {
Bone bone = bonesItems[i];
if (!bone.active) continue;
if (bone.sorted) SortReset(bone.children);
bone.sorted = false;
}
}
/// <summary>
/// Updates the world transform for each bone and applies all constraints.
/// <para>
/// See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
/// Runtimes Guide.</para>
/// </summary>
public void UpdateWorldTransform () {
Bone[] bones = this.bones.Items;
for (int i = 0, n = this.bones.Count; i < n; i++) {
Bone bone = bones[i];
bone.ax = bone.x;
bone.ay = bone.y;
bone.arotation = bone.rotation;
bone.ascaleX = bone.scaleX;
bone.ascaleY = bone.scaleY;
bone.ashearX = bone.shearX;
bone.ashearY = bone.shearY;
}
IUpdatable[] updateCache = this.updateCache.Items;
for (int i = 0, n = this.updateCache.Count; i < n; i++)
updateCache[i].Update();
}
/// <summary>
/// Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies
/// all constraints.
/// </summary>
public void UpdateWorldTransform (Bone parent) {
if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null.");
// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
Bone rootBone = this.RootBone;
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
rootBone.worldX = pa * x + pb * y + parent.worldX;
rootBone.worldY = pc * x + pd * y + parent.worldY;
float rotationY = rootBone.rotation + 90 + rootBone.shearY;
float la = MathUtils.CosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
float lb = MathUtils.CosDeg(rotationY) * rootBone.scaleY;
float lc = MathUtils.SinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
float ld = MathUtils.SinDeg(rotationY) * rootBone.scaleY;
rootBone.a = (pa * la + pb * lc) * scaleX;
rootBone.b = (pa * lb + pb * ld) * scaleX;
rootBone.c = (pc * la + pd * lc) * scaleY;
rootBone.d = (pc * lb + pd * ld) * scaleY;
// Update everything except root bone.
IUpdatable[] updateCache = this.updateCache.Items;
for (int i = 0, n = this.updateCache.Count; i < n; i++) {
IUpdatable updatable = updateCache[i];
if (updatable != rootBone) updatable.Update();
}
}
/// <summary>Sets the bones, constraints, and slots to their setup pose values.</summary>
public void SetToSetupPose () {
SetBonesToSetupPose();
SetSlotsToSetupPose();
}
/// <summary>Sets the bones and constraints to their setup pose values.</summary>
public void SetBonesToSetupPose () {
Bone[] bones = this.bones.Items;
for (int i = 0, n = this.bones.Count; i < n; i++)
bones[i].SetToSetupPose();
IkConstraint[] ikConstraints = this.ikConstraints.Items;
for (int i = 0, n = this.ikConstraints.Count; i < n; i++) {
IkConstraint constraint = ikConstraints[i];
IkConstraintData data = constraint.data;
constraint.mix = data.mix;
constraint.softness = data.softness;
constraint.bendDirection = data.bendDirection;
constraint.compress = data.compress;
constraint.stretch = data.stretch;
}
TransformConstraint[] transformConstraints = this.transformConstraints.Items;
for (int i = 0, n = this.transformConstraints.Count; i < n; i++) {
TransformConstraint constraint = transformConstraints[i];
TransformConstraintData data = constraint.data;
constraint.mixRotate = data.mixRotate;
constraint.mixX = data.mixX;
constraint.mixY = data.mixY;
constraint.mixScaleX = data.mixScaleX;
constraint.mixScaleY = data.mixScaleY;
constraint.mixShearY = data.mixShearY;
}
PathConstraint[] pathConstraints = this.pathConstraints.Items;
for (int i = 0, n = this.pathConstraints.Count; i < n; i++) {
PathConstraint constraint = pathConstraints[i];
PathConstraintData data = constraint.data;
constraint.position = data.position;
constraint.spacing = data.spacing;
constraint.mixRotate = data.mixRotate;
constraint.mixX = data.mixX;
constraint.mixY = data.mixY;
}
}
public void SetSlotsToSetupPose () {
Slot[] slots = this.slots.Items;
int n = this.slots.Count;
Array.Copy(slots, 0, drawOrder.Items, 0, n);
for (int i = 0; i < n; i++)
slots[i].SetToSetupPose();
}
/// <summary>Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it
/// repeatedly.</summary>
/// <returns>May be null.</returns>
public Bone FindBone (string boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
Bone[] bones = this.bones.Items;
for (int i = 0, n = this.bones.Count; i < n; i++) {
Bone bone = bones[i];
if (bone.data.name == boneName) return bone;
}
return null;
}
/// <summary>Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it
/// repeatedly.</summary>
/// <returns>May be null.</returns>
public Slot FindSlot (string slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
Slot[] slots = this.slots.Items;
for (int i = 0, n = this.slots.Count; i < n; i++) {
Slot slot = slots[i];
if (slot.data.name == slotName) return slot;
}
return null;
}
/// <summary>Sets a skin by name (see <see cref="SetSkin(Skin)"/>).</summary>
public void SetSkin (string skinName) {
Skin foundSkin = data.FindSkin(skinName);
if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName");
SetSkin(foundSkin);
}
/// <summary>
/// <para>Sets the skin used to look up attachments before looking in the <see cref="SkeletonData.DefaultSkin"/>. If the
/// skin is changed, <see cref="UpdateCache()"/> is called.
/// </para>
/// <para>Attachments from the new skin are attached if the corresponding attachment from the old skin was attached.
/// If there was no old skin, each slot's setup mode attachment is attached from the new skin.
/// </para>
/// <para>After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling
/// <see cref="Skeleton.SetSlotsToSetupPose()"/>.
/// Also, often <see cref="AnimationState.Apply(Skeleton)"/> is called before the next time the
/// skeleton is rendered to allow any attachment keys in the current animation(s) to hide or show attachments from the new skin.</para>
/// </summary>
/// <param name="newSkin">May be null.</param>
public void SetSkin (Skin newSkin) {
if (newSkin == skin) return;
if (newSkin != null) {
if (skin != null)
newSkin.AttachAll(this, skin);
else {
Slot[] slots = this.slots.Items;
for (int i = 0, n = this.slots.Count; i < n; i++) {
Slot slot = slots[i];
string name = slot.data.attachmentName;
if (name != null) {
Attachment attachment = newSkin.GetAttachment(i, name);
if (attachment != null) slot.Attachment = attachment;
}
}
}
}
skin = newSkin;
UpdateCache();
}
/// <summary>Finds an attachment by looking in the <see cref="Skeleton.Skin"/> and <see cref="SkeletonData.DefaultSkin"/> using the slot name and attachment name.</summary>
/// <returns>May be null.</returns>
public Attachment GetAttachment (string slotName, string attachmentName) {
return GetAttachment(data.FindSlot(slotName).index, attachmentName);
}
/// <summary>Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked.</summary>
/// <returns>May be null.</returns>
public Attachment GetAttachment (int slotIndex, string attachmentName) {
if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null.");
if (skin != null) {
Attachment attachment = skin.GetAttachment(slotIndex, attachmentName);
if (attachment != null) return attachment;
}
return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null;
}
/// <summary>A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment.</summary>
/// <param name="attachmentName">May be null to clear the slot's attachment.</param>
public void SetAttachment (string slotName, string attachmentName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
Slot[] slots = this.slots.Items;
for (int i = 0, n = this.slots.Count; i < n; i++) {
Slot slot = slots[i];
if (slot.data.name == slotName) {
Attachment attachment = null;
if (attachmentName != null) {
attachment = GetAttachment(i, attachmentName);
if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName);
}
slot.Attachment = attachment;
return;
}
}
throw new Exception("Slot not found: " + slotName);
}
/// <summary>Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method
/// than to call it repeatedly.</summary>
/// <returns>May be null.</returns>
public IkConstraint FindIkConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
IkConstraint[] ikConstraints = this.ikConstraints.Items;
for (int i = 0, n = this.ikConstraints.Count; i < n; i++) {
IkConstraint ikConstraint = ikConstraints[i];
if (ikConstraint.data.name == constraintName) return ikConstraint;
}
return null;
}
/// <summary>Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of
/// this method than to call it repeatedly.</summary>
/// <returns>May be null.</returns>
public TransformConstraint FindTransformConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
TransformConstraint[] transformConstraints = this.transformConstraints.Items;
for (int i = 0, n = this.transformConstraints.Count; i < n; i++) {
TransformConstraint transformConstraint = transformConstraints[i];
if (transformConstraint.data.Name == constraintName) return transformConstraint;
}
return null;
}
/// <summary>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 repeatedly.</summary>
/// <returns>May be null.</returns>
public PathConstraint FindPathConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
PathConstraint[] pathConstraints = this.pathConstraints.Items;
for (int i = 0, n = this.pathConstraints.Count; i < n; i++) {
PathConstraint constraint = pathConstraints[i];
if (constraint.data.Name.Equals(constraintName)) return constraint;
}
return null;
}
/// <summary>Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose.</summary>
/// <param name="x">The horizontal distance between the skeleton origin and the left side of the AABB.</param>
/// <param name="y">The vertical distance between the skeleton origin and the bottom side of the AABB.</param>
/// <param name="width">The width of the AABB</param>
/// <param name="height">The height of the AABB.</param>
/// <param name="vertexBuffer">Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed.</param>
public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer) {
float[] temp = vertexBuffer;
temp = temp ?? new float[8];
Slot[] drawOrder = this.drawOrder.Items;
float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
for (int i = 0, n = this.drawOrder.Count; i < n; i++) {
Slot slot = drawOrder[i];
if (!slot.bone.active) continue;
int verticesLength = 0;
float[] vertices = null;
Attachment attachment = slot.attachment;
RegionAttachment region = attachment as RegionAttachment;
if (region != null) {
verticesLength = 8;
vertices = temp;
if (vertices.Length < 8) vertices = temp = new float[8];
region.ComputeWorldVertices(slot, temp, 0, 2);
} else {
MeshAttachment mesh = attachment as MeshAttachment;
if (mesh != null) {
verticesLength = mesh.WorldVerticesLength;
vertices = temp;
if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength];
mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0, 2);
}
}
if (vertices != null) {
for (int ii = 0; ii < verticesLength; ii += 2) {
float vx = vertices[ii], vy = vertices[ii + 1];
minX = Math.Min(minX, vx);
minY = Math.Min(minY, vy);
maxX = Math.Max(maxX, vx);
maxY = Math.Max(maxY, vy);
}
}
}
x = minX;
y = minY;
width = maxX - minX;
height = maxY - minY;
vertexBuffer = temp;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,233 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
/// <summary>
/// Collects each BoundingBoxAttachment that is visible and computes the world vertices for its polygon.
/// The polygon vertices are provided along with convenience methods for doing hit detection.
/// </summary>
public class SkeletonBounds {
private ExposedList<Polygon> polygonPool = new ExposedList<Polygon>();
private float minX, minY, maxX, maxY;
public ExposedList<BoundingBoxAttachment> BoundingBoxes { get; private set; }
public ExposedList<Polygon> Polygons { get; private set; }
public float MinX { get { return minX; } set { minX = value; } }
public float MinY { get { return minY; } set { minY = value; } }
public float MaxX { get { return maxX; } set { maxX = value; } }
public float MaxY { get { return maxY; } set { maxY = value; } }
public float Width { get { return maxX - minX; } }
public float Height { get { return maxY - minY; } }
public SkeletonBounds () {
BoundingBoxes = new ExposedList<BoundingBoxAttachment>();
Polygons = new ExposedList<Polygon>();
}
/// <summary>
/// Clears any previous polygons, finds all visible bounding box attachments,
/// and computes the world vertices for each bounding box's polygon.</summary>
/// <param name="skeleton">The skeleton.</param>
/// <param name="updateAabb">
/// If true, the axis aligned bounding box containing all the polygons is computed.
/// If false, the SkeletonBounds AABB methods will always return true.
/// </param>
public void Update (Skeleton skeleton, bool updateAabb) {
ExposedList<BoundingBoxAttachment> boundingBoxes = BoundingBoxes;
ExposedList<Polygon> polygons = Polygons;
Slot[] slots = skeleton.slots.Items;
int slotCount = skeleton.slots.Count;
boundingBoxes.Clear();
for (int i = 0, n = polygons.Count; i < n; i++)
polygonPool.Add(polygons.Items[i]);
polygons.Clear();
for (int i = 0; i < slotCount; i++) {
Slot slot = slots[i];
if (!slot.bone.active) continue;
BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment;
if (boundingBox == null) continue;
boundingBoxes.Add(boundingBox);
Polygon polygon = null;
int poolCount = polygonPool.Count;
if (poolCount > 0) {
polygon = polygonPool.Items[poolCount - 1];
polygonPool.RemoveAt(poolCount - 1);
} else
polygon = new Polygon();
polygons.Add(polygon);
int count = boundingBox.worldVerticesLength;
polygon.Count = count;
if (polygon.Vertices.Length < count) polygon.Vertices = new float[count];
boundingBox.ComputeWorldVertices(slot, polygon.Vertices);
}
if (updateAabb) {
AabbCompute();
} else {
minX = int.MinValue;
minY = int.MinValue;
maxX = int.MaxValue;
maxY = int.MaxValue;
}
}
private void AabbCompute () {
float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
Polygon[] polygons = Polygons.Items;
for (int i = 0, n = Polygons.Count; i < n; i++) {
Polygon polygon = polygons[i];
float[] vertices = polygon.Vertices;
for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) {
float x = vertices[ii];
float y = vertices[ii + 1];
minX = Math.Min(minX, x);
minY = Math.Min(minY, y);
maxX = Math.Max(maxX, x);
maxY = Math.Max(maxY, y);
}
}
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
}
/// <summary>Returns true if the axis aligned bounding box contains the point.</summary>
public bool AabbContainsPoint (float x, float y) {
return x >= minX && x <= maxX && y >= minY && y <= maxY;
}
/// <summary>Returns true if the axis aligned bounding box intersects the line segment.</summary>
public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) {
float minX = this.minX;
float minY = this.minY;
float maxX = this.maxX;
float maxY = this.maxY;
if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY))
return false;
float m = (y2 - y1) / (x2 - x1);
float y = m * (minX - x1) + y1;
if (y > minY && y < maxY) return true;
y = m * (maxX - x1) + y1;
if (y > minY && y < maxY) return true;
float x = (minY - y1) / m + x1;
if (x > minX && x < maxX) return true;
x = (maxY - y1) / m + x1;
if (x > minX && x < maxX) return true;
return false;
}
/// <summary>Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds.</summary>
public bool AabbIntersectsSkeleton (SkeletonBounds bounds) {
return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY;
}
/// <summary>Returns true if the polygon contains the point.</summary>
public bool ContainsPoint (Polygon polygon, float x, float y) {
float[] vertices = polygon.Vertices;
int nn = polygon.Count;
int prevIndex = nn - 2;
bool inside = false;
for (int ii = 0; ii < nn; ii += 2) {
float vertexY = vertices[ii + 1];
float prevY = vertices[prevIndex + 1];
if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) {
float vertexX = vertices[ii];
if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) inside = !inside;
}
prevIndex = ii;
}
return inside;
}
/// <summary>Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more
/// efficient to only call this method if <see cref="AabbContainsPoint(float, float)"/> returns true.</summary>
public BoundingBoxAttachment ContainsPoint (float x, float y) {
Polygon[] polygons = Polygons.Items;
for (int i = 0, n = Polygons.Count; i < n; i++)
if (ContainsPoint(polygons[i], x, y)) return BoundingBoxes.Items[i];
return null;
}
/// <summary>Returns the first bounding box attachment that contains the line segment, or null. When doing many checks, it is usually
/// more efficient to only call this method if <see cref="aabbIntersectsSegment(float, float, float, float)"/> returns true.</summary>
public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) {
Polygon[] polygons = Polygons.Items;
for (int i = 0, n = Polygons.Count; i < n; i++)
if (IntersectsSegment(polygons[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i];
return null;
}
/// <summary>Returns true if the polygon contains the line segment.</summary>
public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) {
float[] vertices = polygon.Vertices;
int nn = polygon.Count;
float width12 = x1 - x2, height12 = y1 - y2;
float det1 = x1 * y2 - y1 * x2;
float x3 = vertices[nn - 2], y3 = vertices[nn - 1];
for (int ii = 0; ii < nn; ii += 2) {
float x4 = vertices[ii], y4 = vertices[ii + 1];
float det2 = x3 * y4 - y3 * x4;
float width34 = x3 - x4, height34 = y3 - y4;
float det3 = width12 * height34 - height12 * width34;
float x = (det1 * width34 - width12 * det2) / det3;
if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) {
float y = (det1 * height34 - height12 * det2) / det3;
if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) return true;
}
x3 = x4;
y3 = y4;
}
return false;
}
public Polygon GetPolygon (BoundingBoxAttachment attachment) {
int index = BoundingBoxes.IndexOf(attachment);
return index == -1 ? null : Polygons.Items[index];
}
}
public class Polygon {
public float[] Vertices { get; set; }
public int Count { get; set; }
public Polygon () {
Vertices = new float[16];
}
}
}

View File

@@ -0,0 +1,292 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
public class SkeletonClipping {
internal readonly Triangulator triangulator = new Triangulator();
internal readonly ExposedList<float> clippingPolygon = new ExposedList<float>();
internal readonly ExposedList<float> clipOutput = new ExposedList<float>(128);
internal readonly ExposedList<float> clippedVertices = new ExposedList<float>(128);
internal readonly ExposedList<int> clippedTriangles = new ExposedList<int>(128);
internal readonly ExposedList<float> clippedUVs = new ExposedList<float>(128);
internal readonly ExposedList<float> scratch = new ExposedList<float>();
internal ClippingAttachment clipAttachment;
internal ExposedList<ExposedList<float>> clippingPolygons;
public ExposedList<float> ClippedVertices { get { return clippedVertices; } }
public ExposedList<int> ClippedTriangles { get { return clippedTriangles; } }
public ExposedList<float> ClippedUVs { get { return clippedUVs; } }
public bool IsClipping { get { return clipAttachment != null; } }
public int ClipStart (Slot slot, ClippingAttachment clip) {
if (clipAttachment != null) return 0;
clipAttachment = clip;
int n = clip.worldVerticesLength;
float[] vertices = clippingPolygon.Resize(n).Items;
clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2);
MakeClockwise(clippingPolygon);
clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon));
foreach (ExposedList<float> polygon in clippingPolygons) {
MakeClockwise(polygon);
polygon.Add(polygon.Items[0]);
polygon.Add(polygon.Items[1]);
}
return clippingPolygons.Count;
}
public void ClipEnd (Slot slot) {
if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd();
}
public void ClipEnd () {
if (clipAttachment == null) return;
clipAttachment = null;
clippingPolygons = null;
clippedVertices.Clear();
clippedTriangles.Clear();
clippingPolygon.Clear();
}
public void ClipTriangles (float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) {
ExposedList<float> clipOutput = this.clipOutput, clippedVertices = this.clippedVertices;
ExposedList<int> clippedTriangles = this.clippedTriangles;
ExposedList<float>[] polygons = clippingPolygons.Items;
int polygonsCount = clippingPolygons.Count;
int index = 0;
clippedVertices.Clear();
clippedUVs.Clear();
clippedTriangles.Clear();
//outer:
for (int i = 0; i < trianglesLength; i += 3) {
int vertexOffset = triangles[i] << 1;
float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1];
float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1];
vertexOffset = triangles[i + 1] << 1;
float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1];
float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1];
vertexOffset = triangles[i + 2] << 1;
float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1];
float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1];
for (int p = 0; p < polygonsCount; p++) {
int s = clippedVertices.Count;
if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) {
int clipOutputLength = clipOutput.Count;
if (clipOutputLength == 0) continue;
float d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1;
float d = 1 / (d0 * d2 + d1 * (y1 - y3));
int clipOutputCount = clipOutputLength >> 1;
float[] clipOutputItems = clipOutput.Items;
float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items;
float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items;
for (int ii = 0; ii < clipOutputLength; ii += 2) {
float x = clipOutputItems[ii], y = clipOutputItems[ii + 1];
clippedVerticesItems[s] = x;
clippedVerticesItems[s + 1] = y;
float c0 = x - x3, c1 = y - y3;
float a = (d0 * c0 + d1 * c1) * d;
float b = (d4 * c0 + d2 * c1) * d;
float c = 1 - a - b;
clippedUVsItems[s] = u1 * a + u2 * b + u3 * c;
clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c;
s += 2;
}
s = clippedTriangles.Count;
int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items;
clipOutputCount--;
for (int ii = 1; ii < clipOutputCount; ii++) {
clippedTrianglesItems[s] = index;
clippedTrianglesItems[s + 1] = index + ii;
clippedTrianglesItems[s + 2] = index + ii + 1;
s += 3;
}
index += clipOutputCount + 1;
} else {
float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items;
float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items;
clippedVerticesItems[s] = x1;
clippedVerticesItems[s + 1] = y1;
clippedVerticesItems[s + 2] = x2;
clippedVerticesItems[s + 3] = y2;
clippedVerticesItems[s + 4] = x3;
clippedVerticesItems[s + 5] = y3;
clippedUVsItems[s] = u1;
clippedUVsItems[s + 1] = v1;
clippedUVsItems[s + 2] = u2;
clippedUVsItems[s + 3] = v2;
clippedUVsItems[s + 4] = u3;
clippedUVsItems[s + 5] = v3;
s = clippedTriangles.Count;
int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items;
clippedTrianglesItems[s] = index;
clippedTrianglesItems[s + 1] = index + 1;
clippedTrianglesItems[s + 2] = index + 2;
index += 3;
break; //continue outer;
}
}
}
}
/** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping
* area, false is returned. The clipping area must duplicate the first vertex at the end of the vertices list. */
internal bool Clip (float x1, float y1, float x2, float y2, float x3, float y3, ExposedList<float> clippingArea, ExposedList<float> output) {
ExposedList<float> originalOutput = output;
bool clipped = false;
// Avoid copy at the end.
ExposedList<float> input = null;
if (clippingArea.Count % 4 >= 2) {
input = output;
output = scratch;
} else {
input = scratch;
}
input.Clear();
input.Add(x1);
input.Add(y1);
input.Add(x2);
input.Add(y2);
input.Add(x3);
input.Add(y3);
input.Add(x1);
input.Add(y1);
output.Clear();
float[] clippingVertices = clippingArea.Items;
int clippingVerticesLast = clippingArea.Count - 4;
for (int i = 0; ; i += 2) {
float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1];
float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3];
float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2;
float[] inputVertices = input.Items;
int inputVerticesLength = input.Count - 2, outputStart = output.Count;
for (int ii = 0; ii < inputVerticesLength; ii += 2) {
float inputX = inputVertices[ii], inputY = inputVertices[ii + 1];
float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3];
bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0;
if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) {
if (side2) { // v1 inside, v2 inside
output.Add(inputX2);
output.Add(inputY2);
continue;
}
// v1 inside, v2 outside
float c0 = inputY2 - inputY, c2 = inputX2 - inputX;
float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY);
if (Math.Abs(s) > 0.000001f) {
float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s;
output.Add(edgeX + (edgeX2 - edgeX) * ua);
output.Add(edgeY + (edgeY2 - edgeY) * ua);
} else {
output.Add(edgeX);
output.Add(edgeY);
}
} else if (side2) { // v1 outside, v2 inside
float c0 = inputY2 - inputY, c2 = inputX2 - inputX;
float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY);
if (Math.Abs(s) > 0.000001f) {
float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s;
output.Add(edgeX + (edgeX2 - edgeX) * ua);
output.Add(edgeY + (edgeY2 - edgeY) * ua);
} else {
output.Add(edgeX);
output.Add(edgeY);
}
output.Add(inputX2);
output.Add(inputY2);
}
clipped = true;
}
if (outputStart == output.Count) { // All edges outside.
originalOutput.Clear();
return true;
}
output.Add(output.Items[0]);
output.Add(output.Items[1]);
if (i == clippingVerticesLast) break;
ExposedList<float> temp = output;
output = input;
output.Clear();
input = temp;
}
if (originalOutput != output) {
originalOutput.Clear();
for (int i = 0, n = output.Count - 2; i < n; i++)
originalOutput.Add(output.Items[i]);
} else
originalOutput.Resize(originalOutput.Count - 2);
return clipped;
}
public static void MakeClockwise (ExposedList<float> polygon) {
float[] vertices = polygon.Items;
int verticeslength = polygon.Count;
float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y;
for (int i = 0, n = verticeslength - 3; i < n; i += 2) {
p1x = vertices[i];
p1y = vertices[i + 1];
p2x = vertices[i + 2];
p2y = vertices[i + 3];
area += p1x * p2y - p2x * p1y;
}
if (area < 0) return;
for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) {
float x = vertices[i], y = vertices[i + 1];
int other = lastX - i;
vertices[i] = vertices[other];
vertices[i + 1] = vertices[other + 1];
vertices[other] = x;
vertices[other + 1] = y;
}
}
}
}

View File

@@ -0,0 +1,209 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
/// <summary>Stores the setup pose and all of the stateless data for a skeleton.</summary>
public class SkeletonData {
internal string name;
internal ExposedList<BoneData> bones = new ExposedList<BoneData>(); // Ordered parents first
internal ExposedList<SlotData> slots = new ExposedList<SlotData>(); // Setup pose draw order.
internal ExposedList<Skin> skins = new ExposedList<Skin>();
internal Skin defaultSkin;
internal ExposedList<EventData> events = new ExposedList<EventData>();
internal ExposedList<Animation> animations = new ExposedList<Animation>();
internal ExposedList<IkConstraintData> ikConstraints = new ExposedList<IkConstraintData>();
internal ExposedList<TransformConstraintData> transformConstraints = new ExposedList<TransformConstraintData>();
internal ExposedList<PathConstraintData> pathConstraints = new ExposedList<PathConstraintData>();
internal float x, y, width, height;
internal string version, hash;
// Nonessential.
internal float fps;
internal string imagesPath, audioPath;
///<summary>The skeleton's name, which by default is the name of the skeleton data file when possible, or null when a name hasn't been
///set.</summary>
public string Name { get { return name; } set { name = value; } }
/// <summary>The skeleton's bones, sorted parent first. The root bone is always the first bone.</summary>
public ExposedList<BoneData> Bones { get { return bones; } }
public ExposedList<SlotData> Slots { get { return slots; } }
/// <summary>All skins, including the default skin.</summary>
public ExposedList<Skin> Skins { get { return skins; } set { skins = value; } }
/// <summary>
/// The skeleton's default skin.
/// By default this skin contains all attachments that were not in a skin in Spine.
/// </summary>
/// <return>May be null.</return>
public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } }
public ExposedList<EventData> Events { get { return events; } set { events = value; } }
public ExposedList<Animation> Animations { get { return animations; } set { animations = value; } }
public ExposedList<IkConstraintData> IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } }
public ExposedList<TransformConstraintData> TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } }
public ExposedList<PathConstraintData> PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } }
public float X { get { return x; } set { x = value; } }
public float Y { get { return y; } set { y = value; } }
public float Width { get { return width; } set { width = value; } }
public float Height { get { return height; } set { height = value; } }
/// <summary>The Spine version used to export this data, or null.</summary>
public string Version { get { return version; } set { version = value; } }
///<summary>The skeleton data hash. This value will change if any of the skeleton data has changed.
///May be null.</summary>
public string Hash { get { return hash; } set { hash = value; } }
public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } }
/// <summary> The path to the audio directory as defined in Spine. Available only when nonessential data was exported.
/// May be null.</summary>
public string AudioPath { get { return audioPath; } set { audioPath = value; } }
/// <summary>The dopesheet FPS in Spine, or zero if nonessential data was not exported.</summary>
public float Fps { get { return fps; } set { fps = value; } }
// --- Bones
/// <summary>
/// Finds a bone by comparing each bone's name.
/// It is more efficient to cache the results of this method than to call it multiple times.</summary>
/// <returns>May be null.</returns>
public BoneData FindBone (string boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
BoneData[] bones = this.bones.Items;
for (int i = 0, n = this.bones.Count; i < n; i++) {
BoneData bone = bones[i];
if (bone.name == boneName) return bone;
}
return null;
}
// --- Slots
/// <returns>May be null.</returns>
public SlotData FindSlot (string slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
SlotData[] slots = this.slots.Items;
for (int i = 0, n = this.slots.Count; i < n; i++) {
SlotData slot = slots[i];
if (slot.name == slotName) return slot;
}
return null;
}
// --- Skins
/// <returns>May be null.</returns>
public Skin FindSkin (string skinName) {
if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null.");
foreach (Skin skin in skins)
if (skin.name == skinName) return skin;
return null;
}
// --- Events
/// <returns>May be null.</returns>
public EventData FindEvent (string eventDataName) {
if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null.");
foreach (EventData eventData in events)
if (eventData.name == eventDataName) return eventData;
return null;
}
// --- Animations
/// <returns>May be null.</returns>
public Animation FindAnimation (string animationName) {
if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null.");
Animation[] animations = this.animations.Items;
for (int i = 0, n = this.animations.Count; i < n; i++) {
Animation animation = animations[i];
if (animation.name == animationName) return animation;
}
return null;
}
// --- IK constraints
/// <returns>May be null.</returns>
public IkConstraintData FindIkConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
IkConstraintData[] ikConstraints = this.ikConstraints.Items;
for (int i = 0, n = this.ikConstraints.Count; i < n; i++) {
IkConstraintData ikConstraint = ikConstraints[i];
if (ikConstraint.name == constraintName) return ikConstraint;
}
return null;
}
// --- Transform constraints
/// <returns>May be null.</returns>
public TransformConstraintData FindTransformConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
TransformConstraintData[] transformConstraints = this.transformConstraints.Items;
for (int i = 0, n = this.transformConstraints.Count; i < n; i++) {
TransformConstraintData transformConstraint = transformConstraints[i];
if (transformConstraint.name == constraintName) return transformConstraint;
}
return null;
}
// --- Path constraints
/// <summary>
/// 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.
/// </summary>
/// <returns>May be null.</returns>
public PathConstraintData FindPathConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
PathConstraintData[] pathConstraints = this.pathConstraints.Items;
for (int i = 0, n = this.pathConstraints.Count; i < n; i++) {
PathConstraintData constraint = pathConstraints[i];
if (constraint.name.Equals(constraintName)) return constraint;
}
return null;
}
// ---
override public string ToString () {
return name ?? base.ToString();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,92 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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.Collections.Generic;
using System.IO;
namespace SpineRuntime41 {
/// <summary>
/// Base class for loading skeleton data from a file.
/// <para>
/// See<a href="http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data">JSON and binary data</a> in the
/// Spine Runtimes Guide.</para>
/// </summary>
public abstract class SkeletonLoader {
protected readonly AttachmentLoader attachmentLoader;
protected float scale = 1;
protected readonly List<LinkedMesh> linkedMeshes = new List<LinkedMesh>();
/// <summary>Creates a skeleton loader that loads attachments using an <see cref="AtlasAttachmentLoader"/> with the specified atlas.
/// </summary>
public SkeletonLoader (params Atlas[] atlasArray) {
attachmentLoader = new AtlasAttachmentLoader(atlasArray);
}
/// <summary>Creates a skeleton loader that loads attachments using the specified attachment loader.
/// <para>See <a href='http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data'>Loading skeleton data</a> in the
/// Spine Runtimes Guide.</para></summary>
public SkeletonLoader (AttachmentLoader attachmentLoader) {
if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null.");
this.attachmentLoader = attachmentLoader;
}
/// <summary>Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at
/// runtime than were used in Spine.
/// <para>
/// See <a href="http://esotericsoftware.com/spine-loading-skeleton-data#Scaling">Scaling</a> in the Spine Runtimes Guide.</para>
/// </summary>
public float Scale {
get { return scale; }
set {
if (scale == 0) throw new ArgumentNullException("scale", "scale cannot be 0.");
this.scale = value;
}
}
public abstract SkeletonData ReadSkeletonData (string path);
protected class LinkedMesh {
internal string parent, skin;
internal int slotIndex;
internal MeshAttachment mesh;
internal bool inheritTimelines;
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.inheritTimelines = inheritTimelines;
}
}
}
}

View File

@@ -0,0 +1,204 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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.Collections;
using System.Collections.Generic;
namespace SpineRuntime41 {
/// <summary>Stores attachments by slot index and attachment name.
/// <para>See SkeletonData <see cref="Spine.SkeletonData.DefaultSkin"/>, Skeleton <see cref="Spine.Skeleton.Skin"/>, and
/// <a href="http://esotericsoftware.com/spine-runtime-skins">Runtime skins</a> in the Spine Runtimes Guide.</para>
/// </summary>
public class Skin {
internal string name;
// Difference to reference implementation: using Dictionary<SkinKey, SkinEntry> instead of HashSet<SkinEntry>.
// Reason is that there is no efficient way to replace or access an already added element, losing any benefits.
private Dictionary<SkinKey, SkinEntry> attachments = new Dictionary<SkinKey, SkinEntry>(SkinKeyComparer.Instance);
internal readonly ExposedList<BoneData> bones = new ExposedList<BoneData>();
internal readonly ExposedList<ConstraintData> constraints = new ExposedList<ConstraintData>();
public string Name { get { return name; } }
///<summary>Returns all attachments contained in this skin.</summary>
public ICollection<SkinEntry> Attachments { get { return attachments.Values; } }
public ExposedList<BoneData> Bones { get { return bones; } }
public ExposedList<ConstraintData> Constraints { get { return constraints; } }
public Skin (string name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.name = name;
}
/// <summary>Adds an attachment to the skin for the specified slot index and name.
/// If the name already exists for the slot, the previous value is replaced.</summary>
public void SetAttachment (int slotIndex, string name, Attachment attachment) {
if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null.");
attachments[new SkinKey(slotIndex, name)] = new SkinEntry(slotIndex, name, attachment);
}
///<summary>Adds all attachments, bones, and constraints from the specified skin to this skin.</summary>
public void AddSkin (Skin skin) {
foreach (BoneData data in skin.bones)
if (!bones.Contains(data)) bones.Add(data);
foreach (ConstraintData data in skin.constraints)
if (!constraints.Contains(data)) constraints.Add(data);
foreach (KeyValuePair<SkinKey, SkinEntry> item in skin.attachments) {
SkinEntry entry = item.Value;
SetAttachment(entry.slotIndex, entry.name, entry.attachment);
}
}
///<summary>Adds all attachments from the specified skin to this skin. Attachments are deep copied.</summary>
public void CopySkin (Skin skin) {
foreach (BoneData data in skin.bones)
if (!bones.Contains(data)) bones.Add(data);
foreach (ConstraintData data in skin.constraints)
if (!constraints.Contains(data)) constraints.Add(data);
foreach (KeyValuePair<SkinKey, SkinEntry> item in skin.attachments) {
SkinEntry entry = item.Value;
if (entry.attachment is MeshAttachment) {
SetAttachment(entry.slotIndex, entry.name,
entry.attachment != null ? ((MeshAttachment)entry.attachment).NewLinkedMesh() : null);
} else
SetAttachment(entry.slotIndex, entry.name, entry.attachment != null ? entry.attachment.Copy() : null);
}
}
/// <summary>Returns the attachment for the specified slot index and name, or null.</summary>
/// <returns>May be null.</returns>
public Attachment GetAttachment (int slotIndex, string name) {
SkinEntry entry;
bool containsKey = attachments.TryGetValue(new SkinKey(slotIndex, name), out entry);
return containsKey ? entry.attachment : null;
}
/// <summary> Removes the attachment in the skin for the specified slot index and name, if any.</summary>
public void RemoveAttachment (int slotIndex, string name) {
attachments.Remove(new SkinKey(slotIndex, name));
}
/// <summary>Returns all attachments in this skin for the specified slot index.</summary>
/// <param name="slotIndex">The target slotIndex. To find the slot index, use <see cref="Spine.SkeletonData.FindSlot"/> and <see cref="Spine.SlotData.Index"/>.
public void GetAttachments (int slotIndex, List<SkinEntry> attachments) {
if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0.");
if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null.");
foreach (KeyValuePair<SkinKey, SkinEntry> item in this.attachments) {
SkinEntry entry = item.Value;
if (entry.slotIndex == slotIndex) attachments.Add(entry);
}
}
///<summary>Clears all attachments, bones, and constraints.</summary>
public void Clear () {
attachments.Clear();
bones.Clear();
constraints.Clear();
}
override public string ToString () {
return name;
}
/// <summary>Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached.</summary>
internal void AttachAll (Skeleton skeleton, Skin oldSkin) {
Slot[] slots = skeleton.slots.Items;
foreach (KeyValuePair<SkinKey, SkinEntry> item in oldSkin.attachments) {
SkinEntry entry = item.Value;
int slotIndex = entry.slotIndex;
Slot slot = slots[slotIndex];
if (slot.Attachment == entry.attachment) {
Attachment attachment = GetAttachment(slotIndex, entry.name);
if (attachment != null) slot.Attachment = attachment;
}
}
}
/// <summary>Stores an entry in the skin consisting of the slot index, name, and attachment.</summary>
public struct SkinEntry {
internal readonly int slotIndex;
internal readonly string name;
internal readonly Attachment attachment;
public SkinEntry (int slotIndex, string name, Attachment attachment) {
this.slotIndex = slotIndex;
this.name = name;
this.attachment = attachment;
}
public int SlotIndex {
get {
return slotIndex;
}
}
/// <summary>The name the attachment is associated with, equivalent to the skin placeholder name in the Spine editor.</summary>
public String Name {
get {
return name;
}
}
public Attachment Attachment {
get {
return attachment;
}
}
}
private struct SkinKey {
internal readonly int slotIndex;
internal readonly string name;
internal readonly int hashCode;
public SkinKey (int slotIndex, string name) {
if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0.");
if (name == null) throw new ArgumentNullException("name", "name cannot be null");
this.slotIndex = slotIndex;
this.name = name;
this.hashCode = name.GetHashCode() + slotIndex * 37;
}
}
class SkinKeyComparer : IEqualityComparer<SkinKey> {
internal static readonly SkinKeyComparer Instance = new SkinKeyComparer();
bool IEqualityComparer<SkinKey>.Equals (SkinKey e1, SkinKey e2) {
return e1.slotIndex == e2.slotIndex && string.Equals(e1.name, e2.name, StringComparison.Ordinal);
}
int IEqualityComparer<SkinKey>.GetHashCode (SkinKey e) {
return e.hashCode;
}
}
}
}

View File

@@ -0,0 +1,199 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
/// <summary>
/// Stores a slot's current pose. Slots organize attachments for <see cref="Skeleton.DrawOrder"/> purposes and provide a place to store
/// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared
/// across multiple skeletons.
/// </summary>
public class Slot {
internal SlotData data;
internal Bone bone;
internal float r, g, b, a;
internal float r2, g2, b2;
internal bool hasSecondColor;
internal Attachment attachment;
internal int sequenceIndex;
internal ExposedList<float> deform = new ExposedList<float>();
internal int attachmentState;
public Slot (SlotData data, Bone bone) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
this.data = data;
this.bone = bone;
// darkColor = data.darkColor == null ? null : new Color();
if (data.hasSecondColor) {
r2 = g2 = b2 = 0;
}
SetToSetupPose();
}
/// <summary>Copy constructor.</summary>
public Slot (Slot slot, Bone bone) {
if (slot == null) throw new ArgumentNullException("slot", "slot cannot be null.");
if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
data = slot.data;
this.bone = bone;
r = slot.r;
g = slot.g;
b = slot.b;
a = slot.a;
// darkColor = slot.darkColor == null ? null : new Color(slot.darkColor);
if (slot.hasSecondColor) {
r2 = slot.r2;
g2 = slot.g2;
b2 = slot.b2;
} else {
r2 = g2 = b2 = 0;
}
hasSecondColor = slot.hasSecondColor;
attachment = slot.attachment;
sequenceIndex = slot.sequenceIndex;
deform.AddRange(slot.deform);
}
/// <summary>The slot's setup pose data.</summary>
public SlotData Data { get { return data; } }
/// <summary>The bone this slot belongs to.</summary>
public Bone Bone { get { return bone; } }
/// <summary>The skeleton this slot belongs to.</summary>
public Skeleton Skeleton { get { return bone.skeleton; } }
/// <summary>The color used to tint the slot's attachment. If <see cref="HasSecondColor"/> is set, this is used as the light color for two
/// color tinting.</summary>
public float R { get { return r; } set { r = value; } }
/// <summary>The color used to tint the slot's attachment. If <see cref="HasSecondColor"/> is set, this is used as the light color for two
/// color tinting.</summary>
public float G { get { return g; } set { g = value; } }
/// <summary>The color used to tint the slot's attachment. If <see cref="HasSecondColor"/> is set, this is used as the light color for two
/// color tinting.</summary>
public float B { get { return b; } set { b = value; } }
/// <summary>The color used to tint the slot's attachment. If <see cref="HasSecondColor"/> is set, this is used as the light color for two
/// color tinting.</summary>
public float A { get { return a; } set { a = value; } }
public void ClampColor () {
r = MathUtils.Clamp(r, 0, 1);
g = MathUtils.Clamp(g, 0, 1);
b = MathUtils.Clamp(b, 0, 1);
a = MathUtils.Clamp(a, 0, 1);
}
/// <summary>The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used.</summary>
/// <seealso cref="HasSecondColor"/>
public float R2 { get { return r2; } set { r2 = value; } }
/// <summary>The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used.</summary>
/// <seealso cref="HasSecondColor"/>
public float G2 { get { return g2; } set { g2 = value; } }
/// <summary>The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used.</summary>
/// <seealso cref="HasSecondColor"/>
public float B2 { get { return b2; } set { b2 = value; } }
/// <summary>Whether R2 G2 B2 are used to tint the slot's attachment for two color tinting. False if two color tinting is not used.</summary>
public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } }
public void ClampSecondColor () {
r2 = MathUtils.Clamp(r2, 0, 1);
g2 = MathUtils.Clamp(g2, 0, 1);
b2 = MathUtils.Clamp(b2, 0, 1);
}
public Attachment Attachment {
/// <summary>The current attachment for the slot, or null if the slot has no attachment.</summary>
get { return attachment; }
/// <summary>
/// Sets the slot's attachment and, if the attachment changed, resets <see cref="SequenceIndex"/> and clears the <see cref="Deform"/>.
/// The deform is not cleared if the old attachment has the same <see cref="VertexAttachment.TimelineAttachment"/> as the
/// specified attachment.</summary>
/// <param name="value">May be null.</param>
set {
if (attachment == value) return;
if (!(value is VertexAttachment) || !(this.attachment is VertexAttachment)
|| ((VertexAttachment)value).TimelineAttachment != ((VertexAttachment)this.attachment).TimelineAttachment) {
deform.Clear();
}
this.attachment = value;
sequenceIndex = -1;
}
}
/// <summary>
/// The index of the texture region to display when the slot's attachment has a <see cref="Sequence"/>. -1 represents the
/// <see cref="Sequence.SetupIndex"/>.
/// </summary>
public int SequenceIndex { get { return sequenceIndex; } set { sequenceIndex = value; } }
/// <summary> 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.
/// <para />
/// See <see cref="VertexAttachment.ComputeWorldVertices(Slot, int, int, float[], int, int)"/> and <see cref="DeformTimeline"/>.</summary>
public ExposedList<float> Deform {
get {
return deform;
}
set {
if (deform == null) throw new ArgumentNullException("deform", "deform cannot be null.");
deform = value;
}
}
/// <summary>Sets this slot to the setup pose.</summary>
public void SetToSetupPose () {
r = data.r;
g = data.g;
b = data.b;
a = data.a;
// if (darkColor != null) darkColor.set(data.darkColor);
if (HasSecondColor) {
r2 = data.r2;
g2 = data.g2;
b2 = data.b2;
}
if (data.attachmentName == null)
Attachment = null;
else {
attachment = null;
Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName);
}
}
override public string ToString () {
return data.name;
}
}
}

View File

@@ -0,0 +1,77 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
public class SlotData {
internal int index;
internal string name;
internal BoneData boneData;
internal float r = 1, g = 1, b = 1, a = 1;
internal float r2 = 0, g2 = 0, b2 = 0;
internal bool hasSecondColor = false;
internal string attachmentName;
internal BlendMode blendMode;
/// <summary>The index of the slot in <see cref="Skeleton.Slots"/>.</summary>
public int Index { get { return index; } }
/// <summary>The name of the slot, which is unique across all slots in the skeleton.</summary>
public string Name { get { return name; } }
/// <summary>The bone this slot belongs to.</summary>
public BoneData BoneData { get { return boneData; } }
public float R { get { return r; } set { r = value; } }
public float G { get { return g; } set { g = value; } }
public float B { get { return b; } set { b = value; } }
public float A { get { return a; } set { a = value; } }
public float R2 { get { return r2; } set { r2 = value; } }
public float G2 { get { return g2; } set { g2 = value; } }
public float B2 { get { return b2; } set { b2 = value; } }
public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } }
/// <summary>The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible.</summary>
public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } }
/// <summary>The blend mode for drawing the slot's attachment.</summary>
public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } }
public SlotData (int index, String name, BoneData boneData) {
if (index < 0) throw new ArgumentException("index must be >= 0.", "index");
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null.");
this.index = index;
this.name = name;
this.boneData = boneData;
}
override public string ToString () {
return name;
}
}
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<TargetFramework>net8.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>4.1.54</Version>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,49 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
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; } }
}
}

View File

@@ -0,0 +1,311 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
/// <summary>
/// <para>
/// Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained
/// bones to match that of the target bone.</para>
/// <para>
/// See <a href="http://esotericsoftware.com/spine-transform-constraints">Transform constraints</a> in the Spine User Guide.</para>
/// </summary>
public class TransformConstraint : IUpdatable {
internal readonly TransformConstraintData data;
internal readonly ExposedList<Bone> bones;
internal Bone target;
internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY;
internal bool active;
public TransformConstraint (TransformConstraintData 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;
mixRotate = data.mixRotate;
mixX = data.mixX;
mixY = data.mixY;
mixScaleX = data.mixScaleX;
mixScaleY = data.mixScaleY;
mixShearY = data.mixShearY;
bones = new ExposedList<Bone>();
foreach (BoneData boneData in data.bones)
bones.Add(skeleton.bones.Items[boneData.index]);
target = skeleton.bones.Items[data.target.index];
}
/// <summary>Copy constructor.</summary>
public TransformConstraint (TransformConstraint constraint, Skeleton skeleton) {
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<Bone>(constraint.Bones.Count);
foreach (Bone bone in constraint.Bones)
bones.Add(skeleton.Bones.Items[bone.data.index]);
target = skeleton.Bones.Items[constraint.target.data.index];
mixRotate = constraint.mixRotate;
mixX = constraint.mixX;
mixY = constraint.mixY;
mixScaleX = constraint.mixScaleX;
mixScaleY = constraint.mixScaleY;
mixShearY = constraint.mixShearY;
}
public void Update () {
if (mixRotate == 0 && mixX == 0 && mixY == 0 && mixScaleX == 0 && mixScaleY == 0 && mixShearY == 0) return;
if (data.local) {
if (data.relative)
ApplyRelativeLocal();
else
ApplyAbsoluteLocal();
} else {
if (data.relative)
ApplyRelativeWorld();
else
ApplyAbsoluteWorld();
}
}
void ApplyAbsoluteWorld () {
float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
bool translate = mixX != 0 || mixY != 0;
Bone target = this.target;
float ta = target.a, tb = target.b, tc = target.c, td = target.d;
float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect;
Bone[] bones = this.bones.Items;
for (int i = 0, n = this.bones.Count; i < n; i++) {
Bone bone = bones[i];
if (mixRotate != 0) {
float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation;
if (r > MathUtils.PI)
r -= MathUtils.PI2;
else if (r < -MathUtils.PI) //
r += MathUtils.PI2;
r *= mixRotate;
float cos = MathUtils.Cos(r), sin = MathUtils.Sin(r);
bone.a = cos * a - sin * c;
bone.b = cos * b - sin * d;
bone.c = sin * a + cos * c;
bone.d = sin * b + cos * d;
}
if (translate) {
float tx, ty; //Vector2 temp = this.temp;
target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY));
bone.worldX += (tx - bone.worldX) * mixX;
bone.worldY += (ty - bone.worldY) * mixY;
}
if (mixScaleX != 0) {
float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c);
if (s != 0) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * mixScaleX) / s;
bone.a *= s;
bone.c *= s;
}
if (mixScaleY != 0) {
float s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d);
if (s != 0) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * mixScaleY) / s;
bone.b *= s;
bone.d *= s;
}
if (mixShearY > 0) {
float b = bone.b, d = bone.d;
float by = MathUtils.Atan2(d, b);
float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a));
if (r > MathUtils.PI)
r -= MathUtils.PI2;
else if (r < -MathUtils.PI) //
r += MathUtils.PI2;
r = by + (r + offsetShearY) * mixShearY;
float s = (float)Math.Sqrt(b * b + d * d);
bone.b = MathUtils.Cos(r) * s;
bone.d = MathUtils.Sin(r) * s;
}
bone.UpdateAppliedTransform();
}
}
void ApplyRelativeWorld () {
float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
bool translate = mixX != 0 || mixY != 0;
Bone target = this.target;
float ta = target.a, tb = target.b, tc = target.c, td = target.d;
float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect;
Bone[] bones = this.bones.Items;
for (int i = 0, n = this.bones.Count; i < n; i++) {
Bone bone = bones[i];
if (mixRotate != 0) {
float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
float r = MathUtils.Atan2(tc, ta) + offsetRotation;
if (r > MathUtils.PI)
r -= MathUtils.PI2;
else if (r < -MathUtils.PI) //
r += MathUtils.PI2;
r *= mixRotate;
float cos = MathUtils.Cos(r), sin = MathUtils.Sin(r);
bone.a = cos * a - sin * c;
bone.b = cos * b - sin * d;
bone.c = sin * a + cos * c;
bone.d = sin * b + cos * d;
}
if (translate) {
float tx, ty; //Vector2 temp = this.temp;
target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY));
bone.worldX += tx * mixX;
bone.worldY += ty * mixY;
}
if (mixScaleX != 0) {
float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * mixScaleX + 1;
bone.a *= s;
bone.c *= s;
}
if (mixScaleY != 0) {
float s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * mixScaleY + 1;
bone.b *= s;
bone.d *= s;
}
if (mixShearY > 0) {
float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta);
if (r > MathUtils.PI)
r -= MathUtils.PI2;
else if (r < -MathUtils.PI) //
r += MathUtils.PI2;
float b = bone.b, d = bone.d;
r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * mixShearY;
float s = (float)Math.Sqrt(b * b + d * d);
bone.b = MathUtils.Cos(r) * s;
bone.d = MathUtils.Sin(r) * s;
}
bone.UpdateAppliedTransform();
}
}
void ApplyAbsoluteLocal () {
float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
Bone target = this.target;
Bone[] bones = this.bones.Items;
for (int i = 0, n = this.bones.Count; i < n; i++) {
Bone bone = bones[i];
float rotation = bone.arotation;
if (mixRotate != 0) {
float r = target.arotation - rotation + data.offsetRotation;
r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
rotation += r * mixRotate;
}
float x = bone.ax, y = bone.ay;
x += (target.ax - x + data.offsetX) * mixX;
y += (target.ay - y + data.offsetY) * mixY;
float scaleX = bone.ascaleX, scaleY = bone.ascaleY;
if (mixScaleX != 0 && scaleX != 0)
scaleX = (scaleX + (target.ascaleX - scaleX + data.offsetScaleX) * mixScaleX) / scaleX;
if (mixScaleY != 0 && scaleY != 0)
scaleY = (scaleY + (target.ascaleY - scaleY + data.offsetScaleY) * mixScaleY) / scaleY;
float shearY = bone.ashearY;
if (mixShearY != 0) {
float r = target.ashearY - shearY + data.offsetShearY;
r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
shearY += r * mixShearY;
}
bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
}
}
void ApplyRelativeLocal () {
float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
Bone target = this.target;
Bone[] bones = this.bones.Items;
for (int i = 0, n = this.bones.Count; i < n; i++) {
Bone bone = bones[i];
float rotation = bone.arotation + (target.arotation + data.offsetRotation) * mixRotate;
float x = bone.ax + (target.ax + data.offsetX) * mixX;
float y = bone.ay + (target.ay + data.offsetY) * mixY;
float scaleX = bone.ascaleX * (((target.ascaleX - 1 + data.offsetScaleX) * mixScaleX) + 1);
float scaleY = bone.ascaleY * (((target.ascaleY - 1 + data.offsetScaleY) * mixScaleY) + 1);
float shearY = bone.ashearY + (target.ashearY + data.offsetShearY) * mixShearY;
bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
}
}
/// <summary>The bones that will be modified by this transform constraint.</summary>
public ExposedList<Bone> Bones { get { return bones; } }
/// <summary>The target bone whose world transform will be copied to the constrained bones.</summary>
public Bone Target { get { return target; } set { target = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained rotation.</summary>
public float MixRotate { get { return mixRotate; } set { mixRotate = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translation X.</summary>
public float MixX { get { return mixX; } set { mixX = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y.</summary>
public float MixY { get { return mixY; } set { mixY = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained scale X.</summary>
public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y.</summary>
public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y.</summary>
public float MixShearY { get { return mixShearY; } set { mixShearY = value; } }
public bool Active { get { return active; } }
/// <summary>The transform constraint's setup pose data.</summary>
public TransformConstraintData Data { get { return data; } }
override public string ToString () {
return data.name;
}
}
}

View File

@@ -0,0 +1,68 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
public class TransformConstraintData : ConstraintData {
internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
internal BoneData target;
internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY;
internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
internal bool relative, local;
public ExposedList<BoneData> Bones { get { return bones; } }
public BoneData Target { get { return target; } set { target = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained rotation.</summary>
public float MixRotate { get { return mixRotate; } set { mixRotate = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translation X.</summary>
public float MixX { get { return mixX; } set { mixX = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y.</summary>
public float MixY { get { return mixY; } set { mixY = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained scale X.</summary>
public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y.</summary>
public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } }
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y.</summary>
public float MixShearY { get { return mixShearY; } set { mixShearY = value; } }
public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } }
public float OffsetX { get { return offsetX; } set { offsetX = value; } }
public float OffsetY { get { return offsetY; } set { offsetY = value; } }
public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } }
public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } }
public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } }
public bool Relative { get { return relative; } set { relative = value; } }
public bool Local { get { return local; } set { local = value; } }
public TransformConstraintData (string name) : base(name) {
}
}
}

View File

@@ -0,0 +1,275 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 SpineRuntime41 {
public class Triangulator {
private readonly ExposedList<ExposedList<float>> convexPolygons = new ExposedList<ExposedList<float>>();
private readonly ExposedList<ExposedList<int>> convexPolygonsIndices = new ExposedList<ExposedList<int>>();
private readonly ExposedList<int> indicesArray = new ExposedList<int>();
private readonly ExposedList<bool> isConcaveArray = new ExposedList<bool>();
private readonly ExposedList<int> triangles = new ExposedList<int>();
private readonly Pool<ExposedList<float>> polygonPool = new Pool<ExposedList<float>>();
private readonly Pool<ExposedList<int>> polygonIndicesPool = new Pool<ExposedList<int>>();
public ExposedList<int> Triangulate (ExposedList<float> verticesArray) {
float[] vertices = verticesArray.Items;
int vertexCount = verticesArray.Count >> 1;
ExposedList<int> indicesArray = this.indicesArray;
indicesArray.Clear();
int[] indices = indicesArray.Resize(vertexCount).Items;
for (int i = 0; i < vertexCount; i++)
indices[i] = i;
ExposedList<bool> isConcaveArray = this.isConcaveArray;
bool[] isConcave = isConcaveArray.Resize(vertexCount).Items;
for (int i = 0, n = vertexCount; i < n; ++i)
isConcave[i] = IsConcave(i, vertexCount, vertices, indices);
ExposedList<int> triangles = this.triangles;
triangles.Clear();
triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2);
while (vertexCount > 3) {
// Find ear tip.
int previous = vertexCount - 1, i = 0, next = 1;
// outer:
while (true) {
if (!isConcave[i]) {
int p1 = indices[previous] << 1, p2 = indices[i] << 1, p3 = indices[next] << 1;
float p1x = vertices[p1], p1y = vertices[p1 + 1];
float p2x = vertices[p2], p2y = vertices[p2 + 1];
float p3x = vertices[p3], p3y = vertices[p3 + 1];
for (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) {
if (!isConcave[ii]) continue;
int v = indices[ii] << 1;
float vx = vertices[v], vy = vertices[v + 1];
if (PositiveArea(p3x, p3y, p1x, p1y, vx, vy)) {
if (PositiveArea(p1x, p1y, p2x, p2y, vx, vy)) {
if (PositiveArea(p2x, p2y, p3x, p3y, vx, vy)) goto break_outer; // break outer;
}
}
}
break;
}
break_outer:
if (next == 0) {
do {
if (!isConcave[i]) break;
i--;
} while (i > 0);
break;
}
previous = i;
i = next;
next = (next + 1) % vertexCount;
}
// Cut ear tip.
triangles.Add(indices[(vertexCount + i - 1) % vertexCount]);
triangles.Add(indices[i]);
triangles.Add(indices[(i + 1) % vertexCount]);
indicesArray.RemoveAt(i);
isConcaveArray.RemoveAt(i);
vertexCount--;
int previousIndex = (vertexCount + i - 1) % vertexCount;
int nextIndex = i == vertexCount ? 0 : i;
isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices);
isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices);
}
if (vertexCount == 3) {
triangles.Add(indices[2]);
triangles.Add(indices[0]);
triangles.Add(indices[1]);
}
return triangles;
}
public ExposedList<ExposedList<float>> Decompose (ExposedList<float> verticesArray, ExposedList<int> triangles) {
float[] vertices = verticesArray.Items;
ExposedList<ExposedList<float>> convexPolygons = this.convexPolygons;
for (int i = 0, n = convexPolygons.Count; i < n; i++)
polygonPool.Free(convexPolygons.Items[i]);
convexPolygons.Clear();
ExposedList<ExposedList<int>> convexPolygonsIndices = this.convexPolygonsIndices;
for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++)
polygonIndicesPool.Free(convexPolygonsIndices.Items[i]);
convexPolygonsIndices.Clear();
ExposedList<int> polygonIndices = polygonIndicesPool.Obtain();
polygonIndices.Clear();
ExposedList<float> polygon = polygonPool.Obtain();
polygon.Clear();
// Merge subsequent triangles if they form a triangle fan.
int fanBaseIndex = -1, lastWinding = 0;
int[] trianglesItems = triangles.Items;
for (int i = 0, n = triangles.Count; i < n; i += 3) {
int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[i + 2] << 1;
float x1 = vertices[t1], y1 = vertices[t1 + 1];
float x2 = vertices[t2], y2 = vertices[t2 + 1];
float x3 = vertices[t3], y3 = vertices[t3 + 1];
// If the base of the last triangle is the same as this triangle, check if they form a convex polygon (triangle fan).
bool merged = false;
if (fanBaseIndex == t1) {
int o = polygon.Count - 4;
float[] p = polygon.Items;
int winding1 = Winding(p[o], p[o + 1], p[o + 2], p[o + 3], x3, y3);
int winding2 = Winding(x3, y3, p[0], p[1], p[2], p[3]);
if (winding1 == lastWinding && winding2 == lastWinding) {
polygon.Add(x3);
polygon.Add(y3);
polygonIndices.Add(t3);
merged = true;
}
}
// Otherwise make this triangle the new base.
if (!merged) {
if (polygon.Count > 0) {
convexPolygons.Add(polygon);
convexPolygonsIndices.Add(polygonIndices);
} else {
polygonPool.Free(polygon);
polygonIndicesPool.Free(polygonIndices);
}
polygon = polygonPool.Obtain();
polygon.Clear();
polygon.Add(x1);
polygon.Add(y1);
polygon.Add(x2);
polygon.Add(y2);
polygon.Add(x3);
polygon.Add(y3);
polygonIndices = polygonIndicesPool.Obtain();
polygonIndices.Clear();
polygonIndices.Add(t1);
polygonIndices.Add(t2);
polygonIndices.Add(t3);
lastWinding = Winding(x1, y1, x2, y2, x3, y3);
fanBaseIndex = t1;
}
}
if (polygon.Count > 0) {
convexPolygons.Add(polygon);
convexPolygonsIndices.Add(polygonIndices);
}
// Go through the list of polygons and try to merge the remaining triangles with the found triangle fans.
for (int i = 0, n = convexPolygons.Count; i < n; i++) {
polygonIndices = convexPolygonsIndices.Items[i];
if (polygonIndices.Count == 0) continue;
int firstIndex = polygonIndices.Items[0];
int lastIndex = polygonIndices.Items[polygonIndices.Count - 1];
polygon = convexPolygons.Items[i];
int o = polygon.Count - 4;
float[] p = polygon.Items;
float prevPrevX = p[o], prevPrevY = p[o + 1];
float prevX = p[o + 2], prevY = p[o + 3];
float firstX = p[0], firstY = p[1];
float secondX = p[2], secondY = p[3];
int winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY);
for (int ii = 0; ii < n; ii++) {
if (ii == i) continue;
ExposedList<int> otherIndices = convexPolygonsIndices.Items[ii];
if (otherIndices.Count != 3) continue;
int otherFirstIndex = otherIndices.Items[0];
int otherSecondIndex = otherIndices.Items[1];
int otherLastIndex = otherIndices.Items[2];
ExposedList<float> otherPoly = convexPolygons.Items[ii];
float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 1];
if (otherFirstIndex != firstIndex || otherSecondIndex != lastIndex) continue;
int winding1 = Winding(prevPrevX, prevPrevY, prevX, prevY, x3, y3);
int winding2 = Winding(x3, y3, firstX, firstY, secondX, secondY);
if (winding1 == winding && winding2 == winding) {
otherPoly.Clear();
otherIndices.Clear();
polygon.Add(x3);
polygon.Add(y3);
polygonIndices.Add(otherLastIndex);
prevPrevX = prevX;
prevPrevY = prevY;
prevX = x3;
prevY = y3;
ii = 0;
}
}
}
// Remove empty polygons that resulted from the merge step above.
for (int i = convexPolygons.Count - 1; i >= 0; i--) {
polygon = convexPolygons.Items[i];
if (polygon.Count == 0) {
convexPolygons.RemoveAt(i);
polygonPool.Free(polygon);
polygonIndices = convexPolygonsIndices.Items[i];
convexPolygonsIndices.RemoveAt(i);
polygonIndicesPool.Free(polygonIndices);
}
}
return convexPolygons;
}
static private bool IsConcave (int index, int vertexCount, float[] vertices, int[] indices) {
int previous = indices[(vertexCount + index - 1) % vertexCount] << 1;
int current = indices[index] << 1;
int next = indices[(index + 1) % vertexCount] << 1;
return !PositiveArea(vertices[previous], vertices[previous + 1], vertices[current], vertices[current + 1], vertices[next],
vertices[next + 1]);
}
static private bool PositiveArea (float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) {
return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0;
}
static private int Winding (float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) {
float px = p2x - p1x, py = p2y - p1y;
return p3x * py - p3y * px + px * p1y - p1x * py >= 0 ? 1 : -1;
}
}
}

View File

@@ -0,0 +1,340 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SpineRuntime41;
namespace SpineViewer.Spine.Implementations
{
[SpineImplementation(Version.V41)]
internal class Spine41 : Spine
{
private class TextureLoader : SpineRuntime41.TextureLoader
{
public void Load(AtlasPage page, string path)
{
var texture = new SFML.Graphics.Texture(path);
if (page.magFilter == TextureFilter.Linear)
texture.Smooth = true;
if (page.uWrap == TextureWrap.Repeat && page.vWrap == TextureWrap.Repeat)
texture.Repeated = true;
page.rendererObject = texture;
page.width = (int)texture.Size.X;
page.height = (int)texture.Size.Y;
}
public void Unload(object texture)
{
((SFML.Graphics.Texture)texture).Dispose();
}
}
private static TextureLoader textureLoader = new();
private Atlas atlas;
private SkeletonBinary? skeletonBinary;
private SkeletonJson? skeletonJson;
private SkeletonData skeletonData;
private AnimationStateData animationStateData;
private Skeleton skeleton;
private AnimationState animationState;
private SkeletonClipping clipping = new();
public Spine41(string skelPath, string? atlasPath = null) : base(skelPath, atlasPath)
{
atlas = new Atlas(AtlasPath, textureLoader);
try
{
// 先尝试二进制文件
skeletonJson = null;
skeletonBinary = new SkeletonBinary(atlas);
skeletonData = skeletonBinary.ReadSkeletonData(SkelPath);
}
catch
{
try
{
// 再尝试 Json 文件
skeletonBinary = null;
skeletonJson = new SkeletonJson(atlas);
skeletonData = skeletonJson.ReadSkeletonData(SkelPath);
}
catch
{
// 都不行就报错
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
}
}
animationStateData = new AnimationStateData(skeletonData);
skeleton = new Skeleton(skeletonData);
animationState = new AnimationState(animationStateData);
foreach (var anime in skeletonData.Animations)
animationNames.Add(anime.Name);
CurrentAnimation = DefaultAnimationName;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
atlas.Dispose();
}
public override float Scale
{
get
{
if (skeletonBinary is not null)
return skeletonBinary.Scale;
else if (skeletonJson is not null)
return skeletonJson.Scale;
else
return 1f;
}
set
{
// 保存状态
var position = Position;
var flipX = FlipX;
var flipY = FlipY;
var savedTrack0 = animationState.GetCurrent(0);
var val = Math.Max(value, SCALE_MIN);
if (skeletonBinary is not null)
{
skeletonBinary.Scale = val;
skeletonData = skeletonBinary.ReadSkeletonData(SkelPath);
}
else if (skeletonJson is not null)
{
skeletonJson.Scale = val;
skeletonData = skeletonJson.ReadSkeletonData(SkelPath);
}
// reload skel-dependent data
animationStateData = new AnimationStateData(skeletonData) { DefaultMix = animationStateData.DefaultMix };
skeleton = new Skeleton(skeletonData);
animationState = new AnimationState(animationStateData);
// 恢复状态
Position = position;
FlipX = flipX;
FlipY = flipY;
// 恢复原本 Track0 上所有动画
if (savedTrack0 is not null)
{
var entry = animationState.SetAnimation(0, savedTrack0.Animation.Name, true);
entry.TrackTime = savedTrack0.TrackTime;
var savedEntry = savedTrack0.Next;
while (savedEntry is not null)
{
entry = animationState.AddAnimation(0, savedEntry.Animation.Name, true, 0);
entry.TrackTime = savedEntry.TrackTime;
savedEntry = savedEntry.Next;
}
}
}
}
public override PointF Position
{
get => new(skeleton.X, skeleton.Y);
set
{
skeleton.X = value.X;
skeleton.Y = value.Y;
}
}
public override bool FlipX
{
get => skeleton.ScaleX < 0;
set
{
if (skeleton.ScaleX > 0 && value || skeleton.ScaleX < 0 && !value)
skeleton.ScaleX *= -1;
}
}
public override bool FlipY
{
get => skeleton.ScaleY < 0;
set
{
if (skeleton.ScaleY > 0 && value || skeleton.ScaleY < 0 && !value)
skeleton.ScaleY *= -1;
}
}
public override string CurrentAnimation
{
get => animationState.GetCurrent(0)?.Animation.Name ?? DefaultAnimationName;
set { if (animationNames.Contains(value)) { animationState.SetAnimation(0, value, true); Update(0); } }
}
public override RectangleF Bounds
{
get
{
float[] _ = [];
skeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
return new RectangleF(x, y, w, h);
}
}
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
public override void Update(float delta)
{
//skeleton.Update(delta); // XXX: v4.1.x 没有 Update 方法
animationState.Update(delta);
animationState.Apply(skeleton);
skeleton.UpdateWorldTransform();
}
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime41.BlendMode spineBlendMode)
{
return spineBlendMode switch
{
SpineRuntime41.BlendMode.Normal => BlendMode.Normal,
SpineRuntime41.BlendMode.Additive => BlendMode.Additive,
SpineRuntime41.BlendMode.Multiply => BlendMode.Multiply,
SpineRuntime41.BlendMode.Screen => BlendMode.Screen,
_ => throw new NotImplementedException($"{spineBlendMode}"),
};
}
public override void Draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states)
{
vertexArray.Clear();
states.Texture = null;
// 要用 DrawOrder 而不是 Slots
foreach (var slot in skeleton.DrawOrder)
{
var attachment = slot.Attachment;
SFML.Graphics.Texture texture;
float[] worldVertices = worldVerticesBuffer; // 顶点世界坐标, 连续的 [x0, y0, x1, y1, ...] 坐标值
int worldVerticesCount; // 等于顶点数组的长度除以 2
int[] worldTriangleIndices; // 三角形索引, 从顶点坐标数组取的时候要乘以 2, 最大值是 worldVerticesCount - 1
int worldTriangleIndicesLength; // 三角形索引数组长度
float[] uvs; // 纹理坐标
float tintR = skeleton.R * slot.R;
float tintG = skeleton.G * slot.G;
float tintB = skeleton.B * slot.B;
float tintA = skeleton.A * slot.A;
if (attachment is RegionAttachment regionAttachment)
{
texture = (SFML.Graphics.Texture)((AtlasRegion)regionAttachment.Region).page.rendererObject;
regionAttachment.ComputeWorldVertices(slot, worldVertices, 0);
worldVerticesCount = 4;
worldTriangleIndices = [0, 1, 2, 2, 3, 0];
worldTriangleIndicesLength = 6;
uvs = regionAttachment.UVs;
tintR *= regionAttachment.R;
tintG *= regionAttachment.G;
tintB *= regionAttachment.B;
tintA *= regionAttachment.A;
}
else if (attachment is MeshAttachment meshAttachment)
{
texture = (SFML.Graphics.Texture)((AtlasRegion)meshAttachment.Region).page.rendererObject;
if (meshAttachment.WorldVerticesLength > worldVertices.Length)
worldVertices = worldVerticesBuffer = new float[meshAttachment.WorldVerticesLength * 2];
meshAttachment.ComputeWorldVertices(slot, worldVertices);
worldVerticesCount = meshAttachment.WorldVerticesLength / 2;
worldTriangleIndices = meshAttachment.Triangles;
worldTriangleIndicesLength = meshAttachment.Triangles.Length;
uvs = meshAttachment.UVs;
tintR *= meshAttachment.R;
tintG *= meshAttachment.G;
tintB *= meshAttachment.B;
tintA *= meshAttachment.A;
}
else if (attachment is ClippingAttachment clippingAttachment)
{
clipping.ClipStart(slot, clippingAttachment);
continue;
}
else
{
clipping.ClipEnd(slot);
continue;
}
SFML.Graphics.BlendMode blendMode = GetSFMLBlendMode(slot.Data.BlendMode);
states.Texture ??= texture;
if (states.BlendMode != blendMode || states.Texture != texture)
{
if (vertexArray.VertexCount > 0)
{
// XXX: 实测不用设置 sampler2D 的值也正确
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
vertexArray.Clear();
}
states.BlendMode = blendMode;
states.Texture = texture;
}
if (clipping.IsClipping)
{
// 这里必须单独记录 Count, 和 Items 的 Length 是不一致的
clipping.ClipTriangles(worldVertices, worldVerticesCount * 2, worldTriangleIndices, worldTriangleIndicesLength, uvs);
worldVertices = clipping.ClippedVertices.Items;
worldVerticesCount = clipping.ClippedVertices.Count / 2;
worldTriangleIndices = clipping.ClippedTriangles.Items;
worldTriangleIndicesLength = clipping.ClippedTriangles.Count;
uvs = clipping.ClippedUVs.Items;
}
var textureSizeX = texture.Size.X;
var textureSizeY = texture.Size.Y;
SFML.Graphics.Vertex vertex = new();
vertex.Color.R = (byte)(tintR * 255);
vertex.Color.G = (byte)(tintG * 255);
vertex.Color.B = (byte)(tintB * 255);
vertex.Color.A = (byte)(tintA * 255);
// 必须用 worldTriangleIndicesLength 不能直接 foreach
for (int i = 0; i < worldTriangleIndicesLength; i++)
{
var index = worldTriangleIndices[i] * 2;
vertex.Position.X = worldVertices[index];
vertex.Position.Y = worldVertices[index + 1];
vertex.TexCoords.X = uvs[index] * textureSizeX;
vertex.TexCoords.Y = uvs[index + 1] * textureSizeY;
vertexArray.Append(vertex);
}
clipping.ClipEnd(slot);
}
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
clipping.ClipEnd();
}
}
}