mirror of
				https://github.com/luanti-org/luanti.git
				synced 2025-11-04 09:15:29 +01:00 
			
		
		
		
	Add basic unit tests for collisionMoveSimple
This commit is contained in:
		@@ -21,6 +21,8 @@
 | 
			
		||||
#warning "-ffast-math is known to cause bugs in collision code, do not use!"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
bool g_collision_problems_encountered = false;
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
struct NearbyCollisionInfo {
 | 
			
		||||
@@ -342,6 +344,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
 | 
			
		||||
			warningstream << "collisionMoveSimple: maximum step interval exceeded,"
 | 
			
		||||
					" lost movement details!"<<std::endl;
 | 
			
		||||
		}
 | 
			
		||||
		g_collision_problems_encountered = true;
 | 
			
		||||
		dtime = DTIME_LIMIT;
 | 
			
		||||
	} else {
 | 
			
		||||
		time_notification_done = false;
 | 
			
		||||
@@ -415,7 +418,8 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
 | 
			
		||||
		// Avoid infinite loop
 | 
			
		||||
		loopcount++;
 | 
			
		||||
		if (loopcount >= 100) {
 | 
			
		||||
			warningstream << "collisionMoveSimple: Loop count exceeded, aborting to avoid infiniite loop" << std::endl;
 | 
			
		||||
			warningstream << "collisionMoveSimple: Loop count exceeded, aborting to avoid infinite loop" << std::endl;
 | 
			
		||||
			g_collision_problems_encountered = true;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,8 @@ struct CollisionInfo
 | 
			
		||||
	v3f new_pos;
 | 
			
		||||
	v3f old_speed;
 | 
			
		||||
	v3f new_speed;
 | 
			
		||||
 | 
			
		||||
	// FIXME: this is equivalent to `axis`, why does it exist?
 | 
			
		||||
	int plane = -1;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -44,12 +46,16 @@ struct collisionMoveResult
 | 
			
		||||
{
 | 
			
		||||
	collisionMoveResult() = default;
 | 
			
		||||
 | 
			
		||||
	bool touching_ground = false;
 | 
			
		||||
	bool collides = false;
 | 
			
		||||
	bool touching_ground = false;
 | 
			
		||||
	bool standing_on_object = false;
 | 
			
		||||
	std::vector<CollisionInfo> collisions;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Status if any problems were ever encountered during collision detection.
 | 
			
		||||
/// @warning For unit test use only.
 | 
			
		||||
extern bool g_collision_problems_encountered;
 | 
			
		||||
 | 
			
		||||
/// @brief Moves using a single iteration; speed should not exceed pos_max_d/dtime
 | 
			
		||||
/// @param self (optional) ActiveObject to ignore in the collision detection.
 | 
			
		||||
collisionMoveResult collisionMoveSimple(Environment *env,IGameDef *gamedef,
 | 
			
		||||
 
 | 
			
		||||
@@ -24,5 +24,19 @@ public:
 | 
			
		||||
 | 
			
		||||
	~DummyMap() = default;
 | 
			
		||||
 | 
			
		||||
	void fill(v3s16 bpmin, v3s16 bpmax, MapNode n)
 | 
			
		||||
	{
 | 
			
		||||
		for (s16 z = bpmin.Z; z <= bpmax.Z; z++)
 | 
			
		||||
		for (s16 y = bpmin.Y; y <= bpmax.Y; y++)
 | 
			
		||||
		for (s16 x = bpmin.X; x <= bpmax.X; x++) {
 | 
			
		||||
			MapBlock *block = getBlockNoCreateNoEx({x, y, z});
 | 
			
		||||
			if (block) {
 | 
			
		||||
				for (size_t i = 0; i < MapBlock::nodecount; i++)
 | 
			
		||||
					block->getData()[i] = n;
 | 
			
		||||
				block->expireIsAirCache();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool maySaveBlocks() override { return false; }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,9 @@
 | 
			
		||||
// Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
 | 
			
		||||
 | 
			
		||||
#include "test.h"
 | 
			
		||||
#include "dummymap.h"
 | 
			
		||||
#include "environment.h"
 | 
			
		||||
#include "irrlicht_changes/printing.h"
 | 
			
		||||
 | 
			
		||||
#include "collision.h"
 | 
			
		||||
 | 
			
		||||
@@ -14,6 +17,7 @@ public:
 | 
			
		||||
	void runTests(IGameDef *gamedef);
 | 
			
		||||
 | 
			
		||||
	void testAxisAlignedCollision();
 | 
			
		||||
	void testCollisionMoveSimple(IGameDef *gamedef);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static TestCollision g_test_instance;
 | 
			
		||||
@@ -21,8 +25,42 @@ static TestCollision g_test_instance;
 | 
			
		||||
void TestCollision::runTests(IGameDef *gamedef)
 | 
			
		||||
{
 | 
			
		||||
	TEST(testAxisAlignedCollision);
 | 
			
		||||
	TEST(testCollisionMoveSimple, gamedef);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
	class TestEnvironment : public Environment {
 | 
			
		||||
		DummyMap map;
 | 
			
		||||
	public:
 | 
			
		||||
		TestEnvironment(IGameDef *gamedef)
 | 
			
		||||
			: Environment(gamedef), map(gamedef, {-1, -1, -1}, {1, 1, 1})
 | 
			
		||||
		{
 | 
			
		||||
			map.fill({-1, -1, -1}, {1, 1, 1}, MapNode(CONTENT_AIR));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void step(f32 dtime) override {}
 | 
			
		||||
 | 
			
		||||
		Map &getMap() override { return map; }
 | 
			
		||||
 | 
			
		||||
		void getSelectedActiveObjects(const core::line3d<f32> &shootline_on_map,
 | 
			
		||||
			std::vector<PointedThing> &objects,
 | 
			
		||||
			const std::optional<Pointabilities> &pointabilities) override {}
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define UASSERTEQ_F(actual, expected) do { \
 | 
			
		||||
		f32 a = (actual); \
 | 
			
		||||
		f32 e = (expected); \
 | 
			
		||||
		UTEST(fabsf(a - e) <= 0.0001f, "actual: %.f expected: %.f", a, e) \
 | 
			
		||||
	} while (0)
 | 
			
		||||
 | 
			
		||||
#define UASSERTEQ_V3F(actual, expected) do { \
 | 
			
		||||
		v3f va = (actual); \
 | 
			
		||||
		v3f ve = (expected); \
 | 
			
		||||
		UASSERTEQ_F(va.X, ve.X); UASSERTEQ_F(va.Y, ve.Y); UASSERTEQ_F(va.Z, ve.Z); \
 | 
			
		||||
	} while (0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
void TestCollision::testAxisAlignedCollision()
 | 
			
		||||
@@ -163,3 +201,78 @@ void TestCollision::testAxisAlignedCollision()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define fpos(x,y,z) (BS * v3f(x, y, z))
 | 
			
		||||
 | 
			
		||||
void TestCollision::testCollisionMoveSimple(IGameDef *gamedef)
 | 
			
		||||
{
 | 
			
		||||
	auto env = std::make_unique<TestEnvironment>(gamedef);
 | 
			
		||||
	g_collision_problems_encountered = false;
 | 
			
		||||
 | 
			
		||||
	for (s16 x = 0; x < MAP_BLOCKSIZE; x++)
 | 
			
		||||
	for (s16 z = 0; z < MAP_BLOCKSIZE; z++)
 | 
			
		||||
		env->getMap().setNode({x, 0, z}, MapNode(t_CONTENT_STONE));
 | 
			
		||||
 | 
			
		||||
	const f32 pos_max_d = 0.25f * BS; // ?
 | 
			
		||||
	v3f pos, speed, accel;
 | 
			
		||||
	const aabb3f box(fpos(-0.1f, 0, -0.1f), fpos(0.1f, 1.4f, 0.1f));
 | 
			
		||||
	collisionMoveResult res;
 | 
			
		||||
 | 
			
		||||
	/* simple movement with accel */
 | 
			
		||||
	pos   = fpos(4, 1, 4);
 | 
			
		||||
	speed = fpos(0, 0, 0);
 | 
			
		||||
	accel = fpos(0, 1, 0);
 | 
			
		||||
	res = collisionMoveSimple(env.get(), gamedef, pos_max_d, box, 0.0f, 1.0f,
 | 
			
		||||
		&pos, &speed, accel);
 | 
			
		||||
 | 
			
		||||
	UASSERT(!res.touching_ground || !res.collides || !res.standing_on_object);
 | 
			
		||||
	UASSERT(res.collisions.empty());
 | 
			
		||||
	// FIXME: it's easy to tell that this should be y=1.5f, but our code does it wrong.
 | 
			
		||||
	// It's unclear if/how this will be fixed.
 | 
			
		||||
	UASSERTEQ_V3F(pos, fpos(4, 2, 4));
 | 
			
		||||
	UASSERTEQ_V3F(speed, fpos(0, 1, 0));
 | 
			
		||||
 | 
			
		||||
	/* standing on ground */
 | 
			
		||||
	pos   = fpos(0, 0.5f, 0);
 | 
			
		||||
	speed = fpos(0, 0, 0);
 | 
			
		||||
	accel = fpos(0, -9.81f, 0);
 | 
			
		||||
	res = collisionMoveSimple(env.get(), gamedef, pos_max_d, box, 0.0f, 0.04f,
 | 
			
		||||
		&pos, &speed, accel);
 | 
			
		||||
 | 
			
		||||
	UASSERT(res.collides);
 | 
			
		||||
	UASSERT(res.touching_ground);
 | 
			
		||||
	UASSERT(!res.standing_on_object);
 | 
			
		||||
	UASSERTEQ_V3F(pos, fpos(0, 0.5f, 0));
 | 
			
		||||
	UASSERTEQ_V3F(speed, fpos(0, 0, 0));
 | 
			
		||||
	UASSERT(res.collisions.size() == 1);
 | 
			
		||||
	{
 | 
			
		||||
		auto &ci = res.collisions.front();
 | 
			
		||||
		UASSERTEQ(int, ci.type, COLLISION_NODE);
 | 
			
		||||
		UASSERTEQ(int, ci.axis, COLLISION_AXIS_Y);
 | 
			
		||||
		UASSERTEQ(v3s16, ci.node_p, v3s16(0, 0, 0));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* not moving never collides */
 | 
			
		||||
	pos   = fpos(0, -100, 0);
 | 
			
		||||
	speed = fpos(0, 0, 0);
 | 
			
		||||
	accel = fpos(0, 0, 0);
 | 
			
		||||
	res = collisionMoveSimple(env.get(), gamedef, pos_max_d, box, 0.0f, 1/60.0f,
 | 
			
		||||
		&pos, &speed, accel);
 | 
			
		||||
	UASSERT(!res.collides);
 | 
			
		||||
 | 
			
		||||
	/* collision in ignore */
 | 
			
		||||
	pos   = fpos(0, -100, 0);
 | 
			
		||||
	speed = fpos(5, 0, 0);
 | 
			
		||||
	accel = fpos(0, 0, 0);
 | 
			
		||||
	res = collisionMoveSimple(env.get(), gamedef, pos_max_d, box, 0.0f, 1/60.0f,
 | 
			
		||||
		&pos, &speed, accel);
 | 
			
		||||
	UASSERTEQ_V3F(speed, fpos(0, 0, 0));
 | 
			
		||||
	UASSERT(!res.collides); // FIXME this is actually inconsistent
 | 
			
		||||
	UASSERT(res.collisions.empty());
 | 
			
		||||
 | 
			
		||||
	// TODO things to test:
 | 
			
		||||
	// standing_on_object, multiple collisions, bouncy, stepheight
 | 
			
		||||
 | 
			
		||||
	// No warnings should have been raised during our test.
 | 
			
		||||
	UASSERT(!g_collision_problems_encountered);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user