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,764 @@
// 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 <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/MeshShape.h>
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
#include <Jolt/Physics/Collision/CollideShape.h>
#include <Jolt/Physics/Collision/CollideShapeVsShapePerLeaf.h>
#include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
TEST_SUITE("SensorTests")
{
using LogEntry = LoggingContactListener::LogEntry;
using EType = LoggingContactListener::EType;
TEST_CASE("TestDynamicVsSensor")
{
PhysicsTestContext c;
c.ZeroGravity();
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
// Sensor
BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::SENSOR);
sensor_settings.mIsSensor = true;
BodyID sensor_id = c.GetBodyInterface().CreateAndAddBody(sensor_settings, EActivation::DontActivate);
// Dynamic body moving downwards
Body &dynamic = c.CreateBox(RVec3(0, 2, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
dynamic.SetLinearVelocity(Vec3(0, -1, 0));
// After a single step the dynamic object should not have touched the sensor yet
c.SimulateSingleStep();
CHECK(listener.GetEntryCount() == 0);
// After half a second and one step we should be touching the sensor
c.Simulate(0.5f + c.GetStepDeltaTime());
CHECK(listener.Contains(EType::Add, dynamic.GetID(), sensor_id));
listener.Clear();
// The next step we require that the contact persists
c.SimulateSingleStep();
CHECK(listener.Contains(EType::Persist, dynamic.GetID(), sensor_id));
CHECK(!listener.Contains(EType::Remove, dynamic.GetID(), sensor_id));
listener.Clear();
// After 3 more seconds we should have left the sensor at the bottom side
c.Simulate(3.0f);
CHECK(listener.Contains(EType::Remove, dynamic.GetID(), sensor_id));
CHECK_APPROX_EQUAL(dynamic.GetPosition(), RVec3(0, -1.5f - 3.0f * c.GetDeltaTime(), 0), 1.0e-4f);
}
TEST_CASE("TestKinematicVsSensor")
{
PhysicsTestContext c;
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
// Sensor
BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::SENSOR);
sensor_settings.mIsSensor = true;
BodyID sensor_id = c.GetBodyInterface().CreateAndAddBody(sensor_settings, EActivation::DontActivate);
// Kinematic body moving downwards
Body &kinematic = c.CreateBox(RVec3(0, 2, 0), Quat::sIdentity(), EMotionType::Kinematic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
kinematic.SetLinearVelocity(Vec3(0, -1, 0));
// After a single step the kinematic object should not have touched the sensor yet
c.SimulateSingleStep();
CHECK(listener.GetEntryCount() == 0);
// After half a second and one step we should be touching the sensor
c.Simulate(0.5f + c.GetStepDeltaTime());
CHECK(listener.Contains(EType::Add, kinematic.GetID(), sensor_id));
listener.Clear();
// The next step we require that the contact persists
c.SimulateSingleStep();
CHECK(listener.Contains(EType::Persist, kinematic.GetID(), sensor_id));
CHECK(!listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
listener.Clear();
// After 3 more seconds we should have left the sensor at the bottom side
c.Simulate(3.0f);
CHECK(listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
CHECK_APPROX_EQUAL(kinematic.GetPosition(), RVec3(0, -1.5f - 3.0f * c.GetDeltaTime(), 0), 1.0e-4f);
}
TEST_CASE("TestKinematicVsKinematicSensor")
{
// Same as TestKinematicVsSensor but with the sensor being an active kinematic body
PhysicsTestContext c;
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
// Kinematic sensor
BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Kinematic, Layers::SENSOR);
sensor_settings.mIsSensor = true;
BodyID sensor_id = c.GetBodyInterface().CreateAndAddBody(sensor_settings, EActivation::Activate);
// Kinematic body moving downwards
Body &kinematic = c.CreateBox(RVec3(0, 2, 0), Quat::sIdentity(), EMotionType::Kinematic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
kinematic.SetLinearVelocity(Vec3(0, -1, 0));
// After a single step the kinematic object should not have touched the sensor yet
c.SimulateSingleStep();
CHECK(listener.GetEntryCount() == 0);
// After half a second and one step we should be touching the sensor
c.Simulate(0.5f + c.GetStepDeltaTime());
CHECK(listener.Contains(EType::Add, kinematic.GetID(), sensor_id));
listener.Clear();
// The next step we require that the contact persists
c.SimulateSingleStep();
CHECK(listener.Contains(EType::Persist, kinematic.GetID(), sensor_id));
CHECK(!listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
listener.Clear();
// After 3 more seconds we should have left the sensor at the bottom side
c.Simulate(3.0f);
CHECK(listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
CHECK_APPROX_EQUAL(kinematic.GetPosition(), RVec3(0, -1.5f - 3.0f * c.GetDeltaTime(), 0), 1.0e-4f);
}
TEST_CASE("TestKinematicVsKinematicSensorReversed")
{
// Same as TestKinematicVsKinematicSensor but with bodies created in reverse order (this matters for Body::sFindCollidingPairsCanCollide because MotionProperties::mIndexInActiveBodies is swapped between the bodies)
PhysicsTestContext c;
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
// Kinematic body moving downwards
Body &kinematic = c.CreateBox(RVec3(0, 2, 0), Quat::sIdentity(), EMotionType::Kinematic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
kinematic.SetLinearVelocity(Vec3(0, -1, 0));
// Kinematic sensor
BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Kinematic, Layers::SENSOR);
sensor_settings.mIsSensor = true;
BodyID sensor_id = c.GetBodyInterface().CreateAndAddBody(sensor_settings, EActivation::Activate);
// After a single step the kinematic object should not have touched the sensor yet
c.SimulateSingleStep();
CHECK(listener.GetEntryCount() == 0);
// After half a second and one step we should be touching the sensor
c.Simulate(0.5f + c.GetStepDeltaTime());
CHECK(listener.Contains(EType::Add, kinematic.GetID(), sensor_id));
listener.Clear();
// The next step we require that the contact persists
c.SimulateSingleStep();
CHECK(listener.Contains(EType::Persist, kinematic.GetID(), sensor_id));
CHECK(!listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
listener.Clear();
// After 3 more seconds we should have left the sensor at the bottom side
c.Simulate(3.0f);
CHECK(listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
CHECK_APPROX_EQUAL(kinematic.GetPosition(), RVec3(0, -1.5f - 3.0f * c.GetDeltaTime(), 0), 1.0e-4f);
}
TEST_CASE("TestDynamicSleepingVsStaticSensor")
{
PhysicsTestContext c;
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
// Sensor
BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::SENSOR);
sensor_settings.mIsSensor = true;
Body &sensor = *c.GetBodyInterface().CreateBody(sensor_settings);
c.GetBodyInterface().AddBody(sensor.GetID(), EActivation::DontActivate);
// Floor
Body &floor = c.CreateFloor();
// Dynamic body on floor (make them penetrate)
Body &dynamic = c.CreateBox(RVec3(0, 0.5f - c.GetSystem()->GetPhysicsSettings().mMaxPenetrationDistance, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f), EActivation::DontActivate);
// After a single step (because the object is sleeping) there should not be a contact
c.SimulateSingleStep();
CHECK(listener.GetEntryCount() == 0);
// The dynamic object should not be part of an island
CHECK(!sensor.IsActive());
CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() == Body::cInactiveIndex);
// Activate the body
c.GetBodyInterface().ActivateBody(dynamic.GetID());
// After a single step we should have detected the collision with the floor and the sensor
c.SimulateSingleStep();
CHECK(listener.GetEntryCount() == 4);
CHECK(listener.Contains(EType::Validate, floor.GetID(), dynamic.GetID()));
CHECK(listener.Contains(EType::Add, floor.GetID(), dynamic.GetID()));
CHECK(listener.Contains(EType::Validate, sensor.GetID(), dynamic.GetID()));
CHECK(listener.Contains(EType::Add, sensor.GetID(), dynamic.GetID()));
listener.Clear();
// The dynamic object should be part of an island now
CHECK(!sensor.IsActive());
CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
// After a second the body should have gone to sleep and the contacts should have been removed
c.Simulate(1.0f);
CHECK(!dynamic.IsActive());
CHECK(listener.Contains(EType::Remove, floor.GetID(), dynamic.GetID()));
CHECK(listener.Contains(EType::Remove, sensor.GetID(), dynamic.GetID()));
// The dynamic object should not be part of an island
CHECK(!sensor.IsActive());
CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() == Body::cInactiveIndex);
}
TEST_CASE("TestDynamicSleepingVsKinematicSensor")
{
PhysicsTestContext c;
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
// Kinematic sensor that is active (so will keep detecting contacts with sleeping bodies)
BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Kinematic, Layers::SENSOR);
sensor_settings.mIsSensor = true;
Body &sensor = *c.GetBodyInterface().CreateBody(sensor_settings);
c.GetBodyInterface().AddBody(sensor.GetID(), EActivation::Activate);
// Floor
Body &floor = c.CreateFloor();
// Dynamic body on floor (make them penetrate)
Body &dynamic = c.CreateBox(RVec3(0, 0.5f - c.GetSystem()->GetPhysicsSettings().mMaxPenetrationDistance, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f), EActivation::DontActivate);
// After a single step, there should be a contact with the sensor only (the sensor is active)
c.SimulateSingleStep();
CHECK(listener.GetEntryCount() == 2);
CHECK(listener.Contains(EType::Validate, sensor.GetID(), dynamic.GetID()));
CHECK(listener.Contains(EType::Add, sensor.GetID(), dynamic.GetID()));
listener.Clear();
// The sensor should be in its own island
CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() == Body::cInactiveIndex);
// The second step, the contact with the sensor should have persisted
c.SimulateSingleStep();
CHECK(listener.GetEntryCount() == 1);
CHECK(listener.Contains(EType::Persist, sensor.GetID(), dynamic.GetID()));
listener.Clear();
// The sensor should still be in its own island
CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() == Body::cInactiveIndex);
// Activate the body
c.GetBodyInterface().ActivateBody(dynamic.GetID());
// After a single step we should have detected collision with the floor and the collision with the sensor should have persisted
c.SimulateSingleStep();
CHECK(listener.GetEntryCount() == 3);
CHECK(listener.Contains(EType::Validate, floor.GetID(), dynamic.GetID()));
CHECK(listener.Contains(EType::Add, floor.GetID(), dynamic.GetID()));
CHECK(listener.Contains(EType::Persist, sensor.GetID(), dynamic.GetID()));
listener.Clear();
// The sensor should not be part of the same island as the dynamic body (they won't interact, so this is not needed)
CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != dynamic.GetMotionProperties()->GetIslandIndexInternal());
// After another step we should have persisted the collision with the floor and sensor
c.SimulateSingleStep();
CHECK(listener.GetEntryCount() >= 2); // Depending on if we used the contact cache or not there will be validate callbacks too
CHECK(listener.Contains(EType::Persist, floor.GetID(), dynamic.GetID()));
CHECK(!listener.Contains(EType::Remove, floor.GetID(), dynamic.GetID()));
CHECK(listener.Contains(EType::Persist, sensor.GetID(), dynamic.GetID()));
CHECK(!listener.Contains(EType::Remove, sensor.GetID(), dynamic.GetID()));
listener.Clear();
// The same islands as the previous step should have been created
CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != dynamic.GetMotionProperties()->GetIslandIndexInternal());
// After a second the body should have gone to sleep and the contacts with the floor should have been removed, but not with the sensor
c.Simulate(1.0f);
CHECK(!dynamic.IsActive());
CHECK(listener.Contains(EType::Remove, floor.GetID(), dynamic.GetID()));
CHECK(!listener.Contains(EType::Remove, sensor.GetID(), dynamic.GetID()));
}
TEST_CASE("TestSensorVsSensor")
{
for (int test = 0; test < 2; ++test)
{
bool sensor_detects_sensor = test == 1;
PhysicsTestContext c;
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
// Depending on the iteration we either place the sensor in the moving layer which means it will collide with other sensors
// or we put it in the sensor layer which means it won't collide with other sensors
ObjectLayer layer = sensor_detects_sensor? Layers::MOVING : Layers::SENSOR;
// Sensor 1
BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, layer);
sensor_settings.mIsSensor = true;
BodyID sensor_id1 = c.GetBodyInterface().CreateAndAddBody(sensor_settings, EActivation::DontActivate);
// Sensor 2 moving downwards
sensor_settings.mMotionType = EMotionType::Kinematic;
sensor_settings.mPosition = RVec3(0, 3, 0);
sensor_settings.mIsSensor = true;
sensor_settings.mLinearVelocity = Vec3(0, -2, 0);
BodyID sensor_id2 = c.GetBodyInterface().CreateAndAddBody(sensor_settings, EActivation::Activate);
// After a single step the sensors should not touch yet
c.SimulateSingleStep();
CHECK(listener.GetEntryCount() == 0);
// After half a second and one step the sensors should be touching
c.Simulate(0.5f + c.GetDeltaTime());
if (sensor_detects_sensor)
CHECK(listener.Contains(EType::Add, sensor_id1, sensor_id2));
else
CHECK(listener.GetEntryCount() == 0);
listener.Clear();
// The next step we require that the contact persists
c.SimulateSingleStep();
if (sensor_detects_sensor)
{
CHECK(listener.Contains(EType::Persist, sensor_id1, sensor_id2));
CHECK(!listener.Contains(EType::Remove, sensor_id1, sensor_id2));
}
else
CHECK(listener.GetEntryCount() == 0);
listener.Clear();
// After 2 more seconds we should have left the sensor at the bottom side
c.Simulate(2.0f);
if (sensor_detects_sensor)
CHECK(listener.Contains(EType::Remove, sensor_id1, sensor_id2));
else
CHECK(listener.GetEntryCount() == 0);
CHECK_APPROX_EQUAL(c.GetBodyInterface().GetPosition(sensor_id2), sensor_settings.mPosition + sensor_settings.mLinearVelocity * (2.5f + 3.0f * c.GetDeltaTime()), 1.0e-4f);
}
}
TEST_CASE("TestContactListenerMakesSensor")
{
PhysicsTestContext c;
c.ZeroGravity();
class SensorOverridingListener : public LoggingContactListener
{
public:
virtual void OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
{
LoggingContactListener::OnContactAdded(inBody1, inBody2, inManifold, ioSettings);
JPH_ASSERT(ioSettings.mIsSensor == false);
if (inBody1.GetID() == mBodyThatSeesSensorID || inBody2.GetID() == mBodyThatSeesSensorID)
ioSettings.mIsSensor = true;
}
virtual void OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
{
LoggingContactListener::OnContactPersisted(inBody1, inBody2, inManifold, ioSettings);
JPH_ASSERT(ioSettings.mIsSensor == false);
if (inBody1.GetID() == mBodyThatSeesSensorID || inBody2.GetID() == mBodyThatSeesSensorID)
ioSettings.mIsSensor = true;
}
BodyID mBodyThatSeesSensorID;
};
// Register listener
SensorOverridingListener listener;
c.GetSystem()->SetContactListener(&listener);
// Body that will appear as a sensor to one object and as static to another
BodyID static_id = c.GetBodyInterface().CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(5, 1, 5)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
// Dynamic body moving down that will do a normal collision
Body &dynamic1 = c.CreateBox(RVec3(-2, 2, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
dynamic1.SetAllowSleeping(false);
dynamic1.SetLinearVelocity(Vec3(0, -1, 0));
// Dynamic body moving down that will only see the static object as a sensor
Body &dynamic2 = c.CreateBox(RVec3(2, 2, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
dynamic2.SetAllowSleeping(false);
dynamic2.SetLinearVelocity(Vec3(0, -1, 0));
listener.mBodyThatSeesSensorID = dynamic2.GetID();
// After a single step the dynamic object should not have touched the sensor yet
c.SimulateSingleStep();
CHECK(listener.GetEntryCount() == 0);
// After half a second both bodies should be touching the sensor
c.Simulate(0.5f);
CHECK(listener.Contains(EType::Add, dynamic1.GetID(), static_id));
CHECK(listener.Contains(EType::Add, dynamic2.GetID(), static_id));
listener.Clear();
// The next step we require that the contact persists
c.SimulateSingleStep();
CHECK(listener.Contains(EType::Persist, dynamic1.GetID(), static_id));
CHECK(!listener.Contains(EType::Remove, dynamic1.GetID(), static_id));
CHECK(listener.Contains(EType::Persist, dynamic2.GetID(), static_id));
CHECK(!listener.Contains(EType::Remove, dynamic2.GetID(), static_id));
listener.Clear();
// After 3 more seconds one body should be resting on the static body, the other should have fallen through
c.Simulate(3.0f + c.GetDeltaTime());
CHECK(listener.Contains(EType::Persist, dynamic1.GetID(), static_id));
CHECK(!listener.Contains(EType::Remove, dynamic1.GetID(), static_id));
CHECK(listener.Contains(EType::Remove, dynamic2.GetID(), static_id));
CHECK_APPROX_EQUAL(dynamic1.GetPosition(), RVec3(-2, 1.5f, 0), 5.0e-3f);
CHECK_APPROX_EQUAL(dynamic2.GetPosition(), RVec3(2, -1.5f - 3.0f * c.GetDeltaTime(), 0), 1.0e-4f);
}
TEST_CASE("TestContactListenerMakesSensorCCD")
{
PhysicsTestContext c;
c.ZeroGravity();
const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
class SensorOverridingListener : public LoggingContactListener
{
public:
virtual void OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
{
LoggingContactListener::OnContactAdded(inBody1, inBody2, inManifold, ioSettings);
JPH_ASSERT(ioSettings.mIsSensor == false);
if (inBody1.GetID() == mBodyThatBecomesSensor || inBody2.GetID() == mBodyThatBecomesSensor)
ioSettings.mIsSensor = true;
}
virtual void OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
{
LoggingContactListener::OnContactPersisted(inBody1, inBody2, inManifold, ioSettings);
JPH_ASSERT(ioSettings.mIsSensor == false);
if (inBody1.GetID() == mBodyThatBecomesSensor || inBody2.GetID() == mBodyThatBecomesSensor)
ioSettings.mIsSensor = true;
}
BodyID mBodyThatBecomesSensor;
};
// Register listener
SensorOverridingListener listener;
c.GetSystem()->SetContactListener(&listener);
// Body that blocks the path
BodyID static_id = c.GetBodyInterface().CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(0.1f, 10, 10)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
// Dynamic body moving to the static object that will do a normal CCD collision
RVec3 dynamic1_pos(-0.5f, 2, 0);
Vec3 initial_velocity(500, 0, 0);
Body &dynamic1 = c.CreateBox(dynamic1_pos, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(0.1f));
dynamic1.SetAllowSleeping(false);
dynamic1.SetLinearVelocity(initial_velocity);
dynamic1.SetRestitution(1.0f);
// Dynamic body moving through the static object that will become a sensor an thus pass through
RVec3 dynamic2_pos(-0.5f, -2, 0);
Body &dynamic2 = c.CreateBox(dynamic2_pos, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(0.1f));
dynamic2.SetAllowSleeping(false);
dynamic2.SetLinearVelocity(initial_velocity);
dynamic2.SetRestitution(1.0f);
listener.mBodyThatBecomesSensor = dynamic2.GetID();
// After a single step the we should have contact added callbacks for both bodies
c.SimulateSingleStep();
CHECK(listener.Contains(EType::Add, dynamic1.GetID(), static_id));
CHECK(listener.Contains(EType::Add, dynamic2.GetID(), static_id));
listener.Clear();
CHECK_APPROX_EQUAL(dynamic1.GetPosition(), dynamic1_pos + RVec3(0.3f + cPenetrationSlop, 0, 0), 1.0e-4f); // Dynamic 1 should have moved to the surface of the static body
CHECK_APPROX_EQUAL(dynamic2.GetPosition(), dynamic2_pos + initial_velocity * c.GetDeltaTime(), 1.0e-4f); // Dynamic 2 should have passed through the static body because it became a sensor
// The next step the sensor should have its contact removed and the CCD body should have its contact persisted because it starts penetrating
c.SimulateSingleStep();
CHECK(listener.Contains(EType::Persist, dynamic1.GetID(), static_id));
CHECK(listener.Contains(EType::Remove, dynamic2.GetID(), static_id));
listener.Clear();
// The next step all contacts have been removed
c.SimulateSingleStep();
CHECK(listener.Contains(EType::Remove, dynamic1.GetID(), static_id));
listener.Clear();
}
TEST_CASE("TestSensorVsSubShapes")
{
PhysicsTestContext c;
BodyInterface &bi = c.GetBodyInterface();
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
// Create sensor
BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(5.0f)), RVec3(0, 10, 0), Quat::sIdentity(), EMotionType::Static, Layers::SENSOR);
sensor_settings.mIsSensor = true;
BodyID sensor_id = bi.CreateAndAddBody(sensor_settings, EActivation::DontActivate);
// We will be testing if we receive callbacks from the individual sub shapes
enum class EUserData
{
Bottom,
Middle,
Top,
};
// Create compound with 3 sub shapes
Ref<StaticCompoundShapeSettings> shape_settings = new StaticCompoundShapeSettings();
Ref<BoxShapeSettings> shape1 = new BoxShapeSettings(Vec3::sReplicate(0.4f));
shape1->mUserData = (uint64)EUserData::Bottom;
Ref<BoxShapeSettings> shape2 = new BoxShapeSettings(Vec3::sReplicate(0.4f));
shape2->mUserData = (uint64)EUserData::Middle;
Ref<BoxShapeSettings> shape3 = new BoxShapeSettings(Vec3::sReplicate(0.4f));
shape3->mUserData = (uint64)EUserData::Top;
shape_settings->AddShape(Vec3(0, -1.0f, 0), Quat::sIdentity(), shape1);
shape_settings->AddShape(Vec3(0, 0.0f, 0), Quat::sIdentity(), shape2);
shape_settings->AddShape(Vec3(0, 1.0f, 0), Quat::sIdentity(), shape3);
BodyCreationSettings compound_body_settings(shape_settings, RVec3(0, 20, 0), Quat::sIdentity(), JPH::EMotionType::Dynamic, Layers::MOVING);
compound_body_settings.mUseManifoldReduction = false; // Turn off manifold reduction for this body so that we can get proper callbacks for individual sub shapes
JPH::BodyID compound_body = bi.CreateAndAddBody(compound_body_settings, JPH::EActivation::Activate);
// Simulate until the body passes the origin
while (bi.GetPosition(compound_body).GetY() > 0.0f)
c.SimulateSingleStep();
// The expected events
struct Expected
{
EType mType;
EUserData mUserData;
};
const Expected expected[] = {
{ EType::Add, EUserData::Bottom },
{ EType::Add, EUserData::Middle },
{ EType::Add, EUserData::Top },
{ EType::Remove, EUserData::Bottom },
{ EType::Remove, EUserData::Middle },
{ EType::Remove, EUserData::Top }
};
const Expected *next = expected;
const Expected *end = expected + size(expected);
// Loop over events that we received
for (size_t e = 0; e < listener.GetEntryCount(); ++e)
{
const LoggingContactListener::LogEntry &entry = listener.GetEntry(e);
// Only interested in adds/removes
if (entry.mType != EType::Add && entry.mType != EType::Remove)
continue;
// Check if we have more expected events
if (next >= end)
{
CHECK(false);
break;
}
// Check if it is of expected type
CHECK(entry.mType == next->mType);
CHECK(entry.mBody1 == sensor_id);
CHECK(entry.mManifold.mSubShapeID1 == SubShapeID());
CHECK(entry.mBody2 == compound_body);
EUserData user_data = (EUserData)bi.GetShape(compound_body)->GetSubShapeUserData(entry.mManifold.mSubShapeID2);
CHECK(user_data == next->mUserData);
// Next expected event
++next;
}
// Check all expected events received
CHECK(next == end);
}
TEST_CASE("TestSensorVsStatic")
{
PhysicsTestContext c;
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
// Static body 1
Body &static1 = c.CreateSphere(RVec3::sZero(), 1.0f, EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, EActivation::DontActivate);
// Sensor
BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Kinematic, Layers::MOVING); // Put in layer that collides with static
sensor_settings.mIsSensor = true;
Body &sensor = *c.GetBodyInterface().CreateBody(sensor_settings);
BodyID sensor_id = sensor.GetID();
c.GetBodyInterface().AddBody(sensor_id, EActivation::Activate);
// Static body 2 (created after sensor to force higher body ID)
Body &static2 = c.CreateSphere(RVec3::sZero(), 1.0f, EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, EActivation::DontActivate);
// After a step we should not detect the static bodies
c.SimulateSingleStep();
CHECK(listener.GetEntryCount() == 0);
listener.Clear();
// Start detecting static
sensor.SetCollideKinematicVsNonDynamic(true);
// After a single step we should detect both static bodies
c.SimulateSingleStep();
CHECK(listener.GetEntryCount() == 4); // Should also contain validates
CHECK(listener.Contains(EType::Add, static1.GetID(), sensor_id));
CHECK(listener.Contains(EType::Add, static2.GetID(), sensor_id));
listener.Clear();
// Stop detecting static
sensor.SetCollideKinematicVsNonDynamic(false);
// After a single step we should stop detecting both static bodies
c.SimulateSingleStep();
CHECK(listener.GetEntryCount() == 2);
CHECK(listener.Contains(EType::Remove, static1.GetID(), sensor_id));
CHECK(listener.Contains(EType::Remove, static2.GetID(), sensor_id));
listener.Clear();
}
static void sCollideBodyVsBodyAll(const Body &inBody1, const Body &inBody2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, CollideShapeSettings &ioCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter)
{
// Override the back face mode so we get hits with all triangles
ioCollideShapeSettings.mBackFaceMode = EBackFaceMode::CollideWithBackFaces;
PhysicsSystem::sDefaultSimCollideBodyVsBody(inBody1, inBody2, inCenterOfMassTransform1, inCenterOfMassTransform2, ioCollideShapeSettings, ioCollector, inShapeFilter);
}
static void sCollideBodyVsBodyPerBody(const Body &inBody1, const Body &inBody2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, CollideShapeSettings &ioCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter)
{
// Max 1 hit per body pair
AnyHitCollisionCollector<CollideShapeCollector> collector;
SubShapeIDCreator part1, part2;
CollisionDispatch::sCollideShapeVsShape(inBody1.GetShape(), inBody2.GetShape(), Vec3::sOne(), Vec3::sOne(), inCenterOfMassTransform1, inCenterOfMassTransform2, part1, part2, ioCollideShapeSettings, collector);
if (collector.HadHit())
ioCollector.AddHit(collector.mHit);
}
static void sCollideBodyVsBodyPerLeaf(const Body &inBody1, const Body &inBody2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, CollideShapeSettings &ioCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter)
{
// Max 1 hit per leaf shape pair
SubShapeIDCreator part1, part2;
CollideShapeVsShapePerLeaf<AnyHitCollisionCollector<CollideShapeCollector>>(inBody1.GetShape(), inBody2.GetShape(), Vec3::sOne(), Vec3::sOne(), inCenterOfMassTransform1, inCenterOfMassTransform2, part1, part2, ioCollideShapeSettings, ioCollector, inShapeFilter);
}
TEST_CASE("TestSimCollideBodyVsBody")
{
// Create pyramid with flat top
MeshShapeSettings pyramid;
pyramid.mTriangleVertices = { Float3(1, 0, 1), Float3(1, 0, -1), Float3(-1, 0, -1), Float3(-1, 0, 1), Float3(0.1f, 1, 0.1f), Float3(0.1f, 1, -0.1f), Float3(-0.1f, 1, -0.1f), Float3(-0.1f, 1, 0.1f) };
pyramid.mIndexedTriangles = { IndexedTriangle(0, 1, 4), IndexedTriangle(4, 1, 5), IndexedTriangle(1, 2, 5), IndexedTriangle(2, 6, 5), IndexedTriangle(2, 3, 6), IndexedTriangle(3, 7, 6), IndexedTriangle(3, 0, 7), IndexedTriangle(0, 4, 7), IndexedTriangle(4, 5, 6), IndexedTriangle(4, 6, 7) };
pyramid.SetEmbedded();
// Create floor of many pyramids
StaticCompoundShapeSettings compound;
for (int x = -10; x <= 10; ++x)
for (int z = -10; z <= 10; ++z)
compound.AddShape(Vec3(x * 2.0f, 0, z * 2.0f), Quat::sIdentity(), &pyramid);
compound.SetEmbedded();
for (int type = 0; type < 3; ++type)
{
PhysicsTestContext c;
// Register listener
LoggingContactListener listener;
c.GetSystem()->SetContactListener(&listener);
// Create floor
BodyID floor_id = c.GetBodyInterface().CreateAndAddBody(BodyCreationSettings(&compound, RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
// A kinematic sensor that also detects static bodies
// Note that the size has been picked so that it is slightly smaller than 11 x 11 pyramids and incorporates all triangles in those pyramids
BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(10.99f)), RVec3(0, 5, 0), Quat::sIdentity(), EMotionType::Kinematic, Layers::MOVING); // Put in a layer that collides with static
sensor_settings.mIsSensor = true;
sensor_settings.mCollideKinematicVsNonDynamic = true;
sensor_settings.mUseManifoldReduction = false;
BodyID sensor_id = c.GetBodyInterface().CreateAndAddBody(sensor_settings, EActivation::Activate);
// Select the body vs body collision function
switch (type)
{
case 0:
c.GetSystem()->SetSimCollideBodyVsBody(sCollideBodyVsBodyAll);
break;
case 1:
c.GetSystem()->SetSimCollideBodyVsBody(sCollideBodyVsBodyPerLeaf);
break;
case 2:
c.GetSystem()->SetSimCollideBodyVsBody(sCollideBodyVsBodyPerBody);
break;
}
// Trigger collision callbacks
c.SimulateSingleStep();
// Count the number of hits that were detected
size_t count = 0;
for (size_t i = 0; i < listener.GetEntryCount(); ++i)
{
const LoggingContactListener::LogEntry &entry = listener.GetEntry(i);
if (entry.mType == EType::Add && entry.mBody1 == floor_id && entry.mBody2 == sensor_id)
++count;
}
// Check that we received the number of hits that we expected
switch (type)
{
case 0:
// All hits
CHECK(count == 11 * 11 * pyramid.mIndexedTriangles.size());
break;
case 1:
// Only 1 per sub shape
CHECK(count == 11 * 11);
break;
case 2:
// Only 1 per body pair
CHECK(count == 1);
break;
}
}
}
}