From eefa52c4b3b1bbdeb63158d60ebb293f19a9fb0c Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Fri, 25 Oct 2019 16:20:43 -0700 Subject: [PATCH] shape equality optimizations --- Bonk/Bonk.csproj | 2 +- Bonk/MinkowskiDifference.cs | 49 ++++- Bonk/Shapes/Circle.cs | 25 +++ Bonk/Shapes/Line.cs | 36 ++- Bonk/Shapes/Polygon.cs | 71 +++--- Bonk/Shapes/Rectangle.cs | 31 +++ Bonk/Shapes/Simplex.cs | 56 ++++- Test/Equality.cs | 427 ++++++++++++++++++++++++++++++++++++ 8 files changed, 641 insertions(+), 56 deletions(-) create mode 100644 Test/Equality.cs diff --git a/Bonk/Bonk.csproj b/Bonk/Bonk.csproj index 5bd482f..88d507a 100644 --- a/Bonk/Bonk.csproj +++ b/Bonk/Bonk.csproj @@ -15,7 +15,7 @@ https://github.com/MoonsideGames/MoonTools.Core.Bonk - + diff --git a/Bonk/MinkowskiDifference.cs b/Bonk/MinkowskiDifference.cs index e6772eb..940cad1 100644 --- a/Bonk/MinkowskiDifference.cs +++ b/Bonk/MinkowskiDifference.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Microsoft.Xna.Framework; using MoonTools.Core.Structs; @@ -22,18 +23,48 @@ namespace MoonTools.Core.Bonk this.transformB = transformB; } - public bool Equals(MinkowskiDifference other) - { - return - shapeA == other.shapeA && - transformA.Equals(other.transformA) && - shapeB == other.shapeB && - transformB.Equals(other.transformB); - } - public Vector2 Support(Vector2 direction) { return shapeA.Support(direction, transformA) - shapeB.Support(-direction, transformB); } + + public override bool Equals(object other) + { + if (other is MinkowskiDifference otherMinkowskiDifference) + { + return Equals(otherMinkowskiDifference); + } + + return false; + } + + public bool Equals(MinkowskiDifference other) + { + return + shapeA == other.shapeA && + transformA == other.transformA && + shapeB == other.shapeB && + transformB == other.transformB; + } + + public override int GetHashCode() + { + var hashCode = 974363698; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(shapeA); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(transformA); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(shapeB); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(transformB); + return hashCode; + } + + public static bool operator ==(MinkowskiDifference a, MinkowskiDifference b) + { + return a.Equals(b); + } + + public static bool operator !=(MinkowskiDifference a, MinkowskiDifference b) + { + return !(a == b); + } } } \ No newline at end of file diff --git a/Bonk/Shapes/Circle.cs b/Bonk/Shapes/Circle.cs index 8e286d5..af93404 100644 --- a/Bonk/Shapes/Circle.cs +++ b/Bonk/Shapes/Circle.cs @@ -31,6 +31,16 @@ namespace MoonTools.Core.Bonk ); } + public override bool Equals(object obj) + { + if (obj is Circle other) + { + return Equals(other); + } + + return false; + } + public bool Equals(IShape2D other) { if (other is Circle circle) @@ -40,5 +50,20 @@ namespace MoonTools.Core.Bonk return false; } + + public override int GetHashCode() + { + return 598075851 + Radius.GetHashCode(); + } + + public static bool operator ==(Circle a, Circle b) + { + return a.Equals(b); + } + + public static bool operator !=(Circle a, Circle b) + { + return !(a == b); + } } } diff --git a/Bonk/Shapes/Line.cs b/Bonk/Shapes/Line.cs index b927719..825c0e2 100644 --- a/Bonk/Shapes/Line.cs +++ b/Bonk/Shapes/Line.cs @@ -42,15 +42,43 @@ namespace MoonTools.Core.Bonk return Bonk.AABB.FromTransformedVertices(vertices, Transform2D); } - public bool Equals(IShape2D other) + public override bool Equals(object obj) { - if (other is Line) + if (obj is IShape2D other) { - var otherLine = (Line)other; - return v0.ToVector2() == otherLine.v0.ToVector2() && v1.ToVector2() == otherLine.v1.ToVector2(); + return Equals(other); } return false; } + + public bool Equals(IShape2D other) + { + if (other is Line otherLine) + { + return (v0 == otherLine.v0 && v1 == otherLine.v1) || (v1 == otherLine.v0 && v0 == otherLine.v1); + } + + return false; + } + + public override int GetHashCode() + { + var hashCode = -851829407; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(v0); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(v1); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(vertices); + return hashCode; + } + + public static bool operator ==(Line a, Line b) + { + return a.Equals(b); + } + + public static bool operator !=(Line a, Line b) + { + return !(a == b); + } } } diff --git a/Bonk/Shapes/Polygon.cs b/Bonk/Shapes/Polygon.cs index 8a4232d..2cac2d8 100644 --- a/Bonk/Shapes/Polygon.cs +++ b/Bonk/Shapes/Polygon.cs @@ -1,6 +1,10 @@ -using System; +using System.Linq; +using System; +using System.Collections.Generic; +using Collections.Pooled; using Microsoft.Xna.Framework; using MoonTools.Core.Structs; +using MoreLinq; namespace MoonTools.Core.Bonk { @@ -9,31 +13,19 @@ namespace MoonTools.Core.Bonk /// public struct Polygon : IShape2D, IEquatable { - public Position2D[] Vertices { get; private set; } + private PooledSet vertices; + + public IEnumerable Vertices { get { return vertices == null ? Enumerable.Empty() : vertices; } } // vertices are local to the origin public Polygon(params Position2D[] vertices) { - Vertices = vertices; + this.vertices = new PooledSet(vertices, ClearMode.Always); } 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 TransformedVertex = Vector2.Transform(vertex, transform.TransformMatrix); - var distance = Vector2.Dot(TransformedVertex, direction); - if (distance > furthest) - { - furthest = distance; - furthestVertex = TransformedVertex; - } - } - - return furthestVertex; + return Vertices.Select(vertex => Vector2.Transform(vertex, transform.TransformMatrix)).MaxBy(transformed => Vector2.Dot(transformed, direction)).First(); } public AABB AABB(Transform2D Transform2D) @@ -41,23 +33,42 @@ namespace MoonTools.Core.Bonk return Bonk.AABB.FromTransformedVertices(Vertices, Transform2D); } - public bool Equals(IShape2D other) + public override bool Equals(object obj) { - if (other is Polygon) + if (obj is IShape2D other) { - 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 Equals(other); } return false; } + + public bool Equals(IShape2D other) + { + if (other is Polygon otherPolygon) + { + return vertices.SetEquals(otherPolygon.vertices); + } + + return false; + } + + public override int GetHashCode() + { + var hashCode = -1404792980; + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(vertices); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(Vertices); + return hashCode; + } + + public static bool operator ==(Polygon a, Polygon b) + { + return a.Equals(b); + } + + public static bool operator !=(Polygon a, Polygon b) + { + return !(a == b); + } } } diff --git a/Bonk/Shapes/Rectangle.cs b/Bonk/Shapes/Rectangle.cs index 3a6b2f5..069d79c 100644 --- a/Bonk/Shapes/Rectangle.cs +++ b/Bonk/Shapes/Rectangle.cs @@ -46,6 +46,16 @@ namespace MoonTools.Core.Bonk return Bonk.AABB.FromTransformedVertices(vertices, Transform2D); } + public override bool Equals(object obj) + { + if (obj is IShape2D other) + { + return Equals(other); + } + + return false; + } + public bool Equals(IShape2D other) { if (other is Rectangle rectangle) @@ -58,5 +68,26 @@ namespace MoonTools.Core.Bonk return false; } + + public override int GetHashCode() + { + var hashCode = -1260800952; + hashCode = hashCode * -1521134295 + MinX.GetHashCode(); + hashCode = hashCode * -1521134295 + MinY.GetHashCode(); + hashCode = hashCode * -1521134295 + MaxX.GetHashCode(); + hashCode = hashCode * -1521134295 + MaxY.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(vertices); + return hashCode; + } + + public static bool operator ==(Rectangle a, Rectangle b) + { + return a.Equals(b); + } + + public static bool operator !=(Rectangle a, Rectangle b) + { + return !(a == b); + } } } diff --git a/Bonk/Shapes/Simplex.cs b/Bonk/Shapes/Simplex.cs index 7562d73..5e8b876 100644 --- a/Bonk/Shapes/Simplex.cs +++ b/Bonk/Shapes/Simplex.cs @@ -44,18 +44,6 @@ namespace MoonTools.Core.Bonk return Bonk.AABB.FromTransformedVertices(Vertices, transform); } - public bool Equals(IShape2D other) - { - if (other is Simplex polytope) - { - return minkowskiDifference.Equals(polytope.minkowskiDifference) && - directionA == polytope.directionA && - directionB == polytope.directionB; - } - - return false; - } - public Vector2 Support(Vector2 direction) { return minkowskiDifference.Support(direction); @@ -65,5 +53,49 @@ namespace MoonTools.Core.Bonk { return Vector2.Transform(Support(direction), transform.TransformMatrix); } + + public override bool Equals(object obj) + { + if (obj is IShape2D other) + { + return Equals(other); + } + + return false; + } + + public bool Equals(IShape2D other) + { + if (other is Simplex otherSimplex) + { + return minkowskiDifference == otherSimplex.minkowskiDifference && + ((directionA == otherSimplex.directionA && directionB == otherSimplex.directionB) || + (directionA == otherSimplex.directionB && directionB == otherSimplex.directionA)); + } + + return false; + } + + public override int GetHashCode() + { + var hashCode = 74270316; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(minkowskiDifference); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(directionA); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(directionB); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(DirectionA); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(DirectionB); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(Vertices); + return hashCode; + } + + public static bool operator ==(Simplex a, Simplex b) + { + return a.Equals(b); + } + + public static bool operator !=(Simplex a, Simplex b) + { + return !(a == b); + } } } \ No newline at end of file diff --git a/Test/Equality.cs b/Test/Equality.cs new file mode 100644 index 0000000..e23bc52 --- /dev/null +++ b/Test/Equality.cs @@ -0,0 +1,427 @@ +using NUnit.Framework; +using FluentAssertions; + +using MoonTools.Core.Bonk; +using MoonTools.Core.Structs; +using Microsoft.Xna.Framework; + +namespace Tests +{ + public class EqualityTests + { + public class CircleTests + { + [Test] + public void CircleEqual() + { + var a = new Circle(2); + var b = new Circle(2); + + (a.Equals(b)).Should().BeTrue(); + } + + [Test] + public void CircleNotEqual() + { + var a = new Circle(2); + var b = new Circle(3); + + (a.Equals(b)).Should().BeFalse(); + } + + [Test] + public void CircleEqualOperator() + { + var a = new Circle(2); + var b = new Circle(2); + + (a == b).Should().BeTrue(); + } + + [Test] + public void CircleNotEqualOperator() + { + var a = new Circle(2); + var b = new Circle(3); + + (a != b).Should().BeTrue(); + } + } + + public class LineTests + { + [Test] + public void LineEqual() + { + var a = new Line(new Position2D(0, 2), new Position2D(2, 4)); + var b = new Line(new Position2D(0, 2), new Position2D(2, 4)); + + a.Equals(b).Should().BeTrue(); + } + + [Test] + public void LineEqualOperator() + { + var a = new Line(new Position2D(0, 2), new Position2D(2, 4)); + var b = new Line(new Position2D(0, 2), new Position2D(2, 4)); + + (a == b).Should().BeTrue(); + } + + [Test] + public void LineNotEqual() + { + var a = new Line(new Position2D(-2, 4), new Position2D(2, 4)); + var b = new Line(new Position2D(0, 3), new Position2D(5, 1)); + + a.Equals(b).Should().BeFalse(); + } + + [Test] + public void LineNotEqualOperator() + { + var a = new Line(new Position2D(-2, 4), new Position2D(2, 4)); + var b = new Line(new Position2D(0, 3), new Position2D(5, 1)); + + (a != b).Should().BeTrue(); + } + + [Test] + public void LineReversedEqual() + { + var a = new Line(new Position2D(0, 2), new Position2D(2, 4)); + var b = new Line(new Position2D(2, 4), new Position2D(0, 2)); + + a.Equals(b).Should().BeTrue(); + } + + [Test] + public void LineReversedEqualOperator() + { + var a = new Line(new Position2D(0, 2), new Position2D(2, 4)); + var b = new Line(new Position2D(2, 4), new Position2D(0, 2)); + + (a == b).Should().BeTrue(); + } + } + + public class RectangleTests + { + [Test] + public void RectangleEqual() + { + var a = new MoonTools.Core.Bonk.Rectangle(0, 0, 3, 3); + var b = new MoonTools.Core.Bonk.Rectangle(0, 0, 3, 3); + + a.Equals(b).Should().BeTrue(); + } + + [Test] + public void RectangleEqualOperator() + { + var a = new MoonTools.Core.Bonk.Rectangle(0, 0, 3, 3); + var b = new MoonTools.Core.Bonk.Rectangle(0, 0, 3, 3); + + (a == b).Should().BeTrue(); + } + + [Test] + public void RectangleNotEqual() + { + var a = new MoonTools.Core.Bonk.Rectangle(0, 0, 3, 3); + var b = new MoonTools.Core.Bonk.Rectangle(-1, -1, 5, 5); + + a.Equals(b).Should().BeFalse(); + } + + [Test] + public void RectangleNotEqualOperator() + { + var a = new MoonTools.Core.Bonk.Rectangle(0, 0, 3, 3); + var b = new MoonTools.Core.Bonk.Rectangle(-1, -1, 5, 5); + + (a != b).Should().BeTrue(); + } + } + + public class PolygonTests + { + [Test] + public void PolygonEqual() + { + var a = new Polygon( + new Position2D(0, 1), + new Position2D(1, 2), + new Position2D(-1, -1) + ); + + var b = new Polygon( + new Position2D(0, 1), + new Position2D(1, 2), + new Position2D(-1, -1) + ); + + a.Equals(b).Should().BeTrue(); + } + + [Test] + public void PolygonEqualOperator() + { + var a = new Polygon( + new Position2D(0, 1), + new Position2D(1, 2), + new Position2D(-1, -1) + ); + + var b = new Polygon( + new Position2D(0, 1), + new Position2D(1, 2), + new Position2D(-1, -1) + ); + + (a == b).Should().BeTrue(); + } + + [Test] + public void PolygonDifferentOrderEqual() + { + var a = new Polygon( + new Position2D(0, 1), + new Position2D(1, 2), + new Position2D(-1, -1) + ); + + var b = new Polygon( + new Position2D(1, 2), + new Position2D(-1, -1), + new Position2D(0, 1) + ); + + a.Equals(b).Should().BeTrue(); + } + + [Test] + public void PolygonDifferentOrderEqualOperator() + { + var a = new Polygon( + new Position2D(0, 1), + new Position2D(1, 2), + new Position2D(-1, -1) + ); + + var b = new Polygon( + new Position2D(1, 2), + new Position2D(-1, -1), + new Position2D(0, 1) + ); + + (a == b).Should().BeTrue(); + } + + [Test] + public void PolygonNotEqual() + { + var a = new Polygon( + new Position2D(0, 1), + new Position2D(1, 2), + new Position2D(-1, -1) + ); + + var b = new Polygon( + new Position2D(1, 0), + new Position2D(2, 1), + new Position2D(-1, -1) + ); + + a.Equals(b).Should().BeFalse(); + } + + [Test] + public void PolygonNotEqualOperator() + { + var a = new Polygon( + new Position2D(0, 1), + new Position2D(1, 2), + new Position2D(-1, -1) + ); + + var b = new Polygon( + new Position2D(1, 0), + new Position2D(2, 1), + new Position2D(-1, -1) + ); + + (a != b).Should().BeTrue(); + } + } + + public class SimplexTests + { + [Test] + public void SimplexEquals() + { + var shapeA = new Circle(3); + var transformA = new Transform2D(new Position2D(1, 2)); + + var shapeB = new Circle(2); + var transformB = new Transform2D(new Position2D(4, 5)); + + var minkowskiDifference = new MinkowskiDifference(shapeA, transformA, shapeB, transformB); + + var directionA = Vector2.UnitX; + var directionB = Vector2.UnitY; + + var simplexA = new Simplex(minkowskiDifference, directionA, directionB); + var simplexB = new Simplex(minkowskiDifference, directionA, directionB); + + simplexA.Equals(simplexB).Should().BeTrue(); + } + + [Test] + public void SimplexEqualsOperator() + { + var shapeA = new Circle(3); + var transformA = new Transform2D(new Position2D(1, 2)); + + var shapeB = new Circle(2); + var transformB = new Transform2D(new Position2D(4, 5)); + + var minkowskiDifference = new MinkowskiDifference(shapeA, transformA, shapeB, transformB); + + var directionA = Vector2.UnitX; + var directionB = Vector2.UnitY; + + var simplexA = new Simplex(minkowskiDifference, directionA, directionB); + var simplexB = new Simplex(minkowskiDifference, directionA, directionB); + + (simplexA == simplexB).Should().BeTrue(); + } + + [Test] + public void SimplexDirectionOutOfOrderEqual() + { + var shapeA = new Circle(3); + var transformA = new Transform2D(new Position2D(1, 2)); + + var shapeB = new Circle(2); + var transformB = new Transform2D(new Position2D(4, 5)); + + var minkowskiDifference = new MinkowskiDifference(shapeA, transformA, shapeB, transformB); + + var directionA = Vector2.UnitX; + var directionB = Vector2.UnitY; + + var simplexA = new Simplex(minkowskiDifference, directionA, directionB); + var simplexB = new Simplex(minkowskiDifference, directionB, directionA); + + simplexA.Equals(simplexB).Should().BeTrue(); + } + + [Test] + public void SimplexDirectionOutOfOrderEqualOperator() + { + var shapeA = new Circle(3); + var transformA = new Transform2D(new Position2D(1, 2)); + + var shapeB = new Circle(2); + var transformB = new Transform2D(new Position2D(4, 5)); + + var minkowskiDifference = new MinkowskiDifference(shapeA, transformA, shapeB, transformB); + + var directionA = Vector2.UnitX; + var directionB = Vector2.UnitY; + + var simplexA = new Simplex(minkowskiDifference, directionA, directionB); + var simplexB = new Simplex(minkowskiDifference, directionB, directionA); + + (simplexA == simplexB).Should().BeTrue(); + } + + [Test] + public void SimplexMinkowskiNotEqual() + { + var shapeA = new Circle(3); + var transformA = new Transform2D(new Position2D(1, 2)); + + var shapeB = new Circle(2); + var transformB = new Transform2D(new Position2D(4, 5)); + + var minkowskiDifferenceA = new MinkowskiDifference(shapeA, transformA, shapeB, transformB); + var minkowskiDifferenceB = new MinkowskiDifference(shapeB, transformB, shapeA, transformA); + + var directionA = Vector2.UnitX; + var directionB = Vector2.UnitY; + + var simplexA = new Simplex(minkowskiDifferenceA, directionA, directionB); + var simplexB = new Simplex(minkowskiDifferenceB, directionA, directionB); + + simplexA.Equals(simplexB).Should().BeFalse(); + } + + [Test] + public void SimplexMinkowskiNotEqualOperator() + { + var shapeA = new Circle(3); + var transformA = new Transform2D(new Position2D(1, 2)); + + var shapeB = new Circle(2); + var transformB = new Transform2D(new Position2D(4, 5)); + + var minkowskiDifferenceA = new MinkowskiDifference(shapeA, transformA, shapeB, transformB); + var minkowskiDifferenceB = new MinkowskiDifference(shapeB, transformB, shapeA, transformA); + + var directionA = Vector2.UnitX; + var directionB = Vector2.UnitY; + + var simplexA = new Simplex(minkowskiDifferenceA, directionA, directionB); + var simplexB = new Simplex(minkowskiDifferenceB, directionA, directionB); + + (simplexA != simplexB).Should().BeTrue(); + } + + [Test] + public void SimplexDirectionsNotEqual() + { + var shapeA = new Circle(3); + var transformA = new Transform2D(new Position2D(1, 2)); + + var shapeB = new Circle(2); + var transformB = new Transform2D(new Position2D(4, 5)); + + var minkowskiDifference = new MinkowskiDifference(shapeA, transformA, shapeB, transformB); + + var directionA = Vector2.UnitX; + var directionB = Vector2.UnitY; + var directionC = -Vector2.UnitX; + var directionD = -Vector2.UnitY; + + var simplexA = new Simplex(minkowskiDifference, directionA, directionB); + var simplexB = new Simplex(minkowskiDifference, directionC, directionD); + + simplexA.Equals(simplexB).Should().BeFalse(); + } + + [Test] + public void SimplexDirectionsNotEqualOperator() + { + var shapeA = new Circle(3); + var transformA = new Transform2D(new Position2D(1, 2)); + + var shapeB = new Circle(2); + var transformB = new Transform2D(new Position2D(4, 5)); + + var minkowskiDifference = new MinkowskiDifference(shapeA, transformA, shapeB, transformB); + + var directionA = Vector2.UnitX; + var directionB = Vector2.UnitY; + var directionC = -Vector2.UnitX; + var directionD = -Vector2.UnitY; + + var simplexA = new Simplex(minkowskiDifference, directionA, directionB); + var simplexB = new Simplex(minkowskiDifference, directionC, directionD); + + (simplexA != simplexB).Should().BeTrue(); + } + } + } +} \ No newline at end of file