Fixed texture deduping

This commit is contained in:
2025-08-22 22:19:21 +02:00
parent ac0e4d8420
commit 0d1e363dbb
4 changed files with 139 additions and 83 deletions

View File

@@ -28,7 +28,7 @@ namespace SplashEdit.RuntimeCode
public PSXVertex v1; public PSXVertex v1;
public PSXVertex v2; public PSXVertex v2;
public PSXTexture2D Texture; public int TextureIndex;
public readonly PSXVertex[] Vertexes => new PSXVertex[] { v0, v1, v2 }; public readonly PSXVertex[] Vertexes => new PSXVertex[] { v0, v1, v2 };
} }
@@ -83,57 +83,52 @@ namespace SplashEdit.RuntimeCode
/// <param name="transform">Optional transform to convert vertices to world space.</param> /// <param name="transform">Optional transform to convert vertices to world space.</param>
/// <returns>A new PSXMesh containing the converted triangles.</returns> /// <returns>A new PSXMesh containing the converted triangles.</returns>
public static PSXMesh CreateFromUnityRenderer(Renderer renderer, float GTEScaling, Transform transform, List<PSXTexture2D> textures) public static PSXMesh CreateFromUnityRenderer(Renderer renderer, float GTEScaling, Transform transform, List<PSXTexture2D> textures)
{ {
PSXMesh psxMesh = new PSXMesh { Triangles = new List<Tri>() }; PSXMesh psxMesh = new PSXMesh { Triangles = new List<Tri>() };
// Get materials and mesh.
Material[] materials = renderer.sharedMaterials; Material[] materials = renderer.sharedMaterials;
Mesh mesh = renderer.GetComponent<MeshFilter>().sharedMesh; Mesh mesh = renderer.GetComponent<MeshFilter>().sharedMesh;
// Iterate over each submesh.
for (int submeshIndex = 0; submeshIndex < materials.Length; submeshIndex++) for (int submeshIndex = 0; submeshIndex < materials.Length; submeshIndex++)
{ {
// Get the triangles for this submesh.
int[] submeshTriangles = mesh.GetTriangles(submeshIndex); int[] submeshTriangles = mesh.GetTriangles(submeshIndex);
// Get the material for this submesh.
Material material = materials[submeshIndex]; Material material = materials[submeshIndex];
// Get the corresponding texture for this material (assume mainTexture).
Texture2D texture = material.mainTexture as Texture2D; Texture2D texture = material.mainTexture as Texture2D;
PSXTexture2D psxTexture = null;
// Find texture index instead of the texture itself
int textureIndex = -1;
if (texture != null) if (texture != null)
{ {
// Find the corresponding PSX texture based on the Unity texture. for (int i = 0; i < textures.Count; i++)
psxTexture = textures.FirstOrDefault(t => t.OriginalTexture == texture); {
if (textures[i].OriginalTexture == texture)
{
textureIndex = i;
break;
}
}
} }
if (psxTexture == null) if (textureIndex == -1)
{ {
continue; continue;
} }
// Get mesh data arrays. // Get mesh data arrays
mesh.RecalculateNormals(); mesh.RecalculateNormals();
Vector3[] vertices = mesh.vertices; Vector3[] vertices = mesh.vertices;
Vector3[] normals = mesh.normals; Vector3[] normals = mesh.normals;
Vector3[] smoothNormals = RecalculateSmoothNormals(mesh); Vector3[] smoothNormals = RecalculateSmoothNormals(mesh);
Vector2[] uv = mesh.uv; Vector2[] uv = mesh.uv;
PSXVertex convertData(int index) PSXVertex convertData(int index)
{ {
// Scale the vertex based on world scale.
Vector3 v = Vector3.Scale(vertices[index], transform.lossyScale); Vector3 v = Vector3.Scale(vertices[index], transform.lossyScale);
// Transform the vertex to world space.
Vector3 wv = transform.TransformPoint(vertices[index]); Vector3 wv = transform.TransformPoint(vertices[index]);
// Transform the normals to world space.
Vector3 wn = transform.TransformDirection(smoothNormals[index]).normalized; Vector3 wn = transform.TransformDirection(smoothNormals[index]).normalized;
// Compute lighting for each vertex.
Color c = PSXLightingBaker.ComputeLighting(wv, wn); 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], textures[textureIndex]?.Width, textures[textureIndex]?.Height, c);
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) for (int i = 0; i < submeshTriangles.Length; i += 3)
{ {
int vid0 = submeshTriangles[i]; int vid0 = submeshTriangles[i];
@@ -147,13 +142,17 @@ namespace SplashEdit.RuntimeCode
(vid1, vid2) = (vid2, vid1); (vid1, vid2) = (vid2, vid1);
} }
// Add the constructed triangle to the mesh. psxMesh.Triangles.Add(new Tri {
psxMesh.Triangles.Add(new Tri { v0 = convertData(vid0), v1 = convertData(vid1), v2 = convertData(vid2), Texture = psxTexture }); v0 = convertData(vid0),
v1 = convertData(vid1),
v2 = convertData(vid2),
TextureIndex = textureIndex
});
} }
} }
return psxMesh; return psxMesh;
} }
/// <summary> /// <summary>
/// Converts a Unity vertex into a PSXVertex by applying fixed-point conversion, shading, and UV mapping. /// Converts a Unity vertex into a PSXVertex by applying fixed-point conversion, shading, and UV mapping.

View File

@@ -118,6 +118,16 @@ namespace SplashEdit.RuntimeCode
return texture2D; return texture2D;
} }
public PSXTexture2D GetTexture(int index)
{
if (index >= 0 && index < Textures.Count)
{
return Textures[index];
}
return null;
}
/// <summary> /// <summary>
/// Converts the object's mesh into a PlayStation-compatible mesh. /// Converts the object's mesh into a PlayStation-compatible mesh.
/// </summary> /// </summary>

View File

@@ -320,7 +320,7 @@ namespace SplashEdit.RuntimeCode
foreach (Tri tri in exporter.Mesh.Triangles) 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 // Write vertices coordinates
foreachVertexDo(tri, (v) => writeVertexPosition(v)); foreachVertexDo(tri, (v) => writeVertexPosition(v));
@@ -331,19 +331,19 @@ namespace SplashEdit.RuntimeCode
foreachVertexDo(tri, (v) => writeVertexColor(v)); foreachVertexDo(tri, (v) => writeVertexColor(v));
// Write UVs for each vertex, adjusting for texture packing // 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 writer.Write((ushort)0); // padding
TPageAttr tpage = new TPageAttr(); TPageAttr tpage = new TPageAttr();
tpage.SetPageX(tri.Texture.TexpageX); tpage.SetPageX(exporter.GetTexture(tri.TextureIndex).TexpageX);
tpage.SetPageY(tri.Texture.TexpageY); tpage.SetPageY(exporter.GetTexture(tri.TextureIndex).TexpageY);
tpage.Set(tri.Texture.BitDepth.ToColorMode()); tpage.Set(exporter.GetTexture(tri.TextureIndex).BitDepth.ToColorMode());
tpage.SetDithering(true); tpage.SetDithering(true);
writer.Write((ushort)tpage.info); writer.Write((ushort)tpage.info);
writer.Write((ushort)tri.Texture.ClutPackingX); writer.Write((ushort)exporter.GetTexture(tri.TextureIndex).ClutPackingX);
writer.Write((ushort)tri.Texture.ClutPackingY); writer.Write((ushort)exporter.GetTexture(tri.TextureIndex).ClutPackingY);
writer.Write((ushort)0); writer.Write((ushort)0);
} }
} }

View File

@@ -74,8 +74,9 @@ namespace SplashEdit.RuntimeCode
allTextures.AddRange(obj.Textures); allTextures.AddRange(obj.Textures);
} }
// List to track unique textures. // List to track unique textures and their indices
List<PSXTexture2D> uniqueTextures = new List<PSXTexture2D>(); List<PSXTexture2D> uniqueTextures = new List<PSXTexture2D>();
Dictionary<(int, PSXBPP), int> textureToIndexMap = new Dictionary<(int, PSXBPP), int>();
// Group textures by bit depth (highest first). // Group textures by bit depth (highest first).
var texturesByBitDepth = allTextures var texturesByBitDepth = allTextures
@@ -101,10 +102,12 @@ namespace SplashEdit.RuntimeCode
// Process each texture in descending order of area. // Process each texture in descending order of area.
foreach (var texture in group.OrderByDescending(tex => tex.QuantizedWidth * tex.Height)) foreach (var texture in group.OrderByDescending(tex => tex.QuantizedWidth * tex.Height))
{ {
// Remove duplicate textures var textureKey = (texture.OriginalTexture.GetInstanceID(), texture.BitDepth);
if (uniqueTextures.Any(tex => tex.OriginalTexture.GetInstanceID() == texture.OriginalTexture.GetInstanceID() && tex.BitDepth == 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; continue;
} }
@@ -120,20 +123,64 @@ namespace SplashEdit.RuntimeCode
continue; continue;
} }
} }
// Add to unique textures and map
int newIndex = uniqueTextures.Count;
uniqueTextures.Add(texture); 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) foreach (var obj in objects)
{ {
// Create a mapping from old texture indices to new indices for this object
Dictionary<int, int> oldToNewIndexMap = new Dictionary<int, int>();
List<PSXTexture2D> newTextures = new List<PSXTexture2D>();
for (int i = 0; i < obj.Textures.Count; i++) for (int i = 0; i < obj.Textures.Count; i++)
{ {
var unique = uniqueTextures.FirstOrDefault(tex => tex.OriginalTexture.GetInstanceID() == obj.Textures[i].OriginalTexture.GetInstanceID() && var textureKey = (obj.Textures[i].OriginalTexture.GetInstanceID(), obj.Textures[i].BitDepth);
tex.BitDepth == obj.Textures[i].BitDepth); if (textureToIndexMap.TryGetValue(textureKey, out int newIndex))
if (unique != null)
{ {
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;
}
} }
} }
} }