diff --git a/Internal/MeshInfo2/MeshInfo2.cs b/Internal/MeshInfo2/MeshInfo2.cs index 731f2f9a4..ee2a7575e 100644 --- a/Internal/MeshInfo2/MeshInfo2.cs +++ b/Internal/MeshInfo2/MeshInfo2.cs @@ -331,48 +331,31 @@ public void ReadStaticMesh(Mesh mesh) Vertices.Clear(); for (var i = 0; i < mesh.vertexCount; i++) Vertices.Add(new Vertex()); - CopyVertexAttr(mesh.vertices, (x, v) => x.Position = v); - if (mesh.GetVertexAttributeDimension(VertexAttribute.Normal) != 0) - { - HasNormals = true; - CopyVertexAttr(mesh.normals, (x, v) => x.Normal = v); - } - if (mesh.GetVertexAttributeDimension(VertexAttribute.Tangent) != 0) - { - HasTangent = true; - CopyVertexAttr(mesh.tangents, (x, v) => x.Tangent = v); - } - if (mesh.GetVertexAttributeDimension(VertexAttribute.Color) != 0) - { - HasColor = true; - CopyVertexAttr(mesh.colors32, (x, v) => x.Color = v); - } + var vertexBuffers = GetVertexBuffers(mesh); + + CopyVertexAttr(mesh, VertexAttribute.Position, vertexBuffers, DataParsers.Vector3Provider, + setHasAttribute: null, + assign: (x, v) => x.Position = v); + + CopyVertexAttr(mesh, VertexAttribute.Normal, vertexBuffers, DataParsers.Vector3Provider, + setHasAttribute: _ => HasNormals = true, + assign: (x, v) => x.Normal = v); - var uv2 = new List(0); - var uv3 = new List(0); - var uv4 = new List(0); - for (var index = 0; index <= 7; index++) + CopyVertexAttr(mesh, VertexAttribute.Tangent, vertexBuffers, DataParsers.Vector4Provider, + setHasAttribute: _ => HasTangent = true, + assign: (x, v) => x.Tangent = v); + + // TODO: this may lost precision or HDR color + CopyVertexAttr(mesh, VertexAttribute.Color, vertexBuffers, DataParsers.Color32Provider, + setHasAttribute: _ => HasColor = true, + assign: (x, v) => x.Color = v); + + for (var uvChannel = 0; uvChannel <= 7; uvChannel++) { // ReSharper disable AccessToModifiedClosure - switch (mesh.GetVertexAttributeDimension(VertexAttribute.TexCoord0 + index)) - { - case 2: - SetTexCoordStatus(index, TexCoordStatus.Vector2); - mesh.GetUVs(index, uv2); - CopyVertexAttr(uv2, (x, v) => x.SetTexCoord(index, v)); - break; - case 3: - SetTexCoordStatus(index, TexCoordStatus.Vector3); - mesh.GetUVs(index, uv3); - CopyVertexAttr(uv3, (x, v) => x.SetTexCoord(index, v)); - break; - case 4: - SetTexCoordStatus(index, TexCoordStatus.Vector4); - mesh.GetUVs(index, uv4); - CopyVertexAttr(uv4, (x, v) => x.SetTexCoord(index, v)); - break; - } - + CopyVertexAttr(mesh, VertexAttribute.TexCoord0 + uvChannel, vertexBuffers, DataParsers.Vector4Provider, + setHasAttribute: dims => SetTexCoordStatus(uvChannel, TexCoordStatus.Vector2 + (dims - 2)), + assign: (x, v) => x.SetTexCoord(uvChannel, v)); // ReSharper restore AccessToModifiedClosure } @@ -388,16 +371,176 @@ public void ReadStaticMesh(Mesh mesh) Profiler.EndSample(); } - void CopyVertexAttr(T[] attributes, Action assign) + private static (byte[] buffer, int stride)[] GetVertexBuffers(Mesh mesh) { - for (var i = 0; i < attributes.Length; i++) - assign(Vertices[i], attributes[i]); + var vertexBufferCount = mesh.vertexBufferCount; + var vertexBuffers = new (byte[] buffer, int stride)[vertexBufferCount]; + for (var i = 0; i < vertexBufferCount; i++) + { + var vertexBuffer = mesh.GetVertexBuffer(i); + var stride = vertexBuffer.stride; + var data = new byte[vertexBuffer.count * vertexBuffer.stride]; + vertexBuffer.GetData(data); + vertexBuffers[i] = (data, stride); + } + + return vertexBuffers; + } + + delegate T DataParser(byte[] data, int offset); + delegate DataParser ReadDataProvider(VertexAttributeFormat format, int dimension); + + void CopyVertexAttr( + Mesh mesh, + VertexAttribute attribute, + (byte[] buffer, int stride)[] vertexDataList, + ReadDataProvider readProvider, + Action? setHasAttribute, + Action assign) + { + var dimension = mesh.GetVertexAttributeDimension(attribute); + + if (dimension == 0) + { + if (setHasAttribute == null) + throw new InvalidOperationException($"required attribute {attribute} does not exist"); + return; + } + + setHasAttribute?.Invoke(dimension); + + var format = mesh.GetVertexAttributeFormat(attribute); + var stream = mesh.GetVertexAttributeStream(attribute); + var offset = mesh.GetVertexAttributeOffset(attribute); + + var reader = readProvider(format, dimension); + + var stride = vertexDataList[stream].stride; + var buffer = vertexDataList[stream].buffer; + + for (var i = 0; i < Vertices.Count; i++) + { + var data = reader(buffer, stride * i + offset); + assign(Vertices[i], data); + } } - void CopyVertexAttr(List attributes, Action assign) + static class DataParsers { - for (var i = 0; i < attributes.Count; i++) - assign(Vertices[i], attributes[i]); + public static DataParser Vector3Provider(VertexAttributeFormat format, int dimension) + { + switch (dimension) + { + case 1: + var floatParser = FloatParser(format); + return (data, offset) => new Vector3(floatParser(data, offset), 0, 0); + case 2: + var vector2Parser = Vector2Parser(format); + return (data, offset) => vector2Parser(data, offset); + case 3: + return Vector3Parser(format); + case 4: + var vector4Parser = Vector4Parser(format); + return (data, offset) => vector4Parser(data, offset); + default: + throw new ArgumentOutOfRangeException(nameof(dimension), dimension, null); + } + } + + public static DataParser Vector4Provider(VertexAttributeFormat format, int dimension) + { + switch (dimension) + { + case 1: + var floatParser = FloatParser(format); + return (data, offset) => new Vector4(floatParser(data, offset), 0, 0); + case 2: + var vector2Parser = Vector2Parser(format); + return (data, offset) => vector2Parser(data, offset); + case 3: + var vector3Parser = Vector3Parser(format); + return (data, offset) => vector3Parser(data, offset); + case 4: + return Vector4Parser(format); + default: + throw new ArgumentOutOfRangeException(nameof(dimension), dimension, null); + } + } + + public static DataParser Color32Provider(VertexAttributeFormat format, int dimension) + { + var vector4Parser = Vector4Provider(format, dimension); + return (data, offset) => + { + var color = vector4Parser(data, offset); + color *= byte.MaxValue; + return new Color32((byte)color.x, (byte)color.y, (byte)color.z, (byte)color.w); + }; + } + + private static DataParser Vector4Parser(VertexAttributeFormat format) + { + var size = ValueSize(format); + var floatParser = FloatParser(format); + return (data, offset) => new Vector4( + floatParser(data, offset), + floatParser(data, offset + size), + floatParser(data, offset + size * 2), + floatParser(data, offset + size * 3)); + } + + private static DataParser Vector3Parser(VertexAttributeFormat format) + { + var size = ValueSize(format); + var floatParser = FloatParser(format); + return (data, offset) => new Vector3( + floatParser(data, offset), + floatParser(data, offset + size), + floatParser(data, offset + size * 2)); + } + + private static DataParser Vector2Parser(VertexAttributeFormat format) + { + var size = ValueSize(format); + var floatParser = FloatParser(format); + return (data, offset) => new Vector2( + floatParser(data, offset), + floatParser(data, offset + size)); + } + + private static int ValueSize(VertexAttributeFormat format) => + format switch + { + VertexAttributeFormat.Float32 or VertexAttributeFormat.UInt32 or VertexAttributeFormat.SInt32 => 4, + VertexAttributeFormat.Float16 or VertexAttributeFormat.UNorm16 or VertexAttributeFormat.SNorm16 + or VertexAttributeFormat.UInt16 or VertexAttributeFormat.SInt16 => 2, + VertexAttributeFormat.UNorm8 or VertexAttributeFormat.SNorm8 or VertexAttributeFormat.UInt8 + or VertexAttributeFormat.SInt8 => 1, + _ => throw new ArgumentOutOfRangeException(nameof(format), format, null) + }; + + // those casts are checked with this gist + // https://gist.github.com/anatawa12/e27775280a273f7f433c8b427aeb3108 + private static DataParser FloatParser(VertexAttributeFormat format) => + format switch + { + VertexAttributeFormat.Float32 => (data, offset) => BitConverter.ToSingle(data, offset), + VertexAttributeFormat.Float16 => (data, offset) => + Mathf.HalfToFloat(BitConverter.ToUInt16(data, offset)), + VertexAttributeFormat.UNorm8 => (data, offset) => data[offset] / (float)byte.MaxValue, + // -128 become -1.007874015748 is correct behaior + VertexAttributeFormat.SNorm8 => (data, offset) => (sbyte)data[offset] / (float)sbyte.MaxValue, + VertexAttributeFormat.UNorm16 => (data, offset) => BitConverter.ToUInt16(data, offset) / (float)ushort.MaxValue, + VertexAttributeFormat.SNorm16 => (data, offset) => BitConverter.ToInt16(data, offset) / (float)short.MaxValue, + VertexAttributeFormat.UInt8 => (data, offset) => data[offset], + VertexAttributeFormat.SInt8 => (data, offset) => (sbyte)data[offset], + VertexAttributeFormat.UInt16 => (data, offset) => BitConverter.ToUInt16(data, offset), + VertexAttributeFormat.SInt16 => (data, offset) => BitConverter.ToInt16(data, offset), + // TODO: Those can be loose precision + VertexAttributeFormat.UInt32 => (data, offset) => BitConverter.ToUInt32(data, offset), + VertexAttributeFormat.SInt32 => (data, offset) => BitConverter.ToInt32(data, offset), + _ => throw new ArgumentOutOfRangeException(nameof(format), format, null) + }; } private const int BitsPerTexCoordStatus = 2;