123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653 |
- // 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/StaticCompoundShape.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();
- }
- }
|