Adetonics/src/Oct.cs

144 lines
3.6 KiB
C#
Raw Normal View History

2026-03-02 15:03:39 +02:00
#nullable enable
using System.Collections.Generic;
using Godot;
namespace adatonic;
public class Node
{
public Node()
{
}
public Node(int id, Vector3 pos)
{
Id = id;
Position = pos;
}
public int Id;
public Vector3 Position;
}
public class Oct
{
private Vector3 Start;
private Vector3 Extent;
Oct?[]? Trees = null;
private Node? Node = null;
public Oct()
{
Start = -Vector3.One;
Extent = Vector3.One * 2f;
}
public Oct(Vector3 start, Vector3 extent)
{
Start = start;
Extent = extent;
}
public void Insert(Node node)
{
if (!IsInside(node.Position))
{
GD.Print($"Failed to insert to Octree - Point out of bounds!");
return;
}
if (Node == null && Trees == null)
{
Node = node;
return;
}
Trees ??= new Oct?[8];
if (Node != null)
{
int octOld = WhichSubOct(Node.Position);
Trees[octOld] ??= new Oct(GetSubStart(octOld), Extent * 0.5f);
Trees[octOld]?.Insert(Node);
Node = null;
}
int oct = WhichSubOct(node.Position);
Trees[oct] ??= new Oct(GetSubStart(oct), Extent * 0.5f);
Trees[oct]?.Insert(node);
}
public Node? SearchNearest(Vector3 position)
{
Node? best = null;
float bestDist = float.MaxValue;
SearchNearest(position, ref best, ref bestDist);
return best;
}
private void SearchNearest(Vector3 position, ref Node? best, ref float bestDist)
{
if (Node != null)
{
float dist = position.DistanceSquaredTo(Node.Position);
if (dist < bestDist)
{
bestDist = dist;
best = Node;
}
}
if (Trees == null)
return;
int first = WhichSubOct(position);
Trees[first]?.SearchNearest(position, ref best, ref bestDist);
for (int i = 0; i < 8; i++)
{
if (i == first || Trees[i] == null)
continue;
float boxDist = Trees[i]!.DistanceToBox(position);
if (boxDist < bestDist)
{
Trees[i]!.SearchNearest(position, ref best, ref bestDist);
}
}
}
private float DistanceToBox(Vector3 p)
{
Vector3 min = Start;
Vector3 max = Start + Extent;
float dx = Mathf.Max(Mathf.Max(min.X - p.X, 0), p.X - max.X);
float dy = Mathf.Max(Mathf.Max(min.Y - p.Y, 0), p.Y - max.Y);
float dz = Mathf.Max(Mathf.Max(min.Z - p.Z, 0), p.Z - max.Z);
return dx * dx + dy * dy + dz * dz;
}
public int WhichSubOct(Vector3 position)
{
bool left = position.X < Start.X + Extent.X * 0.5f;
bool bottom = position.Y < Start.Y + Extent.Y * 0.5f;
bool near = position.Z < Start.Z + Extent.Z * 0.5f;
return (left ? 1 : 0) + (bottom ? 2 : 0) + (near ? 4 : 0);
}
public Vector3 GetSubStart(int oct)
{
Vector3 start = Vector3.Zero;
bool left = (oct & (1 << 0)) != 0;
bool bottom = (oct & (1 << 1)) != 0;
bool near = (oct & (1 << 2)) != 0;
start.X += left ? Start.X : Start.X + Extent.X * 0.5f;
start.Y += bottom ? Start.Y : Start.Y + Extent.Y * 0.5f;
start.Z += near ? Start.Z : Start.Z + Extent.Z * 0.5f;
return start;
}
public bool IsInside(Vector3 position)
{
return position > Start && position < Start + Extent;
}
}