CollisionComponentTests.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. using System;
  2. using Microsoft.Xna.Framework;
  3. using Xunit;
  4. namespace MonoGame.Extended.Collisions.Tests
  5. {
  6. using MonoGame.Extended.Collisions.Layers;
  7. /// <summary>
  8. /// Test collision of actors with various shapes.
  9. /// </summary>
  10. /// <remarks>
  11. /// Uses the fact that <see cref="BasicActor"/> moves itself away from
  12. /// <see cref="BasicWall"/> on collision.
  13. /// </remarks>
  14. public class CollisionComponentTests
  15. {
  16. private readonly CollisionComponent _collisionComponent;
  17. private readonly GameTime _gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromMilliseconds(16));
  18. public CollisionComponentTests()
  19. {
  20. _collisionComponent = new CollisionComponent(new RectangleF(Vector2.Zero, new Vector2(10, 10)));
  21. }
  22. #region Circle Circle
  23. [Fact]
  24. public void PenetrationVectorSameCircleTest()
  25. {
  26. Vector2 pos1 = Vector2.Zero;
  27. Vector2 pos2 = Vector2.Zero;
  28. IShapeF shape1 = new CircleF(pos1, 2.0f);
  29. IShapeF shape2 = new CircleF(pos2, 2.0f);
  30. var actor1 = new BasicActor()
  31. {
  32. Position = pos1,
  33. Bounds = shape1
  34. };
  35. var actor2 = new BasicWall()
  36. {
  37. Position = pos2,
  38. Bounds = shape2
  39. };
  40. Assert.True(shape1.Intersects(shape2));
  41. _collisionComponent.Insert(actor1);
  42. _collisionComponent.Insert(actor2);
  43. _collisionComponent.Update(_gameTime);
  44. Assert.True(Math.Abs(actor1.Position.Y - -4f) < float.Epsilon);
  45. }
  46. [Fact]
  47. public void PenetrationVectorSlightlyOverlappingCircleTest()
  48. {
  49. Vector2 pos1 = new Vector2(0, 1.5f);
  50. Vector2 pos2 = Vector2.Zero;
  51. IShapeF shape1 = new CircleF(pos1, 2.0f);
  52. IShapeF shape2 = new CircleF(pos2, 2.0f);
  53. var actor1 = new BasicActor()
  54. {
  55. Position = pos1,
  56. Bounds = shape1
  57. };
  58. var actor2 = new BasicWall()
  59. {
  60. Position = pos2,
  61. Bounds = shape2
  62. };
  63. Assert.True(shape1.Intersects(shape2));
  64. _collisionComponent.Insert(actor1);
  65. _collisionComponent.Insert(actor2);
  66. _collisionComponent.Update(_gameTime);
  67. // Actor should have moved up because the distance is shorter.
  68. Assert.True(actor1.Position.Y > actor2.Position.Y);
  69. // The circle centers should be about 4 units away after moving
  70. Assert.True(Math.Abs(actor1.Position.Y - 4.0f) < float.Epsilon);
  71. }
  72. [Fact]
  73. public void PenetrationVectorSlightlyOverlappingOffAxisTest()
  74. {
  75. Vector2 pos1 = new Vector2(2, 2.5f);
  76. Vector2 pos2 = new Vector2(2, 1);
  77. IShapeF shape1 = new CircleF(pos1, 2.0f);
  78. IShapeF shape2 = new CircleF(pos2, 2.0f);
  79. var actor1 = new BasicActor()
  80. {
  81. Position = pos1,
  82. Bounds = shape1
  83. };
  84. var actor2 = new BasicWall()
  85. {
  86. Position = pos2,
  87. Bounds = shape2
  88. };
  89. Assert.True(shape1.Intersects(shape2));
  90. _collisionComponent.Insert(actor1);
  91. _collisionComponent.Insert(actor2);
  92. _collisionComponent.Update(_gameTime);
  93. // Actor should have moved up because the distance is shorter.
  94. Assert.True(actor1.Position.Y > actor2.Position.Y);
  95. // The circle centers should be about 4 units away after moving
  96. Assert.True(Math.Abs(actor1.Position.Y - 5.0f) < float.Epsilon);
  97. Assert.True(Math.Abs(actor1.Position.X - 2.0f) < float.Epsilon);
  98. }
  99. [Fact]
  100. public void PenetrationZeroRadiusCircleCircleTest()
  101. {
  102. Vector2 pos1 = new Vector2(0, 1.5f);
  103. Vector2 pos2 = Vector2.Zero;
  104. IShapeF shape1 = new CircleF(pos1, 0);
  105. IShapeF shape2 = new CircleF(pos2, 2.0f);
  106. var actor1 = new BasicActor()
  107. {
  108. Position = pos1,
  109. Bounds = shape1
  110. };
  111. var actor2 = new BasicWall()
  112. {
  113. Position = pos2,
  114. Bounds = shape2
  115. };
  116. Assert.True(shape1.Intersects(shape2));
  117. _collisionComponent.Insert(actor1);
  118. _collisionComponent.Insert(actor2);
  119. _collisionComponent.Update(_gameTime);
  120. // Actor should have moved up because the distance is shorter.
  121. Assert.True(actor1.Position.Y > actor2.Position.Y);
  122. // The circle centers should be about 4 units away after moving
  123. // Assert.True(Math.Abs(actor1.Position.Y - 2.0f) < float.Epsilon);
  124. }
  125. #endregion
  126. #region Circle Rectangle
  127. [Fact]
  128. public void PenetrationVectorCircleRectangleTest()
  129. {
  130. Vector2 pos1 = new Vector2(0, 1);
  131. Vector2 pos2 = new Vector2(-2, -1);
  132. IShapeF shape1 = new CircleF(pos1, 2.0f);
  133. IShapeF shape2 = new RectangleF(pos2, new SizeF(4, 2));
  134. var actor1 = new BasicActor()
  135. {
  136. Position = pos1,
  137. Bounds = shape1
  138. };
  139. var actor2 = new BasicWall()
  140. {
  141. Position = pos2,
  142. Bounds = shape2
  143. };
  144. Assert.True(shape1.Intersects(shape2));
  145. _collisionComponent.Insert(actor1);
  146. _collisionComponent.Insert(actor2);
  147. _collisionComponent.Update(_gameTime);
  148. Assert.True(Math.Abs(actor1.Position.X - 0.0f) < float.Epsilon);
  149. Assert.True(Math.Abs(actor1.Position.Y - 3.0f) < float.Epsilon);
  150. }
  151. [Fact]
  152. public void PenetrationVectorCircleContainedInRectangleTest()
  153. {
  154. Vector2 pos1 = new Vector2(0, 0);
  155. Vector2 pos2 = new Vector2(-2, -1);
  156. IShapeF shape1 = new CircleF(pos1, 1.0f);
  157. IShapeF shape2 = new RectangleF(pos2, new SizeF(4, 2));
  158. var actor1 = new BasicActor()
  159. {
  160. Position = pos1,
  161. Bounds = shape1
  162. };
  163. var actor2 = new BasicWall()
  164. {
  165. Position = pos2,
  166. Bounds = shape2
  167. };
  168. Assert.True(shape1.Intersects(shape2));
  169. _collisionComponent.Insert(actor1);
  170. _collisionComponent.Insert(actor2);
  171. _collisionComponent.Update(_gameTime);
  172. Assert.True(Math.Abs(actor1.Position.X - 0.0f) < float.Epsilon);
  173. Assert.True(Math.Abs(actor1.Position.Y - -2.0f) < float.Epsilon);
  174. }
  175. [Fact]
  176. public void PenetrationVectorCircleOffAxisRectangleTest()
  177. {
  178. Vector2 pos1 = new Vector2(2, 1);
  179. Vector2 pos2 = new Vector2(-2, -1);
  180. IShapeF shape1 = new CircleF(pos1, 2.0f);
  181. IShapeF shape2 = new RectangleF(pos2, new SizeF(4, 2));
  182. var actor1 = new BasicActor()
  183. {
  184. Position = pos1,
  185. Bounds = shape1
  186. };
  187. var actor2 = new BasicWall()
  188. {
  189. Position = pos2,
  190. Bounds = shape2
  191. };
  192. Assert.True(shape1.Intersects(shape2));
  193. _collisionComponent.Insert(actor1);
  194. _collisionComponent.Insert(actor2);
  195. _collisionComponent.Update(_gameTime);
  196. Assert.True(Math.Abs(actor1.Position.X - 2.0f) < float.Epsilon);
  197. Assert.True(Math.Abs(actor1.Position.Y - 3.0f) < float.Epsilon);
  198. }
  199. #endregion
  200. #region Rectangle Rectangle
  201. [Fact]
  202. public void PenetrationVectorRectangleRectangleTest()
  203. {
  204. Vector2 pos1 = new Vector2(0, 0);
  205. Vector2 pos2 = new Vector2(-2, -1);
  206. IShapeF shape1 = new RectangleF(pos1, new SizeF(4, 2));
  207. IShapeF shape2 = new RectangleF(pos2, new SizeF(4, 2));
  208. var actor1 = new BasicActor()
  209. {
  210. Position = pos1,
  211. Bounds = shape1
  212. };
  213. var actor2 = new BasicWall()
  214. {
  215. Position = pos2,
  216. Bounds = shape2
  217. };
  218. Assert.True(shape1.Intersects(shape2));
  219. _collisionComponent.Insert(actor1);
  220. _collisionComponent.Insert(actor2);
  221. _collisionComponent.Update(_gameTime);
  222. Assert.True(Math.Abs(actor1.Position.X - 0.0f) < float.Epsilon);
  223. Assert.True(Math.Abs(actor1.Position.Y - 1.0f) < float.Epsilon);
  224. }
  225. [Fact]
  226. public void PenetrationVectorRectangleRectangleOffAxisTest()
  227. {
  228. Vector2 pos1 = new Vector2(4, 2);
  229. Vector2 pos2 = new Vector2(3, 1);
  230. IShapeF shape1 = new RectangleF(pos1, new SizeF(4, 2));
  231. IShapeF shape2 = new RectangleF(pos2, new SizeF(4, 2));
  232. var actor1 = new BasicActor()
  233. {
  234. Position = pos1,
  235. Bounds = shape1
  236. };
  237. var actor2 = new BasicWall()
  238. {
  239. Position = pos2,
  240. Bounds = shape2
  241. };
  242. Assert.True(shape1.Intersects(shape2));
  243. _collisionComponent.Insert(actor1);
  244. _collisionComponent.Insert(actor2);
  245. _collisionComponent.Update(_gameTime);
  246. Assert.True(Math.Abs(actor1.Position.X - 4.0f) < float.Epsilon);
  247. Assert.True(Math.Abs(actor1.Position.Y - 3.0f) < float.Epsilon);
  248. }
  249. #endregion
  250. public class Behaviours : CollisionComponentTests
  251. {
  252. [Fact]
  253. public void Actors_is_colliding()
  254. {
  255. var staticBounds = new RectangleF(new Vector2(0, 0), new SizeF(1, 1));
  256. var anotherStaticBounds = new RectangleF(new Vector2(0, 0), new SizeF(1, 1));
  257. var staticActor = new CollisionIndicatingActor(staticBounds);
  258. var anotherStaticActor = new CollisionIndicatingActor(anotherStaticBounds);
  259. _collisionComponent.Insert(staticActor);
  260. _collisionComponent.Insert(anotherStaticActor);
  261. _collisionComponent.Update(_gameTime);
  262. Assert.True(staticActor.IsColliding);
  263. Assert.True(anotherStaticActor.IsColliding);
  264. }
  265. [Fact]
  266. public void Actors_is_not_colliding_when_dynamic_actor_is_moved_out_of_collision_bounds()
  267. {
  268. var staticBounds = new RectangleF(new Vector2(0, 0), new SizeF(1, 1));
  269. var dynamicBounds = new RectangleF(new Vector2(0, 0), new SizeF(1, 1));
  270. var staticActor = new CollisionIndicatingActor(staticBounds);
  271. var dynamicActor = new CollisionIndicatingActor(dynamicBounds);
  272. _collisionComponent.Insert(staticActor);
  273. _collisionComponent.Insert(dynamicActor);
  274. dynamicActor.MoveTo(new Vector2(2, 2));
  275. _collisionComponent.Update(_gameTime);
  276. Assert.False(staticActor.IsColliding);
  277. Assert.False(dynamicActor.IsColliding);
  278. }
  279. [Fact]
  280. public void Actors_is_colliding_when_dynamic_actor_is_moved_after_update()
  281. {
  282. var staticBounds = new RectangleF(new Vector2(0, 0), new SizeF(1, 1));
  283. var staticActor = new CollisionIndicatingActor(staticBounds);
  284. _collisionComponent.Insert(staticActor);
  285. for (int i = 0; i < QuadTree.QuadTree.DefaultMaxObjectsPerNode; i++)
  286. {
  287. var fillerBounds = new RectangleF(new Vector2(0, 2), new SizeF(.1f, .1f));
  288. var fillerActor = new CollisionIndicatingActor(fillerBounds);
  289. _collisionComponent.Insert(fillerActor);
  290. }
  291. var dynamicBounds = new RectangleF(new Vector2(2, 2), new SizeF(1, 1));
  292. var dynamicActor = new CollisionIndicatingActor(dynamicBounds);
  293. _collisionComponent.Insert(dynamicActor);
  294. _collisionComponent.Update(_gameTime);
  295. Assert.False(staticActor.IsColliding);
  296. Assert.False(dynamicActor.IsColliding);
  297. dynamicActor.MoveTo(new Vector2(0, 0));
  298. _collisionComponent.Update(_gameTime);
  299. Assert.True(dynamicActor.IsColliding);
  300. Assert.True(staticActor.IsColliding);
  301. }
  302. [Fact]
  303. public void Actors_is_colliding_when_dynamic_actor_is_moved_into_collision_bounds()
  304. {
  305. var staticBounds = new RectangleF(new Vector2(0, 0), new SizeF(1, 1));
  306. var dynamicBounds = new RectangleF(new Vector2(2, 2), new SizeF(1, 1));
  307. var staticActor = new CollisionIndicatingActor(staticBounds);
  308. var dynamicActor = new CollisionIndicatingActor(dynamicBounds);
  309. _collisionComponent.Insert(staticActor);
  310. _collisionComponent.Insert(dynamicActor);
  311. dynamicActor.MoveTo(new Vector2(0, 0));
  312. _collisionComponent.Update(_gameTime);
  313. Assert.True(staticActor.IsColliding);
  314. Assert.True(dynamicActor.IsColliding);
  315. }
  316. [Fact]
  317. public void InsertActor_ThrowsUndefinedLayerException_IfThereIsNoLayerDefined()
  318. {
  319. var sut = new CollisionComponent();
  320. var act = () => sut.Insert(new CollisionIndicatingActor(RectangleF.Empty));
  321. Assert.Throws<UndefinedLayerException>(act);
  322. }
  323. private class CollisionIndicatingActor : ICollisionActor
  324. {
  325. private RectangleF _bounds;
  326. public CollisionIndicatingActor(RectangleF bounds)
  327. {
  328. _bounds = bounds;
  329. }
  330. public IShapeF Bounds => _bounds;
  331. public void OnCollision(CollisionEventArgs collisionInfo)
  332. {
  333. IsColliding = true;
  334. }
  335. public bool IsColliding { get; private set; }
  336. public void MoveTo(Vector2 position)
  337. {
  338. _bounds = new RectangleF(position, _bounds.Size);
  339. }
  340. }
  341. }
  342. }
  343. }