Local project added

This commit is contained in:
2025-10-19 22:56:25 +02:00
parent 7c15a8b78d
commit 8124165e9b
96 changed files with 17552 additions and 0 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3ac02403714fd184095cb0c32517d4b0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class OctreeNode
{
public Bounds bounds; // The 3D space covered by this node
public bool isLeaf; // True if this node is not subdivided
public bool isOccupied; // True if this node contains solid voxels
public bool hasChildrenOccupied; // True if this node contains solid voxels
public OctreeNode[] children; // 8 sub-nodes (for each octant)
public float penetrationFactor;
public float reflexionFactor;
public OctreeNode(Bounds bounds)
{
penetrationFactor = 1;
reflexionFactor = 1;
this.bounds = bounds;
isLeaf = true;
isOccupied = false;
hasChildrenOccupied = false;
children = new OctreeNode[8];
children[0] = null;
children[1] = null;
children[2] = null;
children[3] = null;
children[4] = null;
children[5] = null;
children[6] = null;
children[7] = null;
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 13827c41731283f4bb6202cac32e51ab

View File

@ -0,0 +1,101 @@
using UnityEngine;
public class VoxelTreeRaycaster
{
public struct HitInfo
{
public Vector3 point;
public Vector3 normal;
public float distance;
public OctreeNode node;
}
// Public API: perform a raycast through the voxel tree
public bool Raycast(OctreeNode root, Vector3 origin, Vector3 direction, float maxDistance, out HitInfo hit)
{
hit = new HitInfo();
if (root == null || direction == Vector3.zero)
return false;
direction.Normalize();
Bounds treeBounds = root.bounds;
// First, check if ray starts inside or intersects the tree bounds
if (!treeBounds.IntersectRay(new Ray(origin, direction), out float entryDist))
{
// If outside, skip rays that miss the whole tree
return false;
}
// Clamp to max distance
float dist = Mathf.Max(0f, entryDist);
float endDist = Mathf.Min(maxDistance, entryDist + maxDistance);
return Traverse(root, origin, direction, ref dist, endDist, out hit);
}
// Recursive traversal
private bool Traverse(OctreeNode node, Vector3 origin, Vector3 dir, ref float dist, float maxDist, out HitInfo hit)
{
hit = new HitInfo();
// Stop if beyond range or node is outside of ray segment
if (dist > maxDist) return false;
if (!node.bounds.IntersectRay(new Ray(origin, dir), out float entry))
return false;
// Adjust distance to this node's entry point
dist = Mathf.Max(dist, entry);
// Leaf node
if (node.isLeaf)
{
if (node.isOccupied)
{
// Approximate hit point (entry point)
hit.point = origin + dir * dist;
hit.distance = dist;
hit.normal = GetApproximateNormal(node, origin, dir);
hit.node = node;
return true;
}
return false;
}
// Otherwise, traverse children
if (node.children != null)
{
// Check all child nodes that intersect the ray
foreach (var child in node.children)
{
if (child == null) continue;
if (Traverse(child, origin, dir, ref dist, maxDist, out hit))
return true;
}
}
return false;
}
// Approximate normal from cube face hit
private Vector3 GetApproximateNormal(OctreeNode node, Vector3 origin, Vector3 dir)
{
Vector3 p = origin;
Bounds b = node.bounds;
Vector3 center = b.center;
Vector3 extents = b.extents;
Vector3 local = p - center;
Vector3 absDir = new Vector3(Mathf.Abs(dir.x), Mathf.Abs(dir.y), Mathf.Abs(dir.z));
Vector3 normal = Vector3.zero;
if (absDir.x > absDir.y && absDir.x > absDir.z)
normal = new Vector3(Mathf.Sign(-dir.x), 0, 0);
else if (absDir.y > absDir.z)
normal = new Vector3(0, Mathf.Sign(-dir.y), 0);
else
normal = new Vector3(0, 0, Mathf.Sign(-dir.z));
return normal;
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 968c451685e18e54a9cb1d419f303c54

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 11755919bdf10884492adbb91aa6837b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,179 @@
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);
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d8d6d3db0d1de6a488ad343cfc26da8d

View File

@ -0,0 +1,60 @@
using UnityEngine;
public class VoxelRaycastGPU : MonoBehaviour
{
public VoxelRaycastGPU(ComputeShader computeShader)
{
raycastShader = computeShader;
}
public ComputeShader raycastShader;
public struct Ray
{
public float pad;
public Vector3 direction;
}
public struct BatchData
{
public Vector3 origin;
public float maxDistance;
};
public struct Hit
{
public float penetrationFactor;
public float reflexionFactor;
public float lastDistance;
private float _pad1;
public Vector3 origin;
private float _pad2;
public Vector3 position;
private float _pad3;
public float distance;
public uint hit;
private float _pad4;
private float _pad5;
}
public void Init( ComputeShader computeShader )
{
raycastShader = computeShader;
}
public void Raycast( in Ray[] rays, float maxDistance, ref ComputeBuffer rayBuffer, ref ComputeBuffer hitBuffer )
{
int kernel = raycastShader.FindKernel("CSMain");
rayBuffer.SetData(rays);
raycastShader.SetBuffer(kernel, "rays", rayBuffer);
raycastShader.SetBuffer(kernel, "hits", hitBuffer);
raycastShader.SetFloat("maxDistance", maxDistance);
int threadGroups = Mathf.CeilToInt(rays.Length / 64f);
raycastShader.Dispatch(kernel, threadGroups, 1, 1);
Hit[] hits = new Hit[rays.Length];
hitBuffer.GetData(hits);
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 46218eff2d8f9a6489444052fb6a7ef6

View File

@ -0,0 +1,142 @@
using UnityEngine;
using System.Runtime.InteropServices;
public class VoxelRaycastGpuManager
{
ComputeShader raycastShader;
OctreeNode root; // assign your built octree root
public VoxelRaycastGpuManager(ComputeShader computeShader, OctreeNode octreeRoot)
{
raycastShader = computeShader;
root = octreeRoot;
}
ComputeBuffer nodeBuffer;
public LinearTree linearTree;
int kernel;
ComputeBuffer hitCounterBuffer = null;
ComputeBuffer rayBuffer = null;
int raysPerBatch;
int batchDataClassSize = Marshal.SizeOf(typeof(VoxelRaycastGPU.BatchData));
int groupsX;
int maxRaycastPerIteration;
public VoxelRaycastGPU.BatchData[] Raycast(in VoxelRaycastGPU.BatchData[] batchData, int datasLenght)
{
ComputeBuffer hitBuffer = new ComputeBuffer(5000, batchDataClassSize, ComputeBufferType.Append);
ComputeBuffer datasBuffer = new ComputeBuffer(datasLenght, batchDataClassSize, ComputeBufferType.Default);
ComputeBuffer countBuffer = new ComputeBuffer(1, sizeof(int), ComputeBufferType.Raw);
int iteration = 0;
int currentCount = datasLenght;
int previousCount = datasLenght;
datasBuffer.SetData(batchData, 0, 0, currentCount);
while (iteration < 4 && currentCount > 0)
{
previousCount = currentCount;
raycastShader.SetBuffer(kernel, "batchDatas", datasBuffer);
raycastShader.SetBuffer(kernel, "hits", hitBuffer);
int threadsY = 8;
int groupsY = Mathf.CeilToInt((float)currentCount / threadsY);
raycastShader.Dispatch(kernel, groupsX, groupsY, 1);
ComputeBuffer.CopyCount(hitBuffer, countBuffer, 0);
int[] countArr = new int[1];
countBuffer.GetData(countArr);
currentCount = countArr[0];
/*VoxelRaycastGPU.BatchData[] hits = new VoxelRaycastGPU.BatchData[currentCount];
hitBuffer.GetData(hits, 0, 0, currentCount);
for (int i = 0; i < currentCount; i++)
{
GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
sphere.transform.position = hits[i].origin;
sphere.transform.localScale = Vector3.one * 0.5f;
}*/
if (currentCount > 0)
{
(datasBuffer, hitBuffer) = (hitBuffer, datasBuffer);
hitBuffer.Release();
hitBuffer = new ComputeBuffer(5000, batchDataClassSize, ComputeBufferType.Append);
}
iteration++;
}
VoxelRaycastGPU.BatchData[] result = new VoxelRaycastGPU.BatchData[previousCount];
if (currentCount == 0)
datasBuffer.GetData(result, 0, 0, previousCount);
else
hitBuffer.GetData(result, 0, 0, previousCount);
hitBuffer.Release();
datasBuffer.Release();
countBuffer.Release();
return result;
}
public void Init( int nbRaysPerBatch, in VoxelRaycastGPU.Ray[] rays )
{
// Flatten octree
linearTree = OctreeGpuHelpers.FlattenOctree(root);
int nodeStride = Marshal.SizeOf(typeof(LinearNode)); // should be 64
rayBuffer = new ComputeBuffer(rays.Length, Marshal.SizeOf(typeof(VoxelRaycastGPU.Ray)), ComputeBufferType.Default);
rayBuffer.SetData(rays, 0, 0, rays.Length);
// Create GPU buffer for nodes
nodeBuffer = new ComputeBuffer(linearTree.nodes.Length, nodeStride, ComputeBufferType.Default);
nodeBuffer.SetData(linearTree.nodes);
hitCounterBuffer = new ComputeBuffer(1, sizeof(int), ComputeBufferType.Raw);
uint[] counterInit = { 0 };
counterInit[0] = 0;
hitCounterBuffer.SetData(counterInit);
kernel = raycastShader.FindKernel("CSMain");
raycastShader.SetBuffer(kernel, "nodes", nodeBuffer);
raycastShader.SetBuffer(kernel, "hitCount", hitCounterBuffer);
raycastShader.SetBuffer(kernel, "rays", rayBuffer);
raycastShader.SetInt("raysPerBatch", nbRaysPerBatch);
raycastShader.SetInt("rootIndex", linearTree.rootIndex);
raycastShader.SetInt("nodeCount", linearTree.nodes.Length);
raycastShader.SetFloat("rootHalfSize", root.bounds.size.x / 2f);
raycastShader.SetFloats("rootCenter", new float[3] { root.bounds.center.x, root.bounds.center.y, root.bounds.center.z });
raysPerBatch = nbRaysPerBatch;
groupsX = Mathf.CeilToInt((float)raysPerBatch / 8);
maxRaycastPerIteration = 5000 / raysPerBatch;
}
~VoxelRaycastGpuManager()
{
if (hitCounterBuffer != null)
hitCounterBuffer.Release();
if (rayBuffer != null)
rayBuffer.Release();
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f13f71d059d1b854bb51ac64bede9396

View File

@ -0,0 +1,128 @@
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
public class VoxelTreeBuilder
{
private int maxDepth;
private float minNodeSize;
public VoxelTreeBuilder(int maxDepth, float minNodeSize)
{
this.maxDepth = maxDepth;
this.minNodeSize = minNodeSize;
}
public OctreeNode Build(Bounds bounds, Func<Vector3, float, IsSolidNodeAnswere> isSolid)
{
return BuildNode(bounds, isSolid, 0);
}
private OctreeNode BuildNode(Bounds bounds, Func<Vector3, float, IsSolidNodeAnswere> isSolid, int depth)
{
OctreeNode node = new OctreeNode(bounds);
IsSolidNodeAnswere answere;
// Stop subdivision if we reached the maximum depth or smallest size
if (depth >= maxDepth)
{
// Check the voxel state at the center
answere = isSolid(bounds.center, bounds.size.x);
node.isOccupied = answere.isSolid;
node.penetrationFactor = answere.penetrationFactor;
node.reflexionFactor = answere.reflexionFactor;
return node;
}
if( bounds.size.x <= minNodeSize )
{
bool allSolid = true;
bool allEmpty = true;
foreach (Vector3 corner in GetCorners(bounds))
{
answere = isSolid(corner, bounds.size.x);
bool solid = answere.isSolid;
node.penetrationFactor = answere.penetrationFactor;
node.reflexionFactor = answere.reflexionFactor;
allSolid &= solid;
allEmpty &= !solid;
}
// Check if the entire cube is homogeneous (either fully solid or empty)
if (depth >= maxDepth || allSolid)
{
// If homogeneous, stop subdivision
if (allSolid || allEmpty)
{
if (allSolid)
{
answere = isSolid(bounds.center, bounds.size.x);
node.isOccupied = answere.isSolid;
node.penetrationFactor = answere.penetrationFactor;
node.reflexionFactor = answere.reflexionFactor;
node.isOccupied = allSolid;
}
return node;
}
}
}
// Otherwise, subdivide into 8 child nodes
node.isLeaf = false;
int arrayIndex = 0;
foreach (var (i, subBounds) in GetSubBounds(bounds).Select((b, i) => (i, b)))
{
OctreeNode childrenNode = BuildNode(subBounds, isSolid, depth + 1);
if (childrenNode.isOccupied == true || childrenNode.hasChildrenOccupied == true)
{
node.hasChildrenOccupied = true;
node.children[arrayIndex] = childrenNode;
}
else
{
node.children[arrayIndex] = null;
}
arrayIndex++;
}
if (arrayIndex == 0)
node.isLeaf = true;
return node;
}
private IEnumerable<Bounds> GetSubBounds(Bounds parent)
{
// Generate 8 sub-bounds for the octree subdivision
Vector3 size = parent.size / 2f;
Vector3 min = parent.min;
for (int x = 0; x < 2; x++)
for (int y = 0; y < 2; y++)
for (int z = 0; z < 2; z++)
{
Vector3 center = min + Vector3.Scale(new Vector3(x + 0.5f, y + 0.5f, z + 0.5f), size);
yield return new Bounds(center, size);
}
}
private IEnumerable<Vector3> GetCorners(Bounds b)
{
// Return all 8 corners of the bounding box
Vector3 min = b.min;
Vector3 max = b.max;
for (int x = 0; x <= 1; x++)
for (int y = 0; y <= 1; y++)
for (int z = 0; z <= 1; z++)
yield return new Vector3(
x == 0 ? min.x : max.x,
y == 0 ? min.y : max.y,
z == 0 ? min.z : max.z);
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 45419c0456ed1a74f97cc990244648a5

View File

@ -0,0 +1,105 @@
using UnityEngine;
public struct IsSolidNodeAnswere
{
public float penetrationFactor;
public float reflexionFactor;
public bool isSolid;
public GameObject Go;
}
public class VoxelTreeManager : MonoBehaviour
{
public LayerMask solidLayerMask;
public float voxelSize = 2f; // Minimum voxel size
public int maxDepth = 6; // Tree subdivision limit
public float boundsSize = 300f; // World size covered by the voxel tree
public ComputeShader computeShader;
private OctreeNode root;
private VoxelTreeRaycaster raycaster = new VoxelTreeRaycaster();
public VoxelRaycastGpuManager gpuRayCaster;
void Start()
{
Debug.Log($"Building voxel tree with LayerMask: {LayerMaskToString(solidLayerMask)}");
var builder = new VoxelTreeBuilder(maxDepth, voxelSize);
root = builder.Build(new Bounds(Vector3.zero, Vector3.one * boundsSize), IsSolid);
var dbg = FindObjectOfType<VoxelTreeDebugger>();
if (dbg) dbg.root = root;
gpuRayCaster = new VoxelRaycastGpuManager(computeShader, root);
}
// This function replaces pos => pos.magnitude < 100f
private IsSolidNodeAnswere IsSolid(Vector3 pos, float size)
{
IsSolidNodeAnswere answere;
answere.Go = null;
answere.penetrationFactor = 1;
answere.reflexionFactor = 1;
float overlapMargin = size / 3f;
answere.isSolid = false;
Collider[] hits = Physics.OverlapSphere(pos, overlapMargin, solidLayerMask, QueryTriggerInteraction.Ignore);
if (hits.Length > 0)
{
answere.isSolid = true;
for (int i = 0; i < hits.Length; i++)
{
AudioObject audioObj = hits[i].GetComponent<AudioObject>();
if (audioObj)
{
answere.penetrationFactor = audioObj.audioMaterialSettings.penetrationFactor;
answere.reflexionFactor = audioObj.audioMaterialSettings.reflexionFactor;
break;
}
}
return answere;
}
return answere;
}
private static string LayerMaskToString(LayerMask mask)
{
string names = "";
for (int i = 0; i < 32; i++)
{
if (((1 << i) & mask.value) != 0)
{
names += LayerMask.LayerToName(i) + " ";
}
}
return names.Trim();
}
public bool CastRay(Vector3 origin, Vector3 dir, float distance, out VoxelTreeRaycaster.HitInfo outHit)
{
if (raycaster.Raycast(root, origin, dir, distance, out var hit))
{
outHit = hit;
// Debug.Log($"Ray hit voxel at {hit.point}, dist={hit.distance:F2}");
//Debug.DrawLine(origin, hit.point, Color.red, 2f);
return true;
}
else
{
outHit = hit;
//Debug.Log("No voxel hit");
return false;
}
}
public VoxelRaycastGPU.BatchData[] CastGpuRay( in VoxelRaycastGPU.BatchData[] batchData, int datasLenght )
{
return gpuRayCaster.Raycast( in batchData, datasLenght );
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 81db4efe0049de943a98566ddc2ff7e2