using UnityEngine; using Pathfinding.RVO.Sampled; namespace Pathfinding.RVO { /// <summary> /// Quadtree for quick nearest neighbour search of rvo agents. /// See: Pathfinding.RVO.Simulator /// </summary> public class RVOQuadtree { const int LeafSize = 15; float maxRadius = 0; /// <summary> /// Node in a quadtree for storing RVO agents. /// See: Pathfinding.GraphNode for the node class that is used for pathfinding data. /// </summary> struct Node { public int child00; public Agent linkedList; public byte count; /// <summary>Maximum speed of all agents inside this node</summary> public float maxSpeed; public void Add (Agent agent) { agent.next = linkedList; linkedList = agent; } /// <summary> /// Distribute the agents in this node among the children. /// Used after subdividing the node. /// </summary> public void Distribute (Node[] nodes, Rect r) { Vector2 c = r.center; while (linkedList != null) { Agent nx = linkedList.next; var index = child00 + (linkedList.position.x > c.x ? 2 : 0) + (linkedList.position.y > c.y ? 1 : 0); nodes[index].Add(linkedList); linkedList = nx; } count = 0; } public float CalculateMaxSpeed (Node[] nodes, int index) { if (child00 == index) { // Leaf node for (var agent = linkedList; agent != null; agent = agent.next) { maxSpeed = System.Math.Max(maxSpeed, agent.CalculatedSpeed); } } else { maxSpeed = System.Math.Max(nodes[child00].CalculateMaxSpeed(nodes, child00), nodes[child00+1].CalculateMaxSpeed(nodes, child00+1)); maxSpeed = System.Math.Max(maxSpeed, nodes[child00+2].CalculateMaxSpeed(nodes, child00+2)); maxSpeed = System.Math.Max(maxSpeed, nodes[child00+3].CalculateMaxSpeed(nodes, child00+3)); } return maxSpeed; } } Node[] nodes = new Node[16]; int filledNodes = 1; Rect bounds; /// <summary>Removes all agents from the tree</summary> public void Clear () { nodes[0] = new Node(); filledNodes = 1; maxRadius = 0; } public void SetBounds (Rect r) { bounds = r; } int GetNodeIndex () { if (filledNodes + 4 >= nodes.Length) { var nds = new Node[nodes.Length*2]; for (int i = 0; i < nodes.Length; i++) nds[i] = nodes[i]; nodes = nds; } nodes[filledNodes] = new Node(); nodes[filledNodes].child00 = filledNodes; filledNodes++; nodes[filledNodes] = new Node(); nodes[filledNodes].child00 = filledNodes; filledNodes++; nodes[filledNodes] = new Node(); nodes[filledNodes].child00 = filledNodes; filledNodes++; nodes[filledNodes] = new Node(); nodes[filledNodes].child00 = filledNodes; filledNodes++; return filledNodes-4; } /// <summary> /// Add a new agent to the tree. /// Warning: Agents must not be added multiple times to the same tree /// </summary> public void Insert (Agent agent) { int i = 0; Rect r = bounds; Vector2 p = new Vector2(agent.position.x, agent.position.y); agent.next = null; maxRadius = System.Math.Max(agent.radius, maxRadius); int depth = 0; while (true) { depth++; if (nodes[i].child00 == i) { // Leaf node. Break at depth 10 in case lots of agents ( > LeafSize ) are in the same spot if (nodes[i].count < LeafSize || depth > 10) { nodes[i].Add(agent); nodes[i].count++; break; } else { // Split nodes[i].child00 = GetNodeIndex(); nodes[i].Distribute(nodes, r); } } // Note, no else if (nodes[i].child00 != i) { // Not a leaf node Vector2 c = r.center; if (p.x > c.x) { if (p.y > c.y) { i = nodes[i].child00+3; r = Rect.MinMaxRect(c.x, c.y, r.xMax, r.yMax); } else { i = nodes[i].child00+2; r = Rect.MinMaxRect(c.x, r.yMin, r.xMax, c.y); } } else { if (p.y > c.y) { i = nodes[i].child00+1; r = Rect.MinMaxRect(r.xMin, c.y, c.x, r.yMax); } else { i = nodes[i].child00; r = Rect.MinMaxRect(r.xMin, r.yMin, c.x, c.y); } } } } } public void CalculateSpeeds () { nodes[0].CalculateMaxSpeed(nodes, 0); } public void Query (Vector2 p, float speed, float timeHorizon, float agentRadius, Agent agent) { new QuadtreeQuery { p = p, speed = speed, timeHorizon = timeHorizon, maxRadius = float.PositiveInfinity, agentRadius = agentRadius, agent = agent, nodes = nodes }.QueryRec(0, bounds); } struct QuadtreeQuery { public Vector2 p; public float speed, timeHorizon, agentRadius, maxRadius; public Agent agent; public Node[] nodes; public void QueryRec (int i, Rect r) { // Determine the radius that we need to search to take all agents into account // Note: the second agentRadius usage should actually be the radius of the other agents, not this agent // but for performance reasons and for simplicity we assume that agents have approximately the same radius. // Thus an agent with a very small radius may in some cases detect an agent with a very large radius too late // however this effect should be minor. var radius = System.Math.Min(System.Math.Max((nodes[i].maxSpeed + speed)*timeHorizon, agentRadius) + agentRadius, maxRadius); if (nodes[i].child00 == i) { // Leaf node for (Agent a = nodes[i].linkedList; a != null; a = a.next) { float v = agent.InsertAgentNeighbour(a, radius*radius); // Limit the search if the agent has hit the max number of nearby agents threshold if (v < maxRadius*maxRadius) { maxRadius = Mathf.Sqrt(v); } } } else { // Not a leaf node Vector2 c = r.center; if (p.x-radius < c.x) { if (p.y-radius < c.y) { QueryRec(nodes[i].child00, Rect.MinMaxRect(r.xMin, r.yMin, c.x, c.y)); radius = System.Math.Min(radius, maxRadius); } if (p.y+radius > c.y) { QueryRec(nodes[i].child00+1, Rect.MinMaxRect(r.xMin, c.y, c.x, r.yMax)); radius = System.Math.Min(radius, maxRadius); } } if (p.x+radius > c.x) { if (p.y-radius < c.y) { QueryRec(nodes[i].child00+2, Rect.MinMaxRect(c.x, r.yMin, r.xMax, c.y)); radius = System.Math.Min(radius, maxRadius); } if (p.y+radius > c.y) { QueryRec(nodes[i].child00+3, Rect.MinMaxRect(c.x, c.y, r.xMax, r.yMax)); } } } } } public void DebugDraw () { DebugDrawRec(0, bounds); } void DebugDrawRec (int i, Rect r) { Debug.DrawLine(new Vector3(r.xMin, 0, r.yMin), new Vector3(r.xMax, 0, r.yMin), Color.white); Debug.DrawLine(new Vector3(r.xMax, 0, r.yMin), new Vector3(r.xMax, 0, r.yMax), Color.white); Debug.DrawLine(new Vector3(r.xMax, 0, r.yMax), new Vector3(r.xMin, 0, r.yMax), Color.white); Debug.DrawLine(new Vector3(r.xMin, 0, r.yMax), new Vector3(r.xMin, 0, r.yMin), Color.white); if (nodes[i].child00 != i) { // Not a leaf node Vector2 c = r.center; DebugDrawRec(nodes[i].child00+3, Rect.MinMaxRect(c.x, c.y, r.xMax, r.yMax)); DebugDrawRec(nodes[i].child00+2, Rect.MinMaxRect(c.x, r.yMin, r.xMax, c.y)); DebugDrawRec(nodes[i].child00+1, Rect.MinMaxRect(r.xMin, c.y, c.x, r.yMax)); DebugDrawRec(nodes[i].child00+0, Rect.MinMaxRect(r.xMin, r.yMin, c.x, c.y)); } for (Agent a = nodes[i].linkedList; a != null; a = a.next) { var p = nodes[i].linkedList.position; Debug.DrawLine(new Vector3(p.x, 0, p.y)+Vector3.up, new Vector3(a.position.x, 0, a.position.y)+Vector3.up, new Color(1, 1, 0, 0.5f)); } } } }