Files
EVEngine/test/box3d.cpp
T
西风逍遥游andCursor Agent c26a077cb5 feat(physics): integrate Box3D for 3D rigid-body simulation (#31)
Add Erin Catto's Box3D (v0.1.0) as external/box3d and expose World3D,
Body3D, and Shape3D through Physics.newWorld3D, mirroring the existing
Box2D script API with meter-space coordinates, queries, and contact events.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-08-11 17:01:13 +08:00

130 lines
4.3 KiB
C++

#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include "physics/Physics.h"
#include "physics/World3D.h"
#include "physics/Body3D.h"
#include "physics/Shape3D.h"
#include <cmath>
#include <memory>
using namespace eve::physics;
TEST_CASE("box3d.world.createAndGravity") {
auto *mod = Physics::create();
std::unique_ptr<World3D> world(mod->newWorld3D(0.f, -9.8f, 0.f, true));
REQUIRE(world.get() != nullptr);
CHECK(std::fabs(world->getGravityX()) < 0.01f);
CHECK(std::fabs(world->getGravityY() + 9.8f) < 0.01f);
CHECK(std::fabs(world->getGravityZ()) < 0.01f);
world->setGravity(1.f, -10.f, 2.f);
CHECK(std::fabs(world->getGravityX() - 1.f) < 0.01f);
CHECK(std::fabs(world->getGravityY() + 10.f) < 0.01f);
CHECK(std::fabs(world->getGravityZ() - 2.f) < 0.01f);
}
TEST_CASE("box3d.body.fallsUnderGravity") {
auto *mod = Physics::create();
std::unique_ptr<World3D> world(mod->newWorld3D(0.f, -10.f, 0.f, true));
Body3D *ground = world->newBody("static", 0.f, -1.f, 0.f);
ground->newBoxShape(20.f, 2.f, 20.f, 0.f, 0.5f, 0.f);
Body3D *box = world->newBody("dynamic", 0.f, 4.f, 0.f);
box->newBoxShape(1.f, 1.f, 1.f, 1.f, 0.3f, 0.1f);
float y0 = box->getY();
for (int i = 0; i < 90; ++i)
world->update(1.f / 60.f);
CHECK_LT(box->getY(), y0);
CHECK_GT(box->getY(), 0.f);
}
TEST_CASE("box3d.body.staticDoesNotMove") {
auto *mod = Physics::create();
std::unique_ptr<World3D> world(mod->newWorld3D(0.f, -10.f, 0.f, true));
Body3D *ground = world->newBody("static", 0.f, 0.f, 0.f);
ground->newBoxShape(4.f, 1.f, 4.f, 0.f);
float y0 = ground->getY();
for (int i = 0; i < 30; ++i)
world->update(1.f / 60.f);
CHECK(std::fabs(ground->getY() - y0) < 0.01f);
CHECK_EQ(ground->getType(), std::string("static"));
}
TEST_CASE("box3d.body.sphereAndVelocity") {
auto *mod = Physics::create();
std::unique_ptr<World3D> world(mod->newWorld3D(0.f, 0.f, 0.f, true));
Body3D *ball = world->newBody("dynamic", 0.f, 0.f, 0.f);
ball->newSphereShape(0.5f, 1.f, 0.2f, 0.8f);
ball->setLinearVelocity(5.f, 0.f, 0.f);
for (int i = 0; i < 30; ++i)
world->update(1.f / 60.f);
CHECK_GT(ball->getX(), 1.f);
CHECK(std::fabs(ball->getY()) < 0.1f);
CHECK(std::fabs(ball->getZ()) < 0.1f);
}
TEST_CASE("box3d.shape.sensorAndMaterial") {
auto *mod = Physics::create();
std::unique_ptr<World3D> world(mod->newWorld3D(0.f, 0.f, 0.f, true));
Body3D *b = world->newBody("dynamic", 0.f, 0.f, 0.f);
Shape3D *shape = b->newBoxShape(1.f, 1.f, 1.f);
CHECK(!shape->isSensor());
shape->setSensor(true);
CHECK(shape->isSensor());
shape->setFriction(0.7f);
CHECK(std::fabs(shape->getFriction() - 0.7f) < 0.001f);
}
TEST_CASE("box3d.body.destroy") {
auto *mod = Physics::create();
std::unique_ptr<World3D> world(mod->newWorld3D(0.f, 0.f, 0.f, true));
Body3D *b = world->newBody("dynamic", 1.f, 2.f, 3.f);
b->newBoxShape(0.5f, 0.5f, 0.5f);
int id = b->getId();
CHECK_GT(id, 0);
b->destroy();
CHECK(std::fabs(b->getX()) < 0.001f);
delete b;
}
TEST_CASE("box3d.query.rayCastAndAABB") {
auto *mod = Physics::create();
std::unique_ptr<World3D> world(mod->newWorld3D(0.f, 0.f, 0.f, true));
Body3D *a = world->newBody("static", 0.f, 0.f, 0.f);
Shape3D *sa = a->newBoxShape(2.f, 2.f, 2.f);
Body3D *b = world->newBody("static", 5.f, 0.f, 0.f);
b->newSphereShape(1.f);
CHECK(sa->testPoint(0.f, 0.f, 0.f));
CHECK(!sa->testPoint(10.f, 10.f, 10.f));
int hit = world->rayCast(-5.f, 0.f, 0.f, 10.f, 0.f, 0.f);
CHECK_EQ(hit, a->getId());
CHECK(world->hasRayHit());
CHECK(std::fabs(world->getRayHitY()) < 0.5f);
int count = world->queryAABB(-2.f, -2.f, -2.f, 2.f, 2.f, 2.f);
CHECK_GE(count, 1);
bool foundA = false;
for (int i = 0; i < world->getQueryCount(); ++i) {
if (world->getQueryBodyId(i) == a->getId()) foundA = true;
}
CHECK(foundA);
}
TEST_CASE("box3d.body.capsuleAndRotation") {
auto *mod = Physics::create();
std::unique_ptr<World3D> world(mod->newWorld3D(0.f, 0.f, 0.f, true));
Body3D *cap = world->newBody("dynamic", 0.f, 1.f, 0.f);
cap->newCapsuleShape(1.f, 0.25f, 1.f);
cap->setRotation(0.f, 0.f, 0.f, 1.f);
CHECK(std::fabs(cap->getRotW() - 1.f) < 0.001f);
cap->setFixedRotation(true);
CHECK(cap->isFixedRotation());
}