using System; using System.Collections.Generic; using System.Linq; using Chernobyl.Mathematics.Geometry; using NUnit.Framework; namespace Chernobyl.Mathematics.Collision { /// /// The test fixture that tests /// [TestFixture] public class QuadTreeTests : CollidableCollectionTests { [Test, Description("A test that ensures that the " + "QuadTree.QuadTree(Rectangle, uint) " + "properly constructs a Rectangle with the proportions specified by " + "the Rectangle passed to it and creates the appropriate number of " + "children after the max number of collidables specified have been " + "added to it.")] public void ConstructorRectangleUint() { Rectangle area = new Rectangle(0, 0, 100, 100); QuadTree quadTree = new QuadTree(area, 3); TestDimensions(area, quadTree); // now test the maxCollidablesPerNode setting in the QuadTree's // constructor quadTree.Add(new Rectangle(20, 20, 1, 1)); Assert.That(quadTree.Children[0] == null, "The " + "QuadTree has children even " + "though we've only added one item (below the maximum " + "collidables per node specified in the constructor)."); quadTree.Add(new Rectangle(25, 25, 1, 1)); Assert.That(quadTree.Children[0] == null, "The " + "QuadTree has children even " + "though we've only added two items (below the maximum " + "collidables per node specified in the constructor)."); quadTree.Add(new Rectangle(30, 30, 1, 1)); Assert.That(quadTree.Children[0] == null, "The " + "QuadTree has children even " + "though we've only added three items (below the maximum " + "collidables per node specified in the constructor)."); quadTree.Add(new Rectangle(35, 35, 1, 1)); Assert.That(quadTree.Children[0] != null, "The " + "QuadTree does NOT have " + "children even though we've added four items (above the maximum " + "collidables per node specified in the constructor)."); } [Test, Description("A test that ensures that the " + "QuadTree.QuadTree(Rectangle, uint) " + "properly throws an ArgumentOutOfRangeException when the " + "uint maxCollidablesPerNode argument is zero.")] public void ConstructorRectangleUintThrowsArgumentOutOfRangeException() { Assert.Throws(() => new QuadTree(new Rectangle(0, 0, 100, 100), 0)); } [Test, Description("A test that ensures that the " + "QuadTree.QuadTree(Rectangle) " + "properly constructs a Rectangle with the proportions specified by " + "Rectangle passed to it.")] public void ConstructorRectangle() { Rectangle area = new Rectangle(5, 6, 189, 24); QuadTree quadTree = new QuadTree(area); TestDimensions(area, quadTree); } [Test, Description("A test that ensures that the QuadTree.TryAdd(T) " + "returns true when client code attempts to add an object that " + "is inside the QuadTree's bounds.")] public void TryAddReturnsTrue() { QuadTree collisionGroup = GenerateQuadTree(); Assert.True(collisionGroup.TryAdd(new Rectangle(1, 1, 1, 1)), "The Rectangle was in the QuadTrees bounds and should have " + "been added."); } [Test, Description("A test that ensures that the QuadTree.TryAdd(T) " + "returns false when client code attempts to add an object that " + "is outside the QuadTree's bounds.")] public void TryAddReturnsFalse() { QuadTree collisionGroup = GenerateQuadTree(); Assert.False(collisionGroup.TryAdd(new Rectangle(-100, -100, 1, 1)), "The Rectangle was out of the QuadTrees bounds and should not " + "have been added."); } [Test, Description("A test that ensures that an ArgumentException is " + "properly thrown when client code attempts to add an object that " + "is outside the QuadTree's bounds.")] public void AddThrowsException() { ICollidableCollection collidableCollection = CreateCollidableCollection(); Assert.Throws(() => collidableCollection.Add(new Rectangle(-100, -100, 1, 1))); } [Test, Description("A test that ensures that an ArgumentException is " + "NOT thrown when client code attempts to add an object that " + "is inside the QuadTree's bounds.")] public void AddDoesNotThrowAnException() { ICollidableCollection collidableCollection = CreateCollidableCollection(); collidableCollection.Add(new Rectangle(1, 1, 1, 1)); } [Test, Description("A test that ensures that a collidable contained within " + "a QuadTree can translate and be properly contained within a sibling " + "QuadTree.")] public void ObjectCanTranslate() { QuadTree quadTree = new QuadTree(new Rectangle(100, 100), 1); Rectangle collidable = new Rectangle(25, 25, 5, 5); quadTree.Add(new Rectangle(60, 60, 1, 1)); quadTree.Add(collidable); collidable.Translate(50, 50); quadTree.Add(new Rectangle(45, 45, 10, 10)); Assert.AreEqual(3, quadTree.Count, "The collidable should still " + "contain 3 Rectangles but it does not."); AssertQuadTreeContainsCollidable(quadTree, collidable); } [Test, Description("A test that ensures that a collidable contained within " + "a QuadTree can scale up and be properly contained within the QuadTree.")] public void ObjectCanScaleUp() { QuadTree quadTree = new QuadTree(new Rectangle(100, 100), 1); Rectangle collidable = new Rectangle(25, 25, 20, 20); quadTree.Add(new Rectangle(60, 60, 1, 1)); quadTree.Add(collidable); collidable.Scale(2); quadTree.Add(new Rectangle(45, 45, 10, 10)); Assert.AreEqual(3, quadTree.Count, "The collidable should still " + "contain 3 Rectangles but it does not."); AssertQuadTreeContainsCollidable(quadTree, collidable); } [Test, Description("A test that ensures that a collidable contained within " + "a QuadTree can scale down and be properly contained within the QuadTree.")] public void ObjectCanScaleDown() { QuadTree quadTree = new QuadTree(new Rectangle(100, 100), 1); Rectangle collidable = new Rectangle(25, 25, 40, 40); quadTree.Add(new Rectangle(60, 60, 1, 1)); quadTree.Add(collidable); collidable.Scale(.5f); quadTree.Add(new Rectangle(45, 45, 10, 10)); Assert.AreEqual(3, quadTree.Count, "The collidable should still " + "contain 3 Rectangles but it does not."); AssertQuadTreeContainsCollidable(quadTree, collidable); } [Test, Description("A test that ensures that a collidable contained within " + "a QuadTree can be properly moved out of the QuadTree.")] public void ObjectCanMoveOut() { QuadTree quadTree = new QuadTree(new Rectangle(100, 100), 1); Rectangle collidable = new Rectangle(25, 25, 20, 20); quadTree.Add(new Rectangle(60, 60, 1, 1)); quadTree.Add(collidable); quadTree.Add(new Rectangle(45, 45, 10, 10)); collidable.Translate(100, 100); Assert.AreEqual(2, quadTree.Count, "The collidable should contain" + "two Rectangles but it does not."); Assert.That(quadTree.Contains(collidable) == false, "The collidable " + "should NOT be contained within the QuadTree but is."); Assert.That(quadTree.IsColliding(collidable) == false, "The " + "collidable is NOT contained within the QuadTree but was " + "collided against."); Assert.That(quadTree.GetCollidables(collidable).Contains(collidable) == false, "The collidable was contained within the Collision enumerable."); } [Test, Description("A test that ensures that a QuadTree's " + "TreeDirty event is raised when that QuadTree has been dirtied.")] public void TreeDirtyEventInvoked() { QuadTree quadTree = new QuadTree(new Rectangle(100, 100), 1); Rectangle collidable = new Rectangle(25, 25, 20, 20); quadTree.Add(new Rectangle(60, 60, 1, 1)); quadTree.Add(collidable); quadTree.Add(new Rectangle(45, 45, 10, 10)); quadTree.TreeDirtied += (sender, e) => Assert.Pass("TreeDirted event was properly raised."); collidable.Translate(10, 10); Assert.Fail("TreeDirtied event was NOT properly raised."); } [Test, Description("A test that ensures that a QuadTree's " + "CollidableMovedOut event is raised when an object moves out.")] public void CollidableMovedOutEventInvoked() { QuadTree quadTree = new QuadTree(new Rectangle(100, 100), 1); Rectangle collidable = new Rectangle(25, 25, 20, 20); quadTree.Add(new Rectangle(60, 60, 1, 1)); quadTree.Add(collidable); quadTree.Add(new Rectangle(45, 45, 10, 10)); quadTree.CollidableMovedOut += (sender, e) => Assert.Pass("CollidableMovedOut event was properly raised."); collidable.Translate(100, 100); // Quadtree won't throw the event until it's been cleaned. quadTree.Clean(); Assert.Fail("CollidableMovedOut event was NOT properly raised."); } protected override ICollidableCollection CreateCollidableCollection() { return GenerateQuadTree(); } /// /// Creates a /// for this class and subclasses to use for testing. /// /// The /// that was created. public static QuadTree GenerateQuadTree() { QuadTree quadTree = new QuadTree(new Rectangle(100, 100)) { CreateCollided(), new Rectangle(47, 93, 33, 6), new Rectangle(2, 6, 66, 23), new Rectangle(13, 8, 0.5f, 0.1f), new Rectangle(36, 65, 25, 5), new Rectangle(24, 86, 3, 1), new Rectangle(20, 12, 75, 13) }; return quadTree; } protected override Rectangle CreateCollider() { return new Rectangle(70, 70, 2, 0.5f); } protected override Rectangle GetCollided() { return CreateCollided(); } protected override Rectangle CreateNonCollider() { return new Rectangle(92, 95, 1, 3); } protected static Rectangle CreateCollided() { return new Rectangle(70, 70, 1, 1); } /// /// Tests the dimensions of the /// against the /// dimensions of the passed in are using the NUnit framework. /// /// The area that is expected from the /// /// The quad tree to test for the appropriate /// dimensions. static void TestDimensions(Rectangle area, QuadTree quadTree) { Assert.AreEqual(area.Position, quadTree.Position, "The Position " + "is different from the position this test specified."); Assert.AreEqual(area.Width, quadTree.Width, "The Width " + "is different from the Width this test specified."); Assert.AreEqual(area.Height, quadTree.Height, "The Height " + "is different from the Height this test specified."); } /// /// Asserts if the is not contained within /// . /// /// The /// to check /// for containment of the . /// The collidable to check for containment /// within . static void AssertQuadTreeContainsCollidable(QuadTree quadTree, Rectangle collidable) { Assert.That(quadTree.Contains(collidable), "The collidable should " + "still be contained within the QuadTree but is not."); Assert.That(quadTree.IsColliding(collidable), "The collidable is " + "still contained within the QuadTree but was not properly " + "collided against."); Assert.That(quadTree.GetCollidables(collidable).Contains(collidable), "The collidable was not properly contained within the Collision " + "enumerable."); } [TestFixture, Description("Tests the IShape and ITransform " + "portions of QuadTree.")] public class QuadTreeShapeTests : ShapeTests { protected override IShape CreateShape() { return GenerateQuadTree(); } [Test] public override void Rotate() { Assert.Throws(() => base.Rotate()); } [Test] public override void Pitch() { Assert.Throws(() => base.Pitch()); } [Test] public override void Yaw() { Assert.Throws(() => base.Yaw()); } [Test] public override void Roll() { Assert.Throws(() => base.Roll()); } protected override float ExpectedWidth { get { return 100; } } protected override float ExpectedHeight { get { return 100; } } protected override float ExpectedDepth { get { return 0; } } protected override bool ExpectedIsConvex { get { return true; } } protected override float ExpectedArea { get { return 10000; } } protected override float ExpectedVolume { get { return 0; } } protected override float ExpectedPerimeter { get { return 400; } } protected override uint ExpectedDimensions { get { return 2; } } } [TestFixture, Description("Tests the IEnumerable> " + "portion of QuadTree.")] public class EnumerationTests : EnumerationTests> { public EnumerationTests() : base(false) { } protected override IEnumerable> CreateSingleItemEnumerable() { return new QuadTree(new Rectangle(50.0f, 50.0f)); } protected override IEnumerable> CreateManyItemEnumerable() { return GenerateQuadTree(); } } [TestFixture, Description("Tests the ICollection and IEnumerable " + "portions of QuadTree.")] public class CollectionTests : CollectionTests { /// /// Initializes a new instance of the /// class. /// public CollectionTests() : base(false) { } protected override ICollection CreateCollection() { return GenerateQuadTree(); } protected override ICollection CreateReadOnlyCollection() { return CreateCollection(); } protected override Rectangle CreateItem() { return new Rectangle(3.5f, 4.3f, 5, 5); } } [TestFixture, Description("Tests the IExtendedCollidable and ICollidable " + "portions of QuadTree.")] public class ExtendedCollidableTests : ExtendedCollidableTests { protected override IExtendedCollidable CreateExtendedCollidable() { return GenerateQuadTree(); } protected override Rectangle CreateCollidingCollidable() { return new Rectangle(40, 67, 3, 5); } protected override Rectangle CreateEdgeCollidingCollidable() { return new Rectangle(47, 7, 1, 1); } protected override Rectangle CreateNonCollidingCollidable() { return new Rectangle(); } } } }