123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
- // SPDX-FileCopyrightText: 2025 Jorrit Rouwe
- // SPDX-License-Identifier: MIT
- #include <TestFramework.h>
- #include <Tests/General/SimCollideBodyVsBodyTest.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/CollisionCollectorImpl.h>
- #include <Jolt/Physics/Collision/CollisionDispatch.h>
- #include <Jolt/Physics/Body/BodyCreationSettings.h>
- #include <Jolt/Core/STLLocalAllocator.h>
- #include <Layers.h>
- #include <Renderer/DebugRendererImp.h>
- JPH_IMPLEMENT_RTTI_VIRTUAL(SimCollideBodyVsBodyTest)
- {
- JPH_ADD_BASE_CLASS(SimCollideBodyVsBodyTest, Test)
- }
- template <class LeafCollector>
- 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 <class LeafCollector>
- 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<LeafShape, STLLocalAllocator<LeafShape, 32>> 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<AnyHitCollisionCollector<CollideShapeCollector>>);
- break;
- case 2:
- mode_string = "Sensor: Collect deepest contact point per body";
- mPhysicsSystem->SetSimCollideBodyVsBody(&sCollideBodyVsBodyPerBody<ClosestHitCollisionCollector<CollideShapeCollector>>);
- break;
- case 3:
- mode_string = "Sensor: Collect any contact point per leaf shape";
- mPhysicsSystem->SetSimCollideBodyVsBody(&sCollideBodyVsBodyPerLeaf<AnyHitCollisionCollector<CollideShapeCollector>>);
- break;
- case 4:
- mode_string = "Sensor: Collect deepest contact point per leaf shape";
- mPhysicsSystem->SetSimCollideBodyVsBody(&sCollideBodyVsBodyPerLeaf<ClosestHitCollisionCollector<CollideShapeCollector>>);
- 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);
- }
|