rework spatial hash to store arbitrary data

cosmonaut 2023-01-15 12:40:45 -08:00
parent 5223e92aa8
commit b892da7749
1 changed files with 73 additions and 44 deletions

View File

@ -7,17 +7,20 @@ namespace MoonWorks.Collision.Fixed
/// Used to quickly check if two shapes are potentially overlapping. /// Used to quickly check if two shapes are potentially overlapping.
/// </summary> /// </summary>
/// <typeparam name="T">The type that will be used to uniquely identify shape-transform pairs.</typeparam> /// <typeparam name="T">The type that will be used to uniquely identify shape-transform pairs.</typeparam>
public class SpatialHash2D<T, U> where T : struct, System.IEquatable<T> where U : IHasAABB2D public class SpatialHash2D<T, U> where T : struct, System.IEquatable<T>
{ {
private readonly Fix64 cellSize; private readonly Fix64 cellSize;
private readonly Dictionary<long, HashSet<T>> hashDictionary = new Dictionary<long, HashSet<T>>(); private readonly Dictionary<long, HashSet<T>> hashDictionary = new Dictionary<long, HashSet<T>>();
private readonly Dictionary<T, (U, Transform2D, uint)> IDLookup = new Dictionary<T, (U, Transform2D, uint)>(); private readonly Dictionary<T, AABB2D> IDBoxLookup = new Dictionary<T, AABB2D>();
private readonly Dictionary<T, U> IDDataLookup = new Dictionary<T, U>();
public int MinX { get; private set; } = 0; private readonly HashSet<T> DynamicIDs = new HashSet<T>();
public int MaxX { get; private set; } = 0;
public int MinY { get; private set; } = 0; private int MinX;
public int MaxY { get; private set; } = 0; private int MaxX;
private int MinY;
private int MaxY;
private Queue<HashSet<T>> hashSetPool = new Queue<HashSet<T>>(); private Queue<HashSet<T>> hashSetPool = new Queue<HashSet<T>>();
@ -35,12 +38,11 @@ namespace MoonWorks.Collision.Fixed
/// Inserts an element into the SpatialHash. /// Inserts an element into the SpatialHash.
/// </summary> /// </summary>
/// <param name="id">A unique ID for the shape-transform pair.</param> /// <param name="id">A unique ID for the shape-transform pair.</param>
/// <param name="shape"></param> public void Insert(T id, AABB2D aabb, Transform2D transform2D, U data, bool dynamic = true)
/// <param name="transform2D"></param>
/// <param name="collisionGroups">A bitmask value specifying the groups this object belongs to.</param>
public void Insert(T id, U shape, Transform2D transform2D, uint collisionGroups = uint.MaxValue)
{ {
var box = AABB2D.Transformed(shape.AABB, transform2D); Remove(id);
var box = AABB2D.Transformed(aabb, transform2D);
var minHash = Hash(box.Min); var minHash = Hash(box.Min);
var maxHash = Hash(box.Max); var maxHash = Hash(box.Max);
@ -52,19 +54,26 @@ namespace MoonWorks.Collision.Fixed
} }
hashDictionary[key].Add(id); hashDictionary[key].Add(id);
IDLookup[id] = (shape, transform2D, collisionGroups); IDDataLookup[id] = data;
} }
MinX = System.Math.Min(MinX, minHash.Item1); MinX = System.Math.Min(MinX, minHash.Item1);
MinY = System.Math.Min(MinY, minHash.Item2); MinY = System.Math.Min(MinY, minHash.Item2);
MaxX = System.Math.Max(MaxX, maxHash.Item1); MaxX = System.Math.Max(MaxX, maxHash.Item1);
MaxY = System.Math.Max(MaxY, maxHash.Item2); MaxY = System.Math.Max(MaxY, maxHash.Item2);
if (dynamic)
{
DynamicIDs.Add(id);
}
IDBoxLookup[id] = box;
} }
/// <summary> /// <summary>
/// Retrieves all the potential collisions of a shape-transform pair. Excludes any shape-transforms with the given ID. /// Retrieves all the potential collisions of a shape-transform pair. Excludes any shape-transforms with the given ID.
/// </summary> /// </summary>
public RetrieveEnumerator Retrieve<V>(T id, V hasAABB, Transform2D transform2D, uint collisionMask = uint.MaxValue) where V : IHasAABB2D public RetrieveEnumerator Retrieve<V>(T id, V hasAABB, Transform2D transform2D) where V : IHasAABB2D
{ {
var box = AABB2D.Transformed(hasAABB.AABB, transform2D); var box = AABB2D.Transformed(hasAABB.AABB, transform2D);
var (minX, minY) = Hash(box.Min); var (minX, minY) = Hash(box.Min);
@ -78,18 +87,17 @@ namespace MoonWorks.Collision.Fixed
return new RetrieveEnumerator( return new RetrieveEnumerator(
this, this,
Keys(minX, minY, maxX, maxY), Keys(minX, minY, maxX, maxY),
id, id
collisionMask
); );
} }
/// <summary> /// <summary>
/// Retrieves all the potential collisions of a shape-transform pair. /// Retrieves all the potential collisions of a shape-transform pair.
/// </summary> /// </summary>
public RetrieveEnumerator Retrieve<V>(V hasAABB, Transform2D transform2D, uint collisionMask = uint.MaxValue) where V : IHasAABB2D public RetrieveEnumerator Retrieve<V>(V hasAABB, Transform2D transform2D) where V : IHasAABB2D
{ {
var box = AABB2D.Transformed(hasAABB.AABB, transform2D); var box = AABB2D.Transformed(hasAABB.AABB, transform2D);
return Retrieve(box, collisionMask); return Retrieve(box);
} }
/// <summary> /// <summary>
@ -97,7 +105,7 @@ namespace MoonWorks.Collision.Fixed
/// </summary> /// </summary>
/// <param name="aabb">A transformed AABB.</param> /// <param name="aabb">A transformed AABB.</param>
/// <returns></returns> /// <returns></returns>
public RetrieveEnumerator Retrieve(AABB2D aabb, uint collisionMask = uint.MaxValue) public RetrieveEnumerator Retrieve(T id, AABB2D aabb)
{ {
var (minX, minY) = Hash(aabb.Min); var (minX, minY) = Hash(aabb.Min);
var (maxX, maxY) = Hash(aabb.Max); var (maxX, maxY) = Hash(aabb.Max);
@ -110,14 +118,29 @@ namespace MoonWorks.Collision.Fixed
return new RetrieveEnumerator( return new RetrieveEnumerator(
this, this,
Keys(minX, minY, maxX, maxY), Keys(minX, minY, maxX, maxY),
collisionMask id
); );
} }
public void Update(T id, U shape, Transform2D transform2D, uint collisionGroups = uint.MaxValue) /// <summary>
/// Retrieves objects based on a pre-transformed AABB.
/// </summary>
/// <param name="aabb">A transformed AABB.</param>
/// <returns></returns>
public RetrieveEnumerator Retrieve(AABB2D aabb)
{ {
Remove(id); var (minX, minY) = Hash(aabb.Min);
Insert(id, shape, transform2D, collisionGroups); var (maxX, maxY) = Hash(aabb.Max);
if (minX < MinX) { minX = MinX; }
if (maxX > MaxX) { maxX = MaxX; }
if (minY < MinY) { minY = MinY; }
if (maxY > MaxY) { maxY = MaxY; }
return new RetrieveEnumerator(
this,
Keys(minX, minY, maxX, maxY)
);
} }
/// <summary> /// <summary>
@ -125,24 +148,24 @@ namespace MoonWorks.Collision.Fixed
/// </summary> /// </summary>
public void Remove(T id) public void Remove(T id)
{ {
if (IDLookup.TryGetValue(id, out var data)) if (IDBoxLookup.TryGetValue(id, out var aabb))
{ {
var (shape, transform, collisionGroups) = data; var minHash = Hash(aabb.Min);
var maxHash = Hash(aabb.Max);
var box = AABB2D.Transformed(shape.AABB, transform);
var minHash = Hash(box.Min);
var maxHash = Hash(box.Max);
foreach (var key in Keys(minHash.Item1, minHash.Item2, maxHash.Item1, maxHash.Item2)) foreach (var key in Keys(minHash.Item1, minHash.Item2, maxHash.Item1, maxHash.Item2))
{ {
if (hashDictionary.ContainsKey(key)) if (hashDictionary.TryGetValue(key, out HashSet<T> value))
{ {
hashDictionary[key].Remove(id); value.Remove(id);
} }
} }
IDLookup.Remove(id); IDDataLookup.Remove(id);
IDBoxLookup.Remove(id);
} }
DynamicIDs.Remove(id);
} }
/// <summary> /// <summary>
@ -155,7 +178,19 @@ namespace MoonWorks.Collision.Fixed
hash.Clear(); hash.Clear();
} }
IDLookup.Clear(); IDDataLookup.Clear();
IDBoxLookup.Clear();
}
/// <summary>
/// Removes
/// </summary>
public void ClearDynamic()
{
foreach (var id in DynamicIDs)
{
Remove(id);
}
} }
private static long MakeLong(int left, int right) private static long MakeLong(int left, int right)
@ -239,15 +274,13 @@ namespace MoonWorks.Collision.Fixed
private bool HashSetEnumeratorActive; private bool HashSetEnumeratorActive;
private HashSet<T> Duplicates; private HashSet<T> Duplicates;
private T? ID; private T? ID;
private uint CollisionMask;
public RetrieveEnumerator GetEnumerator() => this; public RetrieveEnumerator GetEnumerator() => this;
internal RetrieveEnumerator( internal RetrieveEnumerator(
SpatialHash2D<T, U> spatialHash, SpatialHash2D<T, U> spatialHash,
KeysEnumerator keysEnumerator, KeysEnumerator keysEnumerator,
T id, T id
uint collisionMask
) { ) {
SpatialHash = spatialHash; SpatialHash = spatialHash;
KeysEnumerator = keysEnumerator; KeysEnumerator = keysEnumerator;
@ -255,13 +288,11 @@ namespace MoonWorks.Collision.Fixed
HashSetEnumeratorActive = false; HashSetEnumeratorActive = false;
Duplicates = SpatialHash.AcquireHashSet(); Duplicates = SpatialHash.AcquireHashSet();
ID = id; ID = id;
CollisionMask = collisionMask;
} }
internal RetrieveEnumerator( internal RetrieveEnumerator(
SpatialHash2D<T, U> spatialHash, SpatialHash2D<T, U> spatialHash,
KeysEnumerator keysEnumerator, KeysEnumerator keysEnumerator
uint collisionMask
) { ) {
SpatialHash = spatialHash; SpatialHash = spatialHash;
KeysEnumerator = keysEnumerator; KeysEnumerator = keysEnumerator;
@ -269,7 +300,6 @@ namespace MoonWorks.Collision.Fixed
HashSetEnumeratorActive = false; HashSetEnumeratorActive = false;
Duplicates = SpatialHash.AcquireHashSet(); Duplicates = SpatialHash.AcquireHashSet();
ID = null; ID = null;
CollisionMask = collisionMask;
} }
public bool MoveNext() public bool MoveNext()
@ -293,7 +323,6 @@ namespace MoonWorks.Collision.Fixed
// conditions // conditions
var t = HashSetEnumerator.Current; var t = HashSetEnumerator.Current;
var collisionGroups = SpatialHash.IDLookup[t].Item3;
if (Duplicates.Contains(t)) if (Duplicates.Contains(t))
{ {
@ -302,7 +331,7 @@ namespace MoonWorks.Collision.Fixed
if (ID.HasValue) if (ID.HasValue)
{ {
if (ID.Value.Equals(t) || (CollisionMask & collisionGroups) == 0) if (ID.Value.Equals(t))
{ {
return MoveNext(); return MoveNext();
} }
@ -312,13 +341,13 @@ namespace MoonWorks.Collision.Fixed
return true; return true;
} }
public (T, U, Transform2D, uint) Current public (T, U) Current
{ {
get get
{ {
var t = HashSetEnumerator.Current; var t = HashSetEnumerator.Current;
var (u, transform, groups) = SpatialHash.IDLookup[t]; var u = SpatialHash.IDDataLookup[t];
return (t, u, transform, groups); return (t, u);
} }
} }
} }