Compare commits

..

30 Commits

Author SHA1 Message Date
ww-rm
40bde84648 Merge pull request #81 from ww-rm/dev/wpf
v0.15.10
2025-08-18 18:54:13 +08:00
ww-rm
ebb2593526 update to v0.15.10 2025-08-18 18:52:16 +08:00
ww-rm
6a74204ba1 update readme 2025-08-18 18:51:33 +08:00
ww-rm
78c6c47300 update changelog 2025-08-18 18:48:55 +08:00
ww-rm
746a3decc8 补充插槽可见性属性值拷贝 2025-08-18 18:47:39 +08:00
ww-rm
6dfd25b760 修复插槽禁用功能 2025-08-18 18:39:27 +08:00
ww-rm
a697ccc923 Merge pull request #80 from ww-rm/dev/wpf 2025-08-18 01:26:40 +08:00
ww-rm
5bfa625868 更新至v0.15.9 2025-08-18 01:25:22 +08:00
ww-rm
0762a90fa9 update readme 2025-08-18 01:24:54 +08:00
ww-rm
a4926d905b update changelog 2025-08-18 01:24:47 +08:00
ww-rm
b08b6752cd imp v34 & v35 2025-08-18 01:23:31 +08:00
ww-rm
50b1c66e27 add spine34 & spine35 2025-08-17 16:16:59 +08:00
ww-rm
65508782c6 Merge pull request #77 from ww-rm/dev/wpf
v0.15.8
2025-08-01 00:03:42 +08:00
ww-rm
d02ab536b6 更新至v0.15.8 2025-08-01 00:02:26 +08:00
ww-rm
db3700bda3 update readme 2025-08-01 00:02:21 +08:00
ww-rm
6dea656e5e 修复可能的null引用报错 2025-08-01 00:00:54 +08:00
ww-rm
7bc82ab318 Merge pull request #75 from ww-rm/dev/wpf
fix bug
2025-07-26 23:06:01 +08:00
ww-rm
3eb9b1d008 fix bug 2025-07-26 23:05:23 +08:00
ww-rm
eca59dc67b Merge pull request #74 from ww-rm/dev/wpf
v0.15.7
2025-07-26 23:01:21 +08:00
ww-rm
93b806dccd 更新至v0.15.7 2025-07-26 22:59:32 +08:00
ww-rm
89c31d7c77 add contributing 2025-07-26 22:58:27 +08:00
ww-rm
0a5432bb30 增加cli项目的生成 2025-07-26 22:32:15 +08:00
ww-rm
2eded25c03 add cli project 2025-07-26 22:28:37 +08:00
ww-rm
fa00f0064e remove warning 2025-07-26 22:28:25 +08:00
ww-rm
ddd3e94698 fix wrong text 2025-07-26 22:28:15 +08:00
ww-rm
d7ee88f7f6 Merge pull request #72 from ashleney/dev/wpf
Add CLI
2025-07-26 08:51:02 +08:00
ww-rm
1d7a402749 Update SpineViewerCLI.csproj 2025-07-26 08:49:34 +08:00
ashlen
86bcb079b0 Move to its own project 2025-07-25 19:01:23 +02:00
ashlen
390416df06 Add CLI 2025-07-25 17:19:16 +02:00
ww-rm
1344b34d08 修复时长参数0值判断问题 2025-07-25 22:09:24 +08:00
142 changed files with 21015 additions and 55 deletions

View File

@@ -13,6 +13,7 @@ jobs:
runs-on: windows-latest
env:
PROJECT_NAME: SpineViewer
PROJ_CLI_NAME: SpineViewerCLI
steps:
- name: Checkout code
@@ -54,11 +55,13 @@ jobs:
shell: pwsh
run: |
dotnet publish "$env:PROJECT_NAME\$env:PROJECT_NAME.csproj" -c Release -r win-x64 --sc false -o "publish\$env:PROJECT_NAME-$env:VERSION"
dotnet publish "$env:PROJ_CLI_NAME\$env:PROJ_CLI_NAME.csproj" -c Release -r win-x64 --sc false -o "publish\$env:PROJECT_NAME-$env:VERSION"
- name: Publish SelfContained version
shell: pwsh
run: |
dotnet publish "$env:PROJECT_NAME\$env:PROJECT_NAME.csproj" -c Release -r win-x64 --sc true -o "publish\$env:PROJECT_NAME-$env:VERSION-SelfContained"
dotnet publish "$env:PROJ_CLI_NAME\$env:PROJ_CLI_NAME.csproj" -c Release -r win-x64 --sc true -o "publish\$env:PROJECT_NAME-$env:VERSION-SelfContained"
- name: Create release directory
shell: pwsh

View File

@@ -1,5 +1,21 @@
# CHANGELOG
## v0.15.10
- 增加插槽可见性参数, 允许任何情况下对插槽启用和禁用对插槽的渲染
## v0.15.9
- 添加 V34 和 V35 版本支持
## v0.15.8
- 修复渲染纹理过程中可能的 null 错误
## v0.15.7
- 合并社区 CLI 功能项目
## v0.15.6
- 修复导出单个的时长错误

41
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,41 @@
# CONTRIBUTING
## 仓库分支
仓库目前包含 4 个分支:
- `main`: 默认分支, 也是项目最新版的发布用分支
- `dev/wpf`: WPF 版本开发分支
- `release/wf`: Winforms 旧版本发布分支 (已弃用, 仅进行 bug 修复)
- `dev/wf`: Winforms 旧版本开发分支 (已弃用, 仅进行 bug 修复)
仓库的每个发布分支都有对应的开发分支 `dev/*`, **在进行贡献和推送时请在开发分支上进行**, 待开发分支上审核完毕进行必要的确认 (例如版本号的更新) 后, 再从开发分支向对应的发布分支发起 pr, 合并后将会通过 Actions 进行自动生成和发布.
## 仓库结构
仓库目前包含两个可执行文件项目, 分别是:
- `SpineViewer.csproj`
- `SpineViewerCLI.csproj`
前者为仓库主要项目, 提供一个预览操作 Spine 模型文件的 UI 界面, 后者基于社区贡献进行开发, 提供一些便捷的 CLI 功能, 从而可以对模型文件进行一些批量操作.
除此之外其余项目均为一些基础功能库, 为以上两个项目提供必要的功能支持. 原则上 UI 项目和 CLI 项目二者独立互不引用, 仅引用相同的基础功能库, 以保证整个仓库的层次结构清晰便于维护.
## 如何贡献
对于一些小改动, 例如:
- 某些文件内的 bug 修复 (例如一些逻辑上的错误)
- 已有功能的扩展性增强 (例如在已有代码逻辑结构上扩充某些功能字段)
- 其他可能的对**已有功能**的修复改进
可以直接 fork 修改后向开发分支发起 pr, 经 review 无问题后可直接合并.
对于较大的改动, 例如:
- 新增某些代码文件 (例如需要添加一些全新的类)
- 添加一些全新的逻辑或者功能代码 (例如在自行车上加装发动机)
- 其他可能影响项目代码逻辑结构的改动
这些改动请先提 Issue, 进行必要性讨论, 以及确认新功能的引入方式, 请不要直接将这些可能的破坏性改动发起 pr.

View File

@@ -19,6 +19,7 @@ A simple and user-friendly Spine file viewer and exporter with multi-language su
* Batch adjustment of skeleton parameters using multi-selection.
* Multi-track animation settings.
* Skin and custom slot attachment settings.
* Custom slot visibility settings.
* Debug rendering support.
* Fullscreen preview mode.
* Export to single frame/image sequence/animated GIF/video formats.
@@ -32,6 +33,8 @@ A simple and user-friendly Spine file viewer and exporter with multi-language su
| Version | View & Export |
| :-----: | :------------------: |
| `2.1.x` | :white\_check\_mark: |
| `3.4.x` | :white\_check\_mark: |
| `3.5.x` | :white\_check\_mark: |
| `3.6.x` | :white\_check\_mark: |
| `3.7.x` | :white\_check\_mark: |
| `3.8.x` | :white\_check\_mark: |

View File

@@ -19,6 +19,7 @@
- 支持列表多选批量设置骨骼参数
- 支持多轨道动画设置
- 支持皮肤/自定义插槽附件设置
- 支持自定义插槽可见性
- 支持调试渲染
- 支持全屏预览
- 支持单帧/动图/视频文件导出
@@ -32,6 +33,8 @@
| 版本 | 查看&导出 |
| :---: | :---: |
| `2.1.x` | :white_check_mark: |
| `3.4.x` | :white_check_mark: |
| `3.5.x` | :white_check_mark: |
| `3.6.x` | :white_check_mark: |
| `3.7.x` | :white_check_mark: |
| `3.8.x` | :white_check_mark: |

View File

@@ -66,6 +66,8 @@ namespace Spine.Implementations.SpineWrappers.V21
}
}
public bool Disabled { get; set; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,23 @@
using Spine.SpineWrappers;
using SpineRuntime34;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V34
{
internal sealed class Animation34(Animation innerObject) : IAnimation
{
private readonly Animation _o = innerObject;
public Animation InnerObject => _o;
public string Name => _o.Name;
public float Duration => _o.Duration;
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,180 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using SpineRuntime34;
namespace Spine.Implementations.SpineWrappers.V34
{
internal sealed class AnimationState34(AnimationState innerObject, SpineObjectData34 data) : IAnimationState
{
private readonly AnimationState _o = innerObject;
private readonly SpineObjectData34 _data = data;
private readonly Dictionary<TrackEntry, TrackEntry34> _trackEntryPool = [];
private readonly Dictionary<IAnimationState.TrackEntryDelegate, AnimationState.TrackEntryDelegate> _eventMapping = [];
private readonly Dictionary<IAnimationState.TrackEntryDelegate, int> _eventCount = [];
public AnimationState InnerObject => _o;
#pragma warning disable CS0067
// NOTE: 3.4 以下没有这两个事件
public event IAnimationState.TrackEntryDelegate? Interrupt;
public event IAnimationState.TrackEntryDelegate? Dispose;
#pragma warning restore CS0067
public event IAnimationState.TrackEntryDelegate? Start
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Start += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Start -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? End
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.End += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.End -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? Complete
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Complete += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Complete -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public float TimeScale { get => _o.TimeScale; set => _o.TimeScale = value; }
public void Update(float delta) => _o.Update(delta);
public void Apply(ISkeleton skeleton)
{
if (skeleton is Skeleton34 skel)
{
_o.Apply(skel.InnerObject);
return;
}
throw new ArgumentException($"Received {skeleton.GetType().Name}", nameof(skeleton));
}
/// <summary>
/// 获取 <see cref="ITrackEntry"/> 对象, 不存在则创建
/// </summary>
public ITrackEntry GetTrackEntry(TrackEntry trackEntry)
{
if (!_trackEntryPool.TryGetValue(trackEntry, out var tr))
_trackEntryPool[trackEntry] = tr = new(trackEntry, this, _data);
return tr;
}
public IEnumerable<ITrackEntry?> IterTracks() => _o.Tracks.Select(t => t is null ? null : GetTrackEntry(t));
public ITrackEntry? GetCurrent(int index) { var t = _o.GetCurrent(index); return t is null ? null : GetTrackEntry(t); }
public void ClearTrack(int index) => _o.ClearTrack(index);
public void ClearTracks() => _o.ClearTracks();
public ITrackEntry SetAnimation(int trackIndex, string animationName, bool loop)
=> GetTrackEntry(_o.SetAnimation(trackIndex, animationName, loop));
public ITrackEntry SetAnimation(int trackIndex, IAnimation animation, bool loop)
{
if (animation is Animation34 anime)
return GetTrackEntry(_o.SetAnimation(trackIndex, anime.InnerObject, loop));
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
}
public ITrackEntry SetEmptyAnimation(int trackIndex, float mixDuration) => GetTrackEntry(_o.SetEmptyAnimation(trackIndex, mixDuration));
public void SetEmptyAnimations(float mixDuration) => _o.SetEmptyAnimations(mixDuration);
public ITrackEntry AddAnimation(int trackIndex, string animationName, bool loop, float delay)
=> GetTrackEntry(_o.AddAnimation(trackIndex, animationName, loop, delay));
public ITrackEntry AddAnimation(int trackIndex, IAnimation animation, bool loop, float delay)
{
if (animation is Animation34 anime)
return GetTrackEntry(_o.AddAnimation(trackIndex, anime.InnerObject, loop, delay));
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
}
public ITrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay)
=> GetTrackEntry(_o.AddEmptyAnimation(trackIndex, mixDuration, delay));
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using SpineRuntime34;
namespace Spine.Implementations.SpineWrappers.V34.Attachments
{
internal abstract class Attachment34(Attachment innerObject) : IAttachment
{
private readonly Attachment _o = innerObject;
public virtual Attachment InnerObject => _o;
public string Name => _o.Name;
public abstract int ComputeWorldVertices(ISlot slot, ref float[] worldVertices);
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime34;
namespace Spine.Implementations.SpineWrappers.V34.Attachments
{
internal sealed class BoundingBoxAttachment34(BoundingBoxAttachment innerObject) :
Attachment34(innerObject),
IBoundingBoxAttachment
{
private readonly BoundingBoxAttachment _o = innerObject;
public override BoundingBoxAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
{
if (slot is Slot34 st)
{
var length = _o.WorldVerticesLength;
if (worldVertices.Length < length) worldVertices = new float[length];
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
return length;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot34)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime34;
namespace Spine.Implementations.SpineWrappers.V34.Attachments
{
internal sealed class MeshAttachment34(MeshAttachment innerObject) :
Attachment34(innerObject),
IMeshAttachment
{
private readonly MeshAttachment _o = innerObject;
public override MeshAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
{
if (slot is Slot34 st)
{
var length = _o.WorldVerticesLength;
if (worldVertices.Length < length) worldVertices = new float[length];
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
return length;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot34)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
public int[] Triangles => _o.Triangles;
public int HullLength => _o.HullLength;
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime34;
namespace Spine.Implementations.SpineWrappers.V34.Attachments
{
internal sealed class PathAttachment34(PathAttachment innerObject) :
Attachment34(innerObject),
IPathAttachment
{
private readonly PathAttachment _o = innerObject;
public override PathAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
{
if (slot is Slot34 st)
{
var length = _o.WorldVerticesLength;
if (worldVertices.Length < length) worldVertices = new float[length];
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
return length;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot34)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime34;
namespace Spine.Implementations.SpineWrappers.V34.Attachments
{
internal sealed class RegionAttachment34(RegionAttachment innerObject) :
Attachment34(innerObject),
IRegionAttachment
{
private readonly RegionAttachment _o = innerObject;
public override RegionAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
{
if (slot is Slot34 st)
{
if (worldVertices.Length < 8) worldVertices = new float[8];
_o.ComputeWorldVertices(st.InnerObject.Bone, worldVertices);
return 8;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot34)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using SpineRuntime34;
namespace Spine.Implementations.SpineWrappers.V34
{
internal sealed class Bone34(Bone innerObject, Bone34? parent = null) : IBone
{
private readonly Bone _o = innerObject;
private readonly Bone34? _parent = parent;
public Bone InnerObject => _o;
public string Name => _o.Data.Name;
public int Index => _o.Data.Index;
public IBone? Parent => _parent;
public bool Active => true; // NOTE: 3.7 及以下没有 Active 属性, 此处总是返回 true
public float Length => _o.Data.Length;
public float WorldX => _o.WorldX;
public float WorldY => _o.WorldY;
public float A => _o.A;
public float B => _o.B;
public float C => _o.C;
public float D => _o.D;
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Frozen;
using System.Collections.Immutable;
using Spine.SpineWrappers;
using SpineRuntime34;
namespace Spine.Implementations.SpineWrappers.V34
{
internal sealed class Skeleton34 : ISkeleton
{
private readonly Skeleton _o;
private readonly SpineObjectData34 _data;
private readonly ImmutableArray<IBone> _bones;
private readonly FrozenDictionary<string, IBone> _bonesByName;
private readonly ImmutableArray<ISlot> _slots;
private readonly FrozenDictionary<string, ISlot> _slotsByName;
private Skin34? _skin;
public Skeleton34(Skeleton innerObject, SpineObjectData34 data)
{
_o = innerObject;
_data = data;
List<Bone34> bones = [];
Dictionary<string, IBone> bonesByName = [];
foreach (var b in _o.Bones)
{
var bone = new Bone34(b, b.Parent is null ? null : bones[b.Parent.Data.Index]);
bones.Add(bone);
bonesByName[bone.Name] = bone;
}
_bones = bones.Cast<IBone>().ToImmutableArray();
_bonesByName = bonesByName.ToFrozenDictionary();
List<Slot34> slots = [];
Dictionary<string, ISlot> slotsByName = [];
foreach (var s in _o.Slots)
{
var slot = new Slot34(s, _data, bones[s.Bone.Data.Index]);
slots.Add(slot);
slotsByName[slot.Name] = slot;
}
_slots = slots.Cast<ISlot>().ToImmutableArray();
_slotsByName = slotsByName.ToFrozenDictionary();
}
public Skeleton InnerObject => _o;
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public float X { get => _o.X; set => _o.X = value; }
public float Y { get => _o.Y; set => _o.Y = value; }
public float ScaleX { get => _o.ScaleX; set => _o.ScaleX = value; }
public float ScaleY { get => _o.ScaleY; set => _o.ScaleY = value; }
public ImmutableArray<IBone> Bones => _bones;
public FrozenDictionary<string, IBone> BonesByName => _bonesByName;
public ImmutableArray<ISlot> Slots => _slots;
public FrozenDictionary<string, ISlot> SlotsByName => _slotsByName;
public ISkin? Skin
{
get => _skin;
set
{
if (value is null)
{
_o.Skin = null;
_skin = null;
return;
}
if (value is Skin34 sk)
{
_o.Skin = sk.InnerObject;
_skin = sk;
return;
}
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
}
}
public IEnumerable<ISlot> IterDrawOrder() => _o.DrawOrder.Select(s => _slots[s.Data.Index]);
public void UpdateCache() => _o.UpdateCache();
public void UpdateWorldTransform(ISkeleton.Physics physics) => _o.UpdateWorldTransform();
public void SetToSetupPose() => _o.SetToSetupPose();
public void SetBonesToSetupPose() => _o.SetBonesToSetupPose();
public void SetSlotsToSetupPose() => _o.SetSlotsToSetupPose();
public void Update(float delta) => _o.Update(delta);
public void GetBounds(out float x, out float y, out float w, out float h)
{
float[] _ = [];
_o.GetBounds(out x, out y, out w, out h);
}
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,42 @@
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using Spine.Utils;
using SpineRuntime34;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V34
{
internal sealed class SkeletonClipping34 : ISkeletonClipping
{
public bool IsClipping => false;
public float[] ClippedVertices { get; private set; } = [];
public int ClippedVerticesLength { get; private set; } = 0;
public int[] ClippedTriangles { get; private set; } = [];
public int ClippedTrianglesLength { get; private set; } = 0;
public float[] ClippedUVs { get; private set; } = [];
public void ClipEnd(ISlot slot) { }
public void ClipEnd() { }
public void ClipStart(ISlot slot, IClippingAttachment clippingAttachment) { }
public void ClipTriangles(float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs)
{
ClippedVertices = vertices.ToArray();
ClippedVerticesLength = verticesLength;
ClippedTriangles = triangles.ToArray();
ClippedTrianglesLength = trianglesLength;
ClippedUVs = uvs.ToArray();
}
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using SpineRuntime34;
namespace Spine.Implementations.SpineWrappers.V34
{
internal sealed class Skin34 : ISkin
{
private readonly Skin _o;
/// <summary>
/// 使用指定名字创建空皮肤
/// </summary>
public Skin34(string name) => _o = new(name);
/// <summary>
/// 包装已有皮肤对象
/// </summary>
public Skin34(Skin innerObject) => _o = innerObject;
public Skin InnerObject => _o;
public string Name => _o.Name;
public void AddSkin(ISkin skin)
{
if (skin is Skin34 sk)
{
// NOTE: 3.7 及以下不支持 AddSkin
foreach (var (k, v) in sk._o.Attachments)
_o.AddAttachment(k.slotIndex, k.name, v);
return;
}
throw new ArgumentException($"Received {skin.GetType().Name}", nameof(skin));
}
public void Clear() => _o.Attachments.Clear();
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,80 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Utils;
using Spine.SpineWrappers;
using SpineRuntime34;
namespace Spine.Implementations.SpineWrappers.V34
{
internal sealed class Slot34 : ISlot
{
private readonly Slot _o;
private readonly SpineObjectData34 _data;
private readonly Bone34 _bone;
private readonly SFML.Graphics.BlendMode _blendMode;
public Slot34(Slot innerObject, SpineObjectData34 data, Bone34 bone)
{
_o = innerObject;
_data = data;
_bone = bone;
_blendMode = _o.Data.BlendMode switch
{
BlendMode.normal => SFMLBlendMode.NormalPma,
BlendMode.additive => SFMLBlendMode.AdditivePma,
BlendMode.multiply => SFMLBlendMode.MultiplyPma,
BlendMode.screen => SFMLBlendMode.ScreenPma,
_ => throw new NotImplementedException($"{_o.Data.BlendMode}"),
};
}
public Slot InnerObject => _o;
public string Name => _o.Data.Name;
public int Index => _o.Data.Index;
public SFML.Graphics.BlendMode Blend => _blendMode;
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public IBone Bone => _bone;
public Spine.SpineWrappers.Attachments.IAttachment? Attachment
{
get
{
if (_o.Attachment is Attachment att)
{
return _data.SlotAttachments[Name][att.Name];
}
return null;
}
set
{
if (value is null)
{
_o.Attachment = null;
return;
}
if (value is Attachments.Attachment34 att)
{
_o.Attachment = att.InnerObject;
return;
}
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
}
}
public bool Disabled { get; set; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using System.Collections.Frozen;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Utils;
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using SpineRuntime34;
using Spine.Implementations.SpineWrappers.V34.Attachments;
namespace Spine.Implementations.SpineWrappers.V34
{
[SpineImplementation(3, 4)]
internal sealed class SpineObjectData34 : SpineObjectData
{
private readonly Atlas _atlas;
private readonly SkeletonData _skeletonData;
private readonly AnimationStateData _animationStateData;
private readonly ImmutableArray<ISkin> _skins;
private readonly FrozenDictionary<string, ISkin> _skinsByName;
private readonly FrozenDictionary<string, FrozenDictionary<string, IAttachment>> _slotAttachments;
private readonly ImmutableArray<IAnimation> _animations;
private readonly FrozenDictionary<string, IAnimation> _animationsByName;
public SpineObjectData34(string skelPath, string atlasPath, Spine.SpineWrappers.TextureLoader textureLoader)
: base(skelPath, atlasPath, textureLoader)
{
// 加载 atlas
try { _atlas = new Atlas(atlasPath, textureLoader); }
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
try
{
if (Utf8Validator.IsUtf8(skelPath))
{
try
{
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
catch
{
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
}
else
{
try
{
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
catch
{
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
}
}
catch (Exception ex)
{
_atlas.Dispose();
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
}
// 加载动画数据
_animationStateData = new AnimationStateData(_skeletonData);
// 整理皮肤和附件
Dictionary<string, Dictionary<string, IAttachment>> slotAttachments = [];
List<ISkin> skins = [];
Dictionary<string, ISkin> skinsByName = [];
foreach (var s in _skeletonData.Skins)
{
var skin = new Skin34(s);
skins.Add(skin);
skinsByName[s.Name] = skin;
foreach (var (k, att) in s.Attachments)
{
var slotName = _skeletonData.Slots.Items[k.slotIndex].Name;
if (!slotAttachments.TryGetValue(slotName, out var attachments))
slotAttachments[slotName] = attachments = [];
attachments[att.Name] = att switch
{
RegionAttachment regionAtt => new RegionAttachment34(regionAtt),
MeshAttachment meshAtt => new MeshAttachment34(meshAtt),
BoundingBoxAttachment bbAtt => new BoundingBoxAttachment34(bbAtt),
PathAttachment pathAtt => new PathAttachment34(pathAtt),
_ => throw new InvalidOperationException($"Unrecognized attachment type {att.GetType().FullName}")
};
}
}
_slotAttachments = slotAttachments.ToFrozenDictionary(it => it.Key, it => it.Value.ToFrozenDictionary());
_skins = skins.ToImmutableArray();
_skinsByName = skinsByName.ToFrozenDictionary();
// 整理所有动画数据
List<IAnimation> animations = [];
Dictionary<string, IAnimation> animationsByName = [];
foreach (var a in _skeletonData.Animations)
{
var anime = new Animation34(a);
animations.Add(anime);
animationsByName[anime.Name] = anime;
}
_animations = animations.ToImmutableArray();
_animationsByName = animationsByName.ToFrozenDictionary();
}
public override string SkeletonVersion => _skeletonData.Version;
public override ImmutableArray<ISkin> Skins => _skins;
public override FrozenDictionary<string, ISkin> SkinsByName => _skinsByName;
public override FrozenDictionary<string, FrozenDictionary<string, IAttachment>> SlotAttachments => _slotAttachments;
public override float DefaultMix { get => _animationStateData.DefaultMix; set => _animationStateData.DefaultMix = value; }
public override ImmutableArray<IAnimation> Animations => _animations;
public override FrozenDictionary<string, IAnimation> AnimationsByName => _animationsByName;
protected override void DisposeAtlas() => _atlas.Dispose();
public override ISkeleton CreateSkeleton() => new Skeleton34(new(_skeletonData), this);
public override IAnimationState CreateAnimationState() => new AnimationState34(new(_animationStateData), this);
public override ISkeletonClipping CreateSkeletonClipping() => new SkeletonClipping34();
public override ISkin CreateSkin(string name) => new Skin34(name);
}
}

View File

@@ -0,0 +1,135 @@
using Spine.SpineWrappers;
using SpineRuntime34;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V34
{
internal sealed class TrackEntry34(TrackEntry innerObject, AnimationState34 animationState, SpineObjectData34 data): ITrackEntry
{
private readonly TrackEntry _o = innerObject;
private readonly AnimationState34 _animationState = animationState;
private readonly SpineObjectData34 _data = data;
private readonly Dictionary<IAnimationState.TrackEntryDelegate, AnimationState.TrackEntryDelegate> _eventMapping = [];
private readonly Dictionary<IAnimationState.TrackEntryDelegate, int> _eventCount = [];
public TrackEntry InnerObject => _o;
#pragma warning disable CS0067
// 3.4 及以下没有这两个事件
public event IAnimationState.TrackEntryDelegate? Interrupt;
public event IAnimationState.TrackEntryDelegate? Dispose;
#pragma warning restore CS0067
public event IAnimationState.TrackEntryDelegate? Start
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Start += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Start -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? End
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.End += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.End -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? Complete
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Complete += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Complete -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public int TrackIndex { get => _o.TrackIndex; }
public IAnimation Animation { get => _data.AnimationsByName[_o.Animation.Name]; }
public ITrackEntry? Next { get { var t = _o.Next; return t is null ? null : _animationState.GetTrackEntry(t); } }
public bool Loop { get => _o.Loop; set => _o.Loop = value; }
public float TrackTime { get => _o.Time; set => _o.Time = value; }
public float TimeScale { get => _o.TimeScale; set => _o.TimeScale = value; }
public float Alpha { get => _o.Mix; set => _o.Mix = value; }
public float MixDuration { get => _o.MixDuration; set => _o.MixDuration = value; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,23 @@
using Spine.SpineWrappers;
using SpineRuntime35;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V35
{
internal sealed class Animation35(Animation innerObject) : IAnimation
{
private readonly Animation _o = innerObject;
public Animation InnerObject => _o;
public string Name => _o.Name;
public float Duration => _o.Duration;
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,229 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using SpineRuntime35;
namespace Spine.Implementations.SpineWrappers.V35
{
internal sealed class AnimationState35(AnimationState innerObject, SpineObjectData35 data) : IAnimationState
{
private readonly AnimationState _o = innerObject;
private readonly SpineObjectData35 _data = data;
private readonly Dictionary<TrackEntry, TrackEntry35> _trackEntryPool = [];
private readonly Dictionary<IAnimationState.TrackEntryDelegate, AnimationState.TrackEntryDelegate> _eventMapping = [];
private readonly Dictionary<IAnimationState.TrackEntryDelegate, int> _eventCount = [];
public AnimationState InnerObject => _o;
public event IAnimationState.TrackEntryDelegate? Start
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Start += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Start -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? Interrupt
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Interrupt += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Interrupt -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? End
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.End += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.End -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? Complete
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Complete += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Complete -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? Dispose
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Dispose += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Dispose -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public float TimeScale { get => _o.TimeScale; set => _o.TimeScale = value; }
public void Update(float delta) => _o.Update(delta);
public void Apply(ISkeleton skeleton)
{
if (skeleton is Skeleton35 skel)
{
_o.Apply(skel.InnerObject);
return;
}
throw new ArgumentException($"Received {skeleton.GetType().Name}", nameof(skeleton));
}
/// <summary>
/// 获取 <see cref="ITrackEntry"/> 对象, 不存在则创建
/// </summary>
public ITrackEntry GetTrackEntry(TrackEntry trackEntry)
{
if (!_trackEntryPool.TryGetValue(trackEntry, out var tr))
_trackEntryPool[trackEntry] = tr = new(trackEntry, this, _data);
return tr;
}
public IEnumerable<ITrackEntry?> IterTracks() => _o.Tracks.Select(t => t is null ? null : GetTrackEntry(t));
public ITrackEntry? GetCurrent(int index) { var t = _o.GetCurrent(index); return t is null ? null : GetTrackEntry(t); }
public void ClearTrack(int index) => _o.ClearTrack(index);
public void ClearTracks() => _o.ClearTracks();
public ITrackEntry SetAnimation(int trackIndex, string animationName, bool loop)
=> GetTrackEntry(_o.SetAnimation(trackIndex, animationName, loop));
public ITrackEntry SetAnimation(int trackIndex, IAnimation animation, bool loop)
{
if (animation is Animation35 anime)
return GetTrackEntry(_o.SetAnimation(trackIndex, anime.InnerObject, loop));
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
}
public ITrackEntry SetEmptyAnimation(int trackIndex, float mixDuration) => GetTrackEntry(_o.SetEmptyAnimation(trackIndex, mixDuration));
public void SetEmptyAnimations(float mixDuration) => _o.SetEmptyAnimations(mixDuration);
public ITrackEntry AddAnimation(int trackIndex, string animationName, bool loop, float delay)
=> GetTrackEntry(_o.AddAnimation(trackIndex, animationName, loop, delay));
public ITrackEntry AddAnimation(int trackIndex, IAnimation animation, bool loop, float delay)
{
if (animation is Animation35 anime)
return GetTrackEntry(_o.AddAnimation(trackIndex, anime.InnerObject, loop, delay));
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
}
public ITrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay)
=> GetTrackEntry(_o.AddEmptyAnimation(trackIndex, mixDuration, delay));
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using SpineRuntime35;
namespace Spine.Implementations.SpineWrappers.V35.Attachments
{
internal abstract class Attachment35(Attachment innerObject) : IAttachment
{
private readonly Attachment _o = innerObject;
public virtual Attachment InnerObject => _o;
public string Name => _o.Name;
public abstract int ComputeWorldVertices(ISlot slot, ref float[] worldVertices);
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime35;
namespace Spine.Implementations.SpineWrappers.V35.Attachments
{
internal sealed class BoundingBoxAttachment35(BoundingBoxAttachment innerObject) :
Attachment35(innerObject),
IBoundingBoxAttachment
{
private readonly BoundingBoxAttachment _o = innerObject;
public override BoundingBoxAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
{
if (slot is Slot35 st)
{
var length = _o.WorldVerticesLength;
if (worldVertices.Length < length) worldVertices = new float[length];
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
return length;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime35;
namespace Spine.Implementations.SpineWrappers.V35.Attachments
{
internal sealed class ClippingAttachment35(ClippingAttachment innerObject) :
Attachment35(innerObject),
IClippingAttachment
{
private readonly ClippingAttachment _o = innerObject;
public override ClippingAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
{
if (slot is Slot35 st)
{
var length = _o.WorldVerticesLength;
if (worldVertices.Length < length) worldVertices = new float[length];
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
return length;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime35;
namespace Spine.Implementations.SpineWrappers.V35.Attachments
{
internal sealed class MeshAttachment35(MeshAttachment innerObject) :
Attachment35(innerObject),
IMeshAttachment
{
private readonly MeshAttachment _o = innerObject;
public override MeshAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
{
if (slot is Slot35 st)
{
var length = _o.WorldVerticesLength;
if (worldVertices.Length < length) worldVertices = new float[length];
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
return length;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
public int[] Triangles => _o.Triangles;
public int HullLength => _o.HullLength;
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime35;
namespace Spine.Implementations.SpineWrappers.V35.Attachments
{
internal sealed class PathAttachment35(PathAttachment innerObject) :
Attachment35(innerObject),
IPathAttachment
{
private readonly PathAttachment _o = innerObject;
public override PathAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
{
if (slot is Slot35 st)
{
var length = _o.WorldVerticesLength;
if (worldVertices.Length < length) worldVertices = new float[length];
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
return length;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime35;
namespace Spine.Implementations.SpineWrappers.V35.Attachments
{
internal sealed class PointAttachment35(PointAttachment innerObject) :
Attachment35(innerObject),
IPointAttachment
{
private readonly PointAttachment _o = innerObject;
public override PointAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
{
if (slot is Slot35 st)
{
if (worldVertices.Length < 2) worldVertices = new float[2];
_o.ComputeWorldPosition(st.InnerObject.Bone, out worldVertices[0], out worldVertices[1]);
return 2;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime35;
namespace Spine.Implementations.SpineWrappers.V35.Attachments
{
internal sealed class RegionAttachment35(RegionAttachment innerObject) :
Attachment35(innerObject),
IRegionAttachment
{
private readonly RegionAttachment _o = innerObject;
public override RegionAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
{
if (slot is Slot35 st)
{
if (worldVertices.Length < 8) worldVertices = new float[8];
_o.ComputeWorldVertices(st.InnerObject.Bone, worldVertices, 0);
return 8;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using SpineRuntime35;
namespace Spine.Implementations.SpineWrappers.V35
{
internal sealed class Bone35(Bone innerObject, Bone35? parent = null) : IBone
{
private readonly Bone _o = innerObject;
private readonly Bone35? _parent = parent;
public Bone InnerObject => _o;
public string Name => _o.Data.Name;
public int Index => _o.Data.Index;
public IBone? Parent => _parent;
public bool Active => true; // NOTE: 3.7 及以下没有 Active 属性, 此处总是返回 true
public float Length => _o.Data.Length;
public float WorldX => _o.WorldX;
public float WorldY => _o.WorldY;
public float A => _o.A;
public float B => _o.B;
public float C => _o.C;
public float D => _o.D;
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Frozen;
using System.Collections.Immutable;
using Spine.SpineWrappers;
using SpineRuntime35;
namespace Spine.Implementations.SpineWrappers.V35
{
internal sealed class Skeleton35 : ISkeleton
{
private readonly Skeleton _o;
private readonly SpineObjectData35 _data;
private readonly ImmutableArray<IBone> _bones;
private readonly FrozenDictionary<string, IBone> _bonesByName;
private readonly ImmutableArray<ISlot> _slots;
private readonly FrozenDictionary<string, ISlot> _slotsByName;
private Skin35? _skin;
public Skeleton35(Skeleton innerObject, SpineObjectData35 data)
{
_o = innerObject;
_data = data;
List<Bone35> bones = [];
Dictionary<string, IBone> bonesByName = [];
foreach (var b in _o.Bones)
{
var bone = new Bone35(b, b.Parent is null ? null : bones[b.Parent.Data.Index]);
bones.Add(bone);
bonesByName[bone.Name] = bone;
}
_bones = bones.Cast<IBone>().ToImmutableArray();
_bonesByName = bonesByName.ToFrozenDictionary();
List<Slot35> slots = [];
Dictionary<string, ISlot> slotsByName = [];
foreach (var s in _o.Slots)
{
var slot = new Slot35(s, _data, bones[s.Bone.Data.Index]);
slots.Add(slot);
slotsByName[slot.Name] = slot;
}
_slots = slots.Cast<ISlot>().ToImmutableArray();
_slotsByName = slotsByName.ToFrozenDictionary();
}
public Skeleton InnerObject => _o;
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public float X { get => _o.X; set => _o.X = value; }
public float Y { get => _o.Y; set => _o.Y = value; }
public float ScaleX { get => _o.ScaleX; set => _o.ScaleX = value; }
public float ScaleY { get => _o.ScaleY; set => _o.ScaleY = value; }
public ImmutableArray<IBone> Bones => _bones;
public FrozenDictionary<string, IBone> BonesByName => _bonesByName;
public ImmutableArray<ISlot> Slots => _slots;
public FrozenDictionary<string, ISlot> SlotsByName => _slotsByName;
public ISkin? Skin
{
get => _skin;
set
{
if (value is null)
{
_o.Skin = null;
_skin = null;
return;
}
if (value is Skin35 sk)
{
_o.Skin = sk.InnerObject;
_skin = sk;
return;
}
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
}
}
public IEnumerable<ISlot> IterDrawOrder() => _o.DrawOrder.Select(s => _slots[s.Data.Index]);
public void UpdateCache() => _o.UpdateCache();
public void UpdateWorldTransform(ISkeleton.Physics physics) => _o.UpdateWorldTransform();
public void SetToSetupPose() => _o.SetToSetupPose();
public void SetBonesToSetupPose() => _o.SetBonesToSetupPose();
public void SetSlotsToSetupPose() => _o.SetSlotsToSetupPose();
public void Update(float delta) => _o.Update(delta);
public void GetBounds(out float x, out float y, out float w, out float h)
{
float[] _ = [];
_o.GetBounds(out x, out y, out w, out h, ref _);
}
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,56 @@
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using Spine.Utils;
using SpineRuntime35;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V35
{
internal sealed class SkeletonClipping35 : ISkeletonClipping
{
private readonly SkeletonClipping _o = new();
public bool IsClipping => _o.IsClipping();
public float[] ClippedVertices => _o.ClippedVertices.Items;
public int ClippedVerticesLength => _o.ClippedVertices.Count;
public int[] ClippedTriangles => _o.ClippedTriangles.Items;
public int ClippedTrianglesLength => _o.ClippedTriangles.Count;
public float[] ClippedUVs => _o.ClippedUVs.Items;
public void ClipTriangles(float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs)
=> _o.ClipTriangles(vertices, verticesLength, triangles, trianglesLength, uvs);
public void ClipStart(ISlot slot, IClippingAttachment clippingAttachment)
{
if (slot is Slot35 st && clippingAttachment is Attachments.ClippingAttachment35 att)
{
_o.ClipStart(st.InnerObject, att.InnerObject);
return;
}
throw new ArgumentException($"Received {slot.GetType().Name} {clippingAttachment.GetType().Name}");
}
public void ClipEnd(ISlot slot)
{
if (slot is Slot35 st)
{
_o.ClipEnd(st.InnerObject);
return;
}
throw new ArgumentException($"Received {slot.GetType().Name}", nameof(slot));
}
public void ClipEnd() => _o.ClipEnd();
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using SpineRuntime35;
namespace Spine.Implementations.SpineWrappers.V35
{
internal sealed class Skin35 : ISkin
{
private readonly Skin _o;
/// <summary>
/// 使用指定名字创建空皮肤
/// </summary>
public Skin35(string name) => _o = new(name);
/// <summary>
/// 包装已有皮肤对象
/// </summary>
public Skin35(Skin innerObject) => _o = innerObject;
public Skin InnerObject => _o;
public string Name => _o.Name;
public void AddSkin(ISkin skin)
{
if (skin is Skin35 sk)
{
// NOTE: 3.7 及以下不支持 AddSkin
foreach (var (k, v) in sk._o.Attachments)
_o.AddAttachment(k.slotIndex, k.name, v);
return;
}
throw new ArgumentException($"Received {skin.GetType().Name}", nameof(skin));
}
public void Clear() => _o.Attachments.Clear();
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,80 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Utils;
using Spine.SpineWrappers;
using SpineRuntime35;
namespace Spine.Implementations.SpineWrappers.V35
{
internal sealed class Slot35 : ISlot
{
private readonly Slot _o;
private readonly SpineObjectData35 _data;
private readonly Bone35 _bone;
private readonly SFML.Graphics.BlendMode _blendMode;
public Slot35(Slot innerObject, SpineObjectData35 data, Bone35 bone)
{
_o = innerObject;
_data = data;
_bone = bone;
_blendMode = _o.Data.BlendMode switch
{
BlendMode.Normal => SFMLBlendMode.NormalPma,
BlendMode.Additive => SFMLBlendMode.AdditivePma,
BlendMode.Multiply => SFMLBlendMode.MultiplyPma,
BlendMode.Screen => SFMLBlendMode.ScreenPma,
_ => throw new NotImplementedException($"{_o.Data.BlendMode}"),
};
}
public Slot InnerObject => _o;
public string Name => _o.Data.Name;
public int Index => _o.Data.Index;
public SFML.Graphics.BlendMode Blend => _blendMode;
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public IBone Bone => _bone;
public Spine.SpineWrappers.Attachments.IAttachment? Attachment
{
get
{
if (_o.Attachment is Attachment att)
{
return _data.SlotAttachments[Name][att.Name];
}
return null;
}
set
{
if (value is null)
{
_o.Attachment = null;
return;
}
if (value is Attachments.Attachment35 att)
{
_o.Attachment = att.InnerObject;
return;
}
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
}
}
public bool Disabled { get; set; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.Collections.Frozen;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Utils;
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using SpineRuntime35;
using Spine.Implementations.SpineWrappers.V35.Attachments;
namespace Spine.Implementations.SpineWrappers.V35
{
[SpineImplementation(3, 5)]
internal sealed class SpineObjectData35 : SpineObjectData
{
private readonly Atlas _atlas;
private readonly SkeletonData _skeletonData;
private readonly AnimationStateData _animationStateData;
private readonly ImmutableArray<ISkin> _skins;
private readonly FrozenDictionary<string, ISkin> _skinsByName;
private readonly FrozenDictionary<string, FrozenDictionary<string, IAttachment>> _slotAttachments;
private readonly ImmutableArray<IAnimation> _animations;
private readonly FrozenDictionary<string, IAnimation> _animationsByName;
public SpineObjectData35(string skelPath, string atlasPath, Spine.SpineWrappers.TextureLoader textureLoader)
: base(skelPath, atlasPath, textureLoader)
{
// 加载 atlas
try { _atlas = new Atlas(atlasPath, textureLoader); }
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
try
{
if (Utf8Validator.IsUtf8(skelPath))
{
try
{
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
catch
{
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
}
else
{
try
{
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
catch
{
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
}
}
catch (Exception ex)
{
_atlas.Dispose();
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
}
// 加载动画数据
_animationStateData = new AnimationStateData(_skeletonData);
// 整理皮肤和附件
Dictionary<string, Dictionary<string, IAttachment>> slotAttachments = [];
List<ISkin> skins = [];
Dictionary<string, ISkin> skinsByName = [];
foreach (var s in _skeletonData.Skins)
{
var skin = new Skin35(s);
skins.Add(skin);
skinsByName[s.Name] = skin;
foreach (var (k, att) in s.Attachments)
{
var slotName = _skeletonData.Slots.Items[k.slotIndex].Name;
if (!slotAttachments.TryGetValue(slotName, out var attachments))
slotAttachments[slotName] = attachments = [];
attachments[att.Name] = att switch
{
RegionAttachment regionAtt => new RegionAttachment35(regionAtt),
MeshAttachment meshAtt => new MeshAttachment35(meshAtt),
ClippingAttachment clipAtt => new ClippingAttachment35(clipAtt),
BoundingBoxAttachment bbAtt => new BoundingBoxAttachment35(bbAtt),
PathAttachment pathAtt => new PathAttachment35(pathAtt),
PointAttachment ptAtt => new PointAttachment35(ptAtt),
_ => throw new InvalidOperationException($"Unrecognized attachment type {att.GetType().FullName}")
};
}
}
_slotAttachments = slotAttachments.ToFrozenDictionary(it => it.Key, it => it.Value.ToFrozenDictionary());
_skins = skins.ToImmutableArray();
_skinsByName = skinsByName.ToFrozenDictionary();
// 整理所有动画数据
List<IAnimation> animations = [];
Dictionary<string, IAnimation> animationsByName = [];
foreach (var a in _skeletonData.Animations)
{
var anime = new Animation35(a);
animations.Add(anime);
animationsByName[anime.Name] = anime;
}
_animations = animations.ToImmutableArray();
_animationsByName = animationsByName.ToFrozenDictionary();
}
public override string SkeletonVersion => _skeletonData.Version;
public override ImmutableArray<ISkin> Skins => _skins;
public override FrozenDictionary<string, ISkin> SkinsByName => _skinsByName;
public override FrozenDictionary<string, FrozenDictionary<string, IAttachment>> SlotAttachments => _slotAttachments;
public override float DefaultMix { get => _animationStateData.DefaultMix; set => _animationStateData.DefaultMix = value; }
public override ImmutableArray<IAnimation> Animations => _animations;
public override FrozenDictionary<string, IAnimation> AnimationsByName => _animationsByName;
protected override void DisposeAtlas() => _atlas.Dispose();
public override ISkeleton CreateSkeleton() => new Skeleton35(new(_skeletonData), this);
public override IAnimationState CreateAnimationState() => new AnimationState35(new(_animationStateData), this);
public override ISkeletonClipping CreateSkeletonClipping() => new SkeletonClipping35();
public override ISkin CreateSkin(string name) => new Skin35(name);
}
}

View File

@@ -0,0 +1,185 @@
using Spine.SpineWrappers;
using SpineRuntime35;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V35
{
internal sealed class TrackEntry35(TrackEntry innerObject, AnimationState35 animationState, SpineObjectData35 data): ITrackEntry
{
private readonly TrackEntry _o = innerObject;
private readonly AnimationState35 _animationState = animationState;
private readonly SpineObjectData35 _data = data;
private readonly Dictionary<IAnimationState.TrackEntryDelegate, AnimationState.TrackEntryDelegate> _eventMapping = [];
private readonly Dictionary<IAnimationState.TrackEntryDelegate, int> _eventCount = [];
public TrackEntry InnerObject => _o;
public event IAnimationState.TrackEntryDelegate? Start
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Start += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Start -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? Interrupt
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Interrupt += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Interrupt -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? End
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.End += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.End -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? Complete
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Complete += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Complete -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? Dispose
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Dispose += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Dispose -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public int TrackIndex { get => _o.TrackIndex; }
public IAnimation Animation { get => _data.AnimationsByName[_o.Animation.Name]; }
public ITrackEntry? Next { get { var t = _o.Next; return t is null ? null : _animationState.GetTrackEntry(t); } }
public bool Loop { get => _o.Loop; set => _o.Loop = value; }
public float TrackTime { get => _o.TrackTime; set => _o.TrackTime = value; }
public float TimeScale { get => _o.TimeScale; set => _o.TimeScale = value; }
public float Alpha { get => _o.Alpha; set => _o.Alpha = value; }
public float MixDuration { get => _o.MixDuration; set => _o.MixDuration = value; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V36
}
}
public bool Disabled { get; set; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V37
}
}
public bool Disabled { get; set; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -74,6 +74,8 @@ namespace Spine.Implementations.SpineWrappers.V38
}
}
public bool Disabled { get; set; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V40
}
}
public bool Disabled { get; set; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V41
}
}
public bool Disabled { get; set; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V42
}
}
public bool Disabled { get; set; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -7,7 +7,7 @@
<TargetFramework>net8.0-windows</TargetFramework>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.15.6</Version>
<Version>0.15.10</Version>
</PropertyGroup>
<PropertyGroup>
@@ -23,6 +23,8 @@
<ItemGroup>
<ProjectReference Include="..\SpineRuntimes\SpineRuntime21\SpineRuntime21.csproj" />
<ProjectReference Include="..\SpineRuntimes\SpineRuntime34\SpineRuntime34.csproj" />
<ProjectReference Include="..\SpineRuntimes\SpineRuntime35\SpineRuntime35.csproj" />
<ProjectReference Include="..\SpineRuntimes\SpineRuntime36\SpineRuntime36.csproj" />
<ProjectReference Include="..\SpineRuntimes\SpineRuntime37\SpineRuntime37.csproj" />
<ProjectReference Include="..\SpineRuntimes\SpineRuntime38\SpineRuntime38.csproj" />

View File

@@ -172,9 +172,12 @@ namespace Spine
_skinLoadStatus = other._skinLoadStatus.ToDictionary();
ReloadSkins();
// 拷贝自定义插槽附件加载情况
// 拷贝插槽属性值
for (int i = 0; i < other._skeleton.Slots.Length; i++)
{
_skeleton.Slots[i].Attachment = other._skeleton.Slots[i].Attachment;
_skeleton.Slots[i].Disabled = other._skeleton.Slots[i].Disabled;
}
// 拷贝调试属性
EnableDebug = other.EnableDebug;
@@ -299,6 +302,30 @@ namespace Spine
/// </summary>
public bool DebugClippings { get; set; }
/// <summary>
/// 获取插槽可见性, 如果不存在则默认返回 false
/// </summary>
public bool GetSlotVisible(string slotName)
{
if (_skeleton.SlotsByName.TryGetValue(slotName, out var slot))
return !slot.Disabled;
return false;
}
/// <summary>
/// 设置插槽可见性, 插槽不可见后将不会在任何渲染中出现, 插槽不存在则忽略操作
/// </summary>
/// <returns>操作是否成功, 插槽不存在则返回 false</returns>
public bool SetSlotVisible(string slotName, bool visible)
{
if (_skeleton.SlotsByName.TryGetValue(slotName, out var slot))
{
slot.Disabled = !visible;
return true;
}
return false;
}
/// <summary>
/// 获取某个插槽上的附件名, 插槽不存在或者无附件均返回 null
/// </summary>
@@ -310,7 +337,7 @@ namespace Spine
}
/// <summary>
/// 设置某个插槽的附件, 如果不存在则忽略, 可以使用 null 来清除附件
/// 设置某个插槽的附件, 如果不存在则忽略, 可以使用 null 来尝试清除附件
/// </summary>
/// <returns>是否操作成功</returns>
public bool SetAttachment(string slotName, string? attachmentName)
@@ -471,14 +498,13 @@ namespace Spine
foreach (var slot in _skeleton.IterDrawOrder())
{
if (slot.A <= 0 || !slot.Bone.Active)
if (slot.A <= 0 || !slot.Bone.Active || slot.Disabled)
{
_clipping.ClipEnd(slot);
continue;
}
var attachment = slot.Attachment;
SFML.Graphics.Texture texture;
float[] worldVertices; // 顶点世界坐标数组, 连续的 [x0, y0, x1, y1, ...] 坐标值
int worldVerticesLength; // 顶点数组的长度
@@ -491,10 +517,11 @@ namespace Spine
float tintB = _skeleton.B * slot.B;
float tintA = _skeleton.A * slot.A;
SFML.Graphics.Texture texture;
switch (attachment)
{
case IRegionAttachment regionAttachment:
texture = regionAttachment.RendererObject;
worldVerticesLength = regionAttachment.ComputeWorldVertices(slot, ref _worldVertices);
worldVertices = _worldVertices;
triangles = regionAttachment.Triangles;
@@ -504,9 +531,11 @@ namespace Spine
tintG *= regionAttachment.G;
tintB *= regionAttachment.B;
tintA *= regionAttachment.A;
// NOTE: RenderObject 的获取要在 ComputeWorldVertices 发生之后, 否则可能存在某些 Region 尚未被赋值产生 null 引用报错
texture = regionAttachment.RendererObject;
break;
case IMeshAttachment meshAttachment:
texture = meshAttachment.RendererObject;
worldVerticesLength = meshAttachment.ComputeWorldVertices(slot, ref _worldVertices);
worldVertices = _worldVertices;
triangles = meshAttachment.Triangles;
@@ -516,9 +545,9 @@ namespace Spine
tintG *= meshAttachment.G;
tintB *= meshAttachment.B;
tintA *= meshAttachment.A;
texture = meshAttachment.RendererObject;
break;
case ISkinnedMeshAttachment skinnedMeshAttachment:
texture = skinnedMeshAttachment.RendererObject;
worldVerticesLength = skinnedMeshAttachment.ComputeWorldVertices(slot, ref _worldVertices);
worldVertices = _worldVertices;
triangles = skinnedMeshAttachment.Triangles;
@@ -528,6 +557,7 @@ namespace Spine
tintG *= skinnedMeshAttachment.G;
tintB *= skinnedMeshAttachment.B;
tintA *= skinnedMeshAttachment.A;
texture = skinnedMeshAttachment.RendererObject;
break;
case IClippingAttachment clippingAttachment:
_clipping.ClipStart(slot, clippingAttachment);
@@ -599,7 +629,7 @@ namespace Spine
if (DebugRegions)
{
vt.Color = AttachmentLineColor;
foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active))
foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active && !s.Disabled))
{
if (slot.Attachment is IRegionAttachment regionAttachment)
{
@@ -631,7 +661,7 @@ namespace Spine
if (DebugMeshes)
{
vt.Color = MeshLineColor;
foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active))
foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active && !s.Disabled))
{
if (slot.Attachment is IMeshAttachment meshAttachment)
{
@@ -695,7 +725,7 @@ namespace Spine
if (DebugMeshHulls)
{
vt.Color = AttachmentLineColor;
foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active))
foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active && !s.Disabled))
{
if (slot.Attachment is IMeshAttachment meshAttachment)
{
@@ -764,7 +794,7 @@ namespace Spine
if (DebugClippings)
{
vt.Color = ClippingLineColor;
foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active))
foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active && !s.Disabled))
{
if (slot.Attachment is IClippingAttachment clippingAttachment)
{

View File

@@ -19,6 +19,8 @@ namespace Spine
public sealed class SpineVersion : IEquatable<SpineVersion>, IComparable<SpineVersion>
{
public static readonly SpineVersion V21 = new(typeof(SpineRuntime21.Skeleton));
public static readonly SpineVersion V34 = new(typeof(SpineRuntime34.Skeleton));
public static readonly SpineVersion V35 = new(typeof(SpineRuntime35.Skeleton));
public static readonly SpineVersion V36 = new(typeof(SpineRuntime36.Skeleton));
public static readonly SpineVersion V37 = new(typeof(SpineRuntime37.Skeleton));
public static readonly SpineVersion V38 = new(typeof(SpineRuntime38.Skeleton));

View File

@@ -53,5 +53,10 @@ namespace Spine.SpineWrappers
/// 使用的附件, 可以设置为 null 清空附件
/// </summary>
public IAttachment? Attachment { get; set; }
/// <summary>
/// 是否已禁用渲染该插槽
/// </summary>
public bool Disabled { get; set; }
}
}

View File

@@ -11,6 +11,8 @@ namespace Spine.SpineWrappers
/// </summary>
public class TextureLoader :
SpineRuntime21.TextureLoader,
SpineRuntime34.TextureLoader,
SpineRuntime35.TextureLoader,
SpineRuntime36.TextureLoader,
SpineRuntime37.TextureLoader,
SpineRuntime38.TextureLoader,
@@ -106,6 +108,76 @@ namespace Spine.SpineWrappers
page.rendererObject = texture;
}
public virtual void Load(SpineRuntime34.AtlasPage page, string path)
{
var texture = ReadTexture(path);
if (page.magFilter == SpineRuntime34.TextureFilter.Linear)
{
texture.Smooth = true;
}
if (page.uWrap == SpineRuntime34.TextureWrap.Repeat && page.vWrap == SpineRuntime34.TextureWrap.Repeat)
{
texture.Repeated = true;
}
switch (page.minFilter)
{
case SpineRuntime34.TextureFilter.Linear:
texture.Smooth = true;
break;
case SpineRuntime34.TextureFilter.MipMap:
case SpineRuntime34.TextureFilter.MipMapNearestNearest:
texture.GenerateMipmap();
break;
case SpineRuntime34.TextureFilter.MipMapLinearNearest:
case SpineRuntime34.TextureFilter.MipMapNearestLinear:
case SpineRuntime34.TextureFilter.MipMapLinearLinear:
texture.Smooth = true;
texture.GenerateMipmap();
break;
}
if (ForceNearest) texture.Smooth = false;
if (ForceMipmap) texture.GenerateMipmap();
page.rendererObject = texture;
}
public virtual void Load(SpineRuntime35.AtlasPage page, string path)
{
var texture = ReadTexture(path);
if (page.magFilter == SpineRuntime35.TextureFilter.Linear)
{
texture.Smooth = true;
}
if (page.uWrap == SpineRuntime35.TextureWrap.Repeat && page.vWrap == SpineRuntime35.TextureWrap.Repeat)
{
texture.Repeated = true;
}
switch (page.minFilter)
{
case SpineRuntime35.TextureFilter.Linear:
texture.Smooth = true;
break;
case SpineRuntime35.TextureFilter.MipMap:
case SpineRuntime35.TextureFilter.MipMapNearestNearest:
texture.GenerateMipmap();
break;
case SpineRuntime35.TextureFilter.MipMapLinearNearest:
case SpineRuntime35.TextureFilter.MipMapNearestLinear:
case SpineRuntime35.TextureFilter.MipMapLinearLinear:
texture.Smooth = true;
texture.GenerateMipmap();
break;
}
if (ForceNearest) texture.Smooth = false;
if (ForceMipmap) texture.GenerateMipmap();
page.rendererObject = texture;
}
public virtual void Load(SpineRuntime36.AtlasPage page, string path)
{
var texture = ReadTexture(path);

View File

@@ -38,7 +38,7 @@ namespace SpineRuntime21 {
static readonly Animation EmptyAnimation = new Animation("<empty>", new List<Timeline>(), 0);
private AnimationStateData data;
Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
private readonly Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
private List<TrackEntry> tracks = new List<TrackEntry>();
private List<Event> events = new List<Event>();
private float timeScale = 1;

View File

@@ -0,0 +1,874 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.Collections.Generic;
namespace SpineRuntime34 {
public class Animation {
internal ExposedList<Timeline> timelines;
internal float duration;
internal String name;
public String Name { get { return name; } }
public ExposedList<Timeline> Timelines { get { return timelines; } set { timelines = value; } }
public float Duration { get { return duration; } set { duration = value; } }
public Animation (String name, ExposedList<Timeline> timelines, float duration) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null.");
this.name = name;
this.timelines = timelines;
this.duration = duration;
}
/// <summary>Poses the skeleton at the specified time for this animation.</summary>
/// <param name="lastTime">The last time the animation was applied.</param>
/// <param name="events">Any triggered events are added. May be null.</param>
public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList<Event> events) {
if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
if (loop && duration != 0) {
time %= duration;
if (lastTime > 0) lastTime %= duration;
}
ExposedList<Timeline> timelines = this.timelines;
for (int i = 0, n = timelines.Count; i < n; i++)
timelines.Items[i].Apply(skeleton, lastTime, time, events, 1);
}
/// <summary>Poses the skeleton at the specified time for this animation mixed with the current pose.</summary>
/// <param name="lastTime">The last time the animation was applied.</param>
/// <param name="events">Any triggered events are added. May be null.</param>
/// <param name="alpha">The amount of this animation that affects the current pose.</param>
public void Mix (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList<Event> events, float alpha) {
if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
if (loop && duration != 0) {
time %= duration;
if (lastTime > 0) lastTime %= duration;
}
ExposedList<Timeline> timelines = this.timelines;
for (int i = 0, n = timelines.Count; i < n; i++)
timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha);
}
/// <param name="target">After the first and before the last entry.</param>
internal static int binarySearch (float[] values, float target, int step) {
int low = 0;
int high = values.Length / step - 2;
if (high == 0) return step;
int current = (int)((uint)high >> 1);
while (true) {
if (values[(current + 1) * step] <= target)
low = current + 1;
else
high = current;
if (low == high) return (low + 1) * step;
current = (int)((uint)(low + high) >> 1);
}
}
/// <param name="target">After the first and before the last entry.</param>
internal static int binarySearch (float[] values, float target) {
int low = 0;
int high = values.Length - 2;
if (high == 0) return 1;
int current = (int)((uint)high >> 1);
while (true) {
if (values[(current + 1)] <= target)
low = current + 1;
else
high = current;
if (low == high) return (low + 1);
current = (int)((uint)(low + high) >> 1);
}
}
internal static int linearSearch (float[] values, float target, int step) {
for (int i = 0, last = values.Length - step; i <= last; i += step)
if (values[i] > target) return i;
return -1;
}
}
public interface Timeline {
/// <summary>Sets the value(s) for the specified time.</summary>
/// <param name="events">May be null to not collect fired events.</param>
void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha);
}
/// <summary>Base class for frames that use an interpolation bezier curve.</summary>
abstract public class CurveTimeline : Timeline {
protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2;
protected const int BEZIER_SIZE = 10 * 2 - 1;
private float[] curves; // type, x, y, ...
public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } }
public CurveTimeline (int frameCount) {
if (frameCount <= 0) throw new ArgumentException("frameCount must be > 0: " + frameCount, "frameCount");
curves = new float[(frameCount - 1) * BEZIER_SIZE];
}
abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha);
public void SetLinear (int frameIndex) {
curves[frameIndex * BEZIER_SIZE] = LINEAR;
}
public void SetStepped (int frameIndex) {
curves[frameIndex * BEZIER_SIZE] = STEPPED;
}
/// <summary>Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next.
/// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of
/// the difference between the keyframe's values.</summary>
public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) {
float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f;
float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f;
float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy;
float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f;
int i = frameIndex * BEZIER_SIZE;
float[] curves = this.curves;
curves[i++] = BEZIER;
float x = dfx, y = dfy;
for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) {
curves[i] = x;
curves[i + 1] = y;
dfx += ddfx;
dfy += ddfy;
ddfx += dddfx;
ddfy += dddfy;
x += dfx;
y += dfy;
}
}
public float GetCurvePercent (int frameIndex, float percent) {
percent = MathUtils.Clamp (percent, 0, 1);
float[] curves = this.curves;
int i = frameIndex * BEZIER_SIZE;
float type = curves[i];
if (type == LINEAR) return percent;
if (type == STEPPED) return 0;
i++;
float x = 0;
for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) {
x = curves[i];
if (x >= percent) {
float prevX, prevY;
if (i == start) {
prevX = 0;
prevY = 0;
} else {
prevX = curves[i - 2];
prevY = curves[i - 1];
}
return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX);
}
}
float y = curves[i - 1];
return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1.
}
public float GetCurveType (int frameIndex) {
return curves[frameIndex * BEZIER_SIZE];
}
}
public class RotateTimeline : CurveTimeline {
public const int ENTRIES = 2;
internal const int PREV_TIME = -2, PREV_ROTATION = -1;
internal const int ROTATION = 1;
internal int boneIndex;
internal float[] frames;
public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ...
public RotateTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount << 1];
}
/// <summary>Sets the time and value of the specified keyframe.</summary>
public void SetFrame (int frameIndex, float time, float degrees) {
frameIndex <<= 1;
frames[frameIndex] = time;
frames[frameIndex + ROTATION] = degrees;
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.bones.Items[boneIndex];
float amount;
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
amount = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation;
while (amount > 180)
amount -= 360;
while (amount < -180)
amount += 360;
bone.rotation += amount * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, ENTRIES);
float prevRotation = frames[frame + PREV_ROTATION];
float frameTime = frames[frame];
float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
amount = frames[frame + ROTATION] - prevRotation;
while (amount > 180)
amount -= 360;
while (amount < -180)
amount += 360;
amount = bone.data.rotation + (prevRotation + amount * percent) - bone.rotation;
while (amount > 180)
amount -= 360;
while (amount < -180)
amount += 360;
bone.rotation += amount * alpha;
}
}
public class TranslateTimeline : CurveTimeline {
public const int ENTRIES = 3;
protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1;
protected const int X = 1, Y = 2;
internal int boneIndex;
internal float[] frames;
public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ...
public TranslateTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount * ENTRIES];
}
/// <summary>Sets the time and value of the specified keyframe.</summary>
public void SetFrame (int frameIndex, float time, float x, float y) {
frameIndex *= ENTRIES;
frames[frameIndex] = time;
frames[frameIndex + X] = x;
frames[frameIndex + Y] = y;
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.bones.Items[boneIndex];
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
bone.x += (bone.data.x + frames[frames.Length + PREV_X] - bone.x) * alpha;
bone.y += (bone.data.y + frames[frames.Length + PREV_Y] - bone.y) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, ENTRIES);
float prevX = frames[frame + PREV_X];
float prevY = frames[frame + PREV_Y];
float frameTime = frames[frame];
float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
bone.x += (bone.data.x + prevX + (frames[frame + X] - prevX) * percent - bone.x) * alpha;
bone.y += (bone.data.y + prevY + (frames[frame + Y] - prevY) * percent - bone.y) * alpha;
}
}
public class ScaleTimeline : TranslateTimeline {
public ScaleTimeline (int frameCount)
: base(frameCount) {
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.bones.Items[boneIndex];
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
bone.scaleX += (bone.data.scaleX * frames[frames.Length + PREV_X] - bone.scaleX) * alpha;
bone.scaleY += (bone.data.scaleY * frames[frames.Length + PREV_Y] - bone.scaleY) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, ENTRIES);
float prevX = frames[frame + PREV_X];
float prevY = frames[frame + PREV_Y];
float frameTime = frames[frame];
float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
bone.scaleX += (bone.data.scaleX * (prevX + (frames[frame + X] - prevX) * percent) - bone.scaleX) * alpha;
bone.scaleY += (bone.data.scaleY * (prevY + (frames[frame + Y] - prevY) * percent) - bone.scaleY) * alpha;
}
}
public class ShearTimeline : TranslateTimeline {
public ShearTimeline (int frameCount)
: base(frameCount) {
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.bones.Items[boneIndex];
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
bone.shearX += (bone.data.shearX + frames[frames.Length + PREV_X] - bone.shearX) * alpha;
bone.shearY += (bone.data.shearY + frames[frames.Length + PREV_Y] - bone.shearY) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, ENTRIES);
float prevX = frames[frame + PREV_X];
float prevY = frames[frame + PREV_Y];
float frameTime = frames[frame];
float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
bone.shearX += (bone.data.shearX + (prevX + (frames[frame + X] - prevX) * percent) - bone.shearX) * alpha;
bone.shearY += (bone.data.shearY + (prevY + (frames[frame + Y] - prevY) * percent) - bone.shearY) * alpha;
}
}
public class ColorTimeline : CurveTimeline {
public const int ENTRIES = 5;
protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1;
protected const int R = 1, G = 2, B = 3, A = 4;
internal int slotIndex;
internal float[] frames;
public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ...
public ColorTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount * ENTRIES];
}
/// <summary>Sets the time and value of the specified keyframe.</summary>
public void SetFrame (int frameIndex, float time, float r, float g, float b, float a) {
frameIndex *= ENTRIES;
frames[frameIndex] = time;
frames[frameIndex + R] = r;
frames[frameIndex + G] = g;
frames[frameIndex + B] = b;
frames[frameIndex + A] = a;
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
float r, g, b, a;
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
int i = frames.Length;
r = frames[i + PREV_R];
g = frames[i + PREV_G];
b = frames[i + PREV_B];
a = frames[i + PREV_A];
} else {
// Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, ENTRIES);
r = frames[frame + PREV_R];
g = frames[frame + PREV_G];
b = frames[frame + PREV_B];
a = frames[frame + PREV_A];
float frameTime = frames[frame];
float percent = GetCurvePercent(frame / ENTRIES - 1,
1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
r += (frames[frame + R] - r) * percent;
g += (frames[frame + G] - g) * percent;
b += (frames[frame + B] - b) * percent;
a += (frames[frame + A] - a) * percent;
}
Slot slot = skeleton.slots.Items[slotIndex];
if (alpha < 1) {
slot.r += (r - slot.r) * alpha;
slot.g += (g - slot.g) * alpha;
slot.b += (b - slot.b) * alpha;
slot.a += (a - slot.a) * alpha;
} else {
slot.r = r;
slot.g = g;
slot.b = b;
slot.a = a;
}
}
}
public class AttachmentTimeline : Timeline {
internal int slotIndex;
internal float[] frames;
private String[] attachmentNames;
public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } }
public int FrameCount { get { return frames.Length; } }
public AttachmentTimeline (int frameCount) {
frames = new float[frameCount];
attachmentNames = new String[frameCount];
}
/// <summary>Sets the time and value of the specified keyframe.</summary>
public void SetFrame (int frameIndex, float time, String attachmentName) {
frames[frameIndex] = time;
attachmentNames[frameIndex] = attachmentName;
}
public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
int frameIndex;
if (time >= frames[frames.Length - 1]) // Time is after last frame.
frameIndex = frames.Length - 1;
else
frameIndex = Animation.binarySearch(frames, time, 1) - 1;
String attachmentName = attachmentNames[frameIndex];
skeleton.slots.Items[slotIndex]
.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
}
}
public class EventTimeline : Timeline {
internal float[] frames;
private Event[] events;
public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
public Event[] Events { get { return events; } set { events = value; } }
public int FrameCount { get { return frames.Length; } }
public EventTimeline (int frameCount) {
frames = new float[frameCount];
events = new Event[frameCount];
}
/// <summary>Sets the time and value of the specified keyframe.</summary>
public void SetFrame (int frameIndex, Event e) {
frames[frameIndex] = e.Time;
events[frameIndex] = e;
}
/// <summary>Fires events for frames &gt; lastTime and &lt;= time.</summary>
public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
if (firedEvents == null) return;
float[] frames = this.frames;
int frameCount = frames.Length;
if (lastTime > time) { // Fire events after last time for looped animations.
Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha);
lastTime = -1f;
} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
return;
if (time < frames[0]) return; // Time is before first frame.
int frame;
if (lastTime < frames[0])
frame = 0;
else {
frame = Animation.binarySearch(frames, lastTime);
float frameTime = frames[frame];
while (frame > 0) { // Fire multiple events with the same frame.
if (frames[frame - 1] != frameTime) break;
frame--;
}
}
for (; frame < frameCount && time >= frames[frame]; frame++)
firedEvents.Add(events[frame]);
}
}
public class DrawOrderTimeline : Timeline {
internal float[] frames;
private int[][] drawOrders;
public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } }
public int FrameCount { get { return frames.Length; } }
public DrawOrderTimeline (int frameCount) {
frames = new float[frameCount];
drawOrders = new int[frameCount][];
}
/// <summary>Sets the time and value of the specified keyframe.</summary>
/// <param name="drawOrder">May be null to use bind pose draw order.</param>
public void SetFrame (int frameIndex, float time, int[] drawOrder) {
frames[frameIndex] = time;
drawOrders[frameIndex] = drawOrder;
}
public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
int frame;
if (time >= frames[frames.Length - 1]) // Time is after last frame.
frame = frames.Length - 1;
else
frame = Animation.binarySearch(frames, time) - 1;
ExposedList<Slot> drawOrder = skeleton.drawOrder;
ExposedList<Slot> slots = skeleton.slots;
int[] drawOrderToSetupIndex = drawOrders[frame];
if (drawOrderToSetupIndex == null) {
drawOrder.Clear();
for (int i = 0, n = slots.Count; i < n; i++)
drawOrder.Add(slots.Items[i]);
} else {
var drawOrderItems = drawOrder.Items;
var slotsItems = slots.Items;
for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++)
drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]];
}
}
}
public class DeformTimeline : CurveTimeline {
internal int slotIndex;
internal float[] frames;
private float[][] frameVertices;
internal VertexAttachment attachment;
public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } }
public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } }
public DeformTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount];
frameVertices = new float[frameCount][];
}
/// <summary>Sets the time and value of the specified keyframe.</summary>
public void SetFrame (int frameIndex, float time, float[] vertices) {
frames[frameIndex] = time;
frameVertices[frameIndex] = vertices;
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
Slot slot = skeleton.slots.Items[slotIndex];
VertexAttachment slotAttachment = slot.attachment as VertexAttachment;
if (slotAttachment == null || !slotAttachment.ApplyDeform(attachment)) return;
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
float[][] frameVertices = this.frameVertices;
int vertexCount = frameVertices[0].Length;
var verticesArray = slot.attachmentVertices;
if (verticesArray.Count != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices.
// verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count.
if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount;
verticesArray.Count = vertexCount;
float[] vertices = verticesArray.Items;
if (time >= frames[frames.Length - 1]) { // Time is after last frame.
float[] lastVertices = frameVertices[frames.Length - 1];
if (alpha < 1) {
for (int i = 0; i < vertexCount; i++) {
float vertex = vertices[i];
vertices[i] = vertex + (lastVertices[i] - vertex) * alpha;
}
} else
Array.Copy(lastVertices, 0, vertices, 0, vertexCount);
return;
}
// Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time);
float[] prevVertices = frameVertices[frame - 1];
float[] nextVertices = frameVertices[frame];
float frameTime = frames[frame];
float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime));
if (alpha < 1) {
for (int i = 0; i < vertexCount; i++) {
float prev = prevVertices[i];
float vertex = vertices[i];
vertices[i] = vertex + (prev + (nextVertices[i] - prev) * percent - vertex) * alpha;
}
} else {
for (int i = 0; i < vertexCount; i++) {
float prev = prevVertices[i];
vertices[i] = prev + (nextVertices[i] - prev) * percent;
}
}
}
}
public class IkConstraintTimeline : CurveTimeline {
public const int ENTRIES = 3;
private const int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1;
private const int MIX = 1, BEND_DIRECTION = 2;
internal int ikConstraintIndex;
internal float[] frames;
public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ...
public IkConstraintTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount * ENTRIES];
}
/// <summary>Sets the time, mix and bend direction of the specified keyframe.</summary>
public void SetFrame (int frameIndex, float time, float mix, int bendDirection) {
frameIndex *= ENTRIES;
frames[frameIndex] = time;
frames[frameIndex + MIX] = mix;
frames[frameIndex + BEND_DIRECTION] = bendDirection;
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex];
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha;
constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
return;
}
// Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, ENTRIES);
float mix = frames[frame + PREV_MIX];
float frameTime = frames[frame];
float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha;
constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
}
}
public class TransformConstraintTimeline : CurveTimeline {
public const int ENTRIES = 5;
private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1;
private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4;
internal int transformConstraintIndex;
internal float[] frames;
public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ...
public TransformConstraintTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount * ENTRIES];
}
public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) {
frameIndex *= ENTRIES;
frames[frameIndex] = time;
frames[frameIndex + ROTATE] = rotateMix;
frames[frameIndex + TRANSLATE] = translateMix;
frames[frameIndex + SCALE] = scaleMix;
frames[frameIndex + SHEAR] = shearMix;
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex];
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
int i = frames.Length;
constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha;
constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha;
constraint.scaleMix += (frames[i + PREV_SCALE] - constraint.scaleMix) * alpha;
constraint.shearMix += (frames[i + PREV_SHEAR] - constraint.shearMix) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, ENTRIES);
float frameTime = frames[frame];
float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
float rotate = frames[frame + PREV_ROTATE];
float translate = frames[frame + PREV_TRANSLATE];
float scale = frames[frame + PREV_SCALE];
float shear = frames[frame + PREV_SHEAR];
constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha;
constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix)
* alpha;
constraint.scaleMix += (scale + (frames[frame + SCALE] - scale) * percent - constraint.scaleMix) * alpha;
constraint.shearMix += (shear + (frames[frame + SHEAR] - shear) * percent - constraint.shearMix) * alpha;
}
}
public class PathConstraintPositionTimeline : CurveTimeline {
public const int ENTRIES = 2;
protected const int PREV_TIME = -2, PREV_VALUE = -1;
protected const int VALUE = 1;
internal int pathConstraintIndex;
internal float[] frames;
public PathConstraintPositionTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount * ENTRIES];
}
public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ...
/// <summary>Sets the time and value of the specified keyframe.</summary>
public void SetFrame (int frameIndex, float time, float value) {
frameIndex *= ENTRIES;
frames[frameIndex] = time;
frames[frameIndex + VALUE] = value;
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
int i = frames.Length;
constraint.position += (frames[i + PREV_VALUE] - constraint.position) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, ENTRIES);
float position = frames[frame + PREV_VALUE];
float frameTime = frames[frame];
float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
constraint.position += (position + (frames[frame + VALUE] - position) * percent - constraint.position) * alpha;
}
}
public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline {
public PathConstraintSpacingTimeline (int frameCount)
: base(frameCount) {
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
int i = frames.Length;
constraint.spacing += (frames[i + PREV_VALUE] - constraint.spacing) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, ENTRIES);
float spacing = frames[frame + PREV_VALUE];
float frameTime = frames[frame];
float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
constraint.spacing += (spacing + (frames[frame + VALUE] - spacing) * percent - constraint.spacing) * alpha;
}
}
public class PathConstraintMixTimeline : CurveTimeline {
public const int ENTRIES = 3;
private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1;
private const int ROTATE = 1, TRANSLATE = 2;
internal int pathConstraintIndex;
internal float[] frames;
public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ...
public PathConstraintMixTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount * ENTRIES];
}
/** Sets the time and mixes of the specified keyframe. */
public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix) {
frameIndex *= ENTRIES;
frames[frameIndex] = time;
frames[frameIndex + ROTATE] = rotateMix;
frames[frameIndex + TRANSLATE] = translateMix;
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
int i = frames.Length;
constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha;
constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, ENTRIES);
float rotate = frames[frame + PREV_ROTATE];
float translate = frames[frame + PREV_TRANSLATE];
float frameTime = frames[frame];
float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha;
constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix)
* alpha;
}
}
}

View File

@@ -0,0 +1,446 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.Collections.Generic;
using System.Text;
namespace SpineRuntime34 {
public class AnimationState {
static readonly Animation EmptyAnimation = new Animation("<empty>", new ExposedList<Timeline>(), 0);
private AnimationStateData data;
private ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>();
private ExposedList<Event> events = new ExposedList<Event>();
private float timeScale = 1;
public AnimationStateData Data { get { return data; } }
private readonly Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
/// <summary>A list of tracks that have animations, which may contain nulls.</summary>
public ExposedList<TrackEntry> Tracks { get { return tracks; } }
public float TimeScale { get { return timeScale; } set { timeScale = value; } }
public delegate void TrackEntryDelegate(TrackEntry trackEntry);
public event TrackEntryDelegate Start, End, Complete;
public delegate void EventDelegate (AnimationState state, int trackIndex, Event e);
public event EventDelegate Event;
public AnimationState (AnimationStateData data) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
this.data = data;
}
public void Update (float delta) {
delta *= timeScale;
for (int i = 0; i < tracks.Count; i++) {
TrackEntry current = tracks.Items[i];
if (current == null) continue;
float trackDelta = delta * current.timeScale;
float time = current.time + trackDelta;
float endTime = current.endTime;
current.time = time;
if (current.previous != null) {
current.previous.time += trackDelta;
current.mixTime += trackDelta;
}
// Check if completed the animation or a loop iteration.
if (current.loop ? (current.lastTime % endTime > time % endTime) : (current.lastTime < endTime && time >= endTime)) {
int count = (int)(time / endTime);
current.OnComplete();
if (Complete != null) Complete(current);
}
TrackEntry next = current.next;
if (next != null) {
next.time = current.lastTime - next.delay;
if (next.time >= 0) SetCurrent(i, next);
} else {
// End non-looping animation when it reaches its end time and there is no next entry.
if (!current.loop && current.lastTime >= current.endTime) ClearTrack(i);
}
}
}
public void Apply (Skeleton skeleton) {
ExposedList<Event> events = this.events;
for (int i = 0; i < tracks.Count; i++) {
TrackEntry current = tracks.Items[i];
if (current == null) continue;
events.Clear();
float time = current.time;
bool loop = current.loop;
if (!loop && time > current.endTime) time = current.endTime;
TrackEntry previous = current.previous;
if (previous == null) {
if (current.mix == 1)
current.animation.Apply(skeleton, current.lastTime, time, loop, events);
else
current.animation.Mix(skeleton, current.lastTime, time, loop, events, current.mix);
} else {
float previousTime = previous.time;
if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime;
previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, null);
// Remove the line above, and uncomment the line below, to allow previous animations to fire events during mixing.
//previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, events);
previous.lastTime = previousTime;
float alpha = current.mixTime / current.mixDuration * current.mix;
if (alpha >= 1) {
alpha = 1;
current.previous = null;
}
current.animation.Mix(skeleton, current.lastTime, time, loop, events, alpha);
}
for (int ii = 0, nn = events.Count; ii < nn; ii++) {
Event e = events.Items[ii];
current.OnEvent(this, i, e);
if (Event != null) Event(this, i, e);
}
current.lastTime = current.time;
}
}
public void ClearTracks () {
for (int i = 0, n = tracks.Count; i < n; i++)
ClearTrack(i);
tracks.Clear();
}
public void ClearTrack (int trackIndex) {
if (trackIndex >= tracks.Count) return;
TrackEntry current = tracks.Items[trackIndex];
if (current == null) return;
current.OnEnd();
if (End != null) End(current);
tracks.Items[trackIndex] = null;
while (current is not null)
{
var tmp = current.next;
trackEntryPool.Free(current);
current = tmp;
}
}
private TrackEntry ExpandToIndex (int index) {
if (index < tracks.Count) return tracks.Items[index];
while (index >= tracks.Count)
tracks.Add(null);
return null;
}
private void SetCurrent (int index, TrackEntry entry) {
TrackEntry current = ExpandToIndex(index);
if (current != null) {
TrackEntry previous = current.previous;
current.previous = null;
current.OnEnd();
if (End != null) End(current);
entry.mixDuration = data.GetMix(current.animation, entry.animation);
if (entry.mixDuration > 0) {
entry.mixTime = 0;
// If a mix is in progress, mix from the closest animation.
if (previous != null && current.mixTime / current.mixDuration < 0.5f)
entry.previous = previous;
else
entry.previous = current;
}
}
tracks.Items[index] = entry;
while (current is not null)
{
var tmp = current.next;
trackEntryPool.Free(current);
current = tmp;
}
entry.OnStart();
if (Start != null) Start(entry);
}
/// <seealso cref="SetAnimation(int, Animation, bool)" />
public TrackEntry SetAnimation (int trackIndex, String animationName, bool loop) {
Animation animation = data.skeletonData.FindAnimation(animationName);
if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName");
return SetAnimation(trackIndex, animation, loop);
}
/// <summary>Set the current animation. Any queued animations are cleared.</summary>
public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) {
if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null.");
TrackEntry entry = trackEntryPool.Obtain();
entry.trackIndex = trackIndex;
entry.animation = animation;
entry.loop = loop;
entry.time = 0;
entry.endTime = animation.Duration;
SetCurrent(trackIndex, entry);
return entry;
}
/// <seealso cref="AddAnimation(int, Animation, bool, float)" />
public TrackEntry AddAnimation (int trackIndex, String animationName, bool loop, float delay) {
Animation animation = data.skeletonData.FindAnimation(animationName);
if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName");
return AddAnimation(trackIndex, animation, loop, delay);
}
/// <summary>Adds an animation to be played delay seconds after the current or last queued animation.</summary>
/// <param name="delay">May be &lt;= 0 to use duration of previous animation minus any mix duration plus the negative delay.</param>
public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) {
if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null.");
TrackEntry entry = trackEntryPool.Obtain();
entry.trackIndex = trackIndex;
entry.animation = animation;
entry.animation = animation;
entry.loop = loop;
entry.time = 0;
entry.endTime = animation.Duration;
TrackEntry last = ExpandToIndex(trackIndex);
if (last != null) {
while (last.next != null)
last = last.next;
last.next = entry;
} else
tracks.Items[trackIndex] = entry;
if (delay <= 0) {
if (last != null)
delay += last.endTime - data.GetMix(last.animation, animation);
else
delay = 0;
}
entry.delay = delay;
return entry;
}
/// <summary>
/// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration.</summary>
public TrackEntry SetEmptyAnimation(int trackIndex, float mixDuration)
{
TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false);
entry.mixDuration = mixDuration;
entry.endTime = mixDuration;
return entry;
}
/// <summary>
/// Adds an empty animation to be played after the current or last queued animation for a track, and mixes to it over the
/// specified mix duration.</summary>
/// <returns>
/// A track entry to allow further customization of animation playback. References to the track entry must not be kept after <see cref="AnimationState.Dispose"/>.
/// </returns>
/// <param name="trackIndex">Track number.</param>
/// <param name="mixDuration">Mix duration.</param>
/// <param name="delay">Seconds to begin this animation after the start of the previous animation. May be &lt;= 0 to use the animation
/// duration of the previous track minus any mix duration plus the negative delay.</param>
public TrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay)
{
if (delay <= 0) delay -= mixDuration;
TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay);
entry.mixDuration = mixDuration;
entry.endTime = mixDuration;
return entry;
}
/// <summary>
/// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration.</summary>
public void SetEmptyAnimations(float mixDuration)
{
for (int i = 0, n = tracks.Count; i < n; i++)
{
TrackEntry current = tracks.Items[i];
if (current != null) SetEmptyAnimation(i, mixDuration);
}
}
/// <returns>May be null.</returns>
public TrackEntry GetCurrent (int trackIndex) {
if (trackIndex >= tracks.Count) return null;
return tracks.Items[trackIndex];
}
override public String ToString () {
StringBuilder buffer = new StringBuilder();
for (int i = 0, n = tracks.Count; i < n; i++) {
TrackEntry entry = tracks.Items[i];
if (entry == null) continue;
if (buffer.Length > 0) buffer.Append(", ");
buffer.Append(entry.ToString());
}
if (buffer.Length == 0) return "<none>";
return buffer.ToString();
}
}
public class TrackEntry : Pool<TrackEntry>.IPoolable {
internal TrackEntry next, previous;
internal int trackIndex;
internal Animation animation;
internal bool loop;
internal float delay, time, lastTime = -1, endTime, timeScale = 1;
internal float mixTime, mixDuration, mix = 1;
public int TrackIndex { get { return trackIndex; } }
public Animation Animation { get { return animation; } }
public float Delay { get { return delay; } set { delay = value; } }
public float Time { get { return time; } set { time = value; } }
public float LastTime { get { return lastTime; } set { lastTime = value; } }
public float EndTime { get { return endTime; } set { endTime = value; } }
public float TimeScale { get { return timeScale; } set { timeScale = value; } }
public float Mix { get { return mix; } set { mix = value; } }
public bool Loop { get { return loop; } set { loop = value; } }
/// <summary>
/// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by
/// <see cref="AnimationStateData"/> based on the animation before this animation (if any).
///
/// The mix duration can be set manually rather than use the value from AnimationStateData.GetMix.
/// In that case, the mixDuration must be set before <see cref="AnimationState.Update(float)"/> is next called.
/// <para>
/// When using <seealso cref="AnimationState.AddAnimation(int, Animation, bool, float)"/> with a
/// <code>delay</code> less than or equal to 0, note the <seealso cref="Delay"/> is set using the mix duration from the <see cref=" AnimationStateData"/>
/// </para>
///
/// </summary>
public float MixDuration { get { return mixDuration; } set { mixDuration = value; } }
/// <summary>
/// The animation queued to start after this animation, or null.</summary>
public TrackEntry Next { get { return next; } }
public event AnimationState.TrackEntryDelegate Start, End, Complete;
public event AnimationState.EventDelegate Event;
// IPoolable.Reset()
public void Reset()
{
next = null;
previous = null;
animation = null;
Start = null;
End = null;
Complete = null;
Event = null;
}
internal void OnStart() { if (Start != null) Start(this); }
internal void OnEnd() { if (End != null) End(this); }
internal void OnComplete() { if (Complete != null) Complete(this); }
internal void OnEvent(AnimationState state, int index, Event e)
{
if (Event != null) Event(state, index, e);
}
override public String ToString () {
return animation == null ? "<none>" : animation.name;
}
}
public class Pool<T> where T : class, new()
{
public readonly int max;
readonly Stack<T> freeObjects;
public int Count { get { return freeObjects.Count; } }
public int Peak { get; private set; }
public Pool(int initialCapacity = 16, int max = int.MaxValue)
{
freeObjects = new Stack<T>(initialCapacity);
this.max = max;
}
public T Obtain()
{
return freeObjects.Count == 0 ? new T() : freeObjects.Pop();
}
public void Free(T obj)
{
if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null");
if (freeObjects.Count < max)
{
freeObjects.Push(obj);
Peak = Math.Max(Peak, freeObjects.Count);
}
Reset(obj);
}
// protected void FreeAll (List<T> objects) {
// if (objects == null) throw new ArgumentNullException("objects", "objects cannot be null.");
// var freeObjects = this.freeObjects;
// int max = this.max;
// for (int i = 0; i < objects.Count; i++) {
// T obj = objects[i];
// if (obj == null) continue;
// if (freeObjects.Count < max) freeObjects.Push(obj);
// Reset(obj);
// }
// Peak = Math.Max(Peak, freeObjects.Count);
// }
public void Clear()
{
freeObjects.Clear();
}
protected void Reset(T obj)
{
var poolable = obj as IPoolable;
if (poolable != null) poolable.Reset();
}
public interface IPoolable
{
void Reset();
}
}
}

View File

@@ -0,0 +1,96 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.Collections.Generic;
namespace SpineRuntime34 {
public class AnimationStateData {
internal SkeletonData skeletonData;
private Dictionary<AnimationPair, float> animationToMixTime = new Dictionary<AnimationPair, float>(AnimationPairComparer.Instance);
internal float defaultMix;
public SkeletonData SkeletonData { get { return skeletonData; } }
public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } }
public AnimationStateData (SkeletonData skeletonData) {
if (skeletonData == null) throw new ArgumentException ("skeletonData cannot be null.");
this.skeletonData = skeletonData;
}
public void SetMix (String fromName, String toName, float duration) {
Animation from = skeletonData.FindAnimation(fromName);
if (from == null) throw new ArgumentException("Animation not found: " + fromName);
Animation to = skeletonData.FindAnimation(toName);
if (to == null) throw new ArgumentException("Animation not found: " + toName);
SetMix(from, to, duration);
}
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);
}
public float GetMix (Animation from, Animation to) {
AnimationPair key = new AnimationPair(from, to);
float duration;
if (animationToMixTime.TryGetValue(key, out duration)) return duration;
return defaultMix;
}
struct AnimationPair {
public readonly Animation a1;
public readonly Animation a2;
public AnimationPair (Animation a1, Animation a2) {
this.a1 = a1;
this.a2 = a2;
}
}
// Avoids boxing in the dictionary.
class AnimationPairComparer : IEqualityComparer<AnimationPair> {
internal static readonly AnimationPairComparer Instance = new AnimationPairComparer();
bool IEqualityComparer<AnimationPair>.Equals (AnimationPair x, AnimationPair y) {
return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2);
}
int IEqualityComparer<AnimationPair>.GetHashCode (AnimationPair obj) {
// from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2);
int h1 = obj.a1.GetHashCode();
return (((h1 << 5) + h1) ^ obj.a2.GetHashCode());
}
}
}
}

View File

@@ -0,0 +1,293 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
#if WINDOWS_STOREAPP
using System.Threading.Tasks;
using Windows.Storage;
#endif
namespace SpineRuntime34 {
public class Atlas {
List<AtlasPage> pages = new List<AtlasPage>();
List<AtlasRegion> regions = new List<AtlasRegion>();
TextureLoader textureLoader;
#if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) // !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 {
Load(reader, Path.GetDirectoryName(path), 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 {
Load(reader, Path.GetDirectoryName(path), textureLoader);
} catch (Exception ex) {
throw new Exception("Error reading atlas file: " + path, ex);
}
}
}
#endif // WINDOWS_STOREAPP
#endif // !(UNITY)
public Atlas (TextReader reader, String dir, TextureLoader textureLoader) {
Load(reader, dir, textureLoader);
}
public Atlas (List<AtlasPage> pages, List<AtlasRegion> regions) {
this.pages = pages;
this.regions = regions;
this.textureLoader = null;
}
private void Load (TextReader reader, String imagesDir, TextureLoader textureLoader) {
if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null.");
this.textureLoader = textureLoader;
String[] tuple = new String[4];
AtlasPage page = null;
while (true) {
String line = reader.ReadLine();
if (line == null) break;
if (line.Trim().Length == 0)
page = null;
else if (page == null) {
page = new AtlasPage();
page.name = line;
if (ReadTuple(reader, tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker.
page.width = int.Parse(tuple[0]);
page.height = int.Parse(tuple[1]);
ReadTuple(reader, tuple);
}
page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false);
ReadTuple(reader, tuple);
page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false);
page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false);
String direction = ReadValue(reader);
page.uWrap = TextureWrap.ClampToEdge;
page.vWrap = TextureWrap.ClampToEdge;
if (direction == "x")
page.uWrap = TextureWrap.Repeat;
else if (direction == "y")
page.vWrap = TextureWrap.Repeat;
else if (direction == "xy")
page.uWrap = page.vWrap = TextureWrap.Repeat;
textureLoader.Load(page, Path.Combine(imagesDir, line));
pages.Add(page);
} else {
AtlasRegion region = new AtlasRegion();
region.name = line;
region.page = page;
region.rotate = Boolean.Parse(ReadValue(reader));
ReadTuple(reader, tuple);
int x = int.Parse(tuple[0]);
int y = int.Parse(tuple[1]);
ReadTuple(reader, tuple);
int width = int.Parse(tuple[0]);
int height = int.Parse(tuple[1]);
region.u = x / (float)page.width;
region.v = y / (float)page.height;
if (region.rotate) {
region.u2 = (x + height) / (float)page.width;
region.v2 = (y + width) / (float)page.height;
} else {
region.u2 = (x + width) / (float)page.width;
region.v2 = (y + height) / (float)page.height;
}
region.x = x;
region.y = y;
region.width = Math.Abs(width);
region.height = Math.Abs(height);
if (ReadTuple(reader, tuple) == 4) { // split is optional
region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]),
int.Parse(tuple[2]), int.Parse(tuple[3])};
if (ReadTuple(reader, tuple) == 4) { // pad is optional, but only present with splits
region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]),
int.Parse(tuple[2]), int.Parse(tuple[3])};
ReadTuple(reader, tuple);
}
}
region.originalWidth = int.Parse(tuple[0]);
region.originalHeight = int.Parse(tuple[1]);
ReadTuple(reader, tuple);
region.offsetX = int.Parse(tuple[0]);
region.offsetY = int.Parse(tuple[1]);
region.index = int.Parse(ReadValue(reader));
regions.Add(region);
}
}
}
static String ReadValue (TextReader reader) {
String line = reader.ReadLine();
int colon = line.IndexOf(':');
if (colon == -1) throw new Exception("Invalid line: " + line);
return line.Substring(colon + 1).Trim();
}
/// <summary>Returns the number of tuple values read (1, 2 or 4).</summary>
static int ReadTuple (TextReader reader, String[] tuple) {
String line = reader.ReadLine();
int colon = line.IndexOf(':');
if (colon == -1) throw new Exception("Invalid line: " + line);
int i = 0, lastMatch = colon + 1;
for (; i < 3; i++) {
int comma = line.IndexOf(',', lastMatch);
if (comma == -1) break;
tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim();
lastMatch = comma + 1;
}
tuple[i] = line.Substring(lastMatch).Trim();
return i + 1;
}
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 Format format;
public TextureFilter minFilter;
public TextureFilter magFilter;
public TextureWrap uWrap;
public TextureWrap vWrap;
public Object rendererObject;
public int width, height;
}
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 index;
public bool rotate;
public int[] splits;
public int[] pads;
}
public interface TextureLoader {
void Load (AtlasPage page, String path);
void Unload (Object texture);
}
}

View File

@@ -0,0 +1,96 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
public class AtlasAttachmentLoader : AttachmentLoader {
private Atlas[] atlasArray;
public AtlasAttachmentLoader (params Atlas[] atlasArray) {
if (atlasArray == null) throw new ArgumentNullException("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 Exception("Region not found in atlas: " + path + " (region attachment: " + name + ")");
RegionAttachment attachment = new RegionAttachment(name);
attachment.RendererObject = region;
attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate);
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 Exception("Region not found in atlas: " + path + " (mesh attachment: " + 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.RegionRotate = region.rotate;
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 AtlasRegion FindRegion (string name) {
AtlasRegion region;
for (int i = 0; i < atlasArray.Length; i++) {
region = atlasArray[i].FindRegion(name);
if (region != null)
return region;
}
return null;
}
}
}

View File

@@ -0,0 +1,46 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
abstract public class Attachment {
public String Name { get; private set; }
public Attachment (String name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null");
Name = name;
}
override public String ToString () {
return Name;
}
}
}

View File

@@ -0,0 +1,47 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
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);
}
}

View File

@@ -0,0 +1,35 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
namespace SpineRuntime34 {
public enum AttachmentType {
Region, Boundingbox, Mesh, Linkedmesh, Path
}
}

View File

@@ -0,0 +1,40 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
/// <summary>Attachment that has a polygon for bounds checking.</summary>
public class BoundingBoxAttachment : VertexAttachment {
public BoundingBoxAttachment (string name)
: base(name) {
}
}
}

View File

@@ -0,0 +1,119 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
/// <summary>Attachment that displays a texture region using a mesh.</summary>
public class MeshAttachment : VertexAttachment {
internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight;
internal float[] uvs, regionUVs;
internal int[] triangles;
internal float r = 1, g = 1, b = 1, a = 1;
internal int hulllength;
internal MeshAttachment parentMesh;
internal bool inheritDeform;
public int HullLength { get { return hulllength; } set { hulllength = value; } }
public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } }
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 bool RegionRotate { 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 bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } }
public MeshAttachment ParentMesh {
get { return parentMesh; }
set {
parentMesh = value;
if (value != null) {
bones = value.bones;
vertices = value.vertices;
worldVerticesLength = value.worldVerticesLength;
regionUVs = value.regionUVs;
triangles = value.triangles;
HullLength = value.HullLength;
Edges = value.Edges;
Width = value.Width;
Height = value.Height;
}
}
}
// Nonessential.
public int[] Edges { get; set; }
public float Width { get; set; }
public float Height { get; set; }
public MeshAttachment (string name)
: base(name) {
}
public void UpdateUVs () {
float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV;
float[] regionUVs = this.regionUVs;
if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length];
float[] uvs = this.uvs;
if (RegionRotate) {
for (int i = 0, n = uvs.Length; i < n; i += 2) {
uvs[i] = u + regionUVs[i + 1] * width;
uvs[i + 1] = v + height - regionUVs[i] * height;
}
} else {
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;
}
}
}
override public bool ApplyDeform (VertexAttachment sourceAttachment) {
return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment);
}
}
}

View File

@@ -0,0 +1,48 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.Collections.Generic;
namespace SpineRuntime34 {
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; } }
public bool Closed { get { return closed; } set { closed = value; } }
public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } }
public PathAttachment (String name)
: base(name) {
}
}
}

View File

@@ -0,0 +1,152 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
/// <summary>Attachment that displays a texture region.</summary>
public class RegionAttachment : Attachment {
public const int X1 = 0;
public const int Y1 = 1;
public const int X2 = 2;
public const int Y2 = 3;
public const int X3 = 4;
public const int Y3 = 5;
public const int X4 = 6;
public const int Y4 = 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 SetUVs (float u, float v, float u2, float v2, bool rotate) {
float[] uvs = this.uvs;
if (rotate) {
uvs[X2] = u;
uvs[Y2] = v2;
uvs[X3] = u;
uvs[Y3] = v;
uvs[X4] = u2;
uvs[Y4] = v;
uvs[X1] = u2;
uvs[Y1] = v2;
} else {
uvs[X1] = u;
uvs[Y1] = v2;
uvs[X2] = u;
uvs[Y2] = v;
uvs[X3] = u2;
uvs[Y3] = v;
uvs[X4] = u2;
uvs[Y4] = v2;
}
}
public void UpdateOffset () {
float width = this.width;
float height = this.height;
float scaleX = this.scaleX;
float scaleY = this.scaleY;
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 rotation = this.rotation;
float cos = MathUtils.CosDeg(rotation);
float sin = MathUtils.SinDeg(rotation);
float x = this.x;
float 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[X1] = localXCos - localYSin;
offset[Y1] = localYCos + localXSin;
offset[X2] = localXCos - localY2Sin;
offset[Y2] = localY2Cos + localXSin;
offset[X3] = localX2Cos - localY2Sin;
offset[Y3] = localY2Cos + localX2Sin;
offset[X4] = localX2Cos - localYSin;
offset[Y4] = localYCos + localX2Sin;
}
public void ComputeWorldVertices (Bone bone, float[] worldVertices) {
Skeleton skeleton = bone.skeleton;
float x = skeleton.x + bone.worldX, y = skeleton.y + bone.worldY;
float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
float[] offset = this.offset;
worldVertices[X1] = offset[X1] * a + offset[Y1] * b + x;
worldVertices[Y1] = offset[X1] * c + offset[Y1] * d + y;
worldVertices[X2] = offset[X2] * a + offset[Y2] * b + x;
worldVertices[Y2] = offset[X2] * c + offset[Y2] * d + y;
worldVertices[X3] = offset[X3] * a + offset[Y3] * b + x;
worldVertices[Y3] = offset[X3] * c + offset[Y3] * d + y;
worldVertices[X4] = offset[X4] * a + offset[Y4] * b + x;
worldVertices[Y4] = offset[X4] * c + offset[Y4] * d + y;
}
}
}

View File

@@ -0,0 +1,117 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.Collections.Generic;
namespace SpineRuntime34 {
/// <summary>>An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices.</summary>
public class VertexAttachment : Attachment {
internal int[] bones;
internal float[] vertices;
internal int worldVerticesLength;
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; } }
public VertexAttachment (String name)
: base(name) {
}
public void ComputeWorldVertices (Slot slot, float[] worldVertices) {
ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0);
}
public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset) {
count += offset;
Skeleton skeleton = slot.Skeleton;
float x = skeleton.x, y = skeleton.y;
var deformArray = slot.attachmentVertices;
float[] vertices = this.vertices;
int[] bones = this.bones;
if (bones == null) {
if (deformArray.Count > 0) vertices = deformArray.Items;
Bone bone = slot.bone;
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 += 2) {
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 = skeleton.Bones.Items;
if (deformArray.Count == 0) {
for (int w = offset, b = skip * 3; w < count; w += 2) {
float wx = x, wy = y;
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 += 2) {
float wx = x, wy = y;
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>Returns true if a deform originally applied to the specified attachment should be applied to this attachment.</summary>
virtual public bool ApplyDeform (VertexAttachment sourceAttachment) {
return this == sourceAttachment;
}
}
}

View File

@@ -0,0 +1,35 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
namespace SpineRuntime34 {
public enum BlendMode {
normal, additive, multiply, screen
}
}

View File

@@ -0,0 +1,301 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
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 appliedRotation;
internal float a, b, worldX;
internal float c, d, worldY;
internal float worldSignX, worldSignY;
internal bool sorted;
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; } }
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; } }
/// <summary>The rotation, as calculated by any constraints.</summary>
public float AppliedRotation { get { return appliedRotation; } set { appliedRotation = value; } }
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
public float ScaleY { get { return scaleY; } set { scaleY = value; } }
public float ShearX { get { return shearX; } set { shearX = value; } }
public float ShearY { get { return shearY; } set { shearY = value; } }
public float A { get { return a; } }
public float B { get { return b; } }
public float C { get { return c; } }
public float D { get { return d; } }
public float WorldX { get { return worldX; } }
public float WorldY { get { return worldY; } }
public float WorldSignX { get { return worldSignX; } }
public float WorldSignY { get { return worldSignY; } }
public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.radDeg; } }
public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.radDeg; } }
public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c) * worldSignX; } }
public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d) * worldSignY; } }
/// <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>Same as <see cref="UpdateWorldTransform"/>. This method exists for Bone to implement <see cref="Spine.IUpdatable"/>.</summary>
public void Update () {
UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY);
}
/// <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.</summary>
public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) {
appliedRotation = rotation;
float rotationY = rotation + 90 + shearY;
float la = MathUtils.CosDeg(rotation + shearX) * scaleX, lb = MathUtils.CosDeg(rotationY) * scaleY;
float lc = MathUtils.SinDeg(rotation + shearX) * scaleX, ld = MathUtils.SinDeg(rotationY) * scaleY;
Bone parent = this.parent;
if (parent == null) { // Root bone.
Skeleton skeleton = this.skeleton;
float sx = skeleton.scaleX, sy = skeleton.scaleY;
a = la * sx;
b = lb * sx;
c = lc * sy;
d = ld * sy;
worldX = x * sx;
worldY = y * sy;
worldSignX = Math.Sign(scaleX);
worldSignY = Math.Sign(scaleY);
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;
worldSignX = parent.worldSignX * Math.Sign(scaleX);
worldSignY = parent.worldSignY * Math.Sign(scaleY);
if (data.inheritRotation && data.inheritScale) {
a = pa * la + pb * lc;
b = pa * lb + pb * ld;
c = pc * la + pd * lc;
d = pc * lb + pd * ld;
} else {
if (data.inheritRotation) { // No scale inheritance.
pa = 1;
pb = 0;
pc = 0;
pd = 1;
do {
float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation);
float temp = pa * cos + pb * sin;
pb = pb * cos - pa * sin;
pa = temp;
temp = pc * cos + pd * sin;
pd = pd * cos - pc * sin;
pc = temp;
if (!parent.data.inheritRotation) break;
parent = parent.parent;
} while (parent != null);
a = pa * la + pb * lc;
b = pa * lb + pb * ld;
c = pc * la + pd * lc;
d = pc * lb + pd * ld;
} else if (data.inheritScale) { // No rotation inheritance.
pa = 1;
pb = 0;
pc = 0;
pd = 1;
do {
float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation);
float psx = parent.scaleX, psy = parent.scaleY;
float za = cos * psx, zb = sin * psy, zc = sin * psx, zd = cos * psy;
float temp = pa * za + pb * zc;
pb = pb * zd - pa * zb;
pa = temp;
temp = pc * za + pd * zc;
pd = pd * zd - pc * zb;
pc = temp;
if (psx >= 0) sin = -sin;
temp = pa * cos + pb * sin;
pb = pb * cos - pa * sin;
pa = temp;
temp = pc * cos + pd * sin;
pd = pd * cos - pc * sin;
pc = temp;
if (!parent.data.inheritScale) break;
parent = parent.parent;
} while (parent != null);
a = pa * la + pb * lc;
b = pa * lb + pb * ld;
c = pc * la + pd * lc;
d = pc * lb + pd * ld;
} else {
a = la;
b = lb;
c = lc;
d = ld;
}
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;
}
public float WorldToLocalRotationX {
get {
Bone parent = this.parent;
if (parent == null) return rotation;
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 rotation;
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 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;
}
/// <summary>
/// Computes the local transform from the world transform. This can be useful to perform processing on the local transform
/// after the world transform has been modified directly (eg, by a constraint).
///
/// Some redundant information is lost by the world transform, such as -1,-1 scale versus 180 rotation. The computed local
/// transform values may differ from the original values but are functionally the same.
/// </summary>
public void UpdateLocalTransform () {
Bone parent = this.parent;
if (parent == null) {
x = worldX;
y = worldY;
rotation = MathUtils.Atan2(c, a) * MathUtils.radDeg;
scaleX = (float)Math.Sqrt(a * a + c * c);
scaleY = (float)Math.Sqrt(b * b + d * d);
float det = a * d - b * c;
shearX = 0;
shearY = MathUtils.Atan2(a * b + c * d, det) * 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;
x = (dx * pd * pid - dy * pb * pid);
y = (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;
shearX = 0;
scaleX = (float)Math.Sqrt(ra * ra + rc * rc);
if (scaleX > 0.0001f) {
float det = ra * rd - rb * rc;
scaleY = det / scaleX;
shearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.radDeg;
rotation = MathUtils.Atan2(rc, ra) * MathUtils.radDeg;
} else {
scaleX = 0;
scaleY = (float)Math.Sqrt(rb * rb + rd * rd);
shearY = 0;
rotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.radDeg;
}
appliedRotation = rotation;
}
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 invDet = 1 / (a * d - b * c);
float x = worldX - this.worldX, y = worldY - this.worldY;
localX = (x * d * invDet - y * b * invDet);
localY = (y * a * invDet - x * c * invDet);
}
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;
}
override public String ToString () {
return data.name;
}
}
}

View File

@@ -0,0 +1,70 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
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 bool inheritRotation = true, inheritScale = true;
/// <summary>May be null.</summary>
public int Index { get { return index; } }
public String Name { get { return name; } }
public BoneData Parent { get { return parent; } }
public float Length { get { return length; } set { length = value; } }
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 ShearX { get { return shearX; } set { shearX = value; } }
public float ShearY { get { return shearY; } set { shearY = value; } }
public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } }
public bool InheritScale { get { return inheritScale; } set { inheritScale = 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;
}
}
}

View File

@@ -0,0 +1,51 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
public class Event {
public EventData Data { get; private set; }
public int Int { get; set; }
public float Float { get; set; }
public String String { get; set; }
public float Time { get; private set; }
public Event (float time, EventData data) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
Time = time;
Data = data;
}
override public String ToString () {
return Data.Name;
}
}
}

View File

@@ -0,0 +1,51 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
public class EventData {
internal String name;
public String Name { get { return name; } }
public int Int { get; set; }
public float Float { get; set; }
public String String { get; set; }
public EventData (String name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.name = name;
}
override public String ToString () {
return Name;
}
}
}

View File

@@ -0,0 +1,589 @@
//
// 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 SpineRuntime34 {
[Serializable]
[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 newCount) {
int minimumSize = Count + newCount;
if (minimumSize > Items.Length)
Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize);
}
public ExposedList<T> Resize (int newSize) {
if (newSize > Items.Length) Array.Resize(ref Items, newSize);
Count = newSize;
return this;
}
private void CheckRange (int idx, int count) {
if (idx < 0)
throw new ArgumentOutOfRangeException("index");
if (count < 0)
throw new ArgumentOutOfRangeException("count");
if ((uint)idx + (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);
}
}
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);
for (int i = 0; i < Count; i++)
u.Items[i] = converter(Items[i]);
u.Count = Count;
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++;
}
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
[Serializable]
public struct Enumerator : IEnumerator<T>, IDisposable {
private ExposedList<T> l;
private int next;
private int ver;
private T current;
internal Enumerator (ExposedList<T> l)
: this() {
this.l = l;
ver = l.version;
}
public void Dispose () {
l = null;
}
private void VerifyState () {
if (l == null)
throw new ObjectDisposedException(GetType().FullName);
if (ver != l.version)
throw new InvalidOperationException(
"Collection was modified; enumeration operation may not execute.");
}
public bool MoveNext () {
VerifyState();
if (next < 0)
return false;
if (next < l.Count) {
current = l.Items[next++];
return true;
}
next = -1;
return false;
}
public T Current {
get {
return current;
}
}
void IEnumerator.Reset () {
VerifyState();
next = 0;
}
object IEnumerator.Current {
get {
VerifyState();
if (next <= 0)
throw new InvalidOperationException();
return current;
}
}
}
}
}

View File

@@ -0,0 +1,37 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
public interface IUpdatable {
void Update ();
}
}

View File

@@ -0,0 +1,234 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
public class IkConstraint : IUpdatable {
internal IkConstraintData data;
internal ExposedList<Bone> bones = new ExposedList<Bone>();
internal Bone target;
internal float mix;
internal int bendDirection;
internal int level;
public IkConstraintData Data { get { return data; } }
public ExposedList<Bone> Bones { get { return bones; } }
public Bone Target { get { return target; } set { target = value; } }
public int BendDirection { get { return bendDirection; } set { bendDirection = value; } }
public float Mix { get { return mix; } set { mix = value; } }
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;
bendDirection = data.bendDirection;
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);
}
public void Update () {
Apply();
}
public void Apply () {
Bone target = this.target;
ExposedList<Bone> bones = this.bones;
switch (bones.Count) {
case 1:
Apply(bones.Items[0], target.worldX, target.worldY, mix);
break;
case 2:
Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, mix);
break;
}
}
override public String ToString () {
return data.name;
}
/// <summary>Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified
/// in the world coordinate system.</summary>
static public void Apply (Bone bone, float targetX, float targetY, float alpha) {
Bone pp = bone.parent;
float id = 1 / (pp.a * pp.d - pp.b * pp.c);
float x = targetX - pp.worldX, y = targetY - pp.worldY;
float tx = (x * pp.d - y * pp.b) * id - bone.x, ty = (y * pp.a - x * pp.c) * id - bone.y;
float rotationIK = MathUtils.Atan2(ty, tx) * MathUtils.radDeg - bone.shearX - bone.rotation;
if (bone.scaleX < 0) rotationIK += 180;
if (rotationIK > 180)
rotationIK -= 360;
else if (rotationIK < -180) rotationIK += 360;
bone.UpdateWorldTransform(bone.x, bone.y, bone.rotation + rotationIK * alpha, bone.scaleX, bone.scaleY,
bone.shearX, bone.shearY);
}
/// <summary>Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as
/// possible. 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, float alpha) {
if (alpha == 0) {
child.UpdateWorldTransform ();
return;
}
float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX;
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.x, 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) {
cy = 0;
cwx = a * cx + parent.worldX;
cwy = c * cx + parent.worldY;
} else {
cy = child.y;
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 = targetX - pp.worldX, y = targetY - pp.worldY;
float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py;
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 (u) {
l2 *= psx;
float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2);
if (cos < -1)
cos = -1;
else if (cos > 1) cos = 1;
a2 = (float)Math.Acos(cos) * bendDir;
a = l1 + l2 * cos;
b = l2 * MathUtils.Sin(a2);
a1 = MathUtils.Atan2(ty * a - tx * b, tx * a + ty * b);
} else {
a = psx * l2;
b = psy * l2;
float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = MathUtils.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) / 2;
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 - MathUtils.Atan2(y, r);
a2 = MathUtils.Atan2(y / psy, (r - l1) / psx);
goto outer;
}
}
float minAngle = 0, minDist = float.MaxValue, minX = 0, minY = 0;
float maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0;
x = l1 + a;
d = x * x;
if (d > maxDist) {
maxAngle = 0;
maxDist = d;
maxX = x;
}
x = l1 - a;
d = x * x;
if (d < minDist) {
minAngle = MathUtils.PI;
minDist = d;
minX = x;
}
float angle = (float)Math.Acos(-a * l1 / (aa - bb));
x = a * MathUtils.Cos(angle) + l1;
y = b * MathUtils.Sin(angle);
d = x * x + y * y;
if (d < minDist) {
minAngle = angle;
minDist = d;
minX = x;
minY = y;
}
if (d > maxDist) {
maxAngle = angle;
maxDist = d;
maxX = x;
maxY = y;
}
if (dd <= (minDist + maxDist) / 2) {
a1 = ta - MathUtils.Atan2(minY * bendDir, minX);
a2 = minAngle * bendDir;
} else {
a1 = ta - MathUtils.Atan2(maxY * bendDir, maxX);
a2 = maxAngle * bendDir;
}
}
outer:
float os = MathUtils.Atan2(cy, cx) * s2;
float rotation = parent.rotation;
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, parent.scaleX, parent.scaleY, 0, 0);
rotation = child.rotation;
a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2 - rotation;
if (a2 > 180)
a2 -= 360;
else if (a2 < -180) a2 += 360;
child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.scaleX, child.scaleY, child.shearX, child.shearY);
}
}
}

View File

@@ -0,0 +1,57 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.Collections.Generic;
namespace SpineRuntime34 {
public class IkConstraintData {
internal String name;
internal List<BoneData> bones = new List<BoneData>();
internal BoneData target;
internal int bendDirection = 1;
internal float mix = 1;
public String Name { get { return name; } }
public List<BoneData> Bones { get { return bones; } }
public BoneData Target { get { return target; } set { target = value; } }
public int BendDirection { get { return bendDirection; } set { bendDirection = value; } }
public float Mix { get { return mix; } set { mix = value; } }
public IkConstraintData (String name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.name = name;
}
override public String ToString () {
return name;
}
}
}

View File

@@ -0,0 +1,535 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.IO;
using System.Text;
using System.Collections;
using System.Globalization;
using System.Collections.Generic;
namespace SpineRuntime34 {
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;
}
}
}

View File

@@ -0,0 +1,100 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
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;
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 in radians from a lookup table.</summary>
static public float Sin (float radians) {
return sin[(int)(radians * radToIndex) & SIN_MASK];
}
/// <summary>Returns the cosine 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 in radians from a lookup table.</summary>
static public float SinDeg (float degrees) {
return sin[(int)(degrees * degToIndex) & SIN_MASK];
}
/// <summary>Returns the cosine in radians 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;
}
static public float Clamp (float value, float min, float max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
}
}

View File

@@ -0,0 +1,399 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
public class PathConstraint : IUpdatable {
private const int NONE = -1, BEFORE = -2, AFTER = -3;
internal PathConstraintData data;
internal ExposedList<Bone> bones;
internal Slot target;
internal float position, spacing, rotateMix, translateMix;
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 float Position { get { return position; } set { position = value; } }
public float Spacing { get { return spacing; } set { spacing = value; } }
public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
public ExposedList<Bone> Bones { get { return bones; } }
public Slot Target { get { return target; } set { target = value; } }
public PathConstraintData Data { get { return data; } }
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;
rotateMix = data.rotateMix;
translateMix = data.translateMix;
}
public void Apply () {
Update();
}
public void Update () {
PathAttachment attachment = target.Attachment as PathAttachment;
if (attachment == null) return;
float rotateMix = this.rotateMix, translateMix = this.translateMix;
bool translate = translateMix > 0, rotate = rotateMix > 0;
if (!translate && !rotate) return;
PathConstraintData data = this.data;
SpacingMode spacingMode = data.spacingMode;
bool lengthSpacing = spacingMode == SpacingMode.Length;
RotateMode rotateMode = data.rotateMode;
bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale;
int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1;
Bone[] bones = this.bones.Items;
ExposedList<float> spaces = this.spaces.Resize(spacesCount), lengths = null;
float spacing = this.spacing;
if (scale || lengthSpacing) {
if (scale) lengths = this.lengths.Resize(boneCount);
for (int i = 0, n = spacesCount - 1; i < n;) {
Bone bone = bones[i];
float length = bone.data.length, x = length * bone.a, y = length * bone.c;
length = (float)Math.Sqrt(x * x + y * y);
if (scale) lengths.Items[i] = length;
spaces.Items[++i] = lengthSpacing ? Math.Max(0, length + spacing) : spacing;
}
} else {
for (int i = 1; i < spacesCount; i++)
spaces.Items[i] = spacing;
}
float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents,
data.positionMode == PositionMode.Percent, spacingMode == SpacingMode.Percent);
Skeleton skeleton = target.Skeleton;
float skeletonX = skeleton.x, skeletonY = skeleton.y;
float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
bool tip = rotateMode == RotateMode.Chain && offsetRotation == 0;
for (int i = 0, p = 3; i < boneCount; i++, p += 3) {
Bone bone = (Bone)bones[i];
bone.worldX += (boneX - skeletonX - bone.worldX) * translateMix;
bone.worldY += (boneY - skeletonY - bone.worldY) * translateMix;
float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
if (scale) {
float length = lengths.Items[i];
if (length != 0) {
float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1;
bone.a *= s;
bone.c *= s;
}
}
boneX = x;
boneY = y;
if (rotate) {
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.Items[i + 1] == 0)
r = positions[p + 2];
else
r = MathUtils.Atan2(dy, dx);
r -= MathUtils.Atan2(c, a) - offsetRotation * MathUtils.degRad;
if (tip) {
cos = MathUtils.Cos(r);
sin = MathUtils.Sin(r);
float length = bone.data.length;
boneX += (length * (cos * a - sin * c) - dx) * rotateMix;
boneY += (length * (sin * a + cos * c) - dy) * rotateMix;
}
if (r > MathUtils.PI)
r -= MathUtils.PI2;
else if (r < -MathUtils.PI) //
r += MathUtils.PI2;
r *= rotateMix;
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;
}
}
}
float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents, bool percentPosition,
bool percentSpacing) {
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;
if (!path.ConstantSpeed) {
float[] lengths = path.Lengths;
curveCount -= closed ? 1 : 2;
pathLength = lengths[curveCount];
if (percentPosition) position *= pathLength;
if (percentSpacing) {
for (int i = 0; i < spacesCount; i++)
spaces[i] *= pathLength;
}
world = this.world.Resize(8).Items;
for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
float space = spaces[i];
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);
}
AddBeforePosition(p, world, 0, output, o);
continue;
} else if (p > pathLength) {
if (prevCurve != AFTER) {
prevCurve = AFTER;
path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0);
}
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);
path.ComputeWorldVertices(target, 0, 4, world, 4);
} else
path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0);
}
AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o,
tangents || (i > 0 && space == 0));
}
return output;
}
// World vertices.
if (closed) {
verticesLength += 2;
world = this.world.Resize(verticesLength).Items;
path.ComputeWorldVertices(target, 2, verticesLength - 4, world, 0);
path.ComputeWorldVertices(target, 0, 2, world, verticesLength - 4);
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);
}
// 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 (percentPosition) position *= pathLength;
if (percentSpacing) {
for (int i = 0; i < spacesCount; i++)
spaces[i] *= pathLength;
}
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];
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 == 0));
}
return output;
}
private 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;
}
private 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;
}
private 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 == 0) p = 0.0001f;
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) output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
}
}
}

View File

@@ -0,0 +1,73 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
public class PathConstraintData {
internal String name;
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, rotateMix, translateMix;
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; } }
public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
public String Name { get { return name; } }
public PathConstraintData (String name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.name = name;
}
}
public enum PositionMode {
Fixed, Percent
}
public enum SpacingMode {
Length, Fixed, Percent
}
public enum RotateMode {
Tangent, Chain, ChainScale
}
}

View File

@@ -0,0 +1,508 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.Collections.Generic;
namespace SpineRuntime34 {
public class Skeleton {
internal SkeletonData data;
internal ExposedList<Bone> bones;
internal ExposedList<Slot> slots;
internal ExposedList<Slot> drawOrder;
internal ExposedList<IkConstraint> ikConstraints, ikConstraintsSorted;
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;
internal 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 { get { return skin; } set { skin = 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; } 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; } }
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);
foreach (BoneData boneData in data.bones) {
Bone bone;
if (boneData.parent == null) {
bone = new Bone(boneData, this, null);
} else {
Bone parent = bones.Items[boneData.parent.index];
bone = new Bone(boneData, this, parent);
parent.children.Add(bone);
}
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 = bones.Items[slotData.boneData.index];
Slot slot = new Slot(slotData, bone);
slots.Add(slot);
drawOrder.Add(slot);
}
ikConstraints = new ExposedList<IkConstraint>(data.ikConstraints.Count);
ikConstraintsSorted = 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();
UpdateWorldTransform();
}
/// <summary>Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added
/// or removed.</summary>
public void UpdateCache () {
ExposedList<IUpdatable> updateCache = this.updateCache;
updateCache.Clear();
ExposedList<Bone> bones = this.bones;
for (int i = 0, n = bones.Count; i < n; i++)
bones.Items[i].sorted = false;
ExposedList<IkConstraint> ikConstraints = this.ikConstraintsSorted;
ikConstraints.Clear();
ikConstraints.AddRange(this.ikConstraints);
int ikCount = ikConstraints.Count;
for (int i = 0, level, n = ikCount; i < n; i++) {
IkConstraint ik = ikConstraints.Items[i];
Bone bone = ik.bones.Items[0].parent;
for (level = 0; bone != null; level++)
bone = bone.parent;
ik.level = level;
}
for (int i = 1, ii; i < ikCount; i++) {
IkConstraint ik = ikConstraints.Items[i];
int level = ik.level;
for (ii = i - 1; ii >= 0; ii--) {
IkConstraint other = ikConstraints.Items[ii];
if (other.level < level) break;
ikConstraints.Items[ii + 1] = other;
}
ikConstraints.Items[ii + 1] = ik;
}
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
IkConstraint constraint = ikConstraints.Items[i];
Bone target = constraint.target;
SortBone(target);
ExposedList<Bone> constrained = constraint.bones;
Bone parent = constrained.Items[0];
SortBone(parent);
updateCache.Add(constraint);
SortReset(parent.children);
constrained.Items[constrained.Count - 1].sorted = true;
}
ExposedList<PathConstraint> pathConstraints = this.pathConstraints;
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
PathConstraint constraint = pathConstraints.Items[i];
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);
for (int ii = 0, nn = data.skins.Count; ii < nn; ii++)
SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone);
PathAttachment attachment = slot.Attachment as PathAttachment;
if (attachment != null) SortPathConstraintAttachment(attachment, slotBone);
ExposedList<Bone> constrained = constraint.bones;
int boneCount = constrained.Count;
for (int ii = 0; ii < boneCount; ii++)
SortBone(constrained.Items[ii]);
updateCache.Add(constraint);
for (int ii = 0; ii < boneCount; ii++)
SortReset(constrained.Items[ii].children);
for (int ii = 0; ii < boneCount; ii++)
constrained.Items[ii].sorted = true;
}
ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
TransformConstraint constraint = transformConstraints.Items[i];
SortBone(constraint.target);
ExposedList<Bone> constrained = constraint.bones;
int boneCount = constrained.Count;
for (int ii = 0; ii < boneCount; ii++)
SortBone(constrained.Items[ii]);
updateCache.Add(constraint);
for (int ii = 0; ii < boneCount; ii++)
SortReset(constrained.Items[ii].children);
for (int ii = 0; ii < boneCount; ii++)
constrained.Items[ii].sorted = true;
}
for (int i = 0, n = bones.Count; i < n; i++)
SortBone(bones.Items[i]);
}
private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
foreach (var entry in skin.Attachments)
if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone);
}
private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) {
var pathAttachment = attachment as PathAttachment;
if (pathAttachment == null) return;
int[] pathBones = pathAttachment.bones;
if (pathBones == null)
SortBone(slotBone);
else {
var bonesItems = this.bones.Items;
for (int i = 0, n = pathBones.Length; i < n;) {
int nn = pathBones[i++];
nn += i;
while (i < nn)
SortBone(bonesItems[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 void SortReset (ExposedList<Bone> bones) {
var bonesItems = bones.Items;
for (int i = 0, n = bones.Count; i < n; i++) {
Bone bone = bonesItems[i];
if (bone.sorted) SortReset(bone.children);
bone.sorted = false;
}
}
/// <summary>Updates the world transform for each bone and applies constraints.</summary>
public void UpdateWorldTransform () {
var updateItems = this.updateCache.Items;
for (int i = 0, n = updateCache.Count; i < n; i++)
updateItems[i].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 bonesItems = this.bones.Items;
for (int i = 0, n = bones.Count; i < n; i++)
bonesItems[i].SetToSetupPose();
var ikConstraintsItems = this.ikConstraints.Items;
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
IkConstraint constraint = ikConstraintsItems[i];
constraint.bendDirection = constraint.data.bendDirection;
constraint.mix = constraint.data.mix;
}
var transformConstraintsItems = this.transformConstraints.Items;
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
TransformConstraint constraint = transformConstraintsItems[i];
TransformConstraintData data = constraint.data;
constraint.rotateMix = data.rotateMix;
constraint.translateMix = data.translateMix;
constraint.scaleMix = data.scaleMix;
constraint.shearMix = data.shearMix;
}
var pathConstraintItems = this.pathConstraints.Items;
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
PathConstraint constraint = pathConstraintItems[i];
PathConstraintData data = constraint.data;
constraint.position = data.position;
constraint.spacing = data.spacing;
constraint.rotateMix = data.rotateMix;
constraint.translateMix = data.translateMix;
}
}
public void SetSlotsToSetupPose () {
var slots = this.slots;
var slotsItems = slots.Items;
drawOrder.Clear();
for (int i = 0, n = slots.Count; i < n; i++)
drawOrder.Add(slotsItems[i]);
for (int i = 0, n = slots.Count; i < n; i++)
slotsItems[i].SetToSetupPose();
}
/// <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;
var bonesItems = bones.Items;
for (int i = 0, n = bones.Count; i < n; i++) {
Bone bone = bonesItems[i];
if (bone.data.name == boneName) return bone;
}
return null;
}
/// <returns>-1 if the bone was not found.</returns>
public int FindBoneIndex (String boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
var bones = this.bones;
var bonesItems = bones.Items;
for (int i = 0, n = bones.Count; i < n; i++)
if (bonesItems[i].data.name == boneName) return i;
return -1;
}
/// <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;
var slotsItems = slots.Items;
for (int i = 0, n = slots.Count; i < n; i++) {
Slot slot = slotsItems[i];
if (slot.data.name == slotName) return slot;
}
return null;
}
/// <returns>-1 if the bone was not found.</returns>
public int FindSlotIndex (String slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
var slots = this.slots;
var slotsItems = slots.Items;
for (int i = 0, n = slots.Count; i < n; i++)
if (slotsItems[i].data.name.Equals(slotName)) return i;
return -1;
}
/// <summary>Sets a skin by name (see SetSkin).</summary>
public void SetSkin (String skinName) {
Skin skin = data.FindSkin(skinName);
if (skin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName");
SetSkin(skin);
}
/// <summary>Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default
/// skin}. Attachmentsfrom 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.</summary>
/// <param name="newSkin">May be null.</param>
public void SetSkin (Skin newSkin) {
if (newSkin != null) {
if (skin != null)
newSkin.AttachAll(this, skin);
else {
ExposedList<Slot> slots = this.slots;
for (int i = 0, n = slots.Count; i < n; i++) {
Slot slot = slots.Items[i];
String name = slot.data.attachmentName;
if (name != null) {
Attachment attachment = newSkin.GetAttachment(i, name);
if (attachment != null) slot.Attachment = attachment;
}
}
}
}
skin = newSkin;
}
/// <returns>May be null.</returns>
public Attachment GetAttachment (String slotName, String attachmentName) {
return GetAttachment(data.FindSlotIndex(slotName), attachmentName);
}
/// <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;
}
if (data.defaultSkin != null) return data.defaultSkin.GetAttachment(slotIndex, attachmentName);
return null;
}
/// <param name="attachmentName">May be null.</param>
public void SetAttachment (String slotName, String attachmentName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
ExposedList<Slot> slots = this.slots;
for (int i = 0, n = slots.Count; i < n; i++) {
Slot slot = slots.Items[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);
}
/// <returns>May be null.</returns>
public IkConstraint FindIkConstraint (String constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<IkConstraint> ikConstraints = this.ikConstraints;
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
IkConstraint ikConstraint = ikConstraints.Items[i];
if (ikConstraint.data.name == constraintName) return ikConstraint;
}
return null;
}
/// <returns>May be null.</returns>
public TransformConstraint FindTransformConstraint (String constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
TransformConstraint transformConstraint = transformConstraints.Items[i];
if (transformConstraint.data.name == constraintName) return transformConstraint;
}
return null;
}
/// <returns>May be null.</returns>
public PathConstraint FindPathConstraint (String constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<PathConstraint> pathConstraints = this.pathConstraints;
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
PathConstraint constraint = pathConstraints.Items[i];
if (constraint.data.name.Equals(constraintName)) return constraint;
}
return null;
}
public void Update (float delta) {
time += delta;
}
public void GetBounds(out float x, out float y, out float width, out float height)
{
float[] temp = new float[8];
float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
for (int i = 0, n = drawOrder.Count; i < n; i++)
{
Slot slot = drawOrder.Items[i];
int verticesLength = 0;
float[] vertices = null;
Attachment attachment = slot.Attachment;
if (attachment is RegionAttachment regionAttachment)
{
verticesLength = 8;
vertices = temp;
if (vertices.Length < 8) vertices = temp = new float[8];
regionAttachment.ComputeWorldVertices(slot.Bone, temp);
}
else if (attachment is MeshAttachment meshAttachment)
{
MeshAttachment mesh = meshAttachment;
verticesLength = mesh.Vertices.Length;
vertices = temp;
if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength];
mesh.ComputeWorldVertices(slot, temp);
}
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;
}
}
}

View File

@@ -0,0 +1,796 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
#if (UNITY_5 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
#define IS_UNITY
#endif
using System;
using System.IO;
using System.Collections.Generic;
#if WINDOWS_STOREAPP
using System.Threading.Tasks;
using Windows.Storage;
#endif
namespace SpineRuntime34 {
public class SkeletonBinary {
public const int BONE_ROTATE = 0;
public const int BONE_TRANSLATE = 1;
public const int BONE_SCALE = 2;
public const int BONE_SHEAR = 3;
public const int SLOT_ATTACHMENT = 0;
public const int SLOT_COLOR = 1;
public const int PATH_POSITION = 0;
public const int PATH_SPACING = 1;
public const int PATH_MIX = 2;
public const int CURVE_LINEAR = 0;
public const int CURVE_STEPPED = 1;
public const int CURVE_BEZIER = 2;
public float Scale { get; set; }
private AttachmentLoader attachmentLoader;
private byte[] buffer = new byte[32];
private List<SkeletonJson.LinkedMesh> linkedMeshes = new List<SkeletonJson.LinkedMesh>();
public SkeletonBinary (params Atlas[] atlasArray)
: this(new AtlasAttachmentLoader(atlasArray)) {
}
public SkeletonBinary (AttachmentLoader attachmentLoader) {
if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader");
this.attachmentLoader = attachmentLoader;
Scale = 1;
}
#if !ISUNITY && WINDOWS_STOREAPP
private async Task<SkeletonData> ReadFile(string path) {
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) {
SkeletonData skeletonData = ReadSkeletonData(input);
skeletonData.Name = Path.GetFileNameWithoutExtension(path);
return skeletonData;
}
}
public SkeletonData ReadSkeletonData (String path) {
return this.ReadFile(path).Result;
}
#else
public SkeletonData ReadSkeletonData (String path) {
#if WINDOWS_PHONE
using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) {
#else
using (var input = new BufferedStream(new FileStream(path, FileMode.Open))) {
#endif // WINDOWS_PHONE
SkeletonData skeletonData = ReadSkeletonData(input);
skeletonData.name = Path.GetFileNameWithoutExtension(path);
return skeletonData;
}
}
#endif // WINDOWS_STOREAPP
public SkeletonData ReadSkeletonData (Stream input) {
if (input == null) throw new ArgumentNullException("input");
float scale = Scale;
var skeletonData = new SkeletonData();
skeletonData.hash = ReadString(input);
if (skeletonData.hash.Length == 0) skeletonData.hash = null;
skeletonData.version = ReadString(input);
if (skeletonData.version.Length == 0) skeletonData.version = null;
skeletonData.width = ReadFloat(input);
skeletonData.height = ReadFloat(input);
bool nonessential = ReadBoolean(input);
if (nonessential) {
skeletonData.imagesPath = ReadString(input);
if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null;
}
// Bones.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
String name = ReadString(input);
BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)];
BoneData data = new BoneData(i, name, parent);
data.rotation = ReadFloat(input);
data.x = ReadFloat(input) * scale;
data.y = ReadFloat(input) * scale;
data.scaleX = ReadFloat(input);
data.scaleY = ReadFloat(input);
data.shearX = ReadFloat(input);
data.shearY = ReadFloat(input);
data.length = ReadFloat(input) * scale;
data.inheritRotation = ReadBoolean(input);
data.inheritScale = ReadBoolean(input);
if (nonessential) ReadInt(input); // Skip bone color.
skeletonData.bones.Add(data);
}
// Slots.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
String slotName = ReadString(input);
BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)];
SlotData slotData = new SlotData(i, slotName, boneData);
int color = ReadInt(input);
slotData.r = ((color & 0xff000000) >> 24) / 255f;
slotData.g = ((color & 0x00ff0000) >> 16) / 255f;
slotData.b = ((color & 0x0000ff00) >> 8) / 255f;
slotData.a = ((color & 0x000000ff)) / 255f;
slotData.attachmentName = ReadString(input);
slotData.blendMode = (BlendMode)ReadVarint(input, true);
skeletonData.slots.Add(slotData);
}
// IK constraints.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
IkConstraintData data = new IkConstraintData(ReadString(input));
for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
data.target = skeletonData.bones.Items[ReadVarint(input, true)];
data.mix = ReadFloat(input);
data.bendDirection = ReadSByte(input);
skeletonData.ikConstraints.Add(data);
}
// Transform constraints.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
TransformConstraintData data = new TransformConstraintData(ReadString(input));
for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
data.target = skeletonData.bones.Items[ReadVarint(input, true)];
data.offsetRotation = ReadFloat(input);
data.offsetX = ReadFloat(input) * scale;
data.offsetY = ReadFloat(input) * scale;
data.offsetScaleX = ReadFloat(input);
data.offsetScaleY = ReadFloat(input);
data.offsetShearY = ReadFloat(input);
data.rotateMix = ReadFloat(input);
data.translateMix = ReadFloat(input);
data.scaleMix = ReadFloat(input);
data.shearMix = ReadFloat(input);
skeletonData.transformConstraints.Add(data);
}
// Path constraints
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
PathConstraintData data = new PathConstraintData(ReadString(input));
for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
data.target = skeletonData.slots.Items[ReadVarint(input, true)];
data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(ReadVarint(input, true));
data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(ReadVarint(input, true));
data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(ReadVarint(input, true));
data.offsetRotation = ReadFloat(input);
data.position = ReadFloat(input);
if (data.positionMode == PositionMode.Fixed) data.position *= scale;
data.spacing = ReadFloat(input);
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
data.rotateMix = ReadFloat(input);
data.translateMix = ReadFloat(input);
skeletonData.pathConstraints.Add(data);
}
// Default skin.
Skin defaultSkin = ReadSkin(input, "default", nonessential);
if (defaultSkin != null) {
skeletonData.defaultSkin = defaultSkin;
skeletonData.skins.Add(defaultSkin);
}
// Skins.
for (int i = 0, n = ReadVarint(input, true); i < n; i++)
skeletonData.skins.Add(ReadSkin(input, ReadString(input), nonessential));
// Linked meshes.
for (int i = 0, n = linkedMeshes.Count; i < n; i++) {
SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i];
Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin);
if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin);
Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
linkedMesh.mesh.ParentMesh = (MeshAttachment)parent;
linkedMesh.mesh.UpdateUVs();
}
linkedMeshes.Clear();
// Events.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
EventData data = new EventData(ReadString(input));
data.Int = ReadVarint(input, false);
data.Float = ReadFloat(input);
data.String = ReadString(input);
skeletonData.events.Add(data);
}
// Animations.
for (int i = 0, n = ReadVarint(input, true); i < n; i++)
ReadAnimation(ReadString(input), input, skeletonData);
skeletonData.bones.TrimExcess();
skeletonData.slots.TrimExcess();
skeletonData.skins.TrimExcess();
skeletonData.events.TrimExcess();
skeletonData.animations.TrimExcess();
skeletonData.ikConstraints.TrimExcess();
skeletonData.pathConstraints.TrimExcess();
return skeletonData;
}
/// <returns>May be null.</returns>
private Skin ReadSkin (Stream input, String skinName, bool nonessential) {
int slotCount = ReadVarint(input, true);
if (slotCount == 0) return null;
Skin skin = new Skin(skinName);
for (int i = 0; i < slotCount; i++) {
int slotIndex = ReadVarint(input, true);
for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
String name = ReadString(input);
skin.AddAttachment(slotIndex, name, ReadAttachment(input, skin, slotIndex, name, nonessential));
}
}
return skin;
}
private Attachment ReadAttachment (Stream input, Skin skin, int slotIndex, String attachmentName, bool nonessential) {
float scale = Scale;
String name = ReadString(input);
if (name == null) name = attachmentName;
AttachmentType type = (AttachmentType)input.ReadByte();
switch (type) {
case AttachmentType.Region: {
String path = ReadString(input);
float rotation = ReadFloat(input);
float x = ReadFloat(input);
float y = ReadFloat(input);
float scaleX = ReadFloat(input);
float scaleY = ReadFloat(input);
float width = ReadFloat(input);
float height = ReadFloat(input);
int color = ReadInt(input);
if (path == null) path = name;
RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path);
if (region == null) return null;
region.Path = path;
region.x = x * scale;
region.y = y * scale;
region.scaleX = scaleX;
region.scaleY = scaleY;
region.rotation = rotation;
region.width = width * scale;
region.height = height * scale;
region.r = ((color & 0xff000000) >> 24) / 255f;
region.g = ((color & 0x00ff0000) >> 16) / 255f;
region.b = ((color & 0x0000ff00) >> 8) / 255f;
region.a = ((color & 0x000000ff)) / 255f;
region.UpdateOffset();
return region;
}
case AttachmentType.Boundingbox: {
int vertexCount = ReadVarint(input, true);
Vertices vertices = ReadVertices(input, vertexCount);
if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning.
BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
if (box == null) return null;
box.worldVerticesLength = vertexCount << 1;
box.vertices = vertices.vertices;
box.bones = vertices.bones;
return box;
}
case AttachmentType.Mesh: {
String path = ReadString(input);
int color = ReadInt(input);
int vertexCount = ReadVarint(input, true);
float[] uvs = ReadFloatArray(input, vertexCount << 1, 1);
int[] triangles = ReadShortArray(input);
Vertices vertices = ReadVertices(input, vertexCount);
int hullLength = ReadVarint(input, true);
int[] edges = null;
float width = 0, height = 0;
if (nonessential) {
edges = ReadShortArray(input);
width = ReadFloat(input);
height = ReadFloat(input);
}
if (path == null) path = name;
MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
if (mesh == null) return null;
mesh.Path = path;
mesh.r = ((color & 0xff000000) >> 24) / 255f;
mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
mesh.a = ((color & 0x000000ff)) / 255f;
mesh.bones = vertices.bones;
mesh.vertices = vertices.vertices;
mesh.WorldVerticesLength = vertexCount << 1;
mesh.triangles = triangles;
mesh.regionUVs = uvs;
mesh.UpdateUVs();
mesh.HullLength = hullLength << 1;
if (nonessential) {
mesh.Edges = edges;
mesh.Width = width * scale;
mesh.Height = height * scale;
}
return mesh;
}
case AttachmentType.Linkedmesh: {
String path = ReadString(input);
int color = ReadInt(input);
String skinName = ReadString(input);
String parent = ReadString(input);
bool inheritDeform = ReadBoolean(input);
float width = 0, height = 0;
if (nonessential) {
width = ReadFloat(input);
height = ReadFloat(input);
}
if (path == null) path = name;
MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
if (mesh == null) return null;
mesh.Path = path;
mesh.r = ((color & 0xff000000) >> 24) / 255f;
mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
mesh.a = ((color & 0x000000ff)) / 255f;
mesh.inheritDeform = inheritDeform;
if (nonessential) {
mesh.Width = width * scale;
mesh.Height = height * scale;
}
linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent));
return mesh;
}
case AttachmentType.Path: {
bool closed = ReadBoolean(input);
bool constantSpeed = ReadBoolean(input);
int vertexCount = ReadVarint(input, true);
Vertices vertices = ReadVertices(input, vertexCount);
float[] lengths = new float[vertexCount / 3];
for (int i = 0, n = lengths.Length; i < n; i++)
lengths[i] = ReadFloat(input) * scale;
if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning.
PathAttachment path = attachmentLoader.NewPathAttachment(skin, name);
if (path == null) return null;
path.closed = closed;
path.constantSpeed = constantSpeed;
path.worldVerticesLength = vertexCount << 1;
path.vertices = vertices.vertices;
path.bones = vertices.bones;
path.lengths = lengths;
return path;
}
}
return null;
}
private Vertices ReadVertices (Stream input, int vertexCount) {
float scale = Scale;
int verticesLength = vertexCount << 1;
Vertices vertices = new Vertices();
if(!ReadBoolean(input)) {
vertices.vertices = ReadFloatArray(input, verticesLength, scale);
return vertices;
}
var weights = new ExposedList<float>(verticesLength * 3 * 3);
var bonesArray = new ExposedList<int>(verticesLength * 3);
for (int i = 0; i < vertexCount; i++) {
int boneCount = ReadVarint(input, true);
bonesArray.Add(boneCount);
for (int ii = 0; ii < boneCount; ii++) {
bonesArray.Add(ReadVarint(input, true));
weights.Add(ReadFloat(input) * scale);
weights.Add(ReadFloat(input) * scale);
weights.Add(ReadFloat(input));
}
}
vertices.vertices = weights.ToArray();
vertices.bones = bonesArray.ToArray();
return vertices;
}
private float[] ReadFloatArray (Stream input, int n, float scale) {
float[] array = new float[n];
if (scale == 1) {
for (int i = 0; i < n; i++)
array[i] = ReadFloat(input);
} else {
for (int i = 0; i < n; i++)
array[i] = ReadFloat(input) * scale;
}
return array;
}
private int[] ReadShortArray (Stream input) {
int n = ReadVarint(input, true);
int[] array = new int[n];
for (int i = 0; i < n; i++)
array[i] = (input.ReadByte() << 8) | input.ReadByte();
return array;
}
private void ReadAnimation (String name, Stream input, SkeletonData skeletonData) {
var timelines = new ExposedList<Timeline>();
float scale = Scale;
float duration = 0;
// Slot timelines.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
int slotIndex = ReadVarint(input, true);
for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
int timelineType = input.ReadByte();
int frameCount = ReadVarint(input, true);
switch (timelineType) {
case SLOT_COLOR: {
ColorTimeline timeline = new ColorTimeline(frameCount);
timeline.slotIndex = slotIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
float time = ReadFloat(input);
int color = ReadInt(input);
float r = ((color & 0xff000000) >> 24) / 255f;
float g = ((color & 0x00ff0000) >> 16) / 255f;
float b = ((color & 0x0000ff00) >> 8) / 255f;
float a = ((color & 0x000000ff)) / 255f;
timeline.SetFrame(frameIndex, time, r, g, b, a);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]);
break;
}
case SLOT_ATTACHMENT: {
AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
timeline.slotIndex = slotIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input));
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount - 1]);
break;
}
}
}
}
// Bone timelines.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
int boneIndex = ReadVarint(input, true);
for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
int timelineType = input.ReadByte();
int frameCount = ReadVarint(input, true);
switch (timelineType) {
case BONE_ROTATE: {
RotateTimeline timeline = new RotateTimeline(frameCount);
timeline.boneIndex = boneIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input));
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]);
break;
}
case BONE_TRANSLATE:
case BONE_SCALE:
case BONE_SHEAR: {
TranslateTimeline timeline;
float timelineScale = 1;
if (timelineType == BONE_SCALE)
timeline = new ScaleTimeline(frameCount);
else if (timelineType == BONE_SHEAR)
timeline = new ShearTimeline(frameCount);
else {
timeline = new TranslateTimeline(frameCount);
timelineScale = scale;
}
timeline.boneIndex = boneIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input)
* timelineScale);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]);
break;
}
}
}
}
// IK timelines.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
int index = ReadVarint(input, true);
int frameCount = ReadVarint(input, true);
IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount);
timeline.ikConstraintIndex = index;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input));
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]);
}
// Transform constraint timelines.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
int index = ReadVarint(input, true);
int frameCount = ReadVarint(input, true);
TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount);
timeline.transformConstraintIndex = index;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input));
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]);
}
// Path constraint timelines.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
int index = ReadVarint(input, true);
PathConstraintData data = skeletonData.pathConstraints.Items[index];
for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
int timelineType = ReadSByte(input);
int frameCount = ReadVarint(input, true);
switch(timelineType) {
case PATH_POSITION:
case PATH_SPACING: {
PathConstraintPositionTimeline timeline;
float timelineScale = 1;
if (timelineType == PATH_SPACING) {
timeline = new PathConstraintSpacingTimeline(frameCount);
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale;
} else {
timeline = new PathConstraintPositionTimeline(frameCount);
if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
}
timeline.pathConstraintIndex = index;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
break;
}
case PATH_MIX: {
PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount);
timeline.pathConstraintIndex = index;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input));
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
break;
}
}
}
}
// Deform timelines.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
Skin skin = skeletonData.skins.Items[ReadVarint(input, true)];
for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
int slotIndex = ReadVarint(input, true);
for (int iii = 0, nnn = ReadVarint(input, true); iii < nnn; iii++) {
VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, ReadString(input));
bool weighted = attachment.bones != null;
float[] vertices = attachment.vertices;
int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length;
int frameCount = ReadVarint(input, true);
DeformTimeline timeline = new DeformTimeline(frameCount);
timeline.slotIndex = slotIndex;
timeline.attachment = attachment;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
float time = ReadFloat(input);
float[] deform;
int end = ReadVarint(input, true);
if (end == 0)
deform = weighted ? new float[deformLength] : vertices;
else {
deform = new float[deformLength];
int start = ReadVarint(input, true);
end += start;
if (scale == 1) {
for (int v = start; v < end; v++)
deform[v] = ReadFloat(input);
} else {
for (int v = start; v < end; v++)
deform[v] = ReadFloat(input) * scale;
}
if (!weighted) {
for (int v = 0, vn = deform.Length; v < vn; v++)
deform[v] += vertices[v];
}
}
timeline.SetFrame(frameIndex, time, deform);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount - 1]);
}
}
}
// Draw order timeline.
int drawOrderCount = ReadVarint(input, true);
if (drawOrderCount > 0) {
DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount);
int slotCount = skeletonData.slots.Count;
for (int i = 0; i < drawOrderCount; i++) {
float time = ReadFloat(input);
int offsetCount = ReadVarint(input, true);
int[] drawOrder = new int[slotCount];
for (int ii = slotCount - 1; ii >= 0; ii--)
drawOrder[ii] = -1;
int[] unchanged = new int[slotCount - offsetCount];
int originalIndex = 0, unchangedIndex = 0;
for (int ii = 0; ii < offsetCount; ii++) {
int slotIndex = ReadVarint(input, true);
// Collect unchanged items.
while (originalIndex != slotIndex)
unchanged[unchangedIndex++] = originalIndex++;
// Set changed items.
drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++;
}
// Collect remaining unchanged items.
while (originalIndex < slotCount)
unchanged[unchangedIndex++] = originalIndex++;
// Fill in unchanged items.
for (int ii = slotCount - 1; ii >= 0; ii--)
if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex];
timeline.SetFrame(i, time, drawOrder);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]);
}
// Event timeline.
int eventCount = ReadVarint(input, true);
if (eventCount > 0) {
EventTimeline timeline = new EventTimeline(eventCount);
for (int i = 0; i < eventCount; i++) {
float time = ReadFloat(input);
EventData eventData = skeletonData.events.Items[ReadVarint(input, true)];
Event e = new Event(time, eventData);
e.Int = ReadVarint(input, false);
e.Float = ReadFloat(input);
e.String = ReadBoolean(input) ? ReadString(input) : eventData.String;
timeline.SetFrame(i, e);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[eventCount - 1]);
}
timelines.TrimExcess();
skeletonData.animations.Add(new Animation(name, timelines, duration));
}
private void ReadCurve (Stream input, int frameIndex, CurveTimeline timeline) {
switch (input.ReadByte()) {
case CURVE_STEPPED:
timeline.SetStepped(frameIndex);
break;
case CURVE_BEZIER:
timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input));
break;
}
}
private static sbyte ReadSByte (Stream input) {
int value = input.ReadByte();
if (value == -1) throw new EndOfStreamException();
return (sbyte)value;
}
private static bool ReadBoolean (Stream input) {
return input.ReadByte() != 0;
}
private float ReadFloat (Stream input) {
buffer[3] = (byte)input.ReadByte();
buffer[2] = (byte)input.ReadByte();
buffer[1] = (byte)input.ReadByte();
buffer[0] = (byte)input.ReadByte();
return BitConverter.ToSingle(buffer, 0);
}
private static int ReadInt (Stream input) {
return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte();
}
private static int ReadVarint (Stream input, bool optimizePositive) {
int b = input.ReadByte();
int result = b & 0x7F;
if ((b & 0x80) != 0) {
b = input.ReadByte();
result |= (b & 0x7F) << 7;
if ((b & 0x80) != 0) {
b = input.ReadByte();
result |= (b & 0x7F) << 14;
if ((b & 0x80) != 0) {
b = input.ReadByte();
result |= (b & 0x7F) << 21;
if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28;
}
}
}
return optimizePositive ? result : ((result >> 1) ^ -(result & 1));
}
private string ReadString (Stream input) {
int byteCount = ReadVarint(input, true);
switch (byteCount) {
case 0:
return null;
case 1:
return "";
}
byteCount--;
byte[] buffer = this.buffer;
if (buffer.Length < byteCount) buffer = new byte[byteCount];
ReadFully(input, buffer, 0, byteCount);
return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount);
}
private static void ReadFully (Stream input, byte[] buffer, int offset, int length) {
while (length > 0) {
int count = input.Read(buffer, offset, length);
if (count <= 0) throw new EndOfStreamException();
offset += count;
length -= count;
}
}
internal class Vertices {
public int[] bones;
public float[] vertices;
}
}
}

View File

@@ -0,0 +1,214 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
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>();
}
public void Update (Skeleton skeleton, bool updateAabb) {
ExposedList<BoundingBoxAttachment> boundingBoxes = BoundingBoxes;
ExposedList<Polygon> polygons = Polygons;
ExposedList<Slot> slots = skeleton.slots;
int slotCount = 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.Items[i];
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.Vertices.Length;
polygon.Count = count;
if (polygon.Vertices.Length < count) polygon.Vertices = new float[count];
boundingBox.ComputeWorldVertices(slot, polygon.Vertices);
}
if (updateAabb) aabbCompute();
}
private void aabbCompute () {
float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
ExposedList<Polygon> polygons = Polygons;
for (int i = 0, n = polygons.Count; i < n; i++) {
Polygon polygon = polygons.Items[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 {@link #aabbContainsPoint(float, float)} returns true.</summary>
public BoundingBoxAttachment ContainsPoint (float x, float y) {
ExposedList<Polygon> polygons = Polygons;
for (int i = 0, n = polygons.Count; i < n; i++)
if (ContainsPoint(polygons.Items[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 {@link #aabbIntersectsSegment(float, float, float, float)} returns true.</summary>
public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) {
ExposedList<Polygon> polygons = Polygons;
for (int i = 0, n = polygons.Count; i < n; i++)
if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i];
return null;
}
/// <summary>Returns true if the polygon contains the line segment.</summary>
public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) {
float[] vertices = polygon.Vertices;
int nn = polygon.Count;
float width12 = x1 - x2, height12 = y1 - y2;
float det1 = x1 * y2 - y1 * x2;
float x3 = vertices[nn - 2], y3 = vertices[nn - 1];
for (int ii = 0; ii < nn; ii += 2) {
float x4 = vertices[ii], y4 = vertices[ii + 1];
float det2 = x3 * y4 - y3 * x4;
float width34 = x3 - x4, height34 = y3 - y4;
float det3 = width12 * height34 - height12 * width34;
float x = (det1 * width34 - width12 * det2) / det3;
if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) {
float y = (det1 * height34 - height12 * det2) / det3;
if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) return true;
}
x3 = x4;
y3 = y4;
}
return false;
}
public Polygon getPolygon (BoundingBoxAttachment attachment) {
int index = BoundingBoxes.IndexOf(attachment);
return index == -1 ? null : Polygons.Items[index];
}
}
public class Polygon {
public float[] Vertices { get; set; }
public int Count { get; set; }
public Polygon () {
Vertices = new float[16];
}
}
}

View File

@@ -0,0 +1,196 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
public class SkeletonData {
internal String name;
internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
internal ExposedList<SlotData> slots = new ExposedList<SlotData>();
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 width, height;
internal String version, hash, imagesPath;
public String Name { get { return name; } set { name = value; } }
public ExposedList<BoneData> Bones { get { return bones; } } // Ordered parents first.
public ExposedList<SlotData> Slots { get { return slots; } } // Setup pose draw order.
public ExposedList<Skin> Skins { get { return skins; } set { skins = value; } }
/// <summary>May be null.</summary>
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 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.</summary>
public String Version { get { return version; } set { version = value; } }
public String Hash { get { return hash; } set { hash = value; } }
// --- Bones.
/// <returns>May be null.</returns>
public BoneData FindBone (String boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
ExposedList<BoneData> bones = this.bones;
for (int i = 0, n = bones.Count; i < n; i++) {
BoneData bone = bones.Items[i];
if (bone.name == boneName) return bone;
}
return null;
}
/// <returns>-1 if the bone was not found.</returns>
public int FindBoneIndex (String boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
ExposedList<BoneData> bones = this.bones;
for (int i = 0, n = bones.Count; i < n; i++)
if (bones.Items[i].name == boneName) return i;
return -1;
}
// --- Slots.
/// <returns>May be null.</returns>
public SlotData FindSlot (String slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
ExposedList<SlotData> slots = this.slots;
for (int i = 0, n = slots.Count; i < n; i++) {
SlotData slot = slots.Items[i];
if (slot.name == slotName) return slot;
}
return null;
}
/// <returns>-1 if the slot was not found.</returns>
public int FindSlotIndex (String slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
ExposedList<SlotData> slots = this.slots;
for (int i = 0, n = slots.Count; i < n; i++)
if (slots.Items[i].name == slotName) return i;
return -1;
}
// --- 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.");
ExposedList<Animation> animations = this.animations;
for (int i = 0, n = animations.Count; i < n; i++) {
Animation animation = animations.Items[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.");
ExposedList<IkConstraintData> ikConstraints = this.ikConstraints;
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
IkConstraintData ikConstraint = ikConstraints.Items[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.");
ExposedList<TransformConstraintData> transformConstraints = this.transformConstraints;
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
TransformConstraintData transformConstraint = transformConstraints.Items[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.");
ExposedList<PathConstraintData> pathConstraints = this.pathConstraints;
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
PathConstraintData constraint = pathConstraints.Items[i];
if (constraint.name.Equals(constraintName)) return constraint;
}
return null;
}
/// <returns>-1 if the path constraint was not found.</returns>
public int FindPathConstraintIndex (String pathConstraintName) {
if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null.");
ExposedList<PathConstraintData> pathConstraints = this.pathConstraints;
for (int i = 0, n = pathConstraints.Count; i < n; i++)
if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i;
return -1;
}
// ---
override public String ToString () {
return name ?? base.ToString();
}
}
}

View File

@@ -0,0 +1,805 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
#if (UNITY_5 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
#define IS_UNITY
#endif
using System;
using System.IO;
using System.Collections.Generic;
#if WINDOWS_STOREAPP
using System.Threading.Tasks;
using Windows.Storage;
#endif
namespace SpineRuntime34 {
public class SkeletonJson {
public float Scale { get; set; }
private AttachmentLoader attachmentLoader;
private List<LinkedMesh> linkedMeshes = new List<LinkedMesh>();
public SkeletonJson (params Atlas[] atlasArray)
: this(new AtlasAttachmentLoader(atlasArray)) {
}
public SkeletonJson (AttachmentLoader attachmentLoader) {
if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null.");
this.attachmentLoader = attachmentLoader;
Scale = 1;
}
#if !(IS_UNITY) && WINDOWS_STOREAPP
private async Task<SkeletonData> ReadFile(string path) {
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false);
using (var reader = new StreamReader(await file.OpenStreamForReadAsync().ConfigureAwait(false))) {
SkeletonData skeletonData = ReadSkeletonData(reader);
skeletonData.Name = Path.GetFileNameWithoutExtension(path);
return skeletonData;
}
}
public SkeletonData ReadSkeletonData (String path) {
return this.ReadFile(path).Result;
}
#else
public SkeletonData ReadSkeletonData (String path) {
#if WINDOWS_PHONE
Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path);
using (StreamReader reader = new StreamReader(stream)) {
#else
using (var reader = new StreamReader(path)) {
#endif // WINDOWS_PHONE
SkeletonData skeletonData = ReadSkeletonData(reader);
skeletonData.name = Path.GetFileNameWithoutExtension(path);
return skeletonData;
}
}
#endif // WINDOWS_STOREAPP
public SkeletonData ReadSkeletonData (TextReader reader) {
if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null.");
var scale = this.Scale;
var skeletonData = new SkeletonData();
var root = Json.Deserialize(reader) as Dictionary<String, Object>;
if (root == null) throw new Exception("Invalid JSON.");
// Skeleton.
if (root.ContainsKey("skeleton")) {
var skeletonMap = (Dictionary<String, Object>)root["skeleton"];
skeletonData.hash = (String)skeletonMap["hash"];
skeletonData.version = (String)skeletonMap["spine"];
skeletonData.width = GetFloat(skeletonMap, "width", 0);
skeletonData.height = GetFloat(skeletonMap, "height", 0);
}
// Bones.
foreach (Dictionary<String, Object> boneMap in (List<Object>)root["bones"]) {
BoneData parent = null;
if (boneMap.ContainsKey("parent")) {
parent = skeletonData.FindBone((String)boneMap["parent"]);
if (parent == null)
throw new Exception("Parent bone not found: " + boneMap["parent"]);
}
var data = new BoneData(skeletonData.Bones.Count, (String)boneMap["name"], parent);
data.length = GetFloat(boneMap, "length", 0) * scale;
data.x = GetFloat(boneMap, "x", 0) * scale;
data.y = GetFloat(boneMap, "y", 0) * scale;
data.rotation = GetFloat(boneMap, "rotation", 0);
data.scaleX = GetFloat(boneMap, "scaleX", 1);
data.scaleY = GetFloat(boneMap, "scaleY", 1);
data.shearX = GetFloat(boneMap, "shearX", 0);
data.shearY = GetFloat(boneMap, "shearY", 0);
data.inheritRotation = GetBoolean(boneMap, "inheritRotation", true);
data.inheritScale = GetBoolean(boneMap, "inheritScale", true);
skeletonData.bones.Add(data);
}
// Slots.
if (root.ContainsKey("slots")) {
foreach (Dictionary<String, Object> slotMap in (List<Object>)root["slots"]) {
var slotName = (String)slotMap["name"];
var boneName = (String)slotMap["bone"];
BoneData boneData = skeletonData.FindBone(boneName);
if (boneData == null) throw new Exception("Slot bone not found: " + boneName);
var data = new SlotData(skeletonData.Slots.Count, slotName, boneData);
if (slotMap.ContainsKey("color")) {
var color = (String)slotMap["color"];
data.r = ToColor(color, 0);
data.g = ToColor(color, 1);
data.b = ToColor(color, 2);
data.a = ToColor(color, 3);
}
data.attachmentName = GetString(slotMap, "attachment", null);
if (slotMap.ContainsKey("blend"))
data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], false);
else
data.blendMode = BlendMode.normal;
skeletonData.slots.Add(data);
}
}
// IK constraints.
if (root.ContainsKey("ik")) {
foreach (Dictionary<String, Object> constraintMap in (List<Object>)root["ik"]) {
IkConstraintData data = new IkConstraintData((String)constraintMap["name"]);
foreach (String boneName in (List<Object>)constraintMap["bones"]) {
BoneData bone = skeletonData.FindBone(boneName);
if (bone == null) throw new Exception("IK constraint bone not found: " + boneName);
data.bones.Add(bone);
}
String targetName = (String)constraintMap["target"];
data.target = skeletonData.FindBone(targetName);
if (data.target == null) throw new Exception("Target bone not found: " + targetName);
data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1;
data.mix = GetFloat(constraintMap, "mix", 1);
skeletonData.ikConstraints.Add(data);
}
}
// Transform constraints.
if (root.ContainsKey("transform")) {
foreach (Dictionary<String, Object> constraintMap in (List<Object>)root["transform"]) {
TransformConstraintData data = new TransformConstraintData((String)constraintMap["name"]);
foreach (String boneName in (List<Object>)constraintMap["bones"]) {
BoneData bone = skeletonData.FindBone(boneName);
if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName);
data.bones.Add(bone);
}
String targetName = (String)constraintMap["target"];
data.target = skeletonData.FindBone(targetName);
if (data.target == null) throw new Exception("Target bone not found: " + targetName);
data.offsetRotation = GetFloat(constraintMap, "rotation", 0);
data.offsetX = GetFloat(constraintMap, "x", 0) * scale;
data.offsetY = GetFloat(constraintMap, "y", 0) * scale;
data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0);
data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0);
data.offsetShearY = GetFloat(constraintMap, "shearY", 0);
data.rotateMix = GetFloat(constraintMap, "rotateMix", 1);
data.translateMix = GetFloat(constraintMap, "translateMix", 1);
data.scaleMix = GetFloat(constraintMap, "scaleMix", 1);
data.shearMix = GetFloat(constraintMap, "shearMix", 1);
skeletonData.transformConstraints.Add(data);
}
}
// Path constraints.
if(root.ContainsKey("path")) {
foreach (Dictionary<String, Object> constraintMap in (List<Object>)root["path"]) {
PathConstraintData data = new PathConstraintData((String)constraintMap["name"]);
foreach (String boneName in (List<Object>)constraintMap["bones"]) {
BoneData bone = skeletonData.FindBone(boneName);
if (bone == null) throw new Exception("Path bone not found: " + boneName);
data.bones.Add(bone);
}
String targetName = (String)constraintMap["target"];
data.target = skeletonData.FindSlot(targetName);
if (data.target == null) throw new Exception("Target slot not found: " + targetName);
data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true);
data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true);
data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true);
data.offsetRotation = GetFloat(constraintMap, "rotation", 0);
data.position = GetFloat(constraintMap, "position", 0);
if (data.positionMode == PositionMode.Fixed) data.position *= scale;
data.spacing = GetFloat(constraintMap, "spacing", 0);
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
data.rotateMix = GetFloat(constraintMap, "rotateMix", 1);
data.translateMix = GetFloat(constraintMap, "translateMix", 1);
skeletonData.pathConstraints.Add(data);
}
}
// Skins.
if (root.ContainsKey("skins")) {
foreach (KeyValuePair<String, Object> skinMap in (Dictionary<String, Object>)root["skins"]) {
var skin = new Skin(skinMap.Key);
foreach (KeyValuePair<String, Object> slotEntry in (Dictionary<String, Object>)skinMap.Value) {
int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key);
foreach (KeyValuePair<String, Object> entry in ((Dictionary<String, Object>)slotEntry.Value)) {
try {
Attachment attachment = ReadAttachment((Dictionary<String, Object>)entry.Value, skin, slotIndex, entry.Key);
if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment);
} catch (Exception e) {
throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e);
}
}
}
skeletonData.skins.Add(skin);
if (skin.name == "default") skeletonData.defaultSkin = skin;
}
}
// Linked meshes.
for (int i = 0, n = linkedMeshes.Count; i < n; i++) {
LinkedMesh linkedMesh = linkedMeshes[i];
Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin);
if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin);
Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
linkedMesh.mesh.ParentMesh = (MeshAttachment)parent;
linkedMesh.mesh.UpdateUVs();
}
linkedMeshes.Clear();
// Events.
if (root.ContainsKey("events")) {
foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)root["events"]) {
var entryMap = (Dictionary<String, Object>)entry.Value;
var data = new EventData(entry.Key);
data.Int = GetInt(entryMap, "int", 0);
data.Float = GetFloat(entryMap, "float", 0);
data.String = GetString(entryMap, "string", null);
skeletonData.events.Add(data);
}
}
// Animations.
if (root.ContainsKey("animations")) {
foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)root["animations"]) {
try {
ReadAnimation((Dictionary<String, Object>)entry.Value, entry.Key, skeletonData);
} catch (Exception e) {
throw new Exception("Error reading animation: " + entry.Key, e);
}
}
}
skeletonData.bones.TrimExcess();
skeletonData.slots.TrimExcess();
skeletonData.skins.TrimExcess();
skeletonData.events.TrimExcess();
skeletonData.animations.TrimExcess();
skeletonData.ikConstraints.TrimExcess();
return skeletonData;
}
private Attachment ReadAttachment (Dictionary<String, Object> map, Skin skin, int slotIndex, String name) {
var scale = this.Scale;
name = GetString(map, "name", name);
var typeName = GetString(map, "type", "region");
if (typeName == "skinnedmesh") typeName = "weightedmesh";
if (typeName == "weightedmesh") typeName = "mesh";
if (typeName == "weightedlinkedmesh") typeName = "linkedmesh";
var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true);
String path = GetString(map, "path", name);
switch (type) {
case AttachmentType.Region:
RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path);
if (region == null) return null;
region.Path = path;
region.x = GetFloat(map, "x", 0) * scale;
region.y = GetFloat(map, "y", 0) * scale;
region.scaleX = GetFloat(map, "scaleX", 1);
region.scaleY = GetFloat(map, "scaleY", 1);
region.rotation = GetFloat(map, "rotation", 0);
region.width = GetFloat(map, "width", 32) * scale;
region.height = GetFloat(map, "height", 32) * scale;
region.UpdateOffset();
if (map.ContainsKey("color")) {
var color = (String)map["color"];
region.r = ToColor(color, 0);
region.g = ToColor(color, 1);
region.b = ToColor(color, 2);
region.a = ToColor(color, 3);
}
region.UpdateOffset();
return region;
case AttachmentType.Boundingbox:
BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
if (box == null) return null;
ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1);
return box;
case AttachmentType.Mesh:
case AttachmentType.Linkedmesh: {
MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
if (mesh == null) return null;
mesh.Path = path;
if (map.ContainsKey("color")) {
var color = (String)map["color"];
mesh.r = ToColor(color, 0);
mesh.g = ToColor(color, 1);
mesh.b = ToColor(color, 2);
mesh.a = ToColor(color, 3);
}
mesh.Width = GetFloat(map, "width", 0) * scale;
mesh.Height = GetFloat(map, "height", 0) * scale;
String parent = GetString(map, "parent", null);
if (parent != null) {
mesh.InheritDeform = GetBoolean(map, "deform", true);
linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent));
return mesh;
}
float[] uvs = GetFloatArray(map, "uvs", 1);
ReadVertices(map, mesh, uvs.Length);
mesh.triangles = GetIntArray(map, "triangles");
mesh.regionUVs = uvs;
mesh.UpdateUVs();
if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2;
if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges");
return mesh;
}
case AttachmentType.Path: {
PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name);
if (pathAttachment == null) return null;
pathAttachment.closed = GetBoolean(map, "closed", false);
pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true);
int vertexCount = GetInt(map, "vertexCount", 0);
ReadVertices(map, pathAttachment, vertexCount << 1);
// potential BOZO see Java impl
pathAttachment.lengths = GetFloatArray(map, "lengths", scale);
return pathAttachment;
}
}
return null;
}
private void ReadVertices (Dictionary<String, Object> map, VertexAttachment attachment, int verticesLength) {
attachment.WorldVerticesLength = verticesLength;
float[] vertices = GetFloatArray(map, "vertices", 1);
float scale = Scale;
if (verticesLength == vertices.Length) {
if (scale != 1) {
for (int i = 0; i < vertices.Length; i++) {
vertices[i] *= scale;
}
}
attachment.vertices = vertices;
return;
}
ExposedList<float> weights = new ExposedList<float>(verticesLength * 3 * 3);
ExposedList<int> bones = new ExposedList<int>(verticesLength * 3);
for (int i = 0, n = vertices.Length; i < n;) {
int boneCount = (int)vertices[i++];
bones.Add(boneCount);
for (int nn = i + boneCount * 4; i < nn; i += 4) {
bones.Add((int)vertices[i]);
weights.Add(vertices[i + 1] * this.Scale);
weights.Add(vertices[i + 2] * this.Scale);
weights.Add(vertices[i + 3]);
}
}
attachment.bones = bones.ToArray();
attachment.vertices = weights.ToArray();
}
private void ReadAnimation (Dictionary<String, Object> map, String name, SkeletonData skeletonData) {
var scale = this.Scale;
var timelines = new ExposedList<Timeline>();
float duration = 0;
// Slot timelines.
if (map.ContainsKey("slots")) {
foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)map["slots"]) {
String slotName = entry.Key;
int slotIndex = skeletonData.FindSlotIndex(slotName);
var timelineMap = (Dictionary<String, Object>)entry.Value;
foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) {
var values = (List<Object>)timelineEntry.Value;
var timelineName = (String)timelineEntry.Key;
if (timelineName == "color") {
var timeline = new ColorTimeline(values.Count);
timeline.slotIndex = slotIndex;
int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) {
float time = (float)valueMap["time"];
String c = (String)valueMap["color"];
timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3));
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]);
} else if (timelineName == "attachment") {
var timeline = new AttachmentTimeline(values.Count);
timeline.slotIndex = slotIndex;
int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) {
float time = (float)valueMap["time"];
timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
} else
throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");
}
}
}
// Bone timelines.
if (map.ContainsKey("bones")) {
foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)map["bones"]) {
String boneName = entry.Key;
int boneIndex = skeletonData.FindBoneIndex(boneName);
if (boneIndex == -1) throw new Exception("Bone not found: " + boneName);
var timelineMap = (Dictionary<String, Object>)entry.Value;
foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) {
var values = (List<Object>)timelineEntry.Value;
var timelineName = (String)timelineEntry.Key;
if (timelineName == "rotate") {
var timeline = new RotateTimeline(values.Count);
timeline.boneIndex = boneIndex;
int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) {
timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]);
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]);
} else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") {
TranslateTimeline timeline;
float timelineScale = 1;
if (timelineName == "scale")
timeline = new ScaleTimeline(values.Count);
else if (timelineName == "shear")
timeline = new ShearTimeline(values.Count);
else {
timeline = new TranslateTimeline(values.Count);
timelineScale = scale;
}
timeline.boneIndex = boneIndex;
int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) {
float time = (float)valueMap["time"];
float x = GetFloat(valueMap, "x", 0);
float y = GetFloat(valueMap, "y", 0);
timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale);
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]);
} else
throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
}
}
}
// IK constraint timelines.
if (map.ContainsKey("ik")) {
foreach (KeyValuePair<String, Object> constraintMap in (Dictionary<String, Object>)map["ik"]) {
IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key);
var values = (List<Object>)constraintMap.Value;
var timeline = new IkConstraintTimeline(values.Count);
timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint);
int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) {
float time = (float)valueMap["time"];
float mix = GetFloat(valueMap, "mix", 1);
bool bendPositive = GetBoolean(valueMap, "bendPositive", true);
timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1);
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]);
}
}
// Transform constraint timelines.
if (map.ContainsKey("transform")) {
foreach (KeyValuePair<String, Object> constraintMap in (Dictionary<String, Object>)map["transform"]) {
TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key);
var values = (List<Object>)constraintMap.Value;
var timeline = new TransformConstraintTimeline(values.Count);
timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint);
int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) {
float time = (float)valueMap["time"];
float rotateMix = GetFloat(valueMap, "rotateMix", 1);
float translateMix = GetFloat(valueMap, "translateMix", 1);
float scaleMix = GetFloat(valueMap, "scaleMix", 1);
float shearMix = GetFloat(valueMap, "shearMix", 1);
timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix);
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]);
}
}
// Path constraint timelines.
if (map.ContainsKey("paths")) {
foreach (KeyValuePair<String, Object> constraintMap in (Dictionary<String, Object>)map["paths"]) {
int index = skeletonData.FindPathConstraintIndex(constraintMap.Key);
if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key);
PathConstraintData data = skeletonData.pathConstraints.Items[index];
var timelineMap = (Dictionary<String, Object>)constraintMap.Value;
foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) {
var values = (List<Object>)timelineEntry.Value;
var timelineName = (String)timelineEntry.Key;
if (timelineName == "position" || timelineName == "spacing") {
PathConstraintPositionTimeline timeline;
float timelineScale = 1;
if (timelineName == "spacing") {
timeline = new PathConstraintSpacingTimeline(values.Count);
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale;
}
else {
timeline = new PathConstraintPositionTimeline(values.Count);
if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
}
timeline.pathConstraintIndex = index;
int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) {
timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale);
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
}
else if (timelineName == "mix") {
PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count);
timeline.pathConstraintIndex = index;
int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) {
timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1));
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
}
}
}
}
// Deform timelines.
if (map.ContainsKey("deform")) {
foreach (KeyValuePair<String, Object> deformMap in (Dictionary<String, Object>)map["deform"]) {
Skin skin = skeletonData.FindSkin(deformMap.Key);
foreach (KeyValuePair<String, Object> slotMap in (Dictionary<String, Object>)deformMap.Value) {
int slotIndex = skeletonData.FindSlotIndex(slotMap.Key);
if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key);
foreach (KeyValuePair<String, Object> timelineMap in (Dictionary<String, Object>)slotMap.Value) {
var values = (List<Object>)timelineMap.Value;
VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key);
if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key);
bool weighted = attachment.bones != null;
float[] vertices = attachment.vertices;
int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length;
var timeline = new DeformTimeline(values.Count);
timeline.slotIndex = slotIndex;
timeline.attachment = attachment;
int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) {
float[] deform;
if (!valueMap.ContainsKey("vertices")) {
deform = weighted ? new float[deformLength] : vertices;
} else {
deform = new float[deformLength];
int start = GetInt(valueMap, "offset", 0);
float[] verticesValue = GetFloatArray(valueMap, "vertices", 1);
Array.Copy(verticesValue, 0, deform, start, verticesValue.Length);
if (scale != 1) {
for (int i = start, n = i + verticesValue.Length; i < n; i++)
deform[i] *= scale;
}
if (!weighted) {
for (int i = 0; i < deformLength; i++)
deform[i] += vertices[i];
}
}
timeline.SetFrame(frameIndex, (float)valueMap["time"], deform);
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
}
}
}
}
// Draw order timeline.
if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) {
var values = (List<Object>)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"];
var timeline = new DrawOrderTimeline(values.Count);
int slotCount = skeletonData.slots.Count;
int frameIndex = 0;
foreach (Dictionary<String, Object> drawOrderMap in values) {
int[] drawOrder = null;
if (drawOrderMap.ContainsKey("offsets")) {
drawOrder = new int[slotCount];
for (int i = slotCount - 1; i >= 0; i--)
drawOrder[i] = -1;
var offsets = (List<Object>)drawOrderMap["offsets"];
int[] unchanged = new int[slotCount - offsets.Count];
int originalIndex = 0, unchangedIndex = 0;
foreach (Dictionary<String, Object> offsetMap in offsets) {
int slotIndex = skeletonData.FindSlotIndex((String)offsetMap["slot"]);
if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]);
// Collect unchanged items.
while (originalIndex != slotIndex)
unchanged[unchangedIndex++] = originalIndex++;
// Set changed items.
int index = originalIndex + (int)(float)offsetMap["offset"];
drawOrder[index] = originalIndex++;
}
// Collect remaining unchanged items.
while (originalIndex < slotCount)
unchanged[unchangedIndex++] = originalIndex++;
// Fill in unchanged items.
for (int i = slotCount - 1; i >= 0; i--)
if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex];
}
timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
}
// Event timeline.
if (map.ContainsKey("events")) {
var eventsMap = (List<Object>)map["events"];
var timeline = new EventTimeline(eventsMap.Count);
int frameIndex = 0;
foreach (Dictionary<String, Object> eventMap in eventsMap) {
EventData eventData = skeletonData.FindEvent((String)eventMap["name"]);
if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]);
var e = new Event((float)eventMap["time"], eventData);
e.Int = GetInt(eventMap, "int", eventData.Int);
e.Float = GetFloat(eventMap, "float", eventData.Float);
e.String = GetString(eventMap, "string", eventData.String);
timeline.SetFrame(frameIndex++, e);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
}
timelines.TrimExcess();
skeletonData.animations.Add(new Animation(name, timelines, duration));
}
static void ReadCurve (Dictionary<String, Object> valueMap, CurveTimeline timeline, int frameIndex) {
if (!valueMap.ContainsKey("curve"))
return;
Object curveObject = valueMap["curve"];
if (curveObject.Equals("stepped"))
timeline.SetStepped(frameIndex);
else {
var curve = curveObject as List<Object>;
if (curve != null)
timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]);
}
}
internal class LinkedMesh {
internal String parent, skin;
internal int slotIndex;
internal MeshAttachment mesh;
public LinkedMesh (MeshAttachment mesh, String skin, int slotIndex, String parent) {
this.mesh = mesh;
this.skin = skin;
this.slotIndex = slotIndex;
this.parent = parent;
}
}
static float[] GetFloatArray(Dictionary<String, Object> map, String name, float scale) {
var list = (List<Object>)map[name];
var values = new float[list.Count];
if (scale == 1) {
for (int i = 0, n = list.Count; i < n; i++)
values[i] = (float)list[i];
} else {
for (int i = 0, n = list.Count; i < n; i++)
values[i] = (float)list[i] * scale;
}
return values;
}
static int[] GetIntArray(Dictionary<String, Object> map, String name) {
var list = (List<Object>)map[name];
var values = new int[list.Count];
for (int i = 0, n = list.Count; i < n; i++)
values[i] = (int)(float)list[i];
return values;
}
static float GetFloat(Dictionary<String, Object> map, String name, float defaultValue) {
if (!map.ContainsKey(name))
return defaultValue;
return (float)map[name];
}
static int GetInt(Dictionary<String, Object> map, String name, int defaultValue) {
if (!map.ContainsKey(name))
return defaultValue;
return (int)(float)map[name];
}
static bool GetBoolean(Dictionary<String, Object> map, String name, bool defaultValue) {
if (!map.ContainsKey(name))
return defaultValue;
return (bool)map[name];
}
static String GetString(Dictionary<String, Object> map, String name, String defaultValue) {
if (!map.ContainsKey(name))
return defaultValue;
return (String)map[name];
}
static float ToColor(String hexString, int colorIndex) {
if (hexString.Length != 8)
throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString, "hexString");
return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255;
}
}
}

View File

@@ -0,0 +1,114 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.Collections.Generic;
namespace SpineRuntime34 {
/// <summary>Stores attachments by slot index and attachment name.</summary>
public class Skin {
internal String name;
private Dictionary<AttachmentKeyTuple, Attachment> attachments =
new Dictionary<AttachmentKeyTuple, Attachment>(AttachmentKeyTupleComparer.Instance);
public String Name { get { return name; } }
public Dictionary<AttachmentKeyTuple, Attachment> Attachments { get { return attachments; } }
public Skin (String name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.name = name;
}
public void AddAttachment (int slotIndex, String name, Attachment attachment) {
if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null.");
attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment;
}
/// <returns>May be null.</returns>
public Attachment GetAttachment (int slotIndex, String name) {
Attachment attachment;
attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment);
return attachment;
}
public void FindNamesForSlot (int slotIndex, List<String> names) {
if (names == null) throw new ArgumentNullException("names", "names cannot be null.");
foreach (AttachmentKeyTuple key in attachments.Keys)
if (key.slotIndex == slotIndex) names.Add(key.name);
}
public void FindAttachmentsForSlot (int slotIndex, List<Attachment> attachments) {
if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null.");
foreach (KeyValuePair<AttachmentKeyTuple, Attachment> entry in this.attachments)
if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value);
}
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) {
foreach (KeyValuePair<AttachmentKeyTuple, Attachment> entry in oldSkin.attachments) {
int slotIndex = entry.Key.slotIndex;
Slot slot = skeleton.slots.Items[slotIndex];
if (slot.attachment == entry.Value) {
Attachment attachment = GetAttachment(slotIndex, entry.Key.name);
if (attachment != null) slot.Attachment = attachment;
}
}
}
public struct AttachmentKeyTuple {
public readonly int slotIndex;
public readonly string name;
internal readonly int nameHashCode;
public AttachmentKeyTuple (int slotIndex, string name) {
this.slotIndex = slotIndex;
this.name = name;
nameHashCode = this.name.GetHashCode();
}
}
// Avoids boxing in the dictionary.
class AttachmentKeyTupleComparer : IEqualityComparer<AttachmentKeyTuple> {
internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer();
bool IEqualityComparer<AttachmentKeyTuple>.Equals (AttachmentKeyTuple o1, AttachmentKeyTuple o2) {
return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && o1.name == o2.name;
}
int IEqualityComparer<AttachmentKeyTuple>.GetHashCode (AttachmentKeyTuple o) {
return o.slotIndex;
}
}
}
}

View File

@@ -0,0 +1,93 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
public class Slot {
internal SlotData data;
internal Bone bone;
internal float r, g, b, a;
internal Attachment attachment;
internal float attachmentTime;
internal ExposedList<float> attachmentVertices = new ExposedList<float>();
public SlotData Data { get { return data; } }
public Bone Bone { get { return bone; } }
public Skeleton Skeleton { get { return bone.skeleton; } }
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; } }
/// <summary>May be null.</summary>
public Attachment Attachment {
get { return attachment; }
set {
if (attachment == value) return;
attachment = value;
attachmentTime = bone.skeleton.time;
attachmentVertices.Clear(false);
}
}
public float AttachmentTime {
get { return bone.skeleton.time - attachmentTime; }
set { attachmentTime = bone.skeleton.time - value; }
}
public ExposedList<float> AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } }
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;
SetToSetupPose();
}
public void SetToSetupPose () {
r = data.r;
g = data.g;
b = data.b;
a = data.a;
if (data.attachmentName == null)
Attachment = null;
else {
attachment = null;
Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName);
}
}
override public String ToString () {
return data.name;
}
}
}

View File

@@ -0,0 +1,66 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
public class SlotData {
internal int index;
internal String name;
internal BoneData boneData;
internal float r = 1, g = 1, b = 1, a = 1;
internal String attachmentName;
internal BlendMode blendMode;
public int Index { get { return index; } }
public String Name { get { return name; } }
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; } }
/// <summary>May be null.</summary>
public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } }
public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } }
public SlotData (int index, String name, BoneData boneData) {
if (index < 0) throw new ArgumentException ("index must be >= 0.", "index");
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null.");
this.index = index;
this.name = name;
this.boneData = boneData;
}
override public String ToString () {
return name;
}
}
}

View File

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

View File

@@ -0,0 +1,129 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
public class TransformConstraint : IUpdatable {
internal TransformConstraintData data;
internal ExposedList<Bone> bones;
internal Bone target;
internal float rotateMix, translateMix, scaleMix, shearMix;
public TransformConstraintData Data { get { return data; } }
public ExposedList<Bone> Bones { get { return bones; } }
public Bone Target { get { return target; } set { target = value; } }
public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } }
public float ShearMix { get { return shearMix; } set { shearMix = value; } }
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;
rotateMix = data.rotateMix;
translateMix = data.translateMix;
scaleMix = data.scaleMix;
shearMix = data.shearMix;
bones = new ExposedList<Bone>();
foreach (BoneData boneData in data.bones)
bones.Add (skeleton.FindBone (boneData.name));
target = skeleton.FindBone(data.target.name);
}
public void Apply () {
Update();
}
public void Update () {
float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
Bone target = this.target;
float ta = target.a, tb = target.b, tc = target.c, td = target.d;
ExposedList<Bone> bones = this.bones;
for (int i = 0, n = bones.Count; i < n; i++) {
Bone bone = bones.Items[i];
if (rotateMix > 0) {
float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
float r = (float)Math.Atan2(tc, ta) - (float)Math.Atan2(c, a) + data.offsetRotation * MathUtils.degRad;
if (r > MathUtils.PI)
r -= MathUtils.PI2;
else if (r < -MathUtils.PI) r += MathUtils.PI2;
r *= rotateMix;
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 (translateMix > 0) {
float tempx, tempy;
target.LocalToWorld(data.offsetX, data.offsetY, out tempx, out tempy);
bone.worldX += (tempx - bone.worldX) * translateMix;
bone.worldY += (tempy - bone.worldY) * translateMix;
}
if (scaleMix > 0) {
float bs = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c);
float ts = (float)Math.Sqrt(ta * ta + tc * tc);
float s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleX) * scaleMix) / bs : 0;
bone.a *= s;
bone.c *= s;
bs = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d);
ts = (float)Math.Sqrt(tb * tb + td * td);
s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleY) * scaleMix) / bs : 0;
bone.b *= s;
bone.d *= s;
}
if (shearMix > 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 + data.offsetShearY * MathUtils.degRad) * shearMix;
float s = (float)Math.Sqrt(b * b + d * d);
bone.b = MathUtils.Cos(r) * s;
bone.d = MathUtils.Sin(r) * s;
}
}
}
override public String ToString () {
return data.name;
}
}
}

View File

@@ -0,0 +1,65 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime34 {
public class TransformConstraintData {
internal String name;
internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
internal BoneData target;
internal float rotateMix, translateMix, scaleMix, shearMix;
internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
public String Name { get { return name; } }
public ExposedList<BoneData> Bones { get { return bones; } }
public BoneData Target { get { return target; } set { target = value; } }
public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } }
public float ShearMix { get { return shearMix; } set { shearMix = 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 TransformConstraintData (String name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.name = name;
}
override public String ToString () {
return name;
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,115 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.Collections.Generic;
namespace SpineRuntime35 {
/// <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.");
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);
Animation to = skeletonData.FindAnimation(toName);
if (to == null) throw new ArgumentException("Animation not found: " + 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;
}
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.
class AnimationPairComparer : IEqualityComparer<AnimationPair> {
internal static readonly AnimationPairComparer Instance = new AnimationPairComparer();
bool IEqualityComparer<AnimationPair>.Equals (AnimationPair x, AnimationPair y) {
return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2);
}
int IEqualityComparer<AnimationPair>.GetHashCode (AnimationPair obj) {
// from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2);
int h1 = obj.a1.GetHashCode();
return (((h1 << 5) + h1) ^ obj.a2.GetHashCode());
}
}
}
}

View File

@@ -0,0 +1,293 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
#if WINDOWS_STOREAPP
using System.Threading.Tasks;
using Windows.Storage;
#endif
namespace SpineRuntime35 {
public class Atlas {
readonly List<AtlasPage> pages = new List<AtlasPage>();
List<AtlasRegion> regions = new List<AtlasRegion>();
TextureLoader textureLoader;
#if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) // !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 {
Load(reader, Path.GetDirectoryName(path), 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 {
Load(reader, Path.GetDirectoryName(path), textureLoader);
} catch (Exception ex) {
throw new Exception("Error reading atlas file: " + path, ex);
}
}
}
#endif // WINDOWS_STOREAPP
#endif // !(UNITY)
public Atlas (TextReader reader, string dir, TextureLoader textureLoader) {
Load(reader, dir, textureLoader);
}
public Atlas (List<AtlasPage> pages, List<AtlasRegion> regions) {
this.pages = pages;
this.regions = regions;
this.textureLoader = null;
}
private void Load (TextReader reader, string imagesDir, TextureLoader textureLoader) {
if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null.");
this.textureLoader = textureLoader;
string[] tuple = new string[4];
AtlasPage page = null;
while (true) {
string line = reader.ReadLine();
if (line == null) break;
if (line.Trim().Length == 0)
page = null;
else if (page == null) {
page = new AtlasPage();
page.name = line;
if (ReadTuple(reader, tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker.
page.width = int.Parse(tuple[0]);
page.height = int.Parse(tuple[1]);
ReadTuple(reader, tuple);
}
page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false);
ReadTuple(reader, tuple);
page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false);
page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false);
string direction = ReadValue(reader);
page.uWrap = TextureWrap.ClampToEdge;
page.vWrap = TextureWrap.ClampToEdge;
if (direction == "x")
page.uWrap = TextureWrap.Repeat;
else if (direction == "y")
page.vWrap = TextureWrap.Repeat;
else if (direction == "xy")
page.uWrap = page.vWrap = TextureWrap.Repeat;
textureLoader.Load(page, Path.Combine(imagesDir, line));
pages.Add(page);
} else {
AtlasRegion region = new AtlasRegion();
region.name = line;
region.page = page;
region.rotate = Boolean.Parse(ReadValue(reader));
ReadTuple(reader, tuple);
int x = int.Parse(tuple[0]);
int y = int.Parse(tuple[1]);
ReadTuple(reader, tuple);
int width = int.Parse(tuple[0]);
int height = int.Parse(tuple[1]);
region.u = x / (float)page.width;
region.v = y / (float)page.height;
if (region.rotate) {
region.u2 = (x + height) / (float)page.width;
region.v2 = (y + width) / (float)page.height;
} else {
region.u2 = (x + width) / (float)page.width;
region.v2 = (y + height) / (float)page.height;
}
region.x = x;
region.y = y;
region.width = Math.Abs(width);
region.height = Math.Abs(height);
if (ReadTuple(reader, tuple) == 4) { // split is optional
region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]),
int.Parse(tuple[2]), int.Parse(tuple[3])};
if (ReadTuple(reader, tuple) == 4) { // pad is optional, but only present with splits
region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]),
int.Parse(tuple[2]), int.Parse(tuple[3])};
ReadTuple(reader, tuple);
}
}
region.originalWidth = int.Parse(tuple[0]);
region.originalHeight = int.Parse(tuple[1]);
ReadTuple(reader, tuple);
region.offsetX = int.Parse(tuple[0]);
region.offsetY = int.Parse(tuple[1]);
region.index = int.Parse(ReadValue(reader));
regions.Add(region);
}
}
}
static string ReadValue (TextReader reader) {
string line = reader.ReadLine();
int colon = line.IndexOf(':');
if (colon == -1) throw new Exception("Invalid line: " + line);
return line.Substring(colon + 1).Trim();
}
/// <summary>Returns the number of tuple values read (1, 2 or 4).</summary>
static int ReadTuple (TextReader reader, string[] tuple) {
string line = reader.ReadLine();
int colon = line.IndexOf(':');
if (colon == -1) throw new Exception("Invalid line: " + line);
int i = 0, lastMatch = colon + 1;
for (; i < 3; i++) {
int comma = line.IndexOf(',', lastMatch);
if (comma == -1) break;
tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim();
lastMatch = comma + 1;
}
tuple[i] = line.Substring(lastMatch).Trim();
return i + 1;
}
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 Format format;
public TextureFilter minFilter;
public TextureFilter magFilter;
public TextureWrap uWrap;
public TextureWrap vWrap;
public Object rendererObject;
public int width, height;
}
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 index;
public bool rotate;
public int[] splits;
public int[] pads;
}
public interface TextureLoader {
void Load (AtlasPage page, string path);
void Unload (Object texture);
}
}

View File

@@ -0,0 +1,109 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime35 {
/// <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 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.rotate);
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.RegionRotate = region.rotate;
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;
}
}
}

View File

@@ -0,0 +1,46 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime35 {
abstract public class Attachment {
public string Name { get; private set; }
public Attachment (String name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null");
Name = name;
}
override public string ToString () {
return Name;
}
}
}

View File

@@ -0,0 +1,49 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
namespace SpineRuntime35 {
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);
}
}

View File

@@ -0,0 +1,35 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
namespace SpineRuntime35 {
public enum AttachmentType {
Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping
}
}

View File

@@ -0,0 +1,40 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime35 {
/// <summary>Attachment that has a polygon for bounds checking.</summary>
public class BoundingBoxAttachment : VertexAttachment {
public BoundingBoxAttachment (string name)
: base(name) {
}
}
}

View File

@@ -0,0 +1,42 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime35 {
public class ClippingAttachment : VertexAttachment {
internal SlotData endSlot;
public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } }
public ClippingAttachment(string name) : base(name) {
}
}
}

View File

@@ -0,0 +1,120 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime35 {
/// <summary>Attachment that displays a texture region using a mesh.</summary>
public class MeshAttachment : VertexAttachment {
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;
internal bool inheritDeform;
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; //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 bool RegionRotate { 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 bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } }
public MeshAttachment ParentMesh {
get { return parentMesh; }
set {
parentMesh = value;
if (value != null) {
bones = value.bones;
vertices = value.vertices;
worldVerticesLength = value.worldVerticesLength;
regionUVs = value.regionUVs;
triangles = value.triangles;
HullLength = value.HullLength;
Edges = value.Edges;
Width = value.Width;
Height = value.Height;
}
}
}
// Nonessential.
public int[] Edges { get; set; }
public float Width { get; set; }
public float Height { get; set; }
public MeshAttachment (string name)
: base(name) {
}
public void UpdateUVs () {
float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV;
float[] regionUVs = this.regionUVs;
if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length];
float[] uvs = this.uvs;
if (RegionRotate) {
for (int i = 0, n = uvs.Length; i < n; i += 2) {
uvs[i] = u + regionUVs[i + 1] * width;
uvs[i + 1] = v + height - regionUVs[i] * height;
}
} else {
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;
}
}
}
override public bool ApplyDeform (VertexAttachment sourceAttachment) {
return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment);
}
}
}

View File

@@ -0,0 +1,48 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.Collections.Generic;
namespace SpineRuntime35 {
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; } }
public bool Closed { get { return closed; } set { closed = value; } }
public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } }
public PathAttachment (String name)
: base(name) {
}
}
}

View File

@@ -0,0 +1,61 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
namespace SpineRuntime35 {
/// <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;
}
}
}

View File

@@ -0,0 +1,175 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime35 {
/// <summary>Attachment that displays a texture region.</summary>
public class RegionAttachment : Attachment {
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; //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 width = this.width;
float height = this.height;
float scaleX = this.scaleX;
float scaleY = this.scaleY;
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 rotation = this.rotation;
float cos = MathUtils.CosDeg(rotation);
float sin = MathUtils.SinDeg(rotation);
float x = this.x;
float 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, bool rotate) {
float[] uvs = this.uvs;
// UV values differ from RegionAttachment.java
if (rotate) {
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;
}
}
}

View File

@@ -0,0 +1,130 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace SpineRuntime35 {
/// <summary>>An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices.</summary>
public 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;
/// <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; } }
public VertexAttachment (string name)
: base(name) {
lock (VertexAttachment.nextIdLock) {
id = (VertexAttachment.nextID++ & 65535) << 11;
}
}
public void ComputeWorldVertices (Slot slot, float[] worldVertices) {
ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0);
}
/// <summary>Transforms local vertices to world coordinates.</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;
Skeleton skeleton = slot.bone.skeleton;
var deformArray = slot.attachmentVertices;
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;
}
var skeletonBones = 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>Returns true if a deform originally applied to the specified attachment should be applied to this attachment.</summary>
virtual public bool ApplyDeform (VertexAttachment sourceAttachment) {
return this == sourceAttachment;
}
}
}

Some files were not shown because too many files have changed in this diff Show More