diff --git a/src/cs/MonoGame.Extended.Collisions/SpatialHash.cs b/src/cs/MonoGame.Extended.Collisions/SpatialHash.cs new file mode 100644 index 00000000..88dfa550 --- /dev/null +++ b/src/cs/MonoGame.Extended.Collisions/SpatialHash.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace MonoGame.Extended.Collisions; + +public class SpatialHash: ISpaceAlgorithm +{ + private readonly Dictionary> _dictionary = new(); + private readonly List _actors = new(); + + private readonly Size2 _size; + + public SpatialHash(Size2 size) + { + _size = size; + } + + public void Insert(ICollisionActor actor) + { + InsertToHash(actor); + _actors.Add(actor); + } + + private void InsertToHash(ICollisionActor actor) + { + var rect = actor.Bounds.BoundingRectangle; + for (var x = rect.Left; x < rect.Right; x+=_size.Width) + for (var y = rect.Top; y < rect.Bottom; y+=_size.Height) + AddToCell(x, y, actor); + } + + private void AddToCell(float x, float y, ICollisionActor actor) + { + var index = GetIndex(x, y); + if (_dictionary.TryGetValue(index, out var actors)) + actors.Add(actor); + else + _dictionary[index] = new() { actor }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetIndex(float x, float y) + { + return (int)(x / _size.Width) << 16 + (int)(y / _size.Height); + } + + public bool Remove(ICollisionActor actor) + { + foreach (var actors in _dictionary.Values) + actors.Remove(actor); + return _actors.Remove(actor); + } + + public IEnumerable Query(RectangleF boundsBoundingRectangle) + { + var results = new HashSet(); + + for (var x = boundsBoundingRectangle.Left; x < boundsBoundingRectangle.Right; x+=_size.Width) + for (var y = boundsBoundingRectangle.Top; y < boundsBoundingRectangle.Bottom; y+=_size.Height) + if (_dictionary.TryGetValue(GetIndex(x, y), out var actors)) + foreach (var actor in actors) + results.Add(actor); + return results; + } + + public List.Enumerator GetEnumerator() => _actors.GetEnumerator(); + + public void Reset() + { + _dictionary.Clear(); + foreach (var actor in _actors) + InsertToHash(actor); + } +} diff --git a/src/cs/Tests/MonoGame.Extended.Collisions.Tests/SpatialHashTests.cs b/src/cs/Tests/MonoGame.Extended.Collisions.Tests/SpatialHashTests.cs new file mode 100644 index 00000000..f076bf46 --- /dev/null +++ b/src/cs/Tests/MonoGame.Extended.Collisions.Tests/SpatialHashTests.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace MonoGame.Extended.Collisions.Tests; + +public class SpatialHashTests +{ + private SpatialHash generateSpatialHash() => new SpatialHash(new Size2(64, 64)); + private readonly RectangleF RECT = new RectangleF(10, 10, 20, 20); + + [Fact] + public void CollisionOneTrueTest() + { + var hash = generateSpatialHash(); + hash.Insert(new BasicActor()); + var collisions = hash.Query(RECT); + Assert.Equal(1, collisions.Count()); + } + + [Fact] + public void CollisionTwoTest() + { + var hash = generateSpatialHash(); + hash.Insert(new BasicActor()); + hash.Insert(new BasicActor()); + var collisions = hash.Query(RECT); + Assert.Equal(2, collisions.Count()); + } +}