Files
Christopher Whitley 3a5269cfea Reorganization (#909)
* Removed MonoGame.Extended.Animations
This project was just a shell that referenced MonoGame.Extended.csproj. It added no additional functionality.

* Rework BitmapFonts for new project organization.

* Cleanup namespaces

* Cleanup namespaces

* Move MonoGame.Extended.Collision into base project

* Moved MonoGame.Extended.Entities into base project

* Moved MonoGame.Extended.Graphics into base project

* Moved MonoGame.Extended.Graphics into base project

* Moved MonoGame.Extended.Input into base project

* Moved MonoGame.Extended.Particles into base project

* Moved MonoGame.Extended.Tiled into base project

* Moved MonoGame.Extended.Tiled into base project

* Moved MonoGame.Extended.Tweening into base project

* Removing SpriteFactory support
This will be added back in at a later date once the SpriteFactory application has been updated

* Update TexturePacker content import support

* Only needs to reference base project now

* Moved to Serialization namespace

* Moved  Json serialization to Serialization.Json namespace

* Moved TexturePacker content DTOs to the Content namespace

* Renamed to Texture2DRegion.Extensions.cs

* Cleaned up namespace

* Moved ReadTiledMapProperties into the ContentReaderExtensions

* Created an Extended content manager

* Moved Tweening tests to base test project

* Moved Tiled test to base test project

* Moved Entities Tests to base test project

* Moved Entities tests to base test project

* Moved Collisions tests to base test project

* Moved Pipeline Tiled test to Pipeline tests project

* Use `var` instead of full type name

* Add Tiled namespace

* Correct `Metadata` property name to new `Meta` property name

* Cleanup namespace

* Add effects namespace

* Add Content namespace

* Update using statements to new namespaces

* Move Pipeline Tiled tests to Pipeline test project

* Use correct namespace

* Use `var` instead of typing out nested types

* Remove unused namespaces

* Update to new namespaces

* Remove unused namespaces

* Update to new namespaces

* Remove dependency on Particles csproj

* Update included content on build

* Add embedded resources

* Merge multi csproj structure into single csproj structure

* TexturePackerPoint properties should be `double` not `int`

* Update csproj

* Use dotnet tool to rebuild effects

* Move effect compilation to targets file
This will ensure the effects are always built

* Fix workflow to build effects on linux

* Set wine path

* Export instead of using actions env section

* Revert back to manual effect compile until automation can be solved on GitHub Ubuntu runner
2024-07-06 23:46:20 -04:00

417 lines
14 KiB
C#

using System;
using Microsoft.Xna.Framework;
using Xunit;
namespace MonoGame.Extended.Collisions.Tests
{
using MonoGame.Extended.Collisions.Layers;
/// <summary>
/// Test collision of actors with various shapes.
/// </summary>
/// <remarks>
/// Uses the fact that <see cref="BasicActor"/> moves itself away from
/// <see cref="BasicWall"/> on collision.
/// </remarks>
public class CollisionComponentTests
{
private readonly CollisionComponent _collisionComponent;
private readonly GameTime _gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromMilliseconds(16));
public CollisionComponentTests()
{
_collisionComponent = new CollisionComponent(new RectangleF(Vector2.Zero, new Vector2(10, 10)));
}
#region Circle Circle
[Fact]
public void PenetrationVectorSameCircleTest()
{
Vector2 pos1 = Vector2.Zero;
Vector2 pos2 = Vector2.Zero;
IShapeF shape1 = new CircleF(pos1, 2.0f);
IShapeF shape2 = new CircleF(pos2, 2.0f);
var actor1 = new BasicActor()
{
Position = pos1,
Bounds = shape1
};
var actor2 = new BasicWall()
{
Position = pos2,
Bounds = shape2
};
Assert.True(shape1.Intersects(shape2));
_collisionComponent.Insert(actor1);
_collisionComponent.Insert(actor2);
_collisionComponent.Update(_gameTime);
Assert.True(Math.Abs(actor1.Position.Y - -4f) < float.Epsilon);
}
[Fact]
public void PenetrationVectorSlightlyOverlappingCircleTest()
{
Vector2 pos1 = new Vector2(0, 1.5f);
Vector2 pos2 = Vector2.Zero;
IShapeF shape1 = new CircleF(pos1, 2.0f);
IShapeF shape2 = new CircleF(pos2, 2.0f);
var actor1 = new BasicActor()
{
Position = pos1,
Bounds = shape1
};
var actor2 = new BasicWall()
{
Position = pos2,
Bounds = shape2
};
Assert.True(shape1.Intersects(shape2));
_collisionComponent.Insert(actor1);
_collisionComponent.Insert(actor2);
_collisionComponent.Update(_gameTime);
// Actor should have moved up because the distance is shorter.
Assert.True(actor1.Position.Y > actor2.Position.Y);
// The circle centers should be about 4 units away after moving
Assert.True(Math.Abs(actor1.Position.Y - 4.0f) < float.Epsilon);
}
[Fact]
public void PenetrationVectorSlightlyOverlappingOffAxisTest()
{
Vector2 pos1 = new Vector2(2, 2.5f);
Vector2 pos2 = new Vector2(2, 1);
IShapeF shape1 = new CircleF(pos1, 2.0f);
IShapeF shape2 = new CircleF(pos2, 2.0f);
var actor1 = new BasicActor()
{
Position = pos1,
Bounds = shape1
};
var actor2 = new BasicWall()
{
Position = pos2,
Bounds = shape2
};
Assert.True(shape1.Intersects(shape2));
_collisionComponent.Insert(actor1);
_collisionComponent.Insert(actor2);
_collisionComponent.Update(_gameTime);
// Actor should have moved up because the distance is shorter.
Assert.True(actor1.Position.Y > actor2.Position.Y);
// The circle centers should be about 4 units away after moving
Assert.True(Math.Abs(actor1.Position.Y - 5.0f) < float.Epsilon);
Assert.True(Math.Abs(actor1.Position.X - 2.0f) < float.Epsilon);
}
[Fact]
public void PenetrationZeroRadiusCircleCircleTest()
{
Vector2 pos1 = new Vector2(0, 1.5f);
Vector2 pos2 = Vector2.Zero;
IShapeF shape1 = new CircleF(pos1, 0);
IShapeF shape2 = new CircleF(pos2, 2.0f);
var actor1 = new BasicActor()
{
Position = pos1,
Bounds = shape1
};
var actor2 = new BasicWall()
{
Position = pos2,
Bounds = shape2
};
Assert.True(shape1.Intersects(shape2));
_collisionComponent.Insert(actor1);
_collisionComponent.Insert(actor2);
_collisionComponent.Update(_gameTime);
// Actor should have moved up because the distance is shorter.
Assert.True(actor1.Position.Y > actor2.Position.Y);
// The circle centers should be about 4 units away after moving
// Assert.True(Math.Abs(actor1.Position.Y - 2.0f) < float.Epsilon);
}
#endregion
#region Circle Rectangle
[Fact]
public void PenetrationVectorCircleRectangleTest()
{
Vector2 pos1 = new Vector2(0, 1);
Vector2 pos2 = new Vector2(-2, -1);
IShapeF shape1 = new CircleF(pos1, 2.0f);
IShapeF shape2 = new RectangleF(pos2, new SizeF(4, 2));
var actor1 = new BasicActor()
{
Position = pos1,
Bounds = shape1
};
var actor2 = new BasicWall()
{
Position = pos2,
Bounds = shape2
};
Assert.True(shape1.Intersects(shape2));
_collisionComponent.Insert(actor1);
_collisionComponent.Insert(actor2);
_collisionComponent.Update(_gameTime);
Assert.True(Math.Abs(actor1.Position.X - 0.0f) < float.Epsilon);
Assert.True(Math.Abs(actor1.Position.Y - 3.0f) < float.Epsilon);
}
[Fact]
public void PenetrationVectorCircleContainedInRectangleTest()
{
Vector2 pos1 = new Vector2(0, 0);
Vector2 pos2 = new Vector2(-2, -1);
IShapeF shape1 = new CircleF(pos1, 1.0f);
IShapeF shape2 = new RectangleF(pos2, new SizeF(4, 2));
var actor1 = new BasicActor()
{
Position = pos1,
Bounds = shape1
};
var actor2 = new BasicWall()
{
Position = pos2,
Bounds = shape2
};
Assert.True(shape1.Intersects(shape2));
_collisionComponent.Insert(actor1);
_collisionComponent.Insert(actor2);
_collisionComponent.Update(_gameTime);
Assert.True(Math.Abs(actor1.Position.X - 0.0f) < float.Epsilon);
Assert.True(Math.Abs(actor1.Position.Y - -2.0f) < float.Epsilon);
}
[Fact]
public void PenetrationVectorCircleOffAxisRectangleTest()
{
Vector2 pos1 = new Vector2(2, 1);
Vector2 pos2 = new Vector2(-2, -1);
IShapeF shape1 = new CircleF(pos1, 2.0f);
IShapeF shape2 = new RectangleF(pos2, new SizeF(4, 2));
var actor1 = new BasicActor()
{
Position = pos1,
Bounds = shape1
};
var actor2 = new BasicWall()
{
Position = pos2,
Bounds = shape2
};
Assert.True(shape1.Intersects(shape2));
_collisionComponent.Insert(actor1);
_collisionComponent.Insert(actor2);
_collisionComponent.Update(_gameTime);
Assert.True(Math.Abs(actor1.Position.X - 2.0f) < float.Epsilon);
Assert.True(Math.Abs(actor1.Position.Y - 3.0f) < float.Epsilon);
}
#endregion
#region Rectangle Rectangle
[Fact]
public void PenetrationVectorRectangleRectangleTest()
{
Vector2 pos1 = new Vector2(0, 0);
Vector2 pos2 = new Vector2(-2, -1);
IShapeF shape1 = new RectangleF(pos1, new SizeF(4, 2));
IShapeF shape2 = new RectangleF(pos2, new SizeF(4, 2));
var actor1 = new BasicActor()
{
Position = pos1,
Bounds = shape1
};
var actor2 = new BasicWall()
{
Position = pos2,
Bounds = shape2
};
Assert.True(shape1.Intersects(shape2));
_collisionComponent.Insert(actor1);
_collisionComponent.Insert(actor2);
_collisionComponent.Update(_gameTime);
Assert.True(Math.Abs(actor1.Position.X - 0.0f) < float.Epsilon);
Assert.True(Math.Abs(actor1.Position.Y - 1.0f) < float.Epsilon);
}
[Fact]
public void PenetrationVectorRectangleRectangleOffAxisTest()
{
Vector2 pos1 = new Vector2(4, 2);
Vector2 pos2 = new Vector2(3, 1);
IShapeF shape1 = new RectangleF(pos1, new SizeF(4, 2));
IShapeF shape2 = new RectangleF(pos2, new SizeF(4, 2));
var actor1 = new BasicActor()
{
Position = pos1,
Bounds = shape1
};
var actor2 = new BasicWall()
{
Position = pos2,
Bounds = shape2
};
Assert.True(shape1.Intersects(shape2));
_collisionComponent.Insert(actor1);
_collisionComponent.Insert(actor2);
_collisionComponent.Update(_gameTime);
Assert.True(Math.Abs(actor1.Position.X - 4.0f) < float.Epsilon);
Assert.True(Math.Abs(actor1.Position.Y - 3.0f) < float.Epsilon);
}
#endregion
public class Behaviours : CollisionComponentTests
{
[Fact]
public void Actors_is_colliding()
{
var staticBounds = new RectangleF(new Vector2(0, 0), new SizeF(1, 1));
var anotherStaticBounds = new RectangleF(new Vector2(0, 0), new SizeF(1, 1));
var staticActor = new CollisionIndicatingActor(staticBounds);
var anotherStaticActor = new CollisionIndicatingActor(anotherStaticBounds);
_collisionComponent.Insert(staticActor);
_collisionComponent.Insert(anotherStaticActor);
_collisionComponent.Update(_gameTime);
Assert.True(staticActor.IsColliding);
Assert.True(anotherStaticActor.IsColliding);
}
[Fact]
public void Actors_is_not_colliding_when_dynamic_actor_is_moved_out_of_collision_bounds()
{
var staticBounds = new RectangleF(new Vector2(0, 0), new SizeF(1, 1));
var dynamicBounds = new RectangleF(new Vector2(0, 0), new SizeF(1, 1));
var staticActor = new CollisionIndicatingActor(staticBounds);
var dynamicActor = new CollisionIndicatingActor(dynamicBounds);
_collisionComponent.Insert(staticActor);
_collisionComponent.Insert(dynamicActor);
dynamicActor.MoveTo(new Vector2(2, 2));
_collisionComponent.Update(_gameTime);
Assert.False(staticActor.IsColliding);
Assert.False(dynamicActor.IsColliding);
}
[Fact]
public void Actors_is_colliding_when_dynamic_actor_is_moved_after_update()
{
var staticBounds = new RectangleF(new Vector2(0, 0), new SizeF(1, 1));
var staticActor = new CollisionIndicatingActor(staticBounds);
_collisionComponent.Insert(staticActor);
for (int i = 0; i < QuadTree.QuadTree.DefaultMaxObjectsPerNode; i++)
{
var fillerBounds = new RectangleF(new Vector2(0, 2), new SizeF(.1f, .1f));
var fillerActor = new CollisionIndicatingActor(fillerBounds);
_collisionComponent.Insert(fillerActor);
}
var dynamicBounds = new RectangleF(new Vector2(2, 2), new SizeF(1, 1));
var dynamicActor = new CollisionIndicatingActor(dynamicBounds);
_collisionComponent.Insert(dynamicActor);
_collisionComponent.Update(_gameTime);
Assert.False(staticActor.IsColliding);
Assert.False(dynamicActor.IsColliding);
dynamicActor.MoveTo(new Vector2(0, 0));
_collisionComponent.Update(_gameTime);
Assert.True(dynamicActor.IsColliding);
Assert.True(staticActor.IsColliding);
}
[Fact]
public void Actors_is_colliding_when_dynamic_actor_is_moved_into_collision_bounds()
{
var staticBounds = new RectangleF(new Vector2(0, 0), new SizeF(1, 1));
var dynamicBounds = new RectangleF(new Vector2(2, 2), new SizeF(1, 1));
var staticActor = new CollisionIndicatingActor(staticBounds);
var dynamicActor = new CollisionIndicatingActor(dynamicBounds);
_collisionComponent.Insert(staticActor);
_collisionComponent.Insert(dynamicActor);
dynamicActor.MoveTo(new Vector2(0, 0));
_collisionComponent.Update(_gameTime);
Assert.True(staticActor.IsColliding);
Assert.True(dynamicActor.IsColliding);
}
[Fact]
public void InsertActor_ThrowsUndefinedLayerException_IfThereIsNoLayerDefined()
{
var sut = new CollisionComponent();
var act = () => sut.Insert(new CollisionIndicatingActor(RectangleF.Empty));
Assert.Throws<UndefinedLayerException>(act);
}
private class CollisionIndicatingActor : ICollisionActor
{
private RectangleF _bounds;
public CollisionIndicatingActor(RectangleF bounds)
{
_bounds = bounds;
}
public IShapeF Bounds => _bounds;
public void OnCollision(CollisionEventArgs collisionInfo)
{
IsColliding = true;
}
public bool IsColliding { get; private set; }
public void MoveTo(Vector2 position)
{
_bounds = new RectangleF(position, _bounds.Size);
}
}
}
}
}