Files
SoundTool/Assets/Scripts/VoxelOctree/VoxelTreeBuilder.cs
2025-10-19 22:56:25 +02:00

128 lines
4.1 KiB
C#

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);
}
}