Move mesh logic to new file, export all meshes

This commit is contained in:
ackwell 2023-12-28 02:15:14 +11:00
parent ca46e7482f
commit bc24110c9f
2 changed files with 205 additions and 169 deletions

View file

@ -1,11 +1,6 @@
using System.Collections.Immutable;
using Lumina.Data.Parsing;
using Lumina.Extensions;
using OtterGui.Tasks;
using Penumbra.GameData.Files;
using SharpGLTF.Geometry;
using SharpGLTF.Geometry.VertexTypes;
using SharpGLTF.Materials;
using Penumbra.Import.Modules;
using SharpGLTF.Scenes;
namespace Penumbra.Import.Models;
@ -64,175 +59,25 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
public void Execute(CancellationToken token)
{
// lol, lmao even
var meshIndex = 2;
var lod = 0;
var elements = _mdl.VertexDeclarations[meshIndex].VertexElements;
var usages = elements
.Select(element => (MdlFile.VertexUsage)element.Usage)
.ToImmutableHashSet();
var geometryType = GetGeometryType(usages);
// TODO: probablly can do this a bit later but w/e
var meshBuilderType = typeof(MeshBuilder<,,>).MakeGenericType(geometryType, typeof(VertexEmpty), typeof(VertexEmpty));
var meshBuilder = (IMeshBuilder<MaterialBuilder>)Activator.CreateInstance(meshBuilderType, "mesh2")!;
var material = new MaterialBuilder()
.WithDoubleSide(true)
.WithMetallicRoughnessShader()
.WithChannelParam(KnownChannel.BaseColor, KnownProperty.RGBA, new Vector4(1, 1, 1, 1));
var mesh = _mdl.Meshes[meshIndex];
var submesh = _mdl.SubMeshes[mesh.SubMeshIndex]; // just first for now
var positionVertexElement = _mdl.VertexDeclarations[meshIndex].VertexElements
.Where(decl => (MdlFile.VertexUsage)decl.Usage == MdlFile.VertexUsage.Position)
.First();
// reading in the entire indices list
var dataReader = new BinaryReader(new MemoryStream(_mdl.RemainingData));
dataReader.Seek(_mdl.IndexOffset[lod]);
var indices = dataReader.ReadStructuresAsArray<ushort>((int)_mdl.IndexBufferSize[lod] / sizeof(ushort));
// read in verts for this mesh
var vertices = BuildVertices(lod, mesh, _mdl.VertexDeclarations[meshIndex].VertexElements, geometryType);
// build a primitive for the submesh
var primitiveBuilder = meshBuilder.UsePrimitive(material);
// they're all tri list
for (var indexOffset = 0; indexOffset < submesh.IndexCount; indexOffset += 3)
{
var index = indexOffset + submesh.IndexOffset;
primitiveBuilder.AddTriangle(
vertices[indices[index + 0]],
vertices[indices[index + 1]],
vertices[indices[index + 2]]
);
}
var scene = new SceneBuilder();
scene.AddRigidMesh(meshBuilder, Matrix4x4.Identity);
// TODO: group by LoD in output tree
for (byte lodIndex = 0; lodIndex < _mdl.LodCount; lodIndex++)
{
var lod = _mdl.Lods[lodIndex];
// TODO: consider other types?
for (ushort meshOffset = 0; meshOffset < lod.MeshCount; meshOffset++)
{
var meshBuilder = MeshConverter.ToGltf(_mdl, lodIndex, (ushort)(lod.MeshIndex + meshOffset));
scene.AddRigidMesh(meshBuilder, Matrix4x4.Identity);
}
}
var model = scene.ToGltf2();
model.SaveGLTF(_path);
}
// todo all of this is mesh specific so probably should be a class per mesh? with the lod, too?
private IReadOnlyList<IVertexBuilder> BuildVertices(int lod, MdlStructs.MeshStruct mesh, IEnumerable<MdlStructs.VertexElement> elements, Type geometryType)
{
var vertexBuilderType = typeof(VertexBuilder<,,>).MakeGenericType(geometryType, typeof(VertexEmpty), typeof(VertexEmpty));
// todo: demagic the 3
// todo note this assumes that the buffer streams are tightly packed. that's a safe assumption - right? lumina assumes as much
var streams = new BinaryReader[3];
for (var streamIndex = 0; streamIndex < 3; streamIndex++)
{
streams[streamIndex] = new BinaryReader(new MemoryStream(_mdl.RemainingData));
streams[streamIndex].Seek(_mdl.VertexOffset[lod] + mesh.VertexBufferOffset[streamIndex]);
}
var sortedElements = elements
.OrderBy(element => element.Offset)
.ToList();
var vertices = new List<IVertexBuilder>();
// note this is being reused
var attributes = new Dictionary<MdlFile.VertexUsage, object>();
for (var vertexIndex = 0; vertexIndex < mesh.VertexCount; vertexIndex++)
{
attributes.Clear();
foreach (var element in sortedElements)
attributes[(MdlFile.VertexUsage)element.Usage] = ReadVertexAttribute(streams[element.Stream], element);
var vertexGeometry = BuildVertexGeometry(geometryType, attributes);
var vertexBuilder = (IVertexBuilder)Activator.CreateInstance(vertexBuilderType, vertexGeometry, new VertexEmpty(), new VertexEmpty())!;
vertices.Add(vertexBuilder);
}
return vertices;
}
// todo i fucking hate this `object` type god i hate c# gimme sum types pls
private object ReadVertexAttribute(BinaryReader reader, MdlStructs.VertexElement element)
{
return (MdlFile.VertexType)element.Type switch
{
MdlFile.VertexType.Single3 => new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()),
MdlFile.VertexType.Single4 => new Vector4(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()),
MdlFile.VertexType.UInt => reader.ReadBytes(4),
MdlFile.VertexType.ByteFloat4 => new Vector4(reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f),
MdlFile.VertexType.Half2 => new Vector2((float)reader.ReadHalf(), (float)reader.ReadHalf()),
MdlFile.VertexType.Half4 => new Vector4((float)reader.ReadHalf(), (float)reader.ReadHalf(), (float)reader.ReadHalf(), (float)reader.ReadHalf()),
_ => throw new ArgumentOutOfRangeException()
};
}
private Type GetGeometryType(IReadOnlySet<MdlFile.VertexUsage> usages)
{
if (!usages.Contains(MdlFile.VertexUsage.Position))
throw new Exception("Mesh does not contain position vertex elements.");
if (!usages.Contains(MdlFile.VertexUsage.Normal))
return typeof(VertexPosition);
if (!usages.Contains(MdlFile.VertexUsage.Tangent1))
return typeof(VertexPositionNormal);
return typeof(VertexPositionNormalTangent);
}
private IVertexGeometry BuildVertexGeometry(Type geometryType, IReadOnlyDictionary<MdlFile.VertexUsage, object> attributes)
{
if (geometryType == typeof(VertexPosition))
return new VertexPosition(
ToVector3(attributes[MdlFile.VertexUsage.Position])
);
if (geometryType == typeof(VertexPositionNormal))
return new VertexPositionNormal(
ToVector3(attributes[MdlFile.VertexUsage.Position]),
ToVector3(attributes[MdlFile.VertexUsage.Normal])
);
if (geometryType == typeof(VertexPositionNormalTangent))
return new VertexPositionNormalTangent(
ToVector3(attributes[MdlFile.VertexUsage.Position]),
ToVector3(attributes[MdlFile.VertexUsage.Normal]),
ToVector4(attributes[MdlFile.VertexUsage.Tangent1])
);
throw new Exception($"Unknown geometry type {geometryType}.");
}
private Vector3 ToVector3(object data)
{
return data switch
{
Vector2 v2 => new Vector3(v2.X, v2.Y, 0),
Vector3 v3 => v3,
Vector4 v4 => new Vector3(v4.X, v4.Y, v4.Z),
_ => throw new ArgumentOutOfRangeException($"Invalid Vector3 input {data}")
};
}
private Vector4 ToVector4(object data)
{
return data switch
{
Vector2 v2 => new Vector4(v2.X, v2.Y, 0, 0),
Vector3 v3 => new Vector4(v3.X, v3.Y, v3.Z, 1),
Vector4 v4 => v4,
_ => throw new ArgumentOutOfRangeException($"Invalid Vector3 input {data}")
};
}
public bool Equals(IAction? other)
{
if (other is not ExportToGltfAction rhs)