Remove obsolete project files for MrGameEng.AI, MrGameEng.Assets, MrGameEng.Atlases, and MrGameEng.Collisions modules. Introduce new AssetManager and related classes for asset loading and management, including support for texture atlases and mod definitions. Enhance mod loading capabilities with DefDatabase and LanguageManager for JSON-based definitions and localization. Implement a shelf packing algorithm for efficient texture atlas creation.
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
using Friflo.Engine.ECS;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MrGameEng.Core;
|
||||
using MrGameEng.Graphics;
|
||||
using Xunit;
|
||||
|
||||
namespace MrGameEng.Collisions.Tests;
|
||||
|
||||
public class CollisionWorldTests
|
||||
{
|
||||
private sealed class CollisionScene : Scene
|
||||
{
|
||||
public CollisionWorld World = null!;
|
||||
|
||||
protected override void OnLoad() => World = this.UseCollisions(cellSize: 32f);
|
||||
}
|
||||
|
||||
private static (EngineContext Context, CollisionScene Scene) CreateScene()
|
||||
{
|
||||
var context = new EngineContext();
|
||||
var scene = new CollisionScene();
|
||||
context.Scenes.Switch(scene);
|
||||
context.Scenes.Update(context.Clock); // применяет переключение и первый тик
|
||||
return (context, scene);
|
||||
}
|
||||
|
||||
private static Entity Spawn(Scene scene, Vector2 position, in Collider collider) =>
|
||||
scene.Store.CreateEntity(Transform2D.At(position), collider);
|
||||
|
||||
private static void Tick(EngineContext context)
|
||||
{
|
||||
context.Clock.Advance(0.016f);
|
||||
context.Scenes.Update(context.Clock);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OverlappingCircles_ProduceOnePair()
|
||||
{
|
||||
var (context, scene) = CreateScene();
|
||||
var a = Spawn(scene, new Vector2(0f, 0f), Collider.Circle(10f));
|
||||
var b = Spawn(scene, new Vector2(15f, 0f), Collider.Circle(10f));
|
||||
Spawn(scene, new Vector2(100f, 100f), Collider.Circle(10f)); // далёкий — без пар
|
||||
|
||||
Tick(context);
|
||||
|
||||
var pair = Assert.Single(scene.World.Pairs.ToArray());
|
||||
Assert.True((pair.A == a && pair.B == b) || (pair.A == b && pair.B == a));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SeparatedCircles_NoPairs()
|
||||
{
|
||||
var (context, scene) = CreateScene();
|
||||
Spawn(scene, new Vector2(0f, 0f), Collider.Circle(5f));
|
||||
Spawn(scene, new Vector2(11f, 0f), Collider.Circle(5f));
|
||||
|
||||
Tick(context);
|
||||
|
||||
Assert.Equal(0, scene.World.Pairs.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CircleAndBox_Overlap_DetectedBothWays()
|
||||
{
|
||||
var (context, scene) = CreateScene();
|
||||
Spawn(scene, new Vector2(0f, 0f), Collider.Box(20f, 20f));
|
||||
Spawn(scene, new Vector2(14f, 0f), Collider.Circle(5f)); // касается правой грани
|
||||
|
||||
Tick(context);
|
||||
|
||||
Assert.Equal(1, scene.World.Pairs.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CornerCircle_DoesNotTouchBox()
|
||||
{
|
||||
var (context, scene) = CreateScene();
|
||||
Spawn(scene, new Vector2(0f, 0f), Collider.Box(20f, 20f));
|
||||
// Угол бокса (10,10); круг r=5 в (16,16): расстояние до угла ~8.49 > 5.
|
||||
Spawn(scene, new Vector2(16f, 16f), Collider.Circle(5f));
|
||||
|
||||
Tick(context);
|
||||
|
||||
Assert.Equal(0, scene.World.Pairs.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LayerMasks_FilterPairs()
|
||||
{
|
||||
var (context, scene) = CreateScene();
|
||||
var ghost = Collider.Circle(10f);
|
||||
ghost.Layer = 0b10;
|
||||
ghost.CollidesWith = 0b10; // призраки сталкиваются только с призраками
|
||||
|
||||
var wall = Collider.Circle(10f);
|
||||
wall.Layer = 0b01;
|
||||
wall.CollidesWith = 0b01;
|
||||
|
||||
Spawn(scene, new Vector2(0f, 0f), ghost);
|
||||
Spawn(scene, new Vector2(5f, 0f), wall);
|
||||
|
||||
Tick(context);
|
||||
|
||||
Assert.Equal(0, scene.World.Pairs.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MovingApart_PairDisappearsNextTick()
|
||||
{
|
||||
var (context, scene) = CreateScene();
|
||||
Spawn(scene, new Vector2(0f, 0f), Collider.Circle(10f));
|
||||
var mover = Spawn(scene, new Vector2(5f, 0f), Collider.Circle(10f));
|
||||
|
||||
Tick(context);
|
||||
Assert.Equal(1, scene.World.Pairs.Length);
|
||||
|
||||
mover.GetComponent<Transform2D>().Position = new Vector2(100f, 0f);
|
||||
Tick(context);
|
||||
Assert.Equal(0, scene.World.Pairs.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LargeCluster_AllTouchingPairsFound_OnceEach()
|
||||
{
|
||||
var (context, scene) = CreateScene();
|
||||
// Цепочка из 10 кругов: касаются только соседи → ровно 9 пар.
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
Spawn(scene, new Vector2(i * 18f, 0f), Collider.Circle(10f));
|
||||
}
|
||||
|
||||
Tick(context);
|
||||
|
||||
Assert.Equal(9, scene.World.Pairs.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryAabb_ReturnsOnlyEntitiesInArea()
|
||||
{
|
||||
var (context, scene) = CreateScene();
|
||||
var inside = Spawn(scene, new Vector2(10f, 10f), Collider.Circle(5f));
|
||||
Spawn(scene, new Vector2(200f, 200f), Collider.Circle(5f));
|
||||
|
||||
Tick(context);
|
||||
|
||||
Span<Entity> results = new Entity[8];
|
||||
var found = scene.World.QueryAabb(new RectF(0f, 0f, 50f, 50f), results);
|
||||
|
||||
Assert.Equal(1, found);
|
||||
Assert.Equal(inside, results[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Raycast_HitsClosestCollider_AndRespectsMask()
|
||||
{
|
||||
var (context, scene) = CreateScene();
|
||||
var near = Collider.Circle(5f);
|
||||
near.Layer = 0b01;
|
||||
var far = Collider.Circle(5f);
|
||||
far.Layer = 0b10;
|
||||
|
||||
var nearEntity = Spawn(scene, new Vector2(30f, 0f), near);
|
||||
var farEntity = Spawn(scene, new Vector2(60f, 0f), far);
|
||||
|
||||
Tick(context);
|
||||
|
||||
Assert.True(scene.World.Raycast(new Vector2(0f, 0f), new Vector2(100f, 0f), out var hit));
|
||||
Assert.Equal(nearEntity, hit.Entity);
|
||||
Assert.Equal(25f, hit.Point.X, 1);
|
||||
|
||||
Assert.True(
|
||||
scene.World.Raycast(new Vector2(0f, 0f), new Vector2(100f, 0f), out hit, mask: 0b10)
|
||||
);
|
||||
Assert.Equal(farEntity, hit.Entity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Raycast_Miss_ReturnsFalse()
|
||||
{
|
||||
var (context, scene) = CreateScene();
|
||||
Spawn(scene, new Vector2(0f, 50f), Collider.Box(10f, 10f));
|
||||
|
||||
Tick(context);
|
||||
|
||||
Assert.False(scene.World.Raycast(new Vector2(0f, 0f), new Vector2(100f, 0f), out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryAabb_BeforeFirstRebuild_ReturnsZero()
|
||||
{
|
||||
// До первого EndRebuild хэш пуст; запрос не должен зависать и что-то находить.
|
||||
var world = new CollisionWorld();
|
||||
|
||||
Span<Entity> results = new Entity[4];
|
||||
Assert.Equal(0, world.QueryAabb(new RectF(-10f, -10f, 20f, 20f), results));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryAabb_TruncatedResults_DoNotPoisonNextQuery()
|
||||
{
|
||||
var (context, scene) = CreateScene();
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
Spawn(scene, new Vector2(i * 5f, 0f), Collider.Circle(4f));
|
||||
}
|
||||
|
||||
Tick(context);
|
||||
|
||||
var area = new RectF(-10f, -10f, 40f, 20f);
|
||||
Span<Entity> tiny = new Entity[1];
|
||||
Assert.Equal(1, scene.World.QueryAabb(area, tiny)); // обрезан по размеру буфера
|
||||
|
||||
Span<Entity> all = new Entity[8];
|
||||
Assert.Equal(3, scene.World.QueryAabb(area, all)); // повторный запрос видит всех
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Raycast_FromInsideCircle_HitsAtOrigin()
|
||||
{
|
||||
var (context, scene) = CreateScene();
|
||||
var entity = Spawn(scene, new Vector2(0f, 0f), Collider.Circle(10f));
|
||||
|
||||
Tick(context);
|
||||
|
||||
Assert.True(scene.World.Raycast(new Vector2(0f, 0f), new Vector2(100f, 0f), out var hit));
|
||||
Assert.Equal(entity, hit.Entity);
|
||||
Assert.Equal(0f, hit.Fraction);
|
||||
Assert.Equal(Vector2.Zero, hit.Point);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExactlyTouchingBoxes_ProducePair()
|
||||
{
|
||||
var (context, scene) = CreateScene();
|
||||
Spawn(scene, new Vector2(0f, 0f), Collider.Box(20f, 20f));
|
||||
Spawn(scene, new Vector2(20f, 0f), Collider.Box(20f, 20f)); // грани соприкасаются ровно
|
||||
|
||||
Tick(context);
|
||||
|
||||
Assert.Equal(1, scene.World.Pairs.Length); // как и у касающихся кругов
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user