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; } }