185 lines
4.8 KiB
C#
185 lines
4.8 KiB
C#
![]() |
using System;
|
||
|
using UnityEngine;
|
||
|
using System.Collections.Generic;
|
||
|
|
||
|
namespace Pathfinding {
|
||
|
/// <summary>
|
||
|
/// Axis Aligned Bounding Box Tree.
|
||
|
/// Holds a bounding box tree of RecastMeshObj components.
|
||
|
/// Note that it assumes that once an object has been added, it stays at the same
|
||
|
/// world position. If it is moved, then it might not be able to be found.
|
||
|
/// </summary>
|
||
|
public class RecastBBTree {
|
||
|
RecastBBTreeBox root;
|
||
|
|
||
|
/// <summary>Queries the tree for all RecastMeshObjs inside the specified bounds.</summary>
|
||
|
/// <param name="bounds">World space bounds to search within</param>
|
||
|
/// <param name="buffer">The results will be added to the buffer</param>
|
||
|
public void QueryInBounds (Rect bounds, List<RecastMeshObj> buffer) {
|
||
|
if (root == null) return;
|
||
|
QueryBoxInBounds(root, bounds, buffer);
|
||
|
}
|
||
|
|
||
|
void QueryBoxInBounds (RecastBBTreeBox box, Rect bounds, List<RecastMeshObj> boxes) {
|
||
|
if (box.mesh != null) {
|
||
|
// Leaf node
|
||
|
if (RectIntersectsRect(box.rect, bounds)) {
|
||
|
// Found a RecastMeshObj, add it to the result
|
||
|
boxes.Add(box.mesh);
|
||
|
}
|
||
|
} else {
|
||
|
// Search children
|
||
|
if (RectIntersectsRect(box.c1.rect, bounds)) {
|
||
|
QueryBoxInBounds(box.c1, bounds, boxes);
|
||
|
}
|
||
|
|
||
|
if (RectIntersectsRect(box.c2.rect, bounds)) {
|
||
|
QueryBoxInBounds(box.c2, bounds, boxes);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Removes the specified mesh from the tree.
|
||
|
/// Assumes that it has the correct bounds information.
|
||
|
///
|
||
|
/// Returns: True if the mesh was removed from the tree, false otherwise.
|
||
|
/// </summary>
|
||
|
public bool Remove (RecastMeshObj mesh) {
|
||
|
if (mesh == null) throw new ArgumentNullException("mesh");
|
||
|
|
||
|
if (root == null) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool found = false;
|
||
|
Bounds b = mesh.GetBounds();
|
||
|
//Convert to top down rect
|
||
|
Rect r = Rect.MinMaxRect(b.min.x, b.min.z, b.max.x, b.max.z);
|
||
|
|
||
|
root = RemoveBox(root, mesh, r, ref found);
|
||
|
|
||
|
return found;
|
||
|
}
|
||
|
|
||
|
RecastBBTreeBox RemoveBox (RecastBBTreeBox c, RecastMeshObj mesh, Rect bounds, ref bool found) {
|
||
|
if (!RectIntersectsRect(c.rect, bounds)) {
|
||
|
return c;
|
||
|
}
|
||
|
|
||
|
if (c.mesh == mesh) {
|
||
|
found = true;
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
if (c.mesh == null && !found) {
|
||
|
c.c1 = RemoveBox(c.c1, mesh, bounds, ref found);
|
||
|
if (c.c1 == null) {
|
||
|
return c.c2;
|
||
|
}
|
||
|
|
||
|
if (!found) {
|
||
|
c.c2 = RemoveBox(c.c2, mesh, bounds, ref found);
|
||
|
if (c.c2 == null) {
|
||
|
return c.c1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TODO: Will this ever be called?
|
||
|
if (found) {
|
||
|
c.rect = ExpandToContain(c.c1.rect, c.c2.rect);
|
||
|
}
|
||
|
}
|
||
|
return c;
|
||
|
}
|
||
|
|
||
|
/// <summary>Inserts a RecastMeshObj in the tree at its current position</summary>
|
||
|
public void Insert (RecastMeshObj mesh) {
|
||
|
var box = new RecastBBTreeBox(mesh);
|
||
|
|
||
|
if (root == null) {
|
||
|
root = box;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
RecastBBTreeBox c = root;
|
||
|
while (true) {
|
||
|
c.rect = ExpandToContain(c.rect, box.rect);
|
||
|
if (c.mesh != null) {
|
||
|
//Is Leaf
|
||
|
c.c1 = box;
|
||
|
var box2 = new RecastBBTreeBox(c.mesh);
|
||
|
c.c2 = box2;
|
||
|
|
||
|
|
||
|
c.mesh = null;
|
||
|
return;
|
||
|
} else {
|
||
|
float e1 = ExpansionRequired(c.c1.rect, box.rect);
|
||
|
float e2 = ExpansionRequired(c.c2.rect, box.rect);
|
||
|
|
||
|
// Choose the rect requiring the least expansion to contain box.rect
|
||
|
if (e1 < e2) {
|
||
|
c = c.c1;
|
||
|
} else if (e2 < e1) {
|
||
|
c = c.c2;
|
||
|
} else {
|
||
|
// Equal, Choose the one with the smallest area
|
||
|
c = RectArea(c.c1.rect) < RectArea(c.c2.rect) ? c.c1 : c.c2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static bool RectIntersectsRect (Rect r, Rect r2) {
|
||
|
return (r.xMax > r2.xMin && r.yMax > r2.yMin && r2.xMax > r.xMin && r2.yMax > r.yMin);
|
||
|
}
|
||
|
|
||
|
/// <summary>Returns the difference in area between r and r expanded to contain r2</summary>
|
||
|
static float ExpansionRequired (Rect r, Rect r2) {
|
||
|
float xMin = Mathf.Min(r.xMin, r2.xMin);
|
||
|
float xMax = Mathf.Max(r.xMax, r2.xMax);
|
||
|
float yMin = Mathf.Min(r.yMin, r2.yMin);
|
||
|
float yMax = Mathf.Max(r.yMax, r2.yMax);
|
||
|
|
||
|
return (xMax-xMin)*(yMax-yMin)-RectArea(r);
|
||
|
}
|
||
|
|
||
|
/// <summary>Returns a new rect which contains both r and r2</summary>
|
||
|
static Rect ExpandToContain (Rect r, Rect r2) {
|
||
|
float xMin = Mathf.Min(r.xMin, r2.xMin);
|
||
|
float xMax = Mathf.Max(r.xMax, r2.xMax);
|
||
|
float yMin = Mathf.Min(r.yMin, r2.yMin);
|
||
|
float yMax = Mathf.Max(r.yMax, r2.yMax);
|
||
|
|
||
|
return Rect.MinMaxRect(xMin, yMin, xMax, yMax);
|
||
|
}
|
||
|
|
||
|
/// <summary>Returns the area of a rect</summary>
|
||
|
static float RectArea (Rect r) {
|
||
|
return r.width*r.height;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class RecastBBTreeBox {
|
||
|
public Rect rect;
|
||
|
public RecastMeshObj mesh;
|
||
|
|
||
|
public RecastBBTreeBox c1;
|
||
|
public RecastBBTreeBox c2;
|
||
|
|
||
|
public RecastBBTreeBox (RecastMeshObj mesh) {
|
||
|
this.mesh = mesh;
|
||
|
|
||
|
Vector3 min = mesh.bounds.min;
|
||
|
Vector3 max = mesh.bounds.max;
|
||
|
rect = Rect.MinMaxRect(min.x, min.z, max.x, max.z);
|
||
|
}
|
||
|
|
||
|
public bool Contains (Vector3 p) {
|
||
|
return rect.Contains(p);
|
||
|
}
|
||
|
}
|
||
|
}
|