Ajout de Jolt Physics + 1ere version des factory entitecomposants - camera, transform, rigidbody, collider, renderer

This commit is contained in:
Tom Ray
2026-03-22 00:28:03 +01:00
parent 6695d46bcd
commit 48348936a8
1147 changed files with 214331 additions and 353 deletions

View File

@@ -0,0 +1,379 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "UnitTestFramework.h"
#include "PhysicsTestContext.h"
#include "Layers.h"
#include "LoggingContactListener.h"
#include "LoggingBodyActivationListener.h"
TEST_SUITE("MotionQualityLinearCastTests")
{
static const float cBoxExtent = 0.5f;
static const float cFrequency = 60.0f;
static const Vec3 cVelocity(2.0f * cFrequency, 0, 0); // High enough velocity to step 2 meters in a single simulation step
static const RVec3 cPos1(-1, 0, 0);
static const RVec3 cPos2(1, 0, 0);
// Two boxes colliding in the center, each has enough velocity to tunnel though in 1 step
TEST_CASE("TestDiscreteBoxVsDiscreteBox")
{
PhysicsTestContext c(1.0f / cFrequency, 1);
c.ZeroGravity();
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
Body &box1 = c.CreateBox(cPos1, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
box1.SetLinearVelocity(cVelocity);
// Test that the inner radius of the box makes sense (used internally by linear cast)
CHECK_APPROX_EQUAL(box1.GetShape()->GetInnerRadius(), cBoxExtent);
Body &box2 = c.CreateBox(cPos2, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
box2.SetLinearVelocity(-cVelocity);
c.SimulateSingleStep();
// No collisions should be reported and the bodies should have moved according to their velocity (tunneling through each other)
CHECK(listener.GetEntryCount() == 0);
CHECK_APPROX_EQUAL(box1.GetPosition(), cPos1 + cVelocity / cFrequency);
CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), cVelocity);
CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
CHECK_APPROX_EQUAL(box2.GetPosition(), cPos2 - cVelocity / cFrequency);
CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), -cVelocity);
CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
}
// Two boxes colliding in the center, each has enough velocity to step over the other in 1 step, restitution = 1
TEST_CASE("TestLinearCastBoxVsLinearCastBoxElastic")
{
PhysicsTestContext c(1.0f / cFrequency, 1);
c.ZeroGravity();
const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
Body &box1 = c.CreateBox(cPos1, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
box1.SetLinearVelocity(cVelocity);
box1.SetRestitution(1.0f);
Body &box2 = c.CreateBox(cPos2, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
box2.SetLinearVelocity(-cVelocity);
box2.SetRestitution(1.0f);
c.SimulateSingleStep();
// The bodies should have collided and the velocities reversed
CHECK(listener.GetEntryCount() == 2);
CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
CHECK(listener.Contains(LoggingContactListener::EType::Add, box1.GetID(), box2.GetID()));
CHECK_APPROX_EQUAL(box1.GetPosition(), RVec3(-cBoxExtent, 0, 0), cPenetrationSlop);
CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), -cVelocity);
CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
CHECK_APPROX_EQUAL(box2.GetPosition(), RVec3(cBoxExtent, 0, 0), cPenetrationSlop);
CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), cVelocity);
CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
listener.Clear();
c.SimulateSingleStep();
// In the second step the bodies should have moved away, but since they were initially overlapping we should have a contact persist callback
CHECK(listener.GetEntryCount() == 2);
CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
CHECK(listener.Contains(LoggingContactListener::EType::Persist, box1.GetID(), box2.GetID()));
CHECK_APPROX_EQUAL(box1.GetPosition(), RVec3(-cBoxExtent, 0, 0) - cVelocity / cFrequency, cPenetrationSlop);
CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), -cVelocity);
CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
CHECK_APPROX_EQUAL(box2.GetPosition(), RVec3(cBoxExtent, 0, 0) + cVelocity / cFrequency, cPenetrationSlop);
CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), cVelocity);
CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
listener.Clear();
c.SimulateSingleStep();
// In the third step the bodies have separated and a contact remove callback should have been received
CHECK(listener.GetEntryCount() == 1);
CHECK(listener.Contains(LoggingContactListener::EType::Remove, box1.GetID(), box2.GetID()));
CHECK_APPROX_EQUAL(box1.GetPosition(), RVec3(-cBoxExtent, 0, 0) - 2.0f * cVelocity / cFrequency, cPenetrationSlop);
CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), -cVelocity);
CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
CHECK_APPROX_EQUAL(box2.GetPosition(), RVec3(cBoxExtent, 0, 0) + 2.0f * cVelocity / cFrequency, cPenetrationSlop);
CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), cVelocity);
CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
}
// Two boxes colliding in the center, each has enough velocity to step over the other in 1 step, restitution = 0
TEST_CASE("TestLinearCastBoxVsLinearCastBoxInelastic")
{
PhysicsTestContext c(1.0f / cFrequency, 1);
c.ZeroGravity();
const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
Body &box1 = c.CreateBox(cPos1, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
box1.SetLinearVelocity(cVelocity);
Body &box2 = c.CreateBox(cPos2, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
box2.SetLinearVelocity(-cVelocity);
c.SimulateSingleStep();
// The bodies should have collided and both are stopped
CHECK(listener.GetEntryCount() == 2);
CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
CHECK(listener.Contains(LoggingContactListener::EType::Add, box1.GetID(), box2.GetID()));
CHECK_APPROX_EQUAL(box1.GetPosition(), RVec3(-cBoxExtent, 0, 0), cPenetrationSlop);
CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), Vec3::sZero());
CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
CHECK_APPROX_EQUAL(box2.GetPosition(), RVec3(cBoxExtent, 0, 0), cPenetrationSlop);
CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), Vec3::sZero());
CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
// The bodies should persist to contact as they are not moving
for (int i = 0; i < 10; ++i)
{
listener.Clear();
c.SimulateSingleStep();
if (i == 0)
{
// Only in the first step we will receive a validate callback since after this step the contact cache will be used
CHECK(listener.GetEntryCount() == 2);
CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
}
else
CHECK(listener.GetEntryCount() == 1);
CHECK(listener.Contains(LoggingContactListener::EType::Persist, box1.GetID(), box2.GetID()));
CHECK_APPROX_EQUAL(box1.GetPosition(), RVec3(-cBoxExtent, 0, 0), cPenetrationSlop);
CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), Vec3::sZero());
CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
CHECK_APPROX_EQUAL(box2.GetPosition(), RVec3(cBoxExtent, 0, 0), cPenetrationSlop);
CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), Vec3::sZero());
CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
}
}
// Two boxes colliding in the center, linear cast vs inactive linear cast
TEST_CASE("TestLinearCastBoxVsInactiveLinearCastBox")
{
PhysicsTestContext c(1.0f / cFrequency, 1);
c.ZeroGravity();
const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
LoggingBodyActivationListener activation;
c.GetSystem()->SetBodyActivationListener(&activation);
Body &box1 = c.CreateBox(cPos1, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
box1.SetLinearVelocity(cVelocity);
Body &box2 = c.CreateBox(cPos2, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent), EActivation::DontActivate);
CHECK(!box2.IsActive());
c.SimulateSingleStep();
// The bodies should have collided and body 2 should be activated, have velocity, but not moved in this step
CHECK(listener.GetEntryCount() == 2);
CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
CHECK(listener.Contains(LoggingContactListener::EType::Add, box1.GetID(), box2.GetID()));
Vec3 new_velocity = 0.5f * cVelocity;
CHECK_APPROX_EQUAL(box1.GetPosition(), cPos2 - Vec3(2.0f * cBoxExtent, 0, 0), cPenetrationSlop);
CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), new_velocity);
CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
CHECK_APPROX_EQUAL(box2.GetPosition(), cPos2);
CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), new_velocity);
CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
CHECK(box2.IsActive());
CHECK(activation.Contains(LoggingBodyActivationListener::EType::Activated, box2.GetID()));
listener.Clear();
c.SimulateSingleStep();
// In the next step body 2 should have started to move
CHECK(listener.GetEntryCount() == 2);
CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
CHECK(listener.Contains(LoggingContactListener::EType::Persist, box1.GetID(), box2.GetID()));
CHECK_APPROX_EQUAL(box1.GetPosition(), cPos2 - Vec3(2.0f * cBoxExtent, 0, 0) + new_velocity / cFrequency, cPenetrationSlop);
CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), new_velocity);
CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
CHECK_APPROX_EQUAL(box2.GetPosition(), cPos2 + new_velocity / cFrequency);
CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), new_velocity);
CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
}
// Two boxes colliding in the center, linear cast vs inactive discrete
TEST_CASE("TestLinearCastBoxVsInactiveDiscreteBox")
{
PhysicsTestContext c(1.0f / cFrequency, 1);
c.ZeroGravity();
const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
LoggingBodyActivationListener activation;
c.GetSystem()->SetBodyActivationListener(&activation);
Body &box1 = c.CreateBox(cPos1, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
box1.SetLinearVelocity(cVelocity);
Body &box2 = c.CreateBox(cPos2, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(cBoxExtent), EActivation::DontActivate);
CHECK(!box2.IsActive());
c.SimulateSingleStep();
// The bodies should have collided and body 2 should be activated, have velocity, but not moved in this step
CHECK(listener.GetEntryCount() == 2);
CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
CHECK(listener.Contains(LoggingContactListener::EType::Add, box1.GetID(), box2.GetID()));
Vec3 new_velocity = 0.5f * cVelocity;
CHECK_APPROX_EQUAL(box1.GetPosition(), cPos2 - Vec3(2.0f * cBoxExtent, 0, 0), cPenetrationSlop);
CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), new_velocity);
CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
CHECK_APPROX_EQUAL(box2.GetPosition(), cPos2);
CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), new_velocity);
CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
CHECK(box2.IsActive());
CHECK(activation.Contains(LoggingBodyActivationListener::EType::Activated, box2.GetID()));
listener.Clear();
c.SimulateSingleStep();
// In the next step body 2 should have started to move
CHECK(listener.GetEntryCount() == 2);
CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
CHECK(listener.Contains(LoggingContactListener::EType::Persist, box1.GetID(), box2.GetID()));
CHECK_APPROX_EQUAL(box1.GetPosition(), cPos2 - Vec3(2.0f * cBoxExtent, 0, 0) + new_velocity / cFrequency, cPenetrationSlop);
CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), new_velocity);
CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
CHECK_APPROX_EQUAL(box2.GetPosition(), cPos2 + new_velocity / cFrequency);
CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), new_velocity);
CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
}
// Two boxes colliding under an angle, linear cast vs inactive discrete
TEST_CASE("TestLinearCastBoxVsInactiveDiscreteBoxAngled")
{
const Vec3 cAngledOffset1(1, 0, -2);
const Vec3 cAngledVelocity = -cFrequency * 2 * cAngledOffset1;
PhysicsTestContext c(1.0f / cFrequency, 1);
c.ZeroGravity();
const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
LoggingBodyActivationListener activation;
c.GetSystem()->SetBodyActivationListener(&activation);
// Make sure box1 exactly hits the face of box2 in the center
RVec3 pos1 = RVec3(2.0f * cBoxExtent, 0, 0) + cAngledOffset1;
Body &box1 = c.CreateBox(pos1, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
box1.SetLinearVelocity(cAngledVelocity);
box1.SetRestitution(1.0f);
box1.SetFriction(0.0f);
Body &box2 = c.CreateBox(RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(cBoxExtent), EActivation::DontActivate);
box2.SetRestitution(1.0f);
box2.SetFriction(0.0f);
CHECK(!box2.IsActive());
c.SimulateSingleStep();
// The bodies should have collided and body 2 should be activated, have inherited the x velocity of body 1, but not moved in this step. Body 1 should have lost all of its velocity in x direction.
CHECK(listener.GetEntryCount() == 2);
CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
CHECK(listener.Contains(LoggingContactListener::EType::Add, box1.GetID(), box2.GetID()));
Vec3 new_velocity1 = Vec3(0, 0, cAngledVelocity.GetZ());
Vec3 new_velocity2 = Vec3(cAngledVelocity.GetX(), 0, 0);
CHECK_APPROX_EQUAL(box1.GetPosition(), RVec3(2.0f * cBoxExtent, 0, 0), 2.3f * cPenetrationSlop); // We're moving 2x as fast in the z direction and the slop is allowed in x direction: sqrt(1^2 + 2^2) ~ 2.3
CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), new_velocity1, 1.0e-4f);
CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero(), 2.0e-4f);
CHECK_APPROX_EQUAL(box2.GetPosition(), RVec3::sZero());
CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), new_velocity2, 1.0e-4f);
CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero(), 2.0e-4f);
CHECK(box2.IsActive());
CHECK(activation.Contains(LoggingBodyActivationListener::EType::Activated, box2.GetID()));
}
// Two boxes colliding in the center, linear cast vs fast moving discrete, should tunnel through because all discrete bodies are moved before linear cast bodies are tested
TEST_CASE("TestLinearCastBoxVsFastDiscreteBox")
{
PhysicsTestContext c(1.0f / cFrequency, 1);
c.ZeroGravity();
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
Body &box1 = c.CreateBox(cPos1, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
box1.SetLinearVelocity(cVelocity);
Body &box2 = c.CreateBox(cPos2, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
box2.SetLinearVelocity(-cVelocity);
c.SimulateSingleStep();
// No collisions should be reported and the bodies should have moved according to their velocity (tunneling through each other)
CHECK(listener.GetEntryCount() == 0);
CHECK_APPROX_EQUAL(box1.GetPosition(), cPos1 + cVelocity / cFrequency);
CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), cVelocity);
CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
CHECK_APPROX_EQUAL(box2.GetPosition(), cPos2 - cVelocity / cFrequency);
CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), -cVelocity);
CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
}
// Two boxes colliding in the center, linear cast vs moving discrete, discrete is slow enough not to tunnel through linear cast body
TEST_CASE("TestLinearCastBoxVsSlowDiscreteBox")
{
PhysicsTestContext c(1.0f / cFrequency, 1);
c.ZeroGravity();
const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
Body &box1 = c.CreateBox(cPos1, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
box1.SetLinearVelocity(cVelocity);
// In 1 step it should move -0.1 meter on the X axis
const Vec3 cBox2Velocity = Vec3(-0.1f * cFrequency, 0, 0);
Body &box2 = c.CreateBox(cPos2, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
box2.SetLinearVelocity(cBox2Velocity);
c.SimulateSingleStep();
// The bodies should have collided and body 2 should have moved according to its discrete step
CHECK(listener.GetEntryCount() == 2);
CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
CHECK(listener.Contains(LoggingContactListener::EType::Add, box1.GetID(), box2.GetID()));
RVec3 new_pos2 = cPos2 + cBox2Velocity / cFrequency;
Vec3 new_velocity = 0.5f * (cVelocity + cBox2Velocity);
CHECK_APPROX_EQUAL(box1.GetPosition(), new_pos2 - Vec3(2.0f * cBoxExtent, 0, 0), cPenetrationSlop);
CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), new_velocity);
CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
CHECK_APPROX_EQUAL(box2.GetPosition(), new_pos2);
CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), new_velocity);
CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
}
}