This commit is contained in:
Dylan Wilson
2016-10-29 21:19:07 +10:00
20 changed files with 1034 additions and 123 deletions
@@ -55,3 +55,15 @@
/processorParam:TextureFormat=Color
/build:tiny-tiles.png
#begin tiny-grey-cloud.png
/importer:TextureImporter
/processor:TextureProcessor
/processorParam:ColorKeyColor=255,0,255,255
/processorParam:ColorKeyEnabled=True
/processorParam:GenerateMipmaps=False
/processorParam:PremultiplyAlpha=True
/processorParam:ResizeToPowerOfTwo=False
/processorParam:MakeSquare=False
/processorParam:TextureFormat=Color
/build:tiny-grey-cloud.png
@@ -11,9 +11,12 @@
<tileset firstgid="257" name="tiny-characters" tilewidth="32" tileheight="32" tilecount="128" columns="16">
<image source="tiny-characters.png" width="512" height="256"/>
</tileset>
<tileset firstgid="385" name="tiny-grey-cloud" tilewidth="32" tileheight="32" tilecount="1" columns="1">
<image source="tiny-grey-cloud.png" width="32" height="32"/>
</tileset>
<layer name="background" width="25" height="15">
<data encoding="base64">
LAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAuAAAALwAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAAC0AAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACsAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAArAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAAC4AAAAvAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAA
gQEAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAuAAAALwAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAAC0AAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAACBAQAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACsAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAArAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAAgQEAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAAIEBAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAAC4AAAAvAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAAIEBAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAA
</data>
</layer>
<layer name="decorations" width="25" height="15">
Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

@@ -62,6 +62,7 @@
</ItemGroup>
<ItemGroup>
<Content Include="Content\tiny-characters.png" />
<Content Include="Content\tiny-grey-cloud.png" />
<Content Include="Content\tiny-tiles.png" />
<Content Include="Icon.ico" />
</ItemGroup>
@@ -8,21 +8,19 @@ namespace Demo.SpaceGame.Entities
{
public class Explosion : Entity
{
private readonly SpriteSheetAnimator _animator;
private readonly Sprite _sprite;
private readonly AnimatedSprite _sprite;
public Explosion(SpriteSheetAnimationFactory animations, Vector2 position, float radius)
{
_animator = new SpriteSheetAnimator(animations);
_sprite = _animator.CreateSprite();
_sprite = new AnimatedSprite(animations);
_sprite.Position = position;
_sprite.Scale = Vector2.One*radius*0.2f;
_animator.Play("explode", Destroy);
_sprite.Play("explode", Destroy);
}
public override void Update(GameTime gameTime)
{
_animator.Update(gameTime);
_sprite.Update(gameTime);
}
public override void Draw(SpriteBatch spriteBatch)
@@ -27,8 +27,7 @@ namespace Demo.SpriteSheetAnimations
private Zombie _zombie;
private SpriteSheetAnimation _animation;
private Sprite _fireballSprite;
private SpriteSheetAnimator _motwAnimator;
private Sprite _motwSprite;
private AnimatedSprite _motwSprite;
public Game1()
{
@@ -86,9 +85,9 @@ namespace Demo.SpriteSheetAnimations
motwAnimationFactory.Add("walkWest", new SpriteSheetAnimationData(new[] { 12, 13, 14, 13 }, isLooping: false));
motwAnimationFactory.Add("walkEast", new SpriteSheetAnimationData(new[] { 24, 25, 26, 25 }, isLooping: false));
motwAnimationFactory.Add("walkNorth", new SpriteSheetAnimationData(new[] { 36, 37, 38, 37 }, isLooping: false));
_motwAnimator = new SpriteSheetAnimator(motwAnimationFactory);
_motwSprite = _motwAnimator.CreateSprite(new Vector2(350, 800));
_motwAnimator.Play("walkSouth").IsLooping = true;
_motwSprite = new AnimatedSprite(motwAnimationFactory);
_motwSprite.Position = new Vector2(350, 800);
_motwSprite.Play("walkSouth").IsLooping = true;
}
protected override void UnloadContent()
@@ -104,16 +103,16 @@ namespace Demo.SpriteSheetAnimations
// motw
if (keyboardState.IsKeyDown(Keys.W))
_motwAnimator.Play("walkNorth");
_motwSprite.Play("walkNorth");
if (keyboardState.IsKeyDown(Keys.A))
_motwAnimator.Play("walkWest");
_motwSprite.Play("walkWest");
if (keyboardState.IsKeyDown(Keys.S))
_motwAnimator.Play("walkSouth");
_motwSprite.Play("walkSouth");
if (keyboardState.IsKeyDown(Keys.D))
_motwAnimator.Play("walkEast");
_motwSprite.Play("walkEast");
// camera
if (keyboardState.IsKeyDown(Keys.R))
@@ -146,7 +145,7 @@ namespace Demo.SpriteSheetAnimations
_animation.Update(deltaSeconds);
_fireballSprite.TextureRegion = _animation.CurrentFrame;
_motwAnimator.Update(deltaSeconds);
_motwSprite.Update(deltaSeconds);
base.Update(gameTime);
}
@@ -156,7 +155,7 @@ namespace Demo.SpriteSheetAnimations
GraphicsDevice.Clear(Color.CornflowerBlue);
_spriteBatch.Begin(transformMatrix: _camera.GetViewMatrix());
_tiledMap.Draw(_camera);
_tiledMap.Draw(_camera.GetViewMatrix());
_zombie.Draw(_spriteBatch);
_spriteBatch.Draw(_fireballSprite);
_spriteBatch.End();
@@ -22,8 +22,7 @@ namespace Demo.SpriteSheetAnimations
public class Zombie : IUpdate, IActorTarget
{
private readonly SpriteSheetAnimator _animator;
private readonly Sprite _sprite;
private readonly AnimatedSprite _sprite;
private float _direction = -1.0f;
@@ -53,19 +52,19 @@ namespace Demo.SpriteSheetAnimations
switch (_state)
{
case ZombieState.Attacking:
_animator.Play("attack", () => State = ZombieState.Idle);
_sprite.Play("attack", () => State = ZombieState.Idle);
break;
case ZombieState.Dying:
_animator.Play("die", () => State = ZombieState.Appearing);
_sprite.Play("die", () => State = ZombieState.Appearing);
break;
case ZombieState.Idle:
_animator.Play("idle");
_sprite.Play("idle");
break;
case ZombieState.Appearing:
_animator.Play("appear", () => State = ZombieState.Idle);
_sprite.Play("appear", () => State = ZombieState.Idle);
break;
case ZombieState.Walking:
_animator.Play("walk", () => State = ZombieState.Idle);
_sprite.Play("walk", () => State = ZombieState.Idle);
break;
}
}
@@ -76,8 +75,7 @@ namespace Demo.SpriteSheetAnimations
public Zombie(SpriteSheetAnimationFactory animations)
{
_animator = new SpriteSheetAnimator(animations);
_sprite = _animator.CreateSprite();
_sprite = new AnimatedSprite(animations);
State = ZombieState.Appearing;
IsOnGround = false;
@@ -85,7 +83,7 @@ namespace Demo.SpriteSheetAnimations
public void Update(GameTime gameTime)
{
_animator.Update(gameTime);
_sprite.Update(gameTime);
IsOnGround = false;
+1 -1
View File
@@ -101,7 +101,7 @@ namespace Demo.TiledMaps
GraphicsDevice.Clear(Color.CornflowerBlue);
// you can draw the whole map all at once
_tiledMap.Draw(_camera);
_tiledMap.Draw(_camera.GetViewMatrix());
// or you can have more control over drawing each individual layer
//foreach (var layer in _tiledMap.Layers)
+15 -14
View File
@@ -7,7 +7,7 @@ namespace MonoGame.Extended.Tests
[TestFixture]
public class AngleTest
{
private const float DELTA = 0.0000001f;
private const double _delta = 0.000000000000001f;
[Test]
public void ConstructorTest()
{
@@ -16,27 +16,28 @@ namespace MonoGame.Extended.Tests
var degrees = new Angle(value, AngleType.Degree);
var gradians = new Angle(value, AngleType.Gradian);
var revolutions = new Angle(value, AngleType.Revolution);
Assert.AreEqual(value, radians.Radians, DELTA);
Assert.AreEqual(value, degrees.Degrees, DELTA);
Assert.AreEqual(value, gradians.Gradians, DELTA);
Assert.AreEqual(value, revolutions.Revolutions, DELTA);
Assert.AreEqual(value, radians.Radians, _delta);
Assert.AreEqual(value, degrees.Degrees, _delta);
Assert.AreEqual(value, gradians.Gradians, _delta);
Assert.AreEqual(value, revolutions.Revolutions, _delta);
}
[Test]
[Ignore("This test is broken on the build server and I have no idea why")]
public void ConversionTest()
{
//from radians
var radians = new Angle(MathHelper.Pi);
Assert.AreEqual(180d, radians.Degrees, DELTA);
Assert.AreEqual(200d, radians.Gradians, DELTA);
Assert.AreEqual(0.5, radians.Revolutions, DELTA);
Assert.AreEqual(180d, radians.Degrees, _delta);
Assert.AreEqual(200d, radians.Gradians, _delta);
Assert.AreEqual(0.5, radians.Revolutions, _delta);
//to radians
var degrees = new Angle(180f, AngleType.Degree);
var gradians = new Angle(200f, AngleType.Gradian);
var revolutions = new Angle(0.5f, AngleType.Revolution);
Assert.AreEqual(MathHelper.Pi, degrees.Radians, DELTA);
Assert.AreEqual(MathHelper.Pi, gradians.Radians, DELTA);
Assert.AreEqual(MathHelper.Pi, revolutions.Radians, DELTA);
Assert.AreEqual(MathHelper.Pi, degrees.Radians, _delta);
Assert.AreEqual(MathHelper.Pi, gradians.Radians, _delta);
Assert.AreEqual(MathHelper.Pi, revolutions.Radians, _delta);
}
[Test]
@@ -62,15 +63,15 @@ namespace MonoGame.Extended.Tests
public void VectorTest()
{
var angle = Angle.FromVector(Vector2.One);
Assert.AreEqual(-MathHelper.Pi / 4d, angle.Radians, DELTA);
Assert.AreEqual(-MathHelper.Pi / 4d, angle.Radians, _delta);
Assert.AreEqual(10f, angle.ToVector(10f).Length());
angle = Angle.FromVector(Vector2.UnitX);
Assert.AreEqual(0, angle.Radians, DELTA);
Assert.AreEqual(0, angle.Radians, _delta);
Assert.IsTrue(Vector2.UnitX.EqualsWithTolerence(angle.ToUnitVector()));
angle = Angle.FromVector(-Vector2.UnitY);
Assert.AreEqual(MathHelper.Pi / 2d, angle.Radians, DELTA);
Assert.AreEqual(MathHelper.Pi / 2d, angle.Radians, _delta);
Assert.IsTrue((-Vector2.UnitY).EqualsWithTolerence(angle.ToUnitVector()));
}
@@ -69,7 +69,7 @@
<Compile Include="Particles\ParticleBufferTests.cs" />
<Compile Include="Particles\Profiles\PointProfileTests.cs" />
<Compile Include="Particles\Profiles\RingProfileTests.cs" />
<Compile Include="Primitives\BoundingBox2DTests.cs" />
<Compile Include="Primitives\BoundingRectangleTests.cs" />
<Compile Include="Primitives\Point2Tests.cs" />
<Compile Include="Primitives\Ray2DTests.cs" />
<Compile Include="Primitives\Segment2DTests.cs" />
@@ -0,0 +1,397 @@
using System.Collections.Generic;
using System.Globalization;
using Microsoft.Xna.Framework;
using MonoGame.Extended.Primitives;
using NUnit.Framework;
namespace MonoGame.Extended.Tests.Primitives
{
[TestFixture]
public class BoundingRectangleTests
{
public IEnumerable<TestCaseData> ConstructorTestCases
{
get
{
yield return
new TestCaseData(new Point2(), new Size2()).SetName(
"The empty bounding rectangle has the expected position and direction.");
yield return
new TestCaseData(new Point2(5, 5), new Size2(15, 15)).SetName(
"A non-empty bounding rectangle has the expected position and direction.");
}
}
[Test]
[TestCaseSource(nameof(ConstructorTestCases))]
public void Constructor(Point2 centre, Size2 radius)
{
var boundingRectangle = new BoundingRectangle(centre, radius);
Assert.AreEqual(centre, boundingRectangle.Centre);
Assert.AreEqual(radius, boundingRectangle.Radius);
}
public IEnumerable<TestCaseData> CreateFromMinimumMaximumTestCases
{
get
{
yield return
new TestCaseData(new Point2(), new Point2(), new BoundingRectangle()).SetName(
"The bounding rectangle created from the zero minimum point and zero maximum point is the empty bounding rectangle.")
;
yield return
new TestCaseData(new Point2(5, 5), new Point2(15, 15),
new BoundingRectangle(new Point2(10, 10), new Size2(5, 5))).SetName(
"The bounding rectangle created from the non-zero minimum point and the non-zero maximum point is the expected bounding rectangle.")
;
}
}
[Test]
[TestCaseSource(nameof(CreateFromMinimumMaximumTestCases))]
public void CreateFromMinimumMaximum(Point2 minimum, Point2 maximum, BoundingRectangle expectedBoundingRectangle)
{
var actualBoundingRectangle = BoundingRectangle.CreateFrom(minimum, maximum);
Assert.AreEqual(expectedBoundingRectangle, actualBoundingRectangle);
}
public IEnumerable<TestCaseData> CreateFromPointsTestCases
{
get
{
yield return
new TestCaseData(null, new BoundingRectangle()).SetName(
"The bounding rectangle created from null points is the empty bounding rectangle.");
yield return
new TestCaseData(new Point2[0], new BoundingRectangle()).SetName(
"The bounding rectangle created from the empty set of points is the empty bounding rectangle.");
yield return
new TestCaseData(
new[]
{
new Point2(5, 5), new Point2(10, 10), new Point2(15, 15), new Point2(-5, -5),
new Point2(-15, -15)
}, new BoundingRectangle(new Point2(0, 0), new Size2(15, 15))).SetName(
"The bounding rectangle created from a non-empty set of points is the expected bounding rectangle.");
}
}
[Test]
[TestCaseSource(nameof(CreateFromPointsTestCases))]
public void CreateFromPoints(Point2[] points, BoundingRectangle expectedBoundingRectangle)
{
var actualBoundingRectangle = BoundingRectangle.CreateFrom(points);
Assert.AreEqual(expectedBoundingRectangle, actualBoundingRectangle);
}
public IEnumerable<TestCaseData> CreateFromTransformedTestCases
{
get
{
yield return
new TestCaseData(new BoundingRectangle(), Matrix2D.Identity, new BoundingRectangle()).SetName(
"The bounding rectangle created from the empty bounding rectangle transformed by the identity matrix is the empty bounding rectangle.")
;
yield return
new TestCaseData(new BoundingRectangle(new Point2(0, 0), new Size2(20, 20)), Matrix2D.CreateScale(2), new BoundingRectangle(new Point2(0, 0), new Size2(40, 40))).SetName(
"The bounding rectangle created from a non-empty bounding rectangle transformed by a non-identity matrix is the expected bounding rectangle.")
;
}
}
[Test]
[TestCaseSource(nameof(CreateFromTransformedTestCases))]
public void CreateFromTransformed(BoundingRectangle boundingRectangle, Matrix2D transformMatrix,
BoundingRectangle expectedBoundingRectangle)
{
var actualBoundingRectangle = BoundingRectangle.CreateFrom(boundingRectangle, ref transformMatrix);
Assert.AreEqual(expectedBoundingRectangle, actualBoundingRectangle);
}
public IEnumerable<TestCaseData> UnionTestCases
{
get
{
yield return
new TestCaseData(new BoundingRectangle(), new BoundingRectangle(), new BoundingRectangle()).SetName(
"The union of two empty bounding rectangles is the empty bounding rectangle.");
yield return
new TestCaseData(new BoundingRectangle(new Point2(0, 0), new Size2(15, 15)),
new BoundingRectangle(new Point2(20, 20), new Size2(40, 40)), new BoundingRectangle(new Point2(20, 20), new Size2(40, 40)))
.SetName(
"The union of two non-empty bounding rectangles is the expected bounding rectangle.");
}
}
[Test]
[TestCaseSource(nameof(UnionTestCases))]
public void Union(BoundingRectangle boundingRectangle1, BoundingRectangle boundingRectangle2, BoundingRectangle expectedBoundingRectangle)
{
Assert.AreEqual(expectedBoundingRectangle, boundingRectangle1.Union(boundingRectangle2));
Assert.AreEqual(expectedBoundingRectangle, BoundingRectangle.Union(boundingRectangle1, boundingRectangle2));
}
public IEnumerable<TestCaseData> IntersectionTestCases
{
get
{
yield return
new TestCaseData(new BoundingRectangle(), new BoundingRectangle(), new BoundingRectangle()).SetName(
"The intersection of two empty bounding rectangles is the empty bounding box.");
yield return
new TestCaseData(new BoundingRectangle(new Point2(-10, -10), new Size2(15, 15)),
new BoundingRectangle(new Point2(20, 20), new Size2(40, 40)),
new BoundingRectangle(new Point2(-7.5f, -7.5f), new Size2(12.5f, 12.5f))).SetName(
"The intersection of two overlapping non-empty bounding rectangles is the expected bounding rectangle.");
yield return
new TestCaseData(new BoundingRectangle(new Point2(-30, -30), new Size2(15, 15)),
new BoundingRectangle(new Point2(20, 20), new Size2(10, 10)),
null).SetName(
"The intersection of two non-overlapping non-empty bounding rectangles is null.");
}
}
[Test]
[TestCaseSource(nameof(IntersectionTestCases))]
public void Intersection(BoundingRectangle boundingRectangle1, BoundingRectangle boundingRectangle2,
BoundingRectangle? expectedBoundingRectangle)
{
Assert.AreEqual(expectedBoundingRectangle, boundingRectangle1.Intersection(boundingRectangle2));
Assert.AreEqual(expectedBoundingRectangle, BoundingRectangle.Intersection(boundingRectangle1, boundingRectangle2));
}
public IEnumerable<TestCaseData> IntersectsTestCases
{
get
{
yield return
new TestCaseData(new BoundingRectangle(), new BoundingRectangle(), true).SetName(
"Two empty bounding rectangles intersect.");
yield return
new TestCaseData(new BoundingRectangle(new Point2(-10, -10), new Size2(15, 15)),
new BoundingRectangle(new Point2(20, 20), new Size2(40, 40)), true).SetName(
"Two overlapping non-empty bounding rectangles intersect.");
yield return
new TestCaseData(new BoundingRectangle(new Point2(-40, -50), new Size2(15, 15)),
new BoundingRectangle(new Point2(20, 20), new Size2(15, 15)), false).SetName(
"Two non-overlapping non-empty bounding rectangles do not intersect.");
}
}
[Test]
[TestCaseSource(nameof(IntersectsTestCases))]
public void Intersects(BoundingRectangle boundingRectangle1, BoundingRectangle boundingRectangle2, bool expectedToIntersect)
{
Assert.AreEqual(expectedToIntersect, boundingRectangle1.Intersects(boundingRectangle2));
Assert.AreEqual(expectedToIntersect, BoundingRectangle.Intersects(boundingRectangle1, boundingRectangle2));
}
public IEnumerable<TestCaseData> ContainsPointTestCases
{
get
{
yield return
new TestCaseData(new BoundingRectangle(), new Point2(), true).SetName(
"The empty bounding rectangle contains the zero point.");
yield return
new TestCaseData(new BoundingRectangle(new Point2(0, 0), new Size2(15, 15)), new Point2(-15, -15), true)
.SetName(
"A non-empty bounding rectangle contains a point inside it.");
yield return
new TestCaseData(new BoundingRectangle(new Point2(0, 0), new Size2(15, 15)), new Point2(-16, 15), false)
.SetName(
"A non-empty bounding rectangle does not contain a point outside it.");
}
}
[Test]
[TestCaseSource(nameof(ContainsPointTestCases))]
public void ContainsPoint(BoundingRectangle boundingRectangle, Point2 point, bool expectedToContainPoint)
{
Assert.AreEqual(expectedToContainPoint, boundingRectangle.Contains(point));
Assert.AreEqual(expectedToContainPoint, BoundingRectangle.Contains(boundingRectangle, point));
}
public IEnumerable<TestCaseData> ClosestPointTestCases
{
get
{
yield return
new TestCaseData(new BoundingRectangle(), new Point2(), new Point2()).SetName(
"The closest point on the empty bounding rectangle to the zero point is the zero point.");
yield return
new TestCaseData(new BoundingRectangle(new Point2(0, 0), new Point2(50, 50)), new Point2(25, 25),
new Point2(25, 25)).SetName(
"The closest point on a non-empty bounding rectangle to a point which is inside the bounding rectangle is that point.")
;
yield return
new TestCaseData(new BoundingRectangle(new Point2(0, 0), new Point2(50, 50)), new Point2(400, 0),
new Point2(50, 0)).SetName(
"The closest point on a non-empty bounding rectangle to a point which is outside the bounding rectangle is the expected point.")
;
}
}
[Test]
[TestCaseSource(nameof(ClosestPointTestCases))]
public void ClosestPoint(BoundingRectangle boundingRectangle, Point2 point, Point2 expectedClosestPoint)
{
var actualClosestPoint = boundingRectangle.ClosestPointTo(point);
Assert.AreEqual(expectedClosestPoint, actualClosestPoint);
}
public IEnumerable<TestCaseData> EqualityTestCases
{
get
{
yield return
new TestCaseData(new BoundingRectangle(), new BoundingRectangle(), true).SetName(
"Empty bounding rectangles are equal.")
;
yield return
new TestCaseData(
new BoundingRectangle(new Point2(0, 0), new Size2(float.MaxValue, float.MinValue)),
new BoundingRectangle(new Point2(0, 0),
new Point2(float.MinValue, float.MaxValue)), false).SetName(
"Two different non-empty bounding rectangles are not equal.");
yield return
new TestCaseData(
new BoundingRectangle(new Point2(0, 0), new Size2(float.MinValue, float.MaxValue)),
new BoundingRectangle(new Point2(0, 0),
new Size2(float.MinValue, float.MaxValue)), true).SetName(
"Two identical non-empty bounding rectangles are equal.");
}
}
[Test]
[TestCaseSource(nameof(EqualityTestCases))]
public void Equality(BoundingRectangle boundingRectangle1, BoundingRectangle boundingRectangle2, bool expectedToBeEqual)
{
Assert.IsTrue(Equals(boundingRectangle1, boundingRectangle2) == expectedToBeEqual);
Assert.IsTrue(boundingRectangle1 == boundingRectangle2 == expectedToBeEqual);
Assert.IsFalse(boundingRectangle1 == boundingRectangle2 != expectedToBeEqual);
Assert.IsTrue(boundingRectangle1.Equals(boundingRectangle2) == expectedToBeEqual);
if (expectedToBeEqual)
Assert.AreEqual(boundingRectangle1.GetHashCode(), boundingRectangle2.GetHashCode());
}
public IEnumerable<TestCaseData> InequalityTestCases
{
get
{
yield return
new TestCaseData(new BoundingRectangle(), null, false).SetName(
"A bounding rectangle is not equal to a null object.");
yield return
new TestCaseData(new BoundingRectangle(), new object(), false).SetName(
"A bounding rectangle is not equal to an instantiated object.");
}
}
[Test]
[TestCaseSource(nameof(InequalityTestCases))]
public void Inequality(BoundingRectangle boundingRectangle, object obj, bool expectedToBeEqual)
{
Assert.IsTrue(boundingRectangle.Equals(obj) == expectedToBeEqual);
}
public IEnumerable<TestCaseData> HashCodeTestCases
{
get
{
yield return
new TestCaseData(new BoundingRectangle(), new BoundingRectangle(), true).SetName(
"Two empty bounding rectangles have the same hash code.");
yield return
new TestCaseData(new BoundingRectangle(new Point2(0, 0), new Size2(50, 50)),
new BoundingRectangle(new Point2(0, 0), new Size2(50, 50)), true).SetName(
"Two indentical non-empty bounding rectangles have the same hash code.");
yield return
new TestCaseData(new BoundingRectangle(new Point2(0, 0), new Size2(50, 50)),
new BoundingRectangle(new Point2(50, 50), new Size2(50, 50)), false).SetName(
"Two different non-empty bounding rectangles do not have the same hash code.");
}
}
[Test]
[TestCaseSource(nameof(HashCodeTestCases))]
public void HashCode(BoundingRectangle boundingRectangle1, BoundingRectangle boundingRectangle2, bool expectedThatHashCodesAreEqual)
{
var hashCode1 = boundingRectangle1.GetHashCode();
var hashCode2 = boundingRectangle2.GetHashCode();
if (expectedThatHashCodesAreEqual)
Assert.AreEqual(hashCode1, hashCode2);
else
Assert.AreNotEqual(hashCode1, hashCode2);
}
public IEnumerable<TestCaseData> ToRectangleTestCases
{
get
{
yield return
new TestCaseData(new BoundingRectangle(), new Rectangle()).SetName(
"The empty bounding rectangle point converted to a rectangle is the empty rectangle.");
yield return
new TestCaseData(new BoundingRectangle(new Point2(25, 25), new Size2(25, 25)),
new Rectangle(0, 0, 50, 50)).SetName(
"A non-empty bounding rectangle converted to a rectangle is the expected rectangle.");
}
}
[Test]
[TestCaseSource(nameof(ToRectangleTestCases))]
public void ToRectangle(BoundingRectangle boundingRectangle, Rectangle expectedRectangle)
{
var actualRectangle = (Rectangle)boundingRectangle;
Assert.AreEqual(expectedRectangle, actualRectangle);
}
public IEnumerable<TestCaseData> FromRectangleTestCases
{
get
{
yield return
new TestCaseData(new Rectangle(), new BoundingRectangle()).SetName(
"The empty rectangle converted to a bounding rectangle is the empty bounding rectangle.");
yield return
new TestCaseData(new Rectangle(0, 0, 50, 50),
new BoundingRectangle(new Point2(25, 25), new Size2(25, 25))).SetName(
"A non-empty rectangle converted to a bounding rectangle is the expected bounding rectangle.");
}
}
[Test]
[TestCaseSource(nameof(FromRectangleTestCases))]
public void FromRectangle(Rectangle rectangle, BoundingRectangle expectedBoundingRectangle)
{
var actualBoundingRectangle = (BoundingRectangle)rectangle;
Assert.AreEqual(expectedBoundingRectangle, actualBoundingRectangle);
}
public IEnumerable<TestCaseData> StringCases
{
get
{
yield return
new TestCaseData(new BoundingRectangle(),
string.Format(CultureInfo.CurrentCulture, "Centre: {0}, Radius: {1}", new Point2(),
new Size2())).SetName(
"The empty bounding rectangle has the expected string representation using the current culture.");
yield return new TestCaseData(new BoundingRectangle(new Point2(5.1f, -5.123f), new Size2(5.4f, -5.4123f)),
string.Format(CultureInfo.CurrentCulture, "Centre: {0}, Radius: {1}", new Point2(5.1f, -5.123f),
new Size2(5.4f, -5.4123f))).SetName(
"A non-empty bounding rectangle has the expected string representation using the current culture.");
}
}
[Test]
[TestCaseSource(nameof(StringCases))]
public void String(BoundingRectangle boundingRectangle, string expectedString)
{
var actualString = boundingRectangle.ToString();
Assert.AreEqual(expectedString, actualString);
}
}
}
@@ -62,36 +62,36 @@ namespace MonoGame.Extended.Tests.Primitives
Assert.AreEqual(new Vector2(-10.123f, 10.123f), ray.Direction);
}
public IEnumerable<TestCaseData> IntersectsBoundingBoxTestCases
public IEnumerable<TestCaseData> IntersectsBoundingRectangleTestCases
{
get
{
yield return
new TestCaseData(new Ray2D(), new BoundingBox2D(), true, Point2.Zero, Point2.Zero).SetName(
new TestCaseData(new Ray2D(), new BoundingRectangle(), true, Point2.Zero, Point2.Zero).SetName(
"The degenerate ray intersects the empty bounding box.");
yield return
new TestCaseData(new Ray2D(new Point2(-75, -75), new Vector2(75, -75)),
new BoundingBox2D(0, 0, 50, 50), false, Point2.NaN, Point2.NaN).SetName(
new BoundingRectangle(new Point2(), new Size2(50, 50)), false, Point2.NaN, Point2.NaN).SetName(
"A non-degenerate ray that does not cross a non-empty bounding box does not intersect the bounding box.");
yield return
new TestCaseData(new Ray2D(new Point2(0, 0), new Vector2(25, 0)), new BoundingBox2D(0, 0, 50, 50),
new TestCaseData(new Ray2D(new Point2(0, 0), new Vector2(25, 0)), new BoundingRectangle(new Point2(), new Size2(50, 50)),
true, new Point2(0, 0), new Point2(50, 0)).SetName(
"A non-degenerate ray starting from inside a non-empty bounding box intersects the bounding box.");
yield return
new TestCaseData(new Ray2D(new Point2(-100, 0), new Vector2(100, 0)),
new BoundingBox2D(0, 0, 50, 50),
new BoundingRectangle(new Point2(), new Size2(50, 50)),
true, new Point2(-50, 0), new Point2(50, 0)).SetName(
"A non-degenerate ray crossing a non-empty bounding box intersects the bounding box.");
}
}
[Test]
[TestCaseSource(nameof(IntersectsBoundingBoxTestCases))]
public void IntersectsBoundingBox(Ray2D ray, BoundingBox2D axisAlignedBoundingBox, bool expectedResult,
[TestCaseSource(nameof(IntersectsBoundingRectangleTestCases))]
public void IntersectsBoundingRectangle(Ray2D ray, BoundingRectangle boundingRectangle, bool expectedResult,
Point2 firstExpectedIntersectionPoint, Point2 secondExpectedIntersectionPoint)
{
float rayNearDistance, rayFarDistance;
var actualResult = ray.Intersects(axisAlignedBoundingBox, out rayNearDistance, out rayFarDistance);
var actualResult = ray.Intersects(boundingRectangle, out rayNearDistance, out rayFarDistance);
Assert.AreEqual(expectedResult, actualResult);
if (actualResult)
@@ -98,36 +98,36 @@ namespace MonoGame.Extended.Tests.Primitives
Assert.AreEqual(expectedDistance, actualDistance);
}
public IEnumerable<TestCaseData> IntersectsBoundingBoxTestCases
public IEnumerable<TestCaseData> IntersectsBoundingRectangleTestCases
{
get
{
yield return
new TestCaseData(new Segment2D(), new BoundingBox2D(), true, Point2.Zero).SetName(
new TestCaseData(new Segment2D(), new BoundingRectangle(), true, Point2.Zero).SetName(
"The empty segment intersects the empty bounding box.");
yield return
new TestCaseData(new Segment2D(new Point2(-75, -75), new Point2(75, -75)),
new BoundingBox2D(0, 0, 50, 50), false, Point2.NaN).SetName(
new BoundingRectangle(new Point2(), new Size2(50, 50)), false, Point2.NaN).SetName(
"A non-empty segment outside a non-empty bounding box does not intersect the bounding box.");
yield return
new TestCaseData(new Segment2D(new Point2(0, 0), new Point2(25, 0)), new BoundingBox2D(0, 0, 50, 50),
new TestCaseData(new Segment2D(new Point2(0, 0), new Point2(25, 0)), new BoundingRectangle(new Point2(), new Size2(50, 50)),
true, new Point2(0, 0)).SetName(
"A non-empty segment inside a non-empty bounding box intersects the bounding box.");
yield return
new TestCaseData(new Segment2D(new Point2(-100, 0), new Point2(100, 0)),
new BoundingBox2D(0, 0, 50, 50),
new BoundingRectangle(new Point2(), new Size2(50, 50)),
true, new Point2(-50, 0)).SetName(
"A non-empty segment crossing a non-empty bounding box intersects the bounding box.");
}
}
[Test]
[TestCaseSource(nameof(IntersectsBoundingBoxTestCases))]
public void IntersectsBoundingBox(Segment2D segment, BoundingBox2D axisAlignedBoundingBox, bool expectedResult,
[TestCaseSource(nameof(IntersectsBoundingRectangleTestCases))]
public void IntersectsBoundingRectangle(Segment2D segment, BoundingRectangle boundingRectangle, bool expectedResult,
Point2 expectedIntersectionPoint)
{
Point2 actualIntersectionPoint;
var actualResult = segment.Intersects(axisAlignedBoundingBox, out actualIntersectionPoint);
var actualResult = segment.Intersects(boundingRectangle, out actualIntersectionPoint);
Assert.AreEqual(expectedResult, actualResult);
if (actualResult)
@@ -44,28 +44,52 @@ namespace MonoGame.Extended.BitmapFonts
public Size GetSize(string text)
{
var width = 0;
var height = 0;
if (text == null) throw new ArgumentNullException(nameof(text));
var totalWidth = 0;
var lineWidth = 0;
var totalHeight = 0;
var lineHeight = 0;
const int newlineCodePoint = '\n';
var codePoints = GetUnicodeCodePoints(text).ToArray();
for (int i = 0, l = codePoints.Length; i < l; i++)
{
BitmapFontRegion fontRegion;
var c = codePoints[i];
var character = codePoints[i];
var nextCharacter = character;
if (_characterMap.TryGetValue(c, out fontRegion))
if (i < l - 1) nextCharacter = codePoints[i + 1];
if (_characterMap.TryGetValue(character, out fontRegion))
{
if (i != text.Length - 1)
width += fontRegion.XAdvance + LetterSpacing;
// Add LetterSpacing unless end of string or next character is not in _characterMap
if (i != text.Length - 1 && _characterMap.ContainsKey(nextCharacter))
lineWidth += fontRegion.XAdvance + LetterSpacing;
else
width += fontRegion.XOffset + fontRegion.Width;
lineWidth += fontRegion.XOffset + fontRegion.Width;
if (fontRegion.Height + fontRegion.YOffset > height)
height = fontRegion.Height + fontRegion.YOffset;
if (fontRegion.Height + fontRegion.YOffset > lineHeight)
lineHeight = fontRegion.Height + fontRegion.YOffset;
}
if (character == newlineCodePoint)
{
totalHeight += lineHeight;
if (totalWidth < lineWidth) totalWidth = lineWidth;
lineHeight = 0;
lineWidth = 0;
}
}
return new Size(width, height);
if (totalWidth == 0)
totalWidth = lineWidth;
totalHeight += lineHeight;
return new Size(totalWidth, totalHeight);
}
public Size MeasureString(string text)
+46 -20
View File
@@ -111,12 +111,12 @@ namespace MonoGame.Extended.Maps.Tiled
for (var i = 0; i < tilesCount; i++)
{
var thisTileIndexes = new short[6];
thisTileIndexes[0] = (short) (4*indexOffset);
thisTileIndexes[1] = (short) (4*indexOffset + 1);
thisTileIndexes[2] = (short) (4*indexOffset + 2);
thisTileIndexes[3] = (short) (4*indexOffset + 1);
thisTileIndexes[4] = (short) (4*indexOffset + 3);
thisTileIndexes[5] = (short) (4*indexOffset + 2);
thisTileIndexes[0] = (short)(4 * indexOffset);
thisTileIndexes[1] = (short)(4 * indexOffset + 1);
thisTileIndexes[2] = (short)(4 * indexOffset + 2);
thisTileIndexes[3] = (short)(4 * indexOffset + 1);
thisTileIndexes[4] = (short)(4 * indexOffset + 3);
thisTileIndexes[5] = (short)(4 * indexOffset + 2);
tileIndexes.AddRange(thisTileIndexes);
indexOffset++;
}
@@ -185,29 +185,39 @@ namespace MonoGame.Extended.Maps.Tiled
foreach (var layer in _layers)
{
var tiledTileLayer = layer as TiledTileLayer;
if (tiledTileLayer != null)
{
var tileLayer = tiledTileLayer;
var indexCount = tileLayer.NotBlankTilesCount * 6;
var primitivesCount = tileLayer.NotBlankTilesCount * 2;
var indexCount = 6;
var primitivesCount = 2;
if (tileLayer.IsVisible && tileLayer.NotBlankTilesCount > 0)
if (tileLayer.IsVisible)
{
if (_basicEffect.Texture != _tilesets[0].Texture)
_basicEffect.Texture = _tilesets[0].Texture;
pass.Apply();
_graphicsDevice.DrawIndexedPrimitives(
primitiveType: PrimitiveType.TriangleList,
baseVertex: 0,
startIndex: tilesIndexesSoFar,
primitiveCount: primitivesCount
);
foreach (var tileset in _tilesets)
{
var tilesToDraw = tiledTileLayer.Tiles.Count(x => tileset.Equals(GetTileSetByTileId(x.Id)));
if (tilesToDraw > 0)
{
if (_basicEffect.Texture != tileset.Texture)
_basicEffect.Texture = tileset.Texture;
pass.Apply();
_graphicsDevice.DrawIndexedPrimitives(
primitiveType: PrimitiveType.TriangleList,
baseVertex: 0,
startIndex: tilesIndexesSoFar,
primitiveCount: primitivesCount * tilesToDraw
);
tilesIndexesSoFar += indexCount * tilesToDraw;
}
}
}
tilesIndexesSoFar += indexCount;
}
else if (layer is TiledImageLayer)
{
@@ -230,6 +240,22 @@ namespace MonoGame.Extended.Maps.Tiled
}
}
}
TiledTileset GetTileSetByTileId(int id)
{
//if it's a blank tile there is no tileset
if (id == 0) return null;
int tilesetIndex;
//Loop backwards because they are in order of First Tile ID
for (tilesetIndex = _tilesets.Count - 1; tilesetIndex > 0; tilesetIndex--)
{
if (_tilesets[tilesetIndex].FirstId <= id)
break;
}
return _tilesets[tilesetIndex];
}
public TextureRegion2D GetTileRegion(int id)
{
@@ -17,10 +17,10 @@ namespace MonoGame.Extended.Maps.Tiled
_renderTargetSpriteBatch = new SpriteBatch(graphicsDevice);
_map = map;
_tiles = CreateTiles(data);
_tiles = CreateTiles(data, map.Tilesets);
_animatedTiles = new List<TiledTile>();
}
public override void Dispose()
{
_renderTargetSpriteBatch.Dispose();
@@ -43,22 +43,39 @@ namespace MonoGame.Extended.Maps.Tiled
public int TileWidth => _map.TileWidth;
public int TileHeight => _map.TileHeight;
private TiledTile[] CreateTiles(int[] data)
private TiledTile[] CreateTiles(int[] data, IReadOnlyList<TiledTileset> tilesets)
{
var tiles = new TiledTile[data.Length];
var index = 0;
var tileData = new TiledTile[data.Length];
var tiles = tilesets.Select(tileset => new List<TiledTile>()).ToList();
var tileDataIndex = 0;
for (var y = 0; y < Height; y++)
{
for (var x = 0; x < Width; x++)
{
tiles[x + y * Width] = new TiledTile(data[index], x, y, _map.GetTileSetTileById(data[index]));
index++;
int tilesetIndex;
//find which list to add it to
for (tilesetIndex = tilesets.Count - 1; tilesetIndex > 0; tilesetIndex--)
if (tilesets[tilesetIndex].FirstId <= data[tileDataIndex])
break;
tiles.ElementAt(tilesetIndex).Add(new TiledTile(data[tileDataIndex], x, y, _map.GetTileSetTileById(data[tileDataIndex])));
tileDataIndex++;
}
}
NotBlankTilesCount = tiles.Count(x => x.Id != 0);
return tiles;
//Place each Tile in order of their respective Tileset for optimal drawing
var tiledDataIndex = 0;
foreach (var tileChunk in tiles)
foreach (var tile in tileChunk)
{
tileData[tiledDataIndex] = tile;
tiledDataIndex++;
}
NotBlankTilesCount = tileData.Count(x => x.Id != 0);
return tileData;
}
internal VertexPositionTexture[] BuildVertices(float depth)
@@ -79,7 +96,7 @@ namespace MonoGame.Extended.Maps.Tiled
continue;
var region = _map.GetTileRegion(tile.Id);
var point = GetTileLocationFunction()(tile).ToVector2() + new Vector2(OffsetX, OffsetY);
var point = GetTileLocationFunction()(tile);
var tileWidth = region.Width;
var tileHeight = region.Height;
var tc0 = Vector2.Zero;
@@ -119,12 +136,12 @@ namespace MonoGame.Extended.Maps.Tiled
//var vr = visibleRectangle ?? new Rectangle(0, 0, _map.WidthInPixels, _map.HeightInPixels);
var vr = new Rectangle(0, 0, _map.WidthInPixels, _map.HeightInPixels);
var renderOrderFunction = GetRenderOrderFunction();
var firstCol = vr.Left < 0 ? 0 : (int) Math.Floor(vr.Left/(float) _map.TileWidth);
var firstRow = vr.Top < 0 ? 0 : (int) Math.Floor(vr.Top/(float) _map.TileHeight);
var firstCol = vr.Left < 0 ? 0 : (int)Math.Floor(vr.Left / (float)_map.TileWidth);
var firstRow = vr.Top < 0 ? 0 : (int)Math.Floor(vr.Top / (float)_map.TileHeight);
// +3 to cover any gaps
var columns = Math.Min(_map.Width, vr.Width/_map.TileWidth);
var rows = Math.Min(_map.Height, vr.Height/_map.TileHeight);
var columns = Math.Min(_map.Width, vr.Width / _map.TileWidth);
var rows = Math.Min(_map.Height, vr.Height / _map.TileHeight);
_renderTargetSpriteBatch.Begin(blendState: BlendState.AlphaBlend, samplerState: SamplerState.PointClamp);
@@ -144,14 +161,14 @@ namespace MonoGame.Extended.Maps.Tiled
_renderTargetSpriteBatch.End();
}
_uniqueTileSetTiles = _animatedTiles.Select(t => t.TileSetTile).Distinct().ToList();
spriteBatch.Draw(_renderTarget, Vector2.Zero, Color.White);
spriteBatch.Draw(_renderTarget, Vector2.Zero, Color.White);
}
else
{
spriteBatch.Draw(_renderTarget, Vector2.Zero, Color.White);
if (gameTime == null || _animatedTiles.Count == 0) return;
double deltaTime = gameTime.ElapsedGameTime.TotalMilliseconds;
foreach(var tileSetTile in _uniqueTileSetTiles)
foreach (var tileSetTile in _uniqueTileSetTiles)
{
tileSetTile.Update(deltaTime);
}
@@ -38,7 +38,7 @@
<ItemGroup>
<Compile Include="Animations\Animation.cs" />
<Compile Include="Animations\AnimationComponent.cs" />
<Compile Include="Primitives\BoundingBox2D.cs" />
<Compile Include="Primitives\BoundingRectangle.cs" />
<Compile Include="Primitives\Point2.cs" />
<Compile Include="Primitives\Ray2D.cs" />
<Compile Include="Primitives\RayHelper.cs" />
@@ -0,0 +1,435 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.Primitives
{
// Real-Time Collision Detection, Christer Ericson, 2005. Chapter 4.2; Bounding Volumes - Axis-aligned Bounding Boxes (AABBs). pg 77
/// <summary>
/// An axis-aligned, four sided, two dimensional box defined by a centre <see cref="Point2" /> and a radius <see cref="Size2" />.
/// </summary>
/// <remarks>
/// <para>
/// An <see cref="BoundingRectangle" /> is categorized by having its faces oriented in such a way that its
/// face normals are at all times parallel with the axes of the given coordinate system.
/// </para>
/// <para>
/// The <see cref="BoundingRectangle" /> of a rotated <see cref="BoundingRectangle" /> will be equivalent or larger in size
/// than the original depending on the angle of rotation.
/// </para>
/// </remarks>
/// <seealso cref="IEquatable{T}" />
/// <seealso cref="IEquatableByRef{T}" />
[DebuggerDisplay("{DebugDisplayString,nq}")]
public struct BoundingRectangle : IEquatable<BoundingRectangle>,
IEquatableByRef<BoundingRectangle>
{
/// <summary>
/// The centre position of this <see cref="BoundingRectangle" />.
/// </summary>
public Point2 Centre;
/// <summary>
/// The dimensions of this <see cref="BoundingRectangle" />.
/// </summary>
public Size2 Radius;
/// <summary>
/// Initializes a new instance of the <see cref="BoundingRectangle" /> structure from the specified centre
/// <see cref="Point2" /> and the radius <see cref="Size2" />.
/// </summary>
/// <param name="centre">The centre <see cref="Point2" />.</param>
/// <param name="radius">The radius <see cref="Size2" />.</param>
public BoundingRectangle(Point2 centre, Size2 radius)
{
Centre = centre;
Radius = radius;
}
/// <summary>
/// Computes the <see cref="BoundingRectangle" /> from a minimum <see cref="Point2" /> and maximum
/// <see cref="Point2" />.
/// </summary>
/// <param name="minimum">The minimum point.</param>
/// <param name="maximum">The maximum point.</param>
/// <returns>An <see cref="BoundingRectangle" />.</returns>
public static BoundingRectangle CreateFrom(Point2 minimum, Point2 maximum)
{
var centre = new Point2((maximum.X + minimum.X) * 0.5f, (maximum.Y + minimum.Y) * 0.5f);
var radius = new Size2((maximum.X - minimum.X) * 0.5f, (maximum.Y - minimum.Y) * 0.5f);
return new BoundingRectangle(centre, radius);
}
/// <summary>
/// Computes the <see cref="BoundingRectangle" /> from a list of <see cref="Point2" /> structures.
/// </summary>
/// <param name="points">The points.</param>
/// <returns>An <see cref="BoundingRectangle" />.</returns>
public static BoundingRectangle CreateFrom(IReadOnlyList<Point2> points)
{
// Real-Time Collision Detection, Christer Ericson, 2005. Chapter 4.2; Bounding Volumes - Axis-aligned Bounding Boxes (AABBs). pg 82-84
if ((points == null) || (points.Count == 0))
return new BoundingRectangle();
var minimum = new Vector2(float.MaxValue);
var maximum = new Vector2(float.MinValue);
// ReSharper disable once ForCanBeConvertedToForeach
for (var index = 0; index < points.Count; ++index)
{
var point = points[index];
minimum = Point2.Minimum(minimum, point);
maximum = Point2.Maximum(maximum, point);
}
return CreateFrom(minimum, maximum);
}
/// <summary>
/// Computes the <see cref="BoundingRectangle" /> from the specified <see cref="BoundingRectangle" /> transformed by the
/// specified <see cref="Matrix2D" />.
/// </summary>
/// <param name="boundingRectangle">The bounding rectangle.</param>
/// <param name="transformMatrix">The transform matrix.</param>
/// <returns>
/// The <see cref="BoundingRectangle" /> from the <paramref name="boundingRectangle" /> transformed by the
/// <paramref name="transformMatrix" />.
/// </returns>
/// <remarks>
/// <para>
/// If a transformed <see cref="BoundingRectangle" /> is used for <paramref name="boundingRectangle" /> then the
/// resulting <see cref="BoundingRectangle" /> will have the compounded transformation, which most likely is
/// not desired.
/// </para>
/// </remarks>
public static BoundingRectangle CreateFrom(BoundingRectangle boundingRectangle,
ref Matrix2D transformMatrix)
{
// Real-Time Collision Detection, Christer Ericson, 2005. Chapter 4.2; Bounding Volumes - Axis-aligned Bounding Boxes (AABBs). pg 86-87
var centre = transformMatrix.Transform(boundingRectangle.Centre);
var radius = boundingRectangle.Radius;
radius.Width = radius.Width * Math.Abs(transformMatrix.M11) + radius.Width * Math.Abs(transformMatrix.M12) +
radius.Width * Math.Abs(transformMatrix.M31);
radius.Height = radius.Height * Math.Abs(transformMatrix.M21) + radius.Height * Math.Abs(transformMatrix.M22) +
radius.Height * Math.Abs(transformMatrix.M32);
return new BoundingRectangle(centre, radius);
}
/// <summary>
/// Computes the <see cref="BoundingRectangle" /> that contains the two specified
/// <see cref="BoundingRectangle" /> structures.
/// </summary>
/// <param name="first">The first bounding rectangle.</param>
/// <param name="second">The second bounding rectangle.</param>
/// <returns>
/// An <see cref="BoundingRectangle" /> that contains both the <paramref name="first" /> and the
/// <paramref name="second" />.
/// </returns>
public static BoundingRectangle Union(BoundingRectangle first, BoundingRectangle second)
{
// Real-Time Collision Detection, Christer Ericson, 2005. Chapter 6.5; Bounding Volume Hierarchies - Merging Bounding Volumes. pg 267
var firstMinimum = first.Centre - first.Radius;
var firstMaximum = first.Centre + first.Radius;
var secondMinimum = second.Centre - second.Radius;
var secondMaximum = second.Centre + second.Radius;
var minimum = Point2.Minimum(firstMinimum, secondMinimum);
var maximum = Point2.Maximum(firstMaximum, secondMaximum);
return CreateFrom(minimum, maximum);
}
/// <summary>
/// Computes the <see cref="BoundingRectangle" /> that contains both the specified
/// <see cref="BoundingRectangle" /> and this <see cref="BoundingRectangle" />.
/// </summary>
/// <param name="boundingRectangle">The bounding rectangle.</param>
/// <returns>
/// An <see cref="BoundingRectangle" /> that contains both the <paramref name="boundingRectangle" /> and
/// this
/// <see cref="BoundingRectangle" />.
/// </returns>
public BoundingRectangle Union(BoundingRectangle boundingRectangle)
{
return Union(this, boundingRectangle);
}
/// <summary>
/// Computes the <see cref="Nullable{BoundingRectangle}" /> that is in common between the two specified
/// <see cref="BoundingRectangle" /> structures.
/// </summary>
/// <param name="first">The first bounding rectangle.</param>
/// <param name="second">The second bounding rectangle.</param>
/// <returns>
/// An <see cref="BoundingRectangle" /> that is in common between both the <paramref name="first" /> and
/// the <paramref name="second" />, if they intersect; otherwise, <code>null</code>.
/// </returns>
public static BoundingRectangle? Intersection(BoundingRectangle first,
BoundingRectangle second)
{
var firstMinimum = first.Centre - first.Radius;
var firstMaximum = first.Centre + first.Radius;
var secondMinimum = second.Centre - second.Radius;
var secondMaximum = second.Centre + second.Radius;
var minimum = Point2.Maximum(firstMinimum, secondMinimum);
var maximum = Point2.Minimum(firstMaximum, secondMaximum);
if ((maximum.X < minimum.X) || (maximum.Y < minimum.Y))
return null;
return CreateFrom(minimum, maximum);
}
/// <summary>
/// Updates this <see cref="BoundingRectangle" /> from a list of <see cref="Point2" /> structures.
/// </summary>
/// <param name="points">The points.</param>
public void UpdateFromPoints(IReadOnlyList<Point2> points)
{
var boundingRectangle = CreateFrom(points);
Centre = boundingRectangle.Centre;
Radius = boundingRectangle.Radius;
}
/// <summary>
/// Computes the <see cref="BoundingRectangle" /> that is in common between the specified
/// <see cref="BoundingRectangle" /> and this <see cref="BoundingRectangle" />.
/// </summary>
/// <param name="boundingRectangle">The bounding rectangle.</param>
/// <returns>
/// An <see cref="BoundingRectangle" /> that is in common between the specified
/// <see cref="BoundingRectangle" /> and this <see cref="BoundingRectangle" />.
/// </returns>
public BoundingRectangle? Intersection(BoundingRectangle boundingRectangle)
{
return Intersection(this, boundingRectangle);
}
/// <summary>
/// Determines whether the two specified <see cref="BoundingRectangle" /> structures intersect.
/// </summary>
/// <param name="first">The first bounding rectangle.</param>
/// <param name="second">The second bounding rectangle.</param>
/// <returns>
/// <c>true</c> if the <paramref name="first" /> intersects with the <see cref="second" />; otherwise, <c>false</c>.
/// </returns>
public static bool Intersects(BoundingRectangle first, BoundingRectangle second)
{
// Real-Time Collision Detection, Christer Ericson, 2005. Chapter 4.2; Bounding Volumes - Axis-aligned Bounding Boxes (AABBs). pg 80
var distance = first.Centre - second.Centre;
var radius = first.Radius + second.Radius;
return (Math.Abs(distance.X) <= radius.Width) && (Math.Abs(distance.Y) <= radius.Height);
}
/// <summary>
/// Determines whether the specified <see cref="BoundingRectangle" /> intersects with this
/// <see cref="BoundingRectangle" />.
/// </summary>
/// <param name="boundingRectangle">The bounding rectangle.</param>
/// <returns>
/// <c>true</c> if the <paramref name="boundingRectangle" /> intersects with this
/// <see cref="BoundingRectangle" />; otherwise,
/// <c>false</c>.
/// </returns>
public bool Intersects(BoundingRectangle boundingRectangle)
{
return Intersects(this, boundingRectangle);
}
/// <summary>
/// Determines whether the specified <see cref="BoundingRectangle" /> contains the specified
/// <see cref="Point2" />.
/// </summary>
/// <param name="boundingRectangle">The bounding rectangle.</param>
/// <param name="point">The point.</param>
/// <returns>
/// <c>true</c> if the <paramref name="boundingRectangle" /> contains the <paramref name="point" />; otherwise,
/// <c>false</c>.
/// </returns>
public static bool Contains(BoundingRectangle boundingRectangle, Point2 point)
{
// Real-Time Collision Detection, Christer Ericson, 2005. Chapter 4.2; Bounding Volumes - Axis-aligned Bounding Boxes (AABBs). pg 78
var distance = boundingRectangle.Centre - point;
var radius = boundingRectangle.Radius;
return (Math.Abs(distance.X) <= radius.Width) && (Math.Abs(distance.Y) <= radius.Height);
}
/// <summary>
/// Determines whether this <see cref="BoundingRectangle" /> contains the specified <see cref="Point2" />.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// <c>true</c> if this <see cref="BoundingRectangle" /> contains the <paramref name="point" />; otherwise,
/// <c>false</c>.
/// </returns>
public bool Contains(Point2 point)
{
return Contains(this, point);
}
/// <summary>
/// Computes the closest <see cref="Point2" /> on this <see cref="BoundingRectangle" /> to a specified <see cref="Point2" />.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>The closest <see cref="Point2" /> on this <see cref="BoundingRectangle" /> to the <paramref name="point" />.</returns>
public Point2 ClosestPointTo(Point2 point)
{
// Real-Time Collision Detection, Christer Ericson, 2005. Chapter 5.1.2; Basic Primitive Tests - Closest-point Computations. pg 130-131
var minimum = Centre - Radius;
var maximum = Centre + Radius;
var result = point;
// For each coordinate axis, if the point coordinate value is outside box, clamp it to the box, else keep it as is
if (result.X < minimum.X)
result.X = minimum.X;
else if (result.X > maximum.X)
result.X = maximum.X;
if (result.Y < minimum.Y)
result.Y = minimum.Y;
else if (result.Y > maximum.Y)
result.Y = maximum.Y;
return result;
}
/// <summary>
/// Compares two <see cref="BoundingRectangle" /> structures. The result specifies whether the values of the
/// <see cref="Centre" /> and <see cref="Radius" /> fields of the two <see cref="BoundingRectangle" /> structures
/// are equal.
/// </summary>
/// <param name="first">The first bounding rectangle.</param>
/// <param name="second">The second bounding rectangle.</param>
/// <returns>
/// <c>true</c> if the <see cref="Centre" /> and <see cref="Radius" /> fields of the two
/// <see cref="BoundingRectangle" /> structures are equal; otherwise, <c>false</c>.
/// </returns>
public static bool operator ==(BoundingRectangle first, BoundingRectangle second)
{
return first.Equals(ref second);
}
/// <summary>
/// Indicates whether this <see cref="BoundingRectangle" /> is equal to another
/// <see cref="BoundingRectangle" />.
/// </summary>
/// <param name="boundingRectangle">The bounding rectangle.</param>
/// <returns>
/// <c>true</c> if this <see cref="BoundingRectangle" /> is equal to the <paramref name="boundingRectangle" />;
/// otherwise, <c>false</c>.
/// </returns>
public bool Equals(BoundingRectangle boundingRectangle)
{
return Equals(ref boundingRectangle);
}
/// <summary>
/// Indicates whether this <see cref="BoundingRectangle" /> is equal to another <see cref="BoundingRectangle" />.
/// </summary>
/// <param name="boundingRectangle">The bounding rectangle.</param>
/// <returns>
/// <c>true</c> if this <see cref="BoundingRectangle" /> is equal to the <paramref name="boundingRectangle" />; otherwise,
/// <c>false</c>.
/// </returns>
public bool Equals(ref BoundingRectangle boundingRectangle)
{
return (boundingRectangle.Centre == Centre) && (boundingRectangle.Radius == Radius);
}
/// <summary>
/// Returns a value indicating whether this <see cref="BoundingRectangle" /> is equal to a specified object.
/// </summary>
/// <param name="obj">The object to make the comparison with.</param>
/// <returns>
/// <c>true</c> if this <see cref="BoundingRectangle" /> is equal to <paramref name="obj" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj)
{
if (obj is BoundingRectangle)
return Equals((BoundingRectangle)obj);
return false;
}
/// <summary>
/// Compares two <see cref="BoundingRectangle" /> structures. The result specifies whether the values of the
/// <see cref="Centre" /> and <see cref="Radius" /> fields of the two <see cref="BoundingRectangle" /> structures
/// are unequal.
/// </summary>
/// <param name="first">The first bounding box.</param>
/// <param name="second">The second bounding box.</param>
/// <returns>
/// <c>true</c> if the <see cref="Centre" /> and <see cref="Radius" /> fields of the two
/// <see cref="BoundingRectangle" /> structures are unequal; otherwise, <c>false</c>.
/// </returns>
public static bool operator !=(BoundingRectangle first, BoundingRectangle second)
{
return !(first == second);
}
/// <summary>
/// Returns a hash code of this <see cref="BoundingRectangle" /> suitable for use in hashing algorithms and data
/// structures like a hash table.
/// </summary>
/// <returns>
/// A hash code of this <see cref="BoundingRectangle" />.
/// </returns>
public override int GetHashCode()
{
unchecked
{
return (Centre.GetHashCode() * 397) ^ Radius.GetHashCode();
}
}
/// <summary>
/// Performs an implicit conversion from a <see cref="Rectangle" /> to a <see cref="BoundingRectangle" />.
/// </summary>
/// <param name="rectangle">The rectangle.</param>
/// <returns>
/// The resulting <see cref="BoundingRectangle" />.
/// </returns>
public static implicit operator BoundingRectangle(Rectangle rectangle)
{
var radius = new Size2(rectangle.Width / 2f, rectangle.Height / 2f);
var centre = new Point2(rectangle.X + radius.Width, rectangle.Y + radius.Height);
return new BoundingRectangle(centre, radius);
}
/// <summary>
/// Performs an implicit conversion from a <see cref="BoundingRectangle" /> to a <see cref="Rectangle" />.
/// </summary>
/// <param name="boundingRectangle">The bounding rectangle.</param>
/// <returns>
/// The resulting <see cref="Rectangle" />.
/// </returns>
public static implicit operator Rectangle(BoundingRectangle boundingRectangle)
{
var minimum = boundingRectangle.Centre - boundingRectangle.Radius;
return new Rectangle((int)minimum.X, (int)minimum.Y, (int)boundingRectangle.Radius.Width * 2, (int)boundingRectangle.Radius.Height * 2);
}
/// <summary>
/// Returns a <see cref="string" /> that represents this <see cref="BoundingRectangle" />.
/// </summary>
/// <returns>
/// A <see cref="string" /> that represents this <see cref="BoundingRectangle" />.
/// </returns>
public override string ToString()
{
return $"Centre: {Centre}, Radius: {Radius}";
}
internal string DebugDisplayString => ToString();
}
}
+10 -10
View File
@@ -35,29 +35,29 @@ namespace MonoGame.Extended.Primitives
}
/// <summary>
/// Determines whether this <see cref="Ray2D" /> intersects with a specified <see cref="BoundingBox2D" />.
/// Determines whether this <see cref="Ray2D" /> intersects with a specified <see cref="BoundingRectangle" />.
/// </summary>
/// <param name="axisAlignedBoundingBox">The bounding box.</param>
/// <param name="boundingRectangle">The bounding rectangle.</param>
/// <param name="rayNearDistance">
/// When this method returns, contains the distance along the ray to the first intersection
/// point with the <paramref name="axisAlignedBoundingBox" />, if an intersection was found; otherwise, <see cref="float.NaN" />.
/// point with the <paramref name="boundingRectangle" />, if an intersection was found; otherwise, <see cref="float.NaN" />.
/// This parameter is passed uninitialized.
/// </param>
/// <param name="rayFarDistance">
/// When this method returns, contains the distance along the ray to the second intersection
/// point with the <paramref name="axisAlignedBoundingBox" />, if an intersection was found; otherwise, <see cref="float.NaN" />.
/// point with the <paramref name="boundingRectangle" />, if an intersection was found; otherwise, <see cref="float.NaN" />.
/// This parameter is passed uninitialized.
/// </param>
/// <returns>
/// <c>true</c> if this <see cref="Ray2D" /> intersects with <paramref name="axisAlignedBoundingBox" />; otherwise,
/// <c>true</c> if this <see cref="Ray2D" /> intersects with <paramref name="boundingRectangle" />; otherwise,
/// <c>false</c>.
/// </returns>
public bool Intersects(BoundingBox2D axisAlignedBoundingBox, out float rayNearDistance, out float rayFarDistance)
public bool Intersects(BoundingRectangle boundingRectangle, out float rayNearDistance, out float rayFarDistance)
{
// Real-Time Collision Detection, Christer Ericson, 2005. Chapter 5.3; Basic Primitive Tests - Intersecting Lines, Rays, and (Directed Segments). pg 179-181
var boundingBoxMinimumPoint = axisAlignedBoundingBox.Centre - axisAlignedBoundingBox.Radius;
var boundingBoxMaximumPoint = axisAlignedBoundingBox.Centre + axisAlignedBoundingBox.Radius;
var minimum = boundingRectangle.Centre - boundingRectangle.Radius;
var maximum = boundingRectangle.Centre + boundingRectangle.Radius;
// Set to the smallest possible value so the algorithm can find the first hit along the ray
var minimumDistanceAlongRay = float.MinValue;
@@ -67,7 +67,7 @@ namespace MonoGame.Extended.Primitives
// For all relevant slabs which in this case is two.
// The first, horizontal, slab.
if (!RayHelper.IntersectsSlab(Position.X, Direction.X, boundingBoxMinimumPoint.X, boundingBoxMaximumPoint.X,
if (!RayHelper.IntersectsSlab(Position.X, Direction.X, minimum.X, maximum.X,
ref minimumDistanceAlongRay,
ref maximumDistanceAlongRay))
{
@@ -76,7 +76,7 @@ namespace MonoGame.Extended.Primitives
}
// The second, vertical, slab.
if (!RayHelper.IntersectsSlab(Position.Y, Direction.Y, boundingBoxMinimumPoint.Y, boundingBoxMaximumPoint.Y,
if (!RayHelper.IntersectsSlab(Position.Y, Direction.Y, minimum.Y, maximum.Y,
ref minimumDistanceAlongRay,
ref maximumDistanceAlongRay))
{
@@ -106,23 +106,23 @@ namespace MonoGame.Extended.Primitives
}
/// <summary>
/// Determines whether this <see cref="Segment2D" /> intersects with the specified <see cref="BoundingBox2D" />.
/// Determines whether this <see cref="Segment2D" /> intersects with the specified <see cref="BoundingRectangle" />.
/// </summary>
/// <param name="axisAlignedBoundingBox">The bounding box.</param>
/// <param name="boundingRectangle">The bounding box.</param>
/// <param name="intersectionPoint">
/// When this method returns, contains the <see cref="Point2" /> of intersection, if an
/// intersection was found; otherwise, the <see cref="Point2.NaN" />. This parameter is passed uninitialized.
/// </param>
/// <returns>
/// <c>true</c> if this <see cref="Segment2D" /> intersects with <paramref name="axisAlignedBoundingBox" />; otherwise,
/// <c>true</c> if this <see cref="Segment2D" /> intersects with <paramref name="boundingRectangle" />; otherwise,
/// <c>false</c>.
/// </returns>
public bool Intersects(BoundingBox2D axisAlignedBoundingBox, out Point2 intersectionPoint)
public bool Intersects(BoundingRectangle boundingRectangle, out Point2 intersectionPoint)
{
// Real-Time Collision Detection, Christer Ericson, 2005. Chapter 5.3; Basic Primitive Tests - Intersecting Lines, Rays, and (Directed Segments). pg 179-181
var minimumPoint = axisAlignedBoundingBox.Centre - axisAlignedBoundingBox.Radius;
var maximumPoint = axisAlignedBoundingBox.Centre + axisAlignedBoundingBox.Radius;
var minimumPoint = boundingRectangle.Centre - boundingRectangle.Radius;
var maximumPoint = boundingRectangle.Centre + boundingRectangle.Radius;
var minimumDistance = float.MinValue;
var maximumDistance = float.MaxValue;