// 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()) { 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()) { // Tracks information we need about a leaf shape struct LeafShape { LeafShape() = default; LeafShape(const AABox &inBounds, Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const Shape *inShape, const SubShapeIDCreator &inSubShapeIDCreator) : mBounds(inBounds), mCenterOfMassTransform(inCenterOfMassTransform), mScale(inScale), mShape(inShape), mSubShapeIDCreator(inSubShapeIDCreator) { } AABox mBounds; Mat44 mCenterOfMassTransform; Vec3 mScale; const Shape * mShape; SubShapeIDCreator mSubShapeIDCreator; }; // A collector that stores the information we need from a leaf shape in an array that is usually on the stack but can fall back to the heap if needed class MyCollector : public TransformedShapeCollector { public: void AddHit(const TransformedShape &inShape) override { mHits.emplace_back(inShape.GetWorldSpaceBounds(), inShape.GetCenterOfMassTransform().ToMat44(), inShape.GetShapeScale(), inShape.mShape, inShape.mSubShapeIDCreator); } Array> mHits; }; // Get bounds of both shapes AABox bounds1 = inBody1.GetShape()->GetWorldSpaceBounds(inCenterOfMassTransform1, Vec3::sOne()); AABox bounds2 = inBody2.GetShape()->GetWorldSpaceBounds(inCenterOfMassTransform2, Vec3::sOne()); // Get leaf shapes that overlap with the bounds of the other shape SubShapeIDCreator part1, part2; MyCollector leaf_shapes1, leaf_shapes2; inBody1.GetShape()->CollectTransformedShapes(bounds2, inCenterOfMassTransform1.GetTranslation(), inCenterOfMassTransform1.GetQuaternion(), Vec3::sOne(), part1, leaf_shapes1, inShapeFilter); inBody2.GetShape()->CollectTransformedShapes(bounds1, inCenterOfMassTransform2.GetTranslation(), inCenterOfMassTransform2.GetQuaternion(), Vec3::sOne(), part2, leaf_shapes2, inShapeFilter); // Now test each leaf shape against each other leaf for (const LeafShape &leaf1 : leaf_shapes1.mHits) for (const LeafShape &leaf2 : leaf_shapes2.mHits) if (leaf1.mBounds.Overlaps(leaf2.mBounds)) { LeafCollector collector; CollisionDispatch::sCollideShapeVsShape(leaf1.mShape, leaf2.mShape, leaf1.mScale, leaf2.mScale, leaf1.mCenterOfMassTransform, leaf2.mCenterOfMassTransform, leaf1.mSubShapeIDCreator, leaf2.mSubShapeIDCreator, 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); } } 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); }