using AssetRipper.Assets.Generics; using AssetRipper.Numerics; using AssetRipper.SourceGenerated.Classes.ClassID_213; using AssetRipper.SourceGenerated.Classes.ClassID_687078895; using AssetRipper.SourceGenerated.Enums; using AssetRipper.SourceGenerated.Subclasses.SpriteAtlasData; using AssetRipper.SourceGenerated.Subclasses.SpriteBone; using AssetRipper.SourceGenerated.Subclasses.SpriteMetaData; using AssetRipper.SourceGenerated.Subclasses.SpriteRenderData; using AssetRipper.SourceGenerated.Subclasses.SpriteVertex; using AssetRipper.SourceGenerated.Subclasses.SubMesh; using AssetRipper.SourceGenerated.Subclasses.Vector2f; using System.Buffers.Binary; using System.Drawing; using System.Numerics; namespace AssetRipper.SourceGenerated.Extensions { public static class SpriteMetaDataExtensions { public static SpriteAlignment GetAlignment(this ISpriteMetaData data) { return (SpriteAlignment)data.Alignment; } public static void FillSpriteMetaData(this ISpriteMetaData instance, ISprite sprite, ISpriteAtlas? atlas) { sprite.GetSpriteCoordinatesInAtlas(atlas, out RectangleF rect, out Vector2 pivot, out Vector4 border); instance.Name = sprite.Name; instance.Rect.CopyValues(rect); instance.Alignment = (int)SpriteAlignment.Custom; instance.Pivot.CopyValues(pivot); instance.Border?.CopyValues(border); if (instance.Has_Outline()) { GenerateOutline(sprite, atlas, rect, pivot, instance.Outline); } if (instance.Has_PhysicsShape() && sprite.Has_PhysicsShape()) { GeneratePhysicsShape(sprite, atlas, rect, pivot, instance.PhysicsShape); } instance.TessellationDetail = 0; if (instance.Has_Bones() && sprite.Has_Bones() && instance.Has_SpriteID()) { // Scale bones based off of the sprite's PPU foreach (ISpriteBone bone in sprite.Bones) { bone.Position.Scale(sprite.PixelsToUnits); bone.Length *= sprite.PixelsToUnits; // Set root bone position if (bone.ParentId == -1) { bone.Position.X += sprite.Rect.Width / 2; bone.Position.Y += sprite.Rect.Height / 2; } } instance.Bones.Clear(); instance.Bones.Capacity = sprite.Bones.Count; foreach (ISpriteBone bone in sprite.Bones) { instance.Bones.AddNew().CopyValues(bone); } // NOTE: sprite ID is generated by sprite binary content, but we just generate a random value instance.SpriteID = Guid.NewGuid().ToString("N"); instance.SetBoneGeometry(sprite); } } private static void SetBoneGeometry(this ISpriteMetaData instance, ISprite origin) { Vector3[]? vertices = null; BoneWeight4[]? skin = null; if (origin.RD.Has_VertexData()) { VertexDataBlob.Create(origin.RD.VertexData, origin.Collection.Version, origin.Collection.EndianType).ReadData( out vertices, out Vector3[]? _,//normals, out Vector4[]? _,//tangents, out ColorFloat[]? _,//colors, out skin, out Vector2[]? _,//uv0, out Vector2[]? _,//uv1, out Vector2[]? _,//uv2, out Vector2[]? _,//uv3, out Vector2[]? _,//uv4, out Vector2[]? _,//uv5, out Vector2[]? _,//uv6, out Vector2[]? _);//uv7); } if (instance.Has_Vertices()) { instance.Vertices.Clear(); // Convert Vector3f into Vector2f if (vertices is null) { instance.Vertices.Capacity = 0; } else { instance.Vertices.Capacity = vertices.Length; for (int i = 0; i < vertices.Length; i++) { Vector2f vertex = instance.Vertices.AddNew(); // Scale and translate vertices properly vertex.X = vertices[i].X * origin.PixelsToUnits + origin.Rect.Width / 2; vertex.Y = vertices[i].Y * origin.PixelsToUnits + origin.Rect.Height / 2; } } } if (instance.Has_Indices()) { instance.Indices.Clear(); if (origin.RD.Has_IndexBuffer() && origin.RD.IndexBuffer.Length != 0) { instance.Indices.Capacity = origin.RD.IndexBuffer.Length / 2; for (int i = 0, j = 0; i < origin.RD.IndexBuffer.Length / 2; i++, j += 2) { //Endianness might matter here instance.Indices.Add(BinaryPrimitives.ReadInt16LittleEndian(origin.RD.IndexBuffer.AsSpan(j, 2))); } } } #warning TODO: SpriteConverter does not generate instance.Edges if (instance.Has_Weights()) { instance.Weights.Clear(); if (skin is not null) { instance.Weights.EnsureCapacity(skin.Length); for (int i = 0; i < skin.Length; i++) { instance.Weights.AddNew().CopyValues(skin[i]); } } } } private static void GeneratePhysicsShape( ISprite sprite, ISpriteAtlas? atlas, RectangleF rect, Vector2 pivot, AssetList> shape) { if (sprite.Has_PhysicsShape() && sprite.PhysicsShape.Count > 0) { shape.Clear(); shape.Capacity = sprite.PhysicsShape.Count; float pivotShiftX = rect.Width * pivot.X - rect.Width * 0.5f; float pivotShiftY = rect.Height * pivot.Y - rect.Height * 0.5f; Vector2 pivotShift = new Vector2(pivotShiftX, pivotShiftY); for (int i = 0; i < sprite.PhysicsShape.Count; i++) { AssetList sourceList = sprite.PhysicsShape[i]; AssetList targetList = shape.AddNew(); targetList.Capacity = sourceList.Count; for (int j = 0; j < sprite.PhysicsShape[i].Count; j++) { Vector2 point = (Vector2)sourceList[j] * sprite.PixelsToUnits; targetList.AddNew().CopyValues(point + pivotShift); } } shape.FixRotation(sprite, atlas); } } private static void FixRotation(this AssetList> outlines, ISprite sprite, ISpriteAtlas? atlas) { GetPacking(sprite, atlas, out bool isPacked, out SpritePackingRotation rotation); if (isPacked) { switch (rotation) { case SpritePackingRotation.FlipHorizontal: { foreach (AssetList outline in outlines) { for (int i = 0; i < outline.Count; i++) { Vector2f vertex = outline[i]; outline[i].SetValues(-vertex.X, vertex.Y); } } } break; case SpritePackingRotation.FlipVertical: { foreach (AssetList outline in outlines) { for (int i = 0; i < outline.Count; i++) { Vector2f vertex = outline[i]; outline[i].SetValues(vertex.X, -vertex.Y); } } } break; case SpritePackingRotation.Rotate90: { foreach (AssetList outline in outlines) { for (int i = 0; i < outline.Count; i++) { Vector2f vertex = outline[i]; outline[i].SetValues(vertex.Y, vertex.X); } } } break; case SpritePackingRotation.Rotate180: { foreach (AssetList outline in outlines) { for (int i = 0; i < outline.Count; i++) { Vector2f vertex = outline[i]; outline[i].SetValues(-vertex.X, -vertex.Y); } } } break; } } } /// /// Pure /// /// /// /// /// private static void GetPacking(ISprite sprite, ISpriteAtlas? atlas, out bool isPacked, out SpritePackingRotation rotation) { if (atlas is not null && sprite.Has_RenderDataKey()) { ISpriteAtlasData atlasData = atlas.RenderDataMap[sprite.RenderDataKey]; isPacked = atlasData.IsPacked(); rotation = atlasData.GetPackingRotation(); } else { isPacked = sprite.RD.IsPacked(); rotation = sprite.RD.GetPackingRotation(); } } private static void GenerateOutline( ISprite sprite, ISpriteAtlas? atlas, RectangleF rect, Vector2 pivot, AssetList> outlines) { GenerateOutline(sprite.RD, sprite.Collection.Version, outlines); float pivotShiftX = rect.Width * pivot.X - rect.Width * 0.5f; float pivotShiftY = rect.Height * pivot.Y - rect.Height * 0.5f; Vector2 pivotShift = new Vector2(pivotShiftX, pivotShiftY); foreach (AssetList outline in outlines) { for (int i = 0; i < outline.Count; i++) { Vector2 point = (Vector2)outline[i] * sprite.PixelsToUnits; outline[i].CopyValues(point + pivotShift); } } outlines.FixRotation(sprite, atlas); } private static void GenerateOutline( ISpriteRenderData spriteRenderData, UnityVersion version, AssetList> outlines) { outlines.Clear(); if (spriteRenderData.Has_VertexData() && spriteRenderData.SubMeshes!.Count != 0) { for (int i = 0; i < spriteRenderData.SubMeshes.Count; i++) { Vector3[] vertices = spriteRenderData.VertexData.GenerateVertices(version, spriteRenderData.SubMeshes[i]); List vectorArrayList = VertexDataToOutline(spriteRenderData.IndexBuffer, vertices, spriteRenderData.SubMeshes[i]); outlines.AddRanges(vectorArrayList); } } else if (spriteRenderData.Has_Vertices() && spriteRenderData.Vertices.Count != 0) { List vectorArrayList = VerticesToOutline(spriteRenderData.Vertices, spriteRenderData.Indices); outlines.Capacity = vectorArrayList.Count; outlines.AddRanges(vectorArrayList); } } private static List VerticesToOutline(AccessListBase spriteVertexList, AssetList spriteIndexArray) { Vector3[] vertices = new Vector3[spriteVertexList.Count]; for (int i = 0; i < vertices.Length; i++) { vertices[i] = spriteVertexList[i].Pos; } Vector3i[] triangles = new Vector3i[spriteIndexArray.Count / 3]; for (int i = 0, j = 0; i < triangles.Length; i++) { int x = spriteIndexArray[j++]; int y = spriteIndexArray[j++]; int z = spriteIndexArray[j++]; triangles[i] = new Vector3i(x, y, z); } return new MeshOutlineGenerator(vertices, triangles).GenerateOutlines(); } private static List VertexDataToOutline(ReadOnlySpan indexBuffer, Vector3[] vertices, ISubMesh submesh) { Vector3i[] triangles = new Vector3i[submesh.IndexCount / 3]; for (int o = (int)submesh.FirstByte, ti = 0; ti < triangles.Length; o += 3 * sizeof(ushort), ti++) { ushort x = BitConverter.ToUInt16(indexBuffer[o..]); ushort y = BitConverter.ToUInt16(indexBuffer[(o + sizeof(ushort))..]); ushort z = BitConverter.ToUInt16(indexBuffer[(o + 2 * sizeof(ushort))..]); triangles[ti] = new Vector3i(x, y, z); } return new MeshOutlineGenerator(vertices, triangles).GenerateOutlines(); } private static void AddRanges(this AssetList> instance, List vectorArrayList) { foreach (Vector2[] vectorArray in vectorArrayList) { AssetList assetList = instance.AddNew(); assetList.Capacity = vectorArray.Length; foreach (Vector2 v in vectorArray) { assetList.AddNew().CopyValues(v); } } } } }