From 0d1e363dbbb9fab20b25967d6a106b3ca2f19fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20R=C3=A1=C4=8Dek?= Date: Fri, 22 Aug 2025 22:19:21 +0200 Subject: [PATCH] Fixed texture deduping --- Runtime/PSXMesh.cs | 133 +++++++++++++++++------------------ Runtime/PSXObjectExporter.cs | 10 +++ Runtime/PSXSceneExporter.cs | 14 ++-- Runtime/TexturePacker.cs | 65 ++++++++++++++--- 4 files changed, 139 insertions(+), 83 deletions(-) diff --git a/Runtime/PSXMesh.cs b/Runtime/PSXMesh.cs index 59e6c37..dc926d4 100644 --- a/Runtime/PSXMesh.cs +++ b/Runtime/PSXMesh.cs @@ -28,7 +28,7 @@ namespace SplashEdit.RuntimeCode public PSXVertex v1; public PSXVertex v2; - public PSXTexture2D Texture; + public int TextureIndex; public readonly PSXVertex[] Vertexes => new PSXVertex[] { v0, v1, v2 }; } @@ -82,79 +82,78 @@ namespace SplashEdit.RuntimeCode /// Height of the texture (default is 256). /// Optional transform to convert vertices to world space. /// A new PSXMesh containing the converted triangles. - public static PSXMesh CreateFromUnityRenderer(Renderer renderer, float GTEScaling, Transform transform, List textures) + public static PSXMesh CreateFromUnityRenderer(Renderer renderer, float GTEScaling, Transform transform, List textures) +{ + PSXMesh psxMesh = new PSXMesh { Triangles = new List() }; + Material[] materials = renderer.sharedMaterials; + Mesh mesh = renderer.GetComponent().sharedMesh; + + for (int submeshIndex = 0; submeshIndex < materials.Length; submeshIndex++) + { + int[] submeshTriangles = mesh.GetTriangles(submeshIndex); + Material material = materials[submeshIndex]; + Texture2D texture = material.mainTexture as Texture2D; + + // Find texture index instead of the texture itself + int textureIndex = -1; + if (texture != null) { - PSXMesh psxMesh = new PSXMesh { Triangles = new List() }; - - // Get materials and mesh. - Material[] materials = renderer.sharedMaterials; - Mesh mesh = renderer.GetComponent().sharedMesh; - - // Iterate over each submesh. - for (int submeshIndex = 0; submeshIndex < materials.Length; submeshIndex++) + for (int i = 0; i < textures.Count; i++) { - // Get the triangles for this submesh. - int[] submeshTriangles = mesh.GetTriangles(submeshIndex); - - // Get the material for this submesh. - Material material = materials[submeshIndex]; - - // Get the corresponding texture for this material (assume mainTexture). - Texture2D texture = material.mainTexture as Texture2D; - PSXTexture2D psxTexture = null; - - if (texture != null) + if (textures[i].OriginalTexture == texture) { - // Find the corresponding PSX texture based on the Unity texture. - psxTexture = textures.FirstOrDefault(t => t.OriginalTexture == texture); - } - - if (psxTexture == null) - { - continue; - } - - // Get mesh data arrays. - mesh.RecalculateNormals(); - Vector3[] vertices = mesh.vertices; - Vector3[] normals = mesh.normals; - Vector3[] smoothNormals = RecalculateSmoothNormals(mesh); - Vector2[] uv = mesh.uv; - PSXVertex convertData(int index) - { - // Scale the vertex based on world scale. - Vector3 v = Vector3.Scale(vertices[index], transform.lossyScale); - // Transform the vertex to world space. - Vector3 wv = transform.TransformPoint(vertices[index]); - // Transform the normals to world space. - Vector3 wn = transform.TransformDirection(smoothNormals[index]).normalized; - // Compute lighting for each vertex. - Color c = PSXLightingBaker.ComputeLighting(wv, wn); - // Convert vertex to PSX format, including fixed-point conversion and shading. - return ConvertToPSXVertex(v, GTEScaling, normals[index], uv[index], psxTexture?.Width, psxTexture?.Height, c); - } - // Iterate through the triangles of the submesh. - for (int i = 0; i < submeshTriangles.Length; i += 3) - { - int vid0 = submeshTriangles[i]; - int vid1 = submeshTriangles[i + 1]; - int vid2 = submeshTriangles[i + 2]; - - Vector3 faceNormal = Vector3.Cross(vertices[vid1] - vertices[vid0], vertices[vid2] - vertices[vid0]).normalized; - - if (Vector3.Dot(faceNormal, normals[vid0]) < 0) - { - (vid1, vid2) = (vid2, vid1); - } - - // Add the constructed triangle to the mesh. - psxMesh.Triangles.Add(new Tri { v0 = convertData(vid0), v1 = convertData(vid1), v2 = convertData(vid2), Texture = psxTexture }); + textureIndex = i; + break; } } - - return psxMesh; } + if (textureIndex == -1) + { + continue; + } + + // Get mesh data arrays + mesh.RecalculateNormals(); + Vector3[] vertices = mesh.vertices; + Vector3[] normals = mesh.normals; + Vector3[] smoothNormals = RecalculateSmoothNormals(mesh); + Vector2[] uv = mesh.uv; + + PSXVertex convertData(int index) + { + Vector3 v = Vector3.Scale(vertices[index], transform.lossyScale); + Vector3 wv = transform.TransformPoint(vertices[index]); + Vector3 wn = transform.TransformDirection(smoothNormals[index]).normalized; + Color c = PSXLightingBaker.ComputeLighting(wv, wn); + return ConvertToPSXVertex(v, GTEScaling, normals[index], uv[index], textures[textureIndex]?.Width, textures[textureIndex]?.Height, c); + } + + for (int i = 0; i < submeshTriangles.Length; i += 3) + { + int vid0 = submeshTriangles[i]; + int vid1 = submeshTriangles[i + 1]; + int vid2 = submeshTriangles[i + 2]; + + Vector3 faceNormal = Vector3.Cross(vertices[vid1] - vertices[vid0], vertices[vid2] - vertices[vid0]).normalized; + + if (Vector3.Dot(faceNormal, normals[vid0]) < 0) + { + (vid1, vid2) = (vid2, vid1); + } + + psxMesh.Triangles.Add(new Tri { + v0 = convertData(vid0), + v1 = convertData(vid1), + v2 = convertData(vid2), + TextureIndex = textureIndex + }); + } + } + + return psxMesh; +} + /// /// Converts a Unity vertex into a PSXVertex by applying fixed-point conversion, shading, and UV mapping. /// diff --git a/Runtime/PSXObjectExporter.cs b/Runtime/PSXObjectExporter.cs index 889fad7..fe080e2 100644 --- a/Runtime/PSXObjectExporter.cs +++ b/Runtime/PSXObjectExporter.cs @@ -118,6 +118,16 @@ namespace SplashEdit.RuntimeCode return texture2D; } + + public PSXTexture2D GetTexture(int index) + { + if (index >= 0 && index < Textures.Count) + { + return Textures[index]; + } + return null; + } + /// /// Converts the object's mesh into a PlayStation-compatible mesh. /// diff --git a/Runtime/PSXSceneExporter.cs b/Runtime/PSXSceneExporter.cs index c7da4aa..ddf0ed0 100644 --- a/Runtime/PSXSceneExporter.cs +++ b/Runtime/PSXSceneExporter.cs @@ -320,7 +320,7 @@ namespace SplashEdit.RuntimeCode foreach (Tri tri in exporter.Mesh.Triangles) { - int expander = 16 / ((int)tri.Texture.BitDepth); + int expander = 16 / ((int)exporter.GetTexture(tri.TextureIndex).BitDepth); // Write vertices coordinates foreachVertexDo(tri, (v) => writeVertexPosition(v)); @@ -331,19 +331,19 @@ namespace SplashEdit.RuntimeCode foreachVertexDo(tri, (v) => writeVertexColor(v)); // Write UVs for each vertex, adjusting for texture packing - foreachVertexDo(tri, (v) => writeVertexUV(v, tri.Texture, expander)); + foreachVertexDo(tri, (v) => writeVertexUV(v, exporter.GetTexture(tri.TextureIndex), expander)); writer.Write((ushort)0); // padding TPageAttr tpage = new TPageAttr(); - tpage.SetPageX(tri.Texture.TexpageX); - tpage.SetPageY(tri.Texture.TexpageY); - tpage.Set(tri.Texture.BitDepth.ToColorMode()); + tpage.SetPageX(exporter.GetTexture(tri.TextureIndex).TexpageX); + tpage.SetPageY(exporter.GetTexture(tri.TextureIndex).TexpageY); + tpage.Set(exporter.GetTexture(tri.TextureIndex).BitDepth.ToColorMode()); tpage.SetDithering(true); writer.Write((ushort)tpage.info); - writer.Write((ushort)tri.Texture.ClutPackingX); - writer.Write((ushort)tri.Texture.ClutPackingY); + writer.Write((ushort)exporter.GetTexture(tri.TextureIndex).ClutPackingX); + writer.Write((ushort)exporter.GetTexture(tri.TextureIndex).ClutPackingY); writer.Write((ushort)0); } } diff --git a/Runtime/TexturePacker.cs b/Runtime/TexturePacker.cs index 7772fca..7eb4c41 100644 --- a/Runtime/TexturePacker.cs +++ b/Runtime/TexturePacker.cs @@ -74,8 +74,9 @@ namespace SplashEdit.RuntimeCode allTextures.AddRange(obj.Textures); } - // List to track unique textures. + // List to track unique textures and their indices List uniqueTextures = new List(); + Dictionary<(int, PSXBPP), int> textureToIndexMap = new Dictionary<(int, PSXBPP), int>(); // Group textures by bit depth (highest first). var texturesByBitDepth = allTextures @@ -101,10 +102,12 @@ namespace SplashEdit.RuntimeCode // Process each texture in descending order of area. foreach (var texture in group.OrderByDescending(tex => tex.QuantizedWidth * tex.Height)) { - // Remove duplicate textures - if (uniqueTextures.Any(tex => tex.OriginalTexture.GetInstanceID() == texture.OriginalTexture.GetInstanceID() && tex.BitDepth == texture.BitDepth)) + var textureKey = (texture.OriginalTexture.GetInstanceID(), texture.BitDepth); + + // Check if we've already processed this texture + if (textureToIndexMap.TryGetValue(textureKey, out int existingIndex)) { - // Skip packing this texture – it will be replaced later. + // This texture is a duplicate, skip packing but remember the mapping continue; } @@ -120,20 +123,64 @@ namespace SplashEdit.RuntimeCode continue; } } + + // Add to unique textures and map + int newIndex = uniqueTextures.Count; uniqueTextures.Add(texture); + textureToIndexMap[textureKey] = newIndex; } } - // Now update every exporter so that duplicate textures reference the unique instance. + // Now update every exporter and their meshes to use the correct texture indices foreach (var obj in objects) { + // Create a mapping from old texture indices to new indices for this object + Dictionary oldToNewIndexMap = new Dictionary(); + List newTextures = new List(); + for (int i = 0; i < obj.Textures.Count; i++) { - var unique = uniqueTextures.FirstOrDefault(tex => tex.OriginalTexture.GetInstanceID() == obj.Textures[i].OriginalTexture.GetInstanceID() && - tex.BitDepth == obj.Textures[i].BitDepth); - if (unique != null) + var textureKey = (obj.Textures[i].OriginalTexture.GetInstanceID(), obj.Textures[i].BitDepth); + if (textureToIndexMap.TryGetValue(textureKey, out int newIndex)) { - obj.Textures[i] = unique; + oldToNewIndexMap[i] = newIndex; + + // Only add to new textures list if not already present + var texture = uniqueTextures[newIndex]; + if (!newTextures.Contains(texture)) + { + newTextures.Add(texture); + } + } + } + + // Replace the exporter's texture list with the deduplicated list + obj.Textures = newTextures; + + // Update all triangles in the mesh to use the new texture indices + if (obj.Mesh != null && obj.Mesh.Triangles != null) + { + for (int i = 0; i < obj.Mesh.Triangles.Count; i++) + { + var tri = obj.Mesh.Triangles[i]; + if (oldToNewIndexMap.TryGetValue(tri.TextureIndex, out int newGlobalIndex)) + { + // Find the index in the new texture list + var texture = uniqueTextures[newGlobalIndex]; + int finalIndex = newTextures.IndexOf(texture); + + // Create a new Tri with the updated TextureIndex + var updatedTri = new Tri + { + v0 = tri.v0, + v1 = tri.v1, + v2 = tri.v2, + TextureIndex = finalIndex + }; + + // Replace the tri in the list + obj.Mesh.Triangles[i] = updatedTri; + } } } }