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 v2;
public PSXTexture2D Texture;
public int TextureIndex;
public readonly PSXVertex[] Vertexes => new PSXVertex[] { v0, v1, v2 };
}
@@ -85,55 +85,50 @@ namespace SplashEdit.RuntimeCode
public static PSXMesh CreateFromUnityRenderer(Renderer renderer, float GTEScaling, Transform transform, List<PSXTexture2D> textures)
{
PSXMesh psxMesh = new PSXMesh { Triangles = new List<Tri>() };
// Get materials and mesh.
Material[] materials = renderer.sharedMaterials;
Mesh mesh = renderer.GetComponent<MeshFilter>().sharedMesh;
// Iterate over each submesh.
for (int submeshIndex = 0; submeshIndex < materials.Length; submeshIndex++)
{
// 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;
// Find texture index instead of the texture itself
int textureIndex = -1;
if (texture != null)
{
// Find the corresponding PSX texture based on the Unity texture.
psxTexture = textures.FirstOrDefault(t => t.OriginalTexture == texture);
for (int i = 0; i < textures.Count; i++)
{
if (textures[i].OriginalTexture == texture)
{
textureIndex = i;
break;
}
}
}
if (psxTexture == null)
if (textureIndex == -1)
{
continue;
}
// Get mesh data arrays.
// 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);
return ConvertToPSXVertex(v, GTEScaling, normals[index], uv[index], textures[textureIndex]?.Width, textures[textureIndex]?.Height, c);
}
// Iterate through the triangles of the submesh.
for (int i = 0; i < submeshTriangles.Length; i += 3)
{
int vid0 = submeshTriangles[i];
@@ -147,8 +142,12 @@ namespace SplashEdit.RuntimeCode
(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 });
psxMesh.Triangles.Add(new Tri {
v0 = convertData(vid0),
v1 = convertData(vid1),
v2 = convertData(vid2),
TextureIndex = textureIndex
});
}
}

View File

@@ -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;
}
/// <summary>
/// Converts the object's mesh into a PlayStation-compatible mesh.
/// </summary>

View File

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

View File

@@ -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<PSXTexture2D> uniqueTextures = new List<PSXTexture2D>();
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<int, int> oldToNewIndexMap = new Dictionary<int, int>();
List<PSXTexture2D> newTextures = new List<PSXTexture2D>();
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;
}
}
}
}