feat(meshlet)!: consolidate and modernize Cluster LOD logic
Refactored Cluster LOD mesh generation by merging ClodBounds, ClodConfig, ClodMesh, ClodGroup, ClodCluster, Cluster, and related logic into a new MeshletUtility.cs under Ghost.Graphics.Utilities. Removed legacy Clod* files and updated to use improved memory management (UnsafeArray, Allocator.FreeList) and more idiomatic C# patterns. Updated .csproj package versions for compatibility. Minor code style improvements in RenderGraphResourcePool.cs. BREAKING CHANGE: Cluster LOD API has been consolidated and refactored; previous Clod* types and entry points have been removed or replaced. Callers must update to use MeshletUtility.cs.
This commit is contained in:
@@ -21,8 +21,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Misaki.HighPerformance" Version="1.0.4" />
|
<PackageReference Include="Misaki.HighPerformance" Version="1.0.4" />
|
||||||
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.5.1" />
|
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.5.2" />
|
||||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.4.4">
|
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.5.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Meshlet;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the bounding sphere and simplification error for a LOD cluster.
|
|
||||||
/// </summary>
|
|
||||||
public struct ClodBounds
|
|
||||||
{
|
|
||||||
/// <summary> The center of the bounding sphere. </summary>
|
|
||||||
public Vector3 center;
|
|
||||||
/// <summary> The radius of the bounding sphere. </summary>
|
|
||||||
public float radius;
|
|
||||||
/// <summary> The simplification error associated with this LOD level. </summary>
|
|
||||||
public float error;
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Numerics;
|
|
||||||
using Ghost.MeshOptimizer;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Meshlet;
|
|
||||||
|
|
||||||
internal static class ClodBoundsHelper
|
|
||||||
{
|
|
||||||
public static unsafe ClodBounds ComputeBounds(ClodMesh mesh, UnsafeList<uint> indices, float error)
|
|
||||||
{
|
|
||||||
var bounds = MeshOptApi.ComputeClusterBounds((uint*)indices.GetUnsafePtr(), (nuint)indices.Count, mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride);
|
|
||||||
return new ClodBounds
|
|
||||||
{
|
|
||||||
center = new Vector3(bounds.center[0], bounds.center[1], bounds.center[2]),
|
|
||||||
radius = bounds.radius,
|
|
||||||
error = error
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsafe ClodBounds MergeBounds(UnsafeList<Cluster> clusters, UnsafeList<int> group)
|
|
||||||
{
|
|
||||||
var boundsList = new UnsafeList<ClodBounds>(group.Count, Allocator.Temp);
|
|
||||||
for (int j = 0; j < group.Count; j++)
|
|
||||||
boundsList.Add(clusters[group[j]].bounds);
|
|
||||||
|
|
||||||
var merged = MeshOptApi.ComputeSphereBounds(
|
|
||||||
(float*)boundsList.GetUnsafePtr(),
|
|
||||||
(nuint)group.Count,
|
|
||||||
(nuint)sizeof(ClodBounds),
|
|
||||||
(float*)boundsList.GetUnsafePtr() + 3,
|
|
||||||
(nuint)sizeof(ClodBounds)
|
|
||||||
);
|
|
||||||
|
|
||||||
float maxError = 0.0f;
|
|
||||||
for (int j = 0; j < group.Count; j++)
|
|
||||||
maxError = Math.Max(maxError, clusters[group[j]].bounds.error);
|
|
||||||
|
|
||||||
boundsList.Dispose();
|
|
||||||
return new ClodBounds
|
|
||||||
{
|
|
||||||
center = new Vector3(merged.center[0], merged.center[1], merged.center[2]),
|
|
||||||
radius = merged.radius,
|
|
||||||
error = maxError
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using Ghost.MeshOptimizer;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Meshlet;
|
|
||||||
|
|
||||||
internal struct Cluster
|
|
||||||
{
|
|
||||||
public nuint vertices;
|
|
||||||
public UnsafeList<uint> indices;
|
|
||||||
public int group;
|
|
||||||
public int refined;
|
|
||||||
public ClodBounds bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides methods to build a hierarchical Cluster LOD mesh.
|
|
||||||
/// </summary>
|
|
||||||
public unsafe static class ClodBuilder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Builds a cluster LOD hierarchy from the input mesh.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="config">The configuration parameters for the LOD building process.</param>
|
|
||||||
/// <param name="mesh">The input mesh data.</param>
|
|
||||||
/// <param name="outputContext">Optional context pointer passed to the output callback.</param>
|
|
||||||
/// <param name="outputCallback">Delegate invoked for each generated LOD group.</param>
|
|
||||||
/// <returns>The total count of generated clusters.</returns>
|
|
||||||
public static nuint Build(ClodConfig config, ClodMesh mesh, void* outputContext, ClodOutputDelegate outputCallback)
|
|
||||||
{
|
|
||||||
Debug.Assert(mesh.vertexAttributesStride % (nuint)sizeof(float) == 0, "vertexAttributesStride must be a multiple of sizeof(float)");
|
|
||||||
|
|
||||||
var locks = new UnsafeList<byte>((int)mesh.vertexCount, Allocator.Temp);
|
|
||||||
locks.AsSpan().Fill(0);
|
|
||||||
|
|
||||||
var remap = new UnsafeList<uint>((int)mesh.vertexCount, Allocator.Temp);
|
|
||||||
remap.Resize((int)mesh.vertexCount);
|
|
||||||
MeshOptApi.GeneratePositionRemap((uint*)remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride);
|
|
||||||
|
|
||||||
if (mesh.attributeProtectMask != 0)
|
|
||||||
{
|
|
||||||
nuint maxAttributes = mesh.vertexAttributesStride / sizeof(float);
|
|
||||||
for (nuint i = 0; i < mesh.vertexCount; i++)
|
|
||||||
{
|
|
||||||
uint r = ((uint*)remap.GetUnsafePtr())[(int)i];
|
|
||||||
for (nuint j = 0; j < maxAttributes; j++)
|
|
||||||
{
|
|
||||||
if ((r != i) && ((mesh.attributeProtectMask & (1u << (int)j)) != 0))
|
|
||||||
{
|
|
||||||
if (mesh.vertexAttributes[i * maxAttributes + j] != mesh.vertexAttributes[r * maxAttributes + j])
|
|
||||||
{
|
|
||||||
((byte*)locks.GetUnsafePtr())[(int)i] |= (byte)(Api.meshopt_SimplifyVertex_Protect & 0xFF);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var clusters = ClodInternal.Clusterize(config, mesh, mesh.indices, mesh.indexCount, Allocator.Persistent);
|
|
||||||
|
|
||||||
for (int i = 0; i < clusters.Count; i++)
|
|
||||||
{
|
|
||||||
clusters[i].bounds = ClodBoundsHelper.ComputeBounds(mesh, clusters[i].indices, 0.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
var pending = new UnsafeList<int>(clusters.Count, Allocator.Temp);
|
|
||||||
for (int i = 0; i < clusters.Count; i++)
|
|
||||||
pending.Add(i);
|
|
||||||
|
|
||||||
int depth = 0;
|
|
||||||
|
|
||||||
while (pending.Count > 1)
|
|
||||||
{
|
|
||||||
var groups = ClodPartition.Partition(config, mesh, clusters, pending, remap, Allocator.Temp);
|
|
||||||
pending.Clear();
|
|
||||||
|
|
||||||
ClodBoundary.LockBoundary(locks, groups, clusters, remap, mesh.vertexLock);
|
|
||||||
|
|
||||||
for (int i = 0; i < groups.Count; i++)
|
|
||||||
{
|
|
||||||
var merged = new UnsafeList<uint>(groups[i].Count * (int)config.maxTriangles * 3, Allocator.Temp);
|
|
||||||
for (int j = 0; j < groups[i].Count; j++)
|
|
||||||
{
|
|
||||||
var clusterIndices = clusters[groups[i][j]].indices;
|
|
||||||
for (int k = 0; k < clusterIndices.Count; k++)
|
|
||||||
merged.Add(clusterIndices[k]);
|
|
||||||
}
|
|
||||||
|
|
||||||
nuint targetSize = ((nuint)merged.Count / 3) * (nuint)config.simplifyRatio * 3;
|
|
||||||
var bounds = ClodBoundsHelper.MergeBounds(clusters, groups[i]);
|
|
||||||
|
|
||||||
float error = 0.0f;
|
|
||||||
var simplified = ClodSimplify.Simplify(config, mesh, merged, locks, targetSize, &error);
|
|
||||||
|
|
||||||
if ((nuint)simplified.Count > (nuint)(merged.Count * config.simplifyThreshold))
|
|
||||||
{
|
|
||||||
bounds.error = float.MaxValue;
|
|
||||||
OutputGroup(config, mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback);
|
|
||||||
merged.Dispose();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bounds.error = Math.Max(bounds.error * config.simplifyErrorMergePrevious, error) + error * config.simplifyErrorMergeAdditive;
|
|
||||||
|
|
||||||
int refined = OutputGroup(config, mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback);
|
|
||||||
|
|
||||||
for (int j = 0; j < groups[i].Count; j++)
|
|
||||||
clusters[groups[i][j]].indices.Dispose();
|
|
||||||
|
|
||||||
var split = ClodInternal.Clusterize(config, mesh, (uint*)simplified.GetUnsafePtr(), (nuint)simplified.Count, Allocator.Persistent);
|
|
||||||
for (int j = 0; j < split.Count; j++)
|
|
||||||
{
|
|
||||||
split[j].refined = refined;
|
|
||||||
split[j].bounds = bounds;
|
|
||||||
clusters.Add(split[j]);
|
|
||||||
pending.Add(clusters.Count - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
split.Dispose();
|
|
||||||
merged.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < groups.Count; i++)
|
|
||||||
groups[i].Dispose();
|
|
||||||
groups.Dispose();
|
|
||||||
|
|
||||||
depth++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pending.Count > 0)
|
|
||||||
{
|
|
||||||
var bounds = clusters[pending[0]].bounds;
|
|
||||||
bounds.error = float.MaxValue;
|
|
||||||
OutputGroup(config, mesh, clusters, pending, bounds, depth, outputContext, outputCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
nuint finalClusterCount = (nuint)clusters.Count;
|
|
||||||
|
|
||||||
for (int i = 0; i < clusters.Count; i++)
|
|
||||||
clusters[i].indices.Dispose();
|
|
||||||
clusters.Dispose();
|
|
||||||
locks.Dispose();
|
|
||||||
remap.Dispose();
|
|
||||||
pending.Dispose();
|
|
||||||
|
|
||||||
return finalClusterCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int OutputGroup(
|
|
||||||
ClodConfig config,
|
|
||||||
ClodMesh mesh,
|
|
||||||
UnsafeList<Cluster> clusters,
|
|
||||||
UnsafeList<int> group,
|
|
||||||
ClodBounds simplified,
|
|
||||||
int depth,
|
|
||||||
void* outputContext,
|
|
||||||
ClodOutputDelegate outputCallback
|
|
||||||
)
|
|
||||||
{
|
|
||||||
var groupClusters = new UnsafeList<ClodCluster>(group.Count, Allocator.Temp);
|
|
||||||
|
|
||||||
for (int i = 0; i < group.Count; i++)
|
|
||||||
{
|
|
||||||
ref var srcCluster = ref clusters[group[i]];
|
|
||||||
groupClusters.Add(new ClodCluster
|
|
||||||
{
|
|
||||||
refined = srcCluster.refined,
|
|
||||||
bounds = (config.optimizeBounds && srcCluster.refined != -1)
|
|
||||||
? ClodBoundsHelper.ComputeBounds(mesh, srcCluster.indices, srcCluster.bounds.error)
|
|
||||||
: srcCluster.bounds,
|
|
||||||
indices = (uint*)srcCluster.indices.GetUnsafePtr(),
|
|
||||||
indexCount = (nuint)srcCluster.indices.Count,
|
|
||||||
vertexCount = srcCluster.vertices
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var clodGroup = new ClodGroup { depth = depth, simplified = simplified };
|
|
||||||
int result = outputCallback != null
|
|
||||||
? outputCallback(outputContext, clodGroup, (ClodCluster*)groupClusters.GetUnsafePtr(), (nuint)groupClusters.Count)
|
|
||||||
: -1;
|
|
||||||
|
|
||||||
groupClusters.Dispose();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Meshlet;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Configuration parameters for the cluster LOD generation pipeline.
|
|
||||||
/// </summary>
|
|
||||||
public struct ClodConfig
|
|
||||||
{
|
|
||||||
/// <summary> The maximum number of vertices per meshlet. </summary>
|
|
||||||
public nuint maxVertices;
|
|
||||||
/// <summary> The minimum number of triangles per meshlet. </summary>
|
|
||||||
public nuint minTriangles;
|
|
||||||
/// <summary> The maximum number of triangles per meshlet. </summary>
|
|
||||||
public nuint maxTriangles;
|
|
||||||
/// <summary> Whether to use spatial partitioning during meshlet building. </summary>
|
|
||||||
public bool partitionSpatial;
|
|
||||||
/// <summary> Whether to sort clusters after partitioning. </summary>
|
|
||||||
public bool partitionSort;
|
|
||||||
/// <summary> The target size for partitions. </summary>
|
|
||||||
public nuint partitionSize;
|
|
||||||
/// <summary> Whether to cluster meshlets using spatial clustering. </summary>
|
|
||||||
public bool clusterSpatial;
|
|
||||||
/// <summary> Weight factor for cluster fill calculation. </summary>
|
|
||||||
public float clusterFillWeight;
|
|
||||||
/// <summary> Split factor for flexible clustering. </summary>
|
|
||||||
public float clusterSplitFactor;
|
|
||||||
/// <summary> The simplification ratio to achieve per LOD level. </summary>
|
|
||||||
public float simplifyRatio;
|
|
||||||
/// <summary> Threshold for stopping simplification. </summary>
|
|
||||||
public float simplifyThreshold;
|
|
||||||
/// <summary> Error factor used when merging previous LOD level errors. </summary>
|
|
||||||
public float simplifyErrorMergePrevious;
|
|
||||||
/// <summary> Additive error factor when merging LOD levels. </summary>
|
|
||||||
public float simplifyErrorMergeAdditive;
|
|
||||||
/// <summary> Error factor for sloppy simplification. </summary>
|
|
||||||
public float simplifyErrorFactorSloppy;
|
|
||||||
/// <summary> Edge length limit error factor. </summary>
|
|
||||||
public float simplifyErrorEdgeLimit;
|
|
||||||
/// <summary> Whether to allow permissive simplification. </summary>
|
|
||||||
public bool simplifyPermissive;
|
|
||||||
/// <summary> Whether to fallback to permissive simplification. </summary>
|
|
||||||
public bool simplifyFallbackPermissive;
|
|
||||||
/// <summary> Whether to fallback to sloppy simplification. </summary>
|
|
||||||
public bool simplifyFallbackSloppy;
|
|
||||||
/// <summary> Whether to regularize the mesh during simplification. </summary>
|
|
||||||
public bool simplifyRegularize;
|
|
||||||
/// <summary> Whether to optimize cluster bounds. </summary>
|
|
||||||
public bool optimizeBounds;
|
|
||||||
/// <summary> Whether to optimize clusters post-build. </summary>
|
|
||||||
public bool optimizeClusters;
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Ghost.MeshOptimizer;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Meshlet;
|
|
||||||
|
|
||||||
internal static class ClodInternal
|
|
||||||
{
|
|
||||||
public static unsafe UnsafeList<Cluster> Clusterize(ClodConfig config, ClodMesh mesh, uint* indices, nuint indexCount, Allocator allocator)
|
|
||||||
{
|
|
||||||
nuint maxMeshlets = MeshOptApi.BuildMeshletsBound(indexCount, config.maxVertices, config.minTriangles);
|
|
||||||
|
|
||||||
var meshlets = new UnsafeList<meshopt_Meshlet>((int)maxMeshlets, Allocator.Temp);
|
|
||||||
meshlets.Resize((int)maxMeshlets);
|
|
||||||
var meshletVertices = new UnsafeList<uint>((int)indexCount, Allocator.Temp);
|
|
||||||
meshletVertices.Resize((int)indexCount);
|
|
||||||
var meshletTriangles = new UnsafeList<byte>((int)indexCount, Allocator.Temp);
|
|
||||||
meshletTriangles.Resize((int)indexCount);
|
|
||||||
|
|
||||||
meshopt_Meshlet* pMeshlets = (meshopt_Meshlet*)meshlets.GetUnsafePtr();
|
|
||||||
uint* pMeshletVertices = (uint*)meshletVertices.GetUnsafePtr();
|
|
||||||
byte* pMeshletTriangles = (byte*)meshletTriangles.GetUnsafePtr();
|
|
||||||
|
|
||||||
nuint meshletCount;
|
|
||||||
if (config.clusterSpatial)
|
|
||||||
{
|
|
||||||
meshletCount = pMeshlets[0].BuildsSpatial(
|
|
||||||
pMeshletVertices, pMeshletTriangles,
|
|
||||||
indices, indexCount,
|
|
||||||
mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride,
|
|
||||||
config.maxVertices, config.minTriangles, config.maxTriangles,
|
|
||||||
config.clusterFillWeight
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
meshletCount = pMeshlets[0].BuildsFlex(
|
|
||||||
pMeshletVertices, pMeshletTriangles,
|
|
||||||
indices, indexCount,
|
|
||||||
mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride,
|
|
||||||
config.maxVertices, config.minTriangles, config.maxTriangles,
|
|
||||||
0.0f, config.clusterSplitFactor
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var clusters = new UnsafeList<Cluster>((int)meshletCount, allocator);
|
|
||||||
|
|
||||||
for (nuint i = 0; i < meshletCount; i++)
|
|
||||||
{
|
|
||||||
ref var meshlet = ref pMeshlets[i];
|
|
||||||
|
|
||||||
if (config.optimizeClusters)
|
|
||||||
{
|
|
||||||
MeshOptApi.OptimizeMeshlet(
|
|
||||||
pMeshletVertices + meshlet.vertex_offset,
|
|
||||||
pMeshletTriangles + meshlet.triangle_offset,
|
|
||||||
meshlet.triangle_count,
|
|
||||||
meshlet.vertex_count
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var cluster = new Cluster
|
|
||||||
{
|
|
||||||
vertices = meshlet.vertex_count,
|
|
||||||
indices = new UnsafeList<uint>((int)(meshlet.triangle_count * 3), allocator),
|
|
||||||
group = -1,
|
|
||||||
refined = -1
|
|
||||||
};
|
|
||||||
|
|
||||||
for (nuint j = 0; j < meshlet.triangle_count * 3; j++)
|
|
||||||
cluster.indices.Add(pMeshletVertices[meshlet.vertex_offset + pMeshletTriangles[meshlet.triangle_offset + j]]);
|
|
||||||
|
|
||||||
clusters.Add(cluster);
|
|
||||||
}
|
|
||||||
|
|
||||||
meshlets.Dispose();
|
|
||||||
meshletVertices.Dispose();
|
|
||||||
meshletTriangles.Dispose();
|
|
||||||
|
|
||||||
return clusters;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
using Ghost.MeshOptimizer;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Meshlet;
|
|
||||||
|
|
||||||
internal static class ClodBoundary
|
|
||||||
{
|
|
||||||
public static unsafe void LockBoundary(UnsafeList<byte> locks, UnsafeList<UnsafeList<int>> groups, UnsafeList<Cluster> clusters, UnsafeList<uint> remap, byte* vertexLock)
|
|
||||||
{
|
|
||||||
byte* pLocks = (byte*)locks.GetUnsafePtr();
|
|
||||||
uint* pRemap = (uint*)remap.GetUnsafePtr();
|
|
||||||
|
|
||||||
for (int i = 0; i < locks.Count; i++)
|
|
||||||
pLocks[i] = unchecked((byte)(pLocks[i] & ~((1 << 0) | (1 << 7))));
|
|
||||||
|
|
||||||
for (int i = 0; i < groups.Count; i++)
|
|
||||||
{
|
|
||||||
for (int j = 0; j < groups[i].Count; j++)
|
|
||||||
{
|
|
||||||
var cluster = clusters[groups[i][j]];
|
|
||||||
for (int k = 0; k < cluster.indices.Count; k++)
|
|
||||||
{
|
|
||||||
uint r = pRemap[(int)cluster.indices[k]];
|
|
||||||
pLocks[r] |= (byte)(pLocks[r] >> 7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int j = 0; j < groups[i].Count; j++)
|
|
||||||
{
|
|
||||||
var cluster = clusters[groups[i][j]];
|
|
||||||
for (int k = 0; k < cluster.indices.Count; k++)
|
|
||||||
{
|
|
||||||
uint r = pRemap[(int)cluster.indices[k]];
|
|
||||||
pLocks[r] |= (byte)(1 << 7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < locks.Count; i++)
|
|
||||||
{
|
|
||||||
uint r = pRemap[i];
|
|
||||||
pLocks[i] = (byte)((pLocks[r] & 1) | (pLocks[i] & (byte)(Api.meshopt_SimplifyVertex_Protect & 0xFF)));
|
|
||||||
if (vertexLock != null)
|
|
||||||
pLocks[i] |= vertexLock[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Ghost.MeshOptimizer;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Meshlet;
|
|
||||||
|
|
||||||
internal static class ClodPartition
|
|
||||||
{
|
|
||||||
public static unsafe UnsafeList<UnsafeList<int>> Partition(ClodConfig config, ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> pending, UnsafeList<uint> remap, Allocator allocator)
|
|
||||||
{
|
|
||||||
if (pending.Count <= (int)config.partitionSize)
|
|
||||||
{
|
|
||||||
var single = new UnsafeList<UnsafeList<int>>(1, allocator);
|
|
||||||
single.Add(pending);
|
|
||||||
return single;
|
|
||||||
}
|
|
||||||
|
|
||||||
nuint totalIndexCount = 0;
|
|
||||||
for (int i = 0; i < pending.Count; i++)
|
|
||||||
totalIndexCount += (nuint)clusters[pending[i]].indices.Count;
|
|
||||||
|
|
||||||
var clusterIndices = new UnsafeList<uint>((int)totalIndexCount, Allocator.Temp);
|
|
||||||
var clusterCounts = new UnsafeList<uint>(pending.Count, Allocator.Temp);
|
|
||||||
|
|
||||||
nuint offset = 0;
|
|
||||||
for (int i = 0; i < pending.Count; i++)
|
|
||||||
{
|
|
||||||
var cluster = clusters[pending[i]];
|
|
||||||
clusterCounts.Add((uint)cluster.indices.Count);
|
|
||||||
for (int j = 0; j < cluster.indices.Count; j++)
|
|
||||||
clusterIndices.Add(((uint*)remap.GetUnsafePtr())[(int)cluster.indices[j]]);
|
|
||||||
offset += (nuint)cluster.indices.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
var clusterPart = new UnsafeList<uint>(pending.Count, Allocator.Temp);
|
|
||||||
clusterPart.Resize(pending.Count);
|
|
||||||
|
|
||||||
nuint partitionCount = MeshOptApi.PartitionClusters(
|
|
||||||
(uint*)clusterPart.GetUnsafePtr(),
|
|
||||||
(uint*)clusterIndices.GetUnsafePtr(),
|
|
||||||
totalIndexCount,
|
|
||||||
(uint*)clusterCounts.GetUnsafePtr(),
|
|
||||||
(nuint)pending.Count,
|
|
||||||
config.partitionSpatial ? mesh.vertexPositions : null,
|
|
||||||
(nuint)remap.Count,
|
|
||||||
mesh.vertexPositionsStride,
|
|
||||||
config.partitionSize
|
|
||||||
);
|
|
||||||
|
|
||||||
var partitions = new UnsafeList<UnsafeList<int>>((int)partitionCount, allocator);
|
|
||||||
for (nuint i = 0; i < partitionCount; i++)
|
|
||||||
partitions.Add(new UnsafeList<int>((int)(config.partitionSize + config.partitionSize / 3), allocator));
|
|
||||||
|
|
||||||
for (int i = 0; i < pending.Count; i++)
|
|
||||||
partitions[(int)((uint*)clusterPart.GetUnsafePtr())[i]].Add(pending[i]);
|
|
||||||
|
|
||||||
clusterIndices.Dispose();
|
|
||||||
clusterCounts.Dispose();
|
|
||||||
clusterPart.Dispose();
|
|
||||||
|
|
||||||
return partitions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
namespace Ghost.Graphics.Meshlet;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contains input data for the Cluster LOD generation pipeline.
|
|
||||||
/// </summary>
|
|
||||||
public unsafe struct ClodMesh
|
|
||||||
{
|
|
||||||
/// <summary> Pointer to vertex position data (float array). </summary>
|
|
||||||
public float* vertexPositions;
|
|
||||||
/// <summary> Number of vertices in the mesh. </summary>
|
|
||||||
public nuint vertexCount;
|
|
||||||
/// <summary> Stride in bytes for vertex position data. </summary>
|
|
||||||
public nuint vertexPositionsStride;
|
|
||||||
/// <summary> Pointer to vertex attribute data (float array). </summary>
|
|
||||||
public float* vertexAttributes;
|
|
||||||
/// <summary> Stride in bytes for vertex attribute data. </summary>
|
|
||||||
public nuint vertexAttributesStride;
|
|
||||||
/// <summary> Pointer to attribute weights for simplification. </summary>
|
|
||||||
public float* attributeWeights;
|
|
||||||
/// <summary> Number of vertex attributes. </summary>
|
|
||||||
public nuint attributeCount;
|
|
||||||
/// <summary> Pointer to index data. </summary>
|
|
||||||
public uint* indices;
|
|
||||||
/// <summary> Number of indices in the mesh. </summary>
|
|
||||||
public nuint indexCount;
|
|
||||||
/// <summary> Pointer to per-vertex lock flags (1 byte per vertex). </summary>
|
|
||||||
public byte* vertexLock;
|
|
||||||
/// <summary> Mask indicating which attributes are protected during simplification. </summary>
|
|
||||||
public uint attributeProtectMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines a group of clusters in the LOD hierarchy.
|
|
||||||
/// </summary>
|
|
||||||
public struct ClodGroup
|
|
||||||
{
|
|
||||||
/// <summary> LOD hierarchy depth of this group. </summary>
|
|
||||||
public int depth;
|
|
||||||
/// <summary> Bounding information for the simplified group. </summary>
|
|
||||||
public ClodBounds simplified;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a cluster of meshlets in the LOD hierarchy.
|
|
||||||
/// </summary>
|
|
||||||
public unsafe struct ClodCluster
|
|
||||||
{
|
|
||||||
/// <summary> Refinement level of the cluster. </summary>
|
|
||||||
public int refined;
|
|
||||||
/// <summary> Bounding info for the cluster. </summary>
|
|
||||||
public ClodBounds bounds;
|
|
||||||
/// <summary> Pointer to indices for this cluster. </summary>
|
|
||||||
public uint* indices;
|
|
||||||
/// <summary> Number of indices. </summary>
|
|
||||||
public nuint indexCount;
|
|
||||||
/// <summary> Number of vertices in the cluster. </summary>
|
|
||||||
public nuint vertexCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Delegate type for processing generated LOD groups.
|
|
||||||
/// </summary>
|
|
||||||
public unsafe delegate int ClodOutputDelegate(void* context, ClodGroup group, ClodCluster* clusters, nuint clusterCount);
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Ghost.MeshOptimizer;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Meshlet;
|
|
||||||
|
|
||||||
internal static class ClodSimplify
|
|
||||||
{
|
|
||||||
public static unsafe UnsafeList<uint> Simplify(
|
|
||||||
ClodConfig config,
|
|
||||||
ClodMesh mesh,
|
|
||||||
UnsafeList<uint> indices,
|
|
||||||
UnsafeList<byte> locks,
|
|
||||||
nuint targetCount,
|
|
||||||
float* error
|
|
||||||
)
|
|
||||||
{
|
|
||||||
if (targetCount >= (nuint)indices.Count)
|
|
||||||
return indices;
|
|
||||||
|
|
||||||
var lod = new UnsafeList<uint>(indices.Count, Allocator.Temp);
|
|
||||||
lod.Resize(indices.Count);
|
|
||||||
|
|
||||||
uint options = (uint)(Api.meshopt_SimplifySparse | Api.meshopt_SimplifyErrorAbsolute);
|
|
||||||
if (config.simplifyPermissive)
|
|
||||||
options |= (uint)Api.meshopt_SimplifyPermissive;
|
|
||||||
if (config.simplifyRegularize)
|
|
||||||
options |= (uint)Api.meshopt_SimplifyRegularize;
|
|
||||||
|
|
||||||
nuint resultSize = MeshOptApi.SimplifyWithAttributes(
|
|
||||||
(uint*)lod.GetUnsafePtr(),
|
|
||||||
(uint*)indices.GetUnsafePtr(),
|
|
||||||
(nuint)indices.Count,
|
|
||||||
mesh.vertexPositions,
|
|
||||||
mesh.vertexCount,
|
|
||||||
mesh.vertexPositionsStride,
|
|
||||||
mesh.vertexAttributes,
|
|
||||||
mesh.vertexAttributesStride,
|
|
||||||
mesh.attributeWeights,
|
|
||||||
mesh.attributeCount,
|
|
||||||
(byte*)locks.GetUnsafePtr(),
|
|
||||||
targetCount,
|
|
||||||
float.MaxValue,
|
|
||||||
options,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
lod.Resize((int)resultSize);
|
|
||||||
|
|
||||||
if ((nuint)lod.Count > targetCount && config.simplifyFallbackPermissive && !config.simplifyPermissive)
|
|
||||||
{
|
|
||||||
options |= (uint)Api.meshopt_SimplifyPermissive;
|
|
||||||
resultSize = MeshOptApi.SimplifyWithAttributes(
|
|
||||||
(uint*)lod.GetUnsafePtr(),
|
|
||||||
(uint*)indices.GetUnsafePtr(),
|
|
||||||
(nuint)indices.Count,
|
|
||||||
mesh.vertexPositions,
|
|
||||||
mesh.vertexCount,
|
|
||||||
mesh.vertexPositionsStride,
|
|
||||||
mesh.vertexAttributes,
|
|
||||||
mesh.vertexAttributesStride,
|
|
||||||
mesh.attributeWeights,
|
|
||||||
mesh.attributeCount,
|
|
||||||
(byte*)locks.GetUnsafePtr(),
|
|
||||||
targetCount,
|
|
||||||
float.MaxValue,
|
|
||||||
options,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
lod.Resize((int)resultSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((nuint)lod.Count > targetCount && config.simplifyFallbackSloppy)
|
|
||||||
{
|
|
||||||
*error *= config.simplifyErrorFactorSloppy;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.simplifyErrorEdgeLimit > 0)
|
|
||||||
{
|
|
||||||
float maxEdgeSq = 0;
|
|
||||||
uint* pIdx = (uint*)indices.GetUnsafePtr();
|
|
||||||
int posStride = (int)(mesh.vertexPositionsStride / sizeof(float));
|
|
||||||
|
|
||||||
for (int i = 0; i < indices.Count; i += 3)
|
|
||||||
{
|
|
||||||
uint a = pIdx[i], b = pIdx[i + 1], c = pIdx[i + 2];
|
|
||||||
float* va = mesh.vertexPositions + (a * (uint)posStride);
|
|
||||||
float* vb = mesh.vertexPositions + (b * (uint)posStride);
|
|
||||||
float* vc = mesh.vertexPositions + (c * (uint)posStride);
|
|
||||||
|
|
||||||
float dx, dy, dz;
|
|
||||||
dx = va[0] - vb[0]; dy = va[1] - vb[1]; dz = va[2] - vb[2];
|
|
||||||
float eab = dx * dx + dy * dy + dz * dz;
|
|
||||||
dx = va[0] - vc[0]; dy = va[1] - vc[1]; dz = va[2] - vc[2];
|
|
||||||
float eac = dx * dx + dy * dy + dz * dz;
|
|
||||||
dx = vb[0] - vc[0]; dy = vb[1] - vc[1]; dz = vb[2] - vc[2];
|
|
||||||
float ebc = dx * dx + dy * dy + dz * dz;
|
|
||||||
|
|
||||||
float emax = Math.Max(Math.Max(eab, eac), ebc);
|
|
||||||
float emin = Math.Min(Math.Min(eab, eac), ebc);
|
|
||||||
maxEdgeSq = Math.Max(maxEdgeSq, Math.Max(emin, emax / 4));
|
|
||||||
}
|
|
||||||
|
|
||||||
*error = Math.Min(*error, (float)Math.Sqrt(maxEdgeSq) * config.simplifyErrorEdgeLimit);
|
|
||||||
}
|
|
||||||
|
|
||||||
return lod;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -129,8 +129,10 @@ internal sealed class RenderGraphResourceRegistry
|
|||||||
for (var i = 0; i < _resources.Count; i++)
|
for (var i = 0; i < _resources.Count; i++)
|
||||||
{
|
{
|
||||||
if (_resources[i].type == RenderGraphResourceType.Texture)
|
if (_resources[i].type == RenderGraphResourceType.Texture)
|
||||||
|
{
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,8 +144,10 @@ internal sealed class RenderGraphResourceRegistry
|
|||||||
for (var i = 0; i < _resources.Count; i++)
|
for (var i = 0; i < _resources.Count; i++)
|
||||||
{
|
{
|
||||||
if (_resources[i].type == RenderGraphResourceType.Buffer)
|
if (_resources[i].type == RenderGraphResourceType.Buffer)
|
||||||
|
{
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,7 +294,9 @@ internal sealed class RenderGraphResourceRegistry
|
|||||||
{
|
{
|
||||||
var res = _resources[i];
|
var res = _resources[i];
|
||||||
if (res.type != RenderGraphResourceType.Texture || res.isImported)
|
if (res.type != RenderGraphResourceType.Texture || res.isImported)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var desc = res.rgTextureDesc;
|
var desc = res.rgTextureDesc;
|
||||||
if (desc.sizeMode == RGTextureSizeMode.Absolute)
|
if (desc.sizeMode == RGTextureSizeMode.Absolute)
|
||||||
|
|||||||
623
src/Runtime/Ghost.Graphics/Utilities/MeshletUtility.cs
Normal file
623
src/Runtime/Ghost.Graphics/Utilities/MeshletUtility.cs
Normal file
@@ -0,0 +1,623 @@
|
|||||||
|
using Ghost.MeshOptimizer;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
|
using Misaki.HighPerformance.Mathematics;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Ghost.Graphics.Utilities;
|
||||||
|
|
||||||
|
internal struct Cluster : IDisposable
|
||||||
|
{
|
||||||
|
public UnsafeList<uint> indices;
|
||||||
|
public ClodBounds bounds;
|
||||||
|
public nuint vertices;
|
||||||
|
public int group;
|
||||||
|
public int refined;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
indices.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the bounding sphere and simplification error for a LOD cluster.
|
||||||
|
/// </summary>
|
||||||
|
public struct ClodBounds
|
||||||
|
{
|
||||||
|
/// <summary> The center of the bounding sphere. </summary>
|
||||||
|
public float3 center;
|
||||||
|
/// <summary> The radius of the bounding sphere. </summary>
|
||||||
|
public float radius;
|
||||||
|
/// <summary> The simplification error associated with this LOD level. </summary>
|
||||||
|
public float error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration parameters for the cluster LOD generation pipeline.
|
||||||
|
/// </summary>
|
||||||
|
public struct ClodConfig
|
||||||
|
{
|
||||||
|
/// <summary> The maximum number of vertices per meshlet. </summary>
|
||||||
|
public nuint maxVertices;
|
||||||
|
/// <summary> The minimum number of triangles per meshlet. </summary>
|
||||||
|
public nuint minTriangles;
|
||||||
|
/// <summary> The maximum number of triangles per meshlet. </summary>
|
||||||
|
public nuint maxTriangles;
|
||||||
|
/// <summary> Whether to use spatial partitioning during meshlet building. </summary>
|
||||||
|
public bool partitionSpatial;
|
||||||
|
/// <summary> Whether to sort clusters after partitioning. </summary>
|
||||||
|
public bool partitionSort;
|
||||||
|
/// <summary> The target size for partitions. </summary>
|
||||||
|
public nuint partitionSize;
|
||||||
|
/// <summary> Whether to cluster meshlets using spatial clustering. </summary>
|
||||||
|
public bool clusterSpatial;
|
||||||
|
/// <summary> Weight factor for cluster fill calculation. </summary>
|
||||||
|
public float clusterFillWeight;
|
||||||
|
/// <summary> Split factor for flexible clustering. </summary>
|
||||||
|
public float clusterSplitFactor;
|
||||||
|
/// <summary> The simplification ratio to achieve per LOD level. </summary>
|
||||||
|
public float simplifyRatio;
|
||||||
|
/// <summary> Threshold for stopping simplification. </summary>
|
||||||
|
public float simplifyThreshold;
|
||||||
|
/// <summary> Error factor used when merging previous LOD level errors. </summary>
|
||||||
|
public float simplifyErrorMergePrevious;
|
||||||
|
/// <summary> Additive error factor when merging LOD levels. </summary>
|
||||||
|
public float simplifyErrorMergeAdditive;
|
||||||
|
/// <summary> Error factor for sloppy simplification. </summary>
|
||||||
|
public float simplifyErrorFactorSloppy;
|
||||||
|
/// <summary> Edge length limit error factor. </summary>
|
||||||
|
public float simplifyErrorEdgeLimit;
|
||||||
|
/// <summary> Whether to allow permissive simplification. </summary>
|
||||||
|
public bool simplifyPermissive;
|
||||||
|
/// <summary> Whether to fallback to permissive simplification. </summary>
|
||||||
|
public bool simplifyFallbackPermissive;
|
||||||
|
/// <summary> Whether to fallback to sloppy simplification. </summary>
|
||||||
|
public bool simplifyFallbackSloppy;
|
||||||
|
/// <summary> Whether to regularize the mesh during simplification. </summary>
|
||||||
|
public bool simplifyRegularize;
|
||||||
|
/// <summary> Whether to optimize cluster bounds. </summary>
|
||||||
|
public bool optimizeBounds;
|
||||||
|
/// <summary> Whether to optimize clusters post-build. </summary>
|
||||||
|
public bool optimizeClusters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains input data for the Cluster LOD generation pipeline.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe struct ClodMesh
|
||||||
|
{
|
||||||
|
/// <summary> Pointer to vertex position data (float array). </summary>
|
||||||
|
public float* vertexPositions;
|
||||||
|
/// <summary> Number of vertices in the mesh. </summary>
|
||||||
|
public nuint vertexCount;
|
||||||
|
/// <summary> Stride in bytes for vertex position data. </summary>
|
||||||
|
public nuint vertexPositionsStride;
|
||||||
|
/// <summary> Pointer to vertex attribute data (float array). </summary>
|
||||||
|
public float* vertexAttributes;
|
||||||
|
/// <summary> Stride in bytes for vertex attribute data. </summary>
|
||||||
|
public nuint vertexAttributesStride;
|
||||||
|
/// <summary> Pointer to attribute weights for simplification. </summary>
|
||||||
|
public float* attributeWeights;
|
||||||
|
/// <summary> Number of vertex attributes. </summary>
|
||||||
|
public nuint attributeCount;
|
||||||
|
/// <summary> Pointer to index data. </summary>
|
||||||
|
public uint* indices;
|
||||||
|
/// <summary> Number of indices in the mesh. </summary>
|
||||||
|
public nuint indexCount;
|
||||||
|
/// <summary> Pointer to per-vertex lock flags (1 byte per vertex). </summary>
|
||||||
|
public byte* vertexLock;
|
||||||
|
/// <summary> Mask indicating which attributes are protected during simplification. </summary>
|
||||||
|
public uint attributeProtectMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a group of clusters in the LOD hierarchy.
|
||||||
|
/// </summary>
|
||||||
|
public struct ClodGroup
|
||||||
|
{
|
||||||
|
/// <summary> LOD hierarchy depth of this group. </summary>
|
||||||
|
public int depth;
|
||||||
|
/// <summary> Bounding information for the simplified group. </summary>
|
||||||
|
public ClodBounds simplified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a cluster of meshlets in the LOD hierarchy.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe struct ClodCluster
|
||||||
|
{
|
||||||
|
/// <summary> Refinement level of the cluster. </summary>
|
||||||
|
public int refined;
|
||||||
|
/// <summary> Bounding info for the cluster. </summary>
|
||||||
|
public ClodBounds bounds;
|
||||||
|
/// <summary> Pointer to indices for this cluster. </summary>
|
||||||
|
public uint* indices;
|
||||||
|
/// <summary> Number of indices. </summary>
|
||||||
|
public nuint indexCount;
|
||||||
|
/// <summary> Number of vertices in the cluster. </summary>
|
||||||
|
public nuint vertexCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate type for processing generated LOD groups.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe delegate int ClodOutputDelegate(void* context, ClodGroup group, ClodCluster* clusters, nuint clusterCount);
|
||||||
|
|
||||||
|
// FIX: UnsafeList and UnsafeArray are not same as std::vector.
|
||||||
|
|
||||||
|
public static unsafe class MeshletUtility
|
||||||
|
{
|
||||||
|
private static ClodBounds ComputeBounds(ClodMesh mesh, UnsafeList<uint> indices, float error)
|
||||||
|
{
|
||||||
|
var bounds = MeshOptApi.ComputeClusterBounds((uint*)indices.GetUnsafePtr(), (nuint)indices.Count, mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride);
|
||||||
|
return new ClodBounds
|
||||||
|
{
|
||||||
|
center = new float3(bounds.center[0], bounds.center[1], bounds.center[2]),
|
||||||
|
radius = bounds.radius,
|
||||||
|
error = error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClodBounds MergeBounds(UnsafeList<Cluster> clusters, UnsafeList<int> group)
|
||||||
|
{
|
||||||
|
using var boundsList = new UnsafeArray<ClodBounds>(group.Count, Allocator.FreeList);
|
||||||
|
for (var j = 0; j < group.Count; j++)
|
||||||
|
{
|
||||||
|
boundsList[j] = (clusters[group[j]].bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
var merged = MeshOptApi.ComputeSphereBounds(
|
||||||
|
(float*)boundsList.GetUnsafePtr(),
|
||||||
|
(nuint)group.Count,
|
||||||
|
(nuint)sizeof(ClodBounds),
|
||||||
|
(float*)boundsList.GetUnsafePtr() + 3,
|
||||||
|
(nuint)sizeof(ClodBounds)
|
||||||
|
);
|
||||||
|
|
||||||
|
var maxError = 0.0f;
|
||||||
|
for (var j = 0; j < group.Count; j++)
|
||||||
|
{
|
||||||
|
maxError = Math.Max(maxError, clusters[group[j]].bounds.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ClodBounds
|
||||||
|
{
|
||||||
|
center = new float3(merged.center[0], merged.center[1], merged.center[2]),
|
||||||
|
radius = merged.radius,
|
||||||
|
error = maxError
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UnsafeList<Cluster> Clusterize(ClodConfig config, ClodMesh mesh, uint* indices, nuint indexCount, Allocator allocator)
|
||||||
|
{
|
||||||
|
var maxMeshlets = MeshOptApi.BuildMeshletsBound(indexCount, config.maxVertices, config.minTriangles);
|
||||||
|
|
||||||
|
using var meshlets = new UnsafeArray<meshopt_Meshlet>((int)maxMeshlets, Allocator.FreeList);
|
||||||
|
using var meshletVertices = new UnsafeArray<uint>((int)indexCount, Allocator.FreeList);
|
||||||
|
using var meshletTriangles = new UnsafeArray<byte>((int)indexCount, Allocator.FreeList);
|
||||||
|
|
||||||
|
var pMeshlets = (meshopt_Meshlet*)meshlets.GetUnsafePtr();
|
||||||
|
var pMeshletVertices = (uint*)meshletVertices.GetUnsafePtr();
|
||||||
|
var pMeshletTriangles = (byte*)meshletTriangles.GetUnsafePtr();
|
||||||
|
|
||||||
|
nuint meshletCount;
|
||||||
|
if (config.clusterSpatial)
|
||||||
|
{
|
||||||
|
meshletCount = pMeshlets[0].BuildsSpatial(
|
||||||
|
pMeshletVertices, pMeshletTriangles,
|
||||||
|
indices, indexCount,
|
||||||
|
mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride,
|
||||||
|
config.maxVertices, config.minTriangles, config.maxTriangles,
|
||||||
|
config.clusterFillWeight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
meshletCount = pMeshlets[0].BuildsFlex(
|
||||||
|
pMeshletVertices, pMeshletTriangles,
|
||||||
|
indices, indexCount,
|
||||||
|
mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride,
|
||||||
|
config.maxVertices, config.minTriangles, config.maxTriangles,
|
||||||
|
0.0f, config.clusterSplitFactor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var clusters = new UnsafeList<Cluster>((int)meshletCount, allocator);
|
||||||
|
|
||||||
|
for (nuint i = 0; i < meshletCount; i++)
|
||||||
|
{
|
||||||
|
ref var meshlet = ref pMeshlets[i];
|
||||||
|
|
||||||
|
if (config.optimizeClusters)
|
||||||
|
{
|
||||||
|
MeshOptApi.OptimizeMeshlet(
|
||||||
|
pMeshletVertices + meshlet.vertex_offset,
|
||||||
|
pMeshletTriangles + meshlet.triangle_offset,
|
||||||
|
meshlet.triangle_count,
|
||||||
|
meshlet.vertex_count
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var cluster = new Cluster
|
||||||
|
{
|
||||||
|
vertices = meshlet.vertex_count,
|
||||||
|
indices = new UnsafeList<uint>((int)(meshlet.triangle_count * 3), Allocator.Persistent),
|
||||||
|
group = -1,
|
||||||
|
refined = -1
|
||||||
|
};
|
||||||
|
|
||||||
|
for (nuint j = 0; j < meshlet.triangle_count * 3; j++)
|
||||||
|
{
|
||||||
|
cluster.indices.Add(pMeshletVertices[meshlet.vertex_offset + pMeshletTriangles[meshlet.triangle_offset + j]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
clusters.Add(cluster);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clusters;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHANGED parameters: UnsafeList -> UnsafeArray (because UnsafeList with 0 count skips logic loops)
|
||||||
|
internal static void LockBoundary(UnsafeArray<byte> locks, UnsafeList<UnsafeList<int>> groups, UnsafeList<Cluster> clusters, UnsafeArray<uint> remap, byte* vertexLock)
|
||||||
|
{
|
||||||
|
var pLocks = (byte*)locks.GetUnsafePtr();
|
||||||
|
var pRemap = (uint*)remap.GetUnsafePtr();
|
||||||
|
|
||||||
|
// CHANGED: locks.Count -> locks.Length
|
||||||
|
for (var i = 0; i < locks.Length; i++)
|
||||||
|
{
|
||||||
|
pLocks[i] = unchecked((byte)(pLocks[i] & ~((1 << 0) | (1 << 7))));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < groups.Count; i++)
|
||||||
|
{
|
||||||
|
for (var j = 0; j < groups[i].Count; j++)
|
||||||
|
{
|
||||||
|
var cluster = clusters[groups[i][j]];
|
||||||
|
for (var k = 0; k < cluster.indices.Count; k++)
|
||||||
|
{
|
||||||
|
var r = pRemap[(int)cluster.indices[k]];
|
||||||
|
pLocks[r] |= (byte)(pLocks[r] >> 7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var j = 0; j < groups[i].Count; j++)
|
||||||
|
{
|
||||||
|
var cluster = clusters[groups[i][j]];
|
||||||
|
for (var k = 0; k < cluster.indices.Count; k++)
|
||||||
|
{
|
||||||
|
var r = pRemap[(int)cluster.indices[k]];
|
||||||
|
pLocks[r] |= 1 << 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHANGED: locks.Count -> locks.Length
|
||||||
|
for (var i = 0; i < locks.Length; i++)
|
||||||
|
{
|
||||||
|
var r = pRemap[i];
|
||||||
|
pLocks[i] = (byte)((pLocks[r] & 1) | (pLocks[i] & Api.meshopt_SimplifyVertex_Protect & 0xFF));
|
||||||
|
if (vertexLock != null)
|
||||||
|
{
|
||||||
|
pLocks[i] |= vertexLock[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UnsafeList<UnsafeList<int>> Partition(ClodConfig config, ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> pending, UnsafeArray<uint> remap, Allocator allocator)
|
||||||
|
{
|
||||||
|
if (pending.Count <= (int)config.partitionSize)
|
||||||
|
{
|
||||||
|
var single = new UnsafeList<UnsafeList<int>>(1, allocator);
|
||||||
|
single.Add(pending);
|
||||||
|
return single;
|
||||||
|
}
|
||||||
|
|
||||||
|
nuint totalIndexCount = 0;
|
||||||
|
for (var i = 0; i < pending.Count; i++)
|
||||||
|
{
|
||||||
|
totalIndexCount += (nuint)clusters[pending[i]].indices.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var clusterIndices = new UnsafeList<uint>((int)totalIndexCount, Allocator.FreeList);
|
||||||
|
using var clusterCounts = new UnsafeList<uint>(pending.Count, Allocator.FreeList);
|
||||||
|
|
||||||
|
nuint offset = 0;
|
||||||
|
for (var i = 0; i < pending.Count; i++)
|
||||||
|
{
|
||||||
|
var cluster = clusters[pending[i]];
|
||||||
|
clusterCounts.Add((uint)cluster.indices.Count);
|
||||||
|
for (var j = 0; j < cluster.indices.Count; j++)
|
||||||
|
{
|
||||||
|
clusterIndices.Add(((uint*)remap.GetUnsafePtr())[(int)cluster.indices[j]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += (nuint)cluster.indices.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var clusterPart = new UnsafeArray<uint>(pending.Count, Allocator.FreeList);
|
||||||
|
|
||||||
|
var partitionCount = MeshOptApi.PartitionClusters(
|
||||||
|
(uint*)clusterPart.GetUnsafePtr(),
|
||||||
|
(uint*)clusterIndices.GetUnsafePtr(),
|
||||||
|
totalIndexCount,
|
||||||
|
(uint*)clusterCounts.GetUnsafePtr(),
|
||||||
|
(nuint)pending.Count,
|
||||||
|
config.partitionSpatial ? mesh.vertexPositions : null,
|
||||||
|
(nuint)remap.Length,
|
||||||
|
mesh.vertexPositionsStride,
|
||||||
|
config.partitionSize
|
||||||
|
);
|
||||||
|
|
||||||
|
var partitions = new UnsafeList<UnsafeList<int>>((int)partitionCount, allocator);
|
||||||
|
for (nuint i = 0; i < partitionCount; i++)
|
||||||
|
{
|
||||||
|
partitions.Add(new UnsafeList<int>((int)(config.partitionSize + config.partitionSize / 3), allocator));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < pending.Count; i++)
|
||||||
|
{
|
||||||
|
partitions[(int)((uint*)clusterPart.GetUnsafePtr())[i]].Add(pending[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return partitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int OutputGroup(ClodConfig config, ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> group, ClodBounds simplified, int depth, void* outputContext, ClodOutputDelegate outputCallback)
|
||||||
|
{
|
||||||
|
using var groupClusters = new UnsafeList<ClodCluster>(group.Count, Allocator.FreeList);
|
||||||
|
|
||||||
|
for (var i = 0; i < group.Count; i++)
|
||||||
|
{
|
||||||
|
ref var srcCluster = ref clusters[group[i]];
|
||||||
|
groupClusters.Add(new ClodCluster
|
||||||
|
{
|
||||||
|
refined = srcCluster.refined,
|
||||||
|
bounds = (config.optimizeBounds && srcCluster.refined != -1)
|
||||||
|
? ComputeBounds(mesh, srcCluster.indices, srcCluster.bounds.error)
|
||||||
|
: srcCluster.bounds,
|
||||||
|
indices = (uint*)srcCluster.indices.GetUnsafePtr(),
|
||||||
|
indexCount = (nuint)srcCluster.indices.Count,
|
||||||
|
vertexCount = srcCluster.vertices
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var clodGroup = new ClodGroup { depth = depth, simplified = simplified };
|
||||||
|
var result = outputCallback != null
|
||||||
|
? outputCallback(outputContext, clodGroup, (ClodCluster*)groupClusters.GetUnsafePtr(), (nuint)groupClusters.Count)
|
||||||
|
: -1;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UnsafeArray<uint> Simplify(ClodConfig config, ClodMesh mesh, ReadOnlyUnsafeCollection<uint> indices, ReadOnlyUnsafeCollection<byte> locks, nuint targetCount, float* error, Allocator allocator)
|
||||||
|
{
|
||||||
|
var lod = new UnsafeArray<uint>(indices.Count, allocator);
|
||||||
|
|
||||||
|
if (targetCount >= (nuint)indices.Count)
|
||||||
|
{
|
||||||
|
lod.CopyFrom(indices.AsSpan());
|
||||||
|
return lod;
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = (uint)(Api.meshopt_SimplifySparse | Api.meshopt_SimplifyErrorAbsolute);
|
||||||
|
if (config.simplifyPermissive)
|
||||||
|
{
|
||||||
|
options |= Api.meshopt_SimplifyPermissive;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.simplifyRegularize)
|
||||||
|
{
|
||||||
|
options |= Api.meshopt_SimplifyRegularize;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultSize = MeshOptApi.SimplifyWithAttributes(
|
||||||
|
(uint*)lod.GetUnsafePtr(),
|
||||||
|
(uint*)indices.GetUnsafePtr(),
|
||||||
|
(nuint)indices.Count,
|
||||||
|
mesh.vertexPositions,
|
||||||
|
mesh.vertexCount,
|
||||||
|
mesh.vertexPositionsStride,
|
||||||
|
mesh.vertexAttributes,
|
||||||
|
mesh.vertexAttributesStride,
|
||||||
|
mesh.attributeWeights,
|
||||||
|
mesh.attributeCount,
|
||||||
|
(byte*)locks.GetUnsafePtr(),
|
||||||
|
targetCount,
|
||||||
|
float.MaxValue,
|
||||||
|
options,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
lod.Resize((int)resultSize);
|
||||||
|
|
||||||
|
if ((nuint)lod.Length > targetCount && config.simplifyFallbackPermissive && !config.simplifyPermissive)
|
||||||
|
{
|
||||||
|
options |= Api.meshopt_SimplifyPermissive;
|
||||||
|
resultSize = MeshOptApi.SimplifyWithAttributes(
|
||||||
|
(uint*)lod.GetUnsafePtr(),
|
||||||
|
(uint*)indices.GetUnsafePtr(),
|
||||||
|
(nuint)indices.Count,
|
||||||
|
mesh.vertexPositions,
|
||||||
|
mesh.vertexCount,
|
||||||
|
mesh.vertexPositionsStride,
|
||||||
|
mesh.vertexAttributes,
|
||||||
|
mesh.vertexAttributesStride,
|
||||||
|
mesh.attributeWeights,
|
||||||
|
mesh.attributeCount,
|
||||||
|
(byte*)locks.GetUnsafePtr(),
|
||||||
|
targetCount,
|
||||||
|
float.MaxValue,
|
||||||
|
options,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
lod.Resize((int)resultSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((nuint)lod.Length > targetCount && config.simplifyFallbackSloppy)
|
||||||
|
{
|
||||||
|
*error *= config.simplifyErrorFactorSloppy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.simplifyErrorEdgeLimit > 0)
|
||||||
|
{
|
||||||
|
float maxEdgeSq = 0;
|
||||||
|
var pIdx = (uint*)indices.GetUnsafePtr();
|
||||||
|
var posStride = (int)(mesh.vertexPositionsStride / sizeof(float));
|
||||||
|
|
||||||
|
for (var i = 0; i < indices.Count; i += 3)
|
||||||
|
{
|
||||||
|
uint a = pIdx[i], b = pIdx[i + 1], c = pIdx[i + 2];
|
||||||
|
var va = mesh.vertexPositions + (a * (uint)posStride);
|
||||||
|
var vb = mesh.vertexPositions + (b * (uint)posStride);
|
||||||
|
var vc = mesh.vertexPositions + (c * (uint)posStride);
|
||||||
|
|
||||||
|
float dx, dy, dz;
|
||||||
|
dx = va[0] - vb[0]; dy = va[1] - vb[1]; dz = va[2] - vb[2];
|
||||||
|
var eab = dx * dx + dy * dy + dz * dz;
|
||||||
|
dx = va[0] - vc[0]; dy = va[1] - vc[1]; dz = va[2] - vc[2];
|
||||||
|
var eac = dx * dx + dy * dy + dz * dz;
|
||||||
|
dx = vb[0] - vc[0]; dy = vb[1] - vc[1]; dz = vb[2] - vc[2];
|
||||||
|
var ebc = dx * dx + dy * dy + dz * dz;
|
||||||
|
|
||||||
|
var emax = Math.Max(Math.Max(eab, eac), ebc);
|
||||||
|
var emin = Math.Min(Math.Min(eab, eac), ebc);
|
||||||
|
maxEdgeSq = Math.Max(maxEdgeSq, Math.Max(emin, emax / 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
*error = Math.Min(*error, (float)Math.Sqrt(maxEdgeSq) * config.simplifyErrorEdgeLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds a cluster LOD hierarchy from the input mesh.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="config">The configuration parameters for the LOD building process.</param>
|
||||||
|
/// <param name="mesh">The input mesh data.</param>
|
||||||
|
/// <param name="outputContext">Optional context pointer passed to the output callback.</param>
|
||||||
|
/// <param name="outputCallback">Delegate invoked for each generated LOD group.</param>
|
||||||
|
/// <returns>The total count of generated clusters.</returns>
|
||||||
|
public static nuint Build(ClodConfig config, ClodMesh mesh, void* outputContext, ClodOutputDelegate outputCallback)
|
||||||
|
{
|
||||||
|
Debug.Assert(mesh.vertexAttributesStride % sizeof(float) == 0, "vertexAttributesStride must be a multiple of sizeof(float)");
|
||||||
|
|
||||||
|
using var locks = new UnsafeArray<byte>((int)mesh.vertexCount, Allocator.FreeList, AllocationOption.Clear);
|
||||||
|
using var remap = new UnsafeArray<uint>((int)mesh.vertexCount, Allocator.FreeList);
|
||||||
|
|
||||||
|
MeshOptApi.GeneratePositionRemap((uint*)remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride);
|
||||||
|
|
||||||
|
if (mesh.attributeProtectMask != 0)
|
||||||
|
{
|
||||||
|
var maxAttributes = mesh.vertexAttributesStride / sizeof(float);
|
||||||
|
for (nuint i = 0; i < mesh.vertexCount; i++)
|
||||||
|
{
|
||||||
|
var r = ((uint*)remap.GetUnsafePtr())[(int)i];
|
||||||
|
for (nuint j = 0; j < maxAttributes; j++)
|
||||||
|
{
|
||||||
|
if ((r != i) && ((mesh.attributeProtectMask & (1u << (int)j)) != 0))
|
||||||
|
{
|
||||||
|
if (mesh.vertexAttributes[i * maxAttributes + j] != mesh.vertexAttributes[r * maxAttributes + j])
|
||||||
|
{
|
||||||
|
((byte*)locks.GetUnsafePtr())[(int)i] |= Api.meshopt_SimplifyVertex_Protect & 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using var clusters = Clusterize(config, mesh, mesh.indices, mesh.indexCount, Allocator.FreeList);
|
||||||
|
|
||||||
|
for (var i = 0; i < clusters.Count; i++)
|
||||||
|
{
|
||||||
|
clusters[i].bounds = ComputeBounds(mesh, clusters[i].indices, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
using var pending = new UnsafeList<int>(clusters.Count, Allocator.FreeList);
|
||||||
|
for (var i = 0; i < clusters.Count; i++)
|
||||||
|
{
|
||||||
|
pending.Add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
var depth = 0;
|
||||||
|
|
||||||
|
while (pending.Count > 1)
|
||||||
|
{
|
||||||
|
using var groups = Partition(config, mesh, clusters, pending, remap, Allocator.FreeList);
|
||||||
|
pending.Clear();
|
||||||
|
|
||||||
|
LockBoundary(locks, groups, clusters, remap, mesh.vertexLock);
|
||||||
|
|
||||||
|
for (var i = 0; i < groups.Count; i++)
|
||||||
|
{
|
||||||
|
using var merged = new UnsafeList<uint>(groups[i].Count * (int)config.maxTriangles * 3, Allocator.FreeList);
|
||||||
|
for (var j = 0; j < groups[i].Count; j++)
|
||||||
|
{
|
||||||
|
var clusterIndices = clusters[groups[i][j]].indices;
|
||||||
|
for (var k = 0; k < clusterIndices.Count; k++)
|
||||||
|
{
|
||||||
|
merged.Add(clusterIndices[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetSize = ((nuint)merged.Count / 3) * (nuint)config.simplifyRatio * 3;
|
||||||
|
var bounds = MergeBounds(clusters, groups[i]);
|
||||||
|
|
||||||
|
var error = 0.0f;
|
||||||
|
using var simplified = Simplify(config, mesh, merged.AsReadOnly(), locks.AsReadOnly(), targetSize, &error, Allocator.FreeList);
|
||||||
|
|
||||||
|
if ((nuint)simplified.Length > (nuint)(merged.Count * config.simplifyThreshold))
|
||||||
|
{
|
||||||
|
bounds.error = float.MaxValue;
|
||||||
|
OutputGroup(config, mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bounds.error = Math.Max(bounds.error * config.simplifyErrorMergePrevious, error) + error * config.simplifyErrorMergeAdditive;
|
||||||
|
|
||||||
|
var refined = OutputGroup(config, mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback);
|
||||||
|
|
||||||
|
for (var j = 0; j < groups[i].Count; j++)
|
||||||
|
{
|
||||||
|
clusters[groups[i][j]].Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
using var split = Clusterize(config, mesh, (uint*)simplified.GetUnsafePtr(), (nuint)simplified.Length, Allocator.FreeList);
|
||||||
|
for (var j = 0; j < split.Count; j++)
|
||||||
|
{
|
||||||
|
split[j].refined = refined;
|
||||||
|
split[j].bounds = bounds;
|
||||||
|
clusters.Add(split[j]);
|
||||||
|
pending.Add(clusters.Count - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < groups.Count; i++)
|
||||||
|
{
|
||||||
|
groups[i].Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pending.Count > 0)
|
||||||
|
{
|
||||||
|
var bounds = clusters[pending[0]].bounds;
|
||||||
|
bounds.error = float.MaxValue;
|
||||||
|
OutputGroup(config, mesh, clusters, pending, bounds, depth, outputContext, outputCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
var finalClusterCount = (nuint)clusters.Count;
|
||||||
|
|
||||||
|
for (var i = 0; i < clusters.Count; i++)
|
||||||
|
{
|
||||||
|
clusters[i].Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalClusterCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user