Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3cd0b9349 | ||
|
|
1c545b8c37 | ||
|
|
d660dd1c4a | ||
|
|
9c0acf7302 | ||
|
|
415df555c7 | ||
|
|
5ef13239da | ||
|
|
13ef873650 | ||
|
|
78b9834f6c | ||
|
|
672a695b20 | ||
|
|
e9951ed79a | ||
|
|
0c16b2f104 | ||
|
|
7628075420 | ||
|
|
6f896bdaad | ||
|
|
98930db4b6 | ||
|
|
c7493372e9 | ||
|
|
707aa7f570 | ||
|
|
99ff6f9f0a | ||
|
|
be8193e235 | ||
|
|
21b6dbee4c | ||
|
|
f60418fecb | ||
|
|
1180c735c8 | ||
|
|
3d8f6547e0 | ||
|
|
99ec2704fe | ||
|
|
dbd2cef766 | ||
|
|
212ecc2ff3 | ||
|
|
7806f9298d | ||
|
|
3bc57a8990 | ||
|
|
67c9ea9291 | ||
|
|
f404acc834 | ||
|
|
8e1f586d4f | ||
|
|
5dd1b84943 | ||
|
|
ad190d8952 | ||
|
|
ddb11808a7 | ||
|
|
9b1e26b2ac | ||
|
|
c8e35a9aed | ||
|
|
4786b0434c | ||
|
|
40bde84648 | ||
|
|
ebb2593526 | ||
|
|
6a74204ba1 | ||
|
|
78c6c47300 | ||
|
|
746a3decc8 | ||
|
|
6dfd25b760 | ||
|
|
a697ccc923 | ||
|
|
5bfa625868 | ||
|
|
0762a90fa9 | ||
|
|
a4926d905b | ||
|
|
b08b6752cd | ||
|
|
50b1c66e27 | ||
|
|
65508782c6 | ||
|
|
d02ab536b6 | ||
|
|
db3700bda3 | ||
|
|
6dea656e5e | ||
|
|
7bc82ab318 | ||
|
|
3eb9b1d008 | ||
|
|
eca59dc67b | ||
|
|
93b806dccd | ||
|
|
89c31d7c77 | ||
|
|
0a5432bb30 | ||
|
|
2eded25c03 | ||
|
|
fa00f0064e | ||
|
|
ddd3e94698 | ||
|
|
d7ee88f7f6 | ||
|
|
1d7a402749 | ||
|
|
86bcb079b0 | ||
|
|
390416df06 | ||
|
|
1344b34d08 | ||
|
|
497103bdb6 | ||
|
|
04953d13b6 | ||
|
|
b272d9802e | ||
|
|
bd5a537058 | ||
|
|
64a3caf938 | ||
|
|
ca34494483 | ||
|
|
e717eab6df | ||
|
|
068734549c | ||
|
|
3d1fa38eb3 | ||
|
|
bff3b39371 | ||
|
|
a44161053b | ||
|
|
4b64ec74c2 | ||
|
|
1f56e2f03c | ||
|
|
311b09cc63 | ||
|
|
cd7f841e38 |
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: 问题报告/Bug report
|
||||
about: 报告可能的程序错误/Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 问题描述/Describe the bug
|
||||
清晰完整的描述问题是什么以及如何发生的。/A clear and concise description of what the bug is.
|
||||
|
||||
## 复现方式(可选)/To Reproduce (Optional)
|
||||
|
||||
## 截图(可选)/Screenshots (Optional)**
|
||||
如果有必要,提供报错时的有关截图。/If applicable, add screenshots to help explain your problem.
|
||||
|
||||
## 附件(可选)/Attachments (Optional)
|
||||
请将会**出现问题的文件**以及**日志文件**打包成一个 ZIP 后作为附件贴在 issue 内。/Please compress the problematic files and the log files into a single ZIP archive and attach it to this issue.
|
||||
3
.github/workflows/dotnet-desktop.yml
vendored
3
.github/workflows/dotnet-desktop.yml
vendored
@@ -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
|
||||
|
||||
58
CHANGELOG.md
58
CHANGELOG.md
@@ -1,5 +1,63 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v0.15.15
|
||||
|
||||
- 增加报错信息
|
||||
- 导入后自动选中最后一项
|
||||
|
||||
## v0.15.14
|
||||
|
||||
- 将预览画面的首选项移动至上一次状态参数中
|
||||
- 增加预览画面像素的自动保存和恢复
|
||||
- 增加日志启动时的版本号输出
|
||||
|
||||
## v0.15.13
|
||||
|
||||
- 增加程序布局自动存储和还原
|
||||
- 增加部分预览画面首选项
|
||||
|
||||
## v0.15.12
|
||||
|
||||
- 增加单个模型和单个轨道的时间因子
|
||||
- 增加单个轨道的 Alpha 混合参数
|
||||
- 调整轨道清除命令至右键菜单
|
||||
- 设置默认标签页为模型
|
||||
- 完善导入时的报错信息
|
||||
|
||||
## v0.15.11
|
||||
|
||||
- 修复自定义导出中参数构造错误
|
||||
- 增加 mov 格式及参数说明
|
||||
|
||||
## v0.15.10
|
||||
|
||||
- 增加插槽可见性参数, 允许任何情况下对插槽启用和禁用对插槽的渲染
|
||||
|
||||
## v0.15.9
|
||||
|
||||
- 添加 V34 和 V35 版本支持
|
||||
|
||||
## v0.15.8
|
||||
|
||||
- 修复渲染纹理过程中可能的 null 错误
|
||||
|
||||
## v0.15.7
|
||||
|
||||
- 合并社区 CLI 功能项目
|
||||
|
||||
## v0.15.6
|
||||
|
||||
- 修复导出单个的时长错误
|
||||
- 修改默认导出背景色为不透明黑色
|
||||
|
||||
## v0.15.5
|
||||
|
||||
- 修复自定义导出时的画面错误
|
||||
- 设置 mp4 像素格式为 yuv420p 避免 windows 默认播放器无法打开
|
||||
- 增加预览画面和导出时的速度参数设置
|
||||
- 修复一些提示文本错误
|
||||
- 导出时自动将分辨率向下调整为 2 的倍数, 避免 yuv420p 格式出错
|
||||
|
||||
## v0.15.4
|
||||
|
||||
- 修复导出时可能的卡死问题
|
||||
|
||||
41
CONTRIBUTING.md
Normal file
41
CONTRIBUTING.md
Normal 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.
|
||||
@@ -19,7 +19,10 @@ 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.
|
||||
* View/model/track time scale adjustment.
|
||||
* Track alpha blending parameter settings.
|
||||
* Fullscreen preview mode.
|
||||
* Export to single frame/image sequence/animated GIF/video formats.
|
||||
* Automatic resolution batch export.
|
||||
@@ -32,6 +35,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: |
|
||||
|
||||
@@ -19,19 +19,24 @@
|
||||
- 支持列表多选批量设置骨骼参数
|
||||
- 支持多轨道动画设置
|
||||
- 支持皮肤/自定义插槽附件设置
|
||||
- 支持自定义插槽可见性
|
||||
- 支持调试渲染
|
||||
- 支持画面/模型/轨道时间倍速设置
|
||||
- 支持设置轨道 Alpha 混合参数
|
||||
- 支持全屏预览
|
||||
- 支持单帧/动图/视频文件导出
|
||||
- 支持自动分辨率批量导出
|
||||
- 支持 FFmpeg 自定义导出
|
||||
- 支持程序参数保存
|
||||
- ...
|
||||
- ......
|
||||
|
||||
### Spine 版本支持
|
||||
|
||||
| 版本 | 查看&导出 |
|
||||
| :---: | :---: |
|
||||
| `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: |
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace Spine.Exporters
|
||||
/// <param name="height">画布高像素值</param>
|
||||
public BaseExporter(uint width , uint height)
|
||||
{
|
||||
// XXX: 强制变成 2 的倍数, 防止像是 yuv420p 这种像素格式报错
|
||||
width = width >> 1 << 1;
|
||||
height = height >> 1 << 1;
|
||||
if (width <= 0 || height <= 0)
|
||||
throw new ArgumentException($"Invalid resolution: {width}, {height}");
|
||||
_renderTexture = new(width, height);
|
||||
@@ -42,6 +45,9 @@ namespace Spine.Exporters
|
||||
/// </summary>
|
||||
public BaseExporter(Vector2u resolution)
|
||||
{
|
||||
// XXX: 强制变成 2 的倍数, 防止像是 yuv420p 这种像素格式报错
|
||||
resolution.X = resolution.X >> 1 << 1;
|
||||
resolution.Y = resolution.Y >> 1 << 1;
|
||||
if (resolution.X <= 0 || resolution.Y <= 0)
|
||||
throw new ArgumentException($"Invalid resolution: {resolution}");
|
||||
_renderTexture = new(resolution.X, resolution.Y);
|
||||
@@ -76,12 +82,12 @@ namespace Spine.Exporters
|
||||
_backgroundColorPma = bcPma;
|
||||
}
|
||||
}
|
||||
protected Color _backgroundColor = Color.Transparent;
|
||||
protected Color _backgroundColor = Color.Black;
|
||||
|
||||
/// <summary>
|
||||
/// 预乘后的背景颜色
|
||||
/// </summary>
|
||||
protected Color _backgroundColorPma = Color.Transparent;
|
||||
protected Color _backgroundColorPma = Color.Black;
|
||||
|
||||
/// <summary>
|
||||
/// 画面分辨率
|
||||
@@ -92,6 +98,9 @@ namespace Spine.Exporters
|
||||
get => _renderTexture.Size;
|
||||
set
|
||||
{
|
||||
// XXX: 强制变成 2 的倍数, 防止像是 yuv420p 这种像素格式报错
|
||||
value.X = value.X >> 1 << 1;
|
||||
value.Y = value.Y >> 1 << 1;
|
||||
if (value.X <= 0 || value.Y <= 0)
|
||||
{
|
||||
_logger.Warn("Omit invalid exporter resolution: {0}", value);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using FFMpegCore;
|
||||
using FFMpegCore.Pipes;
|
||||
using SFML.Graphics;
|
||||
using SFML.System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -59,8 +60,25 @@ namespace Spine.Exporters
|
||||
if (!string.IsNullOrEmpty(_codec)) options.WithVideoCodec(_codec);
|
||||
if (!string.IsNullOrEmpty(_pixelFormat)) options.ForcePixelFormat(_pixelFormat);
|
||||
if (!string.IsNullOrEmpty(_bitrate)) options.WithCustomArgument($"-b:v {_bitrate}");
|
||||
if (!string.IsNullOrEmpty(_filter)) options.WithCustomArgument($"-vf unpremultiply=inplace=1, {_customArgs}");
|
||||
if (!string.IsNullOrEmpty(_filter)) options.WithCustomArgument($"-vf \"unpremultiply=inplace=1, {_filter}\"");
|
||||
else options.WithCustomArgument("-vf unpremultiply=inplace=1");
|
||||
if (!string.IsNullOrEmpty(_customArgs)) options.WithCustomArgument($"{_customArgs}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取的一帧, 结果是预乘的
|
||||
/// </summary>
|
||||
protected override SFMLImageVideoFrame GetFrame(SpineObject[] spines)
|
||||
{
|
||||
// BUG: 也许和 SFML 多线程或者 FFmpeg 调用有关, 当渲染线程也在运行的时候此处并行渲染会导致和 SFML 有关的内容都卡死
|
||||
// 不知道为什么用 FFmpeg 必须临时创建 RenderTexture, 否则无法正常渲染, 会导致画面帧丢失
|
||||
using var tex = new RenderTexture(_renderTexture.Size.X, _renderTexture.Size.Y);
|
||||
using var view = _renderTexture.GetView();
|
||||
tex.SetView(view);
|
||||
tex.Clear(_backgroundColorPma);
|
||||
foreach (var sp in spines.Reverse()) tex.Draw(sp);
|
||||
tex.Display();
|
||||
return new(tex.Texture.CopyToImage());
|
||||
}
|
||||
|
||||
public override void Export(string output, CancellationToken ct, params SpineObject[] spines)
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace Spine.Exporters
|
||||
Mp4,
|
||||
Webm,
|
||||
Mkv,
|
||||
Mov,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -40,29 +41,35 @@ namespace Spine.Exporters
|
||||
private VideoFormat _format = VideoFormat.Mp4;
|
||||
|
||||
/// <summary>
|
||||
/// 动图是否循环
|
||||
/// 动图是否循环 [Gif/Webp]
|
||||
/// </summary>
|
||||
public bool Loop { get => _loop; set => _loop = value; }
|
||||
private bool _loop = true;
|
||||
|
||||
/// <summary>
|
||||
/// 质量
|
||||
/// 质量 [Webp]
|
||||
/// </summary>
|
||||
public int Quality { get => _quality; set => _quality = Math.Clamp(value, 0, 100); }
|
||||
private int _quality = 75;
|
||||
|
||||
/// <summary>
|
||||
/// 无损压缩 (Webp)
|
||||
/// 无损压缩 [Webp]
|
||||
/// </summary>
|
||||
public bool Lossless { get => _lossless; set => _lossless = value; }
|
||||
private bool _lossless = false;
|
||||
|
||||
/// <summary>
|
||||
/// CRF
|
||||
/// CRF [Mp4/Webm/Mkv]
|
||||
/// </summary>
|
||||
public int Crf { get => _crf; set => _crf = Math.Clamp(value, 0, 63); }
|
||||
private int _crf = 23;
|
||||
|
||||
/// <summary>
|
||||
/// prores_ks 编码器的配置等级, -1 是自动, 越高质量越好, 只有 4 及以上才有透明通道 [Mov]
|
||||
/// </summary>
|
||||
public int Profile { get => _profile; set => _profile = Math.Clamp(value, -1, 5); }
|
||||
private int _profile = 5;
|
||||
|
||||
/// <summary>
|
||||
/// 获取的一帧, 结果是预乘的
|
||||
/// </summary>
|
||||
@@ -89,6 +96,7 @@ namespace Spine.Exporters
|
||||
VideoFormat.Mp4 => SetMp4Options,
|
||||
VideoFormat.Webm => SetWebmOptions,
|
||||
VideoFormat.Mkv => SetMkvOptions,
|
||||
VideoFormat.Mov => SetMovOptions,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
@@ -110,8 +118,8 @@ namespace Spine.Exporters
|
||||
{
|
||||
// Gif 固定使用 256 调色板和 128 透明度阈值
|
||||
var v = "split [s0][s1]";
|
||||
var s0 = "[s0] palettegen=reserve_transparent=1:max_colors=256 [p]";
|
||||
var s1 = "[s1][p] paletteuse=dither=bayer:alpha_threshold=128";
|
||||
var s0 = "[s0] palettegen=max_colors=256 [p]";
|
||||
var s1 = "[s1][p] paletteuse=alpha_threshold=128";
|
||||
var customArgs = $"-vf \"unpremultiply=inplace=1, {v};{s0};{s1}\" -loop {(_loop ? 0 : -1)}";
|
||||
options.ForceFormat("gif")
|
||||
.WithCustomArgument(customArgs);
|
||||
@@ -126,8 +134,10 @@ namespace Spine.Exporters
|
||||
|
||||
private void SetMp4Options(FFMpegArgumentOptions options)
|
||||
{
|
||||
// XXX: windows 默认播放器在播放 MP4 格式时对于 libx264 编码器只支持 yuv420p 的像素格式
|
||||
// 但是如果是 libx265 则没有该限制
|
||||
var customArgs = "-vf unpremultiply=inplace=1";
|
||||
options.ForceFormat("mp4").WithVideoCodec("libx264").ForcePixelFormat("yuv444p")
|
||||
options.ForceFormat("mp4").WithVideoCodec("libx264").ForcePixelFormat("yuv420p")
|
||||
.WithFastStart()
|
||||
.WithConstantRateFactor(_crf)
|
||||
.WithCustomArgument(customArgs);
|
||||
@@ -149,5 +159,13 @@ namespace Spine.Exporters
|
||||
.WithCustomArgument(customArgs);
|
||||
}
|
||||
|
||||
private void SetMovOptions(FFMpegArgumentOptions options)
|
||||
{
|
||||
var customArgs = "-vf unpremultiply=inplace=1";
|
||||
options.ForceFormat("mov").WithVideoCodec("prores_ks").ForcePixelFormat("yuva444p10le")
|
||||
.WithFastStart()
|
||||
.WithCustomArgument($"-profile {_profile}")
|
||||
.WithCustomArgument(customArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,21 @@ namespace Spine.Exporters
|
||||
}
|
||||
protected float _fps = 24;
|
||||
|
||||
public float Speed
|
||||
{
|
||||
get => _speed;
|
||||
set
|
||||
{
|
||||
if (_speed <= 0)
|
||||
{
|
||||
_logger.Warn("Omit invalid speed: {0}", value);
|
||||
return;
|
||||
}
|
||||
_speed = value;
|
||||
}
|
||||
}
|
||||
protected float _speed = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// 是否保留最后一帧
|
||||
/// </summary>
|
||||
@@ -92,7 +107,7 @@ namespace Spine.Exporters
|
||||
// 导出完整帧
|
||||
for (int i = 0; i < total; i++)
|
||||
{
|
||||
foreach (var spine in spines) spine.Update(delta);
|
||||
foreach (var spine in spines) spine.Update(delta * _speed);
|
||||
yield return GetFrame(spines);
|
||||
}
|
||||
|
||||
@@ -100,7 +115,7 @@ namespace Spine.Exporters
|
||||
if (hasFinal)
|
||||
{
|
||||
// XXX: 此处还是按照完整的一帧时长进行更新, 也许可以只更新准确的最后一帧时长
|
||||
foreach (var spine in spines) spine.Update(delta);
|
||||
foreach (var spine in spines) spine.Update(delta * _speed);
|
||||
yield return GetFrame(spines);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ namespace Spine.Implementations.SpineWrappers.V21
|
||||
}
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public override string ToString() => _o.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V21
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V21
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V21
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V21
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
23
Spine/Implementations/SpineWrappers/V34/Animation34.cs
Normal file
23
Spine/Implementations/SpineWrappers/V34/Animation34.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
180
Spine/Implementations/SpineWrappers/V34/AnimationState34.cs
Normal file
180
Spine/Implementations/SpineWrappers/V34/AnimationState34.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
33
Spine/Implementations/SpineWrappers/V34/Bone34.cs
Normal file
33
Spine/Implementations/SpineWrappers/V34/Bone34.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
106
Spine/Implementations/SpineWrappers/V34/Skeleton34.cs
Normal file
106
Spine/Implementations/SpineWrappers/V34/Skeleton34.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
45
Spine/Implementations/SpineWrappers/V34/Skin34.cs
Normal file
45
Spine/Implementations/SpineWrappers/V34/Skin34.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
80
Spine/Implementations/SpineWrappers/V34/Slot34.cs
Normal file
80
Spine/Implementations/SpineWrappers/V34/Slot34.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
146
Spine/Implementations/SpineWrappers/V34/SpineObjectData34.cs
Normal file
146
Spine/Implementations/SpineWrappers/V34/SpineObjectData34.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
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)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (Utf8Validator.IsUtf8(skelPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
_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);
|
||||
}
|
||||
}
|
||||
135
Spine/Implementations/SpineWrappers/V34/TrackEntry34.cs
Normal file
135
Spine/Implementations/SpineWrappers/V34/TrackEntry34.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
23
Spine/Implementations/SpineWrappers/V35/Animation35.cs
Normal file
23
Spine/Implementations/SpineWrappers/V35/Animation35.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
229
Spine/Implementations/SpineWrappers/V35/AnimationState35.cs
Normal file
229
Spine/Implementations/SpineWrappers/V35/AnimationState35.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
33
Spine/Implementations/SpineWrappers/V35/Bone35.cs
Normal file
33
Spine/Implementations/SpineWrappers/V35/Bone35.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
106
Spine/Implementations/SpineWrappers/V35/Skeleton35.cs
Normal file
106
Spine/Implementations/SpineWrappers/V35/Skeleton35.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
45
Spine/Implementations/SpineWrappers/V35/Skin35.cs
Normal file
45
Spine/Implementations/SpineWrappers/V35/Skin35.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
80
Spine/Implementations/SpineWrappers/V35/Slot35.cs
Normal file
80
Spine/Implementations/SpineWrappers/V35/Slot35.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
148
Spine/Implementations/SpineWrappers/V35/SpineObjectData35.cs
Normal file
148
Spine/Implementations/SpineWrappers/V35/SpineObjectData35.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
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)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (Utf8Validator.IsUtf8(skelPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
_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);
|
||||
}
|
||||
}
|
||||
185
Spine/Implementations/SpineWrappers/V35/TrackEntry35.cs
Normal file
185
Spine/Implementations/SpineWrappers/V35/TrackEntry35.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V36
|
||||
}
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public override string ToString() => _o.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V36
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V36
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V36
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V36
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V37
|
||||
}
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public override string ToString() => _o.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V37
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V37
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V37
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V37
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -74,6 +74,8 @@ namespace Spine.Implementations.SpineWrappers.V38
|
||||
}
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public override string ToString() => _o.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,15 @@ namespace Spine.Implementations.SpineWrappers.V38
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -42,8 +49,9 @@ namespace Spine.Implementations.SpineWrappers.V38
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -53,8 +61,9 @@ namespace Spine.Implementations.SpineWrappers.V38
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +71,8 @@ namespace Spine.Implementations.SpineWrappers.V38
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V40
|
||||
}
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public override string ToString() => _o.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,16 @@ namespace Spine.Implementations.SpineWrappers.V40
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
// 加载 skel
|
||||
try
|
||||
{
|
||||
if (Utf8Validator.IsUtf8(skelPath))
|
||||
@@ -42,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V40
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -53,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V40
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V40
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V41
|
||||
}
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public override string ToString() => _o.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,16 @@ namespace Spine.Implementations.SpineWrappers.V41
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
// 加载 skel
|
||||
try
|
||||
{
|
||||
if (Utf8Validator.IsUtf8(skelPath))
|
||||
@@ -42,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V41
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -53,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V41
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V41
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V42
|
||||
}
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public override string ToString() => _o.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,16 @@ namespace Spine.Implementations.SpineWrappers.V42
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
// 加载 skel
|
||||
try
|
||||
{
|
||||
if (Utf8Validator.IsUtf8(skelPath))
|
||||
@@ -42,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V42
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -53,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V42
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V42
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.15.4</Version>
|
||||
<Version>0.15.15</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" />
|
||||
|
||||
@@ -66,10 +66,10 @@ namespace Spine
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
throw new KeyNotFoundException($"Unrecognized skel suffix '{skelPath}'");
|
||||
throw new KeyNotFoundException($"Unrecognized skel file suffix");
|
||||
}
|
||||
}
|
||||
else if (!File.Exists(atlasPath)) throw new FileNotFoundException($"{nameof(atlasPath)} not found", skelPath);
|
||||
else if (!File.Exists(atlasPath)) throw new FileNotFoundException($"{nameof(atlasPath)} not found", atlasPath);
|
||||
AtlasPath = Path.GetFullPath(atlasPath);
|
||||
|
||||
// 自动检测版本, 可能会抛出异常
|
||||
@@ -105,14 +105,22 @@ namespace Spine
|
||||
|
||||
// 依然加载不成功就只能报错
|
||||
if (_data is null || Version is null)
|
||||
throw new InvalidDataException($"Failed to load spine by existed versions: '{skelPath}', '{atlasPath}'");
|
||||
throw new InvalidDataException($"Failed to load spine by existed versions");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 根据版本实例化对象
|
||||
Version = version;
|
||||
try
|
||||
{
|
||||
_data = SpineObjectData.New(Version, skelPath, atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load spine with version '{version}'");
|
||||
}
|
||||
}
|
||||
|
||||
// 创建状态实例
|
||||
_skeleton = _data.CreateSkeleton();
|
||||
@@ -167,14 +175,18 @@ namespace Spine
|
||||
// 拷贝渲染设置
|
||||
UsePma = other.UsePma;
|
||||
Physics = other.Physics;
|
||||
_animationState.TimeScale = other._animationState.TimeScale;
|
||||
|
||||
// 拷贝皮肤加载情况
|
||||
_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 +311,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 +346,7 @@ namespace Spine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置某个插槽的附件, 如果不存在则忽略, 可以使用 null 来清除附件
|
||||
/// 设置某个插槽的附件, 如果不存在则忽略, 可以使用 null 来尝试清除附件
|
||||
/// </summary>
|
||||
/// <returns>是否操作成功</returns>
|
||||
public bool SetAttachment(string slotName, string? attachmentName)
|
||||
@@ -471,14 +507,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 +526,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 +540,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 +554,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 +566,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 +638,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 +670,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 +734,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 +803,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)
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -53,5 +53,10 @@ namespace Spine.SpineWrappers
|
||||
/// 使用的附件, 可以设置为 null 清空附件
|
||||
/// </summary>
|
||||
public IAttachment? Attachment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已禁用渲染该插槽
|
||||
/// </summary>
|
||||
public bool Disabled { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Spine.SpineWrappers.Attachments;
|
||||
|
||||
namespace Spine.SpineWrappers
|
||||
@@ -17,6 +18,8 @@ namespace Spine.SpineWrappers
|
||||
ISpineObjectData,
|
||||
IDisposable
|
||||
{
|
||||
protected static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
/// <summary>
|
||||
/// 构建版本对象
|
||||
/// </summary>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
874
SpineRuntimes/SpineRuntime34/Animation.cs
Normal file
874
SpineRuntimes/SpineRuntime34/Animation.cs
Normal 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 > lastTime and <= 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
446
SpineRuntimes/SpineRuntime34/AnimationState.cs
Normal file
446
SpineRuntimes/SpineRuntime34/AnimationState.cs
Normal 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 <= 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 <= 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
96
SpineRuntimes/SpineRuntime34/AnimationStateData.cs
Normal file
96
SpineRuntimes/SpineRuntime34/AnimationStateData.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
293
SpineRuntimes/SpineRuntime34/Atlas.cs
Normal file
293
SpineRuntimes/SpineRuntime34/Atlas.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
46
SpineRuntimes/SpineRuntime34/Attachments/Attachment.cs
Normal file
46
SpineRuntimes/SpineRuntime34/Attachments/Attachment.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
47
SpineRuntimes/SpineRuntime34/Attachments/AttachmentLoader.cs
Normal file
47
SpineRuntimes/SpineRuntime34/Attachments/AttachmentLoader.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
35
SpineRuntimes/SpineRuntime34/Attachments/AttachmentType.cs
Normal file
35
SpineRuntimes/SpineRuntime34/Attachments/AttachmentType.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
119
SpineRuntimes/SpineRuntime34/Attachments/MeshAttachment.cs
Normal file
119
SpineRuntimes/SpineRuntime34/Attachments/MeshAttachment.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
SpineRuntimes/SpineRuntime34/Attachments/PathAttachment.cs
Normal file
48
SpineRuntimes/SpineRuntime34/Attachments/PathAttachment.cs
Normal 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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
152
SpineRuntimes/SpineRuntime34/Attachments/RegionAttachment.cs
Normal file
152
SpineRuntimes/SpineRuntime34/Attachments/RegionAttachment.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
117
SpineRuntimes/SpineRuntime34/Attachments/VertexAttachment.cs
Normal file
117
SpineRuntimes/SpineRuntime34/Attachments/VertexAttachment.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
SpineRuntimes/SpineRuntime34/BlendMode.cs
Normal file
35
SpineRuntimes/SpineRuntime34/BlendMode.cs
Normal 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
|
||||
}
|
||||
}
|
||||
301
SpineRuntimes/SpineRuntime34/Bone.cs
Normal file
301
SpineRuntimes/SpineRuntime34/Bone.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
70
SpineRuntimes/SpineRuntime34/BoneData.cs
Normal file
70
SpineRuntimes/SpineRuntime34/BoneData.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
SpineRuntimes/SpineRuntime34/Event.cs
Normal file
51
SpineRuntimes/SpineRuntime34/Event.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
SpineRuntimes/SpineRuntime34/EventData.cs
Normal file
51
SpineRuntimes/SpineRuntime34/EventData.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
589
SpineRuntimes/SpineRuntime34/ExposedList.cs
Normal file
589
SpineRuntimes/SpineRuntime34/ExposedList.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
SpineRuntimes/SpineRuntime34/IUpdatable.cs
Normal file
37
SpineRuntimes/SpineRuntime34/IUpdatable.cs
Normal 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 ();
|
||||
}
|
||||
}
|
||||
234
SpineRuntimes/SpineRuntime34/IkConstraint.cs
Normal file
234
SpineRuntimes/SpineRuntime34/IkConstraint.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
SpineRuntimes/SpineRuntime34/IkConstraintData.cs
Normal file
57
SpineRuntimes/SpineRuntime34/IkConstraintData.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
535
SpineRuntimes/SpineRuntime34/Json.cs
Normal file
535
SpineRuntimes/SpineRuntime34/Json.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
100
SpineRuntimes/SpineRuntime34/MathUtils.cs
Normal file
100
SpineRuntimes/SpineRuntime34/MathUtils.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
399
SpineRuntimes/SpineRuntime34/PathConstraint.cs
Normal file
399
SpineRuntimes/SpineRuntime34/PathConstraint.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
73
SpineRuntimes/SpineRuntime34/PathConstraintData.cs
Normal file
73
SpineRuntimes/SpineRuntime34/PathConstraintData.cs
Normal 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
|
||||
}
|
||||
}
|
||||
508
SpineRuntimes/SpineRuntime34/Skeleton.cs
Normal file
508
SpineRuntimes/SpineRuntime34/Skeleton.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
796
SpineRuntimes/SpineRuntime34/SkeletonBinary.cs
Normal file
796
SpineRuntimes/SpineRuntime34/SkeletonBinary.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
214
SpineRuntimes/SpineRuntime34/SkeletonBounds.cs
Normal file
214
SpineRuntimes/SpineRuntime34/SkeletonBounds.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
196
SpineRuntimes/SpineRuntime34/SkeletonData.cs
Normal file
196
SpineRuntimes/SpineRuntime34/SkeletonData.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
805
SpineRuntimes/SpineRuntime34/SkeletonJson.cs
Normal file
805
SpineRuntimes/SpineRuntime34/SkeletonJson.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
114
SpineRuntimes/SpineRuntime34/Skin.cs
Normal file
114
SpineRuntimes/SpineRuntime34/Skin.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
93
SpineRuntimes/SpineRuntime34/Slot.cs
Normal file
93
SpineRuntimes/SpineRuntime34/Slot.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
66
SpineRuntimes/SpineRuntime34/SlotData.cs
Normal file
66
SpineRuntimes/SpineRuntime34/SlotData.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
SpineRuntimes/SpineRuntime34/SpineRuntime34.csproj
Normal file
13
SpineRuntimes/SpineRuntime34/SpineRuntime34.csproj
Normal 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>
|
||||
129
SpineRuntimes/SpineRuntime34/TransformConstraint.cs
Normal file
129
SpineRuntimes/SpineRuntime34/TransformConstraint.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
65
SpineRuntimes/SpineRuntime34/TransformConstraintData.cs
Normal file
65
SpineRuntimes/SpineRuntime34/TransformConstraintData.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
1374
SpineRuntimes/SpineRuntime35/Animation.cs
Normal file
1374
SpineRuntimes/SpineRuntime35/Animation.cs
Normal file
File diff suppressed because it is too large
Load Diff
1056
SpineRuntimes/SpineRuntime35/AnimationState.cs
Normal file
1056
SpineRuntimes/SpineRuntime35/AnimationState.cs
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user