增加spine4.0
This commit is contained in:
2586
SpineRuntimes/SpineRuntime40/Animation.cs
Normal file
2586
SpineRuntimes/SpineRuntime40/Animation.cs
Normal file
File diff suppressed because it is too large
Load Diff
1443
SpineRuntimes/SpineRuntime40/AnimationState.cs
Normal file
1443
SpineRuntimes/SpineRuntime40/AnimationState.cs
Normal file
File diff suppressed because it is too large
Load Diff
114
SpineRuntimes/SpineRuntime40/AnimationStateData.cs
Normal file
114
SpineRuntimes/SpineRuntime40/AnimationStateData.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
|
||||
/// <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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
364
SpineRuntimes/SpineRuntime40/Atlas.cs
Normal file
364
SpineRuntimes/SpineRuntime40/Atlas.cs
Normal file
@@ -0,0 +1,364 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
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 (var 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;
|
||||
|
||||
var 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";
|
||||
});
|
||||
|
||||
var 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;
|
||||
} 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 {
|
||||
public AtlasPage page;
|
||||
public string name;
|
||||
public int x, y, width, height;
|
||||
public float u, v, u2, v2;
|
||||
public float offsetX, offsetY;
|
||||
public int originalWidth, originalHeight;
|
||||
public int degrees;
|
||||
public bool rotate;
|
||||
public int index;
|
||||
public string[] names;
|
||||
public int[][] values;
|
||||
|
||||
public AtlasRegion Clone () {
|
||||
return MemberwiseClone() as AtlasRegion;
|
||||
}
|
||||
}
|
||||
|
||||
public interface TextureLoader {
|
||||
void Load (AtlasPage page, string path);
|
||||
void Unload (Object texture);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) {
|
||||
AtlasRegion region = FindRegion(path);
|
||||
if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name));
|
||||
RegionAttachment attachment = new RegionAttachment(name);
|
||||
attachment.RendererObject = region;
|
||||
attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.degrees);
|
||||
attachment.regionOffsetX = region.offsetX;
|
||||
attachment.regionOffsetY = region.offsetY;
|
||||
attachment.regionWidth = region.width;
|
||||
attachment.regionHeight = region.height;
|
||||
attachment.regionOriginalWidth = region.originalWidth;
|
||||
attachment.regionOriginalHeight = region.originalHeight;
|
||||
return attachment;
|
||||
}
|
||||
|
||||
public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) {
|
||||
AtlasRegion region = FindRegion(path);
|
||||
if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name));
|
||||
MeshAttachment attachment = new MeshAttachment(name);
|
||||
attachment.RendererObject = region;
|
||||
attachment.RegionU = region.u;
|
||||
attachment.RegionV = region.v;
|
||||
attachment.RegionU2 = region.u2;
|
||||
attachment.RegionV2 = region.v2;
|
||||
attachment.RegionDegrees = region.degrees;
|
||||
attachment.regionOffsetX = region.offsetX;
|
||||
attachment.regionOffsetY = region.offsetY;
|
||||
attachment.regionWidth = region.width;
|
||||
attachment.regionHeight = region.height;
|
||||
attachment.regionOriginalWidth = region.originalWidth;
|
||||
attachment.regionOriginalHeight = region.originalHeight;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
52
SpineRuntimes/SpineRuntime40/Attachments/Attachment.cs
Normal file
52
SpineRuntimes/SpineRuntime40/Attachments/Attachment.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
abstract public class Attachment {
|
||||
public string Name { get; private set; }
|
||||
|
||||
protected Attachment (string name) {
|
||||
if (name == null) throw new ArgumentNullException("name", "name cannot be null");
|
||||
Name = name;
|
||||
}
|
||||
|
||||
override public string ToString () {
|
||||
return Name;
|
||||
}
|
||||
|
||||
///<summary>Returns a copy of the attachment.</summary>
|
||||
public abstract Attachment Copy ();
|
||||
}
|
||||
|
||||
public interface IHasRendererObject {
|
||||
object RendererObject { get; set; }
|
||||
}
|
||||
}
|
||||
48
SpineRuntimes/SpineRuntime40/Attachments/AttachmentLoader.cs
Normal file
48
SpineRuntimes/SpineRuntime40/Attachments/AttachmentLoader.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
public interface AttachmentLoader {
|
||||
/// <return>May be null to not load any attachment.</return>
|
||||
RegionAttachment NewRegionAttachment (Skin skin, string name, string path);
|
||||
|
||||
/// <return>May be null to not load any attachment.</return>
|
||||
MeshAttachment NewMeshAttachment (Skin skin, string name, string path);
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
34
SpineRuntimes/SpineRuntime40/Attachments/AttachmentType.cs
Normal file
34
SpineRuntimes/SpineRuntime40/Attachments/AttachmentType.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
public enum AttachmentType {
|
||||
Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
/// <summary>Attachment that has a polygon for bounds checking.</summary>
|
||||
public class BoundingBoxAttachment : VertexAttachment {
|
||||
public BoundingBoxAttachment (string name)
|
||||
: base(name) {
|
||||
}
|
||||
|
||||
public override Attachment Copy () {
|
||||
BoundingBoxAttachment copy = new BoundingBoxAttachment(this.Name);
|
||||
CopyTo(copy);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
public class ClippingAttachment : VertexAttachment {
|
||||
internal SlotData endSlot;
|
||||
|
||||
public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } }
|
||||
|
||||
public ClippingAttachment (string name) : base(name) {
|
||||
}
|
||||
|
||||
public override Attachment Copy () {
|
||||
ClippingAttachment copy = new ClippingAttachment(this.Name);
|
||||
CopyTo(copy);
|
||||
copy.endSlot = endSlot;
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
221
SpineRuntimes/SpineRuntime40/Attachments/MeshAttachment.cs
Normal file
221
SpineRuntimes/SpineRuntime40/Attachments/MeshAttachment.cs
Normal file
@@ -0,0 +1,221 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
/// <summary>Attachment that displays a texture region using a mesh.</summary>
|
||||
public class MeshAttachment : VertexAttachment, IHasRendererObject {
|
||||
internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight;
|
||||
private MeshAttachment parentMesh;
|
||||
internal float[] uvs, regionUVs;
|
||||
internal int[] triangles;
|
||||
internal float r = 1, g = 1, b = 1, a = 1;
|
||||
internal int hulllength;
|
||||
|
||||
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.UpdateUVs"/></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; set; }
|
||||
public object RendererObject { get; set; }
|
||||
public float RegionU { get; set; }
|
||||
public float RegionV { get; set; }
|
||||
public float RegionU2 { get; set; }
|
||||
public float RegionV2 { get; set; }
|
||||
public int RegionDegrees { get; set; }
|
||||
public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } }
|
||||
public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated.
|
||||
public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } }
|
||||
public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size.
|
||||
public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } }
|
||||
public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size.
|
||||
|
||||
public 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) {
|
||||
}
|
||||
|
||||
public void UpdateUVs () {
|
||||
float[] regionUVs = this.regionUVs;
|
||||
if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length];
|
||||
float[] uvs = this.uvs;
|
||||
float u = RegionU, v = RegionV, width = 0, height = 0;
|
||||
|
||||
if (RegionDegrees == 90) {
|
||||
float textureHeight = this.regionWidth / (RegionV2 - RegionV);
|
||||
float textureWidth = this.regionHeight / (RegionU2 - RegionU);
|
||||
u -= (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureWidth;
|
||||
v -= (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureHeight;
|
||||
width = RegionOriginalHeight / textureWidth;
|
||||
height = RegionOriginalWidth / textureHeight;
|
||||
|
||||
for (int i = 0, n = uvs.Length; i < n; i += 2) {
|
||||
uvs[i] = u + regionUVs[i + 1] * width;
|
||||
uvs[i + 1] = v + (1 - regionUVs[i]) * height;
|
||||
}
|
||||
} else if (RegionDegrees == 180) {
|
||||
float textureWidth = this.regionWidth / (RegionU2 - RegionU);
|
||||
float textureHeight = this.regionHeight / (RegionV2 - RegionV);
|
||||
u -= (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureWidth;
|
||||
v -= RegionOffsetY / textureHeight;
|
||||
width = RegionOriginalWidth / textureWidth;
|
||||
height = RegionOriginalHeight / textureHeight;
|
||||
|
||||
for (int i = 0, n = uvs.Length; i < n; i += 2) {
|
||||
uvs[i] = u + (1 - regionUVs[i]) * width;
|
||||
uvs[i + 1] = v + (1 - regionUVs[i + 1]) * height;
|
||||
}
|
||||
} else if (RegionDegrees == 270) {
|
||||
float textureWidth = this.regionWidth / (RegionU2 - RegionU);
|
||||
float textureHeight = this.regionHeight / (RegionV2 - RegionV);
|
||||
u -= RegionOffsetY / textureWidth;
|
||||
v -= RegionOffsetX / textureHeight;
|
||||
width = RegionOriginalHeight / textureWidth;
|
||||
height = RegionOriginalWidth / textureHeight;
|
||||
|
||||
for (int i = 0, n = uvs.Length; i < n; i += 2) {
|
||||
uvs[i] = u + (1 - regionUVs[i + 1]) * width;
|
||||
uvs[i + 1] = v + regionUVs[i] * height;
|
||||
}
|
||||
} else {
|
||||
float textureWidth = this.regionWidth / (RegionU2 - RegionU);
|
||||
float textureHeight = this.regionHeight / (RegionV2 - RegionV);
|
||||
u -= RegionOffsetX / textureWidth;
|
||||
v -= (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureHeight;
|
||||
width = RegionOriginalWidth / textureWidth;
|
||||
height = RegionOriginalHeight / textureHeight;
|
||||
|
||||
for (int i = 0, n = uvs.Length; i < n; i += 2) {
|
||||
uvs[i] = u + regionUVs[i] * width;
|
||||
uvs[i + 1] = v + regionUVs[i + 1] * height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override Attachment Copy () {
|
||||
if (parentMesh != null) return NewLinkedMesh();
|
||||
|
||||
MeshAttachment copy = new MeshAttachment(this.Name);
|
||||
copy.RendererObject = RendererObject;
|
||||
copy.regionOffsetX = regionOffsetX;
|
||||
copy.regionOffsetY = regionOffsetY;
|
||||
copy.regionWidth = regionWidth;
|
||||
copy.regionHeight = regionHeight;
|
||||
copy.regionOriginalWidth = regionOriginalWidth;
|
||||
copy.regionOriginalHeight = regionOriginalHeight;
|
||||
copy.RegionDegrees = RegionDegrees;
|
||||
copy.RegionU = RegionU;
|
||||
copy.RegionV = RegionV;
|
||||
copy.RegionU2 = RegionU2;
|
||||
copy.RegionV2 = RegionV2;
|
||||
|
||||
copy.Path = Path;
|
||||
copy.r = r;
|
||||
copy.g = g;
|
||||
copy.b = b;
|
||||
copy.a = a;
|
||||
|
||||
CopyTo(copy);
|
||||
copy.regionUVs = new float[regionUVs.Length];
|
||||
Array.Copy(regionUVs, 0, copy.regionUVs, 0, regionUVs.Length);
|
||||
copy.uvs = new float[uvs.Length];
|
||||
Array.Copy(uvs, 0, copy.uvs, 0, uvs.Length);
|
||||
copy.triangles = new int[triangles.Length];
|
||||
Array.Copy(triangles, 0, copy.triangles, 0, triangles.Length);
|
||||
copy.HullLength = HullLength;
|
||||
|
||||
// Nonessential.
|
||||
if (Edges != null) {
|
||||
copy.Edges = new int[Edges.Length];
|
||||
Array.Copy(Edges, 0, copy.Edges, 0, Edges.Length);
|
||||
}
|
||||
copy.Width = Width;
|
||||
copy.Height = Height;
|
||||
return copy;
|
||||
}
|
||||
|
||||
///<summary>Returns a new mesh with this mesh set as the <see cref="ParentMesh"/>.
|
||||
public MeshAttachment NewLinkedMesh () {
|
||||
MeshAttachment mesh = new MeshAttachment(Name);
|
||||
mesh.RendererObject = RendererObject;
|
||||
mesh.regionOffsetX = regionOffsetX;
|
||||
mesh.regionOffsetY = regionOffsetY;
|
||||
mesh.regionWidth = regionWidth;
|
||||
mesh.regionHeight = regionHeight;
|
||||
mesh.regionOriginalWidth = regionOriginalWidth;
|
||||
mesh.regionOriginalHeight = regionOriginalHeight;
|
||||
mesh.RegionDegrees = RegionDegrees;
|
||||
mesh.RegionU = RegionU;
|
||||
mesh.RegionV = RegionV;
|
||||
mesh.RegionU2 = RegionU2;
|
||||
mesh.RegionV2 = RegionV2;
|
||||
|
||||
mesh.Path = Path;
|
||||
mesh.r = r;
|
||||
mesh.g = g;
|
||||
mesh.b = b;
|
||||
mesh.a = a;
|
||||
|
||||
mesh.deformAttachment = deformAttachment;
|
||||
mesh.ParentMesh = parentMesh != null ? parentMesh : this;
|
||||
mesh.UpdateUVs();
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
SpineRuntimes/SpineRuntime40/Attachments/PathAttachment.cs
Normal file
60
SpineRuntimes/SpineRuntime40/Attachments/PathAttachment.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
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) {
|
||||
}
|
||||
|
||||
public override Attachment Copy () {
|
||||
PathAttachment copy = new PathAttachment(this.Name);
|
||||
CopyTo(copy);
|
||||
copy.lengths = new float[lengths.Length];
|
||||
Array.Copy(lengths, 0, copy.lengths, 0, lengths.Length);
|
||||
copy.closed = closed;
|
||||
copy.constantSpeed = constantSpeed;
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
SpineRuntimes/SpineRuntime40/Attachments/PointAttachment.cs
Normal file
67
SpineRuntimes/SpineRuntime40/Attachments/PointAttachment.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
/// <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) {
|
||||
}
|
||||
|
||||
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 () {
|
||||
PointAttachment copy = new PointAttachment(this.Name);
|
||||
copy.x = x;
|
||||
copy.y = y;
|
||||
copy.rotation = rotation;
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
194
SpineRuntimes/SpineRuntime40/Attachments/RegionAttachment.cs
Normal file
194
SpineRuntimes/SpineRuntime40/Attachments/RegionAttachment.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
/// <summary>Attachment that displays a texture region.</summary>
|
||||
public class RegionAttachment : Attachment, IHasRendererObject {
|
||||
public const int BLX = 0;
|
||||
public const int BLY = 1;
|
||||
public const int ULX = 2;
|
||||
public const int ULY = 3;
|
||||
public const int URX = 4;
|
||||
public const int URY = 5;
|
||||
public const int BRX = 6;
|
||||
public const int BRY = 7;
|
||||
|
||||
internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height;
|
||||
internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight;
|
||||
internal float[] offset = new float[8], uvs = new float[8];
|
||||
internal float r = 1, g = 1, b = 1, a = 1;
|
||||
|
||||
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 object RendererObject { get; set; }
|
||||
public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } }
|
||||
public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated.
|
||||
public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } }
|
||||
public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size.
|
||||
public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } }
|
||||
public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size.
|
||||
|
||||
public float[] Offset { get { return offset; } }
|
||||
public float[] UVs { get { return uvs; } }
|
||||
|
||||
public RegionAttachment (string name)
|
||||
: base(name) {
|
||||
}
|
||||
|
||||
public void UpdateOffset () {
|
||||
float regionScaleX = width / regionOriginalWidth * scaleX;
|
||||
float regionScaleY = height / regionOriginalHeight * scaleY;
|
||||
float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX;
|
||||
float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY;
|
||||
float localX2 = localX + regionWidth * regionScaleX;
|
||||
float localY2 = localY + regionHeight * regionScaleY;
|
||||
float cos = MathUtils.CosDeg(this.rotation);
|
||||
float sin = MathUtils.SinDeg(this.rotation);
|
||||
float x = this.x, y = this.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;
|
||||
}
|
||||
|
||||
public void SetUVs (float u, float v, float u2, float v2, int degrees) {
|
||||
float[] uvs = this.uvs;
|
||||
// UV values differ from spine-libgdx.
|
||||
if (degrees == 90) {
|
||||
uvs[URX] = u;
|
||||
uvs[URY] = v2;
|
||||
uvs[BRX] = u;
|
||||
uvs[BRY] = v;
|
||||
uvs[BLX] = u2;
|
||||
uvs[BLY] = v;
|
||||
uvs[ULX] = u2;
|
||||
uvs[ULY] = v2;
|
||||
} else {
|
||||
uvs[ULX] = u;
|
||||
uvs[ULY] = v2;
|
||||
uvs[URX] = u;
|
||||
uvs[URY] = v;
|
||||
uvs[BRX] = u2;
|
||||
uvs[BRY] = v;
|
||||
uvs[BLX] = u2;
|
||||
uvs[BLY] = v2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Transforms the attachment's four vertices to world coordinates.</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 (Bone bone, float[] worldVertices, int offset, int stride = 2) {
|
||||
float[] vertexOffset = this.offset;
|
||||
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 () {
|
||||
RegionAttachment copy = new RegionAttachment(this.Name);
|
||||
copy.RendererObject = RendererObject;
|
||||
copy.regionOffsetX = regionOffsetX;
|
||||
copy.regionOffsetY = regionOffsetY;
|
||||
copy.regionWidth = regionWidth;
|
||||
copy.regionHeight = regionHeight;
|
||||
copy.regionOriginalWidth = regionOriginalWidth;
|
||||
copy.regionOriginalHeight = regionOriginalHeight;
|
||||
copy.Path = Path;
|
||||
copy.x = x;
|
||||
copy.y = y;
|
||||
copy.scaleX = scaleX;
|
||||
copy.scaleY = scaleY;
|
||||
copy.rotation = rotation;
|
||||
copy.width = width;
|
||||
copy.height = height;
|
||||
Array.Copy(uvs, 0, copy.uvs, 0, 8);
|
||||
Array.Copy(offset, 0, copy.offset, 0, 8);
|
||||
copy.r = r;
|
||||
copy.g = g;
|
||||
copy.b = b;
|
||||
copy.a = a;
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
153
SpineRuntimes/SpineRuntime40/Attachments/VertexAttachment.cs
Normal file
153
SpineRuntimes/SpineRuntime40/Attachments/VertexAttachment.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
/// <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 int[] bones;
|
||||
internal float[] vertices;
|
||||
internal int worldVerticesLength;
|
||||
internal VertexAttachment deformAttachment;
|
||||
|
||||
/// <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>Deform keys for the deform attachment are also applied to this attachment.
|
||||
/// May be null if no deform keys should be applied.</summary>
|
||||
public VertexAttachment DeformAttachment { get { return deformAttachment; } set { deformAttachment = value; } }
|
||||
|
||||
public VertexAttachment (string name)
|
||||
: base(name) {
|
||||
|
||||
deformAttachment = this;
|
||||
lock (VertexAttachment.nextIdLock) {
|
||||
id = VertexAttachment.nextID++;
|
||||
}
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///<summary>Does not copy id (generated) or name (set on construction).</summary>
|
||||
internal void CopyTo (VertexAttachment attachment) {
|
||||
if (bones != null) {
|
||||
attachment.bones = new int[bones.Length];
|
||||
Array.Copy(bones, 0, attachment.bones, 0, bones.Length);
|
||||
} else
|
||||
attachment.bones = null;
|
||||
|
||||
if (vertices != null) {
|
||||
attachment.vertices = new float[vertices.Length];
|
||||
Array.Copy(vertices, 0, attachment.vertices, 0, vertices.Length);
|
||||
} else
|
||||
attachment.vertices = null;
|
||||
|
||||
attachment.worldVerticesLength = worldVerticesLength;
|
||||
attachment.deformAttachment = deformAttachment;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
SpineRuntimes/SpineRuntime40/BlendMode.cs
Normal file
34
SpineRuntimes/SpineRuntime40/BlendMode.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
public enum BlendMode {
|
||||
Normal, Additive, Multiply, Screen
|
||||
}
|
||||
}
|
||||
378
SpineRuntimes/SpineRuntime40/Bone.cs
Normal file
378
SpineRuntimes/SpineRuntime40/Bone.cs
Normal file
@@ -0,0 +1,378 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
/// <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); } }
|
||||
|
||||
/// <summary>Copy constructor. Does not copy the <see cref="Children"/> bones.</summary>
|
||||
/// <param name="parent">May be null.</param>
|
||||
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>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;
|
||||
}
|
||||
}
|
||||
}
|
||||
105
SpineRuntimes/SpineRuntime40/BoneData.cs
Normal file
105
SpineRuntimes/SpineRuntime40/BoneData.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
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
|
||||
}
|
||||
}
|
||||
61
SpineRuntimes/SpineRuntime40/ConstraintData.cs
Normal file
61
SpineRuntimes/SpineRuntime40/ConstraintData.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
64
SpineRuntimes/SpineRuntime40/Event.cs
Normal file
64
SpineRuntimes/SpineRuntime40/Event.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
56
SpineRuntimes/SpineRuntime40/EventData.cs
Normal file
56
SpineRuntimes/SpineRuntime40/EventData.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
637
SpineRuntimes/SpineRuntime40/ExposedList.cs
Normal file
637
SpineRuntimes/SpineRuntime40/ExposedList.cs
Normal 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 SpineRuntime40 {
|
||||
[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;
|
||||
var 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
SpineRuntimes/SpineRuntime40/IUpdatable.cs
Normal file
42
SpineRuntimes/SpineRuntime40/IUpdatable.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
|
||||
///<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; }
|
||||
}
|
||||
}
|
||||
368
SpineRuntimes/SpineRuntime40/IkConstraint.cs
Normal file
368
SpineRuntimes/SpineRuntime40/IkConstraint.cs
Normal file
@@ -0,0 +1,368 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
/// <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 IkConstraintData data;
|
||||
internal 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.FindBone(boneData.name));
|
||||
target = skeleton.FindBone(data.target.name);
|
||||
}
|
||||
|
||||
/// <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;
|
||||
var 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;
|
||||
ty = targetY - bone.worldY;
|
||||
break;
|
||||
case TransformMode.NoRotationOrReflection: {
|
||||
float s = Math.Abs(pa * pd - pb * pc) / (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;
|
||||
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 = 1 / (a * d - b * c), x = cwx - pp.worldX, y = cwy - pp.worldY;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
103
SpineRuntimes/SpineRuntime40/IkConstraintData.cs
Normal file
103
SpineRuntimes/SpineRuntime40/IkConstraintData.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
/// <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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
517
SpineRuntimes/SpineRuntime40/Json.cs
Normal file
517
SpineRuntimes/SpineRuntime40/Json.cs
Normal file
@@ -0,0 +1,517 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
public static class Json {
|
||||
public static object Deserialize (TextReader text) {
|
||||
var 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) {
|
||||
var 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;
|
||||
|
||||
var result = new string(json, index, charLength);
|
||||
|
||||
index = lastIndex + 1;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public float ParseFloatNumber () {
|
||||
float number;
|
||||
var str = GetNumberString();
|
||||
|
||||
if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number))
|
||||
return 0;
|
||||
|
||||
return number;
|
||||
}
|
||||
|
||||
public double ParseDoubleNumber () {
|
||||
double number;
|
||||
var 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) {
|
||||
var builder = new JsonDecoder();
|
||||
return builder.Decode(text);
|
||||
}
|
||||
|
||||
IDictionary<string, object> ParseObject () {
|
||||
var table = new Dictionary<string, object>();
|
||||
|
||||
// {
|
||||
lexer.NextToken();
|
||||
|
||||
while (true) {
|
||||
var 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 () {
|
||||
var array = new List<object>();
|
||||
|
||||
// [
|
||||
lexer.NextToken();
|
||||
|
||||
while (true) {
|
||||
var 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
173
SpineRuntimes/SpineRuntime40/MathUtils.cs
Normal file
173
SpineRuntimes/SpineRuntime40/MathUtils.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
510
SpineRuntimes/SpineRuntime40/PathConstraint.cs
Normal file
510
SpineRuntimes/SpineRuntime40/PathConstraint.cs
Normal file
@@ -0,0 +1,510 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
|
||||
/// <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 PathConstraintData data;
|
||||
internal ExposedList<Bone> bones;
|
||||
internal Slot target;
|
||||
internal float position, spacing, mixRotate, mixX, mixY;
|
||||
|
||||
internal bool active;
|
||||
|
||||
internal ExposedList<float> spaces = new ExposedList<float>(), positions = new ExposedList<float>();
|
||||
internal ExposedList<float> world = new ExposedList<float>(), curves = new ExposedList<float>(), lengths = new ExposedList<float>();
|
||||
internal 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.FindBone(boneData.name));
|
||||
target = skeleton.FindSlot(data.target.name);
|
||||
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; } }
|
||||
}
|
||||
}
|
||||
72
SpineRuntimes/SpineRuntime40/PathConstraintData.cs
Normal file
72
SpineRuntimes/SpineRuntime40/PathConstraintData.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
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
|
||||
}
|
||||
}
|
||||
614
SpineRuntimes/SpineRuntime40/Skeleton.cs
Normal file
614
SpineRuntimes/SpineRuntime40/Skeleton.cs
Normal file
@@ -0,0 +1,614 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
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;
|
||||
internal float time;
|
||||
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 Time { get { return time; } set { time = value; } }
|
||||
public float X { get { return x; } set { x = value; } }
|
||||
public float Y { get { return y; } set { y = value; } }
|
||||
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
|
||||
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>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 () {
|
||||
var 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++) {
|
||||
var 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);
|
||||
|
||||
var 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 SortPathConstraint (PathConstraint constraint) {
|
||||
constraint.active = constraint.target.bone.active
|
||||
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)));
|
||||
if (!constraint.active) return;
|
||||
|
||||
Slot slot = constraint.target;
|
||||
int slotIndex = slot.data.index;
|
||||
Bone slotBone = slot.bone;
|
||||
if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone);
|
||||
if (data.defaultSkin != null && data.defaultSkin != skin)
|
||||
SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone);
|
||||
|
||||
Attachment attachment = slot.attachment;
|
||||
if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone);
|
||||
|
||||
var constrained = constraint.bones.Items;
|
||||
int boneCount = constraint.bones.Count;
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
SortBone(constrained[i]);
|
||||
|
||||
updateCache.Add(constraint);
|
||||
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
SortReset(constrained[i].children);
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
constrained[i].sorted = true;
|
||||
}
|
||||
|
||||
private void SortTransformConstraint (TransformConstraint constraint) {
|
||||
constraint.active = constraint.target.active
|
||||
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)));
|
||||
if (!constraint.active) return;
|
||||
|
||||
SortBone(constraint.target);
|
||||
|
||||
var 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 SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
|
||||
foreach (var 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 {
|
||||
var 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;
|
||||
}
|
||||
|
||||
var 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.
|
||||
var updateCache = this.updateCache.Items;
|
||||
for (int i = 0, n = this.updateCache.Count; i < n; i++) {
|
||||
var 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 () {
|
||||
var bones = this.bones.Items;
|
||||
for (int i = 0, n = this.bones.Count; i < n; i++)
|
||||
bones[i].SetToSetupPose();
|
||||
|
||||
var 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;
|
||||
}
|
||||
|
||||
var 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;
|
||||
}
|
||||
|
||||
var 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 () {
|
||||
var 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.");
|
||||
var 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.");
|
||||
var 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 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.");
|
||||
var 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.");
|
||||
var 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.");
|
||||
var 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;
|
||||
}
|
||||
|
||||
public void Update (float delta) {
|
||||
time += delta;
|
||||
}
|
||||
|
||||
/// <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];
|
||||
var 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;
|
||||
var regionAttachment = attachment as RegionAttachment;
|
||||
if (regionAttachment != null) {
|
||||
verticesLength = 8;
|
||||
vertices = temp;
|
||||
if (vertices.Length < 8) vertices = temp = new float[8];
|
||||
regionAttachment.ComputeWorldVertices(slot.bone, temp, 0);
|
||||
} else {
|
||||
var meshAttachment = attachment as MeshAttachment;
|
||||
if (meshAttachment != null) {
|
||||
MeshAttachment mesh = meshAttachment;
|
||||
verticesLength = mesh.WorldVerticesLength;
|
||||
vertices = temp;
|
||||
if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength];
|
||||
mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (vertices != null) {
|
||||
for (int ii = 0; ii < verticesLength; ii += 2) {
|
||||
float vx = vertices[ii], vy = vertices[ii + 1];
|
||||
minX = Math.Min(minX, vx);
|
||||
minY = Math.Min(minY, vy);
|
||||
maxX = Math.Max(maxX, vx);
|
||||
maxY = Math.Max(maxY, vy);
|
||||
}
|
||||
}
|
||||
}
|
||||
x = minX;
|
||||
y = minY;
|
||||
width = maxX - minX;
|
||||
height = maxY - minY;
|
||||
vertexBuffer = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
1225
SpineRuntimes/SpineRuntime40/SkeletonBinary.cs
Normal file
1225
SpineRuntimes/SpineRuntime40/SkeletonBinary.cs
Normal file
File diff suppressed because it is too large
Load Diff
233
SpineRuntimes/SpineRuntime40/SkeletonBounds.cs
Normal file
233
SpineRuntimes/SpineRuntime40/SkeletonBounds.cs
Normal file
@@ -0,0 +1,233 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
|
||||
/// <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];
|
||||
}
|
||||
}
|
||||
}
|
||||
292
SpineRuntimes/SpineRuntime40/SkeletonClipping.cs
Normal file
292
SpineRuntimes/SpineRuntime40/SkeletonClipping.cs
Normal file
@@ -0,0 +1,292 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
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 (var 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;
|
||||
var clippedTriangles = this.clippedTriangles;
|
||||
var 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) {
|
||||
var originalOutput = output;
|
||||
var 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;
|
||||
var 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
205
SpineRuntimes/SpineRuntime40/SkeletonData.cs
Normal file
205
SpineRuntimes/SpineRuntime40/SkeletonData.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
|
||||
/// <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.");
|
||||
var 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.");
|
||||
var 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.");
|
||||
var 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.");
|
||||
var 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.");
|
||||
var 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.
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public PathConstraintData FindPathConstraint (string constraintName) {
|
||||
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
|
||||
var 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
1193
SpineRuntimes/SpineRuntime40/SkeletonJson.cs
Normal file
1193
SpineRuntimes/SpineRuntime40/SkeletonJson.cs
Normal file
File diff suppressed because it is too large
Load Diff
92
SpineRuntimes/SpineRuntime40/SkeletonLoader.cs
Normal file
92
SpineRuntimes/SpineRuntime40/SkeletonLoader.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
|
||||
/// <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 inheritDeform;
|
||||
|
||||
public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritDeform) {
|
||||
this.mesh = mesh;
|
||||
this.skin = skin;
|
||||
this.slotIndex = slotIndex;
|
||||
this.parent = parent;
|
||||
this.inheritDeform = inheritDeform;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
206
SpineRuntimes/SpineRuntime40/Skin.cs
Normal file
206
SpineRuntimes/SpineRuntime40/Skin.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
/// <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 (var 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 (var 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 (var 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 (var 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
200
SpineRuntimes/SpineRuntime40/Slot.cs
Normal file
200
SpineRuntimes/SpineRuntime40/Slot.cs
Normal file
@@ -0,0 +1,200 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
|
||||
/// <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 float attachmentTime;
|
||||
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;
|
||||
attachmentTime = slot.attachmentTime;
|
||||
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="AttachmentTime"/> and clears the <see cref="Deform"/>.
|
||||
/// The deform is not cleared if the old attachment has the same <see cref="VertexAttachment.DeformAttachment"/> 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).DeformAttachment != ((VertexAttachment)this.attachment).DeformAttachment) {
|
||||
deform.Clear();
|
||||
}
|
||||
this.attachment = value;
|
||||
attachmentTime = bone.skeleton.time;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton
|
||||
/// <see cref="Skeleton.Time"/></summary>
|
||||
public float AttachmentTime {
|
||||
get { return bone.skeleton.time - attachmentTime; }
|
||||
set { attachmentTime = bone.skeleton.time - 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
77
SpineRuntimes/SpineRuntime40/SlotData.cs
Normal file
77
SpineRuntimes/SpineRuntime40/SlotData.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
SpineRuntimes/SpineRuntime40/SpineRuntime40.csproj
Normal file
14
SpineRuntimes/SpineRuntime40/SpineRuntime40.csproj
Normal 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.0.64</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
311
SpineRuntimes/SpineRuntime40/TransformConstraint.cs
Normal file
311
SpineRuntimes/SpineRuntime40/TransformConstraint.cs
Normal file
@@ -0,0 +1,311 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
/// <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 TransformConstraintData data;
|
||||
internal 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.FindBone(boneData.name));
|
||||
|
||||
target = skeleton.FindBone(data.target.name);
|
||||
}
|
||||
|
||||
/// <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 && mixScaleX == 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;
|
||||
|
||||
var 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;
|
||||
|
||||
var 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;
|
||||
|
||||
var 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;
|
||||
|
||||
var 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
68
SpineRuntimes/SpineRuntime40/TransformConstraintData.cs
Normal file
68
SpineRuntimes/SpineRuntime40/TransformConstraintData.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
275
SpineRuntimes/SpineRuntime40/Triangulator.cs
Normal file
275
SpineRuntimes/SpineRuntime40/Triangulator.cs
Normal file
@@ -0,0 +1,275 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, 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 SpineRuntime40 {
|
||||
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) {
|
||||
var vertices = verticesArray.Items;
|
||||
int vertexCount = verticesArray.Count >> 1;
|
||||
|
||||
var indicesArray = this.indicesArray;
|
||||
indicesArray.Clear();
|
||||
int[] indices = indicesArray.Resize(vertexCount).Items;
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
indices[i] = i;
|
||||
|
||||
var 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);
|
||||
|
||||
var 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) {
|
||||
var vertices = verticesArray.Items;
|
||||
var convexPolygons = this.convexPolygons;
|
||||
for (int i = 0, n = convexPolygons.Count; i < n; i++)
|
||||
polygonPool.Free(convexPolygons.Items[i]);
|
||||
convexPolygons.Clear();
|
||||
|
||||
var convexPolygonsIndices = this.convexPolygonsIndices;
|
||||
for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++)
|
||||
polygonIndicesPool.Free(convexPolygonsIndices.Items[i]);
|
||||
convexPolygonsIndices.Clear();
|
||||
|
||||
var polygonIndices = polygonIndicesPool.Obtain();
|
||||
polygonIndices.Clear();
|
||||
|
||||
var 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).
|
||||
var 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;
|
||||
var otherIndices = convexPolygonsIndices.Items[ii];
|
||||
if (otherIndices.Count != 3) continue;
|
||||
int otherFirstIndex = otherIndices.Items[0];
|
||||
int otherSecondIndex = otherIndices.Items[1];
|
||||
int otherLastIndex = otherIndices.Items[2];
|
||||
|
||||
var 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpineRuntime37", "SpineRuntimes\SpineRuntime37\SpineRuntime37.csproj", "{58954AAB-423F-178B-7B9C-61C54ECEC69C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpineRuntime40", "SpineRuntimes\SpineRuntime40\SpineRuntime40.csproj", "{2E19353C-9C0B-85F7-4EF4-98A778A79059}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
@@ -42,6 +44,10 @@ Global
|
||||
{58954AAB-423F-178B-7B9C-61C54ECEC69C}.Debug|x64.Build.0 = Debug|x64
|
||||
{58954AAB-423F-178B-7B9C-61C54ECEC69C}.Release|x64.ActiveCfg = Release|x64
|
||||
{58954AAB-423F-178B-7B9C-61C54ECEC69C}.Release|x64.Build.0 = Release|x64
|
||||
{2E19353C-9C0B-85F7-4EF4-98A778A79059}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{2E19353C-9C0B-85F7-4EF4-98A778A79059}.Debug|x64.Build.0 = Debug|x64
|
||||
{2E19353C-9C0B-85F7-4EF4-98A778A79059}.Release|x64.ActiveCfg = Release|x64
|
||||
{2E19353C-9C0B-85F7-4EF4-98A778A79059}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -50,6 +56,7 @@ Global
|
||||
{CA964DA9-3649-44BC-84F7-B1108A652905} = {EA2E1399-02BC-43BC-AD9F-42E23E9C3DA8}
|
||||
{ECF7297E-031B-4E37-8033-7C2345DB8766} = {EA2E1399-02BC-43BC-AD9F-42E23E9C3DA8}
|
||||
{58954AAB-423F-178B-7B9C-61C54ECEC69C} = {EA2E1399-02BC-43BC-AD9F-42E23E9C3DA8}
|
||||
{2E19353C-9C0B-85F7-4EF4-98A778A79059} = {EA2E1399-02BC-43BC-AD9F-42E23E9C3DA8}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {91F0EFD1-4B07-4C3C-82D8-90432349D3A5}
|
||||
|
||||
340
SpineViewer/Spine/Implementations/Spine40.cs
Normal file
340
SpineViewer/Spine/Implementations/Spine40.cs
Normal 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 SpineRuntime40;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations
|
||||
{
|
||||
[SpineImplementation(Version.V40)]
|
||||
internal class Spine40 : Spine
|
||||
{
|
||||
private class TextureLoader : SpineRuntime40.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 Spine40(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);
|
||||
animationState.Update(delta);
|
||||
animationState.Apply(skeleton);
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime40.BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
SpineRuntime40.BlendMode.Normal => BlendMode.Normal,
|
||||
SpineRuntime40.BlendMode.Additive => BlendMode.Additive,
|
||||
SpineRuntime40.BlendMode.Multiply => BlendMode.Multiply,
|
||||
SpineRuntime40.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.RendererObject).page.rendererObject;
|
||||
|
||||
regionAttachment.ComputeWorldVertices(slot.Bone, 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.RendererObject).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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ namespace SpineViewer.Spine
|
||||
[Description("v3.6.53")] V36 = 0x0306,
|
||||
[Description("v3.7.94")] V37 = 0x0307,
|
||||
[Description("v3.8.99")] V38 = 0x0308,
|
||||
[Description("v4.0.x")] V40 = 0x0400,
|
||||
[Description("v4.0.64")] V40 = 0x0400,
|
||||
[Description("v4.1.x")] V41 = 0x0401,
|
||||
[Description("v4.2.x")] V42 = 0x0402,
|
||||
[Description("v4.3.x")] V43 = 0x0403,
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<ProjectReference Include="..\SpineRuntimes\SpineRuntime36\SpineRuntime36.csproj" />
|
||||
<ProjectReference Include="..\SpineRuntimes\SpineRuntime37\SpineRuntime37.csproj" />
|
||||
<ProjectReference Include="..\SpineRuntimes\SpineRuntime38\SpineRuntime38.csproj" />
|
||||
<ProjectReference Include="..\SpineRuntimes\SpineRuntime40\SpineRuntime40.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user