using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; // Layout for the GPU buffer: must match HLSL struct exactly and size-aligned. [StructLayout(LayoutKind.Sequential)] public struct LinearNode { // ---- Bloc 1 (16 bytes) public float penetrationFactor; // 4 public float reflexionFactor; // 4 public uint childMask; // 4 public int childBase; // 4 // ---- Bloc 7 (16 bytes) public uint isLeaf; // 4 public uint isOccupied; // 4 public uint pad0; // 4 public uint pad1; // 4 } public struct LinearTree { public LinearNode[] nodes; public int rootIndex; } public static class OctreeGpuHelpers { // Flatten the recursive octree into a linear array. // Returns LinearNode[] and root index (should be 0) public static LinearTree FlattenOctree(OctreeNode root) { nodeToSig = new Dictionary(ReferenceEqualityComparer.Default); sigToIndex = new Dictionary(); List flatNodes = new List(); flatNodes.Add(new LinearNode()); int rootIndex = BuildNode(root, flatNodes, 0); LinearTree linearTree; linearTree.rootIndex = rootIndex; linearTree.nodes = flatNodes.ToArray(); nodeToSig = null; return linearTree; } private static Dictionary nodeToSig; private static Dictionary sigToIndex; private static string ComputeChildrenSignature(OctreeNode node) { // child signatures string[] childSigs = new string[8]; for (int i = 0; i < 8; i++) { var c = node.children[i]; childSigs[i] = c != null ? ComputeChildrenSignature(c) : ""; } // Build signature: combine node properties + child signatures (deterministic) // Use StringBuilder then a hash (optional) — here we keep reasonable precision for floats var sb = new StringBuilder(); sb.Append(node.isLeaf ? 'L' : 'N'); sb.Append(node.isOccupied ? '1' : '0'); sb.Append('|'); sb.Append(node.penetrationFactor.ToString("R")); // "R" for round-trip sb.Append(','); sb.Append(node.reflexionFactor.ToString("R")); sb.Append('|'); for (int i = 0; i < 8; i++) { sb.Append(childSigs[i]); sb.Append('|'); } string sig = sb.ToString(); return sig; } // PASS 1: compute a stable signature for each node (string here for simplicity) private static string ComputeSignature(OctreeNode node) { // child signatures string[] childSigs = new string[8]; for (int i = 0; i < 8; i++) { var c = node.children[i]; childSigs[i] = c != null ? ComputeChildrenSignature(c) : ""; } // Build signature: combine node properties + child signatures (deterministic) // Use StringBuilder then a hash (optional) — here we keep reasonable precision for floats var sb = new StringBuilder(); for (int i = 0; i < 8; i++) { sb.Append(childSigs[i]); sb.Append('|'); } string sig = sb.ToString(); return sig; } // PASS 2: build nodes list — parent reserved first so unique children end up contiguous after parent. private static int BuildNode(OctreeNode node, List nodes, int nodeIndex) { LinearNode ln = nodes[nodeIndex]; // Now fill the LinearNode with correct fields. ln.penetrationFactor = node.penetrationFactor; ln.reflexionFactor = node.reflexionFactor; ln.isLeaf = node.isLeaf ? 1u : 0u; ln.isOccupied = node.isOccupied ? 1u : 0u; ln.pad0 = 0; ln.pad1 = 0; ln.childMask = 0; string sig = ComputeSignature(node); if(sigToIndex.TryGetValue(sig, out int existingIndex)) { ln.childBase = nodes[existingIndex].childBase; for (int i = 0; i < 8; i++) { if (node.children[i] != null) ln.childMask |= (1u << i); } nodes[nodeIndex] = ln; return nodeIndex; } ln.childBase = nodes.Count; for (int i = 0; i < 8; i++) { OctreeNode childNode = node.children[i]; if (childNode != null) { nodes.Add(new LinearNode()); } } int realIndex = 0; for (int i = 0; i < 8; i++) { OctreeNode childNode = node.children[i]; if (childNode != null) { int index = BuildNode(childNode, nodes, ln.childBase + realIndex); string childSig = ComputeSignature(childNode); sigToIndex[childSig] = index; ln.childMask |= 1u << i; realIndex++; } } nodes[nodeIndex] = ln; return nodeIndex; } // Simple reference-equality comparer for OctreeNode (so Dictionary uses node identity) private class ReferenceEqualityComparer : IEqualityComparer where T : class { public static readonly ReferenceEqualityComparer Default = new ReferenceEqualityComparer(); public bool Equals(T x, T y) => ReferenceEquals(x, y); public int GetHashCode(T obj) => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); } }