Compare commits
399 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7017f8984 | ||
|
|
7b58eeafe3 | ||
|
|
0e2eb3fbb1 | ||
|
|
1f65dfb854 | ||
|
|
522bd26581 | ||
|
|
8849ddec54 | ||
|
|
5039bc666f | ||
|
|
7bd3e3669b | ||
|
|
0dcf8c5577 | ||
|
|
9a62d7eb53 | ||
|
|
5f189a066d | ||
|
|
2287542522 | ||
|
|
333c5e9981 | ||
|
|
0e7e7dd5d9 | ||
|
|
6fe639d6dd | ||
|
|
6114d4c954 | ||
|
|
7ec938b415 | ||
|
|
b3010360b4 | ||
|
|
125ce6fa86 | ||
|
|
7f61ebda78 | ||
|
|
1092f37a02 | ||
|
|
9be77ba8bd | ||
|
|
28fd11cf3e | ||
|
|
16d4388f3e | ||
|
|
42cb782a96 | ||
|
|
fffe69c49f | ||
|
|
707bdf7d33 | ||
|
|
54f9a054cf | ||
|
|
550dafb2c2 | ||
|
|
5aaca437af | ||
|
|
668b264836 | ||
|
|
6fbf902756 | ||
|
|
f7940d1223 | ||
|
|
10008166ac | ||
|
|
44b5bf8613 | ||
|
|
39dae5cdb6 | ||
|
|
d7f7c7116c | ||
|
|
17257a0ffe | ||
|
|
d0f629d9ba | ||
|
|
cd652a72a1 | ||
|
|
828ff30dbf | ||
|
|
f452fe8a71 | ||
|
|
15e29a3b8a | ||
|
|
5c6e98f5e1 | ||
|
|
ef06073119 | ||
|
|
bca8b0ad85 | ||
|
|
4983b1fa88 | ||
|
|
f6b6d9f0e7 | ||
|
|
12a168df92 | ||
|
|
e06d2012d6 | ||
|
|
faed2448eb | ||
|
|
a80aed54a0 | ||
|
|
3f9c4dfe99 | ||
|
|
3c91f335fc | ||
|
|
1ea4586360 | ||
|
|
45e08b5b60 | ||
|
|
43ce5a5288 | ||
|
|
887315ceda | ||
|
|
30b5c8dd74 | ||
|
|
ef7ef76a59 | ||
|
|
a9834bcc13 | ||
|
|
ec2752464d | ||
|
|
7b6a9b2a0f | ||
|
|
8eda4f0849 | ||
|
|
cde25214ae | ||
|
|
9f1f66776c | ||
|
|
c93b78528d | ||
|
|
e567e61383 | ||
|
|
7c3184d88a | ||
|
|
e6f38657a3 | ||
|
|
ebcd61a9a2 | ||
|
|
179fbfe84f | ||
|
|
79441abff3 | ||
|
|
c3db8cd4ea | ||
|
|
27c58ffd87 | ||
|
|
244249db2b | ||
|
|
33b937da87 | ||
|
|
058834e8a6 | ||
|
|
b134af3727 | ||
|
|
6d2eafb4f1 | ||
|
|
9f618804df | ||
|
|
61a3a62b65 | ||
|
|
ced25dec87 | ||
|
|
60d5f3361a | ||
|
|
c8ee4cf0c9 | ||
|
|
177434e503 | ||
|
|
58b45cde31 | ||
|
|
ff87030894 | ||
|
|
7635adf637 | ||
|
|
ca7a40044c | ||
|
|
ce5be30f1d | ||
|
|
1cb8f077f5 | ||
|
|
61794cf784 | ||
|
|
045b191e4d | ||
|
|
6116f6a4be | ||
|
|
ca7e94a306 | ||
|
|
165be38cfe | ||
|
|
f1fb5fe5e1 | ||
|
|
17554d3a8e | ||
|
|
054ac3939b | ||
|
|
9566c0a6f5 | ||
|
|
b8509ccb69 | ||
|
|
1e043e4faa | ||
|
|
5b1c177f58 | ||
|
|
794de783db | ||
|
|
7f6aa26986 | ||
|
|
fcd809fda5 | ||
|
|
71ff8007fb | ||
|
|
a8261f4f58 | ||
|
|
bb6a6f3664 | ||
|
|
9c6af07d32 | ||
|
|
afc011adbb | ||
|
|
da42c14d35 | ||
|
|
a7438e2026 | ||
|
|
47e5314bb3 | ||
|
|
1978c1da11 | ||
|
|
914c9d0ea3 | ||
|
|
7134aebb7f | ||
|
|
abb32e9ed2 | ||
|
|
2c77e385c5 | ||
|
|
ba0f5ac124 | ||
|
|
c4f17e3f06 | ||
|
|
b802ec252a | ||
|
|
68779caab0 | ||
|
|
5d819114d0 | ||
|
|
46b3937236 | ||
|
|
6803b8cf4a | ||
|
|
bd9c5a176b | ||
|
|
76c1d96c87 | ||
|
|
304af805cb | ||
|
|
027d3af619 | ||
|
|
c612c01ac7 | ||
|
|
cd7855a877 | ||
|
|
cdd81e0bfb | ||
|
|
750a8b8aff | ||
|
|
cd86155878 | ||
|
|
16739c39d6 | ||
|
|
c7971a9829 | ||
|
|
44c4fc4b21 | ||
|
|
6f1c8e3320 | ||
|
|
8f818416ba | ||
|
|
de6858ca48 | ||
|
|
3fd3d2a378 | ||
|
|
706c9125e6 | ||
|
|
5f026b000c | ||
|
|
0b0d036f08 | ||
|
|
6b9017d535 | ||
|
|
5eb47e33ac | ||
|
|
4d31335da0 | ||
|
|
0b5e76a448 | ||
|
|
775268c01a | ||
|
|
b0b1c85047 | ||
|
|
5f08fc6695 | ||
|
|
2de3bdf12b | ||
|
|
3a424c7dc1 | ||
|
|
c3e2b37072 | ||
|
|
65bd11a346 | ||
|
|
e6e7fc539f | ||
|
|
6522d415b7 | ||
|
|
378c66a333 | ||
|
|
07204417a5 | ||
|
|
c9c909cdf9 | ||
|
|
a9f59a4d2f | ||
|
|
1d2513cef5 | ||
|
|
febb797ae2 | ||
|
|
68d279a7c3 | ||
|
|
d2d8b7955c | ||
|
|
2a55fd9c36 | ||
|
|
695d3c0735 | ||
|
|
ce95db469b | ||
|
|
5d187cf80f | ||
|
|
e704ebc224 | ||
|
|
ee36f8981c | ||
|
|
09dd220abf | ||
|
|
15bc2dc3b8 | ||
|
|
1deb74eca9 | ||
|
|
de76ce64ab | ||
|
|
94b4ba33e6 | ||
|
|
7ce8a115f4 | ||
|
|
c036a4bb45 | ||
|
|
aa62f30b05 | ||
|
|
3d967c9812 | ||
|
|
e87e9efb99 | ||
|
|
8c1f6fb4a6 | ||
|
|
df82ed8a00 | ||
|
|
d01e3920ba | ||
|
|
777cd5ea3f | ||
|
|
3b73aea5c0 | ||
|
|
168f7a8173 | ||
|
|
04437e2de2 | ||
|
|
2ec83b2e87 | ||
|
|
90bfaa7b56 | ||
|
|
2ae175abd0 | ||
|
|
e2a84d8f88 | ||
|
|
b6f9cd0c7c | ||
|
|
61b7b90722 | ||
|
|
093c159753 | ||
|
|
32d36c0757 | ||
|
|
94dabebf2b | ||
|
|
8e875d4f7e | ||
|
|
86c383f2cf | ||
|
|
b404d8e79a | ||
|
|
d32b480ef2 | ||
|
|
3654825f27 | ||
|
|
d7231e8a09 | ||
|
|
98161aaf2e | ||
|
|
7a942b16bc | ||
|
|
067719c69b | ||
|
|
f3fce53b91 | ||
|
|
e35903f436 | ||
|
|
64cfe5fdd7 | ||
|
|
dbe586cff8 | ||
|
|
3104733db0 | ||
|
|
9d4bdd1028 | ||
|
|
f8030b1645 | ||
|
|
0a999ceb41 | ||
|
|
64bd9907cb | ||
|
|
580eaf990d | ||
|
|
5ab232a961 | ||
|
|
e596cd7ea4 | ||
|
|
05c47a4daa | ||
|
|
5a8783b5f4 | ||
|
|
08bc171a72 | ||
|
|
7372f5fe08 | ||
|
|
6f032bdd05 | ||
|
|
153d3603d2 | ||
|
|
95261e6907 | ||
|
|
17b344376d | ||
|
|
0ed4e44878 | ||
|
|
b42c1832f0 | ||
|
|
058534ba67 | ||
|
|
204dcd6498 | ||
|
|
2c846c0db9 | ||
|
|
2faeb044e0 | ||
|
|
09c8e4f779 | ||
|
|
6994fa6be8 | ||
|
|
cc7beb7670 | ||
|
|
510653732d | ||
|
|
93e8178d67 | ||
|
|
cebc4864cc | ||
|
|
6ad0449376 | ||
|
|
c33c977326 | ||
|
|
f0299d365a | ||
|
|
6ecdca73f5 | ||
|
|
af6a709b2c | ||
|
|
d5c27450ef | ||
|
|
d10269fb07 | ||
|
|
53d987476e | ||
|
|
8b7866d37f | ||
|
|
bb529729b6 | ||
|
|
b7735d9ba8 | ||
|
|
ce744e2b84 | ||
|
|
631c92da3f | ||
|
|
b7063804e9 | ||
|
|
75d47c8419 | ||
|
|
114fb05e80 | ||
|
|
1fec65b37d | ||
|
|
9498e8f334 | ||
|
|
83b8411929 | ||
|
|
e9accd13b3 | ||
|
|
9e27a19258 | ||
|
|
252f3a5bea | ||
|
|
e0626bb126 | ||
|
|
7ff62c7f40 | ||
|
|
4b07e02acb | ||
|
|
4654d1d9c2 | ||
|
|
ce1f75e8a5 | ||
|
|
4d9aebc758 | ||
|
|
e814368ef3 | ||
|
|
bbbb02500f | ||
|
|
404f255f14 | ||
|
|
7a15e0d38a | ||
|
|
bfe669bdd9 | ||
|
|
c0553042fd | ||
|
|
af8b02654b | ||
|
|
4779ec91d0 | ||
|
|
14d7f4af0e | ||
|
|
f9888b23dd | ||
|
|
411cdbb00f | ||
|
|
d859f07469 | ||
|
|
c111819093 | ||
|
|
aa8321d13c | ||
|
|
5e3bd972e5 | ||
|
|
ad39a04fff | ||
|
|
9a97e84296 | ||
|
|
1b7b0dcb13 | ||
|
|
d365a5060b | ||
|
|
b69589394a | ||
|
|
00f5791766 | ||
|
|
38cab2eda7 | ||
|
|
0db4d6e4e0 | ||
|
|
549712962f | ||
|
|
34b7002faf | ||
|
|
0e6f47b23c | ||
|
|
a372a89b5e | ||
|
|
239847aee7 | ||
|
|
813249c6a7 | ||
|
|
293ab28bce | ||
|
|
98e73cdec5 | ||
|
|
6d34bb9d25 | ||
|
|
479a5e4da9 | ||
|
|
4829454877 | ||
|
|
28664f6387 | ||
|
|
1a08a23a9c | ||
|
|
16f344ff1b | ||
|
|
693ce0e2e8 | ||
|
|
e6f533ea65 | ||
|
|
fcc21d63b0 | ||
|
|
afc0ffcb67 | ||
|
|
9ffb9840e1 | ||
|
|
4766ccf1b6 | ||
|
|
16b75c80a3 | ||
|
|
880f063046 | ||
|
|
723c11b886 | ||
|
|
5e074b1cf7 | ||
|
|
71d2fee36e | ||
|
|
7dc701464f | ||
|
|
fd876ef90f | ||
|
|
0597852178 | ||
|
|
81b1333091 | ||
|
|
7baebd79a6 | ||
|
|
951d0e30ae | ||
|
|
711e172769 | ||
|
|
faa60f0ea1 | ||
|
|
99d81c4329 | ||
|
|
17904326f3 | ||
|
|
5ee74f39d8 | ||
|
|
72f898ed60 | ||
|
|
157eab5bac | ||
|
|
e1b0d0a2ad | ||
|
|
2c050ba031 | ||
|
|
41518b16b4 | ||
|
|
72a16dc95f | ||
|
|
3404c64f55 | ||
|
|
b9015422f8 | ||
|
|
a7441b968d | ||
|
|
2d44be31f7 | ||
|
|
c2cf25bb2b | ||
|
|
7c4c53dcb0 | ||
|
|
aceb3b17c8 | ||
|
|
adfcfdb1de | ||
|
|
da329723bc | ||
|
|
63eb53fa06 | ||
|
|
d32c824515 | ||
|
|
e9ee8c481c | ||
|
|
6d78e52605 | ||
|
|
90136a5562 | ||
|
|
1592767c8c | ||
|
|
afa6ce2113 | ||
|
|
50e6e414ee | ||
|
|
ba9b8edcdc | ||
|
|
d7a927475c | ||
|
|
afe210343f | ||
|
|
4e293daf62 | ||
|
|
f9d7fdc516 | ||
|
|
6a04f3955c | ||
|
|
dce3b1780c | ||
|
|
f47f3e9db6 | ||
|
|
4ac74acaf7 | ||
|
|
cf7588c288 | ||
|
|
ec7bdf4000 | ||
|
|
51cd97f782 | ||
|
|
a16f2f096d | ||
|
|
4e92f14551 | ||
|
|
8f6cc9ff44 | ||
|
|
f885df5c67 | ||
|
|
0ccb110e36 | ||
|
|
2c238dca9b | ||
|
|
3e0aa53fca | ||
|
|
12b4e44296 | ||
|
|
9a2cf4aefe | ||
|
|
0e2a116e0a | ||
|
|
7bf30eb54a | ||
|
|
8dda8c8ff3 | ||
|
|
988fdb22be | ||
|
|
1dd2c8fb4d | ||
|
|
2b39384b28 | ||
|
|
28d1275023 | ||
|
|
979181fc3b | ||
|
|
b374b88ad5 | ||
|
|
6643c19a20 | ||
|
|
7460874c81 | ||
|
|
13dd7511f6 | ||
|
|
f153d251c8 | ||
|
|
3442ace981 | ||
|
|
547cebf5a9 | ||
|
|
7a24d22bc6 | ||
|
|
8f5728afe4 | ||
|
|
41b5ac2c61 | ||
|
|
694ca3bf25 | ||
|
|
674d314b55 | ||
|
|
08a35cc5d1 | ||
|
|
176e5db4d9 | ||
|
|
2535a9ebf9 | ||
|
|
8ff99ee925 | ||
|
|
abc8218487 | ||
|
|
e4765750c3 | ||
|
|
02cddf556b | ||
|
|
e1e6d3c72d |
70
.github/workflows/dotnet-desktop.yml
vendored
70
.github/workflows/dotnet-desktop.yml
vendored
@@ -1,46 +1,80 @@
|
||||
name: Build & Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
build-release:
|
||||
if: ${{ github.event.pull_request.merged == true }}
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
PROJECT_NAME: SpineViewer
|
||||
VERSION: ${{ github.ref_name }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
with:
|
||||
fetch-tags: true
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
- name: Extract version from csproj
|
||||
shell: pwsh
|
||||
run: |
|
||||
[xml]$proj = Get-Content "$env:PROJECT_NAME\$env:PROJECT_NAME.csproj"
|
||||
$VERSION_NUM = $proj.Project.PropertyGroup.Version
|
||||
$VERSION_TAG = "v$VERSION_NUM".Trim()
|
||||
"VERSION=$VERSION_TAG" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Check Version Tag
|
||||
shell: pwsh
|
||||
run: |
|
||||
if (-not $env:VERSION) {
|
||||
Write-Error "Version tag not found in csproj file."
|
||||
exit 1
|
||||
}
|
||||
Write-Host "Version tag found: $env:VERSION"
|
||||
|
||||
- name: Tag merge commit
|
||||
shell: pwsh
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git tag $env:VERSION
|
||||
git push --tags
|
||||
|
||||
- name: Publish FrameworkDependent version
|
||||
shell: pwsh
|
||||
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
|
||||
shell: pwsh
|
||||
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
|
||||
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -ItemType Directory -Path release -Force | Out-Null
|
||||
|
||||
- name: Compress FrameworkDependent version
|
||||
shell: pwsh
|
||||
run: |
|
||||
Compress-Archive -Path "publish/${env:PROJECT_NAME}-${env:VERSION}" -DestinationPath "release/${env:PROJECT_NAME}-${env:VERSION}.zip" -Force
|
||||
|
||||
Compress-Archive -Path "publish\$env:PROJECT_NAME-$env:VERSION\*" -DestinationPath "release\$env:PROJECT_NAME-$env:VERSION.zip" -Force
|
||||
|
||||
- name: Compress SelfContained version
|
||||
shell: pwsh
|
||||
run: |
|
||||
Compress-Archive -Path "publish/${env:PROJECT_NAME}-${env:VERSION}-SelfContained" -DestinationPath "release/${env:PROJECT_NAME}-${env:VERSION}-SelfContained.zip" -Force
|
||||
|
||||
Compress-Archive -Path "publish\$env:PROJECT_NAME-$env:VERSION-SelfContained\*" -DestinationPath "release\$env:PROJECT_NAME-$env:VERSION-SelfContained.zip" -Force
|
||||
|
||||
- name: Create GitHub Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
@@ -51,7 +85,7 @@ jobs:
|
||||
release_name: Release ${{ env.VERSION }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
|
||||
- name: Upload FrameworkDependent zip
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
@@ -61,7 +95,7 @@ jobs:
|
||||
asset_path: release/${{ env.PROJECT_NAME }}-${{ env.VERSION }}.zip
|
||||
asset_name: ${{ env.PROJECT_NAME }}-${{ env.VERSION }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
|
||||
- name: Upload SelfContained zip
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
|
||||
208
CHANGELOG.md
208
CHANGELOG.md
@@ -1,28 +1,214 @@
|
||||
# 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
|
||||
|
||||
- 增加纹理全局加载选项
|
||||
|
||||
## v0.12.9
|
||||
|
||||
- 修复由于未调用 UpdateCache 导致的约束 bug
|
||||
|
||||
## v0.12.8
|
||||
|
||||
- 增加英语界面文本
|
||||
- 增加 4.2 版本格式转换
|
||||
- 修改格式转换中一些问题和编码范式
|
||||
|
||||
## v0.12.7
|
||||
|
||||
- 修复一些问题
|
||||
|
||||
## v0.12.6
|
||||
|
||||
- 增加全屏预览
|
||||
- 增加桌面投影 (实验性功能)
|
||||
- 增加预览画面背景色设置
|
||||
- 增加分辨率和颜色预设列表
|
||||
- 皮肤面板显示 default
|
||||
|
||||
## v0.12.5
|
||||
|
||||
- 增加插槽属性面板
|
||||
- 修改皮肤属性面板设置方式为True/False
|
||||
|
||||
## v0.12.4
|
||||
|
||||
- 增加导出自动分辨率参数
|
||||
- 增加导出边缘和填充参数
|
||||
- 增加导出内容溢出参数
|
||||
- 支持3.7及以下版本多皮肤功能
|
||||
- 增加3.8版本的骨骼文件二进制和文本格式互转
|
||||
- 增加格式转换输出文件夹参数
|
||||
- 修改打开对话框的默认文件后缀筛选为所有类型
|
||||
|
||||
## v0.12.3
|
||||
|
||||
- 增加按住 ctrl 缩放选中模型
|
||||
- 增加对骨骼/网格/剪裁的调试渲染
|
||||
- 换回以前的上下参数面板布局
|
||||
- 修改窗口缩放模式为 Font -> Dpi
|
||||
- 修复部分问题
|
||||
|
||||
## v0.12.2
|
||||
|
||||
- 模型参数分标签显示
|
||||
- 皮肤/动画列表使用右键菜单进行增删
|
||||
- 标题栏显示版本号
|
||||
- 增加 webp 和 avif 动图格式
|
||||
- 增加导出参数缓存
|
||||
- 动图默认帧率修改为 24 帧
|
||||
- 增加保留最后一帧参数
|
||||
|
||||
## v0.12.1
|
||||
|
||||
- 优化使用体验, 提供初始皮肤/动画空位
|
||||
- 修复预览画面分辨率调整时父容器尺寸获取错误
|
||||
|
||||
## v0.12.0
|
||||
|
||||
- 支持皮肤列表 (仅 3.8.x 及以上支持)
|
||||
- 支持多轨道动画
|
||||
- 动画和皮肤列表多选时改为取并集
|
||||
- 修复导出时没有正确处理预乘像素的问题
|
||||
|
||||
## v0.11.5
|
||||
|
||||
- 导出格式全面支持
|
||||
- 修复预览图不显示的问题
|
||||
- 优化列表卡顿问题
|
||||
- 模型列表增加数量显示
|
||||
|
||||
## v0.11.4
|
||||
|
||||
- 增加 MP4 导出格式
|
||||
- 增加导出背景颜色参数
|
||||
- 增加日志输出 FFMpeg 参数字符串
|
||||
- 增加导出时任务栏图标执行动效
|
||||
- 修复预览面板移动模型时物理效果不同步的问题
|
||||
- 优化部分使用体验
|
||||
|
||||
## v0.11.3
|
||||
|
||||
- 增加模型隐藏设置属性
|
||||
- 加宽面板分割条 (4 -> 8 像素)
|
||||
- 优化属性面板分组显示
|
||||
- 增加调试纹理
|
||||
|
||||
## v0.11.2
|
||||
|
||||
- 增加皮肤切换
|
||||
- 优化模型缩放实现
|
||||
- 修复部分情况纹理加载异常
|
||||
|
||||
## v0.11.1
|
||||
|
||||
- 增加 GIF 导出格式
|
||||
- 增加逐个导出时可选自动时长
|
||||
- 优化使用体验
|
||||
|
||||
## v0.11.0
|
||||
|
||||
- 完成导出系统, 支持完整的单帧和帧序列导出功能
|
||||
- 预览画面增加快进功能
|
||||
|
||||
## v0.10.9
|
||||
|
||||
- 预览图导出增加名称后缀参数
|
||||
|
||||
## v0.10.8
|
||||
|
||||
- 完善预览图导出
|
||||
- 优化骨骼文件选择
|
||||
|
||||
## v0.10.7
|
||||
|
||||
- 增加仅导出选中
|
||||
- 增加模型调试属性
|
||||
|
||||
## v0.10.6
|
||||
|
||||
- 增加文件夹检测
|
||||
- 增加从剪贴板添加(可复制本地文件/文件夹直接打开)
|
||||
- 修复预览图导致的批量添加可能卡死
|
||||
|
||||
## v0.10.5
|
||||
|
||||
- 修复一些问题
|
||||
|
||||
## v0.10.4
|
||||
|
||||
- <EFBFBD><EFBFBD>һЩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
- 修复一些问题
|
||||
|
||||
## v0.10.3
|
||||
|
||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><EFBFBD>汾<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD>ϷŴ<EFBFBD><EFBFBD><EFBFBD>
|
||||
- 增加自动版本检测
|
||||
- 增加文件拖放打开
|
||||
|
||||
## v0.10.2
|
||||
|
||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><EFBFBD>Ҽ<EFBFBD><EFBFBD>˵<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݼ<EFBFBD>
|
||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ԥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD>л<EFBFBD>
|
||||
- 增加列表右键菜单快捷键
|
||||
- 增加预览缩略图复制
|
||||
- 增加列表视图切换
|
||||
|
||||
## v0.10.1
|
||||
|
||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD>Ԥ<EFBFBD><EFBFBD>ͼ
|
||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD>Ԥ<EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
- 增加列表预览图
|
||||
- 增加列表预览图导出
|
||||
|
||||
## v0.10.0
|
||||
|
||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˻<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><EFBFBD><EFBFBD>ѡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɾ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ԥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><EFBFBD>Χ<EFBFBD><EFBFBD>ѡ<EFBFBD><EFBFBD>
|
||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˹<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>ʽת<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܣ<EFBFBD>Ŀǰ<EFBFBD><EFBFBD>֧<EFBFBD>ֲ<EFBFBD><EFBFBD>ְ汾<EFBFBD>IJ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
- <EFBFBD>Ż<EFBFBD><EFBFBD>˲<EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
- 增加了画面和列表的选择联动,并删除了预览画面显示包围盒选项
|
||||
- 增加了骨骼文件格式转换功能,目前仅支持部分版本的不完整功能
|
||||
- 优化了部分使用体验
|
||||
|
||||
|
||||
18
NLog.Windows.Wpf/NLog.Windows.Wpf.csproj
Normal file
18
NLog.Windows.Wpf/NLog.Windows.Wpf.csproj
Normal 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.1</Version>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NLog" Version="5.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
92
NLog.Windows.Wpf/RichTextBoxRowColoringRule.cs
Normal file
92
NLog.Windows.Wpf/RichTextBoxRowColoringRule.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
256
NLog.Windows.Wpf/RichTextBoxTarget.cs
Normal file
256
NLog.Windows.Wpf/RichTextBoxTarget.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
119
NLog.Windows.Wpf/RichTextBoxWordColoringRule.cs
Normal file
119
NLog.Windows.Wpf/RichTextBoxWordColoringRule.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
137
README.en.md
137
README.en.md
@@ -1,70 +1,133 @@
|
||||
# [SpineViewer](https://github.com/ww-rm/SpineViewer)
|
||||
|
||||
[](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
|
||||
[](https://github.com/ww-rm/SpineViewer/releases)
|
||||
[](https://github.com/ww-rm/SpineViewer/releases)
|
||||
|
||||
[中文](README.md) | [English](README.en.md)
|
||||
|
||||
A simple and user-friendly Spine file viewer and exporter.
|
||||
A simple and user-friendly Spine file viewer and exporter with multi-language support (Chinese/English/Japanese).
|
||||
|
||||

|
||||
|
||||
---
|
||||
## Features
|
||||
|
||||
* Supports multiple versions of Spine files.
|
||||
* Batch open files via drag-and-drop or copy-paste.
|
||||
* Batch preview functionality.
|
||||
* List-based multi-skeleton viewing and render order management.
|
||||
* Batch adjustment of skeleton parameters using multi-selection.
|
||||
* Multi-track animation settings.
|
||||
* Skin and custom slot attachment settings.
|
||||
* Debug rendering support.
|
||||
* Fullscreen preview mode.
|
||||
* Export to single frame/image sequence/animated GIF/video formats.
|
||||
* Automatic resolution batch export.
|
||||
* FFmpeg custom export support.
|
||||
* Program parameter saving.
|
||||
* ...
|
||||
|
||||
### Supported Spine Versions
|
||||
|
||||
| Version | View & Export |
|
||||
| :-----: | :------------------: |
|
||||
| `2.1.x` | :white\_check\_mark: |
|
||||
| `3.6.x` | :white\_check\_mark: |
|
||||
| `3.7.x` | :white\_check\_mark: |
|
||||
| `3.8.x` | :white\_check\_mark: |
|
||||
| `4.0.x` | :white\_check\_mark: |
|
||||
| `4.1.x` | :white\_check\_mark: |
|
||||
| `4.2.x` | :white\_check\_mark: |
|
||||
| `4.3.x` | |
|
||||
|
||||
More versions under development \:rocket: \:rocket: \:rocket:
|
||||
|
||||
### Supported Export Formats
|
||||
|
||||
| Format | Use Case |
|
||||
| -------------- | ----------------------------------------------------------------------------- |
|
||||
| Single Frame | Generate high-resolution images of models; manually adjust the desired frame. |
|
||||
| Frame Sequence | Supports PNG format with transparency and lossless compression. |
|
||||
| GIF/Video | Export preview animations or common video formats. |
|
||||
| Custom Export | Supports arbitrary FFmpeg parameters for custom, complex export needs. |
|
||||
|
||||
## Installation
|
||||
|
||||
Download the zip package from the [Releases](https://github.com/ww-rm/SpineViewer/releases) page.
|
||||
Download the compressed package from the [Release](https://github.com/ww-rm/SpineViewer/releases) page.
|
||||
|
||||
The application requires the [.NET Desktop Runtime 8.0.x](https://dotnet.microsoft.com/en-us/download/dotnet/8.0).
|
||||
The software requires the [.NET Desktop Runtime 8.0.x](https://dotnet.microsoft.com/download/dotnet/8.0) to run.
|
||||
|
||||
Alternatively, you can download the zip package with the `SelfContained` suffix, which can run independently.
|
||||
Alternatively, download the package with the `SelfContained` suffix for standalone execution.
|
||||
|
||||
## Features
|
||||
|
||||
- Supports viewing Spine files of different versions:
|
||||
- [x] `v2.1.x`
|
||||
- [x] `v3.6.x`
|
||||
- [x] `v3.7.x`
|
||||
- [x] `v3.8.x`
|
||||
- [x] `v4.0.x`
|
||||
- [x] `v4.1.x`
|
||||
- [x] `v4.2.x`
|
||||
- [ ] `v4.3.x`
|
||||
- Supports animation preview for multi-skeleton files
|
||||
- Allows independent parameter settings for each skeleton
|
||||
- Supports exporting animation as PNG frame sequences
|
||||
- Provides export settings such as zoom and rotation
|
||||
- More features coming soon...
|
||||
For exporting GIF/MP4 and other animation/video formats, FFmpeg must be installed and added to the system environment variables. Visit the [FFmpeg Windows download page](https://ffmpeg.org/download.html#build-windows) or download the latest version directly: [ffmpeg-release-full.7z](https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z).
|
||||
|
||||
## Usage
|
||||
|
||||
### Importing Skeletons
|
||||
### How to Change the Display Language
|
||||
|
||||
Use the **File** menu to select **Open** or **Batch Open** to import skeleton files.
|
||||
In the menu, go to "File" -> "Preferences..." -> "Language," select your desired language, and confirm the change.
|
||||
|
||||
### Adjusting Skeletons
|
||||
### Basic Overview
|
||||
|
||||
Select one or more items in the **Model List** to display adjustable parameters in the **Model Parameters** panel.
|
||||
The program is organized into a left-right layout:
|
||||
|
||||
Right-clicking in the **Model List** allows you to add, delete, or adjust list items. You can also drag items with the left mouse button to rearrange them.
|
||||
* **Left Panel:** Functionality panel.
|
||||
* **Right Panel:** Preview display.
|
||||
|
||||
### Adjusting the View
|
||||
The left panel includes three sub-panels:
|
||||
|
||||
Mouse operations supported in the **Preview** window:
|
||||
* **Browse:** Preview the content of a specified folder without importing files into the program. This panel allows generating `.webp` previews for models or importing selected models.
|
||||
* **Model:** Lists imported models for rendering. Parameters and rendering order can be adjusted here, along with other model-related functionalities.
|
||||
* **Display:** Adjust parameters for the right-side preview display.
|
||||
|
||||
- Left-click to drag the skeleton
|
||||
- Right-click to drag the view
|
||||
- Scroll wheel to zoom in/out
|
||||
Hover your mouse over buttons, labels, or input fields to see help text for most UI elements.
|
||||
|
||||
Additionally, you can adjust export and preview parameters through the **View Parameters** panel.
|
||||
### Skeleton Import
|
||||
|
||||
In the **Functions** menu, you can reset and synchronize the animation time for all skeletons.
|
||||
Drag-and-drop or paste skeleton files/directories into the Model panel.
|
||||
|
||||
### Exporting Animations
|
||||
Alternatively, use the right-click menu in the Browse panel to import selected items.
|
||||
|
||||
Select **Export** from the **File** menu to export all loaded skeleton animations as PNG frame sequences, based on the current preview settings.
|
||||
### Content Adjustment
|
||||
|
||||
You can view the full duration of each animation in the **Model Parameters** of each skeleton.
|
||||
The Model panel supports right-click menus, some shortcuts, and batch adjustments of model parameters through multi-selection.
|
||||
|
||||
For preview display adjustments:
|
||||
|
||||
* **Left-click:** Select and drag models. Hold `Ctrl` for multi-selection, synchronized with the left-side list.
|
||||
* **Right-click:** Drag the entire display.
|
||||
* **Scroll wheel:** Zoom in/out. Hold `Ctrl` to scale selected models.
|
||||
* **Render selected-only mode:** In this mode, the preview only shows selected models, and selection status can only be changed via the left-side list.
|
||||
|
||||
The buttons below the preview display allow time adjustments, serving as a simple playback control.
|
||||
|
||||
### Content Export
|
||||
|
||||
Export follows the **WYSIWYG (What You See Is What You Get)** principle, meaning the preview display reflects the exported output.
|
||||
|
||||
Use the right-click menu in the Model panel to export selected items.
|
||||
|
||||
Key export parameters include:
|
||||
|
||||
* **Output folder:** Optional. When not specified, output is saved to the respective model folder; otherwise, all output is saved to the provided folder.
|
||||
* **Export single:** By default, each model is exported independently. Selecting "Export single" renders all selected models in a single frame, producing a unified output.
|
||||
* **Auto resolution:** Ignores the preview resolution and viewport parameters, exporting output at the actual size of the content. For animations/videos, the output matches the size required for full visibility.
|
||||
|
||||
### More Information
|
||||
|
||||
For detailed usage and documentation, see the [Wiki](https://github.com/ww-rm/SpineViewer/wiki). For usage questions or bug reports, submit an [Issue](https://github.com/ww-rm/SpineViewer/issues).
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
* [spine-runtimes](https://github.com/EsotericSoftware/spine-runtimes)
|
||||
* [SFML.Net](https://github.com/SFML/SFML.Net)
|
||||
* [FFMpegCore](https://github.com/rosenbjerg/FFMpegCore)
|
||||
* [HandyControl](https://github.com/HandyOrg/HandyControl)
|
||||
* [NLog](https://github.com/NLog/NLog)
|
||||
* [SkiaSharp](https://github.com/mono/SkiaSharp)
|
||||
|
||||
---
|
||||
|
||||
*If you find this project helpful, please give it a :star: and share it with others! :)*
|
||||
*If you find this project helpful, please give it a \:star: and share it with others! :)*
|
||||
|
||||
[](https://starchart.cc/ww-rm/SpineViewer)
|
||||
|
||||
132
README.md
132
README.md
@@ -1,14 +1,55 @@
|
||||
# [SpineViewer](https://github.com/ww-rm/SpineViewer)
|
||||
|
||||
[](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
|
||||
[](https://github.com/ww-rm/SpineViewer/releases)
|
||||
[](https://github.com/ww-rm/SpineViewer/releases)
|
||||
|
||||
[中文](README.md) | [English](README.en.md)
|
||||
|
||||
一个简单好用的 Spine 文件查看&导出程序.
|
||||
一个简单好用的 Spine 文件查看&导出程序, 支持中/英/日多语言界面.
|
||||
|
||||

|
||||
|
||||
---
|
||||
## 功能
|
||||
|
||||
- 支持多版本 spine 文件
|
||||
- 支持拖拽/复制粘贴批量打开文件
|
||||
- 支持批量预览
|
||||
- 支持列表式多骨骼查看和渲染层级管理
|
||||
- 支持列表多选批量设置骨骼参数
|
||||
- 支持多轨道动画设置
|
||||
- 支持皮肤/自定义插槽附件设置
|
||||
- 支持调试渲染
|
||||
- 支持全屏预览
|
||||
- 支持单帧/动图/视频文件导出
|
||||
- 支持自动分辨率批量导出
|
||||
- 支持 FFmpeg 自定义导出
|
||||
- 支持程序参数保存
|
||||
- ...
|
||||
|
||||
### Spine 版本支持
|
||||
|
||||
| 版本 | 查看&导出 |
|
||||
| :---: | :---: |
|
||||
| `2.1.x` | :white_check_mark: |
|
||||
| `3.6.x` | :white_check_mark: |
|
||||
| `3.7.x` | :white_check_mark: |
|
||||
| `3.8.x` | :white_check_mark: |
|
||||
| `4.0.x` | :white_check_mark: |
|
||||
| `4.1.x` | :white_check_mark: |
|
||||
| `4.2.x` | :white_check_mark: |
|
||||
| `4.3.x` | |
|
||||
|
||||
更多版本正在施工 :rocket: :rocket: :rocket:
|
||||
|
||||
### 导出格式支持
|
||||
|
||||
| 导出格式 | 适用场景 |
|
||||
| --- | --- |
|
||||
| 单帧画面 | 支持生成高清模型画面图像, 可手动调节需要的一帧. |
|
||||
| 帧序列 | 支持 PNG 格式帧序列, 可保留透明通道且无损压缩. |
|
||||
| 动图/视频 | 可以生成预览动图或者常见格式视频. |
|
||||
| 自定义导出 | 除上述预设方案, 支持提供任意 FFmpeg 参数进行导出, 满足自定义复杂需求. |
|
||||
|
||||
## 安装
|
||||
|
||||
@@ -18,63 +59,72 @@
|
||||
|
||||
也可以下载带有 `SelfContained` 后缀的压缩包, 可以独立运行.
|
||||
|
||||
## 功能支持
|
||||
|
||||
| 版本 | 查看&导出 | 格式转换 |
|
||||
| :---: | :---: | :---: |
|
||||
| `2.1.x` | :white_check_mark: | |
|
||||
| `3.1.x` | | |
|
||||
| `3.4.x` | | |
|
||||
| `3.5.x` | | |
|
||||
| `3.6.x` | :white_check_mark: | |
|
||||
| `3.7.x` | :white_check_mark: | |
|
||||
| `3.8.x` | :white_check_mark: | :white_check_mark: |
|
||||
| `4.1.x` | :white_check_mark: | |
|
||||
| `4.2.x` | :white_check_mark: | |
|
||||
| `4.3.x` | | |
|
||||
|
||||
- 支持文件拖放打开
|
||||
- 支持自动检测版本
|
||||
- 支持列表缩略图预览
|
||||
- 支持多骨骼文件动画预览
|
||||
- 支持每个骨骼独立参数设置
|
||||
- 支持动画PNG帧序列导出
|
||||
- 支持缩放旋转等导出画面设置
|
||||
- 支持对独立的骨骼文件进行格式转换
|
||||
- Coming soon...
|
||||
导出 GIF/MP4 等动图/视频格式需要在本地安装 ffmpeg 命令行, 并且添加至环境变量, [点击前往 FFmpeg-Windows 下载页面](https://ffmpeg.org/download.html#build-windows), 也可以点这个下载最新版本 [ffmpeg-release-full.7z](https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z).
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 如何修改显示语言
|
||||
|
||||
窗口菜单的 "文件" -> "首选项..." -> "语言", 选择你需要的语言并确认修改.
|
||||
|
||||
### 基本介绍
|
||||
|
||||
程序大致是左右布局, 左侧是功能面板, 右侧是画面.
|
||||
|
||||
左侧有三个子面板, 分别是:
|
||||
|
||||
- **浏览**. 该面板用于预览指定文件夹的内容, 并没有真正导入文件到程序. 在该面板可以为模型生成 webp 格式的预览图, 或者导入选中的模型.
|
||||
- **模型**. 该面板记录导入并进行渲染的模型列表, 可以在这个面板设置与模型渲染相关的参数和渲染顺序, 以及一些与模型有关的功能.
|
||||
- **画面**. 该面板用于设置右侧预览画面的参数.
|
||||
|
||||
绝大部分按钮或者标签或者输入框都可以通过鼠标指针悬停来获取帮助文本.
|
||||
|
||||
### 骨骼导入
|
||||
|
||||
**文件**菜单可以选择**打开**或者**批量打开**进行骨骼文件导入.
|
||||
可以直接拖放/粘贴需要导入的骨骼文件/目录到模型面板.
|
||||
|
||||
或者直接把要打开的骨骼文件拖进列表, 这种方式只支持 `.json` 和 `.skel` 后缀的文件, 其他的会被忽略.
|
||||
或者在浏览面板内右键菜单导入选中项.
|
||||
|
||||
### 骨骼调整
|
||||
### 内容调整
|
||||
|
||||
在**模型列表**中选择一项或多项, 将会在**模型参数**面板显示可供调节的参数.
|
||||
模型面板支持右键菜单以及部分快捷键, 并且可以多选进行模型参数的批量调整.
|
||||
|
||||
**模型列表**右键菜单可以对列表项进行增删调整, 也可以使用鼠标左键拖动调整顺序.
|
||||
预览画面除了使用面板进行参数设置外, 支持部分鼠标动作:
|
||||
|
||||
### 画面调整
|
||||
- 左键可以选择和拖拽模型, 按下 `Ctrl` 键可以实现多选, 与左侧列表选择是联动的.
|
||||
- 右键对整体画面进行拖动.
|
||||
- 滚轮进行画面缩放, 按住 `Ctrl` 可以对选中的模型进行批量缩放.
|
||||
- 仅渲染选中模式, 在该模式下, 预览画面仅包含被选中的模型, 并且只能通过左侧列表改变选中状态.
|
||||
|
||||
**预览画面**支持的鼠标操作:
|
||||
预览画面下方按钮支持对画面时间进行调整, 可以当作一个简易的播放器.
|
||||
|
||||
- 左键可以对骨骼进行拖动
|
||||
- 右键对画面进行拖动
|
||||
- 滚轮进行画面缩放
|
||||
### 内容导出
|
||||
|
||||
除此之外, 也可以通过**画面参数**面板调节导出和预览时的画面参数.
|
||||
导出遵循 "所见即所得" 原则, 即实时预览的画面就是你导出的画面.
|
||||
|
||||
在**功能**菜单中, 可以重置同步所有骨骼动画时间.
|
||||
在模型面板里, 右键菜单可以对选中项进行导出操作.
|
||||
|
||||
### 动画导出
|
||||
导出有以下几个关键参数:
|
||||
|
||||
**文件**菜单中选择**导出**可以将目前加载的所有骨骼动画按照预览时的画面进行PNG帧序列导出.
|
||||
- 输出文件夹. 这个参数某些时候可选, 当不提供时, 则将输出产物输出到每个模型各自的模型文件夹, 否则输出产物全部输出到提供的输出文件夹.
|
||||
- 导出单个. 默认是每个模型独立导出, 即对模型列表进行批量操作, 如果选择仅导出单个, 那么被导出的所有模型将在同一个画面上被渲染, 输出产物只有一份.
|
||||
- 自动分辨率. 该模式会忽略预览画面的分辨率和视区参数, 导出产物的分辨率与被导出内容的实际大小一致, 如果是动图或者视频则会与完整显示动画的必需大小一致.
|
||||
|
||||
可以在每个骨骼的**模型参数**中查看动画完整时长.
|
||||
### 更多
|
||||
|
||||
更为详细的使用方法和说明见 [Wiki](https://github.com/ww-rm/SpineViewer/wiki), 有使用上的问题或者 BUG 可以提个 [Issue](https://github.com/ww-rm/SpineViewer/issues).
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
- [spine-runtimes](https://github.com/EsotericSoftware/spine-runtimes)
|
||||
- [SFML.Net](https://github.com/SFML/SFML.Net)
|
||||
- [FFMpegCore](https://github.com/rosenbjerg/FFMpegCore)
|
||||
- [HandyControl](https://github.com/HandyOrg/HandyControl)
|
||||
- [NLog](https://github.com/NLog/NLog)
|
||||
- [SkiaSharp](https://github.com/mono/SkiaSharp)
|
||||
|
||||
---
|
||||
|
||||
*如果你觉得这个项目不错请给个 :star:, 并分享给更多人知道! :)*
|
||||
|
||||
[](https://starchart.cc/ww-rm/SpineViewer)
|
||||
|
||||
138
SFMLRenderer/ISFMLRenderer.cs
Normal file
138
SFMLRenderer/ISFMLRenderer.cs
Normal 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
21
SFMLRenderer/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# SFMLRenderer
|
||||
|
||||
这个库封装了一个用于 WPF 的 SFML 渲染控件.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
|
||||
namespace SFMLRenderer {
|
||||
class ISFMLRenderer {
|
||||
<<Interface>>
|
||||
}
|
||||
|
||||
class SFMLHwndHost
|
||||
|
||||
class SFMLRenderPanel
|
||||
}
|
||||
|
||||
ISFMLRenderer <|.. SFMLRenderPanel
|
||||
SFMLHwndHost <.. SFMLRenderPanel
|
||||
|
||||
```
|
||||
73
SFMLRenderer/SFMLHwndHost.cs
Normal file
73
SFMLRenderer/SFMLHwndHost.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
SFMLRenderer/SFMLRenderPanel.xaml
Normal file
14
SFMLRenderer/SFMLRenderPanel.xaml
Normal 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>
|
||||
253
SFMLRenderer/SFMLRenderPanel.xaml.cs
Normal file
253
SFMLRenderer/SFMLRenderPanel.xaml.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
SFMLRenderer/SFMLRenderer.csproj
Normal file
22
SFMLRenderer/SFMLRenderer.csproj
Normal 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.1</Version>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<NoWarn>$(NoWarn);NETSDK1206</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SFML.Net" Version="2.6.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
198
Spine/Exporters/BaseExporter.cs
Normal file
198
Spine/Exporters/BaseExporter.cs
Normal 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
|
||||
}
|
||||
}
|
||||
82
Spine/Exporters/CustomFFmpegExporter.cs
Normal file
82
Spine/Exporters/CustomFFmpegExporter.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
146
Spine/Exporters/FFmpegVideoExporter.cs
Normal file
146
Spine/Exporters/FFmpegVideoExporter.cs
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
37
Spine/Exporters/FrameExporter.cs
Normal file
37
Spine/Exporters/FrameExporter.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Spine/Exporters/FrameSequenceExporter.cs
Normal file
61
Spine/Exporters/FrameSequenceExporter.cs
Normal 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Spine/Exporters/SFMLImageVideoFrame.cs
Normal file
52
Spine/Exporters/SFMLImageVideoFrame.cs
Normal 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
|
||||
}
|
||||
}
|
||||
142
Spine/Exporters/VideoExporter.cs
Normal file
142
Spine/Exporters/VideoExporter.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
23
Spine/Implementations/SpineWrappers/V21/Animation21.cs
Normal file
23
Spine/Implementations/SpineWrappers/V21/Animation21.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
179
Spine/Implementations/SpineWrappers/V21/AnimationState21.cs
Normal file
179
Spine/Implementations/SpineWrappers/V21/AnimationState21.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
33
Spine/Implementations/SpineWrappers/V21/Bone21.cs
Normal file
33
Spine/Implementations/SpineWrappers/V21/Bone21.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Spine.SpineWrappers;
|
||||
using 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();
|
||||
}
|
||||
}
|
||||
105
Spine/Implementations/SpineWrappers/V21/Skeleton21.cs
Normal file
105
Spine/Implementations/SpineWrappers/V21/Skeleton21.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
45
Spine/Implementations/SpineWrappers/V21/Skin21.cs
Normal file
45
Spine/Implementations/SpineWrappers/V21/Skin21.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Spine.SpineWrappers;
|
||||
using 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();
|
||||
}
|
||||
}
|
||||
71
Spine/Implementations/SpineWrappers/V21/Slot21.cs
Normal file
71
Spine/Implementations/SpineWrappers/V21/Slot21.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
136
Spine/Implementations/SpineWrappers/V21/SpineObjectData21.cs
Normal file
136
Spine/Implementations/SpineWrappers/V21/SpineObjectData21.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Spine.Utils;
|
||||
using Spine.SpineWrappers;
|
||||
using Spine.SpineWrappers.Attachments;
|
||||
using 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, Spine.SpineWrappers.TextureLoader textureLoader)
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
|
||||
try
|
||||
{
|
||||
if (Utf8Validator.IsUtf8(skelPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
_animationStateData = new AnimationStateData(_skeletonData);
|
||||
|
||||
// 整理皮肤和附件
|
||||
Dictionary<string, Dictionary<string, IAttachment>> slotAttachments = [];
|
||||
List<ISkin> skins = [];
|
||||
Dictionary<string, ISkin> skinsByName = [];
|
||||
foreach (var s in _skeletonData.Skins)
|
||||
{
|
||||
var skin = new 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);
|
||||
}
|
||||
}
|
||||
135
Spine/Implementations/SpineWrappers/V21/TrackEntry21.cs
Normal file
135
Spine/Implementations/SpineWrappers/V21/TrackEntry21.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
23
Spine/Implementations/SpineWrappers/V36/Animation36.cs
Normal file
23
Spine/Implementations/SpineWrappers/V36/Animation36.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
229
Spine/Implementations/SpineWrappers/V36/AnimationState36.cs
Normal file
229
Spine/Implementations/SpineWrappers/V36/AnimationState36.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Spine.SpineWrappers;
|
||||
using 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
33
Spine/Implementations/SpineWrappers/V36/Bone36.cs
Normal file
33
Spine/Implementations/SpineWrappers/V36/Bone36.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Spine.SpineWrappers;
|
||||
using 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();
|
||||
}
|
||||
}
|
||||
106
Spine/Implementations/SpineWrappers/V36/Skeleton36.cs
Normal file
106
Spine/Implementations/SpineWrappers/V36/Skeleton36.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Immutable;
|
||||
using Spine.SpineWrappers;
|
||||
using 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
45
Spine/Implementations/SpineWrappers/V36/Skin36.cs
Normal file
45
Spine/Implementations/SpineWrappers/V36/Skin36.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Spine.SpineWrappers;
|
||||
using 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();
|
||||
}
|
||||
}
|
||||
78
Spine/Implementations/SpineWrappers/V36/Slot36.cs
Normal file
78
Spine/Implementations/SpineWrappers/V36/Slot36.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
138
Spine/Implementations/SpineWrappers/V36/SpineObjectData36.cs
Normal file
138
Spine/Implementations/SpineWrappers/V36/SpineObjectData36.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Spine.Utils;
|
||||
using Spine.SpineWrappers;
|
||||
using Spine.SpineWrappers.Attachments;
|
||||
using 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, Spine.SpineWrappers.TextureLoader textureLoader)
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
|
||||
try
|
||||
{
|
||||
if (Utf8Validator.IsUtf8(skelPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
_animationStateData = new AnimationStateData(_skeletonData);
|
||||
|
||||
// 整理皮肤和附件
|
||||
Dictionary<string, Dictionary<string, IAttachment>> slotAttachments = [];
|
||||
List<ISkin> skins = [];
|
||||
Dictionary<string, ISkin> skinsByName = [];
|
||||
foreach (var s in _skeletonData.Skins)
|
||||
{
|
||||
var skin = new 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);
|
||||
}
|
||||
}
|
||||
185
Spine/Implementations/SpineWrappers/V36/TrackEntry36.cs
Normal file
185
Spine/Implementations/SpineWrappers/V36/TrackEntry36.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
23
Spine/Implementations/SpineWrappers/V37/Animation37.cs
Normal file
23
Spine/Implementations/SpineWrappers/V37/Animation37.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
229
Spine/Implementations/SpineWrappers/V37/AnimationState37.cs
Normal file
229
Spine/Implementations/SpineWrappers/V37/AnimationState37.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Spine.SpineWrappers;
|
||||
using 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
33
Spine/Implementations/SpineWrappers/V37/Bone37.cs
Normal file
33
Spine/Implementations/SpineWrappers/V37/Bone37.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Spine.SpineWrappers;
|
||||
using 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();
|
||||
}
|
||||
}
|
||||
106
Spine/Implementations/SpineWrappers/V37/Skeleton37.cs
Normal file
106
Spine/Implementations/SpineWrappers/V37/Skeleton37.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Immutable;
|
||||
using Spine.SpineWrappers;
|
||||
using 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
45
Spine/Implementations/SpineWrappers/V37/Skin37.cs
Normal file
45
Spine/Implementations/SpineWrappers/V37/Skin37.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Spine.SpineWrappers;
|
||||
using 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();
|
||||
}
|
||||
}
|
||||
78
Spine/Implementations/SpineWrappers/V37/Slot37.cs
Normal file
78
Spine/Implementations/SpineWrappers/V37/Slot37.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
138
Spine/Implementations/SpineWrappers/V37/SpineObjectData37.cs
Normal file
138
Spine/Implementations/SpineWrappers/V37/SpineObjectData37.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Spine.Utils;
|
||||
using Spine.SpineWrappers;
|
||||
using Spine.SpineWrappers.Attachments;
|
||||
using 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, Spine.SpineWrappers.TextureLoader textureLoader)
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
|
||||
try
|
||||
{
|
||||
if (Utf8Validator.IsUtf8(skelPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
_animationStateData = new AnimationStateData(_skeletonData);
|
||||
|
||||
// 整理皮肤和附件
|
||||
Dictionary<string, Dictionary<string, IAttachment>> slotAttachments = [];
|
||||
List<ISkin> skins = [];
|
||||
Dictionary<string, ISkin> skinsByName = [];
|
||||
foreach (var s in _skeletonData.Skins)
|
||||
{
|
||||
var skin = new 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);
|
||||
}
|
||||
}
|
||||
185
Spine/Implementations/SpineWrappers/V37/TrackEntry37.cs
Normal file
185
Spine/Implementations/SpineWrappers/V37/TrackEntry37.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
23
Spine/Implementations/SpineWrappers/V38/Animation38.cs
Normal file
23
Spine/Implementations/SpineWrappers/V38/Animation38.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
229
Spine/Implementations/SpineWrappers/V38/AnimationState38.cs
Normal file
229
Spine/Implementations/SpineWrappers/V38/AnimationState38.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Spine.SpineWrappers;
|
||||
using 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
33
Spine/Implementations/SpineWrappers/V38/Bone38.cs
Normal file
33
Spine/Implementations/SpineWrappers/V38/Bone38.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Spine.SpineWrappers;
|
||||
using 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();
|
||||
}
|
||||
}
|
||||
106
Spine/Implementations/SpineWrappers/V38/Skeleton38.cs
Normal file
106
Spine/Implementations/SpineWrappers/V38/Skeleton38.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Immutable;
|
||||
using Spine.SpineWrappers;
|
||||
using 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
43
Spine/Implementations/SpineWrappers/V38/Skin38.cs
Normal file
43
Spine/Implementations/SpineWrappers/V38/Skin38.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
79
Spine/Implementations/SpineWrappers/V38/Slot38.cs
Normal file
79
Spine/Implementations/SpineWrappers/V38/Slot38.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
139
Spine/Implementations/SpineWrappers/V38/SpineObjectData38.cs
Normal file
139
Spine/Implementations/SpineWrappers/V38/SpineObjectData38.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
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, Spine.SpineWrappers.TextureLoader textureLoader)
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
|
||||
try
|
||||
{
|
||||
if (Utf8Validator.IsUtf8(skelPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
_animationStateData = new AnimationStateData(_skeletonData);
|
||||
|
||||
// 整理皮肤和附件
|
||||
Dictionary<string, Dictionary<string, IAttachment>> slotAttachments = [];
|
||||
List<ISkin> skins = [];
|
||||
Dictionary<string, ISkin> skinsByName = [];
|
||||
foreach (var s in _skeletonData.Skins)
|
||||
{
|
||||
var skin = new 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);
|
||||
}
|
||||
}
|
||||
185
Spine/Implementations/SpineWrappers/V38/TrackEntry38.cs
Normal file
185
Spine/Implementations/SpineWrappers/V38/TrackEntry38.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
23
Spine/Implementations/SpineWrappers/V40/Animation40.cs
Normal file
23
Spine/Implementations/SpineWrappers/V40/Animation40.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
229
Spine/Implementations/SpineWrappers/V40/AnimationState40.cs
Normal file
229
Spine/Implementations/SpineWrappers/V40/AnimationState40.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Spine.SpineWrappers;
|
||||
using 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
33
Spine/Implementations/SpineWrappers/V40/Bone40.cs
Normal file
33
Spine/Implementations/SpineWrappers/V40/Bone40.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Spine.SpineWrappers;
|
||||
using 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();
|
||||
}
|
||||
}
|
||||
106
Spine/Implementations/SpineWrappers/V40/Skeleton40.cs
Normal file
106
Spine/Implementations/SpineWrappers/V40/Skeleton40.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Immutable;
|
||||
using Spine.SpineWrappers;
|
||||
using 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
43
Spine/Implementations/SpineWrappers/V40/Skin40.cs
Normal file
43
Spine/Implementations/SpineWrappers/V40/Skin40.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
78
Spine/Implementations/SpineWrappers/V40/Slot40.cs
Normal file
78
Spine/Implementations/SpineWrappers/V40/Slot40.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
140
Spine/Implementations/SpineWrappers/V40/SpineObjectData40.cs
Normal file
140
Spine/Implementations/SpineWrappers/V40/SpineObjectData40.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
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, Spine.SpineWrappers.TextureLoader textureLoader)
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
|
||||
// 加载 skel
|
||||
try
|
||||
{
|
||||
if (Utf8Validator.IsUtf8(skelPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
_animationStateData = new AnimationStateData(_skeletonData);
|
||||
|
||||
// 整理皮肤和附件
|
||||
Dictionary<string, Dictionary<string, IAttachment>> slotAttachments = [];
|
||||
List<ISkin> skins = [];
|
||||
Dictionary<string, ISkin> skinsByName = [];
|
||||
foreach (var s in _skeletonData.Skins)
|
||||
{
|
||||
var skin = new 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);
|
||||
}
|
||||
}
|
||||
185
Spine/Implementations/SpineWrappers/V40/TrackEntry40.cs
Normal file
185
Spine/Implementations/SpineWrappers/V40/TrackEntry40.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
23
Spine/Implementations/SpineWrappers/V41/Animation41.cs
Normal file
23
Spine/Implementations/SpineWrappers/V41/Animation41.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user