using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Numerics;
using System.Collections;
using System.Collections.ObjectModel;
using SFML.System;
using SFML.Window;
using System.ComponentModel;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text.Json.Nodes;
namespace SpineViewer.Spine
{
///
/// Spine 实现类标记
///
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class SpineImplementationAttribute : Attribute
{
public Version Version { get; }
public SpineImplementationAttribute(Version version)
{
Version = version;
}
}
///
/// Spine 基类, 使用静态方法 New 来创建具体版本对象
///
public abstract class Spine : SFML.Graphics.Drawable, IDisposable
{
///
/// 空动画标记
///
public const string EMPTY_ANIMATION = "";
///
/// 预览图大小
///
public static readonly Size PREVIEW_SIZE = new(256, 256);
///
/// 缩放最小值
///
public const float SCALE_MIN = 0.001f;
///
/// 实现类缓存
///
private static readonly Dictionary ImplementationTypes = [];
public static readonly Dictionary.KeyCollection ImplementedVersions;
///
/// 用于解决 PMA 和渐变动画问题的片段着色器
///
private const string FRAGMENT_SHADER = (
"uniform sampler2D t;" +
"void main() { vec4 p = texture2D(t, gl_TexCoord[0].xy);" +
"if (p.a > 0) p.rgb /= max(max(max(p.r, p.g), p.b), p.a);" +
"gl_FragColor = gl_Color * p; }"
);
///
/// 用于解决 PMA 和渐变动画问题的片段着色器
///
protected static readonly SFML.Graphics.Shader? FragmentShader = null;
///
/// 静态构造函数
///
static Spine()
{
// 遍历并缓存标记了 SpineImplementationAttribute 的类型
var impTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => typeof(Spine).IsAssignableFrom(t) && !t.IsAbstract);
foreach (var type in impTypes)
{
var attr = type.GetCustomAttribute();
if (attr is not null)
{
if (ImplementationTypes.ContainsKey(attr.Version))
throw new InvalidOperationException($"Multiple implementations found: {attr.Version}");
ImplementationTypes[attr.Version] = type;
}
}
Program.Logger.Debug("Find Spine implementations: [{}]", string.Join(", ", ImplementationTypes.Keys));
ImplementedVersions = ImplementationTypes.Keys;
// 加载 FragmentShader
try
{
FragmentShader = SFML.Graphics.Shader.FromString(null, null, FRAGMENT_SHADER);
}
catch (Exception ex)
{
FragmentShader = null;
Program.Logger.Error(ex.ToString());
Program.Logger.Error("Failed to load fragment shader");
MessageBox.Show("Fragment shader 加载失败,预乘Alpha通道属性失效", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
///
/// 尝试检测骨骼文件版本
///
public static Version? GetVersion(string skelPath)
{
string versionString = null;
Version? version = null;
using var input = File.OpenRead(skelPath);
var reader = new SkeletonConverter.BinaryReader(input);
// try json format
try
{
if (JsonNode.Parse(input) is JsonObject root && root.TryGetPropertyValue("spine", out var node))
versionString = (string)node;
}
catch { }
// try v4 binary format
if (versionString is null)
{
try
{
input.Position = 0;
var hash = reader.ReadLong();
var versionPosition = input.Position;
var versionByteCount = reader.ReadVarInt();
input.Position = versionPosition;
if (versionByteCount <= 13)
versionString = reader.ReadString();
}
catch { }
}
// try v3 binary format
if (versionString is null)
{
try
{
input.Position = 0;
var hash = reader.ReadString();
versionString = reader.ReadString();
}
catch { }
}
if (versionString is not null)
{
if (versionString.StartsWith("2.1.")) version = Version.V21;
else if (versionString.StartsWith("3.6.")) version = Version.V36;
else if (versionString.StartsWith("3.7.")) version = Version.V37;
else if (versionString.StartsWith("3.8.")) version = Version.V38;
else if (versionString.StartsWith("4.0.")) version = Version.V40;
else if (versionString.StartsWith("4.1.")) version = Version.V41;
else if (versionString.StartsWith("4.2.")) version = Version.V42;
else if (versionString.StartsWith("4.3.")) version = Version.V43;
}
return version;
}
///
/// 创建特定版本的 Spine
///
public static Spine New(Version version, string skelPath, string? atlasPath = null)
{
if (version == Version.Auto)
{
if (GetVersion(skelPath) is Version detectedVersion)
version = detectedVersion;
else
throw new InvalidDataException($"Auto version detection failed for {skelPath}, try to use a specific version");
}
if (!ImplementationTypes.TryGetValue(version, out var spineType))
{
throw new NotImplementedException($"Not implemented version: {version}");
}
return (Spine)Activator.CreateInstance(spineType, skelPath, atlasPath);
}
///
/// 标识符
///
public readonly string ID = Guid.NewGuid().ToString();
///
/// 构造函数
///
public Spine(string skelPath, string? atlasPath = null)
{
// 获取子类类型
var type = GetType();
var attr = type.GetCustomAttribute();
if (attr is null)
{
throw new InvalidOperationException($"Class {type.Name} has no SpineImplementationAttribute");
}
atlasPath ??= Path.ChangeExtension(skelPath, ".atlas");
// 设置 Version
Version = attr.Version;
SkelPath = Path.GetFullPath(skelPath);
AtlasPath = Path.GetFullPath(atlasPath);
Name = Path.GetFileNameWithoutExtension(skelPath);
}
~Spine() { Dispose(false); }
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
protected virtual void Dispose(bool disposing) { preview?.Dispose(); }
///
/// 获取所属版本
///
[TypeConverter(typeof(VersionConverter))]
[Category("基本信息"), DisplayName("运行时版本")]
public Version Version { get; }
///
/// skel 文件完整路径
///
[Category("基本信息"), DisplayName("skel文件路径")]
public string SkelPath { get; }
///
/// atlas 文件完整路径
///
[Category("基本信息"), DisplayName("atlas文件路径")]
public string AtlasPath { get; }
[Category("基本信息"), DisplayName("名称")]
public string Name { get; }
///
/// 获取所属文件版本
///
[Category("基本信息"), DisplayName("文件版本")]
public abstract string FileVersion { get; }
///
/// 缩放比例
///
[Category("变换"), DisplayName("缩放比例")]
public abstract float Scale { get; set; }
///
/// 位置
///
[TypeConverter(typeof(PointFConverter))]
[Category("变换"), DisplayName("位置")]
public abstract PointF Position { get; set; }
///
/// 水平翻转
///
[Category("变换"), DisplayName("水平翻转")]
public abstract bool FlipX { get; set; }
///
/// 垂直翻转
///
[Category("变换"), DisplayName("垂直翻转")]
public abstract bool FlipY { get; set; }
///
/// 是否使用预乘Alpha
///
[Category("画面"), DisplayName("预乘Alpha通道")]
public bool UsePremultipliedAlpha { get; set; } = true;
///
/// 包含的所有动画名称
///
[Browsable(false)]
public ReadOnlyCollection AnimationNames { get => animationNames.AsReadOnly(); }
protected List animationNames = [EMPTY_ANIMATION];
///
/// 默认动画名称
///
[Browsable(false)]
public string DefaultAnimationName { get => animationNames.Last(); }
///
/// 当前动画名称
///
[TypeConverter(typeof(AnimationConverter))]
[Category("动画"), DisplayName("当前动画")]
public abstract string CurrentAnimation { get; set; }
///
/// 当前动画时长
///
[Category("动画"), DisplayName("当前动画时长")]
public float CurrentAnimationDuration { get => GetAnimationDuration(CurrentAnimation); }
///
/// 骨骼包围盒
///
[Browsable(false)]
public abstract RectangleF Bounds { get; }
///
/// 骨骼预览图
///
[Browsable(false)]
public Image Preview
{
get
{
if (preview is null)
{
using var img = GetPreview((uint)PREVIEW_SIZE.Width, (uint)PREVIEW_SIZE.Height);
img.SaveToMemory(out var imgBuffer, "bmp");
using var stream = new MemoryStream(imgBuffer);
preview = new Bitmap(stream);
}
return preview;
}
}
private Image preview = null;
///
/// 获取指定尺寸的预览图
///
public SFML.Graphics.Image GetPreview(uint width, uint height)
{
var curAnimation = CurrentAnimation;
CurrentAnimation = EMPTY_ANIMATION;
var bounds = Bounds;
float viewX = width;
float viewY = height;
float sizeX = bounds.Width;
float sizeY = bounds.Height;
var scale = 1f;
if ((sizeY / sizeX) < (viewY / viewX))
scale = sizeX / viewX;// 相同的 X, 视窗 Y 更大
else
scale = sizeY / viewY;// 相同的 Y, 视窗 X 更大
viewX *= scale;
viewY *= scale;
using var tex = new SFML.Graphics.RenderTexture(width, height);
var view = tex.GetView();
view.Center = new(bounds.X + viewX / 2, bounds.Y + viewY / 2);
view.Size = new(viewX, -viewY);
tex.SetView(view);
tex.Clear(SFML.Graphics.Color.Transparent);
tex.Draw(this);
tex.Display();
CurrentAnimation = curAnimation;
return tex.Texture.CopyToImage();
}
///
/// 获取动画时长, 如果动画不存在则返回 0
///
public abstract float GetAnimationDuration(string name);
///
/// 更新内部状态
///
/// 时间间隔
public abstract void Update(float delta);
///
/// 顶点坐标缓冲区
///
protected float[] worldVerticesBuffer = new float[1024];
///
/// 顶点缓冲区
///
protected SFML.Graphics.VertexArray vertexArray = new(SFML.Graphics.PrimitiveType.Triangles);
///
/// SFML.Graphics.Drawable 接口实现
///
public abstract void Draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states);
///
/// 是否被选中
///
[Browsable(false)]
public bool IsSelected { get; set; } = false;
}
}