initial commit
						commit
						3d1aa25a1e
					
				|  | @ -0,0 +1,2 @@ | ||||||
|  | bin/ | ||||||
|  | obj/ | ||||||
|  | @ -0,0 +1,40 @@ | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using Microsoft.Xna.Framework; | ||||||
|  | using MoonTools.Core.Structs; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.Core.Bonk | ||||||
|  | { | ||||||
|  |     public struct AABB | ||||||
|  |     { | ||||||
|  |         public int MinX { get; private set; } | ||||||
|  |         public int MinY { get; private set; } | ||||||
|  |         public int MaxX { get; private set; } | ||||||
|  |         public int MaxY { get; private set; } | ||||||
|  | 
 | ||||||
|  |         public int Width { get { return MaxX - MinX; } } | ||||||
|  |         public int Height { get { return MaxY - MinY; } } | ||||||
|  | 
 | ||||||
|  |         public static AABB FromTransform2DedVertices(IEnumerable<Position2D> vertices, Transform2D transform) | ||||||
|  |         { | ||||||
|  |             var Transform2DedVertices = vertices.Select(vertex => Vector2.Transform(vertex, transform.TransformMatrix)); | ||||||
|  | 
 | ||||||
|  |             return new AABB | ||||||
|  |             { | ||||||
|  |                 MinX = (int)Math.Round(Transform2DedVertices.Min(vertex => vertex.X)), | ||||||
|  |                 MinY = (int)Math.Round(Transform2DedVertices.Min(vertex => vertex.Y)), | ||||||
|  |                 MaxX = (int)Math.Round(Transform2DedVertices.Max(vertex => vertex.X)), | ||||||
|  |                 MaxY = (int)Math.Round(Transform2DedVertices.Max(vertex => vertex.Y)) | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public AABB(int minX, int minY, int maxX, int maxY) | ||||||
|  |         { | ||||||
|  |             MinX = minX; | ||||||
|  |             MinY = minY; | ||||||
|  |             MaxX = maxX; | ||||||
|  |             MaxY = maxY; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <Version>1.0.0</Version> | ||||||
|  |     <TargetFramework>netstandard2.0</TargetFramework> | ||||||
|  |     <Description>.NET Core Collision Detection for MonoGame</Description> | ||||||
|  |     <PackageId>MoonTools.Core.Bonk</PackageId> | ||||||
|  |     <RootNamespace>MoonTools.Core.Bonk</RootNamespace> | ||||||
|  |     <Company>Moonside Games</Company> | ||||||
|  |     <Authors>Evan Hemsley</Authors> | ||||||
|  |     <Copyright>Evan Hemsley 2019</Copyright> | ||||||
|  |     <Product>MoonTools.Core.Bonk</Product> | ||||||
|  |     <GeneratePackageOnBuild>true</GeneratePackageOnBuild> | ||||||
|  |     <AssemblyName>MoonTools.Core.Bonk</AssemblyName> | ||||||
|  |     <PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression> | ||||||
|  |     <PackageProjectUrl>https://github.com/MoonsideGames/MoonTools.Core.Bonk</PackageProjectUrl> | ||||||
|  |   </PropertyGroup> | ||||||
|  |   <ItemGroup> | ||||||
|  |     <PackageReference Include="MoonTools.Core.Structs" Version="1.0.1"/> | ||||||
|  |   </ItemGroup> | ||||||
|  | </Project> | ||||||
|  | @ -0,0 +1,41 @@ | ||||||
|  | using System; | ||||||
|  | using Microsoft.Xna.Framework; | ||||||
|  | using MoonTools.Core.Structs; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.Core.Bonk | ||||||
|  | { | ||||||
|  |     public struct Circle : IShape2D, IEquatable<IShape2D> | ||||||
|  |     { | ||||||
|  |         public int Radius { get; private set; } | ||||||
|  | 
 | ||||||
|  |         public Circle(int radius) | ||||||
|  |         { | ||||||
|  |             Radius = radius; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public Vector2 Support(Vector2 direction, Transform2D transform) | ||||||
|  |         { | ||||||
|  |             return Vector2.Transform(Vector2.Normalize(direction) * Radius, transform.TransformMatrix); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public AABB AABB(Transform2D Transform2D) | ||||||
|  |         { | ||||||
|  |             return new AABB( | ||||||
|  |                 Transform2D.Position.X - Radius, | ||||||
|  |                 Transform2D.Position.Y - Radius, | ||||||
|  |                 Transform2D.Position.X + Radius, | ||||||
|  |                 Transform2D.Position.Y + Radius | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public bool Equals(IShape2D other) | ||||||
|  |         { | ||||||
|  |             if (other is Circle circle) | ||||||
|  |             { | ||||||
|  |                 return Radius == circle.Radius; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,102 @@ | ||||||
|  | /* | ||||||
|  |  * Implementation of the Expanding Polytope Algorithm | ||||||
|  |  * as based on the following blog post: | ||||||
|  |  * https://blog.hamaluik.ca/posts/building-a-collision-engine-part-2-2d-penetration-vectors/ | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | using Microsoft.Xna.Framework; | ||||||
|  | using MoonTools.Core.Structs; | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.Core.Bonk | ||||||
|  | { | ||||||
|  |     enum PolygonWinding | ||||||
|  |     { | ||||||
|  |         Clockwise, | ||||||
|  |         CounterClockwise | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static class EPA2D | ||||||
|  |     { | ||||||
|  |         // vector returned gives direction from A to B | ||||||
|  |         public static Vector2 Intersect(IShape2D shapeA, Transform2D Transform2DA, IShape2D shapeB, Transform2D Transform2DB, IEnumerable<Vector2> givenSimplexVertices) | ||||||
|  |         { | ||||||
|  |             var simplexVertices = new SimplexVertices(new Vector2?[36]); | ||||||
|  | 
 | ||||||
|  |             foreach (var vertex in givenSimplexVertices) | ||||||
|  |             { | ||||||
|  |                 simplexVertices.Add(vertex); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var e0 = (simplexVertices[1].X - simplexVertices[0].X) * (simplexVertices[1].Y + simplexVertices[0].Y); | ||||||
|  |             var e1 = (simplexVertices[2].X - simplexVertices[1].X) * (simplexVertices[2].Y + simplexVertices[1].Y); | ||||||
|  |             var e2 = (simplexVertices[0].X - simplexVertices[2].X) * (simplexVertices[0].Y + simplexVertices[2].Y); | ||||||
|  |             var winding = e0 + e1 + e2 >= 0 ? PolygonWinding.Clockwise : PolygonWinding.CounterClockwise; | ||||||
|  | 
 | ||||||
|  |             Vector2 intersection = default; | ||||||
|  | 
 | ||||||
|  |             for (int i = 0; i < 32; i++) | ||||||
|  |             { | ||||||
|  |                 var edge = FindClosestEdge(winding, simplexVertices); | ||||||
|  |                 var support = CalculateSupport(shapeA, Transform2DA, shapeB, Transform2DB, edge.normal); | ||||||
|  |                 var distance = Vector2.Dot(support, edge.normal); | ||||||
|  | 
 | ||||||
|  |                 intersection = edge.normal; | ||||||
|  |                 intersection *= distance; | ||||||
|  | 
 | ||||||
|  |                 if (Math.Abs(distance - edge.distance) <= float.Epsilon) | ||||||
|  |                 { | ||||||
|  |                     return intersection; | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     simplexVertices.Insert(edge.index, support); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return intersection; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static Edge FindClosestEdge(PolygonWinding winding, SimplexVertices simplexVertices) | ||||||
|  |         { | ||||||
|  |             var closestDistance = float.PositiveInfinity; | ||||||
|  |             var closestNormal = Vector2.Zero; | ||||||
|  |             var closestIndex = 0; | ||||||
|  | 
 | ||||||
|  |             for (int i = 0; i < simplexVertices.Count; i++) | ||||||
|  |             { | ||||||
|  |                 var j = i + 1; | ||||||
|  |                 if (j >= simplexVertices.Count) { j = 0; } | ||||||
|  |                 Vector2 edge = simplexVertices[j] - simplexVertices[i]; | ||||||
|  | 
 | ||||||
|  |                 Vector2 norm; | ||||||
|  |                 if (winding == PolygonWinding.Clockwise) | ||||||
|  |                 { | ||||||
|  |                     norm = new Vector2(edge.Y, -edge.X); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     norm = new Vector2(-edge.Y, edge.X); | ||||||
|  |                 } | ||||||
|  |                 norm.Normalize(); | ||||||
|  | 
 | ||||||
|  |                 var dist = Vector2.Dot(norm, simplexVertices[i]); | ||||||
|  | 
 | ||||||
|  |                 if (dist < closestDistance) | ||||||
|  |                 { | ||||||
|  |                     closestDistance = dist; | ||||||
|  |                     closestNormal = norm; | ||||||
|  |                     closestIndex = j; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return new Edge(closestDistance, closestNormal, closestIndex); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static Vector2 CalculateSupport(IShape2D shapeA, Transform2D Transform2DA, IShape2D shapeB, Transform2D Transform2DB, Vector2 direction) | ||||||
|  |         { | ||||||
|  |             return shapeA.Support(direction, Transform2DA) - shapeB.Support(-direction, Transform2DB); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,17 @@ | ||||||
|  | using Microsoft.Xna.Framework; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.Core.Bonk | ||||||
|  | { | ||||||
|  |     public struct Edge | ||||||
|  |     { | ||||||
|  |         public float distance; | ||||||
|  |         public Vector2 normal; | ||||||
|  |         public int index; | ||||||
|  | 
 | ||||||
|  |         public Edge(float distance, Vector2 normal, int index) { | ||||||
|  |             this.distance = distance; | ||||||
|  |             this.normal = normal; | ||||||
|  |             this.index = index; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,126 @@ | ||||||
|  | /* | ||||||
|  |  * Implementation of the GJK collision algorithm | ||||||
|  |  * Based on some math blogs | ||||||
|  |  * https://blog.hamaluik.ca/posts/building-a-collision-engine-part-1-2d-gjk-collision-detection/ | ||||||
|  |  * and some code from https://github.com/kroitor/gjk.c | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | using Microsoft.Xna.Framework; | ||||||
|  | using MoonTools.Core.Structs; | ||||||
|  | using System; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.Core.Bonk | ||||||
|  | { | ||||||
|  |     public static class GJK2D | ||||||
|  |     { | ||||||
|  |         private enum SolutionStatus | ||||||
|  |         { | ||||||
|  |             NoIntersection, | ||||||
|  |             Intersection, | ||||||
|  |             StillSolving | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public static ValueTuple<bool, SimplexVertices> TestCollision(IShape2D shapeA, Transform2D Transform2DA, IShape2D shapeB, Transform2D Transform2DB) | ||||||
|  |         { | ||||||
|  |             var vertices = new SimplexVertices(new Vector2?[] { null, null, null, null }); | ||||||
|  | 
 | ||||||
|  |             const SolutionStatus solutionStatus = SolutionStatus.StillSolving; | ||||||
|  |             var direction = Transform2DB.Position - Transform2DA.Position; | ||||||
|  | 
 | ||||||
|  |             var result = (solutionStatus, direction); | ||||||
|  | 
 | ||||||
|  |             while (result.solutionStatus == SolutionStatus.StillSolving) | ||||||
|  |             { | ||||||
|  |                 result = EvolveSimplex(shapeA, Transform2DA, shapeB, Transform2DB, vertices, result.direction); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return ValueTuple.Create(result.solutionStatus == SolutionStatus.Intersection, vertices); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static (SolutionStatus, Vector2) EvolveSimplex(IShape2D shapeA, Transform2D Transform2DA, IShape2D shapeB, Transform2D Transform2DB, SimplexVertices vertices, Vector2 direction) | ||||||
|  |         { | ||||||
|  |             switch(vertices.Count) | ||||||
|  |             { | ||||||
|  |                 case 0: | ||||||
|  |                     if (direction == Vector2.Zero) | ||||||
|  |                     { | ||||||
|  |                         direction = Vector2.UnitX; | ||||||
|  |                     } | ||||||
|  |                     break; | ||||||
|  | 
 | ||||||
|  |                 case 1: | ||||||
|  |                     direction *= -1; | ||||||
|  |                     break; | ||||||
|  | 
 | ||||||
|  |                 case 2: | ||||||
|  |                     var ab = vertices[1] - vertices[0]; | ||||||
|  |                     var a0 = vertices[0] * -1; | ||||||
|  | 
 | ||||||
|  |                     direction = TripleProduct(ab, a0, ab); | ||||||
|  |                     if (direction == Vector2.Zero) | ||||||
|  |                     { | ||||||
|  |                         direction = Perpendicular(ab); | ||||||
|  |                     } | ||||||
|  |                     break; | ||||||
|  | 
 | ||||||
|  |                 case 3: | ||||||
|  |                     var c0 = vertices[2] * -1; | ||||||
|  |                     var bc = vertices[1] - vertices[2]; | ||||||
|  |                     var ca = vertices[0] - vertices[2]; | ||||||
|  | 
 | ||||||
|  |                     var bcNorm = TripleProduct(ca, bc, bc); | ||||||
|  |                     var caNorm = TripleProduct(bc, ca, ca); | ||||||
|  | 
 | ||||||
|  |                     // the origin is outside line bc | ||||||
|  |                     // get rid of a and add a new support in the direction of bcNorm | ||||||
|  |                     if (Vector2.Dot(bcNorm, c0) > 0) | ||||||
|  |                     { | ||||||
|  |                         vertices.RemoveAt(0); | ||||||
|  |                         direction = bcNorm; | ||||||
|  |                     } | ||||||
|  |                     // the origin is outside line ca | ||||||
|  |                     // get rid of b and add a new support in the direction of caNorm | ||||||
|  |                     else if (Vector2.Dot(caNorm, c0) > 0) | ||||||
|  |                     { | ||||||
|  |                         vertices.RemoveAt(1); | ||||||
|  |                         direction = caNorm; | ||||||
|  |                     } | ||||||
|  |                     // the origin is inside both ab and ac, | ||||||
|  |                     // so it must be inside the triangle! | ||||||
|  |                     else | ||||||
|  |                     { | ||||||
|  |                         return (SolutionStatus.Intersection, direction); | ||||||
|  |                     } | ||||||
|  |                     break; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return (AddSupport(shapeA, Transform2DA, shapeB, Transform2DB, vertices, direction) ? | ||||||
|  |                 SolutionStatus.StillSolving : | ||||||
|  |                 SolutionStatus.NoIntersection, direction); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static bool AddSupport(IShape2D shapeA, Transform2D Transform2DA, IShape2D shapeB, Transform2D Transform2DB, SimplexVertices vertices, Vector2 direction) | ||||||
|  |         { | ||||||
|  |             var newVertex = shapeA.Support(direction, Transform2DA) - shapeB.Support(-direction, Transform2DB); | ||||||
|  |             vertices.Add(newVertex); | ||||||
|  |             return Vector2.Dot(direction, newVertex) >= 0; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static Vector2 TripleProduct(Vector2 a, Vector2 b, Vector2 c) | ||||||
|  |         { | ||||||
|  |             var A = new Vector3(a.X, a.Y, 0); | ||||||
|  |             var B = new Vector3(b.X, b.Y, 0); | ||||||
|  |             var C = new Vector3(c.X, c.Y, 0); | ||||||
|  | 
 | ||||||
|  |             var first = Vector3.Cross(A, B); | ||||||
|  |             var second = Vector3.Cross(first, C); | ||||||
|  | 
 | ||||||
|  |             return new Vector2(second.X, second.Y); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static Vector2 Perpendicular(Vector2 v) | ||||||
|  |         { | ||||||
|  |             return new Vector2(v.Y, -v.X); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,15 @@ | ||||||
|  | using System; | ||||||
|  | using Microsoft.Xna.Framework; | ||||||
|  | using MoonTools.Core.Structs; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.Core.Bonk | ||||||
|  | { | ||||||
|  |     public interface IShape2D : IEquatable<IShape2D> | ||||||
|  |     { | ||||||
|  |         // A Support function for a Minkowski sum. | ||||||
|  |         // A Support function gives the point on the edge of a shape based on a direction. | ||||||
|  |         Vector2 Support(Vector2 direction, Transform2D transform); | ||||||
|  | 
 | ||||||
|  |         AABB AABB(Transform2D transform); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,41 @@ | ||||||
|  | using System; | ||||||
|  | using Microsoft.Xna.Framework; | ||||||
|  | using MoonTools.Core.Structs; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.Core.Bonk | ||||||
|  | { | ||||||
|  |     public struct Line : IShape2D, IEquatable<IShape2D> | ||||||
|  |     { | ||||||
|  |         private Position2D[] vertices; | ||||||
|  | 
 | ||||||
|  |         public Line(Position2D start, Position2D end) | ||||||
|  |         { | ||||||
|  |             vertices = new Position2D[2] { start, end }; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public Vector2 Support(Vector2 direction, Transform2D transform) | ||||||
|  |         { | ||||||
|  |             var Transform2DedStart = Vector2.Transform(vertices[0], transform.TransformMatrix); | ||||||
|  |             var Transform2DedEnd = Vector2.Transform(vertices[1], transform.TransformMatrix); | ||||||
|  |             return Vector2.Dot(Transform2DedStart, direction) > Vector2.Dot(Transform2DedEnd, direction) ? | ||||||
|  |                 Transform2DedStart : | ||||||
|  |                 Transform2DedEnd; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public AABB AABB(Transform2D Transform2D) | ||||||
|  |         { | ||||||
|  |             return Bonk.AABB.FromTransform2DedVertices(vertices, Transform2D); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public bool Equals(IShape2D other) | ||||||
|  |         { | ||||||
|  |             if (other is Line) | ||||||
|  |             { | ||||||
|  |                 var otherLine = (Line)other; | ||||||
|  |                 return vertices[0].ToVector2() == otherLine.vertices[0].ToVector2() && vertices[1].ToVector2() == otherLine.vertices[1].ToVector2(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,60 @@ | ||||||
|  | using System; | ||||||
|  | using Microsoft.Xna.Framework; | ||||||
|  | using MoonTools.Core.Structs; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.Core.Bonk | ||||||
|  | { | ||||||
|  |     public struct Polygon : IShape2D, IEquatable<IShape2D> | ||||||
|  |     { | ||||||
|  |         public Position2D[] Vertices { get; private set; } | ||||||
|  | 
 | ||||||
|  |         // vertices are local to the origin | ||||||
|  |         public Polygon(params Position2D[] vertices) | ||||||
|  |         { | ||||||
|  |             Vertices = vertices; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public Vector2 Support(Vector2 direction, Transform2D transform) | ||||||
|  |         { | ||||||
|  |             var furthest = float.NegativeInfinity; | ||||||
|  |             var furthestVertex = Vector2.Transform(Vertices[0], transform.TransformMatrix); | ||||||
|  | 
 | ||||||
|  |             foreach (var vertex in Vertices) | ||||||
|  |             { | ||||||
|  |                 var Transform2DedVertex = Vector2.Transform(vertex, transform.TransformMatrix); | ||||||
|  |                 var distance = Vector2.Dot(Transform2DedVertex, direction); | ||||||
|  |                 if (distance > furthest) | ||||||
|  |                 { | ||||||
|  |                     furthest = distance; | ||||||
|  |                     furthestVertex = Transform2DedVertex; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return furthestVertex; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public AABB AABB(Transform2D Transform2D) | ||||||
|  |         { | ||||||
|  |             return Bonk.AABB.FromTransform2DedVertices(Vertices, Transform2D); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public bool Equals(IShape2D other) | ||||||
|  |         { | ||||||
|  |             if (other is Polygon) | ||||||
|  |             { | ||||||
|  |                 var otherPolygon = (Polygon)other; | ||||||
|  | 
 | ||||||
|  |                 if (Vertices.Length != otherPolygon.Vertices.Length) { return false; } | ||||||
|  | 
 | ||||||
|  |                 for (int i = 0; i < Vertices.Length; i++) | ||||||
|  |                 { | ||||||
|  |                     if (Vertices[i].ToVector2() != otherPolygon.Vertices[i].ToVector2()) { return false;} | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,69 @@ | ||||||
|  | using System; | ||||||
|  | using Microsoft.Xna.Framework; | ||||||
|  | using MoonTools.Core.Structs; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.Core.Bonk | ||||||
|  | { | ||||||
|  |     public struct Rectangle : IShape2D, IEquatable<IShape2D> | ||||||
|  |     { | ||||||
|  |         public int MinX { get; } | ||||||
|  |         public int MinY { get; } | ||||||
|  |         public int MaxX { get; } | ||||||
|  |         public int MaxY { get; } | ||||||
|  | 
 | ||||||
|  |         private Position2D[] vertices; | ||||||
|  | 
 | ||||||
|  |         public Rectangle(int minX, int minY, int maxX, int maxY) | ||||||
|  |         { | ||||||
|  |             MinX = minX; | ||||||
|  |             MinY = minY; | ||||||
|  |             MaxX = maxX; | ||||||
|  |             MaxY = maxY; | ||||||
|  | 
 | ||||||
|  |             vertices = new Position2D[4] | ||||||
|  |             { | ||||||
|  |                 new Position2D(minX, minY), | ||||||
|  |                 new Position2D(minX, maxY), | ||||||
|  |                 new Position2D(maxX, minY), | ||||||
|  |                 new Position2D(maxX, maxY) | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public Vector2 Support(Vector2 direction, Transform2D transform) | ||||||
|  |         { | ||||||
|  |             var furthestDistance = float.NegativeInfinity; | ||||||
|  |             var furthestVertex = Vector2.Transform(vertices[0], transform.TransformMatrix); | ||||||
|  | 
 | ||||||
|  |             foreach (var v in vertices) | ||||||
|  |             { | ||||||
|  |                 var Transform2DedVertex = Vector2.Transform(v, transform.TransformMatrix); | ||||||
|  |                 var distance = Vector2.Dot(Transform2DedVertex, direction); | ||||||
|  |                 if (distance > furthestDistance) | ||||||
|  |                 { | ||||||
|  |                     furthestDistance = distance; | ||||||
|  |                     furthestVertex = Transform2DedVertex; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return furthestVertex; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public AABB AABB(Transform2D Transform2D) | ||||||
|  |         { | ||||||
|  |             return Bonk.AABB.FromTransform2DedVertices(vertices, Transform2D); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public bool Equals(IShape2D other) | ||||||
|  |         { | ||||||
|  |             if (other is Rectangle rectangle) | ||||||
|  |             { | ||||||
|  |                 return MinX == rectangle.MinX && | ||||||
|  |                     MinY == rectangle.MinY && | ||||||
|  |                     MaxX == rectangle.MaxX && | ||||||
|  |                     MaxY == rectangle.MaxY; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,91 @@ | ||||||
|  | using System; | ||||||
|  | using System.Collections; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using Microsoft.Xna.Framework; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.Core.Bonk | ||||||
|  | { | ||||||
|  |     public struct SimplexVertices : IEnumerable<Vector2> | ||||||
|  |     { | ||||||
|  |         public Vector2?[] vertices; | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         /// Make sure to pass in all nulls | ||||||
|  |         /// </summary> | ||||||
|  |         public SimplexVertices(Vector2?[] vertices) | ||||||
|  |         { | ||||||
|  |             this.vertices = vertices; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public Vector2 this[int key] | ||||||
|  |         { | ||||||
|  |             get | ||||||
|  |             { | ||||||
|  |                 if (!vertices[key].HasValue) { throw new IndexOutOfRangeException(); } | ||||||
|  |                 return vertices[key].Value; | ||||||
|  |             } | ||||||
|  |             set | ||||||
|  |             { | ||||||
|  |                 vertices[key] = value; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public int Count { | ||||||
|  |             get | ||||||
|  |             { | ||||||
|  |                 for (int i = 0; i < vertices.Length; i++) | ||||||
|  |                 { | ||||||
|  |                     if (!vertices[i].HasValue) { return i; } | ||||||
|  |                 } | ||||||
|  |                 return vertices.Length; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public void Add(Vector2 vertex) | ||||||
|  |         { | ||||||
|  |             if (Count > vertices.Length - 1) { throw new IndexOutOfRangeException(); } | ||||||
|  | 
 | ||||||
|  |             vertices[Count] = vertex; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public void Insert(int index, Vector2 vertex) | ||||||
|  |         { | ||||||
|  |             if (Count >= vertices.Length || index > vertices.Length - 1) { throw new IndexOutOfRangeException(); } | ||||||
|  | 
 | ||||||
|  |             var currentCount = Count; | ||||||
|  | 
 | ||||||
|  |             for (int i = currentCount - 1; i >= index; i--) | ||||||
|  |             { | ||||||
|  |                 vertices[i + 1] = vertices[i]; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             vertices[index] = vertex; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public IEnumerator<Vector2> GetEnumerator() | ||||||
|  |         { | ||||||
|  |             foreach (Vector2? vec in vertices) | ||||||
|  |             { | ||||||
|  |                 if (!vec.HasValue) { yield break; } | ||||||
|  |                 yield return vec.Value; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public void RemoveAt(int index) | ||||||
|  |         { | ||||||
|  |             if (index > vertices.Length - 1 || index > Count) { throw new ArgumentOutOfRangeException(); } | ||||||
|  | 
 | ||||||
|  |             for (int i = vertices.Length - 2; i >= index; i--) | ||||||
|  |             { | ||||||
|  |                 vertices[i] = vertices[i + 1]; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             vertices[vertices.Length - 1] = null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         IEnumerator IEnumerable.GetEnumerator() | ||||||
|  |         { | ||||||
|  |             return GetEnumerator(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,85 @@ | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using MoonTools.Core.Structs; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.Core.Bonk | ||||||
|  | { | ||||||
|  |     public class SpatialHash<T> where T : IEquatable<T> | ||||||
|  |     { | ||||||
|  |         private readonly int cellSize; | ||||||
|  | 
 | ||||||
|  |         private readonly Dictionary<int, Dictionary<int, HashSet<(IShape2D, Transform2D)>>> hashDictionary = new Dictionary<int, Dictionary<int, HashSet<(IShape2D, Transform2D)>>>(); | ||||||
|  |         private readonly Dictionary<(IShape2D, Transform2D), T> IDLookup = new Dictionary<(IShape2D, Transform2D), T>(); | ||||||
|  | 
 | ||||||
|  |         public SpatialHash(int cellSize) | ||||||
|  |         { | ||||||
|  |             this.cellSize = cellSize; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public (int, int) Hash(int x, int y) | ||||||
|  |         { | ||||||
|  |             return ((int)Math.Floor((float)x / cellSize), (int)Math.Floor((float)y / cellSize)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public void Insert(T id, IShape2D shape, Transform2D Transform2D) | ||||||
|  |         { | ||||||
|  |             var box = shape.AABB(Transform2D); | ||||||
|  |             var minHash = Hash(box.MinX, box.MinY); | ||||||
|  |             var maxHash = Hash(box.MaxX, box.MaxY); | ||||||
|  | 
 | ||||||
|  |             for (int i = minHash.Item1; i <= maxHash.Item1; i++) | ||||||
|  |             { | ||||||
|  |                 for (int j = minHash.Item2; j <= maxHash.Item2; j++) | ||||||
|  |                 { | ||||||
|  |                     if (!hashDictionary.ContainsKey(i)) | ||||||
|  |                     { | ||||||
|  |                         hashDictionary.Add(i, new Dictionary<int, HashSet<(IShape2D, Transform2D)>>()); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     if (!hashDictionary[i].ContainsKey(j)) | ||||||
|  |                     { | ||||||
|  |                         hashDictionary[i].Add(j, new HashSet<(IShape2D, Transform2D)>()); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     hashDictionary[i][j].Add((shape, Transform2D)); | ||||||
|  |                     IDLookup[(shape, Transform2D)] = id; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public IEnumerable<(T, IShape2D, Transform2D)> Retrieve(T id, IShape2D shape, Transform2D Transform2D) | ||||||
|  |         { | ||||||
|  |             var box = shape.AABB(Transform2D); | ||||||
|  |             var minHash = Hash(box.MinX, box.MinY); | ||||||
|  |             var maxHash = Hash(box.MaxX, box.MaxY); | ||||||
|  | 
 | ||||||
|  |             for (int i = minHash.Item1; i <= maxHash.Item1; i++) | ||||||
|  |             { | ||||||
|  |                 for (int j = minHash.Item2; j <= maxHash.Item2; j++) | ||||||
|  |                 { | ||||||
|  |                     if (hashDictionary.ContainsKey(i) && hashDictionary[i].ContainsKey(j)) | ||||||
|  |                     { | ||||||
|  |                         foreach (var (otherShape, otherTransform2D) in hashDictionary[i][j]) | ||||||
|  |                         { | ||||||
|  |                             var otherID = IDLookup[(otherShape, otherTransform2D)]; | ||||||
|  |                             if (!id.Equals(otherID)) { yield return (otherID, otherShape, otherTransform2D); } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public void Clear() | ||||||
|  |         { | ||||||
|  |             foreach (var innerDict in hashDictionary.Values) | ||||||
|  |             { | ||||||
|  |                 foreach (var set in innerDict.Values) | ||||||
|  |                 { | ||||||
|  |                     set.Clear(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             IDLookup.Clear(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,70 @@ | ||||||
|  | using NUnit.Framework; | ||||||
|  | using FluentAssertions; | ||||||
|  | 
 | ||||||
|  | using Microsoft.Xna.Framework; | ||||||
|  | using System; | ||||||
|  | using MoonTools.Core.Structs; | ||||||
|  | using MoonTools.Core.Bonk; | ||||||
|  | 
 | ||||||
|  | namespace Tests | ||||||
|  | { | ||||||
|  |     public class EPA2DTest | ||||||
|  |     { | ||||||
|  |         [Test] | ||||||
|  |         public void RectangleOverlap() | ||||||
|  |         { | ||||||
|  |             var squareA = new MoonTools.Core.Bonk.Rectangle(-1, -1, 1, 1); | ||||||
|  |             var transformA = Transform2D.DefaultTransform; | ||||||
|  |             var squareB = new MoonTools.Core.Bonk.Rectangle(-1, -1, 1, 1); | ||||||
|  |             var transformB = new Transform2D(new Vector2(1.5f, 0)); | ||||||
|  | 
 | ||||||
|  |             var test = GJK2D.TestCollision(squareA, transformA, squareB, transformB); | ||||||
|  | 
 | ||||||
|  |             Assert.That(test.Item1, Is.True); | ||||||
|  | 
 | ||||||
|  |             var intersection = EPA2D.Intersect(squareA, transformA, squareB, transformB, test.Item2); | ||||||
|  | 
 | ||||||
|  |             intersection.X.Should().Be(1f); | ||||||
|  |             intersection.Y.Should().Be(0); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Test] | ||||||
|  |         public void CircleOverlap() | ||||||
|  |         { | ||||||
|  |             var circleA = new Circle(2); | ||||||
|  |             var transformA = Transform2D.DefaultTransform; | ||||||
|  |             var circleB = new Circle(1); | ||||||
|  |             var transformB = new Transform2D(new Vector2(1, 1)); | ||||||
|  | 
 | ||||||
|  |             var test = GJK2D.TestCollision(circleA, transformA, circleB, transformB); | ||||||
|  | 
 | ||||||
|  |             Assert.That(test.Item1, Is.True); | ||||||
|  | 
 | ||||||
|  |             var intersection = EPA2D.Intersect(circleA, transformA, circleB, transformB, test.Item2); | ||||||
|  | 
 | ||||||
|  |             var ix = circleA.Radius * (float)Math.Cos(Math.PI / 4) - (circleB.Radius * (float)Math.Cos(5 * Math.PI / 4) + transformB.Position.X); | ||||||
|  |             var iy = circleA.Radius * (float)Math.Sin(Math.PI / 4) - (circleB.Radius * (float)Math.Sin(5 * Math.PI / 4) + transformB.Position.Y); | ||||||
|  | 
 | ||||||
|  |             intersection.X.Should().BeApproximately(ix, 0.01f); | ||||||
|  |             intersection.Y.Should().BeApproximately(iy, 0.01f); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Test] | ||||||
|  |         public void LineRectangleOverlap() | ||||||
|  |         { | ||||||
|  |             var line = new Line(new Position2D(-4, -4), new Position2D(4, 4)); | ||||||
|  |             var transformA = Transform2D.DefaultTransform; | ||||||
|  |             var square = new MoonTools.Core.Bonk.Rectangle(-1, -1, 1, 1); | ||||||
|  |             var transformB = Transform2D.DefaultTransform; | ||||||
|  | 
 | ||||||
|  |             var test = GJK2D.TestCollision(line, transformA, square, transformB); | ||||||
|  | 
 | ||||||
|  |             Assert.That(test.Item1, Is.True); | ||||||
|  | 
 | ||||||
|  |             var intersection = EPA2D.Intersect(line, transformA, square, transformB, test.Item2); | ||||||
|  | 
 | ||||||
|  |             intersection.X.Should().Be(-1); | ||||||
|  |             intersection.Y.Should().Be(1); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,201 @@ | ||||||
|  | using NUnit.Framework; | ||||||
|  | using MoonTools.Core.Bonk; | ||||||
|  | using MoonTools.Core.Structs; | ||||||
|  | using Microsoft.Xna.Framework; | ||||||
|  | 
 | ||||||
|  | namespace Tests | ||||||
|  | { | ||||||
|  |     public class GJK2DTest | ||||||
|  |     { | ||||||
|  |         [Test] | ||||||
|  |         public void LineLineOverlapping() | ||||||
|  |         { | ||||||
|  |             var lineA = new Line(new Position2D(-1, -1), new Position2D(1, 1)); | ||||||
|  |             var lineB = new Line(new Position2D(-1, 1), new Position2D(1, -1)); | ||||||
|  | 
 | ||||||
|  |             Assert.IsTrue(GJK2D.TestCollision(lineA, Transform2D.DefaultTransform, lineB, Transform2D.DefaultTransform).Item1); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Test] | ||||||
|  |         public void LineLineNotOverlapping() | ||||||
|  |         { | ||||||
|  |             var lineA = new Line(new Position2D(0, 1), new Position2D(1, 0)); | ||||||
|  |             var lineB = new Line(new Position2D(-1, -1), new Position2D(-2, -2)); | ||||||
|  | 
 | ||||||
|  |             Assert.IsFalse(GJK2D.TestCollision(lineA, Transform2D.DefaultTransform, lineB, Transform2D.DefaultTransform).Item1); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Test] | ||||||
|  |         public void CircleCircleOverlapping() | ||||||
|  |         { | ||||||
|  |             var circleA = new Circle(2); | ||||||
|  |             var transformA = new Transform2D(new Vector2(-1, -1)); | ||||||
|  |             var circleB = new Circle(2); | ||||||
|  |             var transformB = new Transform2D(new Vector2(1, 1)); | ||||||
|  | 
 | ||||||
|  |             Assert.IsTrue(GJK2D.TestCollision(circleA, transformA, circleB, transformB).Item1); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Test] | ||||||
|  |         public void CircleCircleNotOverlapping() | ||||||
|  |         { | ||||||
|  |             var circleA = new Circle(2); | ||||||
|  |             var transformA = new Transform2D(new Vector2(-5, -5)); | ||||||
|  |             var circleB = new Circle(2); | ||||||
|  |             var transformB = new Transform2D(new Vector2(5, 5)); | ||||||
|  | 
 | ||||||
|  |             Assert.IsFalse(GJK2D.TestCollision(circleA, transformA, circleB, transformB).Item1); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Test] | ||||||
|  |         public void PolygonPolygonOverlapping() | ||||||
|  |         { | ||||||
|  |             var shapeA = new Polygon( | ||||||
|  |                 new Position2D(-1, 1), new Position2D(1, 1), | ||||||
|  |                 new Position2D(-1, -1), new Position2D(1, -1) | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             var transformA = Transform2D.DefaultTransform; | ||||||
|  | 
 | ||||||
|  |             var shapeB = new Polygon( | ||||||
|  |                 new Position2D(-1, 1), new Position2D(1, 1), | ||||||
|  |                 new Position2D(-1, -1), new Position2D(1, -1) | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             var transformB = new Transform2D(new Vector2(0.5f, 0.5f)); | ||||||
|  | 
 | ||||||
|  |             Assert.IsTrue(GJK2D.TestCollision(shapeA, transformA, shapeB, transformB).Item1); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Test] | ||||||
|  |         public void PolygonPolygonNotOverlapping() | ||||||
|  |         { | ||||||
|  |             var shapeA = new Polygon(new Position2D(0, 0), | ||||||
|  |                 new Position2D(-1, 1), new Position2D(1, 1), | ||||||
|  |                 new Position2D(-1, -1), new Position2D(1, -1) | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             var transformA = Transform2D.DefaultTransform; | ||||||
|  | 
 | ||||||
|  |             var shapeB = new Polygon( | ||||||
|  |                 new Position2D(-1, 1), new Position2D(1, 1), | ||||||
|  |                 new Position2D(-1, -1), new Position2D(1, -1) | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             var transformB = new Transform2D(new Vector2(5, 0)); | ||||||
|  | 
 | ||||||
|  |             Assert.IsFalse(GJK2D.TestCollision(shapeA, transformA, shapeB, transformB).Item1); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Test] | ||||||
|  |         public void LinePolygonOverlapping() | ||||||
|  |         { | ||||||
|  |             var line = new Line(new Position2D(-1, -1), new Position2D(1, 1)); | ||||||
|  | 
 | ||||||
|  |             var transformA = Transform2D.DefaultTransform; | ||||||
|  | 
 | ||||||
|  |             var polygon = new Polygon( | ||||||
|  |                 new Position2D(-1, -1), new Position2D(1, -1), | ||||||
|  |                 new Position2D(1, 1), new Position2D(-1, 1) | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             var transformB = Transform2D.DefaultTransform; | ||||||
|  | 
 | ||||||
|  |             Assert.IsTrue(GJK2D.TestCollision(line, transformA, polygon, transformB).Item1); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Test] | ||||||
|  |         public void LinePolygonNotOverlapping() | ||||||
|  |         { | ||||||
|  |             var line = new Line(new Position2D(-5, 5), new Position2D(-5, 5)); | ||||||
|  | 
 | ||||||
|  |             var transformA = Transform2D.DefaultTransform; | ||||||
|  | 
 | ||||||
|  |             var polygon = new Polygon(new Position2D(0, 0), | ||||||
|  |                 new Position2D(-1, -1), new Position2D(1, -1), | ||||||
|  |                 new Position2D(1, 1), new Position2D(-1, 1) | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             var transformB = Transform2D.DefaultTransform; | ||||||
|  | 
 | ||||||
|  |             Assert.IsFalse(GJK2D.TestCollision(line, transformA, polygon, transformB).Item1); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Test] | ||||||
|  |         public void LineCircleOverlapping() | ||||||
|  |         { | ||||||
|  |             var line = new Line(new Position2D(-1, -1), new Position2D(1, 1)); | ||||||
|  |             var transformA = Transform2D.DefaultTransform; | ||||||
|  |             var circle = new Circle(1); | ||||||
|  |             var transformB = Transform2D.DefaultTransform; | ||||||
|  | 
 | ||||||
|  |             Assert.IsTrue(GJK2D.TestCollision(line, transformA, circle, transformB).Item1); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Test] | ||||||
|  |         public void LineCircleNotOverlapping() | ||||||
|  |         { | ||||||
|  |             var line = new Line(new Position2D(-5, -5), new Position2D(-4, -4)); | ||||||
|  |             var transformA = Transform2D.DefaultTransform; | ||||||
|  |             var circle = new Circle(1); | ||||||
|  |             var transformB = Transform2D.DefaultTransform; | ||||||
|  | 
 | ||||||
|  |             Assert.IsFalse(GJK2D.TestCollision(line, transformA, circle, transformB).Item1); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Test] | ||||||
|  |         public void CirclePolygonOverlapping() | ||||||
|  |         { | ||||||
|  |             var circle = new Circle(1); | ||||||
|  |             var transformA = new Transform2D(new Vector2(0.25f, 0)); | ||||||
|  | 
 | ||||||
|  |             var square = new Polygon( | ||||||
|  |                 new Position2D(-1, -1), new Position2D(1, -1), | ||||||
|  |                 new Position2D(1, 1), new Position2D(-1, 1) | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             var transformB = Transform2D.DefaultTransform; | ||||||
|  | 
 | ||||||
|  |             Assert.IsTrue(GJK2D.TestCollision(circle, transformA, square, transformB).Item1); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Test] | ||||||
|  |         public void CirclePolygonNotOverlapping() | ||||||
|  |         { | ||||||
|  |             var circle = new Circle(1); | ||||||
|  |             var circleTransform = new Transform2D(new Vector2(5, 0)); | ||||||
|  | 
 | ||||||
|  |             var square = new Polygon(new Position2D(0, 0), | ||||||
|  |                 new Position2D(-1, -1), new Position2D(1, -1), | ||||||
|  |                 new Position2D(1, 1), new Position2D(-1, 1) | ||||||
|  |             ); | ||||||
|  |             var squareTransform = Transform2D.DefaultTransform; | ||||||
|  | 
 | ||||||
|  |             Assert.IsFalse(GJK2D.TestCollision(circle, circleTransform, square, squareTransform).Item1); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Test] | ||||||
|  |         public void RotatedRectanglesOverlapping() | ||||||
|  |         { | ||||||
|  |             var rectangleA = new MoonTools.Core.Bonk.Rectangle(-1, -1, 2, 2); | ||||||
|  |             var transformA = new Transform2D(new Vector2(-1, 0), -90f); | ||||||
|  | 
 | ||||||
|  |             var rectangleB = new MoonTools.Core.Bonk.Rectangle(-1, -1, 1, 1); | ||||||
|  |             var transformB = new Transform2D(new Vector2(1, 0)); | ||||||
|  | 
 | ||||||
|  |             Assert.IsTrue(GJK2D.TestCollision(rectangleA, transformA, rectangleB, transformB).Item1); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Test] | ||||||
|  |         public void RectanglesTouching() | ||||||
|  |         { | ||||||
|  |             var rectangleA = new MoonTools.Core.Bonk.Rectangle(-1, -1, 1, 1); | ||||||
|  |             var transformA = new Transform2D(new Position2D(-1, 0)); | ||||||
|  | 
 | ||||||
|  |             var rectangleB = new MoonTools.Core.Bonk.Rectangle(-1, -1, 1, 1); | ||||||
|  |             var transformB = new Transform2D(new Vector2(1, 0)); | ||||||
|  | 
 | ||||||
|  |             Assert.IsTrue(GJK2D.TestCollision(rectangleA, transformA, rectangleB, transformB).Item1); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,75 @@ | ||||||
|  | using FluentAssertions; | ||||||
|  | using Microsoft.Xna.Framework; | ||||||
|  | using NUnit.Framework; | ||||||
|  | using MoonTools.Core.Structs; | ||||||
|  | using MoonTools.Core.Bonk; | ||||||
|  | 
 | ||||||
|  | namespace Tests | ||||||
|  | { | ||||||
|  |     public class SpatialHashTest | ||||||
|  |     { | ||||||
|  |         [Test] | ||||||
|  |         public void InsertAndRetrieve() | ||||||
|  |         { | ||||||
|  |             var spatialHash = new SpatialHash<int>(16); | ||||||
|  | 
 | ||||||
|  |             var rectA = new MoonTools.Core.Bonk.Rectangle(-2, -2, 2, 2); | ||||||
|  |             var rectATransform = new Transform2D(new Vector2(-8, -8)); | ||||||
|  | 
 | ||||||
|  |             var rectB = new MoonTools.Core.Bonk.Rectangle(-2, -2, 2, 2); | ||||||
|  |             var rectBTransform = new Transform2D(new Vector2(8, 8)); | ||||||
|  | 
 | ||||||
|  |             var rectC = new MoonTools.Core.Bonk.Rectangle(-2, -2, 2, 2); | ||||||
|  |             var rectCTransform = new Transform2D(new Vector2(24, -4)); | ||||||
|  | 
 | ||||||
|  |             var rectD = new MoonTools.Core.Bonk.Rectangle(-2, -2, 2, 2); | ||||||
|  |             var rectDTransform = new Transform2D(new Vector2(24, 24)); | ||||||
|  | 
 | ||||||
|  |             var circleA = new MoonTools.Core.Bonk.Circle(2); | ||||||
|  |             var circleATransform = new Transform2D(new Vector2(24, -8)); | ||||||
|  | 
 | ||||||
|  |             var circleB = new MoonTools.Core.Bonk.Circle(8); | ||||||
|  |             var circleBTransform = new Transform2D(new Vector2(16, 16)); | ||||||
|  | 
 | ||||||
|  |             var line = new MoonTools.Core.Bonk.Line(new Position2D(20, -4), new Position2D(22, -12)); | ||||||
|  |             var lineTransform = new Transform2D(new Vector2(0, 0)); | ||||||
|  | 
 | ||||||
|  |             spatialHash.Insert(0, rectA, rectATransform); | ||||||
|  |             spatialHash.Insert(1, rectB, rectBTransform); | ||||||
|  |             spatialHash.Insert(2, rectC, rectCTransform); | ||||||
|  |             spatialHash.Insert(3, rectD, rectDTransform); | ||||||
|  |             spatialHash.Insert(4, circleA, circleATransform); | ||||||
|  |             spatialHash.Insert(1, circleB, circleBTransform); | ||||||
|  |             spatialHash.Insert(6, line, lineTransform); | ||||||
|  | 
 | ||||||
|  |             spatialHash.Retrieve(0, rectA, rectATransform).Should().BeEmpty(); | ||||||
|  |             spatialHash.Retrieve(1, rectB, rectBTransform).Should().NotContain((1, circleB, circleBTransform)); | ||||||
|  |             spatialHash.Retrieve(2, rectC, rectCTransform).Should().Contain((6, line, lineTransform)).And.Contain((4, circleA, circleATransform)); | ||||||
|  |             spatialHash.Retrieve(3, rectD, rectDTransform).Should().Contain((1, circleB, circleBTransform)); | ||||||
|  | 
 | ||||||
|  |             spatialHash.Retrieve(4, circleA, circleATransform).Should().Contain((6, line, lineTransform)).And.Contain((2, rectC, rectCTransform)); | ||||||
|  |             spatialHash.Retrieve(1, circleB, circleBTransform).Should().NotContain((1, rectB, rectBTransform)).And.Contain((3, rectD, rectDTransform)); | ||||||
|  | 
 | ||||||
|  |             spatialHash.Retrieve(6, line, lineTransform).Should().Contain((4, circleA, circleATransform)).And.Contain((2, rectC, rectCTransform)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Test] | ||||||
|  |         public void Clear() | ||||||
|  |         { | ||||||
|  |             var spatialHash = new SpatialHash<int>(16); | ||||||
|  | 
 | ||||||
|  |             var rectA = new MoonTools.Core.Bonk.Rectangle(-2, -2, 2, 2); | ||||||
|  |             var rectATransform = new Transform2D(new Vector2(-8, -8)); | ||||||
|  | 
 | ||||||
|  |             var rectB = new MoonTools.Core.Bonk.Rectangle(-2, -2, 2, 2); | ||||||
|  |             var rectBTransform = new Transform2D(new Vector2(8, 8)); | ||||||
|  | 
 | ||||||
|  |             spatialHash.Insert(0, rectA, rectATransform); | ||||||
|  |             spatialHash.Insert(1, rectB, rectBTransform); | ||||||
|  | 
 | ||||||
|  |             spatialHash.Clear(); | ||||||
|  | 
 | ||||||
|  |             spatialHash.Retrieve(0, rectA, rectATransform).Should().HaveCount(0); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,15 @@ | ||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <TargetFramework>netcoreapp3.0</TargetFramework> | ||||||
|  |     <IsPackable>false</IsPackable> | ||||||
|  |   </PropertyGroup> | ||||||
|  |   <ItemGroup> | ||||||
|  |     <PackageReference Include="nunit" Version="3.11.0"/> | ||||||
|  |     <PackageReference Include="NUnit3TestAdapter" Version="3.12.0"/> | ||||||
|  |     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0"/> | ||||||
|  |     <PackageReference Include="FluentAssertions" Version="5.9.0"/> | ||||||
|  |   </ItemGroup> | ||||||
|  |   <ItemGroup> | ||||||
|  |     <ProjectReference Include="..\Bonk\Bonk.csproj"/> | ||||||
|  |   </ItemGroup> | ||||||
|  | </Project> | ||||||
|  | @ -0,0 +1,48 @@ | ||||||
|  |  | ||||||
|  | Microsoft Visual Studio Solution File, Format Version 12.00 | ||||||
|  | # Visual Studio 15 | ||||||
|  | VisualStudioVersion = 15.0.26124.0 | ||||||
|  | MinimumVisualStudioVersion = 15.0.26124.0 | ||||||
|  | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonk", "Bonk\Bonk.csproj", "{F5349EC2-5BA2-4051-A1BB-48649344739B}" | ||||||
|  | EndProject | ||||||
|  | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{866BDF4C-96C3-4DA9-B461-E01BF2146D19}" | ||||||
|  | EndProject | ||||||
|  | Global | ||||||
|  | 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||||
|  | 		Debug|Any CPU = Debug|Any CPU | ||||||
|  | 		Debug|x64 = Debug|x64 | ||||||
|  | 		Debug|x86 = Debug|x86 | ||||||
|  | 		Release|Any CPU = Release|Any CPU | ||||||
|  | 		Release|x64 = Release|x64 | ||||||
|  | 		Release|x86 = Release|x86 | ||||||
|  | 	EndGlobalSection | ||||||
|  | 	GlobalSection(SolutionProperties) = preSolution | ||||||
|  | 		HideSolutionNode = FALSE | ||||||
|  | 	EndGlobalSection | ||||||
|  | 	GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||||||
|  | 		{F5349EC2-5BA2-4051-A1BB-48649344739B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{F5349EC2-5BA2-4051-A1BB-48649344739B}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||||
|  | 		{F5349EC2-5BA2-4051-A1BB-48649344739B}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{F5349EC2-5BA2-4051-A1BB-48649344739B}.Debug|x64.Build.0 = Debug|Any CPU | ||||||
|  | 		{F5349EC2-5BA2-4051-A1BB-48649344739B}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{F5349EC2-5BA2-4051-A1BB-48649344739B}.Debug|x86.Build.0 = Debug|Any CPU | ||||||
|  | 		{F5349EC2-5BA2-4051-A1BB-48649344739B}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||||
|  | 		{F5349EC2-5BA2-4051-A1BB-48649344739B}.Release|Any CPU.Build.0 = Release|Any CPU | ||||||
|  | 		{F5349EC2-5BA2-4051-A1BB-48649344739B}.Release|x64.ActiveCfg = Release|Any CPU | ||||||
|  | 		{F5349EC2-5BA2-4051-A1BB-48649344739B}.Release|x64.Build.0 = Release|Any CPU | ||||||
|  | 		{F5349EC2-5BA2-4051-A1BB-48649344739B}.Release|x86.ActiveCfg = Release|Any CPU | ||||||
|  | 		{F5349EC2-5BA2-4051-A1BB-48649344739B}.Release|x86.Build.0 = Release|Any CPU | ||||||
|  | 		{866BDF4C-96C3-4DA9-B461-E01BF2146D19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{866BDF4C-96C3-4DA9-B461-E01BF2146D19}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||||
|  | 		{866BDF4C-96C3-4DA9-B461-E01BF2146D19}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{866BDF4C-96C3-4DA9-B461-E01BF2146D19}.Debug|x64.Build.0 = Debug|Any CPU | ||||||
|  | 		{866BDF4C-96C3-4DA9-B461-E01BF2146D19}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{866BDF4C-96C3-4DA9-B461-E01BF2146D19}.Debug|x86.Build.0 = Debug|Any CPU | ||||||
|  | 		{866BDF4C-96C3-4DA9-B461-E01BF2146D19}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||||
|  | 		{866BDF4C-96C3-4DA9-B461-E01BF2146D19}.Release|Any CPU.Build.0 = Release|Any CPU | ||||||
|  | 		{866BDF4C-96C3-4DA9-B461-E01BF2146D19}.Release|x64.ActiveCfg = Release|Any CPU | ||||||
|  | 		{866BDF4C-96C3-4DA9-B461-E01BF2146D19}.Release|x64.Build.0 = Release|Any CPU | ||||||
|  | 		{866BDF4C-96C3-4DA9-B461-E01BF2146D19}.Release|x86.ActiveCfg = Release|Any CPU | ||||||
|  | 		{866BDF4C-96C3-4DA9-B461-E01BF2146D19}.Release|x86.Build.0 = Release|Any CPU | ||||||
|  | 	EndGlobalSection | ||||||
|  | EndGlobal | ||||||
		Loading…
	
		Reference in New Issue