Compare commits
2 Commits
1a91811621
...
e3a02437c3
| Author | SHA1 | Date | |
|---|---|---|---|
| e3a02437c3 | |||
| 5903ddda2b |
@@ -1,6 +1,7 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Engine;
|
using Ghost.Engine;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
@@ -20,15 +21,15 @@ public class MeshNode : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MeshNode? Parent
|
public MeshNode? Parent
|
||||||
{
|
|
||||||
get; init;
|
|
||||||
}
|
|
||||||
|
|
||||||
public required IReadOnlyCollection<MeshNode> Children
|
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IReadOnlyCollection<MeshNode> Children
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
} = Array.Empty<MeshNode>();
|
||||||
|
|
||||||
~MeshNode()
|
~MeshNode()
|
||||||
{
|
{
|
||||||
Dispose(false);
|
Dispose(false);
|
||||||
@@ -45,15 +46,36 @@ public class MeshNode : IDisposable
|
|||||||
child.Dispose();
|
child.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Parent = null;
|
||||||
|
Children = Array.Empty<MeshNode>();
|
||||||
|
|
||||||
Dispose(true);
|
Dispose(true);
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes one material partition within a unified vertex/index buffer.
|
||||||
|
/// </summary>
|
||||||
|
public struct MaterialPartInfo
|
||||||
|
{
|
||||||
|
/// <summary> The material slot index (from ufbx face_material). </summary>
|
||||||
|
public int materialIndex;
|
||||||
|
/// <summary> Byte offset into the unified index buffer. </summary>
|
||||||
|
public int indexStart;
|
||||||
|
/// <summary> Number of indices belonging to this part. </summary>
|
||||||
|
public int indexCount;
|
||||||
|
/// <summary> Byte offset into the unified vertex buffer. </summary>
|
||||||
|
public int vertexStart;
|
||||||
|
/// <summary> Number of unique vertices belonging to this part. </summary>
|
||||||
|
public int vertexCount;
|
||||||
|
}
|
||||||
|
|
||||||
public class GeometryMeshNode : MeshNode
|
public class GeometryMeshNode : MeshNode
|
||||||
{
|
{
|
||||||
private UnsafeList<Vertex> _vertices;
|
private UnsafeList<Vertex> _vertices;
|
||||||
private UnsafeList<uint> _indices;
|
private UnsafeList<uint> _indices;
|
||||||
|
private UnsafeArray<MaterialPartInfo> _materialParts;
|
||||||
|
|
||||||
public UnsafeList<Vertex> Vertices
|
public UnsafeList<Vertex> Vertices
|
||||||
{
|
{
|
||||||
@@ -75,15 +97,21 @@ public class GeometryMeshNode : MeshNode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int MaterialIndex
|
public UnsafeArray<MaterialPartInfo> MaterialParts
|
||||||
{
|
{
|
||||||
get; set;
|
get => _materialParts;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_materialParts.Dispose();
|
||||||
|
_materialParts = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
_vertices.Dispose();
|
_vertices.Dispose();
|
||||||
_indices.Dispose();
|
_indices.Dispose();
|
||||||
|
_materialParts.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
|
using Ghost.Engine.Utilities;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Ghost.Graphics.Utilities;
|
using Ghost.Graphics.Utilities;
|
||||||
using Ghost.MeshOptimizer;
|
using Ghost.MeshOptimizer;
|
||||||
@@ -11,21 +12,32 @@ using Misaki.HighPerformance.LowLevel.Utilities;
|
|||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace Ghost.Editor.Core.Assets;
|
namespace Ghost.Editor.Core.Assets;
|
||||||
|
|
||||||
internal unsafe class MeshParsingWorkItem : IJob
|
internal readonly unsafe struct MeshParsingWorkItem : IJob
|
||||||
{
|
{
|
||||||
|
private struct GeometryPart : IDisposable
|
||||||
|
{
|
||||||
|
public UnsafeList<Vertex> vertices;
|
||||||
|
public UnsafeList<uint> indices;
|
||||||
|
public int materialIndex;
|
||||||
|
public bool missingNormals;
|
||||||
|
public bool missingTangents;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
vertices.Dispose();
|
||||||
|
indices.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly string _filePath;
|
private readonly string _filePath;
|
||||||
private readonly AllocationHandle _allocationHandle;
|
private readonly AllocationHandle _allocationHandle;
|
||||||
private readonly MeshAssetSettings _settings;
|
private readonly MeshAssetSettings _settings;
|
||||||
private readonly TaskCompletionSource<Result<MeshNode>> _taskCompletionSource;
|
private readonly TaskCompletionSource<Result<MeshNode>> _taskCompletionSource;
|
||||||
|
|
||||||
public UnsafeList<Vertex> vertices;
|
public readonly Task<Result<MeshNode>> Task => _taskCompletionSource.Task;
|
||||||
public UnsafeList<uint> indices;
|
|
||||||
|
|
||||||
public Task<Result<MeshNode>> Task => _taskCompletionSource.Task;
|
|
||||||
|
|
||||||
public MeshParsingWorkItem(string filePath, AllocationHandle allocationHandle, MeshAssetSettings settings)
|
public MeshParsingWorkItem(string filePath, AllocationHandle allocationHandle, MeshAssetSettings settings)
|
||||||
{
|
{
|
||||||
@@ -59,23 +71,64 @@ internal unsafe class MeshParsingWorkItem : IJob
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private GeometryMeshNode ParseGeometry(ufbx_mesh* pMesh)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static float4x4 ToFloat4x4(ufbx_vec3 t, ufbx_quat q, ufbx_vec3 s)
|
||||||
{
|
{
|
||||||
var meshNode = new GeometryMeshNode
|
return float4x4.TRS(
|
||||||
|
new float3(t.x, t.y, t.z),
|
||||||
|
new quaternion(q.x, q.y, q.z, q.w),
|
||||||
|
new float3(s.x, s.y, s.z)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MeshNode ParseHierarchy(ufbx_node* node)
|
||||||
{
|
{
|
||||||
Name = pMesh->name.ToString(),
|
var children = new List<MeshNode>();
|
||||||
Children = Array.Empty<MeshNode>(),
|
var meshNode = new MeshNode
|
||||||
|
{
|
||||||
|
Name = node->name.ToString(),
|
||||||
|
LocalTransform = ToFloat4x4(node->local_transform.translation, node->local_transform.rotation, node->local_transform.scale),
|
||||||
|
Children = children
|
||||||
};
|
};
|
||||||
|
|
||||||
if (pMesh->num_faces == 0)
|
if (node->mesh != null)
|
||||||
{
|
{
|
||||||
|
var geoNode = ParseGeometry(node->mesh);
|
||||||
|
if (geoNode != null)
|
||||||
|
{
|
||||||
|
children.Add(geoNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle lights, cameras, and other node types.
|
||||||
|
|
||||||
|
for (var i = 0u; i < node->children.count; i++)
|
||||||
|
{
|
||||||
|
children.Add(ParseHierarchy(node->children.data[i]));
|
||||||
|
}
|
||||||
|
|
||||||
return meshNode;
|
return meshNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
var missingNormals = false;
|
private GeometryMeshNode? ParseGeometry(ufbx_mesh* pMesh)
|
||||||
var missingTangents = false;
|
{
|
||||||
|
if (pMesh->num_faces == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
using var flatVertices = new UnsafeList<Vertex>(1024, AllocationHandle.FreeList);
|
var numMaterials = pMesh->materials.count > 0 ? (int)pMesh->materials.count : 1;
|
||||||
|
|
||||||
|
// Bucket faces by material
|
||||||
|
|
||||||
|
using var materialBuckets = new UnsafeArray<UnsafeList<Vertex>>(numMaterials, AllocationHandle.FreeList);
|
||||||
|
using var missingNormalsBucket = new UnsafeArray<bool>(numMaterials, AllocationHandle.FreeList);
|
||||||
|
using var missingTangentsBucket = new UnsafeArray<bool>(numMaterials, AllocationHandle.FreeList);
|
||||||
|
|
||||||
|
for (var i = 0; i < numMaterials; i++)
|
||||||
|
{
|
||||||
|
materialBuckets[i] = new UnsafeList<Vertex>(1024, AllocationHandle.FreeList);
|
||||||
|
}
|
||||||
|
|
||||||
var maxScratchIndices = (int)(pMesh->max_face_triangles * 3u);
|
var maxScratchIndices = (int)(pMesh->max_face_triangles * 3u);
|
||||||
|
|
||||||
@@ -84,6 +137,7 @@ internal unsafe class MeshParsingWorkItem : IJob
|
|||||||
for (var j = 0u; j < pMesh->num_faces; j++)
|
for (var j = 0u; j < pMesh->num_faces; j++)
|
||||||
{
|
{
|
||||||
var face = pMesh->faces.data[j];
|
var face = pMesh->faces.data[j];
|
||||||
|
var materialIdx = pMesh->face_material.count > j ? pMesh->face_material.data[j] : 0;
|
||||||
|
|
||||||
var numTris = UfbxApi.TriangulateFace(triIndicesArray.AsSpan(0, maxScratchIndices), pMesh, face);
|
var numTris = UfbxApi.TriangulateFace(triIndicesArray.AsSpan(0, maxScratchIndices), pMesh, face);
|
||||||
|
|
||||||
@@ -123,22 +177,33 @@ internal unsafe class MeshParsingWorkItem : IJob
|
|||||||
vertex.tangent = ComputeTangent(t, n, b);
|
vertex.tangent = ComputeTangent(t, n, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
var newIndex = (uint)flatVertices.Count;
|
materialBuckets[materialIdx].Add(vertex);
|
||||||
|
|
||||||
flatVertices.Add(vertex);
|
if (!missingNormalsBucket[materialIdx])
|
||||||
|
|
||||||
if (!missingNormals)
|
|
||||||
{
|
{
|
||||||
missingNormals = normIdx == uint.MaxValue;
|
missingNormalsBucket[materialIdx] = normIdx == uint.MaxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!missingTangents)
|
if (!missingTangentsBucket[materialIdx])
|
||||||
{
|
{
|
||||||
missingTangents = tanIdx == uint.MaxValue || btanIdx == uint.MaxValue;
|
missingTangentsBucket[materialIdx] = tanIdx == uint.MaxValue || btanIdx == uint.MaxValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Per-material weld + optimize, collect intermediate results
|
||||||
|
|
||||||
|
using var partResults = new UnsafeList<GeometryPart>(numMaterials, AllocationHandle.FreeList);
|
||||||
|
|
||||||
|
for (var m = 0; m < numMaterials; m++)
|
||||||
|
{
|
||||||
|
ref var flatVertices = ref materialBuckets[m];
|
||||||
|
if (flatVertices.Count == 0)
|
||||||
|
{
|
||||||
|
flatVertices.Dispose();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var numIndices = (uint)flatVertices.Count;
|
var numIndices = (uint)flatVertices.Count;
|
||||||
|
|
||||||
using var weldedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
|
using var weldedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
|
||||||
@@ -155,47 +220,108 @@ internal unsafe class MeshParsingWorkItem : IJob
|
|||||||
var numUniqueVertices = UfbxApi.GenerateIndices([stream], weldedIndices, null, &error);
|
var numUniqueVertices = UfbxApi.GenerateIndices([stream], weldedIndices, null, &error);
|
||||||
if (numUniqueVertices == 0 && error.type != ufbx_error_type.UFBX_ERROR_NONE)
|
if (numUniqueVertices == 0 && error.type != ufbx_error_type.UFBX_ERROR_NONE)
|
||||||
{
|
{
|
||||||
return;
|
flatVertices.Dispose();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
MeshOptApi.OptimizeVertexCache((uint*)cachedIndices.GetUnsafePtr(), (uint*)weldedIndices.GetUnsafePtr(), numIndices, numUniqueVertices);
|
MeshOptApi.OptimizeVertexCache((uint*)cachedIndices.GetUnsafePtr(), (uint*)weldedIndices.GetUnsafePtr(), numIndices, numUniqueVertices);
|
||||||
|
|
||||||
vertices = new UnsafeList<Vertex>((int)numUniqueVertices, _allocationHandle);
|
// Allocate temporary per-part buffers (will be merged then disposed)
|
||||||
indices = new UnsafeList<uint>((int)numIndices, _allocationHandle);
|
var partVertices = new UnsafeList<Vertex>((int)numUniqueVertices, AllocationHandle.FreeList);
|
||||||
|
var partIndices = new UnsafeList<uint>((int)numIndices, AllocationHandle.FreeList);
|
||||||
|
|
||||||
var finalVertexCount = MeshOptApi.OptimizeVertexFetch(vertices.GetUnsafePtr(), (uint*)cachedIndices.GetUnsafePtr(), numIndices, flatVertices.GetUnsafePtr(), numIndices, (nuint)sizeof(Vertex));
|
var finalVertexCount = MeshOptApi.OptimizeVertexFetch(partVertices.GetUnsafePtr(), (uint*)cachedIndices.GetUnsafePtr(), numIndices, flatVertices.GetUnsafePtr(), numIndices, (nuint)sizeof(Vertex));
|
||||||
|
|
||||||
vertices.UnsafeSetCount((int)finalVertexCount);
|
partVertices.UnsafeSetCount((int)finalVertexCount);
|
||||||
|
|
||||||
MemoryUtility.MemCpy(indices.GetUnsafePtr(), cachedIndices.GetUnsafePtr(), numIndices * sizeof(uint));
|
MemoryUtility.MemCpy(partIndices.GetUnsafePtr(), cachedIndices.GetUnsafePtr(), numIndices * sizeof(uint));
|
||||||
indices.UnsafeSetCount((int)numIndices);
|
partIndices.UnsafeSetCount((int)numIndices);
|
||||||
|
|
||||||
if (_settings.NormalDataSource == VertexDataSource.Computed || (_settings.NormalDataSource == VertexDataSource.ComputedIfMissing && missingNormals))
|
var part = new GeometryPart
|
||||||
{
|
{
|
||||||
MeshBuilder.ComputeNormal(vertices, indices);
|
vertices = partVertices,
|
||||||
|
indices = partIndices,
|
||||||
|
materialIndex = m,
|
||||||
|
missingNormals = missingNormalsBucket[m],
|
||||||
|
missingTangents = missingTangentsBucket[m]
|
||||||
|
};
|
||||||
|
|
||||||
|
partResults.Add(part);
|
||||||
|
flatVertices.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_settings.TangentDataSource == VertexDataSource.Computed || (_settings.TangentDataSource == VertexDataSource.ComputedIfMissing && missingTangents))
|
if (partResults.Count == 0)
|
||||||
{
|
{
|
||||||
MeshBuilder.ComputeTangents(vertices, indices);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge all material parts into one unified vertex/index buffer
|
||||||
|
|
||||||
|
var totalVertexCount = 0;
|
||||||
|
var totalIndexCount = 0;
|
||||||
|
for (var i = 0; i < partResults.Count; i++)
|
||||||
|
{
|
||||||
|
totalVertexCount += partResults[i].vertices.Count;
|
||||||
|
totalIndexCount += partResults[i].indices.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mergedVertices = new UnsafeList<Vertex>(totalVertexCount, _allocationHandle);
|
||||||
|
var mergedIndices = new UnsafeList<uint>(totalIndexCount, _allocationHandle);
|
||||||
|
var materialParts = new UnsafeArray<MaterialPartInfo>(partResults.Count, _allocationHandle);
|
||||||
|
|
||||||
|
var vertexOffset = 0;
|
||||||
|
var indexOffset = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < partResults.Count; i++)
|
||||||
|
{
|
||||||
|
ref var part = ref partResults[i];
|
||||||
|
|
||||||
|
// Compute normals/tangents per-part before merge (requires local indices)
|
||||||
|
if (_settings.NormalDataSource == VertexDataSource.Computed || (_settings.NormalDataSource == VertexDataSource.ComputedIfMissing && part.missingNormals))
|
||||||
|
{
|
||||||
|
MeshBuilder.ComputeNormal(part.vertices, part.indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_settings.TangentDataSource == VertexDataSource.Computed || (_settings.TangentDataSource == VertexDataSource.ComputedIfMissing && part.missingTangents))
|
||||||
|
{
|
||||||
|
MeshBuilder.ComputeTangents(part.vertices, part.indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
materialParts[i] = new MaterialPartInfo
|
||||||
|
{
|
||||||
|
materialIndex = part.materialIndex,
|
||||||
|
vertexStart = vertexOffset,
|
||||||
|
vertexCount = part.vertices.Count,
|
||||||
|
indexStart = indexOffset,
|
||||||
|
indexCount = part.indices.Count,
|
||||||
|
};
|
||||||
|
|
||||||
|
mergedVertices.AddRange(part.vertices.AsSpan());
|
||||||
|
|
||||||
|
// Rebase indices to global vertex space
|
||||||
|
for (var j = 0; j < part.indices.Count; j++)
|
||||||
|
{
|
||||||
|
mergedIndices.Add(part.indices[j] + (uint)vertexOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
vertexOffset += part.vertices.Count;
|
||||||
|
indexOffset += part.indices.Count;
|
||||||
|
|
||||||
|
part.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GeometryMeshNode
|
||||||
|
{
|
||||||
|
Name = pMesh->name.ToString(),
|
||||||
|
LocalTransform = float4x4.identity,
|
||||||
|
Vertices = mergedVertices,
|
||||||
|
Indices = mergedIndices,
|
||||||
|
MaterialParts = materialParts,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Execute(ref readonly JobExecutionContext context)
|
public void Execute(ref readonly JobExecutionContext context)
|
||||||
{
|
{
|
||||||
if (!File.Exists(_filePath))
|
|
||||||
{
|
|
||||||
_taskCompletionSource.SetResult(Result.Failure("Invalid file path."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Path.GetExtension(_filePath).Equals(".obj", StringComparison.OrdinalIgnoreCase)
|
|
||||||
&& !Path.GetExtension(_filePath).Equals(".fbx", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
_taskCompletionSource.SetResult(Result.Failure("Unsupported file format. Only .obj and .fbx are supported."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var error = new ufbx_error();
|
var error = new ufbx_error();
|
||||||
var load_Opts = new ufbx_load_opts
|
var load_Opts = new ufbx_load_opts
|
||||||
{
|
{
|
||||||
@@ -231,68 +357,10 @@ internal unsafe class MeshParsingWorkItem : IJob
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var flatVertices = new UnsafeList<Vertex>(1024, AllocationHandle.FreeList);
|
var rootNode = ParseHierarchy(scene.Get()->root_node);
|
||||||
|
rootNode.Name = Path.GetFileNameWithoutExtension(_filePath);
|
||||||
|
|
||||||
var missingNormals = false;
|
_taskCompletionSource.SetResult(Result.Success(rootNode));
|
||||||
var missingTangents = false;
|
|
||||||
|
|
||||||
for (var i = 0u; i < scene.Get()->nodes.count; i++)
|
|
||||||
{
|
|
||||||
var data = scene.Get()->nodes.data;
|
|
||||||
var node = scene.Get()->nodes.data[i];
|
|
||||||
if (node->is_root)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node->mesh != null)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var numIndices = (uint)flatVertices.Count;
|
|
||||||
|
|
||||||
using var weldedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
|
|
||||||
using var cachedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
|
|
||||||
|
|
||||||
var stream = new ufbx_vertex_stream
|
|
||||||
{
|
|
||||||
data = flatVertices.GetUnsafePtr(),
|
|
||||||
vertex_count = numIndices,
|
|
||||||
vertex_size = (nuint)sizeof(Vertex)
|
|
||||||
};
|
|
||||||
|
|
||||||
var numUniqueVertices = UfbxApi.GenerateIndices([stream], weldedIndices, null, &error);
|
|
||||||
if (numUniqueVertices == 0 && error.type != ufbx_error_type.UFBX_ERROR_NONE)
|
|
||||||
{
|
|
||||||
_taskCompletionSource.SetResult(Result.Failure($"Welding failed: {error.description}"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MeshOptApi.OptimizeVertexCache((uint*)cachedIndices.GetUnsafePtr(), (uint*)weldedIndices.GetUnsafePtr(), numIndices, numUniqueVertices);
|
|
||||||
|
|
||||||
vertices = new UnsafeList<Vertex>((int)numUniqueVertices, _allocationHandle);
|
|
||||||
indices = new UnsafeList<uint>((int)numIndices, _allocationHandle);
|
|
||||||
|
|
||||||
var finalVertexCount = MeshOptApi.OptimizeVertexFetch(vertices.GetUnsafePtr(), (uint*)cachedIndices.GetUnsafePtr(), numIndices, flatVertices.GetUnsafePtr(), numIndices, (nuint)sizeof(Vertex));
|
|
||||||
|
|
||||||
vertices.UnsafeSetCount((int)finalVertexCount);
|
|
||||||
|
|
||||||
MemoryUtility.MemCpy(indices.GetUnsafePtr(), cachedIndices.GetUnsafePtr(), numIndices * sizeof(uint));
|
|
||||||
indices.UnsafeSetCount((int)numIndices);
|
|
||||||
|
|
||||||
if (_settings.NormalDataSource == VertexDataSource.Computed || (_settings.NormalDataSource == VertexDataSource.ComputedIfMissing && missingNormals))
|
|
||||||
{
|
|
||||||
MeshBuilder.ComputeNormal(vertices, indices);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_settings.TangentDataSource == VertexDataSource.Computed || (_settings.TangentDataSource == VertexDataSource.ComputedIfMissing && missingTangents))
|
|
||||||
{
|
|
||||||
MeshBuilder.ComputeTangents(vertices, indices);
|
|
||||||
}
|
|
||||||
|
|
||||||
_taskCompletionSource.SetResult(Result.Success());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -695,67 +695,17 @@ public static unsafe partial class MeshProcessor
|
|||||||
return finalClusterCount;
|
return finalClusterCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void BuildMeshlets(MeshletMeshData* pMeshletData, ReadOnlyUnsafeCollection<Vertex> vertices, ReadOnlyUnsafeCollection<uint> indices)
|
private struct MeshletContext
|
||||||
{
|
{
|
||||||
Logger.DebugAssert(pMeshletData->meshletCount > 0, "Mesh must have vertices to build meshlets.");
|
public MeshletMeshData* data;
|
||||||
|
public int materialIndex;
|
||||||
var config = new ClodConfig
|
|
||||||
{
|
|
||||||
maxVertices = 64,
|
|
||||||
minTriangles = 32,
|
|
||||||
maxTriangles = 124,
|
|
||||||
|
|
||||||
partitionSpatial = true,
|
|
||||||
partitionSize = 16,
|
|
||||||
|
|
||||||
clusterSpatial = false,
|
|
||||||
clusterSplitFactor = 2.0f,
|
|
||||||
|
|
||||||
optimizeClusters = true,
|
|
||||||
optimizeClustersLevel = 1,
|
|
||||||
|
|
||||||
simplifyRatio = 0.5f,
|
|
||||||
simplifyThreshold = 0.85f,
|
|
||||||
simplifyErrorMergePrevious = 1.0f,
|
|
||||||
simplifyErrorFactorSloppy = 2.0f,
|
|
||||||
simplifyPermissive = true,
|
|
||||||
simplifyFallbackPermissive = false,
|
|
||||||
simplifyFallbackSloppy = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
var clodMesh = new ClodMesh
|
|
||||||
{
|
|
||||||
vertexPositions = (float*)Unsafe.AsPointer(in vertices[0].position),
|
|
||||||
vertexCount = (nuint)vertices.Count,
|
|
||||||
vertexPositionsStride = (nuint)sizeof(Vertex),
|
|
||||||
vertexAttributes = (float*)Unsafe.AsPointer(in vertices[0].normal),
|
|
||||||
vertexAttributesStride = (nuint)sizeof(Vertex),
|
|
||||||
indices = (uint*)indices.GetUnsafePtr(),
|
|
||||||
indexCount = (nuint)indices.Count,
|
|
||||||
attributeProtectMask = 0, // TODO: We need to protect UVs and other vertex attributes to ensure they are not altered during simplification.
|
|
||||||
};
|
|
||||||
|
|
||||||
Build(in config, in clodMesh, pMeshletData, MeshletOutputCallback);
|
|
||||||
|
|
||||||
pMeshletData->meshletCount = pMeshletData->meshlets.IsCreated ? pMeshletData->meshlets.Count : 0;
|
|
||||||
|
|
||||||
if (pMeshletData->groups.IsCreated && pMeshletData->groups.Count > 0)
|
|
||||||
{
|
|
||||||
var maxLodLevel = 0u;
|
|
||||||
for (var i = 0; i < pMeshletData->groups.Count; i++)
|
|
||||||
{
|
|
||||||
maxLodLevel = Math.Max(maxLodLevel, pMeshletData->groups[i].lodLevel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pMeshletData->lodLevelCount = (int)maxLodLevel + 1;
|
private static int MeshletOutputCallback(void* contextPtr, ClodGroup group, ReadOnlyUnsafeCollection<ClodCluster> clusters)
|
||||||
}
|
|
||||||
|
|
||||||
pMeshletData->materialSlotCount = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int MeshletOutputCallback(void* context, ClodGroup group, ReadOnlyUnsafeCollection<ClodCluster> clusters)
|
|
||||||
{
|
{
|
||||||
var pMeshletData = (MeshletMeshData*)context;
|
var context = (MeshletContext*)contextPtr;
|
||||||
|
var pMeshletData = context->data;
|
||||||
|
var materialIndex = context->materialIndex;
|
||||||
|
|
||||||
// Ensure lists are initialized
|
// Ensure lists are initialized
|
||||||
if (!pMeshletData->groups.IsCreated) pMeshletData->groups = new UnsafeList<MeshletGroup>(16, AllocationHandle.Persistent);
|
if (!pMeshletData->groups.IsCreated) pMeshletData->groups = new UnsafeList<MeshletGroup>(16, AllocationHandle.Persistent);
|
||||||
@@ -790,7 +740,7 @@ public static unsafe partial class MeshProcessor
|
|||||||
groupIndex = (uint)pMeshletData->groups.Count - 1,
|
groupIndex = (uint)pMeshletData->groups.Count - 1,
|
||||||
clusterError = cluster.bounds.error,
|
clusterError = cluster.bounds.error,
|
||||||
parentError = group.simplified.error,
|
parentError = group.simplified.error,
|
||||||
localMaterialIndex = 0, // TODO: support multiple materials
|
localMaterialIndex = (byte)materialIndex,
|
||||||
lodLevel = (byte)group.depth,
|
lodLevel = (byte)group.depth,
|
||||||
};
|
};
|
||||||
pMeshletData->meshlets.Add(meshlet);
|
pMeshletData->meshlets.Add(meshlet);
|
||||||
@@ -815,6 +765,91 @@ public static unsafe partial class MeshProcessor
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds meshlets for a unified multi-material mesh.
|
||||||
|
/// Each <see cref="MaterialPartInfo"/> describes a material partition's index range within the unified buffer.
|
||||||
|
/// Meshlets are built per-part and tagged with the corresponding <c>localMaterialIndex</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static void BuildMeshlets(MeshletMeshData* pMeshletData, ReadOnlyUnsafeCollection<Vertex> vertices, ReadOnlyUnsafeCollection<uint> indices, ReadOnlySpan<MaterialPartInfo> parts)
|
||||||
|
{
|
||||||
|
Logger.DebugAssert(pMeshletData->meshletCount == 0, "Meshlet data is not empty.");
|
||||||
|
Logger.DebugAssert(vertices.Count > 0, "Mesh must have vertices to build meshlets.");
|
||||||
|
Logger.DebugAssert(indices.Count > 0, "Mesh must have indices to build meshlets.");
|
||||||
|
Logger.DebugAssert(parts.Length > 0, "Must have at least one material part.");
|
||||||
|
|
||||||
|
var config = new ClodConfig
|
||||||
|
{
|
||||||
|
maxVertices = 64,
|
||||||
|
minTriangles = 32,
|
||||||
|
maxTriangles = 124,
|
||||||
|
|
||||||
|
partitionSpatial = true,
|
||||||
|
partitionSize = 16,
|
||||||
|
|
||||||
|
clusterSpatial = false,
|
||||||
|
clusterSplitFactor = 2.0f,
|
||||||
|
|
||||||
|
optimizeClusters = true,
|
||||||
|
optimizeClustersLevel = 1,
|
||||||
|
|
||||||
|
simplifyRatio = 0.5f,
|
||||||
|
simplifyThreshold = 0.85f,
|
||||||
|
simplifyErrorMergePrevious = 1.0f,
|
||||||
|
simplifyErrorFactorSloppy = 2.0f,
|
||||||
|
simplifyPermissive = true,
|
||||||
|
simplifyFallbackPermissive = false,
|
||||||
|
simplifyFallbackSloppy = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var i = 0; i < parts.Length; i++)
|
||||||
|
{
|
||||||
|
ref readonly var part = ref parts[i];
|
||||||
|
|
||||||
|
// Each part references a slice of the global index buffer,
|
||||||
|
// but vertex positions are the full unified buffer so global indices remain valid.
|
||||||
|
var clodMesh = new ClodMesh
|
||||||
|
{
|
||||||
|
vertexPositions = (float*)Unsafe.AsPointer(in vertices[0].position),
|
||||||
|
vertexCount = (nuint)vertices.Count,
|
||||||
|
vertexPositionsStride = (nuint)sizeof(Vertex),
|
||||||
|
vertexAttributes = (float*)Unsafe.AsPointer(in vertices[0].normal),
|
||||||
|
vertexAttributesStride = (nuint)sizeof(Vertex),
|
||||||
|
indices = (uint*)indices.GetUnsafePtr() + part.indexStart,
|
||||||
|
indexCount = (nuint)part.indexCount,
|
||||||
|
attributeProtectMask = 0, // TODO: Protect UVs at material boundaries.
|
||||||
|
};
|
||||||
|
|
||||||
|
var context = new MeshletContext
|
||||||
|
{
|
||||||
|
data = pMeshletData,
|
||||||
|
materialIndex = part.materialIndex
|
||||||
|
};
|
||||||
|
|
||||||
|
Build(in config, in clodMesh, &context, MeshletOutputCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pMeshletData->meshletCount = pMeshletData->meshlets.IsCreated ? pMeshletData->meshlets.Count : 0;
|
||||||
|
|
||||||
|
if (pMeshletData->groups.IsCreated && pMeshletData->groups.Count > 0)
|
||||||
|
{
|
||||||
|
var maxLodLevel = 0u;
|
||||||
|
for (var j = 0; j < pMeshletData->groups.Count; j++)
|
||||||
|
{
|
||||||
|
maxLodLevel = Math.Max(maxLodLevel, pMeshletData->groups[j].lodLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
pMeshletData->lodLevelCount = (int)maxLodLevel + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxMaterialSlot = 0;
|
||||||
|
for (var j = 0; j < parts.Length; j++)
|
||||||
|
{
|
||||||
|
maxMaterialSlot = Math.Max(maxMaterialSlot, parts[j].materialIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
pMeshletData->materialSlotCount = maxMaterialSlot + 1;
|
||||||
|
}
|
||||||
|
|
||||||
public static void BuildClusterLodHierarchy()
|
public static void BuildClusterLodHierarchy()
|
||||||
{
|
{
|
||||||
// TODO: Implement a function that builds a cluster LOD hierarchy for a mesh, which can be used for efficient rendering of large meshes with varying levels of detail.
|
// TODO: Implement a function that builds a cluster LOD hierarchy for a mesh, which can be used for efficient rendering of large meshes with varying levels of detail.
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Engine;
|
using Ghost.Engine;
|
||||||
using Ghost.Nvtt;
|
using Ghost.Nvtt;
|
||||||
using Misaki.HighPerformance.Jobs;
|
|
||||||
using Misaki.HighPerformance.LowLevel;
|
using Misaki.HighPerformance.LowLevel;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using Misaki.HighPerformance.Mathematics.SPMD;
|
|
||||||
using System.IO.Hashing;
|
using System.IO.Hashing;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
@@ -191,45 +189,41 @@ internal static partial class TextureProcessor
|
|||||||
|
|
||||||
pCtx.Get()->SetCudaAcceleration(NvttApi.IsCudaSupported());
|
pCtx.Get()->SetCudaAcceleration(NvttApi.IsCudaSupported());
|
||||||
|
|
||||||
int edgeLength;
|
var maxCubeMips = _mipLevels.Length;
|
||||||
using (var cubeSurface0 = new DisposablePtr<NvttCubeSurface>(NvttCubeSurface.Create()))
|
var w0 = _mipLevels[0].width;
|
||||||
using (var mip0Surf = new DisposablePtr<NvttSurface>(NvttSurface.Create()))
|
|
||||||
|
if (!pCtx.Get()->OutputHeaderData(NvttTextureType.NVTT_TextureType_Cube, w0, w0, 1, maxCubeMips, false, pCompOpts.Get(), pOutOpts.Get()))
|
||||||
{
|
{
|
||||||
if (!mip0Surf.Get()->SetImageData(NvttInputFormat.NVTT_InputFormat_RGBA_32F, _mipLevels[0].width, _mipLevels[0].height, 1, _mipLevels[0].data.GetUnsafePtr(), false, null))
|
return Result.Failure("Failed to output header for cube map.");
|
||||||
{
|
|
||||||
return Result.Failure<int>("Failed to set image data for NVTT compression.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cubeSurface0.Get()->Fold(mip0Surf.Get(), NvttCubeLayout.NVTT_CubeLayout_LatitudeLongitude);
|
|
||||||
edgeLength = cubeSurface0.Get()->EdgeLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
pCtx.Get()->OutputHeaderData(NvttTextureType.NVTT_TextureType_Cube, edgeLength, edgeLength, 1, _mipLevels.Length, false, pCompOpts.Get(), pOutOpts.Get());
|
|
||||||
|
|
||||||
for (var level = 0; level < _mipLevels.Length; level++)
|
|
||||||
{
|
|
||||||
using var cubeSurface = new DisposablePtr<NvttCubeSurface>(NvttCubeSurface.Create());
|
|
||||||
using var mipSurf = new DisposablePtr<NvttSurface>(NvttSurface.Create());
|
|
||||||
|
|
||||||
mipSurf.Get()->SetImageData(NvttInputFormat.NVTT_InputFormat_RGBA_32F, _mipLevels[level].width, _mipLevels[level].height, 1, _mipLevels[level].data.GetUnsafePtr(), false, null);
|
|
||||||
cubeSurface.Get()->Fold(mipSurf.Get(), NvttCubeLayout.NVTT_CubeLayout_LatitudeLongitude);
|
|
||||||
|
|
||||||
for (var face = 0; face < 6; face++)
|
for (var face = 0; face < 6; face++)
|
||||||
{
|
{
|
||||||
var faceSurf = cubeSurface.Get()->Face(face);
|
for (var level = 0; level < maxCubeMips; level++)
|
||||||
|
{
|
||||||
|
using var faceSurf = new DisposablePtr<NvttSurface>(NvttSurface.Create());
|
||||||
|
var w = _mipLevels[level].width;
|
||||||
|
var faceSize = w * w * _textureInfo.colorComponents;
|
||||||
|
var pSrcData = (float*)_mipLevels[level].data.GetUnsafePtr() + face * faceSize;
|
||||||
|
|
||||||
|
if (!faceSurf.Get()->SetImageData(NvttInputFormat.NVTT_InputFormat_RGBA_32F, w, w, 1, pSrcData, false, null))
|
||||||
|
{
|
||||||
|
return Result.Failure("Failed to set image data for NVTT compression.");
|
||||||
|
}
|
||||||
|
|
||||||
if (_settings.Basic.IsSRGB)
|
if (_settings.Basic.IsSRGB)
|
||||||
{
|
{
|
||||||
faceSurf->ToSrgb(null);
|
faceSurf.Get()->ToSrgb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pCtx.Get()->Compress(faceSurf, face, level, pCompOpts.Get(), pOutOpts.Get()))
|
if (!pCtx.Get()->Compress(faceSurf.Get(), face, level, pCompOpts.Get(), pOutOpts.Get()))
|
||||||
{
|
{
|
||||||
return Result.Failure("Failed to compress mipmap.");
|
return Result.Failure("Failed to compress cube map face.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.Success(_mipLevels.Length);
|
return Result.Success(maxCubeMips);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Execute()
|
public void Execute()
|
||||||
@@ -316,8 +310,54 @@ internal static partial class TextureProcessor
|
|||||||
{
|
{
|
||||||
if (settings.Basic.TextureShape == TextureShape.TextureCube)
|
if (settings.Basic.TextureShape == TextureShape.TextureCube)
|
||||||
{
|
{
|
||||||
var handle = GenerateMipHDRI(scheduler, textureInfo, out mipLevels);
|
int maxCubeMips;
|
||||||
|
int edge;
|
||||||
|
UnsafeArray<float> baseCubeData;
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
using var cubeSurface0 = new DisposablePtr<NvttCubeSurface>(NvttCubeSurface.Create());
|
||||||
|
using var mip0Surf = new DisposablePtr<NvttSurface>(NvttSurface.Create());
|
||||||
|
if (!mip0Surf.Get()->SetImageData(NvttInputFormat.NVTT_InputFormat_RGBA_32F, textureInfo.width, textureInfo.height, 1, (void*)textureInfo.pixelData, false, null))
|
||||||
|
{
|
||||||
|
return Result.Failure("Failed to set image data for cube map.");
|
||||||
|
}
|
||||||
|
|
||||||
|
cubeSurface0.Get()->Fold(mip0Surf.Get(), NvttCubeLayout.NVTT_CubeLayout_LatitudeLongitude);
|
||||||
|
edge = cubeSurface0.Get()->EdgeLength();
|
||||||
|
maxCubeMips = (int)Math.Floor(Math.Log2(edge)) + 1;
|
||||||
|
|
||||||
|
var pixelsPerFace = edge * edge;
|
||||||
|
var faceSize = pixelsPerFace * textureInfo.colorComponents;
|
||||||
|
baseCubeData = new UnsafeArray<float>(faceSize * 6, AllocationHandle.FreeList);
|
||||||
|
|
||||||
|
var channels = textureInfo.colorComponents;
|
||||||
|
var channelPtrs = stackalloc float*[channels];
|
||||||
|
for (var face = 0; face < 6; face++)
|
||||||
|
{
|
||||||
|
using var faceSurf = new DisposablePtr<NvttSurface>(cubeSurface0.Get()->Face(face));
|
||||||
|
|
||||||
|
// NVTT stores data in planar format: [RRRR...][GGGG...][BBBB...][AAAA...]
|
||||||
|
// We need to interleave into RGBARGBA... for our sampling code.
|
||||||
|
var pDst = (float*)baseCubeData.GetUnsafePtr() + face * faceSize;
|
||||||
|
|
||||||
|
for (var ch = 0; ch < channels; ch++)
|
||||||
|
{
|
||||||
|
channelPtrs[ch] = faceSurf.Get()->Channel(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var p = 0; p < pixelsPerFace; p++)
|
||||||
|
{
|
||||||
|
for (var ch = 0; ch < channels; ch++)
|
||||||
|
{
|
||||||
|
pDst[p * channels + ch] = channelPtrs[ch][p];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var handle = GenerateMipHDRI(scheduler, textureInfo, baseCubeData, edge, maxCubeMips, out mipLevels);
|
||||||
await scheduler.WaitAsync(handle, cancellationToken);
|
await scheduler.WaitAsync(handle, cancellationToken);
|
||||||
|
baseCubeData.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
var workItem = new NvttPipelineTask(cachePath, textureInfo, settings, mipLevels);
|
var workItem = new NvttPipelineTask(cachePath, textureInfo, settings, mipLevels);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using Ghost.Core;
|
|||||||
using Misaki.HighPerformance.Jobs;
|
using Misaki.HighPerformance.Jobs;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
using Misaki.HighPerformance.Mathematics;
|
||||||
using Misaki.HighPerformance.Mathematics.SPMD;
|
using Misaki.HighPerformance.Mathematics.SPMD;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using static Misaki.HighPerformance.Mathematics.math;
|
using static Misaki.HighPerformance.Mathematics.math;
|
||||||
@@ -72,34 +73,70 @@ internal static partial class TextureProcessor
|
|||||||
return MathV.Normalize(sampleVec);
|
return MathV.Normalize(sampleVec);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maps a 3D direction vector to 2D equirectangular UVs
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static Vector2<TFloat, float> DirToEquirectangularUV(Vector3<TFloat, float> dir)
|
private static float3 CubemapUVToDir(int face, float u, float v)
|
||||||
{
|
{
|
||||||
var u = TFloat.Atan2(dir.z, dir.x);
|
var sc = 2.0f * u - 1.0f;
|
||||||
var v = TFloat.Asin(dir.y);
|
var tc = 1.0f - 2.0f * v;
|
||||||
|
|
||||||
u = u / (2.0f * PI) + 0.5f;
|
float x = 0, y = 0, z = 0;
|
||||||
v = v / PI + 0.5f;
|
switch (face)
|
||||||
return MathV.Create<TFloat, float>(u, v);
|
{
|
||||||
|
case 0: x = 1.0f; y = tc; z = -sc; break;
|
||||||
|
case 1: x = -1.0f; y = tc; z = sc; break;
|
||||||
|
case 2: x = sc; y = 1.0f; z = -tc; break;
|
||||||
|
case 3: x = sc; y = -1.0f; z = tc; break;
|
||||||
|
case 4: x = sc; y = tc; z = 1.0f; break;
|
||||||
|
case 5: x = -sc; y = tc; z = -1.0f; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalize(float3(x, y, z));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Samples the source HDR image using bilinear interpolation (simplified to nearest neighbor for brevity here)
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static Vector3<TFloat, float> SampleEquirectangularMap(float* img, int w, int h, int c, Vector3<TFloat, float> dir)
|
private static Vector3<TFloat, float> SampleCubemap(float* img, int edge, int c, Vector3<TFloat, float> dir)
|
||||||
{
|
{
|
||||||
var uv = DirToEquirectangularUV(dir);
|
var absX = TFloat.Abs(dir.x);
|
||||||
|
var absY = TFloat.Abs(dir.y);
|
||||||
|
var absZ = TFloat.Abs(dir.z);
|
||||||
|
|
||||||
// Nearest neighbor pixel coordinates
|
var isXPos = dir.x >= TFloat.Zero;
|
||||||
var px = (uv.x * (w - 1.0f)).Cast<TInt, int>();
|
var isYPos = dir.y >= TFloat.Zero;
|
||||||
var py = (uv.y * (h - 1.0f)).Cast<TInt, int>();
|
var isZPos = dir.z >= TFloat.Zero;
|
||||||
|
|
||||||
// Clamp
|
var maxAxis = TFloat.Max(TFloat.Max(absX, absY), absZ);
|
||||||
px = TInt.Clamp(px, TInt.Zero, w - 1);
|
|
||||||
py = TInt.Clamp(py, TInt.Zero, h - 1);
|
|
||||||
|
|
||||||
// Assuming float RGB array format
|
var faceIndexF = TFloat.Select(maxAxis == absX,
|
||||||
var idx = (py * w + px) * c;
|
TFloat.Select(isXPos, 0.0f, 1.0f),
|
||||||
|
TFloat.Select(maxAxis == absY,
|
||||||
|
TFloat.Select(isYPos, 2.0f, 3.0f),
|
||||||
|
TFloat.Select(isZPos, 4.0f, 5.0f)));
|
||||||
|
|
||||||
|
var faceIndex = faceIndexF.Cast<TInt, int>();
|
||||||
|
|
||||||
|
var sc = TFloat.Select(maxAxis == absX,
|
||||||
|
TFloat.Select(isXPos, -dir.z, dir.z),
|
||||||
|
TFloat.Select(maxAxis == absY,
|
||||||
|
dir.x,
|
||||||
|
TFloat.Select(isZPos, dir.x, -dir.x)));
|
||||||
|
|
||||||
|
var tc = TFloat.Select(maxAxis == absX,
|
||||||
|
dir.y,
|
||||||
|
TFloat.Select(maxAxis == absY,
|
||||||
|
TFloat.Select(isYPos, -dir.z, dir.z),
|
||||||
|
dir.y));
|
||||||
|
|
||||||
|
var u = 0.5f * (sc / maxAxis + 1.0f);
|
||||||
|
var v = 0.5f * (1.0f - tc / maxAxis);
|
||||||
|
|
||||||
|
var px = (u * (edge - 1.0f)).Cast<TInt, int>();
|
||||||
|
var py = (v * (edge - 1.0f)).Cast<TInt, int>();
|
||||||
|
|
||||||
|
px = TInt.Clamp(px, TInt.Zero, edge - 1);
|
||||||
|
py = TInt.Clamp(py, TInt.Zero, edge - 1);
|
||||||
|
|
||||||
|
var faceOffset = faceIndex * (edge * edge);
|
||||||
|
var idx = (faceOffset + py * edge + px) * c;
|
||||||
return MathV.GatherVector3<TFloat, float>(img, idx.GetUnsafePtr(), 1);
|
return MathV.GatherVector3<TFloat, float>(img, idx.GetUnsafePtr(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,22 +152,20 @@ internal static partial class TextureProcessor
|
|||||||
var pLevel = &pMipLevels[m];
|
var pLevel = &pMipLevels[m];
|
||||||
|
|
||||||
var w = pLevel->width;
|
var w = pLevel->width;
|
||||||
var h = pLevel->height;
|
var data = pLevel->data;
|
||||||
var pData = pLevel->data;
|
|
||||||
|
|
||||||
var local_i = loopIndex - pLevel->offset;
|
var local_i = loopIndex - pLevel->offset;
|
||||||
var x = local_i % w;
|
|
||||||
var y = local_i / w;
|
|
||||||
var u = (float)x / (w - 1);
|
|
||||||
var v = (float)y / (h - 1);
|
|
||||||
|
|
||||||
var phi = (u - 0.5f) * 2.0f * PI;
|
var faceArea = w * w;
|
||||||
var theta = (v - 0.5f) * PI;
|
var face = local_i / faceArea;
|
||||||
|
var face_local_i = local_i % faceArea;
|
||||||
|
var x = face_local_i % w;
|
||||||
|
var y = face_local_i / w;
|
||||||
|
|
||||||
sincos(theta, out var sinTheta, out var cosTheta);
|
var u = (x + 0.5f) / w;
|
||||||
sincos(phi, out var sinPhi, out var cosPhi);
|
var v = (y + 0.5f) / w;
|
||||||
var N = float3(cosTheta * cosPhi, sinTheta, cosTheta * sinPhi);
|
|
||||||
N = normalize(N);
|
var N = CubemapUVToDir(face, u, v);
|
||||||
|
|
||||||
// For split-sum, we assume View and Reflection directions equal the Normal
|
// For split-sum, we assume View and Reflection directions equal the Normal
|
||||||
var V = N;
|
var V = N;
|
||||||
@@ -173,7 +208,7 @@ internal static partial class TextureProcessor
|
|||||||
L = MathV.Normalize(L);
|
L = MathV.Normalize(L);
|
||||||
|
|
||||||
var NdotL = TFloat.Max(MathV.Dot(vN, L), TFloat.Zero);
|
var NdotL = TFloat.Max(MathV.Dot(vN, L), TFloat.Zero);
|
||||||
var sampleColor = SampleEquirectangularMap(pImage, imageWidth, imageHeight, channelCount, L);
|
var sampleColor = SampleCubemap(pImage, imageWidth, channelCount, L);
|
||||||
|
|
||||||
NdotL &= validLaneMask;
|
NdotL &= validLaneMask;
|
||||||
|
|
||||||
@@ -208,10 +243,14 @@ internal static partial class TextureProcessor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write to output mip array
|
// Write to output mip array
|
||||||
var out_idx = (y * w + x) * channelCount;
|
var out_idx = (face * (w * w) + y * w + x) * channelCount;
|
||||||
pData[out_idx] = prefilteredColor.x;
|
data[out_idx] = prefilteredColor.x;
|
||||||
pData[out_idx + 1] = prefilteredColor.y;
|
data[out_idx + 1] = prefilteredColor.y;
|
||||||
pData[out_idx + 2] = prefilteredColor.z;
|
data[out_idx + 2] = prefilteredColor.z;
|
||||||
|
if (channelCount == 4)
|
||||||
|
{
|
||||||
|
data[out_idx + 3] = 1.0f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,11 +290,10 @@ internal static partial class TextureProcessor
|
|||||||
return bits * 2.3283064365386963e-10f; // bits / 0x100000000
|
return bits * 2.3283064365386963e-10f; // bits / 0x100000000
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JobHandle GenerateMipHDRI(JobScheduler scheduler, TextureAssetHandler.TextureInfo textureInfo, out UnsafeArray<MipLevel> mipLevels)
|
private static JobHandle GenerateMipHDRI(JobScheduler scheduler, TextureAssetHandler.TextureInfo textureInfo, UnsafeArray<float> baseCubeData, int edge, int totalMipLevels, out UnsafeArray<MipLevel> mipLevels)
|
||||||
{
|
{
|
||||||
Logger.DebugAssert(textureInfo.isHDR, "GenerateMipHDRI should only be called for HDR textures.");
|
Logger.DebugAssert(textureInfo.isHDR, "GenerateMipHDRI should only be called for HDR textures.");
|
||||||
|
Logger.DebugAssert(textureInfo.colorComponents >= 3, "Texture must have at least 3 color components for RGB.");
|
||||||
var totalMipLevels = (int)Math.Floor(Math.Log2(Math.Max(textureInfo.width, textureInfo.height))) + 1;
|
|
||||||
|
|
||||||
mipLevels = new UnsafeArray<MipLevel>(totalMipLevels, AllocationHandle.FreeList);
|
mipLevels = new UnsafeArray<MipLevel>(totalMipLevels, AllocationHandle.FreeList);
|
||||||
var radicalInverse_VdCLut = new UnsafeArray<float>(_SAMPLE_COUNT, AllocationHandle.FreeList);
|
var radicalInverse_VdCLut = new UnsafeArray<float>(_SAMPLE_COUNT, AllocationHandle.FreeList);
|
||||||
@@ -265,24 +303,23 @@ internal static partial class TextureProcessor
|
|||||||
radicalInverse_VdCLut[i] = RadicalInverse_VdC(i);
|
radicalInverse_VdCLut[i] = RadicalInverse_VdC(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
int w, h;
|
int w;
|
||||||
var totalPixel = 0;
|
var totalPixel = 0;
|
||||||
|
|
||||||
for (var i = 0; i < totalMipLevels; i++)
|
for (var i = 0; i < totalMipLevels; i++)
|
||||||
{
|
{
|
||||||
w = Math.Max(1, textureInfo.width >> i);
|
w = Math.Max(1, edge >> i);
|
||||||
h = Math.Max(1, textureInfo.height >> i);
|
|
||||||
|
|
||||||
mipLevels[i] = new MipLevel
|
mipLevels[i] = new MipLevel
|
||||||
{
|
{
|
||||||
data = new UnsafeArray<float>(w * h * textureInfo.colorComponents, AllocationHandle.FreeList),
|
data = new UnsafeArray<float>(w * w * 6 * textureInfo.colorComponents, AllocationHandle.FreeList),
|
||||||
width = w,
|
width = w,
|
||||||
height = h,
|
height = w,
|
||||||
offset = totalPixel,
|
offset = totalPixel,
|
||||||
roughness = (float)i / (totalMipLevels - 1) // Linear roughness from 0 to 1 across mip levels
|
roughness = (float)i / (totalMipLevels - 1) // Linear roughness from 0 to 1 across mip levels
|
||||||
};
|
};
|
||||||
|
|
||||||
totalPixel += w * h;
|
totalPixel += w * w * 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
JobHandle handle;
|
JobHandle handle;
|
||||||
@@ -292,11 +329,11 @@ internal static partial class TextureProcessor
|
|||||||
{
|
{
|
||||||
var job = new GGXMipGenerationJobSPMD<WideLane<float>, WideLane<int>>
|
var job = new GGXMipGenerationJobSPMD<WideLane<float>, WideLane<int>>
|
||||||
{
|
{
|
||||||
pImage = (float*)textureInfo.pixelData,
|
pImage = (float*)baseCubeData.GetUnsafePtr(),
|
||||||
pMipLevels = (MipLevel*)mipLevels.GetUnsafePtr(),
|
pMipLevels = (MipLevel*)mipLevels.GetUnsafePtr(),
|
||||||
pRadicalInverse_VdCLut = (float*)radicalInverse_VdCLut.GetUnsafePtr(),
|
pRadicalInverse_VdCLut = (float*)radicalInverse_VdCLut.GetUnsafePtr(),
|
||||||
imageWidth = textureInfo.width,
|
imageWidth = edge,
|
||||||
imageHeight = textureInfo.height,
|
imageHeight = edge,
|
||||||
numMipLevels = totalMipLevels,
|
numMipLevels = totalMipLevels,
|
||||||
channelCount = textureInfo.colorComponents,
|
channelCount = textureInfo.colorComponents,
|
||||||
};
|
};
|
||||||
@@ -307,11 +344,11 @@ internal static partial class TextureProcessor
|
|||||||
{
|
{
|
||||||
var job = new GGXMipGenerationJobSPMD<ScalarLane<float>, ScalarLane<int>>
|
var job = new GGXMipGenerationJobSPMD<ScalarLane<float>, ScalarLane<int>>
|
||||||
{
|
{
|
||||||
pImage = (float*)textureInfo.pixelData,
|
pImage = (float*)baseCubeData.GetUnsafePtr(),
|
||||||
pMipLevels = (MipLevel*)mipLevels.GetUnsafePtr(),
|
pMipLevels = (MipLevel*)mipLevels.GetUnsafePtr(),
|
||||||
pRadicalInverse_VdCLut = (float*)radicalInverse_VdCLut.GetUnsafePtr(),
|
pRadicalInverse_VdCLut = (float*)radicalInverse_VdCLut.GetUnsafePtr(),
|
||||||
imageWidth = textureInfo.width,
|
imageWidth = edge,
|
||||||
imageHeight = textureInfo.height,
|
imageHeight = edge,
|
||||||
numMipLevels = totalMipLevels,
|
numMipLevels = totalMipLevels,
|
||||||
channelCount = textureInfo.colorComponents,
|
channelCount = textureInfo.colorComponents,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ internal sealed partial class ImportCoordinator : IDisposable
|
|||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hash = XxHash128.HashToUInt128(JsonSerializer.SerializeToUtf8Bytes(settings));
|
var hash = XxHash128.HashToUInt128(JsonSerializer.SerializeToUtf8Bytes(settings, settings.GetType()));
|
||||||
Span<byte> bytes = stackalloc byte[16];
|
Span<byte> bytes = stackalloc byte[16];
|
||||||
Unsafe.WriteUnaligned(ref bytes[0], hash);
|
Unsafe.WriteUnaligned(ref bytes[0], hash);
|
||||||
|
|
||||||
|
|||||||
@@ -66,13 +66,13 @@ internal static class ActivationHandler
|
|||||||
|
|
||||||
AllocationManager.Initialize(opts);
|
AllocationManager.Initialize(opts);
|
||||||
|
|
||||||
//var assetRegistry = App.GetService<IAssetRegistry>();
|
var assetRegistry = App.GetService<IAssetRegistry>();
|
||||||
//var engineCore = App.GetService<EngineCore>();
|
var engineCore = App.GetService<EngineCore>();
|
||||||
|
|
||||||
//assetRegistry.OnAssetImported += (sender, e) =>
|
assetRegistry.OnAssetImported += (sender, e) =>
|
||||||
//{
|
{
|
||||||
// engineCore.AssetManager.ReimportAsset(e);
|
engineCore.AssetManager.ReimportAsset(e);
|
||||||
//};
|
};
|
||||||
|
|
||||||
return ValueTask.CompletedTask;
|
return ValueTask.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Ghost.Core.Utilities;
|
||||||
using Ghost.Editor.Core;
|
using Ghost.Editor.Core;
|
||||||
|
using Ghost.Editor.Core.Assets;
|
||||||
using Ghost.Editor.Core.Contracts;
|
using Ghost.Editor.Core.Contracts;
|
||||||
using Ghost.Editor.Core.Utilities;
|
using Ghost.Editor.Core.Utilities;
|
||||||
using Ghost.Editor.Models;
|
using Ghost.Editor.Models;
|
||||||
using Ghost.Editor.Core.AssetHandler;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using Microsoft.UI.Dispatching;
|
|
||||||
using Ghost.Engine;
|
using Ghost.Engine;
|
||||||
using Ghost.Core.Utilities;
|
using Microsoft.UI.Dispatching;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
namespace Ghost.Editor.ViewModels.Controls;
|
namespace Ghost.Editor.ViewModels.Controls;
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ internal partial class ContentBrowserViewModel : ObservableObject
|
|||||||
if (!Files.Any(f => string.Equals(f.Path, fullPath, StringComparison.OrdinalIgnoreCase)))
|
if (!Files.Any(f => string.Equals(f.Path, fullPath, StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
var isDir = Directory.Exists(fullPath);
|
var isDir = Directory.Exists(fullPath);
|
||||||
AssetType assetType = AssetType.Unknown;
|
var assetType = AssetType.Unknown;
|
||||||
if (!isDir)
|
if (!isDir)
|
||||||
{
|
{
|
||||||
var ext = Path.GetExtension(fullPath);
|
var ext = Path.GetExtension(fullPath);
|
||||||
|
|||||||
@@ -8,12 +8,13 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS</DefineConstants>
|
<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS;MHP_ENABLE_MIMALLOC</DefineConstants>
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
<IsTrimmable>True</IsTrimmable>
|
<IsTrimmable>True</IsTrimmable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
<DefineConstants>$(DefineConstants);MHP_ENABLE_MIMALLOC</DefineConstants>
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
<IsTrimmable>True</IsTrimmable>
|
<IsTrimmable>True</IsTrimmable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.3.3" />
|
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.3.3" />
|
||||||
<PackageReference Include="Misaki.HighPerformance.Mathematics.SPMD" Version="1.3.0" />
|
<PackageReference Include="Misaki.HighPerformance.Mathematics.SPMD" Version="1.3.0" />
|
||||||
<PackageReference Include="System.IO.Hashing" Version="10.0.7" />
|
<PackageReference Include="System.IO.Hashing" Version="10.0.7" />
|
||||||
|
<PackageReference Include="TerraFX.Interop.Mimalloc" Version="1.6.7.2" />
|
||||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.6" />
|
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ internal class AssetHandlerRegistrationGenerator : IIncrementalGenerator
|
|||||||
|
|
||||||
foreach (var symbol in array)
|
foreach (var symbol in array)
|
||||||
{
|
{
|
||||||
var attribute = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == "Ghost.Editor.Core.AssetHandler.CustomAssetHandlerAttribute");
|
var attribute = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == "Ghost.Editor.Core.Assets.CustomAssetHandlerAttribute");
|
||||||
if (attribute == null)
|
if (attribute == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -44,7 +44,7 @@ internal class AssetHandlerRegistrationGenerator : IIncrementalGenerator
|
|||||||
var extensions = $"new string[] {{ {string.Join(", ", extensionsTypesConstants.Select(v => v.ToCSharpString()))} }}";
|
var extensions = $"new string[] {{ {string.Join(", ", extensionsTypesConstants.Select(v => v.ToCSharpString()))} }}";
|
||||||
var version = (int)attribute.ConstructorArguments[2].Value;
|
var version = (int)attribute.ConstructorArguments[2].Value;
|
||||||
|
|
||||||
sb.Append($" global::Ghost.Editor.Core.AssetHandler.AssetHandlerRegistry.RegisterHandler(new {symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}(), Guid.Parse(\"{id}\"), {extensions}, {version});");
|
sb.AppendLine($" global::Ghost.Editor.Core.Assets.AssetHandlerRegistry.RegisterHandler(new {symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}(), Guid.Parse(\"{id}\"), {extensions}, {version});");
|
||||||
}
|
}
|
||||||
|
|
||||||
var registerTypeName = "g_assethandler_registeration";
|
var registerTypeName = "g_assethandler_registeration";
|
||||||
@@ -71,7 +71,7 @@ internal static partial class {registerTypeName}
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var iHandlerSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Editor.Core.AssetHandler.IAssetHandler");
|
var iHandlerSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Editor.Core.Assets.IAssetHandler");
|
||||||
if (iHandlerSymbol == null)
|
if (iHandlerSymbol == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -114,7 +114,7 @@ internal class IAssetSettingsRegistrationGenerator : IIncrementalGenerator
|
|||||||
|
|
||||||
foreach (var iface in array)
|
foreach (var iface in array)
|
||||||
{
|
{
|
||||||
sb.AppendLine($" global::Ghost.Editor.Core.AssetHandler.AssetHandlerRegistry.RegisterIAssetSettingsType(typeof({iface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}), \"{iface.Name}\");");
|
sb.AppendLine($" global::Ghost.Editor.Core.Assets.AssetHandlerRegistry.RegisterIAssetSettingsType(typeof({iface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}), \"{iface.Name}\");");
|
||||||
}
|
}
|
||||||
|
|
||||||
var registerTypeName = "g_iassetsettings_registeration";
|
var registerTypeName = "g_iassetsettings_registeration";
|
||||||
@@ -141,7 +141,7 @@ internal static partial class {registerTypeName}
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var iSettingsSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Editor.Core.AssetHandler.IAssetSettings");
|
var iSettingsSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Editor.Core.Assets.IAssetSettings");
|
||||||
if (iSettingsSymbol == null)
|
if (iSettingsSymbol == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -190,19 +190,18 @@ public static unsafe class MeshBuilder
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="vertices">The vertex list.</param>
|
/// <param name="vertices">The vertex list.</param>
|
||||||
/// <param name="indices">The index list.</param>
|
/// <param name="indices">The index list.</param>
|
||||||
public static void ComputeNormal(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
|
public static void ComputeNormal(Span<Vertex> vertices, Span<uint> indices)
|
||||||
{
|
{
|
||||||
if (!vertices.IsCreated || vertices.Count < 3
|
if (vertices.Length < 3 || indices.Length < 3)
|
||||||
|| !indices.IsCreated || indices.Count < 3)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < indices.Count; i += 3)
|
for (var i = 0; i < indices.Length; i += 3)
|
||||||
{
|
{
|
||||||
var i0 = indices[i];
|
var i0 = (int)indices[i];
|
||||||
var i1 = indices[i + 1];
|
var i1 = (int)indices[i + 1];
|
||||||
var i2 = indices[i + 2];
|
var i2 = (int)indices[i + 2];
|
||||||
|
|
||||||
var v0 = vertices[i0];
|
var v0 = vertices[i0];
|
||||||
var v1 = vertices[i1];
|
var v1 = vertices[i1];
|
||||||
@@ -217,7 +216,7 @@ public static unsafe class MeshBuilder
|
|||||||
vertices[i2].normal.xyz += faceNormal;
|
vertices[i2].normal.xyz += faceNormal;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < vertices.Count; i++)
|
for (var i = 0; i < vertices.Length; i++)
|
||||||
{
|
{
|
||||||
vertices[i].normal = math.normalize(vertices[i].normal);
|
vertices[i].normal = math.normalize(vertices[i].normal);
|
||||||
}
|
}
|
||||||
@@ -228,16 +227,16 @@ public static unsafe class MeshBuilder
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="vertices">The vertex list.</param>
|
/// <param name="vertices">The vertex list.</param>
|
||||||
/// <param name="indices">The index list.</param>
|
/// <param name="indices">The index list.</param>
|
||||||
public static void ComputeTangents(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
|
public static void ComputeTangents(Span<Vertex> vertices, Span<uint> indices)
|
||||||
{
|
{
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
var bitangents = new UnsafeArray<float3>(vertices.Count, scope.AllocationHandle, AllocationOption.Clear);
|
var bitangents = new UnsafeArray<float3>(vertices.Length, scope.AllocationHandle, AllocationOption.Clear);
|
||||||
|
|
||||||
for (var i = 0; i < indices.Count; i += 3)
|
for (var i = 0; i < indices.Length; i += 3)
|
||||||
{
|
{
|
||||||
var i0 = indices[i];
|
var i0 = (int)indices[i];
|
||||||
var i1 = indices[i + 1];
|
var i1 = (int)indices[i + 1];
|
||||||
var i2 = indices[i + 2];
|
var i2 = (int)indices[i + 2];
|
||||||
|
|
||||||
var v0 = vertices[i0];
|
var v0 = vertices[i0];
|
||||||
var v1 = vertices[i1];
|
var v1 = vertices[i1];
|
||||||
@@ -258,7 +257,7 @@ public static unsafe class MeshBuilder
|
|||||||
|
|
||||||
for (var j = 0; j < 3; j++)
|
for (var j = 0; j < 3; j++)
|
||||||
{
|
{
|
||||||
var idx = indices[i + j];
|
var idx = (int)indices[i + j];
|
||||||
var t = vertices[idx].tangent;
|
var t = vertices[idx].tangent;
|
||||||
vertices[idx].tangent.xyz = t.xyz + tangent.xyz;
|
vertices[idx].tangent.xyz = t.xyz + tangent.xyz;
|
||||||
|
|
||||||
@@ -266,7 +265,7 @@ public static unsafe class MeshBuilder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < vertices.Count; i++)
|
for (var i = 0; i < vertices.Length; i++)
|
||||||
{
|
{
|
||||||
var n = vertices[i].normal;
|
var n = vertices[i].normal;
|
||||||
var t = vertices[i].tangent.xyz;
|
var t = vertices[i].tangent.xyz;
|
||||||
|
|||||||
Reference in New Issue
Block a user