Compare commits

..

19 Commits

Author SHA1 Message Date
ww-rm
668b264836 修改生成规则为非单文件 2025-05-27 19:37:18 +08:00
ww-rm
6fbf902756 增加ja语言 2025-05-27 19:21:14 +08:00
ww-rm
f7940d1223 完善文档 2025-05-27 19:10:40 +08:00
ww-rm
10008166ac 补充文档 2025-05-27 17:23:32 +08:00
ww-rm
44b5bf8613 update changelog 2025-05-27 16:47:33 +08:00
ww-rm
39dae5cdb6 增加颜色提示文本 2025-05-27 16:40:09 +08:00
ww-rm
d7f7c7116c 增加按钮提示文本 2025-05-27 16:00:08 +08:00
ww-rm
17257a0ffe 更换为wpf 2025-05-27 15:55:10 +08:00
ww-rm
d0f629d9ba 补充v2.1的一些实现,与v3.x版本一致 2025-05-27 15:54:57 +08:00
ww-rm
cd652a72a1 更新至v0.12.13 2025-05-19 10:31:34 +08:00
ww-rm
828ff30dbf update changelog 2025-05-19 10:31:15 +08:00
ww-rm
f452fe8a71 生成文件增加额外的随机后缀 2025-05-19 10:30:29 +08:00
ww-rm
15e29a3b8a 修复readattachmentline里的顺序错误 2025-05-17 10:47:49 +08:00
ww-rm
5c6e98f5e1 更新至v0.12.12 2025-05-13 14:21:13 +08:00
ww-rm
ef06073119 update changelog 2025-05-13 14:19:52 +08:00
ww-rm
bca8b0ad85 补充SkinnedMeshAttachment附件的渲染 2025-05-13 14:19:15 +08:00
ww-rm
4983b1fa88 更新至v0.12.11 2025-05-08 18:35:23 +08:00
ww-rm
f6b6d9f0e7 update changelog 2025-05-08 18:35:12 +08:00
ww-rm
12a168df92 修复atlas null 引用导致的闪退 2025-05-08 18:34:43 +08:00
324 changed files with 19786 additions and 76498 deletions

View File

@@ -22,11 +22,11 @@ jobs:
- name: Publish FrameworkDependent version
run: |
dotnet publish ${{ env.PROJECT_NAME }}/${{ env.PROJECT_NAME }}.csproj -c Release -r win-x64 --sc false -p:PublishSingleFile=true -o publish/${{ env.PROJECT_NAME }}-${{ env.VERSION }}
dotnet publish ${{ env.PROJECT_NAME }}/${{ env.PROJECT_NAME }}.csproj -c Release -r win-x64 --sc false -o publish/${{ env.PROJECT_NAME }}-${{ env.VERSION }}
- name: Publish SelfContained version
run: |
dotnet publish ${{ env.PROJECT_NAME }}/${{ env.PROJECT_NAME }}.csproj -c Release -r win-x64 --sc true -p:PublishSingleFile=true -o publish/${{ env.PROJECT_NAME }}-${{ env.VERSION }}-SelfContained
dotnet publish ${{ env.PROJECT_NAME }}/${{ env.PROJECT_NAME }}.csproj -c Release -r win-x64 --sc true -o publish/${{ env.PROJECT_NAME }}-${{ env.VERSION }}-SelfContained
- name: Create release directory
run: mkdir release

View File

@@ -1,5 +1,56 @@
# CHANGELOG
## v0.15.0
### 项目分支变更
自 v0.15.0 开始, 该项目将全面更换至 WPF 框架, Winforms 版本将不再进行功能更新, 只进行 bug 修复.
整个项目将具有下列分支:
- `dev/wf`: Winforms 版本开发分支, 继承 v0.15.0 之前的内容.
- `dev/wpf`: WPF 版本开发分支, v0.15.0 之后的内容.
- `release/wf`: `dev/wf` 的发布分支, 用于保留旧版发布功能.
- `main`: 最新的稳定发布分支, 也就是现在的 WPF 版本发布分支.
所有的本地开发和 pr 操作均在 `dev` 子分支下进行, 确认无误后再合并到对应的发布分支进行发布.
### 项目结构变更
粗略的将一些功能模块划分为独立的库项目:
- `SpineViewer`: 项目主体, UI 和程序逻辑
- `Spine`: 对不同版本 Spine 运行时的封装库, 提供所有必需操作的统一接口
- `SFMLRenderer`: 一个 WPF 控件, 支持渲染 SFML 内容
- `SpineRuntimes/*`: 官方不同版本的运行时库, 部分版本在官方基础上有修改和扩展
- `NLog.Windows.Wpf`: NLog 在 WPF 上的扩展库 (尚未完工)
每个项目的具体内容见各自的 README 文档.
### 功能变更
目前 v0.15.0 仅为 pre-release, 功能尚未完全迁移, 有以下功能变化和预期计划:
- 完善了全屏查看功能. 快捷键 F11 可快速切换全屏/窗口模式, 并且支持全屏模式下, 鼠标移动至边缘唤出操作面板.
- 增加了浏览面板. 支持打开文件夹进行浏览, 可以对指定文件夹下所有模型生成预览图进行查看.
- 支持复制指定模型的参数, 并且可以一键应用到多个模型上, 无法应用的项会忽略.
- 导出功能进行了精简. 分为 4 种类型的导出, 且减少了参数项, 仅保留常用参数.
- 导出方式变化. 导出方式变为直接对选中项然后右键菜单进行导出, 不再受 "显示" 和 "仅渲染选中" 参数影响.
- 版本转换功能将暂时不在新版本中提供, 旧版本中已有的功能仍然可用.
- 未来将增加动态桌面功能.
## v0.12.13
- 导出文件名增加额外的随机字符串
## v0.12.12
- 修复 2.1 版本遗漏的 SkinnedMeshAttachment 附件渲染
## v0.12.11
- 修复可能的闪退错误
## v0.12.10
- 增加纹理全局加载选项

View File

@@ -0,0 +1,18 @@
<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>0.15.0</Version>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NLog" Version="5.4.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,92 @@
//
// Copyright (c) 2004-2011 Jaroslaw Kowalski <jaak@jkowalski.net>
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Jaroslaw Kowalski nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) 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 NLog.Conditions;
using NLog.Config;
using NLog;
using System.ComponentModel;
using System.Windows;
namespace NLog.Windows.Wpf
{
[NLogConfigurationItem]
public class RichTextBoxRowColoringRule
{
static RichTextBoxRowColoringRule()
{
Default = new RichTextBoxRowColoringRule();
}
public RichTextBoxRowColoringRule()
: this(null, "Empty", "Empty", FontStyles.Normal, FontWeights.Normal)
{
}
public RichTextBoxRowColoringRule(string condition, string fontColor, string backColor, FontStyle fontStyle, FontWeight fontWeight)
{
Condition = condition;
FontColor = fontColor;
BackgroundColor = backColor;
Style = fontStyle;
Weight = fontWeight;
}
public RichTextBoxRowColoringRule(string condition, string fontColor, string backColor)
{
Condition = condition;
FontColor = fontColor;
BackgroundColor = backColor;
Style = FontStyles.Normal;
Weight = FontWeights.Normal;
}
public static RichTextBoxRowColoringRule Default { get; private set; }
[RequiredParameter]
public ConditionExpression Condition { get; set; }
[DefaultValue("Empty")]
public string FontColor { get; set; }
[DefaultValue("Empty")]
public string BackgroundColor { get; set; }
public FontStyle Style { get; set; }
public FontWeight Weight { get; set; }
public bool CheckCondition(LogEventInfo logEvent)
{
return true.Equals(Condition.Evaluate(logEvent));
}
}
}

View File

@@ -0,0 +1,256 @@
using NLog.Config;
using NLog.Layouts;
using NLog.Targets;
using NLog;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows;
namespace NLog.Windows.Wpf
{
// TODO: 完善日志实现
[Target("RichTextBox")]
public sealed class RichTextBoxTarget : TargetWithLayout
{
private int lineCount;
private int _width = 500;
private int _height = 500;
private static readonly TypeConverter colorConverter = new ColorConverter();
static RichTextBoxTarget()
{
var rules = new List<RichTextBoxRowColoringRule>()
{
new RichTextBoxRowColoringRule("level == LogLevel.Fatal", "White", "Red", FontStyles.Normal, FontWeights.Bold),
new RichTextBoxRowColoringRule("level == LogLevel.Error", "Red", "Empty", FontStyles.Italic, FontWeights.Bold),
new RichTextBoxRowColoringRule("level == LogLevel.Warn", "Orange", "Empty"),
new RichTextBoxRowColoringRule("level == LogLevel.Info", "Black", "Empty"),
new RichTextBoxRowColoringRule("level == LogLevel.Debug", "Gray", "Empty"),
new RichTextBoxRowColoringRule("level == LogLevel.Trace", "DarkGray", "Empty", FontStyles.Italic, FontWeights.Normal),
};
DefaultRowColoringRules = rules.AsReadOnly();
}
public RichTextBoxTarget()
{
WordColoringRules = new List<RichTextBoxWordColoringRule>();
RowColoringRules = new List<RichTextBoxRowColoringRule>();
ToolWindow = true;
}
private delegate void DelSendTheMessageToRichTextBox(string logMessage, RichTextBoxRowColoringRule rule);
private delegate void FormCloseDelegate();
public static ReadOnlyCollection<RichTextBoxRowColoringRule> DefaultRowColoringRules { get; private set; }
public string ControlName { get; set; }
public string FormName { get; set; }
[DefaultValue(false)]
public bool UseDefaultRowColoringRules { get; set; }
[ArrayParameter(typeof(RichTextBoxRowColoringRule), "row-coloring")]
public IList<RichTextBoxRowColoringRule> RowColoringRules { get; private set; }
[ArrayParameter(typeof(RichTextBoxWordColoringRule), "word-coloring")]
public IList<RichTextBoxWordColoringRule> WordColoringRules { get; private set; }
[DefaultValue(true)]
public bool ToolWindow { get; set; }
public bool ShowMinimized { get; set; }
public int Width
{
get { return _width; }
set { _width = value; }
}
public int Height
{
get { return _height; }
set { _height = value; }
}
public bool AutoScroll { get; set; }
public int MaxLines { get; set; }
internal Window TargetForm { get; set; }
internal RichTextBox TargetRichTextBox { get; set; }
internal bool CreatedForm { get; set; }
protected override void InitializeTarget()
{
TargetRichTextBox = Application.Current.MainWindow.FindName(ControlName) as RichTextBox;
if (TargetRichTextBox != null) return;
//this.TargetForm = FormHelper.CreateForm(this.FormName, this.Width, this.Height, false, this.ShowMinimized, this.ToolWindow);
//this.CreatedForm = true;
var openFormByName = Application.Current.Windows.Cast<Window>().FirstOrDefault(x => x.GetType().Name == FormName);
if (openFormByName != null)
{
TargetForm = openFormByName;
if (string.IsNullOrEmpty(ControlName))
{
// throw new NLogConfigurationException("Rich text box control name must be specified for " + GetType().Name + ".");
Trace.WriteLine("Rich text box control name must be specified for " + GetType().Name + ".");
}
CreatedForm = false;
TargetRichTextBox = TargetForm.FindName(ControlName) as RichTextBox;
if (TargetRichTextBox == null)
{
// throw new NLogConfigurationException("Rich text box control '" + ControlName + "' cannot be found on form '" + FormName + "'.");
Trace.WriteLine("Rich text box control '" + ControlName + "' cannot be found on form '" + FormName + "'.");
}
}
if (TargetRichTextBox == null)
{
TargetForm = new Window
{
Name = FormName,
Width = Width,
Height = Height,
WindowStyle = ToolWindow ? WindowStyle.ToolWindow : WindowStyle.None,
WindowState = ShowMinimized ? WindowState.Minimized : WindowState.Normal,
Title = "NLog Messages"
};
TargetForm.Show();
TargetRichTextBox = new RichTextBox { Name = ControlName };
var style = new Style(typeof(Paragraph));
TargetRichTextBox.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
style.Setters.Add(new Setter(Block.MarginProperty, new Thickness(0, 0, 0, 0)));
TargetRichTextBox.Resources.Add(typeof(Paragraph), style);
TargetForm.Content = TargetRichTextBox;
CreatedForm = true;
}
}
protected override void CloseTarget()
{
if (CreatedForm)
{
try
{
TargetForm.Dispatcher.Invoke(() =>
{
TargetForm.Close();
TargetForm = null;
});
}
catch
{
}
}
}
protected override void Write(LogEventInfo logEvent)
{
RichTextBoxRowColoringRule matchingRule = RowColoringRules.FirstOrDefault(rr => rr.CheckCondition(logEvent));
if (UseDefaultRowColoringRules && matchingRule == null)
{
foreach (var rr in DefaultRowColoringRules.Where(rr => rr.CheckCondition(logEvent)))
{
matchingRule = rr;
break;
}
}
if (matchingRule == null)
{
matchingRule = RichTextBoxRowColoringRule.Default;
}
var logMessage = Layout.Render(logEvent);
if (Application.Current == null) return;
try
{
if (Application.Current.Dispatcher.CheckAccess() == false)
{
Application.Current.Dispatcher.Invoke(() => SendTheMessageToRichTextBox(logMessage, matchingRule));
}
else
{
SendTheMessageToRichTextBox(logMessage, matchingRule);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
private static Color GetColorFromString(string color, Brush defaultColor)
{
if (color == "Empty")
{
return defaultColor is SolidColorBrush solidBrush ? solidBrush.Color : Colors.White;
}
return (Color)colorConverter.ConvertFromString(color);
}
private void SendTheMessageToRichTextBox(string logMessage, RichTextBoxRowColoringRule rule)
{
RichTextBox rtbx = TargetRichTextBox;
var tr = new TextRange(rtbx.Document.ContentEnd, rtbx.Document.ContentEnd);
tr.Text = logMessage + "\n";
tr.ApplyPropertyValue(TextElement.ForegroundProperty,
new SolidColorBrush(GetColorFromString(rule.FontColor, (Brush)tr.GetPropertyValue(TextElement.ForegroundProperty)))
);
tr.ApplyPropertyValue(TextElement.BackgroundProperty,
new SolidColorBrush(GetColorFromString(rule.BackgroundColor, (Brush)tr.GetPropertyValue(TextElement.BackgroundProperty)))
);
tr.ApplyPropertyValue(TextElement.FontStyleProperty, rule.Style);
tr.ApplyPropertyValue(TextElement.FontWeightProperty, rule.Weight);
if (MaxLines > 0)
{
lineCount++;
if (lineCount > MaxLines)
{
tr = new TextRange(rtbx.Document.ContentStart, rtbx.Document.ContentEnd);
tr.Text.Remove(0, tr.Text.IndexOf('\n'));
lineCount--;
}
}
if (AutoScroll)
{
rtbx.ScrollToEnd();
}
}
}
}

View File

@@ -0,0 +1,119 @@
//
// Copyright (c) 2004-2011 Jaroslaw Kowalski <jaak@jkowalski.net>
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Jaroslaw Kowalski nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) 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.ComponentModel;
using System.Text.RegularExpressions;
using System.Windows;
using NLog.Config;
namespace NLog.Windows.Wpf
{
[NLogConfigurationItem]
public class RichTextBoxWordColoringRule
{
private Regex compiledRegex;
public RichTextBoxWordColoringRule()
{
FontColor = "Empty";
BackgroundColor = "Empty";
}
public RichTextBoxWordColoringRule(string text, string fontColor, string backgroundColor)
{
Text = text;
FontColor = fontColor;
BackgroundColor = backgroundColor;
Style = FontStyles.Normal;
Weight = FontWeights.Normal;
}
public RichTextBoxWordColoringRule(string text, string textColor, string backgroundColor, FontStyle fontStyle, FontWeight fontWeight)
{
Text = text;
FontColor = textColor;
BackgroundColor = backgroundColor;
Style = fontStyle;
Weight = fontWeight;
}
public string Regex { get; set; }
public string Text { get; set; }
[DefaultValue(false)]
public bool WholeWords { get; set; }
[DefaultValue(false)]
public bool IgnoreCase { get; set; }
public FontStyle Style { get; set; }
public FontWeight Weight { get; set; }
public Regex CompiledRegex
{
get
{
if (compiledRegex == null)
{
string regexpression = Regex;
if (regexpression == null && Text != null)
{
regexpression = System.Text.RegularExpressions.Regex.Escape(Text);
if (WholeWords)
{
regexpression = "\b" + regexpression + "\b";
}
}
RegexOptions regexOptions = RegexOptions.Compiled;
if (IgnoreCase)
{
regexOptions |= RegexOptions.IgnoreCase;
}
compiledRegex = new Regex(regexpression, regexOptions);
}
return compiledRegex;
}
}
[DefaultValue("Empty")]
public string FontColor { get; set; }
[DefaultValue("Empty")]
public string BackgroundColor { get; set; }
}
}

View File

@@ -0,0 +1,138 @@
using SFML.Graphics;
using SFML.System;
using SFML.Window;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SFMLRenderer
{
/// <summary>
/// 定义了 SFML 渲染器的基本功能和事件, 基本上是对 <see cref="RenderWindow"/> 的抽象
/// <para>实现示例可以见 <see cref="SFMLRenderPanel"/></para>
/// </summary>
public interface ISFMLRenderer
{
/// <summary>
/// 发生在资源首次创建完成后, 该事件发生之后渲染器才是可用的, 操作才会生效
/// </summary>
public event EventHandler? RendererCreated;
/// <summary>
/// 发生在资源即将不可用之前, 该事件发生之后对渲染器的操作将被忽略
/// </summary>
public event EventHandler? RendererDisposing;
public event EventHandler<MouseMoveEventArgs>? CanvasMouseMove;
public event EventHandler<MouseButtonEventArgs>? CanvasMouseButtonPressed;
public event EventHandler<MouseButtonEventArgs>? CanvasMouseButtonReleased;
public event EventHandler<MouseWheelScrollEventArgs>? CanvasMouseWheelScrolled;
/// <summary>
/// 分辨率, 影响画面的相对比例
/// </summary>
public Vector2u Resolution { get; set; }
/// <summary>
/// 快捷设置视区中心点
/// </summary>
public Vector2f Center { get; set; }
/// <summary>
/// 快捷设置视区缩放
/// </summary>
public float Zoom { get; set; }
/// <summary>
/// 快捷设置视区旋转
/// </summary>
public float Rotation { get; set; }
/// <summary>
/// 快捷设置视区水平翻转
/// </summary>
public bool FlipX { get; set; }
/// <summary>
/// 快捷设置视区垂直翻转
/// </summary>
public bool FlipY { get; set; }
/// <summary>
/// 最大帧率, 影响 Draw 的最大调用频率, <see cref="RenderWindow.SetFramerateLimit(uint)"/>
/// </summary>
public uint MaxFps { get; set; }
/// <summary>
/// 垂直同步, <see cref="RenderWindow.SetVerticalSyncEnabled(bool)"/>
/// </summary>
public bool VerticalSync { get; set; }
/// <summary>
/// <inheritdoc cref="RenderWindow.SetActive(bool)"/>
/// </summary>
public bool SetActive(bool active);
/// <summary>
/// <inheritdoc cref="RenderWindow.GetView"/>
/// </summary>
public View GetView();
/// <summary>
/// <inheritdoc cref="RenderWindow.SetView(View)"/>
/// </summary>
public void SetView(View view);
/// <summary>
/// <inheritdoc cref="RenderWindow.MapPixelToCoords(Vector2i)"/>
/// </summary>
public Vector2f MapPixelToCoords(Vector2i point);
/// <summary>
/// <inheritdoc cref="RenderWindow.MapCoordsToPixel(Vector2f)"/>
/// </summary>
public Vector2i MapCoordsToPixel(Vector2f point);
/// <summary>
/// <inheritdoc cref="RenderWindow.Clear()"/>
/// </summary>
public void Clear();
/// <summary>
/// <inheritdoc cref="RenderWindow.Clear(Color)"/>
/// </summary>
public void Clear(Color color);
/// <summary>
/// <inheritdoc cref="RenderWindow.Draw(Drawable)"/>
/// </summary>
public void Draw(Drawable drawable);
/// <summary>
/// <inheritdoc cref="RenderWindow.Draw(Drawable, RenderStates)"/>
/// </summary>
public void Draw(Drawable drawable, RenderStates states);
/// <summary>
/// <inheritdoc cref="RenderWindow.Draw(Vertex[], PrimitiveType)"/>
/// </summary>
public void Draw(Vertex[] vertices, PrimitiveType type);
/// <summary>
/// <inheritdoc cref="RenderWindow.Draw(Vertex[], PrimitiveType, RenderStates)"/>
/// </summary>
public void Draw(Vertex[] vertices, PrimitiveType type, RenderStates states);
/// <summary>
/// <inheritdoc cref="RenderWindow.Draw(Vertex[], uint, uint, PrimitiveType)"/>
/// </summary>
public void Draw(Vertex[] vertices, uint start, uint count, PrimitiveType type);
/// <summary>
/// <inheritdoc cref="RenderWindow.Display"/>
/// </summary>
public void Display();
}
}

21
SFMLRenderer/README.md Normal file
View File

@@ -0,0 +1,21 @@
# SFMLRenderer
这个库封装了一个用于 WPF 的 SFML 渲染控件.
```mermaid
classDiagram
namespace SFMLRenderer {
class ISFMLRenderer {
<<Interface>>
}
class SFMLHwndHost
class SFMLRenderPanel
}
ISFMLRenderer <|.. SFMLRenderPanel
SFMLHwndHost <.. SFMLRenderPanel
```

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
namespace SFMLRenderer
{
/// <summary>
/// 原生窗口控件, 不应直接使用该类, 而是使用 <see cref="SFMLRenderPanel"/> 或者二次封装
/// </summary>
public class SFMLHwndHost : HwndHost
{
private HwndSource? _hwndSource;
private SFML.Graphics.RenderWindow? _renderWindow;
/// <summary>
/// 内部的 SFML 窗口对象
/// </summary>
public SFML.Graphics.RenderWindow? RenderWindow => _renderWindow;
/// <summary>
/// 窗口建立事件
/// </summary>
public event EventHandler? RenderWindowBuilded;
/// <summary>
/// 窗口销毁事件
/// </summary>
public event EventHandler? RenderWindowDestroying;
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
{
var ps = new HwndSourceParameters(GetType().Name, (int)Width, (int)Height)
{
ParentWindow = hwndParent.Handle,
WindowStyle = 0x40000000 | 0x10000000, // WS_CHILD | WS_VISIBLE
HwndSourceHook = HwndMessageHook
};
_hwndSource = new HwndSource(ps);
_renderWindow = new(_hwndSource.Handle);
_renderWindow.SetActive(false);
RenderWindowBuilded?.Invoke(this, EventArgs.Empty);
return new HandleRef(this, _hwndSource.Handle);
}
protected override void DestroyWindowCore(HandleRef hwnd)
{
RenderWindowDestroying?.Invoke(this, EventArgs.Empty);
_renderWindow?.Close();
var rw = _renderWindow;
_renderWindow = null;
rw?.Dispose();
var hs = _hwndSource;
_hwndSource = null;
hs?.Dispose();
}
private nint HwndMessageHook(nint hwnd, int msg, nint wParam, nint lParam, ref bool handled)
{
_renderWindow?.DispatchEvents();
return nint.Zero;
}
}
}

View File

@@ -0,0 +1,14 @@
<UserControl x:Class="SFMLRenderer.SFMLRenderPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SFMLRenderer"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<local:SFMLHwndHost x:Name="_hwndHost"
Width="100"
Height="100"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</UserControl>

View File

@@ -0,0 +1,253 @@
using SFML.Graphics;
using SFML.System;
using SFML.Window;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SFMLRenderer
{
/// <summary>
/// SFMLRenderPanel.xaml 的交互逻辑
/// </summary>
public partial class SFMLRenderPanel : System.Windows.Controls.UserControl, ISFMLRenderer
{
private RenderWindow? RenderWindow => _hwndHost.RenderWindow;
public SFMLRenderPanel()
{
InitializeComponent();
}
public event EventHandler? RendererCreated
{
add => _hwndHost.RenderWindowBuilded += value;
remove => _hwndHost.RenderWindowBuilded -= value;
}
public event EventHandler? RendererDisposing
{
add => _hwndHost.RenderWindowDestroying += value;
remove => _hwndHost.RenderWindowDestroying -= value;
}
public event EventHandler<MouseMoveEventArgs>? CanvasMouseMove
{
add { if (RenderWindow is RenderWindow w) w.MouseMoved += value; }
remove { if (RenderWindow is RenderWindow w) w.MouseMoved -= value; }
}
public event EventHandler<MouseButtonEventArgs>? CanvasMouseButtonPressed
{
add { if (RenderWindow is RenderWindow w) w.MouseButtonPressed += value; }
remove { if (RenderWindow is RenderWindow w) w.MouseButtonPressed -= value; }
}
public event EventHandler<MouseButtonEventArgs>? CanvasMouseButtonReleased
{
add { if (RenderWindow is RenderWindow w) w.MouseButtonReleased += value; }
remove { if (RenderWindow is RenderWindow w) w.MouseButtonReleased -= value; }
}
public event EventHandler<MouseWheelScrollEventArgs>? CanvasMouseWheelScrolled
{
add { if (RenderWindow is RenderWindow w) w.MouseWheelScrolled += value; }
remove { if (RenderWindow is RenderWindow w) w.MouseWheelScrolled -= value; }
}
public Vector2u Resolution
{
get => _resolution;
set
{
if (RenderWindow is null) return;
if (value == _resolution) return;
if (value.X <= 0 || value.Y <= 0) return;
var zoom = Zoom;
float parentW = (float)ActualWidth;
float parentH = (float)ActualHeight;
float renderW = value.X;
float renderH = value.Y;
float scale = Math.Min(parentW / renderW, parentH / renderH); // 两方向取较小值, 保证 parent 覆盖 render
renderW *= scale;
renderH *= scale;
_hwndHost.Width = renderW;
_hwndHost.Height = renderH;
_resolution = value;
// 设置完 resolution 后还原缩放比例
Zoom = zoom;
}
}
private Vector2u _resolution = new(100, 100);
public Vector2f Center
{
get
{
if (RenderWindow is null) return default;
using var view = RenderWindow.GetView();
return view.Center;
}
set
{
if (RenderWindow is null) return;
using var view = RenderWindow.GetView();
view.Center = value;
RenderWindow.SetView(view);
}
}
public float Zoom
{
get
{
if (RenderWindow is null) return 1;
using var view = RenderWindow.GetView();
return Math.Abs(_resolution.X / view.Size.X); // XXX: 仅使用宽度进行缩放计算
}
set
{
value = Math.Abs(value);
if (RenderWindow is null || value <= 0) return;
using var view = RenderWindow.GetView();
var signX = Math.Sign(view.Size.X);
var signY = Math.Sign(view.Size.Y);
view.Size = new(_resolution.X / value * signX, _resolution.Y / value * signY);
RenderWindow.SetView(view);
}
}
public float Rotation
{
get
{
if (RenderWindow is null) return default;
using var view = RenderWindow.GetView();
return view.Rotation;
}
set
{
if (RenderWindow is null) return;
using var view = RenderWindow.GetView();
view.Rotation = value;
RenderWindow.SetView(view);
}
}
public bool FlipX
{
get
{
if (RenderWindow is null) return false;
using var view = RenderWindow.GetView();
return view.Size.X < 0;
}
set
{
if (RenderWindow is null) return;
using var view = RenderWindow.GetView();
var size = view.Size;
if (size.X > 0 && value || size.X < 0 && !value)
size.X *= -1;
view.Size = size;
RenderWindow.SetView(view);
}
}
public bool FlipY
{
get
{
if (RenderWindow is null) return false;
using var view = RenderWindow.GetView();
return view.Size.Y < 0;
}
set
{
if (RenderWindow is null) return;
using var view = RenderWindow.GetView();
var size = view.Size;
if (size.Y > 0 && value || size.Y < 0 && !value)
size.Y *= -1;
view.Size = size;
RenderWindow.SetView(view);
}
}
public uint MaxFps
{
get => _maxFps;
set
{
if (RenderWindow is null) return;
RenderWindow.SetFramerateLimit(value);
_maxFps = value;
}
}
private uint _maxFps = 0;
public bool VerticalSync
{
get => _verticalSync;
set
{
if (RenderWindow is null) return;
RenderWindow.SetVerticalSyncEnabled(value);
_verticalSync = value;
}
}
private bool _verticalSync = false;
public void Clear() => RenderWindow?.Clear();
public void Clear(Color color) => RenderWindow?.Clear(color);
public void Display() => RenderWindow?.Display();
public void Draw(Drawable drawable) => RenderWindow?.Draw(drawable);
public void Draw(Drawable drawable, RenderStates states) => RenderWindow?.Draw(drawable, states);
public void Draw(Vertex[] vertices, PrimitiveType type) => RenderWindow?.Draw(vertices, type);
public void Draw(Vertex[] vertices, PrimitiveType type, RenderStates states) => RenderWindow?.Draw(vertices, type, states);
public void Draw(Vertex[] vertices, uint start, uint count, PrimitiveType type) => RenderWindow?.Draw(vertices, start, count, type);
public View GetView() => RenderWindow?.GetView() ?? new();
public Vector2i MapCoordsToPixel(Vector2f point) => RenderWindow?.MapCoordsToPixel(point) ?? default;
public Vector2f MapPixelToCoords(Vector2i point) => RenderWindow?.MapPixelToCoords(point) ?? default;
public bool SetActive(bool active) => RenderWindow?.SetActive(active) ?? false;
public void SetView(View view) => RenderWindow?.SetView(view);
protected override void OnRenderSizeChanged(System.Windows.SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
if (RenderWindow is null) return;
float parentW = (float)sizeInfo.NewSize.Width;
float parentH = (float)sizeInfo.NewSize.Height;
float renderW = (float)_hwndHost.ActualWidth;
float renderH = (float)_hwndHost.ActualHeight;
float scale = Math.Min(parentW / renderW, parentH / renderH); // 两方向取较小值, 保证 parent 覆盖 render
renderW *= scale;
renderH *= scale;
_hwndHost.Width = renderW;
_hwndHost.Height = renderH;
}
}
}

View File

@@ -0,0 +1,22 @@
<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>0.15.0</Version>
<UseWPF>true</UseWPF>
</PropertyGroup>
<PropertyGroup>
<NoWarn>$(NoWarn);NETSDK1206</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SFML.Net" Version="2.6.1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,198 @@
using NLog;
using SFML.Graphics;
using SFML.System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Exporters
{
/// <summary>
/// 导出类基类, 提供基本的帧渲染功能
/// </summary>
public abstract class BaseExporter : IDisposable
{
/// <summary>
/// 日志器
/// </summary>
protected static readonly Logger _logger = LogManager.GetCurrentClassLogger();
/// <summary>
/// 用于渲染的画布
/// </summary>
protected RenderTexture _renderTexture;
/// <summary>
/// 初始化导出器
/// </summary>
/// <param name="width">画布宽像素值</param>
/// <param name="height">画布高像素值</param>
public BaseExporter(uint width , uint height)
{
if (width <= 0 || height <= 0)
throw new ArgumentException($"Invalid resolution: {width}, {height}");
_renderTexture = new(width, height);
_renderTexture.SetActive(false);
}
/// <summary>
/// 初始化导出器
/// </summary>
public BaseExporter(Vector2u resolution)
{
if (resolution.X <= 0 || resolution.Y <= 0)
throw new ArgumentException($"Invalid resolution: {resolution}");
_renderTexture = new(resolution.X, resolution.Y);
_renderTexture.SetActive(false);
}
/// <summary>
/// 可选的进度回调函数
/// <list type="number">
/// <item><c>total</c>: 任务总量</item>
/// <item><c>done</c>: 已完成量</item>
/// <item><c>progressText</c>: 需要设置的进度提示文本</item>
/// </list>
/// </summary>
public Action<float, float, string>? ProgressReporter { get => _progressReporter; set => _progressReporter = value; }
protected Action<float, float, string>? _progressReporter;
/// <summary>
/// 背景颜色
/// </summary>
public Color BackgroundColor
{
get => _backgroundColor;
set
{
_backgroundColor = value;
var bcPma = value;
var a = bcPma.A / 255f;
bcPma.R = (byte)(bcPma.R * a);
bcPma.G = (byte)(bcPma.G * a);
bcPma.B = (byte)(bcPma.B * a);
_backgroundColorPma = bcPma;
}
}
protected Color _backgroundColor = Color.Transparent;
/// <summary>
/// 预乘后的背景颜色
/// </summary>
protected Color _backgroundColorPma = Color.Transparent;
/// <summary>
/// 画面分辨率
/// <inheritdoc cref="RenderTexture.Size"/>
/// </summary>
public Vector2u Resolution
{
get => _renderTexture.Size;
set
{
if (value.X <= 0 || value.Y <= 0)
{
_logger.Warn("Omit invalid exporter resolution: {0}", value);
return;
}
if (_renderTexture.Size != value)
{
using var old = _renderTexture;
using var view = old.GetView();
var renderTexture = new RenderTexture(value.X, value.Y);
renderTexture.SetActive(false);
renderTexture.SetView(view);
_renderTexture = renderTexture;
}
}
}
/// <summary>
/// <inheritdoc cref="View.Viewport"/>
/// </summary>
public FloatRect Viewport
{
get { using var view = _renderTexture.GetView(); return view.Viewport; }
set { using var view = _renderTexture.GetView(); view.Viewport = value; _renderTexture.SetView(view); }
}
/// <summary>
/// <inheritdoc cref="View.Center"/>
/// </summary>
public Vector2f Center
{
get { using var view = _renderTexture.GetView(); return view.Center; }
set { using var view = _renderTexture.GetView(); view.Center = value; _renderTexture.SetView(view); }
}
/// <summary>
/// <inheritdoc cref="View.Size"/>
/// </summary>
public Vector2f Size
{
get { using var view = _renderTexture.GetView(); return view.Size; }
set { using var view = _renderTexture.GetView(); view.Size = value; _renderTexture.SetView(view); }
}
/// <summary>
/// <inheritdoc cref="View.Rotation"/>
/// </summary>
public float Rotation
{
get { using var view = _renderTexture.GetView(); return view.Rotation; }
set { using var view = _renderTexture.GetView(); view.Rotation = value; _renderTexture.SetView(view); }
}
/// <summary>
/// 获取的一帧, 结果是预乘的
/// </summary>
protected virtual SFMLImageVideoFrame GetFrame(SpineObject[] spines)
{
_renderTexture.SetActive(true);
_renderTexture.Clear(_backgroundColorPma);
foreach (var sp in spines.Reverse()) _renderTexture.Draw(sp);
_renderTexture.Display();
_renderTexture.SetActive(false);
return new(_renderTexture.Texture.CopyToImage());
}
/// <summary>
/// 导出给定的模型, 从前往后对应从上往下的渲染顺序
/// </summary>
/// <param name="output">输出路径, 一般而言都是文件路径, 少数情况指定的是文件夹</param>
/// <param name="spines">要导出的模型, 从前往后对应从上往下的渲染顺序</param>
public abstract void Export(string output, params SpineObject[] spines);
#region IDisposable
private bool _disposed = false;
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
_renderTexture.Dispose();
}
_disposed = true;
}
~BaseExporter()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
if (_disposed)
{
GC.SuppressFinalize(this);
}
}
#endregion
}
}

View File

@@ -0,0 +1,82 @@
using FFMpegCore;
using FFMpegCore.Pipes;
using SFML.System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Exporters
{
/// <summary>
/// 自定义参数的 FFmpeg 导出类
/// </summary>
public class CustomFFmpegExporter : VideoExporter
{
public CustomFFmpegExporter(uint width = 100, uint height = 100) : base(width, height) { }
public CustomFFmpegExporter(Vector2u resolution) : base(resolution) { }
/// <summary>
/// <c>-f</c>
/// </summary>
public string? Format { get => _format; set => _format = value; }
private string? _format;
/// <summary>
/// <c>-c:v</c>
/// </summary>
public string? Codec { get => _codec; set => _codec = value; }
private string? _codec;
/// <summary>
/// <c>-pix_fmt</c>
/// </summary>
public string? PixelFormat { get => _pixelFormat; set => _pixelFormat = value; }
private string? _pixelFormat;
/// <summary>
/// <c>-b:v</c>
/// </summary>
public string? Bitrate { get => _bitrate; set => _bitrate = value; }
private string? _bitrate;
/// <summary>
/// <c>-vf</c>
/// </summary>
public string? Filter { get => _filter; set => _filter = value; }
private string? _filter;
/// <summary>
/// 其他自定义参数
/// </summary>
public string? CustomArgs { get => _customArgs; set => _customArgs = value; }
private string? _customArgs;
private void SetOutputOptions(FFMpegArgumentOptions options)
{
if (!string.IsNullOrEmpty(_format)) options.ForceFormat(_format);
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}");
else options.WithCustomArgument("-vf unpremultiply=inplace=1");
}
public override void Export(string output, CancellationToken ct, params SpineObject[] spines)
{
var videoFramesSource = new RawVideoPipeSource(GetFrames(spines, output, ct)) { FrameRate = _fps };
try
{
var ffmpegArgs = FFMpegArguments.FromPipeInput(videoFramesSource).OutputToFile(output, true, SetOutputOptions);
_logger.Info("FFmpeg arguments: {0}", ffmpegArgs.Arguments);
ffmpegArgs.ProcessSynchronously();
}
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_logger.Error("Failed to export {0} {1}, {2}", _format, output, ex.Message);
}
}
}
}

View File

@@ -0,0 +1,146 @@
using FFMpegCore;
using FFMpegCore.Enums;
using FFMpegCore.Pipes;
using NLog;
using SFML.Graphics;
using SFML.System;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Exporters
{
/// <summary>
/// 基于 FFmpeg 命令行的导出类, 可以导出动图及视频格式
/// </summary>
public class FFmpegVideoExporter : VideoExporter
{
public FFmpegVideoExporter(uint width = 100, uint height = 100) : base(width, height) { }
public FFmpegVideoExporter(Vector2u resolution) : base(resolution) { }
/// <summary>
/// FFmpeg 导出格式
/// </summary>
public enum VideoFormat
{
Gif,
Webp,
Mp4,
Webm,
Mkv,
}
/// <summary>
/// 视频格式
/// </summary>
public VideoFormat Format { get => _format; set => _format = value; }
private VideoFormat _format = VideoFormat.Mp4;
/// <summary>
/// 动图是否循环
/// </summary>
public bool Loop { get => _loop; set => _loop = value; }
private bool _loop = true;
/// <summary>
/// 质量
/// </summary>
public int Quality { get => _quality; set => _quality = Math.Clamp(value, 0, 100); }
private int _quality = 75;
/// <summary>
/// CRF
/// </summary>
public int Crf { get => _crf; set => _crf = Math.Clamp(value, 0, 63); }
private int _crf = 23;
/// <summary>
/// 获取的一帧, 结果是预乘的
/// </summary>
protected override SFMLImageVideoFrame GetFrame(SpineObject[] spines)
{
// XXX: 不知道为什么用 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)
{
var videoFramesSource = new RawVideoPipeSource(GetFrames(spines, output, ct)) { FrameRate = _fps };
Action<FFMpegArgumentOptions> setOutputOptions = _format switch
{
VideoFormat.Gif => SetGifOptions,
VideoFormat.Webp => SetWebpOptions,
VideoFormat.Mp4 => SetMp4Options,
VideoFormat.Webm => SetWebmOptions,
VideoFormat.Mkv => SetMkvOptions,
_ => throw new NotImplementedException(),
};
try
{
var ffmpegArgs = FFMpegArguments.FromPipeInput(videoFramesSource).OutputToFile(output, true, setOutputOptions);
_logger.Info("FFmpeg arguments: {0}", ffmpegArgs.Arguments);
ffmpegArgs.ProcessSynchronously();
}
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_logger.Error("Failed to export {0} {1}, {2}", _format, output, ex.Message);
}
}
private void SetGifOptions(FFMpegArgumentOptions options)
{
// 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 customArgs = $"-vf \"unpremultiply=inplace=1, {v};{s0};{s1}\" -loop {(_loop ? 0 : -1)}";
options.ForceFormat("gif")
.WithCustomArgument(customArgs);
}
private void SetWebpOptions(FFMpegArgumentOptions options)
{
var customArgs = $"-vf unpremultiply=inplace=1 -quality {_quality} -loop {(_loop ? 0 : 1)}";
options.ForceFormat("webp").WithVideoCodec("libwebp_anim").ForcePixelFormat("yuva420p")
.WithCustomArgument(customArgs);
}
private void SetMp4Options(FFMpegArgumentOptions options)
{
var customArgs = "-vf unpremultiply=inplace=1";
options.ForceFormat("mp4").WithVideoCodec("libx264").ForcePixelFormat("yuv444p")
.WithFastStart()
.WithConstantRateFactor(_crf)
.WithCustomArgument(customArgs);
}
private void SetWebmOptions(FFMpegArgumentOptions options)
{
var customArgs = "-vf unpremultiply=inplace=1";
options.ForceFormat("webm").WithVideoCodec("libvpx-vp9").ForcePixelFormat("yuva420p")
.WithConstantRateFactor(_crf)
.WithCustomArgument(customArgs);
}
private void SetMkvOptions(FFMpegArgumentOptions options)
{
var customArgs = "-vf unpremultiply=inplace=1";
options.ForceFormat("matroska").WithVideoCodec("libx265").ForcePixelFormat("yuv444p")
.WithConstantRateFactor(_crf)
.WithCustomArgument(customArgs);
}
}
}

View File

@@ -0,0 +1,37 @@
using SFML.System;
using SkiaSharp;
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Exporters
{
/// <summary>
/// 单帧画面导出类
/// </summary>
public class FrameExporter : BaseExporter
{
public FrameExporter(uint width = 100, uint height = 100) : base(width, height) { }
public FrameExporter(Vector2u resolution) : base(resolution) { }
public SKEncodedImageFormat Format { get => _format; set => _format = value; }
protected SKEncodedImageFormat _format = SKEncodedImageFormat.Png;
public int Quality { get => _quality; set => _quality = Math.Clamp(value, 0, 100); }
protected int _quality = 80;
public override void Export(string output, params SpineObject[] spines)
{
using var frame = GetFrame(spines);
var info = new SKImageInfo(frame.Width, frame.Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var skImage = SKImage.FromPixelCopy(info, frame.Image.Pixels);
using var data = skImage.Encode(_format, _quality);
using var stream = File.OpenWrite(output);
data.SaveTo(stream);
}
}
}

View File

@@ -0,0 +1,61 @@
using NLog;
using SFML.System;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Exporters
{
/// <summary>
/// 帧序列导出器, 导出 png 帧序列
/// </summary>
public class FrameSequenceExporter : VideoExporter
{
public FrameSequenceExporter(uint width = 100, uint height = 100) : base(width, height) { }
public FrameSequenceExporter(Vector2u resolution) : base(resolution) { }
public override void Export(string output, CancellationToken ct, params SpineObject[] spines)
{
Directory.CreateDirectory(output);
int frameCount = GetFrameCount();
int frameIdx = 0;
_progressReporter?.Invoke(frameCount, 0, $"[{frameIdx}/{frameCount}] {output}");
foreach (var frame in GetFrames(spines))
{
if (ct.IsCancellationRequested)
{
_logger.Info("Export cancelled");
frame.Dispose();
break;
}
var savePath = Path.Combine(output, $"frame_{_fps}_{frameIdx:d6}.png");
var info = new SKImageInfo(frame.Width, frame.Height, SKColorType.Rgba8888, SKAlphaType.Premul);
_progressReporter?.Invoke(frameCount, frameIdx, $"[{frameIdx + 1}/{frameCount}] {savePath}");
try
{
using var skImage = SKImage.FromPixelCopy(info, frame.Image.Pixels);
using var data = skImage.Encode(SKEncodedImageFormat.Png, 100);
using var stream = File.OpenWrite(savePath);
data.SaveTo(stream);
}
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_logger.Error("Failed to save frame {0}, {1}", savePath, ex.Message);
}
finally
{
frame.Dispose();
}
frameIdx++;
}
}
}
}

View File

@@ -0,0 +1,52 @@
using FFMpegCore.Pipes;
using SFML.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Spine.Exporters
{
/// <summary>
/// <see cref="SFML.Graphics.Image"/> 帧对象包装类, 将接管给定对象生命周期
/// </summary>
public class SFMLImageVideoFrame(Image image) : IVideoFrame, IDisposable
{
private readonly Image _image = image;
/// <summary>
/// 接管的 <see cref="SFML.Graphics.Image"/> 内部对象
/// </summary>
public Image Image => _image;
public int Width => (int)_image.Size.X;
public int Height => (int)_image.Size.Y;
public string Format => "rgba";
public void Serialize(Stream pipe) => pipe.Write(_image.Pixels);
public async Task SerializeAsync(Stream pipe, CancellationToken token) => await pipe.WriteAsync(_image.Pixels, token);
#region IDisposable
private bool _disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_image.Dispose();
}
_disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

View File

@@ -0,0 +1,142 @@
using NLog;
using SFML.System;
using SkiaSharp;
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Exporters
{
/// <summary>
/// 多帧画面导出基类, 可以获取连续的帧序列
/// </summary>
public abstract class VideoExporter : BaseExporter
{
public VideoExporter(uint width, uint height) : base(width, height) { }
public VideoExporter(Vector2u resolution) : base(resolution) { }
/// <summary>
/// 导出时长
/// </summary>
public float Duration
{
get => _duration;
set
{
if (value < 0)
{
_logger.Warn("Omit invalid duration: {0}", value);
return;
}
_duration = value;
}
}
protected float _duration = 0;
/// <summary>
/// 帧率
/// </summary>
public float Fps
{
get => _fps;
set
{
if (value <= 0)
{
_logger.Warn("Omit invalid fps: {0}", value);
return;
}
_fps = value;
}
}
protected float _fps = 24;
/// <summary>
/// 是否保留最后一帧
/// </summary>
public bool KeepLast { get => _keepLast; set => _keepLast = value; }
protected bool _keepLast = true;
/// <summary>
/// 获取总帧数
/// </summary>
public int GetFrameCount()
{
var delta = 1f / _fps;
var total = (int)(_duration * _fps); // 完整帧的数量
var deltaFinal = _duration - delta * total; // 最后一帧时长
var final = _keepLast && deltaFinal > 1e-3 ? 1 : 0;
var frameCount = 1 + total + final; // 所有帧的数量 = 起始帧 + 完整帧 + 最后一帧
return frameCount;
}
/// <summary>
/// 生成帧序列
/// </summary>
protected IEnumerable<SFMLImageVideoFrame> GetFrames(SpineObject[] spines)
{
float delta = 1f / _fps;
int total = (int)(_duration * _fps); // 完整帧的数量
bool hasFinal = _keepLast && (_duration - delta * total) > 1e-3;
// 导出首帧
var firstFrame = GetFrame(spines);
yield return firstFrame;
// 导出完整帧
for (int i = 0; i < total; i++)
{
foreach (var spine in spines) spine.Update(delta);
yield return GetFrame(spines);
}
// 导出最后一帧
if (hasFinal)
{
// XXX: 此处还是按照完整的一帧时长进行更新, 也许可以只更新准确的最后一帧时长
foreach (var spine in spines) spine.Update(delta);
yield return GetFrame(spines);
}
}
/// <summary>
/// 生成帧序列, 支持中途取消和进度输出
/// </summary>
protected IEnumerable<SFMLImageVideoFrame> GetFrames(SpineObject[] spines, string output, CancellationToken ct)
{
int frameCount = GetFrameCount();
int frameIdx = 0;
_progressReporter?.Invoke(frameCount, 0, $"[{frameIdx}/{frameCount}] {output}");
foreach (var frame in GetFrames(spines))
{
if (ct.IsCancellationRequested)
{
_logger.Info("Export cancelled");
frame.Dispose();
break;
}
_progressReporter?.Invoke(frameCount, frameIdx, $"[{frameIdx + 1}/{frameCount}] {output}");
yield return frame;
frameIdx++;
}
}
public sealed override void Export(string output, params SpineObject[] spines) => Export(output, default, spines);
/// <summary>
/// 导出给定的模型, 从前往后对应从上往下的渲染顺序
/// </summary>
/// <param name="output">输出路径, 一般而言都是文件路径, 少数情况指定的是文件夹</param>
/// <param name="ct">取消令牌</param>
/// <param name="spines">要导出的模型, 从前往后对应从上往下的渲染顺序</param>
public abstract void Export(string output, CancellationToken ct, params SpineObject[] spines);
}
}

View File

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

View File

@@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using SpineRuntime21;
namespace Spine.Implementations.SpineWrappers.V21
{
internal sealed class AnimationState21(AnimationState innerObject, SpineObjectData21 data) : IAnimationState
{
private readonly AnimationState _o = innerObject;
private readonly SpineObjectData21 _data = data;
private readonly Dictionary<TrackEntry, TrackEntry21> _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: 2.1 没有这两个事件
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 Skeleton21 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 Animation21 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 Animation21 anime)
return GetTrackEntry(_o.AddAnimation(trackIndex, anime.InnerObject, loop, delay));
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
}
public ITrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay)
=> GetTrackEntry(_o.AddEmptyAnimation(trackIndex, mixDuration, delay));
public override string ToString() => _o.ToString();
}
}

View File

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

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime21;
namespace Spine.Implementations.SpineWrappers.V21.Attachments
{
internal sealed class BoundingBoxAttachment21(BoundingBoxAttachment innerObject) :
Attachment21(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 Slot21 st)
{
var length = _o.Vertices.Length;
if (worldVertices.Length < length) worldVertices = new float[length];
_o.ComputeWorldVertices(st.InnerObject.Bone, worldVertices);
return length;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot21)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime21;
namespace Spine.Implementations.SpineWrappers.V21.Attachments
{
internal sealed class MeshAttachment21(MeshAttachment innerObject) :
Attachment21(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 Slot21 st)
{
var length = _o.Vertices.Length;
if (worldVertices.Length < length) worldVertices = new float[length];
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
return length;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot21)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
public int[] Triangles => _o.Triangles;
public int HullLength => _o.HullLength;
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime21;
namespace Spine.Implementations.SpineWrappers.V21.Attachments
{
internal sealed class RegionAttachment21(RegionAttachment innerObject) :
Attachment21(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 Slot21 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(Slot21)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
}
}

View File

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

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using SpineRuntime21;
namespace Spine.Implementations.SpineWrappers.V21
{
internal sealed class Bone21(Bone innerObject, Bone21? parent = null) : IBone
{
private readonly Bone _o = innerObject;
private readonly Bone21? _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.M00;
public float B => _o.M01;
public float C => _o.M10;
public float D => _o.M11;
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,105 @@
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 SpineRuntime21;
namespace Spine.Implementations.SpineWrappers.V21
{
internal sealed class Skeleton21 : ISkeleton
{
private readonly Skeleton _o;
private readonly SpineObjectData21 _data;
private readonly ImmutableArray<IBone> _bones;
private readonly FrozenDictionary<string, IBone> _bonesByName;
private readonly ImmutableArray<ISlot> _slots;
private readonly FrozenDictionary<string, ISlot> _slotsByName;
private Skin21? _skin;
public Skeleton21(Skeleton innerObject, SpineObjectData21 data)
{
_o = innerObject;
_data = data;
List<Bone21> bones = [];
Dictionary<string, IBone> bonesByName = [];
foreach (var b in _o.Bones)
{
var bone = new Bone21(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<Slot21> slots = [];
Dictionary<string, ISlot> slotsByName = [];
foreach (var s in _o.Slots)
{
var slot = new Slot21(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 Skin21 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)
{
_o.GetBounds(out x, out y, out w, out h);
}
public override string ToString() => _o.ToString();
}
}

View File

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

View File

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

View File

@@ -0,0 +1,71 @@
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 SpineRuntime21;
namespace Spine.Implementations.SpineWrappers.V21
{
internal sealed class Slot21 : ISlot
{
private readonly Slot _o;
private readonly SpineObjectData21 _data;
private readonly Bone21 _bone;
private readonly SFML.Graphics.BlendMode _blendMode;
public Slot21(Slot innerObject, SpineObjectData21 data, Bone21 bone)
{
_o = innerObject;
_data = data;
_bone = bone;
_blendMode = _o.Data.AdditiveBlending ? SFMLBlendMode.AdditivePma : SFMLBlendMode.NormalPma; // NOTE: 2.1 没有完整的 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.Attachment21 att)
{
_o.Attachment = att.InnerObject;
return;
}
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
}
}
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,117 @@
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 SpineRuntime21;
using Spine.Implementations.SpineWrappers.V21.Attachments;
namespace Spine.Implementations.SpineWrappers.V21
{
[SpineImplementation(2, 1)]
internal sealed class SpineObjectData21 : 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 SpineObjectData21(string skelPath, string atlasPath) : base(skelPath, atlasPath)
{
// 加载 atlas
try { _atlas = new Atlas(atlasPath, _textureLoader); }
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
try
{
if (Utf8Validator.IsUtf8(skelPath))
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
else
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
catch (Exception ex)
{
_atlas.Dispose();
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
}
// 加载动画数据
_animationStateData = new AnimationStateData(_skeletonData);
// 整理皮肤和附件
Dictionary<string, Dictionary<string, IAttachment>> slotAttachments = [];
List<ISkin> skins = [];
Dictionary<string, ISkin> skinsByName = [];
foreach (var s in _skeletonData.Skins)
{
var skin = new Skin21(s);
skins.Add(skin);
skinsByName[s.Name] = skin;
foreach (var (k, att) in s.Attachments)
{
var slotName = _skeletonData.Slots[k.Key].Name;
if (!slotAttachments.TryGetValue(slotName, out var attachments))
slotAttachments[slotName] = attachments = [];
attachments[att.Name] = att switch
{
RegionAttachment regionAtt => new RegionAttachment21(regionAtt),
MeshAttachment meshAtt => new MeshAttachment21(meshAtt),
SkinnedMeshAttachment skMeshAtt => new SkinnedMeshAttachment21(skMeshAtt),
BoundingBoxAttachment bbAtt => new BoundingBoxAttachment21(bbAtt),
_ => 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 Animation21(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 Skeleton21(new(_skeletonData), this);
public override IAnimationState CreateAnimationState() => new AnimationState21(new(_animationStateData), this);
public override ISkeletonClipping CreateSkeletonClipping() => new SkeletonClipping21();
public override ISkin CreateSkin(string name) => new Skin21(name);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,229 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36
{
internal sealed class AnimationState36(AnimationState innerObject, SpineObjectData36 data) : IAnimationState
{
private readonly AnimationState _o = innerObject;
private readonly SpineObjectData36 _data = data;
private readonly Dictionary<TrackEntry, TrackEntry36> _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 Skeleton36 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 Animation36 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 Animation36 anime)
return GetTrackEntry(_o.AddAnimation(trackIndex, anime.InnerObject, loop, delay));
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
}
public ITrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay)
=> GetTrackEntry(_o.AddEmptyAnimation(trackIndex, mixDuration, delay));
public override string ToString() => _o.ToString();
}
}

View File

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

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36.Attachments
{
internal sealed class BoundingBoxAttachment36(BoundingBoxAttachment innerObject) :
Attachment36(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 Slot36 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(Slot36)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36.Attachments
{
internal sealed class ClippingAttachment36(ClippingAttachment innerObject) :
Attachment36(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 Slot36 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(Slot36)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36.Attachments
{
internal sealed class MeshAttachment36(MeshAttachment innerObject) :
Attachment36(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 Slot36 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(Slot36)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
public int[] Triangles => _o.Triangles;
public int HullLength => _o.HullLength;
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36.Attachments
{
internal sealed class PathAttachment36(PathAttachment innerObject) :
Attachment36(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 Slot36 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(Slot36)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36.Attachments
{
internal sealed class PointAttachment36(PointAttachment innerObject) :
Attachment36(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 Slot36 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(Slot36)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36.Attachments
{
internal sealed class RegionAttachment36(RegionAttachment innerObject) :
Attachment36(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 Slot36 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(Slot36)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
}
}

View File

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

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Frozen;
using System.Collections.Immutable;
using Spine.SpineWrappers;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36
{
internal sealed class Skeleton36 : ISkeleton
{
private readonly Skeleton _o;
private readonly SpineObjectData36 _data;
private readonly ImmutableArray<IBone> _bones;
private readonly FrozenDictionary<string, IBone> _bonesByName;
private readonly ImmutableArray<ISlot> _slots;
private readonly FrozenDictionary<string, ISlot> _slotsByName;
private Skin36? _skin;
public Skeleton36(Skeleton innerObject, SpineObjectData36 data)
{
_o = innerObject;
_data = data;
List<Bone36> bones = [];
Dictionary<string, IBone> bonesByName = [];
foreach (var b in _o.Bones)
{
var bone = new Bone36(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<Slot36> slots = [];
Dictionary<string, ISlot> slotsByName = [];
foreach (var s in _o.Slots)
{
var slot = new Slot36(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 Skin36 sk)
{
_o.Skin = sk.InnerObject;
_skin = sk;
return;
}
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
}
}
public IEnumerable<ISlot> IterDrawOrder() => _o.DrawOrder.Select(s => _slots[s.Data.Index]);
public void UpdateCache() => _o.UpdateCache();
public void UpdateWorldTransform(ISkeleton.Physics physics) => _o.UpdateWorldTransform();
public void SetToSetupPose() => _o.SetToSetupPose();
public void SetBonesToSetupPose() => _o.SetBonesToSetupPose();
public void SetSlotsToSetupPose() => _o.SetSlotsToSetupPose();
public void Update(float delta) => _o.Update(delta);
public void GetBounds(out float x, out float y, out float w, out float h)
{
float[] _ = [];
_o.GetBounds(out x, out y, out w, out h, ref _);
}
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,56 @@
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using Spine.Utils;
using SpineRuntime36;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V36
{
internal sealed class SkeletonClipping36 : 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 Slot36 st && clippingAttachment is Attachments.ClippingAttachment36 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 Slot36 st)
{
_o.ClipEnd(st.InnerObject);
return;
}
throw new ArgumentException($"Received {slot.GetType().Name}", nameof(slot));
}
public void ClipEnd() => _o.ClipEnd();
public override string ToString() => _o.ToString();
}
}

View File

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

View File

@@ -0,0 +1,78 @@
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 SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36
{
internal sealed class Slot36 : ISlot
{
private readonly Slot _o;
private readonly SpineObjectData36 _data;
private readonly Bone36 _bone;
private readonly SFML.Graphics.BlendMode _blendMode;
public Slot36(Slot innerObject, SpineObjectData36 data, Bone36 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.Attachment36 att)
{
_o.Attachment = att.InnerObject;
return;
}
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
}
}
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,119 @@
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 SpineRuntime36;
using Spine.Implementations.SpineWrappers.V36.Attachments;
namespace Spine.Implementations.SpineWrappers.V36
{
[SpineImplementation(3, 6)]
internal sealed class SpineObjectData36 : 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 SpineObjectData36(string skelPath, string atlasPath) : base(skelPath, atlasPath)
{
// 加载 atlas
try { _atlas = new Atlas(atlasPath, _textureLoader); }
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
try
{
if (Utf8Validator.IsUtf8(skelPath))
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
else
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
catch (Exception ex)
{
_atlas.Dispose();
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
}
// 加载动画数据
_animationStateData = new AnimationStateData(_skeletonData);
// 整理皮肤和附件
Dictionary<string, Dictionary<string, IAttachment>> slotAttachments = [];
List<ISkin> skins = [];
Dictionary<string, ISkin> skinsByName = [];
foreach (var s in _skeletonData.Skins)
{
var skin = new Skin36(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 RegionAttachment36(regionAtt),
MeshAttachment meshAtt => new MeshAttachment36(meshAtt),
ClippingAttachment clipAtt => new ClippingAttachment36(clipAtt),
BoundingBoxAttachment bbAtt => new BoundingBoxAttachment36(bbAtt),
PathAttachment pathAtt => new PathAttachment36(pathAtt),
PointAttachment ptAtt => new PointAttachment36(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 Animation36(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 Skeleton36(new(_skeletonData), this);
public override IAnimationState CreateAnimationState() => new AnimationState36(new(_animationStateData), this);
public override ISkeletonClipping CreateSkeletonClipping() => new SkeletonClipping36();
public override ISkin CreateSkin(string name) => new Skin36(name);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,229 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37
{
internal sealed class AnimationState37(AnimationState innerObject, SpineObjectData37 data) : IAnimationState
{
private readonly AnimationState _o = innerObject;
private readonly SpineObjectData37 _data = data;
private readonly Dictionary<TrackEntry, TrackEntry37> _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 Skeleton37 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 Animation37 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 Animation37 anime)
return GetTrackEntry(_o.AddAnimation(trackIndex, anime.InnerObject, loop, delay));
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
}
public ITrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay)
=> GetTrackEntry(_o.AddEmptyAnimation(trackIndex, mixDuration, delay));
public override string ToString() => _o.ToString();
}
}

View File

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

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37.Attachments
{
internal sealed class BoundingBoxAttachment37(BoundingBoxAttachment innerObject) :
Attachment37(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 Slot37 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(Slot37)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37.Attachments
{
internal sealed class ClippingAttachment37(ClippingAttachment innerObject) :
Attachment37(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 Slot37 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(Slot37)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37.Attachments
{
internal sealed class MeshAttachment37(MeshAttachment innerObject) :
Attachment37(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 Slot37 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(Slot37)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
public int[] Triangles => _o.Triangles;
public int HullLength => _o.HullLength;
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37.Attachments
{
internal sealed class PathAttachment37(PathAttachment innerObject) :
Attachment37(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 Slot37 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(Slot37)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37.Attachments
{
internal sealed class PointAttachment37(PointAttachment innerObject) :
Attachment37(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 Slot37 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(Slot37)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37.Attachments
{
internal sealed class RegionAttachment37(RegionAttachment innerObject) :
Attachment37(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 Slot37 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(Slot37)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
}
}

View File

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

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Frozen;
using System.Collections.Immutable;
using Spine.SpineWrappers;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37
{
internal sealed class Skeleton37 : ISkeleton
{
private readonly Skeleton _o;
private readonly SpineObjectData37 _data;
private readonly ImmutableArray<IBone> _bones;
private readonly FrozenDictionary<string, IBone> _bonesByName;
private readonly ImmutableArray<ISlot> _slots;
private readonly FrozenDictionary<string, ISlot> _slotsByName;
private Skin37? _skin;
public Skeleton37(Skeleton innerObject, SpineObjectData37 data)
{
_o = innerObject;
_data = data;
List<Bone37> bones = [];
Dictionary<string, IBone> bonesByName = [];
foreach (var b in _o.Bones)
{
var bone = new Bone37(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<Slot37> slots = [];
Dictionary<string, ISlot> slotsByName = [];
foreach (var s in _o.Slots)
{
var slot = new Slot37(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 Skin37 sk)
{
_o.Skin = sk.InnerObject;
_skin = sk;
return;
}
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
}
}
public IEnumerable<ISlot> IterDrawOrder() => _o.DrawOrder.Select(s => _slots[s.Data.Index]);
public void UpdateCache() => _o.UpdateCache();
public void UpdateWorldTransform(ISkeleton.Physics physics) => _o.UpdateWorldTransform();
public void SetToSetupPose() => _o.SetToSetupPose();
public void SetBonesToSetupPose() => _o.SetBonesToSetupPose();
public void SetSlotsToSetupPose() => _o.SetSlotsToSetupPose();
public void Update(float delta) => _o.Update(delta);
public void GetBounds(out float x, out float y, out float w, out float h)
{
float[] _ = [];
_o.GetBounds(out x, out y, out w, out h, ref _);
}
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,56 @@
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using Spine.Utils;
using SpineRuntime37;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V37
{
internal sealed class SkeletonClipping37 : 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 Slot37 st && clippingAttachment is Attachments.ClippingAttachment37 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 Slot37 st)
{
_o.ClipEnd(st.InnerObject);
return;
}
throw new ArgumentException($"Received {slot.GetType().Name}", nameof(slot));
}
public void ClipEnd() => _o.ClipEnd();
public override string ToString() => _o.ToString();
}
}

View File

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

View File

@@ -0,0 +1,78 @@
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 SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37
{
internal sealed class Slot37 : ISlot
{
private readonly Slot _o;
private readonly SpineObjectData37 _data;
private readonly Bone37 _bone;
private readonly SFML.Graphics.BlendMode _blendMode;
public Slot37(Slot innerObject, SpineObjectData37 data, Bone37 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.Attachment37 att)
{
_o.Attachment = att.InnerObject;
return;
}
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
}
}
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,119 @@
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 SpineRuntime37;
using Spine.Implementations.SpineWrappers.V37.Attachments;
namespace Spine.Implementations.SpineWrappers.V37
{
[SpineImplementation(3, 7)]
internal sealed class SpineObjectData37 : 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 SpineObjectData37(string skelPath, string atlasPath) : base(skelPath, atlasPath)
{
// 加载 atlas
try { _atlas = new Atlas(atlasPath, _textureLoader); }
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
try
{
if (Utf8Validator.IsUtf8(skelPath))
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
else
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
catch (Exception ex)
{
_atlas.Dispose();
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
}
// 加载动画数据
_animationStateData = new AnimationStateData(_skeletonData);
// 整理皮肤和附件
Dictionary<string, Dictionary<string, IAttachment>> slotAttachments = [];
List<ISkin> skins = [];
Dictionary<string, ISkin> skinsByName = [];
foreach (var s in _skeletonData.Skins)
{
var skin = new Skin37(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 RegionAttachment37(regionAtt),
MeshAttachment meshAtt => new MeshAttachment37(meshAtt),
ClippingAttachment clipAtt => new ClippingAttachment37(clipAtt),
BoundingBoxAttachment bbAtt => new BoundingBoxAttachment37(bbAtt),
PathAttachment pathAtt => new PathAttachment37(pathAtt),
PointAttachment ptAtt => new PointAttachment37(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 Animation37(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 Skeleton37(new(_skeletonData), this);
public override IAnimationState CreateAnimationState() => new AnimationState37(new(_animationStateData), this);
public override ISkeletonClipping CreateSkeletonClipping() => new SkeletonClipping37();
public override ISkin CreateSkin(string name) => new Skin37(name);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,229 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using SpineRuntime38;
namespace Spine.Implementations.SpineWrappers.V38
{
internal sealed class AnimationState38(AnimationState innerObject, SpineObjectData38 data) : IAnimationState
{
private readonly AnimationState _o = innerObject;
private readonly SpineObjectData38 _data = data;
private readonly Dictionary<TrackEntry, TrackEntry38> _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 Skeleton38 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 Animation38 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 Animation38 anime)
return GetTrackEntry(_o.AddAnimation(trackIndex, anime.InnerObject, loop, delay));
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
}
public ITrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay)
=> GetTrackEntry(_o.AddEmptyAnimation(trackIndex, mixDuration, delay));
public override string ToString() => _o.ToString();
}
}

View File

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

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime38;
using SpineRuntime38.Attachments;
namespace Spine.Implementations.SpineWrappers.V38.Attachments
{
internal sealed class BoundingBoxAttachment38(BoundingBoxAttachment innerObject) :
Attachment38(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 Slot38 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(Slot38)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime38;
using SpineRuntime38.Attachments;
namespace Spine.Implementations.SpineWrappers.V38.Attachments
{
internal sealed class ClippingAttachment38(ClippingAttachment innerObject) :
Attachment38(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 Slot38 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(Slot38)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime38;
using SpineRuntime38.Attachments;
namespace Spine.Implementations.SpineWrappers.V38.Attachments
{
internal sealed class MeshAttachment38(MeshAttachment innerObject) :
Attachment38(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 Slot38 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(Slot38)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
public int[] Triangles => _o.Triangles;
public int HullLength => _o.HullLength;
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime38;
using SpineRuntime38.Attachments;
namespace Spine.Implementations.SpineWrappers.V38.Attachments
{
internal sealed class PathAttachment38(PathAttachment innerObject) :
Attachment38(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 Slot38 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(Slot38)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime38;
using SpineRuntime38.Attachments;
namespace Spine.Implementations.SpineWrappers.V38.Attachments
{
internal sealed class PointAttachment38(PointAttachment innerObject) :
Attachment38(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 Slot38 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(Slot38)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime38;
using SpineRuntime38.Attachments;
namespace Spine.Implementations.SpineWrappers.V38.Attachments
{
internal sealed class RegionAttachment38(RegionAttachment innerObject) :
Attachment38(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 Slot38 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(Slot38)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using SpineRuntime38;
namespace Spine.Implementations.SpineWrappers.V38
{
internal sealed class Bone38(Bone innerObject, Bone38? parent = null) : IBone
{
private readonly Bone _o = innerObject;
private readonly Bone38? _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 => _o.Active;
public float Length => _o.Data.Length;
public float WorldX => _o.WorldX;
public float WorldY => _o.WorldY;
public float A => _o.A;
public float B => _o.B;
public float C => _o.C;
public float D => _o.D;
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Frozen;
using System.Collections.Immutable;
using Spine.SpineWrappers;
using SpineRuntime38;
namespace Spine.Implementations.SpineWrappers.V38
{
internal sealed class Skeleton38 : ISkeleton
{
private readonly Skeleton _o;
private readonly SpineObjectData38 _data;
private readonly ImmutableArray<IBone> _bones;
private readonly FrozenDictionary<string, IBone> _bonesByName;
private readonly ImmutableArray<ISlot> _slots;
private readonly FrozenDictionary<string, ISlot> _slotsByName;
private Skin38? _skin;
public Skeleton38(Skeleton innerObject, SpineObjectData38 data)
{
_o = innerObject;
_data = data;
List<Bone38> bones = [];
Dictionary<string, IBone> bonesByName = [];
foreach (var b in _o.Bones)
{
var bone = new Bone38(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<Slot38> slots = [];
Dictionary<string, ISlot> slotsByName = [];
foreach (var s in _o.Slots)
{
var slot = new Slot38(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 Skin38 sk)
{
_o.Skin = sk.InnerObject;
_skin = sk;
return;
}
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
}
}
public IEnumerable<ISlot> IterDrawOrder() => _o.DrawOrder.Select(s => _slots[s.Data.Index]);
public void UpdateCache() => _o.UpdateCache();
public void UpdateWorldTransform(ISkeleton.Physics physics) => _o.UpdateWorldTransform();
public void SetToSetupPose() => _o.SetToSetupPose();
public void SetBonesToSetupPose() => _o.SetBonesToSetupPose();
public void SetSlotsToSetupPose() => _o.SetSlotsToSetupPose();
public void Update(float delta) => _o.Update(delta);
public void GetBounds(out float x, out float y, out float w, out float h)
{
float[] _ = [];
_o.GetBounds(out x, out y, out w, out h, ref _);
}
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,56 @@
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using Spine.Utils;
using SpineRuntime38;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V38
{
internal sealed class SkeletonClipping38 : 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 Slot38 st && clippingAttachment is Attachments.ClippingAttachment38 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 Slot38 st)
{
_o.ClipEnd(st.InnerObject);
return;
}
throw new ArgumentException($"Received {slot.GetType().Name}", nameof(slot));
}
public void ClipEnd() => _o.ClipEnd();
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using SpineRuntime38;
namespace Spine.Implementations.SpineWrappers.V38
{
internal sealed class Skin38 : ISkin
{
private readonly Skin _o;
/// <summary>
/// 使用指定名字创建空皮肤
/// </summary>
public Skin38(string name) => _o = new(name);
/// <summary>
/// 包装已有皮肤对象
/// </summary>
public Skin38(Skin innerObject) => _o = innerObject;
public Skin InnerObject => _o;
public string Name => _o.Name;
public void AddSkin(ISkin skin)
{
if (skin is Skin38 sk)
{
_o.AddSkin(sk._o);
return;
}
throw new ArgumentException($"Received {skin.GetType().Name}", nameof(skin));
}
public void Clear() => _o.Clear();
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,79 @@
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 SpineRuntime38;
using SpineRuntime38.Attachments;
namespace Spine.Implementations.SpineWrappers.V38
{
internal sealed class Slot38 : ISlot
{
private readonly Slot _o;
private readonly SpineObjectData38 _data;
private readonly Bone38 _bone;
private readonly SFML.Graphics.BlendMode _blendMode;
public Slot38(Slot innerObject, SpineObjectData38 data, Bone38 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.Attachment38 att)
{
_o.Attachment = att.InnerObject;
return;
}
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
}
}
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,120 @@
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 SpineRuntime38;
using SpineRuntime38.Attachments;
using Spine.Implementations.SpineWrappers.V38.Attachments;
namespace Spine.Implementations.SpineWrappers.V38
{
[SpineImplementation(3, 8)]
internal sealed class SpineObjectData38 : 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 SpineObjectData38(string skelPath, string atlasPath) : base(skelPath, atlasPath)
{
// 加载 atlas
try { _atlas = new Atlas(atlasPath, _textureLoader); }
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
try
{
if (Utf8Validator.IsUtf8(skelPath))
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
else
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
catch (Exception ex)
{
_atlas.Dispose();
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
}
// 加载动画数据
_animationStateData = new AnimationStateData(_skeletonData);
// 整理皮肤和附件
Dictionary<string, Dictionary<string, IAttachment>> slotAttachments = [];
List<ISkin> skins = [];
Dictionary<string, ISkin> skinsByName = [];
foreach (var s in _skeletonData.Skins)
{
var skin = new Skin38(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 RegionAttachment38(regionAtt),
MeshAttachment meshAtt => new MeshAttachment38(meshAtt),
ClippingAttachment clipAtt => new ClippingAttachment38(clipAtt),
BoundingBoxAttachment bbAtt => new BoundingBoxAttachment38(bbAtt),
PathAttachment pathAtt => new PathAttachment38(pathAtt),
PointAttachment ptAtt => new PointAttachment38(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 Animation38(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 Skeleton38(new(_skeletonData), this);
public override IAnimationState CreateAnimationState() => new AnimationState38(new(_animationStateData), this);
public override ISkeletonClipping CreateSkeletonClipping() => new SkeletonClipping38();
public override ISkin CreateSkin(string name) => new Skin38(name);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,229 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using SpineRuntime40;
namespace Spine.Implementations.SpineWrappers.V40
{
internal sealed class AnimationState40(AnimationState innerObject, SpineObjectData40 data) : IAnimationState
{
private readonly AnimationState _o = innerObject;
private readonly SpineObjectData40 _data = data;
private readonly Dictionary<TrackEntry, TrackEntry40> _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 Skeleton40 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 Animation40 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 Animation40 anime)
return GetTrackEntry(_o.AddAnimation(trackIndex, anime.InnerObject, loop, delay));
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
}
public ITrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay)
=> GetTrackEntry(_o.AddEmptyAnimation(trackIndex, mixDuration, delay));
public override string ToString() => _o.ToString();
}
}

View File

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

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime40;
namespace Spine.Implementations.SpineWrappers.V40.Attachments
{
internal sealed class BoundingBoxAttachment40(BoundingBoxAttachment innerObject) :
Attachment40(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 Slot40 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(Slot40)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime40;
namespace Spine.Implementations.SpineWrappers.V40.Attachments
{
internal sealed class ClippingAttachment40(ClippingAttachment innerObject) :
Attachment40(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 Slot40 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(Slot40)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime40;
namespace Spine.Implementations.SpineWrappers.V40.Attachments
{
internal sealed class MeshAttachment40(MeshAttachment innerObject) :
Attachment40(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 Slot40 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(Slot40)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
public int[] Triangles => _o.Triangles;
public int HullLength => _o.HullLength;
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime40;
namespace Spine.Implementations.SpineWrappers.V40.Attachments
{
internal sealed class PathAttachment40(PathAttachment innerObject) :
Attachment40(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 Slot40 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(Slot40)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime40;
namespace Spine.Implementations.SpineWrappers.V40.Attachments
{
internal sealed class PointAttachment40(PointAttachment innerObject) :
Attachment40(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 Slot40 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(Slot40)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using SpineRuntime40;
namespace Spine.Implementations.SpineWrappers.V40.Attachments
{
internal sealed class RegionAttachment40(RegionAttachment innerObject) :
Attachment40(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 Slot40 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(Slot40)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using SpineRuntime40;
namespace Spine.Implementations.SpineWrappers.V40
{
internal sealed class Bone40(Bone innerObject, Bone40? parent = null) : IBone
{
private readonly Bone _o = innerObject;
private readonly Bone40? _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 => _o.Active;
public float Length => _o.Data.Length;
public float WorldX => _o.WorldX;
public float WorldY => _o.WorldY;
public float A => _o.A;
public float B => _o.B;
public float C => _o.C;
public float D => _o.D;
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Frozen;
using System.Collections.Immutable;
using Spine.SpineWrappers;
using SpineRuntime40;
namespace Spine.Implementations.SpineWrappers.V40
{
internal sealed class Skeleton40 : ISkeleton
{
private readonly Skeleton _o;
private readonly SpineObjectData40 _data;
private readonly ImmutableArray<IBone> _bones;
private readonly FrozenDictionary<string, IBone> _bonesByName;
private readonly ImmutableArray<ISlot> _slots;
private readonly FrozenDictionary<string, ISlot> _slotsByName;
private Skin40? _skin;
public Skeleton40(Skeleton innerObject, SpineObjectData40 data)
{
_o = innerObject;
_data = data;
List<Bone40> bones = [];
Dictionary<string, IBone> bonesByName = [];
foreach (var b in _o.Bones)
{
var bone = new Bone40(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<Slot40> slots = [];
Dictionary<string, ISlot> slotsByName = [];
foreach (var s in _o.Slots)
{
var slot = new Slot40(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 Skin40 sk)
{
_o.Skin = sk.InnerObject;
_skin = sk;
return;
}
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
}
}
public IEnumerable<ISlot> IterDrawOrder() => _o.DrawOrder.Select(s => _slots[s.Data.Index]);
public void UpdateCache() => _o.UpdateCache();
public void UpdateWorldTransform(ISkeleton.Physics physics) => _o.UpdateWorldTransform();
public void SetToSetupPose() => _o.SetToSetupPose();
public void SetBonesToSetupPose() => _o.SetBonesToSetupPose();
public void SetSlotsToSetupPose() => _o.SetSlotsToSetupPose();
public void Update(float delta) => _o.Update(delta);
public void GetBounds(out float x, out float y, out float w, out float h)
{
float[] _ = [];
_o.GetBounds(out x, out y, out w, out h, ref _);
}
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,56 @@
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using Spine.Utils;
using SpineRuntime40;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V40
{
internal sealed class SkeletonClipping40 : 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 Slot40 st && clippingAttachment is Attachments.ClippingAttachment40 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 Slot40 st)
{
_o.ClipEnd(st.InnerObject);
return;
}
throw new ArgumentException($"Received {slot.GetType().Name}", nameof(slot));
}
public void ClipEnd() => _o.ClipEnd();
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using SpineRuntime40;
namespace Spine.Implementations.SpineWrappers.V40
{
internal sealed class Skin40 : ISkin
{
private readonly Skin _o;
/// <summary>
/// 使用指定名字创建空皮肤
/// </summary>
public Skin40(string name) => _o = new(name);
/// <summary>
/// 包装已有皮肤对象
/// </summary>
public Skin40(Skin innerObject) => _o = innerObject;
public Skin InnerObject => _o;
public string Name => _o.Name;
public void AddSkin(ISkin skin)
{
if (skin is Skin40 sk)
{
_o.AddSkin(sk._o);
return;
}
throw new ArgumentException($"Received {skin.GetType().Name}", nameof(skin));
}
public void Clear() => _o.Clear();
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,78 @@
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 SpineRuntime40;
namespace Spine.Implementations.SpineWrappers.V40
{
internal sealed class Slot40 : ISlot
{
private readonly Slot _o;
private readonly SpineObjectData40 _data;
private readonly Bone40 _bone;
private readonly SFML.Graphics.BlendMode _blendMode;
public Slot40(Slot innerObject, SpineObjectData40 data, Bone40 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.Attachment40 att)
{
_o.Attachment = att.InnerObject;
return;
}
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
}
}
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,121 @@
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 SpineRuntime40;
using Spine.Implementations.SpineWrappers.V40.Attachments;
namespace Spine.Implementations.SpineWrappers.V40
{
[SpineImplementation(4, 0)]
internal sealed class SpineObjectData40 : 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 SpineObjectData40(string skelPath, string atlasPath) : base(skelPath, atlasPath)
{
// 加载 atlas
try { _atlas = new Atlas(atlasPath, _textureLoader); }
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
// 加载 skel
try
{
if (Utf8Validator.IsUtf8(skelPath))
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
else
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
catch (Exception ex)
{
_atlas.Dispose();
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
}
// 加载动画数据
_animationStateData = new AnimationStateData(_skeletonData);
// 整理皮肤和附件
Dictionary<string, Dictionary<string, IAttachment>> slotAttachments = [];
List<ISkin> skins = [];
Dictionary<string, ISkin> skinsByName = [];
foreach (var s in _skeletonData.Skins)
{
var skin = new Skin40(s);
skins.Add(skin);
skinsByName[s.Name] = skin;
foreach (var entry in s.Attachments)
{
var att = entry.Attachment;
var slotName = _skeletonData.Slots.Items[entry.SlotIndex].Name;
if (!slotAttachments.TryGetValue(slotName, out var attachments))
slotAttachments[slotName] = attachments = [];
attachments[att.Name] = att switch
{
RegionAttachment regionAtt => new RegionAttachment40(regionAtt),
MeshAttachment meshAtt => new MeshAttachment40(meshAtt),
ClippingAttachment clipAtt => new ClippingAttachment40(clipAtt),
BoundingBoxAttachment bbAtt => new BoundingBoxAttachment40(bbAtt),
PathAttachment pathAtt => new PathAttachment40(pathAtt),
PointAttachment ptAtt => new PointAttachment40(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 Animation40(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 Skeleton40(new(_skeletonData), this);
public override IAnimationState CreateAnimationState() => new AnimationState40(new(_animationStateData), this);
public override ISkeletonClipping CreateSkeletonClipping() => new SkeletonClipping40();
public override ISkin CreateSkin(string name) => new Skin40(name);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,229 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using SpineRuntime41;
namespace Spine.Implementations.SpineWrappers.V41
{
internal sealed class AnimationState41(AnimationState innerObject, SpineObjectData41 data) : IAnimationState
{
private readonly AnimationState _o = innerObject;
private readonly SpineObjectData41 _data = data;
private readonly Dictionary<TrackEntry, TrackEntry41> _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 Skeleton41 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 Animation41 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 Animation41 anime)
return GetTrackEntry(_o.AddAnimation(trackIndex, anime.InnerObject, loop, delay));
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
}
public ITrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay)
=> GetTrackEntry(_o.AddEmptyAnimation(trackIndex, mixDuration, delay));
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using SpineRuntime41;
namespace Spine.Implementations.SpineWrappers.V41.Attachments
{
internal abstract class Attachment41(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();
}
}

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