multishape system

generics
Evan Hemsley 2020-01-04 16:13:07 -08:00
parent b28196605e
commit d23238bcfc
7 changed files with 107 additions and 92 deletions

View File

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

65
Bonk/MultiShape.cs Normal file
View File

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

View File

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

View File

@ -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)
{ {

View File

@ -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>
{ {

View File

@ -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)))

View File

@ -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]