380 lines
18 KiB
C++
380 lines
18 KiB
C++
|
|
// 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());
|
||
|
|
}
|
||
|
|
}
|