mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-15 05:04:15 +01:00
Flesh out geometry handling
This commit is contained in:
parent
81425b458e
commit
ca46e7482f
1 changed files with 131 additions and 18 deletions
|
|
@ -1,3 +1,5 @@
|
|||
using System.Collections.Immutable;
|
||||
using Lumina.Data.Parsing;
|
||||
using Lumina.Extensions;
|
||||
using OtterGui.Tasks;
|
||||
using Penumbra.GameData.Files;
|
||||
|
|
@ -62,17 +64,26 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
|
|||
|
||||
public void Execute(CancellationToken token)
|
||||
{
|
||||
var meshBuilder = new MeshBuilder<VertexPosition>("mesh");
|
||||
// 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));
|
||||
|
||||
// lol, lmao even
|
||||
var meshIndex = 2;
|
||||
var lod = 0;
|
||||
|
||||
var mesh = _mdl.Meshes[meshIndex];
|
||||
var submesh = _mdl.SubMeshes[mesh.SubMeshIndex]; // just first for now
|
||||
|
||||
|
|
@ -86,18 +97,7 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
|
|||
var indices = dataReader.ReadStructuresAsArray<ushort>((int)_mdl.IndexBufferSize[lod] / sizeof(ushort));
|
||||
|
||||
// read in verts for this mesh
|
||||
var baseOffset = _mdl.VertexOffset[lod] + mesh.VertexBufferOffset[positionVertexElement.Stream] + positionVertexElement.Offset;
|
||||
var vertices = new List<VertexPosition>();
|
||||
for (var vertexIndex = 0; vertexIndex < mesh.VertexCount; vertexIndex++)
|
||||
{
|
||||
dataReader.Seek(baseOffset + vertexIndex * mesh.VertexBufferStride[positionVertexElement.Stream]);
|
||||
// todo handle type
|
||||
vertices.Add(new VertexPosition(
|
||||
dataReader.ReadSingle(),
|
||||
dataReader.ReadSingle(),
|
||||
dataReader.ReadSingle()
|
||||
));
|
||||
}
|
||||
var vertices = BuildVertices(lod, mesh, _mdl.VertexDeclarations[meshIndex].VertexElements, geometryType);
|
||||
|
||||
// build a primitive for the submesh
|
||||
var primitiveBuilder = meshBuilder.UsePrimitive(material);
|
||||
|
|
@ -120,12 +120,125 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
|
|||
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)
|
||||
return false;
|
||||
|
||||
// TODO: compare configuration
|
||||
// TODO: compare configuration and such
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue