Files
2026-07-13 13:13:17 +08:00

232 lines
8.9 KiB
C#

#nullable enable
using T3.Core.Rendering;
using T3.Core.Utils;
// ReSharper disable RedundantNameQualifier
namespace Lib.mesh.generate;
[Guid("be52b670-9749-4c0d-89f0-d8b101395227")]
internal sealed class LoadObj : Instance<LoadObj>, IDescriptiveFilename, IStatusProvider
{
[Output(Guid = "1F4E7CAC-1F62-4633-B0F3-A3017A026753")]
public readonly Slot<MeshBuffers?> Data = new();
public LoadObj()
{
_resource = new Resource<MeshDataSet>(Path, TryCreateResource, allowDisposal: false);
_resource.AddDependentSlots(Data);
Data.UpdateAction += Update;
}
private bool TryCreateResource(FileResource file, MeshDataSet? currentValue, [NotNullWhen(true)] out MeshDataSet? newValue, [NotNullWhen(false)]out string? failureReason)
{
var absolutePath = file.AbsolutePath;
if (_useGpuCaching)
{
if (_meshBufferCache.TryGetValue(absolutePath, out var cachedBuffer))
{
newValue = cachedBuffer;
failureReason = null;
return true;
}
}
if(!ObjMesh.TryLoadFromFile(absolutePath, out var mesh))
{
failureReason = $"Can't read file {absolutePath}";
Log.Warning(failureReason, this);
_warningMessage = failureReason;
newValue = null;
return false;
}
mesh.UpdateVertexSorting(_vertexSorting);
newValue = new MeshDataSet(mesh, _scaleFactor);
if (_useGpuCaching)
{
_meshBufferCache[absolutePath] = newValue;
}
else
{
currentValue?.Dispose();
}
failureReason = null;
return true;
}
private float _scaleFactor;
private void Update(EvaluationContext context)
{
if (ClearGPUCache.GetValue(context))
{
_meshBufferCache.Clear();
}
var shouldInvoke = SortVertices.DirtyFlag.IsDirty || UseGPUCaching.DirtyFlag.IsDirty;
var scaleFactor = ScaleFactor.GetValue(context);
var scaleHasChangedSignificantly = Math.Abs(scaleFactor - _scaleFactor) > 0.001f;
shouldInvoke |= scaleHasChangedSignificantly;
_scaleFactor = scaleFactor;
_vertexSorting = SortVertices.GetEnumValue<ObjMesh.SortDirections>(context);
_useGpuCaching = UseGPUCaching.GetValue(context);
if(shouldInvoke)
{
_resource.MarkFileAsChanged();
}
if (_resource.TryGetValue(context, out var meshDataSet))
{
Data.Value = meshDataSet.DataBuffers;
_warningMessage = string.Empty;
}
else
{
_warningMessage = $"Failed loading {SourcePathSlot.Value}";
}
}
public InputSlot<string> SourcePathSlot => Path;
private ObjMesh.SortDirections _vertexSorting;
private bool _useGpuCaching;
private sealed class MeshDataSet : IDisposable
{
public readonly MeshBuffers DataBuffers;
public MeshDataSet(ObjMesh mesh, float scaleFactor)
{
var vertexBufferWithViews = new BufferWithViews();
var indexBufferWithViews = new BufferWithViews();
var distinctVertices = mesh.DistinctDistinctVertices;
var verticesCount = distinctVertices.Count;
var faces = mesh.Faces;
var faceCount = faces.Count;
var sortedVertexIndices = mesh.SortedVertexIndices;
var vertexBufferData = new PbrVertex[verticesCount];
var indexBufferData = new Int3[faceCount];
var reversedLookup = new int[verticesCount];
// Create Vertex buffer
{
for (var vertexIndex = 0; vertexIndex < verticesCount; vertexIndex++)
{
var sortedVertexIndex = sortedVertexIndices[vertexIndex];
var sortedVertex = distinctVertices[sortedVertexIndex];
reversedLookup[sortedVertexIndex] = vertexIndex;
var distinctVertex = distinctVertices[sortedVertexIndex];
var colorRgb = mesh.Colors != null && mesh.Colors.Count > distinctVertex.PositionIndex
? new Vector3(mesh.Colors[distinctVertex.PositionIndex].X,
mesh.Colors[distinctVertex.PositionIndex].Y,
mesh.Colors[distinctVertex.PositionIndex].Z)
: Vector3.One;
vertexBufferData[vertexIndex] = new PbrVertex
{
Position = mesh.Positions[sortedVertex.PositionIndex] * scaleFactor,
Normal = mesh.Normals[sortedVertex.NormalIndex],
Tangent = mesh.VertexTangents[sortedVertexIndex],
Bitangent = mesh.VertexBinormals[sortedVertexIndex],
Texcoord = mesh.TexCoords[sortedVertex.TextureCoordsIndex],
Selection = 1,
ColorRgb = colorRgb,
};
}
Buffer? vertexBuffer = null;
ResourceManager.SetupStructuredBuffer(vertexBufferData, PbrVertex.Stride * verticesCount, PbrVertex.Stride,
ref vertexBuffer);
ResourceManager.CreateStructuredBufferSrv(vertexBuffer, ref vertexBufferWithViews.Srv);
ResourceManager.CreateStructuredBufferUav(vertexBuffer, UnorderedAccessViewBufferFlags.None, ref vertexBufferWithViews.Uav);
vertexBufferWithViews.Buffer = vertexBuffer;
}
// Create Index buffer
{
for (var faceIndex = 0; faceIndex < faceCount; faceIndex++)
{
var face = faces[faceIndex];
var v1Index = mesh.GetVertexIndex(face.V0, face.V0n, face.V0t);
var v2Index = mesh.GetVertexIndex(face.V1, face.V1n, face.V1t);
var v3Index = mesh.GetVertexIndex(face.V2, face.V2n, face.V2t);
indexBufferData[faceIndex]
= new Int3(reversedLookup[v1Index],
reversedLookup[v2Index],
reversedLookup[v3Index]);
}
Buffer? indexBuffer = null;
const int stride = 3 * 4;
ResourceManager.SetupStructuredBuffer(indexBufferData, stride * faceCount, stride, ref indexBuffer);
ResourceManager.CreateStructuredBufferSrv(indexBuffer, ref indexBufferWithViews.Srv);
ResourceManager.CreateStructuredBufferUav(indexBuffer, UnorderedAccessViewBufferFlags.None, ref indexBufferWithViews.Uav);
indexBufferWithViews.Buffer = indexBuffer;
}
DataBuffers = new MeshBuffers
{
VertexBuffer = vertexBufferWithViews,
IndicesBuffer = indexBufferWithViews
};
}
public void Dispose()
{
// Constructor may throw before DataBuffers is assigned.
DataBuffers?.Dispose();
GC.SuppressFinalize(this);
}
~MeshDataSet()
{
// Finalizers must never throw — would terminate the finalizer thread.
try { Dispose(); }
catch { /* swallow */ }
}
}
public IStatusProvider.StatusLevel GetStatusLevel()
{
return string.IsNullOrEmpty(_warningMessage) ? IStatusProvider.StatusLevel.Success : IStatusProvider.StatusLevel.Warning;
}
public string GetStatusMessage()
{
return _warningMessage;
}
private string _warningMessage = string.Empty;
private static readonly Dictionary<string, MeshDataSet> _meshBufferCache = new();
[Input(Guid = "7d576017-89bd-4813-bc9b-70214efe6a27")]
public readonly InputSlot<string> Path = new();
[Input(Guid = "FFD22736-A600-4C97-A4A4-AD3526B8B35C")]
public readonly InputSlot<bool> UseGPUCaching = new();
[Input(Guid = "DDD22736-A600-4C97-A4A4-AD3526B8B35C")]
public readonly InputSlot<bool> ClearGPUCache = new();
[Input(Guid = "AA19E71D-329C-448B-901C-565BF8C0DA4F", MappedType = typeof(ObjMesh.SortDirections))]
public readonly InputSlot<int> SortVertices = new();
[Input(Guid = "C39A61B3-FB6B-4611-8F13-273F13C9C491")]
public readonly InputSlot<float> ScaleFactor = new();
private readonly Resource<MeshDataSet> _resource;
}