mirror of
https://github.com/simtactics/mysimulation.git
synced 2025-03-21 01:01:22 +00:00
- NioTSO client isn't needed because we're using RayLib - Added FreeSO's API server to handle most backend operations
359 lines
14 KiB
C#
Executable file
359 lines
14 KiB
C#
Executable file
using FSO.Common.Model;
|
|
using FSO.Common.WorldGeometry;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace FSO.SimAntics.Model.Routing
|
|
{
|
|
/// <summary>
|
|
/// (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.
|
|
/// </summary>
|
|
public class BaseTriangleSet
|
|
{
|
|
public VMObstacleSetNode[] Nodes;
|
|
protected List<int> FreeList = new List<int>();
|
|
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<BaseMeshTriangle> 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<BaseMeshTriangle> AllIntersect(BaseMeshTriangle rect)
|
|
{
|
|
var result = new List<BaseMeshTriangle>();
|
|
if (Root == -1) return result;
|
|
else
|
|
{
|
|
AllIntersect(ref Nodes[Root], rect, result);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
public void AllIntersect(ref VMObstacleSetNode node, BaseMeshTriangle rect, List<BaseMeshTriangle> 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<BaseMeshTriangle> OnEdge(BaseMeshTriangle rect)
|
|
{
|
|
var result = new List<BaseMeshTriangle>();
|
|
if (Root == -1) return result;
|
|
else
|
|
{
|
|
OnEdge(ref Nodes[Root], rect, result);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
public void OnEdge(ref VMObstacleSetNode node, BaseMeshTriangle rect, List<BaseMeshTriangle> 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<BaseMeshTriangle> 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);
|
|
}
|
|
}
|
|
}
|