180 lines
5.5 KiB
C#
180 lines
5.5 KiB
C#
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<OctreeNode, string>(ReferenceEqualityComparer<OctreeNode>.Default);
|
|
sigToIndex = new Dictionary<string, int>();
|
|
|
|
List<LinearNode> flatNodes = new List<LinearNode>();
|
|
|
|
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<OctreeNode, string> nodeToSig;
|
|
private static Dictionary<string, int> 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) : "<null>";
|
|
}
|
|
|
|
// 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) : "<null>";
|
|
}
|
|
|
|
// 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<LinearNode> 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<T> : IEqualityComparer<T> where T : class
|
|
{
|
|
public static readonly ReferenceEqualityComparer<T> Default = new ReferenceEqualityComparer<T>();
|
|
public bool Equals(T x, T y) => ReferenceEquals(x, y);
|
|
public int GetHashCode(T obj) => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj);
|
|
}
|
|
}
|