// 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;
			}
		}
	}
}
