using System.Collections.Generic; using MoonWorks.Math.Fixed; namespace MoonWorks.Collision.Fixed { /// /// Used to quickly check if two shapes are potentially overlapping. /// /// The type that will be used to uniquely identify shape-transform pairs. public class SpatialHash2D where T : struct, System.IEquatable { private readonly Fix64 cellSize; private readonly Dictionary> hashDictionary = new Dictionary>(); private readonly Dictionary IDBoxLookup = new Dictionary(); private readonly Dictionary IDDataLookup = new Dictionary(); private readonly HashSet DynamicIDs = new HashSet(); private int MinX; private int MaxX; private int MinY; private int MaxY; private Queue> hashSetPool = new Queue>(); public SpatialHash2D(int cellSize) { this.cellSize = new Fix64(cellSize); } private (int, int) Hash(Vector2 position) { return ((int) (position.X / cellSize), (int) (position.Y / cellSize)); } /// /// Inserts an element into the SpatialHash. /// /// A unique ID for the shape-transform pair. public void Insert(T id, AABB2D aabb, Transform2D transform2D, U data, bool dynamic = true) { Remove(id); var box = AABB2D.Transformed(aabb, transform2D); var minHash = Hash(box.Min); var maxHash = Hash(box.Max); foreach (var key in Keys(minHash.Item1, minHash.Item2, maxHash.Item1, maxHash.Item2)) { if (!hashDictionary.ContainsKey(key)) { hashDictionary.Add(key, new HashSet()); } hashDictionary[key].Add(id); IDDataLookup[id] = data; } MinX = System.Math.Min(MinX, minHash.Item1); MinY = System.Math.Min(MinY, minHash.Item2); MaxX = System.Math.Max(MaxX, maxHash.Item1); MaxY = System.Math.Max(MaxY, maxHash.Item2); if (dynamic) { DynamicIDs.Add(id); } IDBoxLookup[id] = box; } /// /// Retrieves all the potential collisions of a shape-transform pair. Excludes any shape-transforms with the given ID. /// public RetrieveEnumerator Retrieve(T id, V hasAABB, Transform2D transform2D) where V : IHasAABB2D { var box = AABB2D.Transformed(hasAABB.AABB, transform2D); var (minX, minY) = Hash(box.Min); var (maxX, maxY) = Hash(box.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), id ); } /// /// Retrieves all the potential collisions of a shape-transform pair. /// public RetrieveEnumerator Retrieve(V hasAABB, Transform2D transform2D) where V : IHasAABB2D { var box = AABB2D.Transformed(hasAABB.AABB, transform2D); return Retrieve(box); } /// /// Retrieves objects based on a pre-transformed AABB. /// /// A transformed AABB. /// public RetrieveEnumerator Retrieve(T id, AABB2D aabb) { var (minX, minY) = Hash(aabb.Min); 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), id ); } /// /// Retrieves objects based on a pre-transformed AABB. /// /// A transformed AABB. /// public RetrieveEnumerator Retrieve(AABB2D aabb) { var (minX, minY) = Hash(aabb.Min); 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) ); } /// /// Removes a specific ID from the SpatialHash. /// public void Remove(T id) { if (IDBoxLookup.TryGetValue(id, out var aabb)) { var minHash = Hash(aabb.Min); var maxHash = Hash(aabb.Max); foreach (var key in Keys(minHash.Item1, minHash.Item2, maxHash.Item1, maxHash.Item2)) { if (hashDictionary.TryGetValue(key, out HashSet value)) { value.Remove(id); } } IDDataLookup.Remove(id); IDBoxLookup.Remove(id); } DynamicIDs.Remove(id); } /// /// Removes everything that has been inserted into the SpatialHash. /// public void Clear() { foreach (var hash in hashDictionary.Values) { hash.Clear(); } IDDataLookup.Clear(); IDBoxLookup.Clear(); } /// /// Removes /// public void ClearDynamic() { foreach (var id in DynamicIDs) { Remove(id); } } private static long MakeLong(int left, int right) { return ((long) left << 32) | ((uint) right); } internal static KeysEnumerator Keys(int minX, int minY, int maxX, int maxY) { return new KeysEnumerator(minX, minY, maxX, maxY); } internal HashSet AcquireHashSet() { if (hashSetPool.Count == 0) { hashSetPool.Enqueue(new HashSet()); } var hashSet = hashSetPool.Dequeue(); hashSet.Clear(); return hashSet; } internal void FreeHashSet(HashSet hashSet) { hashSetPool.Enqueue(hashSet); } internal ref struct KeysEnumerator { private int MinX; private int MinY; private int MaxX; private int MaxY; private int i, j; public KeysEnumerator GetEnumerator() => this; public KeysEnumerator(int minX, int minY, int maxX, int maxY) { MinX = minX; MinY = minY; MaxX = maxX; MaxY = maxY; i = minX; j = minY - 1; } public bool MoveNext() { if (j < MaxY) { j += 1; return true; } else if (i < MaxX) { i += 1; j = MinY; return true; } return false; } public long Current { get { return MakeLong(i, j); } } } public ref struct RetrieveEnumerator { public SpatialHash2D SpatialHash; private KeysEnumerator KeysEnumerator; private HashSet.Enumerator HashSetEnumerator; private bool HashSetEnumeratorActive; private HashSet Duplicates; private T? ID; public RetrieveEnumerator GetEnumerator() => this; internal RetrieveEnumerator( SpatialHash2D spatialHash, KeysEnumerator keysEnumerator, T id ) { SpatialHash = spatialHash; KeysEnumerator = keysEnumerator; HashSetEnumerator = default; HashSetEnumeratorActive = false; Duplicates = SpatialHash.AcquireHashSet(); ID = id; } internal RetrieveEnumerator( SpatialHash2D spatialHash, KeysEnumerator keysEnumerator ) { SpatialHash = spatialHash; KeysEnumerator = keysEnumerator; HashSetEnumerator = default; HashSetEnumeratorActive = false; Duplicates = SpatialHash.AcquireHashSet(); ID = null; } public bool MoveNext() { if (!HashSetEnumeratorActive || !HashSetEnumerator.MoveNext()) { if (!KeysEnumerator.MoveNext()) { SpatialHash.FreeHashSet(Duplicates); return false; } if (SpatialHash.hashDictionary.TryGetValue(KeysEnumerator.Current, out var hashset)) { HashSetEnumerator = hashset.GetEnumerator(); HashSetEnumeratorActive = true; } return MoveNext(); } // conditions var t = HashSetEnumerator.Current; if (Duplicates.Contains(t)) { return MoveNext(); } if (ID.HasValue) { if (ID.Value.Equals(t)) { return MoveNext(); } } Duplicates.Add(t); return true; } public (T, U) Current { get { var t = HashSetEnumerator.Current; var u = SpatialHash.IDDataLookup[t]; return (t, u); } } } } }