forked from MoonsideGames/MoonTools.Bonk
multishape system
parent
b28196605e
commit
d23238bcfc
|
@ -1,10 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using MoonTools.Core.Structs;
|
|
||||||
|
|
||||||
namespace MoonTools.Core.Bonk
|
|
||||||
{
|
|
||||||
public interface IMultiShape2D : IHasAABB2D
|
|
||||||
{
|
|
||||||
IEnumerable<(IShape2D, Transform2D)> ShapeTransformPairs { get; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using MoonTools.Core.Structs;
|
||||||
|
|
||||||
|
namespace MoonTools.Core.Bonk
|
||||||
|
{
|
||||||
|
public struct MultiShape : IHasAABB2D
|
||||||
|
{
|
||||||
|
public ImmutableArray<(IShape2D, Transform2D)> ShapeTransformPairs { get; }
|
||||||
|
|
||||||
|
public AABB AABB { get; }
|
||||||
|
|
||||||
|
public MultiShape(ImmutableArray<(IShape2D, Transform2D)> shapeTransformPairs)
|
||||||
|
{
|
||||||
|
ShapeTransformPairs = shapeTransformPairs;
|
||||||
|
|
||||||
|
AABB = AABBFromShapes(shapeTransformPairs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AABB TransformedAABB(Transform2D transform)
|
||||||
|
{
|
||||||
|
return AABB.Transformed(AABB, transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<(IShape2D, Transform2D)> TransformedShapeTransforms(Transform2D transform)
|
||||||
|
{
|
||||||
|
foreach (var (shape, shapeTransform) in ShapeTransformPairs)
|
||||||
|
{
|
||||||
|
yield return (shape, transform.Compose(shapeTransform));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AABB AABBFromShapes(IEnumerable<(IShape2D, Transform2D)> shapeTransforms)
|
||||||
|
{
|
||||||
|
var minX = float.MaxValue;
|
||||||
|
var minY = float.MaxValue;
|
||||||
|
var maxX = float.MinValue;
|
||||||
|
var maxY = float.MinValue;
|
||||||
|
|
||||||
|
foreach (var (shape, transform) in shapeTransforms)
|
||||||
|
{
|
||||||
|
var aabb = shape.TransformedAABB(transform);
|
||||||
|
|
||||||
|
if (aabb.Min.X < minX)
|
||||||
|
{
|
||||||
|
minX = aabb.Min.X;
|
||||||
|
}
|
||||||
|
if (aabb.Min.Y < minY)
|
||||||
|
{
|
||||||
|
minY = aabb.Min.Y;
|
||||||
|
}
|
||||||
|
if (aabb.Max.X > maxX)
|
||||||
|
{
|
||||||
|
maxX = aabb.Max.X;
|
||||||
|
}
|
||||||
|
if (aabb.Max.Y > maxY)
|
||||||
|
{
|
||||||
|
maxY = aabb.Max.Y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AABB(minX, minY, maxX, maxY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,65 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using MoonTools.Core.Structs;
|
|
||||||
|
|
||||||
namespace MoonTools.Core.Bonk
|
|
||||||
{
|
|
||||||
public struct MultiRectangle : IMultiShape2D
|
|
||||||
{
|
|
||||||
private ImmutableArray<(Rectangle, Transform2D)> Rectangles { get; }
|
|
||||||
|
|
||||||
public IEnumerable<(IShape2D, Transform2D)> ShapeTransformPairs
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
foreach (var rectangle in Rectangles) { yield return rectangle; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AABB AABB { get; }
|
|
||||||
|
|
||||||
public MultiRectangle(ImmutableArray<(Rectangle, Transform2D)> rectangles)
|
|
||||||
{
|
|
||||||
Rectangles = rectangles;
|
|
||||||
|
|
||||||
AABB = AABBFromRectangles(rectangles);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AABB TransformedAABB(Transform2D transform)
|
|
||||||
{
|
|
||||||
return AABB.Transformed(AABB, transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AABB AABBFromRectangles(IEnumerable<(Rectangle, Transform2D)> rectangles)
|
|
||||||
{
|
|
||||||
var minX = float.MaxValue;
|
|
||||||
var minY = float.MaxValue;
|
|
||||||
var maxX = float.MinValue;
|
|
||||||
var maxY = float.MinValue;
|
|
||||||
|
|
||||||
foreach (var (rectangle, transform) in rectangles)
|
|
||||||
{
|
|
||||||
var transformedAABB = rectangle.TransformedAABB(transform);
|
|
||||||
|
|
||||||
if (transformedAABB.Min.X < minX)
|
|
||||||
{
|
|
||||||
minX = transformedAABB.Min.X;
|
|
||||||
}
|
|
||||||
if (transformedAABB.Min.Y < minY)
|
|
||||||
{
|
|
||||||
minY = transformedAABB.Min.Y;
|
|
||||||
}
|
|
||||||
if (transformedAABB.Max.X > maxX)
|
|
||||||
{
|
|
||||||
maxX = transformedAABB.Max.X;
|
|
||||||
}
|
|
||||||
if (transformedAABB.Max.Y > maxY)
|
|
||||||
{
|
|
||||||
maxY = transformedAABB.Max.Y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AABB(minX, minY, maxX, maxY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,6 +14,30 @@ namespace MoonTools.Core.Bonk
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests if two shape-transform pairs are overlapping. Automatically detects fast-path optimizations.
|
/// Tests if two shape-transform pairs are overlapping. Automatically detects fast-path optimizations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
public static bool TestCollision(IHasAABB2D hasBoundingBoxA, Transform2D transformA, IHasAABB2D hasBoundingBoxB, Transform2D transformB)
|
||||||
|
{
|
||||||
|
if (hasBoundingBoxA is MultiShape && hasBoundingBoxB is MultiShape)
|
||||||
|
{
|
||||||
|
return TestCollision((MultiShape)hasBoundingBoxA, transformA, (MultiShape)hasBoundingBoxB, transformB);
|
||||||
|
}
|
||||||
|
else if (hasBoundingBoxA is MultiShape && hasBoundingBoxB is IShape2D)
|
||||||
|
{
|
||||||
|
return TestCollision((MultiShape)hasBoundingBoxA, transformA, (IShape2D)hasBoundingBoxB, transformB);
|
||||||
|
}
|
||||||
|
else if (hasBoundingBoxA is IShape2D && hasBoundingBoxB is MultiShape)
|
||||||
|
{
|
||||||
|
return TestCollision((IShape2D)hasBoundingBoxA, transformA, (MultiShape)hasBoundingBoxB, transformB);
|
||||||
|
}
|
||||||
|
else if (hasBoundingBoxA is IShape2D && hasBoundingBoxB is IShape2D)
|
||||||
|
{
|
||||||
|
return TestCollision((IShape2D)hasBoundingBoxA, transformA, (IShape2D)hasBoundingBoxB, transformB);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.ArgumentException("Collision testing requires MultiShapes or IShape2Ds.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static bool TestCollision(IShape2D shapeA, Transform2D transformA, IShape2D shapeB, Transform2D transformB)
|
public static bool TestCollision(IShape2D shapeA, Transform2D transformA, IShape2D shapeB, Transform2D transformB)
|
||||||
{
|
{
|
||||||
if (shapeA is Rectangle rectangleA && shapeB is Rectangle rectangleB && transformA.Rotation == 0 && transformB.Rotation == 0)
|
if (shapeA is Rectangle rectangleA && shapeB is Rectangle rectangleB && transformA.Rotation == 0 && transformB.Rotation == 0)
|
||||||
|
@ -44,7 +68,7 @@ namespace MoonTools.Core.Bonk
|
||||||
/// <param name="shape"></param>
|
/// <param name="shape"></param>
|
||||||
/// <param name="shapeTransform"></param>
|
/// <param name="shapeTransform"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static bool TestCollision(IMultiShape2D multiShape, Transform2D multiShapeTransform, IShape2D shape, Transform2D shapeTransform)
|
public static bool TestCollision(MultiShape multiShape, Transform2D multiShapeTransform, IShape2D shape, Transform2D shapeTransform)
|
||||||
{
|
{
|
||||||
foreach (var (otherShape, otherTransform) in multiShape.ShapeTransformPairs)
|
foreach (var (otherShape, otherTransform) in multiShape.ShapeTransformPairs)
|
||||||
{
|
{
|
||||||
|
@ -62,7 +86,7 @@ namespace MoonTools.Core.Bonk
|
||||||
/// <param name="shape"></param>
|
/// <param name="shape"></param>
|
||||||
/// <param name="shapeTransform"></param>
|
/// <param name="shapeTransform"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static bool TestCollision(IShape2D shape, Transform2D shapeTransform, IMultiShape2D multiShape, Transform2D multiShapeTransform)
|
public static bool TestCollision(IShape2D shape, Transform2D shapeTransform, MultiShape multiShape, Transform2D multiShapeTransform)
|
||||||
{
|
{
|
||||||
foreach (var (otherShape, otherTransform) in multiShape.ShapeTransformPairs)
|
foreach (var (otherShape, otherTransform) in multiShape.ShapeTransformPairs)
|
||||||
{
|
{
|
||||||
|
@ -80,7 +104,7 @@ namespace MoonTools.Core.Bonk
|
||||||
/// <param name="multiShapeB"></param>
|
/// <param name="multiShapeB"></param>
|
||||||
/// <param name="transformB"></param>
|
/// <param name="transformB"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static bool TestCollision(IMultiShape2D multiShapeA, Transform2D transformA, IMultiShape2D multiShapeB, Transform2D transformB)
|
public static bool TestCollision(MultiShape multiShapeA, Transform2D transformA, MultiShape multiShapeB, Transform2D transformB)
|
||||||
{
|
{
|
||||||
foreach (var (shapeA, shapeTransformA) in multiShapeA.ShapeTransformPairs)
|
foreach (var (shapeA, shapeTransformA) in multiShapeA.ShapeTransformPairs)
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,7 +5,7 @@ using MoonTools.Core.Structs;
|
||||||
namespace MoonTools.Core.Bonk
|
namespace MoonTools.Core.Bonk
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A rectangle is a shape defined by a minimum and maximum X value and a minimum and maximum Y value.
|
/// A rectangle is a shape defined by a width and height. The origin is the center of the rectangle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public struct Rectangle : IShape2D, IEquatable<Rectangle>
|
public struct Rectangle : IShape2D, IEquatable<Rectangle>
|
||||||
{
|
{
|
||||||
|
|
|
@ -366,11 +366,11 @@ namespace Tests
|
||||||
[Test]
|
[Test]
|
||||||
public void RotatedRectanglesOverlapping()
|
public void RotatedRectanglesOverlapping()
|
||||||
{
|
{
|
||||||
var rectangleA = new Rectangle(3, 3);
|
var rectangleA = new Rectangle(3, 6);
|
||||||
var transformA = new Transform2D(new Vector2(-1, 0), -90f);
|
var transformA = new Transform2D(new Vector2(4f, 0), (float)System.Math.PI / 2);
|
||||||
|
|
||||||
var rectangleB = new Rectangle(2, 2);
|
var rectangleB = new Rectangle(2, 2);
|
||||||
var transformB = new Transform2D(new Vector2(1, 0));
|
var transformB = new Transform2D(new Vector2(0, 0));
|
||||||
|
|
||||||
NarrowPhase.TestCollision(rectangleA, transformA, rectangleB, transformB).Should().BeTrue();
|
NarrowPhase.TestCollision(rectangleA, transformA, rectangleB, transformB).Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
@ -426,8 +426,8 @@ namespace Tests
|
||||||
[Test]
|
[Test]
|
||||||
public void MultiRectanglesOverlapping()
|
public void MultiRectanglesOverlapping()
|
||||||
{
|
{
|
||||||
var multiRectangleA = new MultiRectangle(
|
var multiRectangleA = new MultiShape(
|
||||||
ImmutableArray.Create(
|
ImmutableArray.Create<(IShape2D, Transform2D)>(
|
||||||
(new Rectangle(4, 1), new Transform2D(new Position2D(-5, 0))),
|
(new Rectangle(4, 1), new Transform2D(new Position2D(-5, 0))),
|
||||||
(new Rectangle(4, 1), new Transform2D(new Position2D(-5, 1))),
|
(new Rectangle(4, 1), new Transform2D(new Position2D(-5, 1))),
|
||||||
(new Rectangle(4, 1), new Transform2D(new Position2D(-5, 2)))
|
(new Rectangle(4, 1), new Transform2D(new Position2D(-5, 2)))
|
||||||
|
@ -435,8 +435,8 @@ namespace Tests
|
||||||
);
|
);
|
||||||
var transformA = new Transform2D(new Position2D(5, 0));
|
var transformA = new Transform2D(new Position2D(5, 0));
|
||||||
|
|
||||||
var multiRectangleB = new MultiRectangle(
|
var multiRectangleB = new MultiShape(
|
||||||
ImmutableArray.Create(
|
ImmutableArray.Create<(IShape2D, Transform2D)>(
|
||||||
(new Rectangle(4, 1), new Transform2D(new Position2D(4, -1))),
|
(new Rectangle(4, 1), new Transform2D(new Position2D(4, -1))),
|
||||||
(new Rectangle(4, 1), new Transform2D(new Position2D(4, 0))),
|
(new Rectangle(4, 1), new Transform2D(new Position2D(4, 0))),
|
||||||
(new Rectangle(4, 1), new Transform2D(new Position2D(4, 1)))
|
(new Rectangle(4, 1), new Transform2D(new Position2D(4, 1)))
|
||||||
|
@ -450,8 +450,8 @@ namespace Tests
|
||||||
[Test]
|
[Test]
|
||||||
public void MultiRectanglesNotOverlapping()
|
public void MultiRectanglesNotOverlapping()
|
||||||
{
|
{
|
||||||
var multiRectangleA = new MultiRectangle(
|
var multiRectangleA = new MultiShape(
|
||||||
ImmutableArray.Create(
|
ImmutableArray.Create<(IShape2D, Transform2D)>(
|
||||||
(new Rectangle(4, 1), new Transform2D(new Position2D(-5, 0))),
|
(new Rectangle(4, 1), new Transform2D(new Position2D(-5, 0))),
|
||||||
(new Rectangle(4, 1), new Transform2D(new Position2D(-5, 1))),
|
(new Rectangle(4, 1), new Transform2D(new Position2D(-5, 1))),
|
||||||
(new Rectangle(4, 1), new Transform2D(new Position2D(-5, 2)))
|
(new Rectangle(4, 1), new Transform2D(new Position2D(-5, 2)))
|
||||||
|
@ -459,8 +459,8 @@ namespace Tests
|
||||||
);
|
);
|
||||||
var transformA = new Transform2D(new Position2D(5, 0));
|
var transformA = new Transform2D(new Position2D(5, 0));
|
||||||
|
|
||||||
var multiRectangleB = new MultiRectangle(
|
var multiRectangleB = new MultiShape(
|
||||||
ImmutableArray.Create(
|
ImmutableArray.Create<(IShape2D, Transform2D)>(
|
||||||
(new Rectangle(4, 1), new Transform2D(new Position2D(4, -1))),
|
(new Rectangle(4, 1), new Transform2D(new Position2D(4, -1))),
|
||||||
(new Rectangle(4, 1), new Transform2D(new Position2D(4, 0))),
|
(new Rectangle(4, 1), new Transform2D(new Position2D(4, 0))),
|
||||||
(new Rectangle(4, 1), new Transform2D(new Position2D(4, 1)))
|
(new Rectangle(4, 1), new Transform2D(new Position2D(4, 1)))
|
||||||
|
|
|
@ -38,8 +38,8 @@ namespace Tests
|
||||||
var point = new Point();
|
var point = new Point();
|
||||||
var pointTransform = new Transform2D(new Position2D(8, 8));
|
var pointTransform = new Transform2D(new Position2D(8, 8));
|
||||||
|
|
||||||
var multiRectangle = new MultiRectangle(
|
var multiRectangle = new MultiShape(
|
||||||
ImmutableArray.Create(
|
ImmutableArray.Create<(IShape2D, Transform2D)>(
|
||||||
(new Rectangle(4, 1), new Transform2D(new Position2D(-2, -2))),
|
(new Rectangle(4, 1), new Transform2D(new Position2D(-2, -2))),
|
||||||
(new Rectangle(4, 1), new Transform2D(new Position2D(-2, -1))),
|
(new Rectangle(4, 1), new Transform2D(new Position2D(-2, -1))),
|
||||||
(new Rectangle(4, 1), new Transform2D(new Position2D(-2, 0)))
|
(new Rectangle(4, 1), new Transform2D(new Position2D(-2, 0)))
|
||||||
|
@ -70,6 +70,7 @@ namespace Tests
|
||||||
spatialHash.Retrieve(6, line, lineTransform).Should().Contain((4, circleA, circleATransform)).And.Contain((2, rectC, rectCTransform));
|
spatialHash.Retrieve(6, line, lineTransform).Should().Contain((4, circleA, circleATransform)).And.Contain((2, rectC, rectCTransform));
|
||||||
|
|
||||||
spatialHash.Retrieve(8, multiRectangle, multiRectangleTransform).Should().Contain((1, rectB, rectBTransform));
|
spatialHash.Retrieve(8, multiRectangle, multiRectangleTransform).Should().Contain((1, rectB, rectBTransform));
|
||||||
|
spatialHash.Retrieve(8, multiRectangle, multiRectangleTransform).Should().NotContain((0, rectA, rectATransform));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
Loading…
Reference in New Issue