mirror of
https://github.com/AssetRipper/AssetRipper.git
synced 2025-12-11 20:15:29 +01:00
parent
e828517534
commit
1d8165d904
72
Source/AssetRipper.Processing/StaticMeshes/Bounds.cs
Normal file
72
Source/AssetRipper.Processing/StaticMeshes/Bounds.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
52
Source/AssetRipper.Processing/StaticMeshes/SubMeshData.cs
Normal file
52
Source/AssetRipper.Processing/StaticMeshes/SubMeshData.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user