144 lines
3.6 KiB
C#
144 lines
3.6 KiB
C#
|
|
#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;
|
||
|
|
}
|
||
|
|
}
|