Support 16 bit indices in static mesh separation

* Resolves #659
This commit is contained in:
Jeremy Pritts 2023-09-19 19:02:03 -04:00
parent e828517534
commit 1d8165d904
8 changed files with 192 additions and 71 deletions

View File

@ -0,0 +1,72 @@
using AssetRipper.SourceGenerated.Extensions;
using AssetRipper.SourceGenerated.Subclasses.AABB;
using System.Numerics;
namespace AssetRipper.Processing.StaticMeshes;
public record struct Bounds(
Vector3 Center,
Vector3 Extent)
{
public static implicit operator Bounds(AABB aabb)
{
return new Bounds(aabb.Center, aabb.Extent);
}
public readonly void CopyTo(AABB destination)
{
destination.Center.CopyValues(Center);
destination.Extent.CopyValues(Extent);
}
public static Bounds CalculateFromVertexArray(ReadOnlySpan<Vector3> vertices)
{
if (vertices.Length == 0)
{
return default;
}
else
{
Vector3 first = vertices[0];
float minX = first.X;
float minY = first.Y;
float minZ = first.Z;
float maxX = first.X;
float maxY = first.Y;
float maxZ = first.Z;
for (int i = 1; i < vertices.Length; i++)
{
Vector3 vertex = vertices[i];
if (vertex.X < minX)
{
minX = vertex.X;
}
else if (vertex.X > maxX)
{
maxX = vertex.X;
}
if (vertex.Y < minY)
{
minY = vertex.Y;
}
else if (vertex.Y > maxY)
{
maxY = vertex.Y;
}
if (vertex.Z < minZ)
{
minZ = vertex.Z;
}
else if (vertex.Z > maxZ)
{
maxZ = vertex.Z;
}
}
Vector3 center = new Vector3((maxX + minX) / 2, (maxY + minY) / 2, (maxZ + minZ) / 2);
Vector3 extent = new Vector3((maxX - minX) / 2, (maxY - minY) / 2, (maxZ - minZ) / 2);
return new(center, extent);
}
}
}

View File

@ -1,6 +1,7 @@
using AssetRipper.Assets.Generics;
using AssetRipper.Numerics;
using AssetRipper.SourceGenerated.Classes.ClassID_43;
using AssetRipper.SourceGenerated.Enums;
using AssetRipper.SourceGenerated.Extensions;
using AssetRipper.SourceGenerated.Subclasses.SubMesh;
using System.Numerics;
@ -25,7 +26,7 @@ namespace AssetRipper.Processing.StaticMeshes
/// <param name="UV7"></param>
/// <param name="BindPose"></param>
/// <param name="ProcessedIndexBuffer"></param>
/// <param name="SubMeshes">For <see cref="ISubMesh.FirstByte"/>, the value is standardized for 32 bit indices.</param>
/// <param name="SubMeshes"></param>
internal readonly record struct MeshData(
Vector3[] Vertices,
Vector3[]? Normals,
@ -42,7 +43,7 @@ namespace AssetRipper.Processing.StaticMeshes
Vector2[]? UV7,
Matrix4x4[]? BindPose,
uint[] ProcessedIndexBuffer,
ISubMesh[] SubMeshes)
SubMeshData[] SubMeshes)
{
public static bool TryMakeFromMesh(IMesh mesh, out MeshData meshData)
{
@ -63,7 +64,7 @@ namespace AssetRipper.Processing.StaticMeshes
out Matrix4x4[]? bindpose,
out uint[] processedIndexBuffer);
ISubMesh[] subMeshes = GetDuplicatedSubMeshes(mesh);
SubMeshData[] subMeshes = GetSubMeshArray(mesh);
if (vertices is null)
{
@ -100,7 +101,7 @@ namespace AssetRipper.Processing.StaticMeshes
};
}
[return: NotNullIfNotNull("array")]
[return: NotNullIfNotNull(nameof(array))]
private static T[]? DuplicateArray<T>(T[]? array)
{
if (array is null)
@ -119,42 +120,19 @@ namespace AssetRipper.Processing.StaticMeshes
}
}
private static ISubMesh[] DuplicateArray(ISubMesh[] array)
{
if (array.Length == 0)
{
return Array.Empty<ISubMesh>();
}
else
{
ISubMesh[] arrayCopy = new ISubMesh[array.Length];
for (int i = 0; i < array.Length; i++)
{
arrayCopy[i] = array[i].DeepClone();
}
return arrayCopy;
}
}
private static ISubMesh[] GetDuplicatedSubMeshes(IMesh mesh)
private static SubMeshData[] GetSubMeshArray(IMesh mesh)
{
AccessListBase<ISubMesh> list = mesh.SubMeshes_C43;
if (list.Count == 0)
{
return Array.Empty<ISubMesh>();
return Array.Empty<SubMeshData>();
}
else
{
bool is16Bit = mesh.Is16BitIndices();
ISubMesh[] array = new ISubMesh[list.Count];
SubMeshData[] array = new SubMeshData[list.Count];
for (int i = 0; i < list.Count; i++)
{
ISubMesh subMesh = list[i].DeepClone();
if (is16Bit)
{
subMesh.FirstByte *= 2;//After this point, FirstByte is assumed to be FirstIndex * sizeof(uint)
}
array[i] = subMesh;
array[i] = SubMeshData.Create(list[i], mesh.IndexFormat_C43E);
}
return array;
}
@ -206,12 +184,12 @@ namespace AssetRipper.Processing.StaticMeshes
processedIndexBuffer[i] = (uint)i;
}
ISubMesh[] subMeshes = DuplicateArray(SubMeshes);
SubMeshData[] subMeshes = DuplicateArray(SubMeshes);
for (int i = 0; i < subMeshes.Length; i++)
{
ISubMesh subMesh = subMeshes[i];
SubMeshData subMesh = subMeshes[i];
subMesh.VertexCount = subMesh.IndexCount;
subMesh.FirstVertex = subMesh.FirstByte / sizeof(uint);
subMesh.FirstVertex = subMesh.FirstIndex;
subMesh.BaseVertex = 0;//I'm concerned about this. This always seems to be 0 in static meshes,
//but that doesn't mean 0 is an appropriate value here. Given that this method is used primarily on
@ -248,5 +226,17 @@ namespace AssetRipper.Processing.StaticMeshes
return true;
}
}
public IndexFormat GetIndexFormat()
{
if (Vertices.Length > ushort.MaxValue)
{
return IndexFormat.UInt32;
}
else
{
return IndexFormat.UInt16;
}
}
}
}

View File

@ -321,12 +321,10 @@ namespace AssetRipper.Processing.StaticMeshes
private static IMesh MakeMeshFromData(string cleanName, MeshData instanceMeshData, ProcessedAssetCollection processedCollection)
{
const IndexFormat IndexFormat32Bit = IndexFormat.UInt32;
IMesh newMesh = CreateMesh(processedCollection);
newMesh.Name = cleanName;
newMesh.SetIndexFormat(IndexFormat32Bit);
newMesh.SetIndexFormat(instanceMeshData.GetIndexFormat());
ICompressedMesh compressedMesh = newMesh.CompressedMesh_C43;
compressedMesh.SetVertices(instanceMeshData.Vertices);
@ -348,8 +346,8 @@ namespace AssetRipper.Processing.StaticMeshes
newMesh.KeepIndices_C43 = true;//Not sure about this. Seems to be for animated meshes
newMesh.KeepVertices_C43 = true;//Not sure about this. Seems to be for animated meshes
newMesh.MeshMetrics_0__C43 = CalculateMeshMetric(instanceMeshData.Vertices, instanceMeshData.UV0, instanceMeshData.ProcessedIndexBuffer, instanceMeshData.SubMeshes, IndexFormat32Bit, 0);
newMesh.MeshMetrics_1__C43 = CalculateMeshMetric(instanceMeshData.Vertices, instanceMeshData.UV1, instanceMeshData.ProcessedIndexBuffer, instanceMeshData.SubMeshes, IndexFormat32Bit, 1);
newMesh.MeshMetrics_0__C43 = CalculateMeshMetric(instanceMeshData.Vertices, instanceMeshData.UV0, instanceMeshData.ProcessedIndexBuffer, instanceMeshData.SubMeshes, 0);
newMesh.MeshMetrics_1__C43 = CalculateMeshMetric(instanceMeshData.Vertices, instanceMeshData.UV1, instanceMeshData.ProcessedIndexBuffer, instanceMeshData.SubMeshes, 1);
newMesh.MeshUsageFlags_C43 = (int)SourceGenerated.NativeEnums.Global.MeshUsageFlags.MeshUsageFlagNone;
newMesh.CookingOptions_C43 = (int)SourceGenerated.NativeEnums.Global.MeshColliderCookingOptions.DefaultCookingFlags;
//I copied 30 from a vanilla compressed mesh (with MeshCompression.Low), and it aligned with this enum.
@ -357,9 +355,9 @@ namespace AssetRipper.Processing.StaticMeshes
newMesh.SetMeshCompression(ModelImporterMeshCompression.Low);
AccessListBase<ISubMesh> subMeshList = newMesh.SubMeshes_C43;
foreach (ISubMesh subMesh in instanceMeshData.SubMeshes)
foreach (SubMeshData subMesh in instanceMeshData.SubMeshes)
{
subMeshList.AddNew().CopyValues(subMesh);
subMesh.CopyTo(subMeshList.AddNew(), newMesh.GetIndexFormat());
}
newMesh.LocalAABB_C43.CalculateFromVertexArray(instanceMeshData.Vertices);
@ -367,7 +365,7 @@ namespace AssetRipper.Processing.StaticMeshes
return newMesh;
}
private static float CalculateMeshMetric(ReadOnlySpan<Vector3> vertexBuffer, ReadOnlySpan<Vector2> uvBuffer, uint[] indexBuffer, IReadOnlyList<ISubMesh> subMeshList, IndexFormat indexFormat, int uvSetIndex, float uvAreaThreshold = 1e-9f)
private static float CalculateMeshMetric(ReadOnlySpan<Vector3> vertexBuffer, ReadOnlySpan<Vector2> uvBuffer, uint[] indexBuffer, IReadOnlyList<SubMeshData> subMeshList, int uvSetIndex, float uvAreaThreshold = 1e-9f)
{
//https://docs.unity3d.com/ScriptReference/Mesh.GetUVDistributionMetric.html
//https://docs.unity3d.com/ScriptReference/Mesh.RecalculateUVDistributionMetric.html
@ -382,7 +380,7 @@ namespace AssetRipper.Processing.StaticMeshes
int n = 0;
float vertexAreaSum = 0.0f;
float uvAreaSum = 0.0f;
foreach ((uint ia, uint ib, uint ic) in new TriangleEnumerable(subMeshList[uvSetIndex], indexFormat, indexBuffer))
foreach ((uint ia, uint ib, uint ic) in new TriangleEnumerable(subMeshList[uvSetIndex], indexBuffer))
{
(Vector2 uva, Vector2 uvb, Vector2 uvc) = (uvBuffer[(int)ia], uvBuffer[(int)ib], uvBuffer[(int)ic]);
float uvArea = TriangleArea(uva, uvb, uvc);
@ -478,7 +476,7 @@ namespace AssetRipper.Processing.StaticMeshes
{
IReadOnlyList<uint> subMeshIndicies = renderer.GetSubmeshIndices();
uint count = 0;
int count = 0;
for (int i = 0; i < subMeshIndicies.Count; i++)
{
count += combinedMeshData.SubMeshes[(int)subMeshIndicies[i]].IndexCount;
@ -499,16 +497,16 @@ namespace AssetRipper.Processing.StaticMeshes
Vector2[]? uv7 = combinedMeshData.UV7.IsNullOrEmpty() ? null : new Vector2[count];
Matrix4x4[]? bindpose = combinedMeshData.BindPose.IsNullOrEmpty() ? null : new Matrix4x4[count];
uint[] processedIndexBuffer = new uint[count];
ISubMesh[] subMeshes = new ISubMesh[subMeshIndicies.Count];
SubMeshData[] subMeshes = new SubMeshData[subMeshIndicies.Count];
uint offset = 0;
int offset = 0;
for (int k = 0; k < subMeshIndicies.Count; k++)
{
ISubMesh combinedSubMesh = combinedMeshData.SubMeshes[(int)subMeshIndicies[k]];
int combinedIndexStart = (int)(combinedSubMesh.FirstByte / sizeof(uint));//FirstByte is standardized
SubMeshData combinedSubMesh = combinedMeshData.SubMeshes[(int)subMeshIndicies[k]];
int combinedIndexStart = combinedSubMesh.FirstIndex;
for (int j = 0; j < combinedSubMesh.IndexCount; j++)
{
int newIndex = j + (int)offset;
int newIndex = j + offset;
int combinedIndex = (int)combinedMeshData.ProcessedIndexBuffer[j + combinedIndexStart];
vertices[newIndex] = combinedMeshData.Vertices[combinedIndex];
SetUnlessNull(normals, newIndex, combinedMeshData.Normals, combinedIndex);
@ -526,12 +524,12 @@ namespace AssetRipper.Processing.StaticMeshes
SetUnlessNull(bindpose, newIndex, combinedMeshData.BindPose, combinedIndex);
processedIndexBuffer[newIndex] = (uint)newIndex;
}
ISubMesh newSubMesh = combinedSubMesh.DeepClone();
newSubMesh.FirstByte = offset * sizeof(uint);
SubMeshData newSubMesh = combinedSubMesh;
newSubMesh.FirstIndex = offset;
newSubMesh.FirstVertex = offset;
newSubMesh.VertexCount = newSubMesh.IndexCount;
//newSubMesh.BaseVertex //Might need set
newSubMesh.LocalAABB.CalculateFromVertexArray(new ReadOnlySpan<Vector3>(vertices, (int)offset, (int)newSubMesh.IndexCount));
newSubMesh.LocalBounds = Bounds.CalculateFromVertexArray(new ReadOnlySpan<Vector3>(vertices, (int)offset, (int)newSubMesh.IndexCount));
subMeshes[k] = newSubMesh;
offset += combinedSubMesh.IndexCount;

View File

@ -0,0 +1,52 @@
using AssetRipper.SourceGenerated.Enums;
using AssetRipper.SourceGenerated.Extensions;
using AssetRipper.SourceGenerated.Subclasses.SubMesh;
namespace AssetRipper.Processing.StaticMeshes;
/// <summary>
///
/// </summary>
/// <param name="BaseVertex"></param>
/// <param name="FirstIndex">Offset in the index buffer.</param>
/// <param name="FirstVertex">Offset in the vertex list.</param>
/// <param name="IndexCount"></param>
/// <param name="TriangleCount"></param>
/// <param name="VertexCount"></param>
/// <param name="Topology"></param>
/// <param name="LocalBounds"></param>
public record struct SubMeshData(
uint BaseVertex,
int FirstIndex,
int FirstVertex,
int IndexCount,
int TriangleCount,
int VertexCount,
MeshTopology Topology,
Bounds LocalBounds)
{
public static SubMeshData Create(ISubMesh subMesh, IndexFormat indexFormat)
{
return new SubMeshData(
subMesh.BaseVertex,
(int)subMesh.FirstByte / indexFormat.ToSize(),
(int)subMesh.FirstVertex,
(int)subMesh.IndexCount,
(int)subMesh.TriangleCount,
(int)subMesh.VertexCount,
subMesh.GetTopology(),
subMesh.LocalAABB);
}
public readonly void CopyTo(ISubMesh destination, IndexFormat indexFormat)
{
destination.BaseVertex = BaseVertex;
destination.FirstByte = (uint)(FirstIndex * indexFormat.ToSize());
destination.FirstVertex = (uint)FirstVertex;
destination.IndexCount = (uint)IndexCount;
destination.TriangleCount = (uint)TriangleCount;
destination.VertexCount = (uint)VertexCount;
destination.SetTopology(Topology);
LocalBounds.CopyTo(destination.LocalAABB);
}
}

View File

@ -1,15 +1,14 @@
using AssetRipper.SourceGenerated.Enums;
using AssetRipper.SourceGenerated.Subclasses.SubMesh;
using System.Collections;
namespace AssetRipper.SourceGenerated.Extensions;
namespace AssetRipper.Processing.StaticMeshes;
public readonly struct TriangleEnumerable : IEnumerable<(uint, uint, uint)>
{
private readonly MeshTopology topology;
private readonly ArraySegment<uint> indexBuffer;
public TriangleEnumerable(ISubMesh subMesh, IndexFormat indexFormat, uint[] indexBuffer) : this(subMesh.GetTopology(), GetIndexBufferSegment(subMesh, indexFormat, indexBuffer))
public TriangleEnumerable(SubMeshData subMesh, uint[] indexBuffer) : this(subMesh.Topology, GetIndexBufferSegment(subMesh, indexBuffer))
{
}
@ -76,16 +75,9 @@ public readonly struct TriangleEnumerable : IEnumerable<(uint, uint, uint)>
}
}
private static ArraySegment<uint> GetIndexBufferSegment(ISubMesh subMesh, IndexFormat indexFormat, uint[] indexBuffer)
private static ArraySegment<uint> GetIndexBufferSegment(SubMeshData subMesh, uint[] indexBuffer)
{
if (indexFormat == IndexFormat.UInt16)
{
return new ArraySegment<uint>(indexBuffer, (int)subMesh.FirstByte / sizeof(ushort), (int)subMesh.IndexCount);
}
else
{
return new ArraySegment<uint>(indexBuffer, (int)subMesh.FirstByte / sizeof(uint), (int)subMesh.IndexCount);
}
return new ArraySegment<uint>(indexBuffer, subMesh.FirstIndex, subMesh.IndexCount);
}
private static void ThrowIfNotSupported(MeshTopology topology)

View File

@ -5,12 +5,6 @@ namespace AssetRipper.SourceGenerated.Extensions
{
public static class AABBExtensions
{
public static void CopyValuesFrom(this IAABB instance, IAABB source)
{
instance.Center.CopyValues(source.Center);
instance.Extent.CopyValues(source.Extent);
}
public static void CopyValuesFrom(this IAABB instance, Vector3 center, Vector3 extent)
{
instance.Center.CopyValues(center);

View File

@ -0,0 +1,11 @@
using AssetRipper.SourceGenerated.Enums;
namespace AssetRipper.SourceGenerated.Extensions;
public static class IndexFormatExtensions
{
public static int ToSize(this IndexFormat instance)
{
return instance is IndexFormat.UInt16 ? sizeof(ushort) : sizeof(uint);
}
}

View File

@ -24,6 +24,18 @@ namespace AssetRipper.SourceGenerated.Extensions
return subMesh.Has_Topology() ? subMesh.TopologyE : subMesh.IsTriStripE;
}
public static void SetTopology(this ISubMesh subMesh, MeshTopology topology)
{
if (subMesh.Has_Topology())
{
subMesh.TopologyE = topology;
}
else
{
subMesh.IsTriStripE = topology;
}
}
private static void UpdateSubMeshVertexRange(UnityVersion version, IMesh mesh, ISubMesh submesh)
{
if (submesh.IndexCount == 0)