using System; using System.Collections.Generic; using System.IO; using System.Text.Json; using Spine; // Офлайн-экспортёр: читает Spine .skel.bytes+.atlas через spine-csharp и // выгружает геометрию (кости setup + привязки с мировыми вершинами/UV/треуг/весами) в свой JSON. // Использование: spineconv class NullTex : TextureLoader { public void Load(AtlasPage page, string path) { } public void Unload(object texture) { } } class Program { static void Main(string[] args) { string baseP = args[0]; string outPath = args[1]; var atlas = new Atlas(baseP + ".atlas.txt", new NullTex()); var loader = new AtlasAttachmentLoader(atlas); var binary = new SkeletonBinary(loader) { Scale = 1f }; SkeletonData data = binary.ReadSkeletonData(baseP + ".skel.bytes"); var skeleton = new Skeleton(data); skeleton.SetToSetupPose(); skeleton.UpdateWorldTransform(Skeleton.Physics.Update); var boneIndex = new Dictionary(); var bonesOut = new List(); var bones = data.Bones; for (int i = 0; i < bones.Count; i++) { var b = bones.Items[i]; boneIndex[b] = i; bonesOut.Add(new { name = b.Name, parent = b.Parent != null ? b.Parent.Name : null, x = b.X, y = b.Y, rotation = b.Rotation, scaleX = b.ScaleX, scaleY = b.ScaleY, shearX = b.ShearX, shearY = b.ShearY, length = b.Length }); } var attsOut = new List(); var pages = new HashSet(); var drawOrder = skeleton.DrawOrder; for (int d = 0; d < drawOrder.Count; d++) { Slot slot = drawOrder.Items[d]; Attachment att = slot.Attachment; if (att == null) continue; int sBone = boneIndex[slot.Data.BoneData]; var sd = slot.Data; float[] color = { sd.R, sd.G, sd.B, sd.A }; if (att is RegionAttachment ra) { float[] wv = new float[8]; ra.ComputeWorldVertices(slot, wv, 0, 2); var reg = (AtlasRegion)ra.Region; pages.Add(reg.page.name); var weights = new List(); for (int k = 0; k < 4; k++) weights.Add(new object[] { new object[] { sBone, 1f } }); attsOut.Add(new { slot = sd.Name, name = ra.Name, type = "region", page = reg.page.name, world = wv, uv = ra.UVs, triangles = new int[] { 0, 1, 2, 2, 3, 0 }, weights, color }); } else if (att is MeshAttachment ma) { int n = ma.WorldVerticesLength; float[] wv = new float[n]; ma.ComputeWorldVertices(slot, 0, n, wv, 0, 2); var reg = (AtlasRegion)ma.Region; pages.Add(reg.page.name); attsOut.Add(new { slot = sd.Name, name = ma.Name, type = "mesh", page = reg.page.name, world = wv, uv = ma.UVs, triangles = ma.Triangles, weights = ParseWeights(ma, sBone), color }); } } var outObj = new { bones = bonesOut, pages = new List(pages), attachments = attsOut }; File.WriteAllText(outPath, JsonSerializer.Serialize(outObj)); int meshes = 0, regions = 0, weighted = 0; foreach (var s in drawOrder) { if (s.Attachment is MeshAttachment m) { meshes++; if (m.Bones != null) weighted++; } else if (s.Attachment is RegionAttachment) regions++; } Console.WriteLine($"OK bones={bonesOut.Count} regions={regions} meshes={meshes} (weighted={weighted}) pages={pages.Count}"); } static List ParseWeights(MeshAttachment ma, int fallbackBone) { var res = new List(); int verts = ma.WorldVerticesLength / 2; if (ma.Bones == null) { for (int i = 0; i < verts; i++) res.Add(new object[] { new object[] { fallbackBone, 1f } }); return res; } int[] bs = ma.Bones; float[] vs = ma.Vertices; int vb = 0, vv = 0; for (int i = 0; i < verts; i++) { int cnt = bs[vb++]; var list = new List(); for (int k = 0; k < cnt; k++) { int bi = bs[vb++]; float x = vs[vv++], y = vs[vv++], w = vs[vv++]; list.Add(new object[] { bi, w }); } res.Add(list); } return res; } }