mysimulation/server/tso.common/WorldGeometry/Utils/TriangleSet.cs
Tony Bark 22191ce648 Removed NioTSO client and server
- NioTSO client isn't needed because we're using RayLib
- Added FreeSO's API server to handle most backend operations
2024-05-01 02:55:43 -04:00

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);
}
}
}