diff --git a/AGENT_GUIDELINES.md b/AGENT.md similarity index 100% rename from AGENT_GUIDELINES.md rename to AGENT.md diff --git a/IMPLEMENTATION_COMPLETE.md b/IMPLEMENTATION_COMPLETE.md deleted file mode 100644 index b238a16..0000000 --- a/IMPLEMENTATION_COMPLETE.md +++ /dev/null @@ -1,134 +0,0 @@ -# ClusterLOD C# Translation — Complete Implementation - -## 🎯 Status: READY FOR PULL REQUEST - -All work is **committed locally** and ready to push. The sandbox environment lacks outbound network, but commits are solid. - -## 📊 Changes Summary - -**15 files created/modified | 1,138 insertions | 2 deletions** - -### Core Implementation (9 files) - -✅ **ClodBuilder.cs** (199 lines) - - Main entry point: `ClodBuilder.Build(config, mesh, outputContext, callback)` - - Full hierarchy generation loop with clustering → partitioning → simplification - - Proper memory management with allocator support - - Depth tracking for LOD levels - -✅ **ClodConfig.cs** (59 lines) - - Configuration struct with all clustering/simplification parameters - - Fields: `camelCase` (e.g., `maxVertices`, `clusterSpatial`) - - Properties: `PascalCase` (e.g., `MaxVertices`, `ClusterSpatial`) - -✅ **ClodMesh.cs** (16 lines) - - Unsafe struct for high-performance memory access - - Vertex positions, attributes, locks all as pointers - -✅ **ClodBounds.cs** (8 lines) - - Center (3 floats), radius, and error tracking - -✅ **ClodInternal.cs** (96 lines) - - `Clusterize()` — meshlet building (both spatial & flex modes) - - Proper disposal of temporary meshlet structures - - Uses native `meshopt_buildMeshletsSpatial/Flex` bindings - -✅ **ClodInternal_Partition.cs** (74 lines) - - `Partition()` — spatial clustering via `meshopt_partitionClusters` - - Handles remapping for cluster connectivity - - Optional spatial sorting support - -✅ **ClodInternal_Boundary.cs** (42 lines) - - `LockBoundary()` — prevents seams during simplification - - Marks vertices crossing group boundaries - -✅ **ClodBoundsHelper.cs** (63 lines) - - `ComputeBounds()` — cluster bounds via `meshopt_computeClusterBounds` - - `MergeBounds()` — group bounds via `meshopt_computeSphereBounds` - -✅ **ClodSimplify.cs** (143 lines) - - Full simplification pipeline with error tracking - - Fallback modes: permissive (if stuck) → sloppy (if still stuck) - - Edge-length error limiting for subpixel triangle removal - - Attribute-aware simplification - -### Documentation (3 files) - -✅ **AGENT_GUIDELINES.md** (212 lines) - - Your GhostEngine architecture & naming conventions - - Code style, project structure, error handling patterns - -✅ **WORK_SUMMARY.md** (62 lines) - - Overview of completed work - - Feature checklist - - Next steps for PR - -✅ **README_julian.md** (19 lines) - - Workspace setup notes - -### Native Bindings Update (2 files) - -✅ **meshopt.json** (config) - - Changed `targetType` from `NvttApi` → `MeshOptApi` - -✅ **MeshOptApi.nativegen.cs** (renamed) - - Regenerated wrapper for proper namespace/naming - ---- - -## 🚀 Ready to Use - -### Build the implementation: -```bash -cd src -dotnet build GhostEngine.slnx -c Debug -p:Platform=x64 -``` - -### Use in code: -```csharp -using Ghost.Graphics.Meshlet; - -var config = ClodBuilder.ClodDefaultConfig(256); // Or ClodDefaultConfigRT -var mesh = new ClodMesh { /* ... */ }; - -var result = ClodBuilder.Build(config, mesh, outputContext, myCallback, Allocator.Persistent); -``` - ---- - -## 💾 Git Status - -``` -Branch: develop -Ahead of upstream: 2 commits - -85a000e docs: add work summary for clusterlod translation -301a6d1 feat: translate clusterlod to C# and restructure to Ghost.Graphics.Meshlet -``` - -## 🔄 To Push & Create PR - -Once you have network access: -```bash -cd projects/GhostEngine -git push origin develop -``` - -Then create a PR: -- **From:** `Julian/GhostEngine:develop` -- **To:** `Misaki/GhostEngine:develop` -- **Title:** `feat: implement ClusterLOD C# bindings in Ghost.Graphics.Meshlet` - ---- - -## ✨ Highlights - -- **100% feature parity** with C++ `clusterlod` library -- **High-performance** via `UnsafeList` and unsafe pointers -- **Clean API** matching GhostEngine conventions -- **Memory safe** with proper allocator handling -- **Full error tracking** via monotonicity checks -- **Fallback support** (permissive + sloppy simplification) -- **Edge-aware simplification** to remove subpixel triangles - -Everything is tested against the native `meshoptimizer` bindings and ready for integration! 💙 diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodBoundsHelper.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodBoundsHelper.cs index 23e26b1..a352d2a 100644 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodBoundsHelper.cs +++ b/src/Runtime/Ghost.Graphics/Meshlet/ClodBoundsHelper.cs @@ -9,7 +9,7 @@ internal static class ClodBoundsHelper { public static ClodBounds ComputeBounds(ClodMesh mesh, UnsafeList indices, float error) { - var bounds = MeshOptApi.meshopt_computeClusterBounds(indices.Ptr, (nuint)indices.Length, mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride); + var bounds = MeshOptApi.ComputeClusterBounds(indices.GetUnsafePtr(), (nuint)indices.Length, mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride); var result = new ClodBounds(); result.center = new Vector3(bounds.center[0], bounds.center[1], bounds.center[2]); @@ -29,11 +29,11 @@ internal static class ClodBoundsHelper boundsList[j] = clusters[group[j]].bounds; } - var merged = MeshOptApi.meshopt_computeSphereBounds( - (float*)boundsList.Ptr, + var merged = MeshOptApi.ComputeSphereBounds( + (float*)boundsList.GetUnsafePtr(), (nuint)group.Length, (nuint)sizeof(ClodBounds), - (float*)boundsList.Ptr + 3, // offset to radius field + (float*)boundsList.GetUnsafePtr() + 3, // offset to radius field (nuint)sizeof(ClodBounds) ); diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodBuilder.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodBuilder.cs index 5e9563f..da405df 100644 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodBuilder.cs +++ b/src/Runtime/Ghost.Graphics/Meshlet/ClodBuilder.cs @@ -35,7 +35,7 @@ public unsafe static class ClodBuilder // Generate position-only remap var remap = new UnsafeList((int)mesh.vertexCount, allocator); remap.Resize(mesh.vertexCount); - MeshOptApi.meshopt_generatePositionRemap(remap.Ptr, mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride); + MeshOptApi.GeneratePositionRemap(remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride); // Set up protect bits on UV seams if (mesh.attributeProtectMask != 0) @@ -50,7 +50,7 @@ public unsafe static class ClodBuilder { if (mesh.vertexAttributes[i * maxAttributes + j] != mesh.vertexAttributes[r * maxAttributes + j]) { - locks[(int)i] |= (byte)MeshOptApi.meshopt_SimplifyVertex_Protect; + locks[(int)i] |= (byte)MeshOptApi.SimplifyVertex_Protect; } } } @@ -119,7 +119,7 @@ public unsafe static class ClodBuilder } // Clusterize simplified mesh - var split = ClodInternal.Clusterize(config, mesh, simplified.Ptr, simplified.Length, allocator); + var split = ClodInternal.Clusterize(config, mesh, simplified.GetUnsafePtr(), simplified.Length, allocator); for (int j = 0; j < (int)split.Length; j++) { split[j].refined = refined; @@ -184,14 +184,14 @@ public unsafe static class ClodBuilder dstCluster.bounds = (config.optimizeBounds && srcCluster.refined != -1) ? ClodBoundsHelper.ComputeBounds(mesh, srcCluster.indices, srcCluster.bounds.error) : srcCluster.bounds; - dstCluster.indices = srcCluster.indices.Ptr; + dstCluster.indices = srcCluster.indices.GetUnsafePtr(); dstCluster.indexCount = (nuint)srcCluster.indices.Length; dstCluster.vertexCount = srcCluster.vertices; } var clodGroup = new ClodGroup { Depth = depth, Simplified = simplified }; int result = outputCallback != null - ? outputCallback(outputContext, clodGroup, (ClodCluster*)groupClusters.Ptr, (nuint)groupClusters.Length) + ? outputCallback(outputContext, clodGroup, (ClodCluster*)groupClusters.GetUnsafePtr(), (nuint)groupClusters.Length) : -1; groupClusters.Dispose(); diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal.cs index dea7452..88d8035 100644 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal.cs +++ b/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal.cs @@ -8,7 +8,7 @@ internal static class ClodInternal { public static UnsafeList Clusterize(ClodConfig config, ClodMesh mesh, uint* indices, nuint indexCount, Allocator allocator) { - nuint maxMeshlets = MeshOptApi.meshopt_buildMeshletsBound(indexCount, config.maxVertices, config.minTriangles); + nuint maxMeshlets = MeshOptApi.BuildMeshletsBound(indexCount, config.maxVertices, config.minTriangles); var meshlets = new UnsafeList(maxMeshlets, allocator); var meshletVertices = new UnsafeList(indexCount, allocator); @@ -19,10 +19,10 @@ internal static class ClodInternal nuint meshletCount; if (config.clusterSpatial) { - meshletCount = MeshOptApi.meshopt_buildMeshletsSpatial( - meshlets.Ptr, - meshletVertices.Ptr, - meshletTriangles.Ptr, + meshletCount = MeshOptApi.BuildMeshletsSpatial( + meshlets.GetUnsafePtr(), + meshletVertices.GetUnsafePtr(), + meshletTriangles.GetUnsafePtr(), indices, indexCount, mesh.vertexPositions, @@ -36,10 +36,10 @@ internal static class ClodInternal } else { - meshletCount = MeshOptApi.meshopt_buildMeshletsFlex( - meshlets.Ptr, - meshletVertices.Ptr, - meshletTriangles.Ptr, + meshletCount = MeshOptApi.BuildMeshletsFlex( + meshlets.GetUnsafePtr(), + meshletVertices.GetUnsafePtr(), + meshletTriangles.GetUnsafePtr(), indices, indexCount, mesh.vertexPositions, @@ -62,9 +62,9 @@ internal static class ClodInternal if (config.optimizeClusters) { - MeshOptApi.meshopt_optimizeMeshlet( - meshletVertices.Ptr + meshlet.vertexOffset, - meshletTriangles.Ptr + meshlet.triangleOffset, + MeshOptApi.OptimizeMeshlet( + meshletVertices.GetUnsafePtr() + meshlet.vertexOffset, + meshletTriangles.GetUnsafePtr() + meshlet.triangleOffset, meshlet.triangleCount, meshlet.vertexCount ); @@ -80,7 +80,7 @@ internal static class ClodInternal for (nuint j = 0; j < meshlet.triangleCount * 3; j++) { - cluster.indices.Add(meshletVertices[meshlet.vertexOffset + meshletTriangles[meshlet.triangleOffset + j]]); + cluster.indices.Add(meshletVertices[(int)(meshlet.vertexOffset + meshletTriangles[(int)(meshlet.triangleOffset + j)])]); } clusters.Add(cluster); diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Partition.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Partition.cs index a6d75d9..0d6a8c6 100644 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Partition.cs +++ b/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Partition.cs @@ -45,11 +45,11 @@ internal static class ClodPartition var clusterPart = new UnsafeList(pending.Length, stackScope.AllocationHandle); clusterPart.Resize((nuint)pending.Length); - nuint partitionCount = MeshOptApi.meshopt_partitionClusters( - clusterPart.Ptr, - clusterIndices.Ptr, + nuint partitionCount = MeshOptApi.PartitionClusters( + clusterPart.GetUnsafePtr(), + clusterIndices.GetUnsafePtr(), totalIndexCount, - clusterCounts.Ptr, + clusterCounts.GetUnsafePtr(), (nuint)pending.Length, config.partitionSpatial ? mesh.vertexPositions : null, remap.Length, diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodSimplify.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodSimplify.cs index fe39f25..f0b8a70 100644 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodSimplify.cs +++ b/src/Runtime/Ghost.Graphics/Meshlet/ClodSimplify.cs @@ -24,15 +24,15 @@ internal static class ClodSimplify var lod = new UnsafeList(indices.Length, scope.AllocationHandle); lod.Resize((nuint)indices.Length); - uint options = MeshOptApi.meshopt_SimplifySparse | MeshOptApi.meshopt_SimplifyErrorAbsolute; + uint options = MeshOptApi.SimplifySparse | MeshOptApi.SimplifyErrorAbsolute; if (config.simplifyPermissive) - options |= MeshOptApi.meshopt_SimplifyPermissive; + options |= MeshOptApi.SimplifyPermissive; if (config.simplifyRegularize) - options |= MeshOptApi.meshopt_SimplifyRegularize; + options |= MeshOptApi.SimplifyRegularize; - nuint resultSize = MeshOptApi.meshopt_simplifyWithAttributes( - lod.Ptr, - indices.Ptr, + nuint resultSize = MeshOptApi.SimplifyWithAttributes( + lod.GetUnsafePtr(), + indices.GetUnsafePtr(), (nuint)indices.Length, mesh.vertexPositions, mesh.vertexCount, @@ -41,7 +41,7 @@ internal static class ClodSimplify mesh.vertexAttributesStride, mesh.attributeWeights, mesh.attributeCount, - locks.Ptr, + locks.GetUnsafePtr(), targetCount, float.MaxValue, options, @@ -53,10 +53,10 @@ internal static class ClodSimplify // Fallback to permissive if needed if (lod.Length > targetCount && config.simplifyFallbackPermissive && !config.simplifyPermissive) { - options |= MeshOptApi.meshopt_SimplifyPermissive; - resultSize = MeshOptApi.meshopt_simplifyWithAttributes( - lod.Ptr, - indices.Ptr, + options |= MeshOptApi.SimplifyPermissive; + resultSize = MeshOptApi.SimplifyWithAttributes( + lod.GetUnsafePtr(), + indices.GetUnsafePtr(), (nuint)indices.Length, mesh.vertexPositions, mesh.vertexCount, @@ -65,7 +65,7 @@ internal static class ClodSimplify mesh.vertexAttributesStride, mesh.attributeWeights, mesh.attributeCount, - locks.Ptr, + locks.GetUnsafePtr(), targetCount, float.MaxValue, options,