2024-12-08 10:12:01 -08:00

833 lines
22 KiB
C#

using AssetRipper.IO.Endian;
using AssetRipper.Numerics;
using AssetRipper.SourceGenerated.Classes.ClassID_43;
using AssetRipper.SourceGenerated.Subclasses.ChannelInfo;
using AssetRipper.SourceGenerated.Subclasses.StreamInfo;
using AssetRipper.SourceGenerated.Subclasses.VertexData;
using System.Buffers.Binary;
using System.Numerics;
namespace AssetRipper.SourceGenerated.Extensions;
public readonly record struct VertexDataBlob(
IReadOnlyList<ChannelInfo> Channels,
IReadOnlyList<IStreamInfo> Streams,
byte[] Data,
int VertexCount,
UnityVersion Version,
EndianType EndianType)
{
private const int VertexStreamAlign = 16;
public static VertexDataBlob Create(IMesh mesh)
{
return Create(mesh.VertexData, mesh.Collection.Version, mesh.Collection.EndianType, mesh.GetVertexDataBytes());
}
public static VertexDataBlob Create(IVertexData vertexData, UnityVersion version, EndianType endianType)
{
return Create(vertexData, version, endianType, vertexData.Data);
}
private static VertexDataBlob Create(IVertexData vertexData, UnityVersion version, EndianType endianType, byte[] data)
{
IReadOnlyList<ChannelInfo> channels;
IReadOnlyList<IStreamInfo> streams;
if (vertexData.Has_Channels())
{
channels = vertexData.Channels;
streams = vertexData.GetStreams() ?? ConvertChannelsToStreams(vertexData.Channels, vertexData.VertexCount, version);
}
else
{
streams = vertexData.GetStreams()!;
channels = ConvertStreamsToChannels(streams, version);
}
return new VertexDataBlob(channels, streams, data, (int)vertexData.VertexCount, version, endianType);
}
public static VertexDataBlob Create(MeshData meshData, UnityVersion version, EndianType endianType)
{
if (meshData.Vertices.Length == 0)
{
return new VertexDataBlob([], [], [], 0, version, endianType);
}
byte[] data;
List<ChannelInfo> channels = new List<ChannelInfo>();
{
using MemoryStream memoryStream = new();
using EndianWriter writer = new(memoryStream, endianType);
if (version.GreaterThanOrEquals(2018))
{
//kShaderChannelVertex
channels.Add(CreateChannelInfoForFloatVector(3, 0, version));
Write(writer, meshData.Vertices);
WriteAlignmentBytes(writer);
//kShaderChannelNormal
if (meshData.HasNormals)
{
// Maybe this should be dimension 4? I've seen models with 4D normals. The W component is always 0.
channels.Add(CreateChannelInfoForFloatVector(3, 1, version));
Write(writer, meshData.Normals);
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 1, version));
}
//kShaderChannelTangent
if (meshData.HasTangents)
{
channels.Add(CreateChannelInfoForFloatVector(4, 2, version));
Write(writer, meshData.Tangents);
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 2, version));
}
//kShaderChannelColor
if (meshData.HasColors)
{
channels.Add(new ChannelInfo()
{
Dimension = GetColorDimension(version),
Offset = 0,
Format = (byte)MeshHelper.GetChannelFormat_Color(version),
Stream = 3,
});
for (int i = 0; i < meshData.Colors.Length; i++)
{
writer.Write((Color32)meshData.Colors[i]);
}
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 3, version));
}
//kShaderChannelTexCoord0
if (meshData.HasUV0)
{
channels.Add(CreateChannelInfoForFloatVector(2, 4, version));
Write(writer, meshData.UV0);
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 4, version));
}
//kShaderChannelTexCoord1
if (meshData.HasUV1)
{
channels.Add(CreateChannelInfoForFloatVector(2, 5, version));
Write(writer, meshData.UV1);
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 5, version));
}
//kShaderChannelTexCoord2
if (meshData.HasUV2)
{
channels.Add(CreateChannelInfoForFloatVector(2, 6, version));
Write(writer, meshData.UV2);
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 6, version));
}
//kShaderChannelTexCoord3
if (meshData.HasUV3)
{
channels.Add(CreateChannelInfoForFloatVector(2, 7, version));
Write(writer, meshData.UV3);
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 7, version));
}
//kShaderChannelTexCoord4
if (meshData.HasUV4)
{
channels.Add(CreateChannelInfoForFloatVector(2, 8, version));
Write(writer, meshData.UV4);
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 8, version));
}
//kShaderChannelTexCoord5
if (meshData.HasUV5)
{
channels.Add(CreateChannelInfoForFloatVector(2, 9, version));
Write(writer, meshData.UV5);
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 9, version));
}
//kShaderChannelTexCoord6
if (meshData.HasUV6)
{
channels.Add(CreateChannelInfoForFloatVector(2, 10, version));
Write(writer, meshData.UV6);
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 10, version));
}
//kShaderChannelTexCoord7
if (meshData.HasUV7)
{
channels.Add(CreateChannelInfoForFloatVector(2, 11, version));
Write(writer, meshData.UV7);
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 11, version));
}
if (version.GreaterThanOrEquals(2018, 2))
{
if (meshData.HasSkin)
{
//todo
//kShaderChannelBlendWeight
channels.Add(CreateChannelInfoForFloatVector(0, 12, version));
//kShaderChannelBlendIndices
channels.Add(CreateChannelInfoForFloatVector(0, 13, version));
}
else
{
//kShaderChannelBlendWeight
channels.Add(CreateChannelInfoForFloatVector(0, 12, version));
//kShaderChannelBlendIndices
channels.Add(CreateChannelInfoForFloatVector(0, 13, version));
}
}
}
else
{
//kShaderChannelVertex
channels.Add(CreateChannelInfoForFloatVector(3, 0, version));
Write(writer, meshData.Vertices);
WriteAlignmentBytes(writer);
//kShaderChannelNormal
if (meshData.HasNormals)
{
// Maybe this should be dimension 4? I've seen models with 4D normals. The W component is always 0.
channels.Add(CreateChannelInfoForFloatVector(3, 1, version));
Write(writer, meshData.Normals);
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 1, version));
}
//kShaderChannelColor
if (meshData.HasColors)
{
channels.Add(new ChannelInfo()
{
Dimension = GetColorDimension(version),
Offset = 0,
Format = (byte)MeshHelper.GetChannelFormat_Color(version),
Stream = 2,
});
for (int i = 0; i < meshData.Colors.Length; i++)
{
writer.Write((Color32)meshData.Colors[i]);
}
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 2, version));
}
//kShaderChannelTexCoord0
if (meshData.HasUV0)
{
channels.Add(CreateChannelInfoForFloatVector(2, 3, version));
Write(writer, meshData.UV0);
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 3, version));
}
//kShaderChannelTexCoord1
if (meshData.HasUV1)
{
channels.Add(CreateChannelInfoForFloatVector(2, 4, version));
Write(writer, meshData.UV1);
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 4, version));
}
if (version.LessThan(5))
{
//kShaderChannelTangent
if (meshData.HasTangents)
{
channels.Add(CreateChannelInfoForFloatVector(4, 5, version));
Write(writer, meshData.Tangents);
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 5, version));
}
}
else
{
//kShaderChannelTexCoord2
if (meshData.HasUV2)
{
channels.Add(CreateChannelInfoForFloatVector(2, 5, version));
Write(writer, meshData.UV2);
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 5, version));
}
//kShaderChannelTexCoord3
if (meshData.HasUV3)
{
channels.Add(CreateChannelInfoForFloatVector(2, 6, version));
Write(writer, meshData.UV3);
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 6, version));
}
//kShaderChannelTangent
if (meshData.HasTangents)
{
channels.Add(CreateChannelInfoForFloatVector(4, 7, version));
Write(writer, meshData.Tangents);
WriteAlignmentBytes(writer);
}
else
{
channels.Add(CreateChannelInfoForFloatVector(0, 7, version));
}
}
}
writer.Flush();
memoryStream.Flush();
data = memoryStream.ToArray();
}
IReadOnlyList<IStreamInfo> streams = ConvertChannelsToStreams(channels, (uint)meshData.Vertices.Length, version);
return new VertexDataBlob(channels, streams, data, meshData.Vertices.Length, version, endianType);
}
private static ChannelInfo CreateChannelInfoForFloatVector(byte dimension, byte streamIndex, UnityVersion version)
{
return new ChannelInfo()
{
Dimension = dimension,
Offset = 0,
Format = (byte)MeshHelper.ToChannelFormat(MeshHelper.VertexFormat.kVertexFormatFloat, version),
Stream = streamIndex,
};
}
private static byte GetColorDimension(UnityVersion version)
{
// Todo: Need to check various Unity versions
if (version.GreaterThanOrEquals(2018))
{
return 4;
}
else
{
return 1;
}
}
private static void Write(EndianWriter writer, Vector2[] vertices)
{
for (int i = 0; i < vertices.Length; i++)
{
writer.Write(vertices[i]);
}
}
private static void Write(EndianWriter writer, Vector3[] vertices)
{
for (int i = 0; i < vertices.Length; i++)
{
writer.Write(vertices[i]);
}
}
private static void Write(EndianWriter writer, Vector4[] vertices)
{
for (int i = 0; i < vertices.Length; i++)
{
writer.Write(vertices[i]);
}
}
private static void WriteAlignmentBytes(EndianWriter writer)
{
int align = VertexStreamAlign - (int)writer.BaseStream.Position % VertexStreamAlign;
if (align != VertexStreamAlign)
{
Span<byte> span = stackalloc byte[align];
span.Clear();
writer.Write(span);
}
}
public MeshData ToMeshData()
{
if (ReadData(
out Vector3[]? vertices,
out Vector3[]? normals,
out Vector4[]? tangents,
out ColorFloat[]? colors,
out BoneWeight4[]? skin,
out Vector2[]? uv0,
out Vector2[]? uv1,
out Vector2[]? uv2,
out Vector2[]? uv3,
out Vector2[]? uv4,
out Vector2[]? uv5,
out Vector2[]? uv6,
out Vector2[]? uv7))
{
return new MeshData(vertices, normals, tangents, colors, uv0, uv1, uv2, uv3, uv4, uv5, uv6, uv7, skin, []);
}
else
{
return MeshData.Empty;
}
}
public VertexDataBlob MakeLittleEndian()
{
if (EndianType == EndianType.LittleEndian)
{
return this;
}
MeshData meshData = ToMeshData();
return Create(meshData, Version, EndianType.LittleEndian);
}
public bool ReadData(
[NotNullWhen(true)] out Vector3[]? vertices,
out Vector3[]? normals,
out Vector4[]? tangents,
out ColorFloat[]? colors,
out BoneWeight4[]? skin,
out Vector2[]? uv0,
out Vector2[]? uv1,
out Vector2[]? uv2,
out Vector2[]? uv3,
out Vector2[]? uv4,
out Vector2[]? uv5,
out Vector2[]? uv6,
out Vector2[]? uv7)
{
int vertexCount = VertexCount;
vertices = default;
normals = default;
tangents = default;
colors = default;
skin = default;
uv0 = default;
uv1 = default;
uv2 = default;
uv3 = default;
uv4 = default;
uv5 = default;
uv6 = default;
uv7 = default;
byte[] data = Data;
if (data.Length == 0)
{
return false;
}
for (int chn = 0; chn < Channels.Count; chn++)
{
ChannelInfo channel = Channels[chn];
byte dimension = GetChannelDimension(channel, chn, Version);
if (dimension == 0)
{
continue;
}
IStreamInfo m_Stream = Streams[channel.Stream];
if (!GetBit(m_Stream.ChannelMask, chn))
{
continue;
}
MeshHelper.VertexFormat vertexFormat = MeshHelper.ToVertexFormat(channel.Format, Version);
int componentByteSize = MeshHelper.GetFormatSize(vertexFormat);
byte[] componentBytes = new byte[vertexCount * dimension * componentByteSize];
for (int v = 0; v < vertexCount; v++)
{
int vertexOffset = (int)m_Stream.Offset + channel.Offset + (int)m_Stream.GetStride() * v;
for (int d = 0; d < dimension; d++)
{
int componentOffset = vertexOffset + componentByteSize * d;
Buffer.BlockCopy(data, componentOffset, componentBytes, componentByteSize * (v * dimension + d), componentByteSize);
}
}
if (EndianType == EndianType.BigEndian && componentByteSize > 1) //swap bytes
{
if (componentBytes.Length % componentByteSize != 0)
{
throw new InvalidDataException("Component bytes length is not divisible by component byte size");
}
switch (componentByteSize)
{
case sizeof(ushort):
for (int i = componentBytes.Length - sizeof(ushort); i >= 0; i -= sizeof(ushort))
{
Span<byte> span = componentBytes.AsSpan(i);
ushort value = BinaryPrimitives.ReadUInt16BigEndian(span);
BinaryPrimitives.WriteUInt16LittleEndian(span, value);
}
break;
case sizeof(uint):
for (int i = componentBytes.Length - sizeof(uint); i >= 0; i -= sizeof(uint))
{
Span<byte> span = componentBytes.AsSpan(i);
uint value = BinaryPrimitives.ReadUInt32BigEndian(span);
BinaryPrimitives.WriteUInt32LittleEndian(span, value);
}
break;
}
}
int[] componentsIntArray;
float[] componentsFloatArray;
if (MeshHelper.IsIntFormat(vertexFormat))
{
componentsIntArray = MeshHelper.BytesToIntArray(componentBytes, vertexFormat);
componentsFloatArray = [];
}
else
{
componentsIntArray = [];
componentsFloatArray = MeshHelper.BytesToFloatArray(componentBytes, vertexFormat);
}
if (Version.GreaterThanOrEquals(2018))
{
switch (chn)
{
case 0: //kShaderChannelVertex
vertices = MeshHelper.FloatArrayToVector3(componentsFloatArray, dimension);
break;
case 1: //kShaderChannelNormal
normals = MeshHelper.FloatArrayToVector3(componentsFloatArray, dimension);
break;
case 2: //kShaderChannelTangent
tangents = MeshHelper.FloatArrayToVector4(componentsFloatArray, dimension);
break;
case 3: //kShaderChannelColor
colors = MeshHelper.FloatArrayToColorFloat(componentsFloatArray);
break;
case 4: //kShaderChannelTexCoord0
uv0 = MeshHelper.FloatArrayToVector2(componentsFloatArray, dimension);
break;
case 5: //kShaderChannelTexCoord1
uv1 = MeshHelper.FloatArrayToVector2(componentsFloatArray, dimension);
break;
case 6: //kShaderChannelTexCoord2
uv2 = MeshHelper.FloatArrayToVector2(componentsFloatArray, dimension);
break;
case 7: //kShaderChannelTexCoord3
uv3 = MeshHelper.FloatArrayToVector2(componentsFloatArray, dimension);
break;
case 8: //kShaderChannelTexCoord4
uv4 = MeshHelper.FloatArrayToVector2(componentsFloatArray, dimension);
break;
case 9: //kShaderChannelTexCoord5
uv5 = MeshHelper.FloatArrayToVector2(componentsFloatArray, dimension);
break;
case 10: //kShaderChannelTexCoord6
uv6 = MeshHelper.FloatArrayToVector2(componentsFloatArray, dimension);
break;
case 11: //kShaderChannelTexCoord7
uv7 = MeshHelper.FloatArrayToVector2(componentsFloatArray, dimension);
break;
//2018.2 and up
case 12: //kShaderChannelBlendWeight
skin ??= new BoneWeight4[vertexCount];
for (int i = 0; i < vertexCount; i++)
{
BoneWeight4 boneWeight = skin[i];
for (int j = 0; j < dimension; j++)
{
boneWeight.Weights[j] = componentsFloatArray[i * dimension + j];
}
skin[i] = boneWeight;
}
break;
case 13: //kShaderChannelBlendIndices
skin ??= new BoneWeight4[vertexCount];
for (int i = 0; i < vertexCount; i++)
{
BoneWeight4 boneWeight = skin[i];
for (int j = 0; j < dimension; j++)
{
boneWeight.Indices[j] = componentsIntArray[i * dimension + j];
}
skin[i] = boneWeight;
}
break;
}
}
else
{
switch (chn)
{
case 0: //kShaderChannelVertex
vertices = MeshHelper.FloatArrayToVector3(componentsFloatArray, dimension);
break;
case 1: //kShaderChannelNormal
normals = MeshHelper.FloatArrayToVector3(componentsFloatArray, dimension);
break;
case 2: //kShaderChannelColor
colors = MeshHelper.FloatArrayToColorFloat(componentsFloatArray);
break;
case 3: //kShaderChannelTexCoord0
uv0 = MeshHelper.FloatArrayToVector2(componentsFloatArray, dimension);
break;
case 4: //kShaderChannelTexCoord1
uv1 = MeshHelper.FloatArrayToVector2(componentsFloatArray, dimension);
break;
case 5:
if (Version.GreaterThanOrEquals(5)) //kShaderChannelTexCoord2
{
uv2 = MeshHelper.FloatArrayToVector2(componentsFloatArray, dimension);
}
else //kShaderChannelTangent
{
tangents = MeshHelper.FloatArrayToVector4(componentsFloatArray, dimension);
}
break;
case 6: //kShaderChannelTexCoord3
uv3 = MeshHelper.FloatArrayToVector2(componentsFloatArray, dimension);
break;
case 7: //kShaderChannelTangent
tangents = MeshHelper.FloatArrayToVector4(componentsFloatArray, dimension);
break;
}
}
}
return vertices is not null;
}
private static bool GetBit(uint mask, int index)
{
return (mask & (1u << index)) != 0;
}
public int GetStreamStride(int stream)
{
return (int)Streams[stream].GetStride();
}
public int GetStreamSize(int stream)
{
return GetStreamStride(stream) * VertexCount;
}
public int GetStreamOffset(int stream)
{
int offset = 0;
for (int i = 0; i < stream; i++)
{
offset += GetStreamSize(i);
offset = (offset + (VertexStreamAlign - 1)) & ~(VertexStreamAlign - 1);
}
return offset;
}
private static IStreamInfo[] ConvertChannelsToStreams(IReadOnlyList<ChannelInfo> channels, uint vertexCount, UnityVersion version)
{
int streamCount = channels.Max(x => x.Stream) + 1;
IStreamInfo[] streams = new IStreamInfo[streamCount];
long offset = 0;
for (int s = 0; s < streamCount; s++)
{
uint chnMask = 0;
uint stride = 0;
for (int chn = 0; chn < channels.Count; chn++)
{
ChannelInfo channel = channels[chn];
if (channel.Stream == s)
{
byte dimension = GetChannelDimension(channel, chn, version);
if (dimension > 0)
{
chnMask |= 1u << chn;
stride += dimension * (uint)MeshHelper.GetFormatSize(MeshHelper.ToVertexFormat(channel.Format, version));
}
}
}
StreamInfo_4 newStream = new()
{
ChannelMask = chnMask,
Offset = (uint)offset,
Stride_Byte = (byte)stride,
DividerOp = 0,
Frequency = 0,
};
streams[s] = newStream;
offset += vertexCount * stride;
offset = AlignStream(offset);
}
return streams;
}
private static byte GetChannelDimension(ChannelInfo channel, int chn, UnityVersion version)
{
if (version.LessThan(2018) && chn == 2 && channel.Format == 2) //kShaderChannelColor && kChannelFormatColor
{
return 4;
}
else
{
return channel.GetDataDimension();
}
}
private static IReadOnlyList<ChannelInfo> ConvertStreamsToChannels(IReadOnlyList<IStreamInfo> streams, UnityVersion version)
{
List<ChannelInfo> channels = new List<ChannelInfo>(6);
for (int s = 0; s < streams.Count; s++)
{
IStreamInfo m_Stream = streams[s];
byte offset = 0;
for (int i = 0; i < 6; i++)
{
if (!GetBit(m_Stream.ChannelMask, i))
{
continue;
}
ChannelInfo m_Channel = new();
m_Channel.Stream = (byte)s;
m_Channel.Offset = offset;
switch (i)
{
case 0: //kShaderChannelVertex
case 1: //kShaderChannelNormal
m_Channel.Format = 0; //kChannelFormatFloat
m_Channel.SetDataDimension(3);
break;
case 2: //kShaderChannelColor
m_Channel.Format = 2; //kChannelFormatColor
m_Channel.SetDataDimension(4);
break;
case 3: //kShaderChannelTexCoord0
case 4: //kShaderChannelTexCoord1
m_Channel.Format = 0; //kChannelFormatFloat
m_Channel.SetDataDimension(2);
break;
case 5: //kShaderChannelTangent
m_Channel.Format = 0; //kChannelFormatFloat
m_Channel.SetDataDimension(4);
break;
}
offset += (byte)(m_Channel.GetDataDimension() * MeshHelper.GetFormatSize(MeshHelper.ToVertexFormat(m_Channel.Format, version)));
channels.Add(m_Channel);
}
}
return channels;
}
private static long AlignStream(long offset)
{
return offset + (VertexStreamAlign - 1L) & ~(VertexStreamAlign - 1L);
}
public static bool SupportsUV2(UnityVersion version)
{
return version.GreaterThanOrEquals(5);
}
public static bool SupportsUV3(UnityVersion version)
{
return version.GreaterThanOrEquals(5);
}
public static bool SupportsUV4(UnityVersion version)
{
return version.GreaterThanOrEquals(2018);
}
public static bool SupportsUV5(UnityVersion version)
{
return version.GreaterThanOrEquals(2018);
}
public static bool SupportsUV6(UnityVersion version)
{
return version.GreaterThanOrEquals(2018);
}
public static bool SupportsUV7(UnityVersion version)
{
return version.GreaterThanOrEquals(2018);
}
public static bool SupportsSkin(UnityVersion version)
{
return version.GreaterThanOrEquals(2018, 2);
}
}