using FSO.Common.Model; using FSO.Common.WorldGeometry; using System; using System.Collections.Generic; using System.Linq; namespace FSO.SimAntics.Model.Routing { /// /// (copied from VMObstacleSet) /// A k-d Tree for looking up rectangle intersections /// Ideally much faster lookups for routing rectangular cover, avatar and object movement. O(log n) vs O(n) /// /// Concerns: /// - Tree balancing: a random insert may be the best solution, as algorithms for this can be quite complex. /// - Should be mutable, as rect cover routing will add new entries. We also may want to add or remove elements from a "static" set. /// - Tree cloning: wall and object sets would be nice, but for routing ideally we want to add new free-rects to the set dynamically. This means we need to clone the tree. /// - Return true if ANY found, or return ALL found. First useful for routing, second for checking collision validity. /// public class BaseTriangleSet { public VMObstacleSetNode[] Nodes; protected List FreeList = new List(); protected int PoolInd = 0; public int Root = -1; public int Count; public BaseTriangleSet() { InitNodes(64); } public BaseTriangleSet(BaseTriangleSet last) { if (last.Root != -1) { Count = last.Count; Nodes = (VMObstacleSetNode[])last.Nodes.Clone(); Root = last.Root; FreeList = last.FreeList.ToList(); PoolInd = last.PoolInd; } else { InitNodes(64); } } public BaseTriangleSet(IEnumerable obstacles) { InitNodes(obstacles.Count()); foreach (var obstacle in obstacles) Add(obstacle); } private void InitNodes(int capacity) { if (Nodes == null) { Nodes = new VMObstacleSetNode[capacity]; } else { Array.Resize(ref Nodes, capacity); } } private int GetNode() { if (FreeList.Count > 0) { var free = FreeList.Last(); FreeList.RemoveAt(FreeList.Count - 1); return free; } else { return PoolInd++; } } private int GetNode(IntersectRectDimension dir, BaseMeshTriangle rect) { var ind = GetNode(); Nodes[ind] = new VMObstacleSetNode() { Dimension = dir, Rect = rect, LeftChild = -1, RightChild = -1, Index = ind, x1 = rect.x1, x2 = rect.x2, y1 = rect.y1, y2 = rect.y2 }; return ind; } private void Reclaim(int index) { if (index == PoolInd - 1) PoolInd--; else FreeList.Add(index); } public void Add(BaseMeshTriangle rect) { if (PoolInd >= Nodes.Length && FreeList.Count == 0) InitNodes(Nodes.Length * 2); Count++; if (Root == -1) { Root = GetNode(IntersectRectDimension.Left, rect); } else { AddAsChild(ref Nodes[Root], rect); } } private void AddAsChild(ref VMObstacleSetNode node, BaseMeshTriangle rect) { bool rightSide = false; switch (node.Dimension) { case IntersectRectDimension.Top: rightSide = rect.y1 > node.Rect.y1; break; case IntersectRectDimension.Left: rightSide = rect.x1 > node.Rect.x1; break; case IntersectRectDimension.Bottom: rightSide = rect.y2 > node.Rect.y2; break; case IntersectRectDimension.Right: rightSide = rect.x2 > node.Rect.x2; break; } if (rightSide) { if (node.RightChild != -1) AddAsChild(ref Nodes[node.RightChild], rect); else { node.RightChild = GetNode((IntersectRectDimension)(((int)node.Dimension + 1) % 4), rect); } } else { if (node.LeftChild != -1) AddAsChild(ref Nodes[node.LeftChild], rect); else { node.LeftChild = GetNode((IntersectRectDimension)(((int)node.Dimension + 1) % 4), rect); } } } public void RecursiveReAdd(VMObstacleSetNode node) { Count--; Reclaim(node.Index); Add(node.Rect); if (node.LeftChild != -1) RecursiveReAdd(Nodes[node.LeftChild]); if (node.RightChild != -1) RecursiveReAdd(Nodes[node.RightChild]); } public bool SearchForIntersect(BaseMeshTriangle rect) { if (Root == -1) return false; else { return SearchForIntersect(ref Nodes[Root], rect); } } public bool SearchForIntersect(ref VMObstacleSetNode node, BaseMeshTriangle rect) { if (node.Intersects(rect)) return true; //search in child nodes. int dontSearch = 0; switch (node.Dimension) { case IntersectRectDimension.Top: dontSearch = (rect.y2 <= node.y1) ? 2 : 0; break; //if true, do not have to search right (where top greater) case IntersectRectDimension.Left: dontSearch = (rect.x2 <= node.x1) ? 2 : 0; break; //if true, do not have to search right (where left greater) case IntersectRectDimension.Bottom: dontSearch = (rect.y1 >= node.y2) ? 1 : 0; break; //if true, do not have to search left (where bottom less) case IntersectRectDimension.Right: dontSearch = (rect.x1 >= node.x2) ? 1 : 0; break; //if true, do not have to search left (where right less) } //may need to search both :'( won't happen often with our small rectangles over large space though. return ((dontSearch != 1 && node.LeftChild != -1 && SearchForIntersect(ref Nodes[node.LeftChild], rect)) || (dontSearch != 2 && node.RightChild != -1 && SearchForIntersect(ref Nodes[node.RightChild], rect))); } public List AllIntersect(BaseMeshTriangle rect) { var result = new List(); if (Root == -1) return result; else { AllIntersect(ref Nodes[Root], rect, result); return result; } } public void AllIntersect(ref VMObstacleSetNode node, BaseMeshTriangle rect, List result) { if (node.Intersects(rect)) result.Add(node.Rect); //search in child nodes. int dontSearch = 0; switch (node.Dimension) { case IntersectRectDimension.Top: dontSearch = (rect.y2 <= node.y1) ? 2 : 0; break; //if true, do not have to search right (where top greater) case IntersectRectDimension.Left: dontSearch = (rect.x2 <= node.x1) ? 2 : 0; break; //if true, do not have to search right (where left greater) case IntersectRectDimension.Bottom: dontSearch = (rect.y1 >= node.y2) ? 1 : 0; break; //if true, do not have to search left (where bottom less) case IntersectRectDimension.Right: dontSearch = (rect.x1 >= node.x2) ? 1 : 0; break; //if true, do not have to search left (where right less) } //may need to search both :'( won't happen often with our small rectangles over large space though. //if (node.LeftChild != -1) AllIntersect(ref Nodes[node.LeftChild], rect, result); //if (node.RightChild != -1) AllIntersect(ref Nodes[node.RightChild], rect, result); if (dontSearch != 1 && node.LeftChild != -1) AllIntersect(ref Nodes[node.LeftChild], rect, result); if (dontSearch != 2 && node.RightChild != -1) AllIntersect(ref Nodes[node.RightChild], rect, result); } public List OnEdge(BaseMeshTriangle rect) { var result = new List(); if (Root == -1) return result; else { OnEdge(ref Nodes[Root], rect, result); return result; } } public void OnEdge(ref VMObstacleSetNode node, BaseMeshTriangle rect, List result) { if (node.OnEdge(rect)) result.Add(node.Rect); //search in child nodes. //binary search to find equal opposing edges. int dontSearch = 0; switch (node.Dimension) { case IntersectRectDimension.Top: dontSearch = (rect.y2 < node.y1) ? 2 : 0; break; //if true, do not have to search right (where top greater) case IntersectRectDimension.Left: dontSearch = (rect.x2 < node.x1) ? 2 : 0; break; //if true, do not have to search right (where left greater) case IntersectRectDimension.Bottom: dontSearch = (rect.y1 > node.y2) ? 1 : 0; break; //if true, do not have to search left (where bottom less) case IntersectRectDimension.Right: dontSearch = (rect.x1 > node.x2) ? 1 : 0; break; //if true, do not have to search left (where right less) } //may need to search both :'( won't happen often with our small rectangles over large space though. //if (node.LeftChild != -1) OnEdge(ref Nodes[node.LeftChild], rect, result); //if (node.RightChild != -1) OnEdge(ref Nodes[node.RightChild], rect, result); if (dontSearch != 1 && node.LeftChild != -1) OnEdge(ref Nodes[node.LeftChild], rect, result); if (dontSearch != 2 && node.RightChild != -1) OnEdge(ref Nodes[node.RightChild], rect, result); } public static BaseTriangleSet RoughBalanced(List input) { //roughly attempts to balance the set. //...currently by random shuffle. at least it's deterministic? var rand = new Random(1); for (int i = 1; i < input.Count; i++) { var swap = input[i - 1]; var ind = rand.Next(input.Count - i) + i; input[i - 1] = input[ind]; input[ind] = swap; } return new BaseTriangleSet(input); } /* public bool Delete(VMEntityObstacle rect) { if (Root == -1) return false; else { var result = Delete(ref Nodes[Root], rect, ref Nodes[Root]); if (result) { Count--; } return result; } } */ /* public bool Delete(ref VMObstacleSetNode node, VMEntityObstacle rect, ref VMObstacleSetNode parent) { if (rect.Parent == (node.Rect as VMEntityObstacle).Parent) { if (parent.Index == node.Index) { Root = -1; } else { if (parent.LeftChild == node.Index) parent.LeftChild = -1; if (parent.RightChild == node.Index) parent.RightChild = -1; } if (node.LeftChild != -1) RecursiveReAdd(Nodes[node.LeftChild]); if (node.RightChild != -1) RecursiveReAdd(Nodes[node.RightChild]); Reclaim(node.Index); return true; } //search in child nodes. //binary search to find equal opposing edges. bool rightSide = false; switch (node.Dimension) { case IntersectRectDimension.Top: rightSide = rect.y1 > node.y1; break; case IntersectRectDimension.Left: rightSide = rect.x1 > node.x1; break; case IntersectRectDimension.Bottom: rightSide = rect.y2 > node.y2; break; case IntersectRectDimension.Right: rightSide = rect.x2 > node.x2; break; } return ((rightSide && node.RightChild != -1 && Delete(ref Nodes[node.RightChild], rect, ref node)) || (!rightSide && node.LeftChild != -1 && Delete(ref Nodes[node.LeftChild], rect, ref node))); } */ } public struct VMObstacleSetNode { public int LeftChild; public int RightChild; public IntersectRectDimension Dimension; public BaseMeshTriangle Rect; public int Index; public float x1; public float x2; public float y1; public float y2; public bool Intersects(BaseMeshTriangle other) { return !((other.x1 >= x2 || other.x2 <= x1) || (other.y1 >= y2 || other.y2 <= y1)); } public bool OnEdge(BaseMeshTriangle other) { return (x2 == other.x1) || (x1 == other.x2) || (y1 == other.y2) || (y2 == other.y1); } } }