// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) // SPDX-FileCopyrightText: 2025 Jorrit Rouwe // SPDX-License-Identifier: MIT #include #include #include #include #include #include #include #include #include #include #include JPH_IMPLEMENT_RTTI_VIRTUAL(SimCollideBodyVsBodyTest) { JPH_ADD_BASE_CLASS(SimCollideBodyVsBodyTest, Test) } template static void sCollideBodyVsBodyPerBody(const Body &inBody1, const Body &inBody2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, CollideShapeSettings &ioCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter) { if (inBody1.IsSensor() || inBody2.IsSensor()) { // A sensor will return max 1 hit per body pair LeafCollector 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); } else { // If not a sensor: fall back to the default PhysicsSystem::sDefaultSimCollideBodyVsBody(inBody1, inBody2, inCenterOfMassTransform1, inCenterOfMassTransform2, ioCollideShapeSettings, ioCollector, inShapeFilter); } } template static void sCollideBodyVsBodyPerLeaf(const Body &inBody1, const Body &inBody2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, CollideShapeSettings &ioCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter) { if (inBody1.IsSensor() || inBody2.IsSensor()) { // A sensor will return 1 hit per leaf shape pair SubShapeIDCreator part1, part2; CollideShapeVsShapePerLeaf(inBody1.GetShape(), inBody2.GetShape(), Vec3::sOne(), Vec3::sOne(), inCenterOfMassTransform1, inCenterOfMassTransform2, part1, part2, ioCollideShapeSettings, ioCollector, inShapeFilter); } else { // If not a sensor: fall back to the default PhysicsSystem::sDefaultSimCollideBodyVsBody(inBody1, inBody2, inCenterOfMassTransform1, inCenterOfMassTransform2, ioCollideShapeSettings, ioCollector, inShapeFilter); } } void SimCollideBodyVsBodyTest::Initialize() { // 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(); mBodyInterface->CreateAndAddBody(BodyCreationSettings(&compound, RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate); // A kinematic sensor that also detects static bodies BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(10.0f)), 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; mSensorID = mBodyInterface->CreateAndAddBody(sensor_settings, EActivation::Activate); // Dynamic bodies for (int i = 0; i < 10; ++i) mBodyIDs.push_back(mBodyInterface->CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(0.1f, 0.5f, 0.2f)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate)); } void SimCollideBodyVsBodyTest::PrePhysicsUpdate(const PreUpdateParams &inParams) { // Update time mTime += inParams.mDeltaTime; const char *mode_string = nullptr; int mode = int(mTime / 3.0f) % 5; switch (mode) { default: mode_string = "Sensor: Collect all contact points"; mPhysicsSystem->SetSimCollideBodyVsBody(&PhysicsSystem::sDefaultSimCollideBodyVsBody); break; case 1: mode_string = "Sensor: Collect any contact point per body"; mPhysicsSystem->SetSimCollideBodyVsBody(&sCollideBodyVsBodyPerBody>); break; case 2: mode_string = "Sensor: Collect deepest contact point per body"; mPhysicsSystem->SetSimCollideBodyVsBody(&sCollideBodyVsBodyPerBody>); break; case 3: mode_string = "Sensor: Collect any contact point per leaf shape"; mPhysicsSystem->SetSimCollideBodyVsBody(&sCollideBodyVsBodyPerLeaf>); break; case 4: mode_string = "Sensor: Collect deepest contact point per leaf shape"; mPhysicsSystem->SetSimCollideBodyVsBody(&sCollideBodyVsBodyPerLeaf>); break; } DebugRenderer::sInstance->DrawText3D(RVec3(0, 5, 0), mode_string); // If the mode changes if (mode != mPrevMode) { // Start all bodies from the top for (int i = 0; i < (int)mBodyIDs.size(); ++i) { BodyID id = mBodyIDs[i]; mBodyInterface->SetPositionRotationAndVelocity(id, RVec3(-4.9_r + i * 1.0_r, 5.0_r, 0), Quat::sIdentity(), Vec3::sZero(), Vec3::sZero()); mBodyInterface->ActivateBody(id); } // Invalidate collisions with sensor to refresh contacts mBodyInterface->InvalidateContactCache(mSensorID); mPrevMode = mode; } } void SimCollideBodyVsBodyTest::OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) { if (!inBody1.IsSensor()) { mDebugRenderer->DrawWirePolygon(RMat44::sTranslation(inManifold.mBaseOffset), inManifold.mRelativeContactPointsOn1, Color::sGreen, 0.01f); Vec3 average = Vec3::sZero(); for (const Vec3 &p : inManifold.mRelativeContactPointsOn1) average += p; average /= (float)inManifold.mRelativeContactPointsOn1.size(); mDebugRenderer->DrawArrow(inManifold.mBaseOffset + average, inManifold.mBaseOffset + average - inManifold.mWorldSpaceNormal, Color::sYellow, 0.1f); } if (!inBody2.IsSensor()) { mDebugRenderer->DrawWirePolygon(RMat44::sTranslation(inManifold.mBaseOffset), inManifold.mRelativeContactPointsOn2, Color::sGreen, 0.01f); Vec3 average = Vec3::sZero(); for (const Vec3 &p : inManifold.mRelativeContactPointsOn2) average += p; average /= (float)inManifold.mRelativeContactPointsOn2.size(); mDebugRenderer->DrawArrow(inManifold.mBaseOffset + average, inManifold.mBaseOffset + average + inManifold.mWorldSpaceNormal, Color::sYellow, 0.1f); } } void SimCollideBodyVsBodyTest::OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) { OnContactAdded(inBody1, inBody2, inManifold, ioSettings); } void SimCollideBodyVsBodyTest::SaveState(StateRecorder &inStream) const { inStream.Write(mPrevMode); inStream.Write(mTime); } void SimCollideBodyVsBodyTest::RestoreState(StateRecorder &inStream) { inStream.Read(mPrevMode); inStream.Read(mTime); }