Ajout de Jolt Physics + 1ere version des factory entitecomposants - camera, transform, rigidbody, collider, renderer

This commit is contained in:
Tom Ray
2026-03-22 00:28:03 +01:00
parent 6695d46bcd
commit 48348936a8
1147 changed files with 214331 additions and 353 deletions

View File

@@ -0,0 +1,47 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/ActivateDuringUpdateTest.h>
#include <Layers.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(ActivateDuringUpdateTest)
{
JPH_ADD_BASE_CLASS(ActivateDuringUpdateTest, Test)
}
void ActivateDuringUpdateTest::Initialize()
{
// Floor
CreateFloor();
BodyCreationSettings settings;
settings.SetShape(new BoxShape(Vec3::sReplicate(0.5f)));
settings.mMotionType = EMotionType::Dynamic;
settings.mObjectLayer = Layers::MOVING;
const int cNumBodies = 3;
const float cPenetrationSlop = mPhysicsSystem->GetPhysicsSettings().mPenetrationSlop;
for (int i = 0; i < cNumBodies; ++i)
{
settings.mPosition = RVec3(i * (1.0f - cPenetrationSlop), 2.0f, 0);
BodyID body_id = mBodyInterface->CreateBody(settings)->GetID();
mBodyInterface->AddBody(body_id, EActivation::DontActivate);
if (i == 0)
mBodyInterface->SetLinearVelocity(body_id, Vec3(500, 0, 0));
}
for (int i = 0; i < cNumBodies; ++i)
{
settings.mPosition = RVec3(i * (1.0f - cPenetrationSlop), 2.0f, 2.0f);
BodyID body_id = mBodyInterface->CreateBody(settings)->GetID();
mBodyInterface->AddBody(body_id, EActivation::DontActivate);
if (i == cNumBodies - 1)
mBodyInterface->SetLinearVelocity(body_id, Vec3(-500, 0, 0));
}
}

View File

@@ -0,0 +1,23 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class ActivateDuringUpdateTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, ActivateDuringUpdateTest)
// Description of the test
virtual const char * GetDescription() const override
{
return "Three initially colliding boxes where only 1 is awake and has a high velocity.\n"
"The 2nd and 3rd box should wake up at the same time and not pass through each other.";
}
// Initialize the test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,98 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/ActiveEdgesTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/MeshShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Geometry/Triangle.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(ActiveEdgesTest)
{
JPH_ADD_BASE_CLASS(ActiveEdgesTest, Test)
}
void ActiveEdgesTest::Initialize()
{
const float cWidth = 5.0f;
const float cLength = 10.0f;
// Settings for a frictionless box
Ref<BoxShape> box_shape = new BoxShape(Vec3(1.0f, 1.0f, 1.0f), cDefaultConvexRadius);
BodyCreationSettings box_settings(box_shape, RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
box_settings.mFriction = 0.0f;
box_settings.mLinearDamping = 0.0f;
box_settings.mAllowSleeping = false;
// Create various triangle strips
TriangleList triangles;
for (int angle = -90; angle <= 90; angle++)
{
// Under which normal we want to place the block
Vec3 desired_normal = angle < 0? Vec3(0, 1, -1).Normalized() : Vec3(0, 1, 0);
float best_dot = -FLT_MAX;
// Place segments
float x = cWidth * angle;
Vec3 v1(x, 0.0f, -0.5f * cLength);
Vec3 v2(x + cWidth, 0.0f, -0.5f * cLength);
for (int total_angle = 0, cur_segment = 0; abs(total_angle) <= 90 && cur_segment < 90; total_angle += angle, ++cur_segment)
{
// Determine positions of end of this segment
float total_angle_rad = DegreesToRadians(float(total_angle));
Quat rotation = Quat::sRotation(Vec3::sAxisX(), total_angle_rad);
Vec3 delta = cLength * rotation.RotateAxisZ();
Vec3 v3 = v1 + delta;
Vec3 v4 = v2 + delta;
// Check if this segment is the best segment to place the dynamic block on
Vec3 normal = (v3 - v1).Cross(v2 - v1).Normalized();
float dot = normal.Dot(desired_normal);
if (dot > best_dot)
{
best_dot = dot;
box_settings.mPosition = RVec3((v1 + v2 + v3 + v4) / 4 + normal);
box_settings.mRotation = rotation;
}
// Add segment
triangles.push_back(Triangle(v1, v3, v4));
triangles.push_back(Triangle(v1, v4, v2));
// Add segment mirrored in Z axis
if (cur_segment != 0)
{
Vec3 flip(1, 1, -1);
triangles.push_back(Triangle(flip * v1, flip * v4, flip * v3));
triangles.push_back(Triangle(flip * v1, flip * v2, flip * v4));
}
// The end of the segment will be the start for the next iteration
v1 = v3;
v2 = v4;
}
// Place box on best segment
Body &body = *mBodyInterface->CreateBody(box_settings);
mBodyInterface->AddBody(body.GetID(), EActivation::Activate);
// For convex segments give the block a push
if (angle >= 0)
body.SetLinearVelocity(Vec3(0, 0, 2.0f));
}
// Mesh shape
MeshShapeSettings mesh_shape(triangles);
mesh_shape.SetEmbedded();
mesh_shape.mActiveEdgeCosThresholdAngle = Cos(DegreesToRadians(50.0f));
// Mesh
BodyCreationSettings mesh_settings(&mesh_shape, RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
// Instead of setting mActiveEdgeCosThresholdAngle you can also set: mesh_settings.mEnhancedInternalEdgeRemoval = true
mesh_settings.mFriction = 0.0f;
mBodyInterface->CreateAndAddBody(mesh_settings, EActivation::DontActivate);
}

View File

@@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class ActiveEdgesTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, ActiveEdgesTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Boxes sliding over the ramps should not collide with internal triangle edges of the ramp (aka ghost collisions).";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,62 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/AllowedDOFsTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Constraints/DistanceConstraint.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Renderer/DebugRendererImp.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(AllowedDOFsTest)
{
JPH_ADD_BASE_CLASS(AllowedDOFsTest, Test)
}
void AllowedDOFsTest::Initialize()
{
// Floor
CreateFloor();
Vec3 box_size(0.5f, 1.0f, 2.0f);
RefConst<Shape> box_shape = new BoxShape(box_size);
for (int allowed_dofs = 1; allowed_dofs <= 0b111111; ++allowed_dofs)
{
float x = -35.0f + 10.0f * (allowed_dofs & 0b111);
float z = -35.0f + 10.0f * ((allowed_dofs >> 3) & 0b111);
// Create body
BodyCreationSettings bcs(box_shape, RVec3(x, 10, z), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
bcs.mAllowedDOFs = (EAllowedDOFs)allowed_dofs;
BodyID id = mBodyInterface->CreateAndAddBody(bcs, EActivation::Activate);
mBodies.push_back(id);
// Create a constraint
DistanceConstraintSettings dcs;
dcs.mPoint1 = bcs.mPosition + Vec3(5, 5, 5);
dcs.mPoint2 = bcs.mPosition + box_size;
dcs.mMinDistance = 0.0f;
dcs.mMaxDistance = sqrt(3.0f) * 5.0f + 1.0f;
mPhysicsSystem->AddConstraint(mBodyInterface->CreateConstraint(&dcs, BodyID(), id));
// Draw degrees of freedom
String allowed_dofs_str = "";
if ((EAllowedDOFs(allowed_dofs) & EAllowedDOFs::TranslationX) == EAllowedDOFs::TranslationX)
allowed_dofs_str += "X ";
if ((EAllowedDOFs(allowed_dofs) & EAllowedDOFs::TranslationY) == EAllowedDOFs::TranslationY)
allowed_dofs_str += "Y ";
if ((EAllowedDOFs(allowed_dofs) & EAllowedDOFs::TranslationZ) == EAllowedDOFs::TranslationZ)
allowed_dofs_str += "Z ";
if ((EAllowedDOFs(allowed_dofs) & EAllowedDOFs::RotationX) == EAllowedDOFs::RotationX)
allowed_dofs_str += "RX ";
if ((EAllowedDOFs(allowed_dofs) & EAllowedDOFs::RotationY) == EAllowedDOFs::RotationY)
allowed_dofs_str += "RY ";
if ((EAllowedDOFs(allowed_dofs) & EAllowedDOFs::RotationZ) == EAllowedDOFs::RotationZ)
allowed_dofs_str += "RZ ";
SetBodyLabel(id, allowed_dofs_str);
}
}

View File

@@ -0,0 +1,26 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class AllowedDOFsTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, AllowedDOFsTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Shows all permutations of allowed degrees of freedom for a body (see EAllowedDOFs).\n"
"The boxes are constrained to the world using a distance constraint, press C to show it.";
}
// See: Test
virtual void Initialize() override;
private:
BodyIDVector mBodies;
};

View File

@@ -0,0 +1,30 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/BigVsSmallTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/MeshShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Geometry/Triangle.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(BigVsSmallTest)
{
JPH_ADD_BASE_CLASS(BigVsSmallTest, Test)
}
void BigVsSmallTest::Initialize()
{
// Create a big triangle
TriangleList triangles;
triangles.push_back(Triangle(Float3(-100, 0, 0), Float3(0, 0, 100), Float3(100, 0, -100)));
mBodyInterface->CreateAndAddBody(BodyCreationSettings(new MeshShapeSettings(triangles), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
// A small box
Body &body = *mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3(0.1f, 0.1f, 0.1f)), RVec3(0, 1.0f, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
body.SetAllowSleeping(false);
mBodyInterface->AddBody(body.GetID(), EActivation::Activate);
}

View File

@@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class BigVsSmallTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, BigVsSmallTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "A small box falling on a big triangle to test for numerical precision errors.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,49 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/CenterOfMassTest.h>
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
#include <Jolt/Physics/Collision/Shape/ConvexHullShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(CenterOfMassTest)
{
JPH_ADD_BASE_CLASS(CenterOfMassTest, Test)
}
void CenterOfMassTest::Initialize()
{
// Floor
CreateFloor();
// Compound shape with center of mass offset
Ref<StaticCompoundShapeSettings> compound_shape1 = new StaticCompoundShapeSettings;
compound_shape1->AddShape(Vec3(10, 0, 0), Quat::sIdentity(), new SphereShape(2));
mBodyInterface->CreateAndAddBody(BodyCreationSettings(compound_shape1, RVec3(0, 10.0f, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
// Create box with center of mass offset
Array<Vec3> box;
box.push_back(Vec3(10, 10, 10));
box.push_back(Vec3(5, 10, 10));
box.push_back(Vec3(10, 5, 10));
box.push_back(Vec3(5, 5, 10));
box.push_back(Vec3(10, 10, 5));
box.push_back(Vec3(5, 10, 5));
box.push_back(Vec3(10, 5, 5));
box.push_back(Vec3(5, 5, 5));
mBodyInterface->CreateAndAddBody(BodyCreationSettings(new ConvexHullShapeSettings(box), RVec3(0, 10.0f, 20.0f), Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
// Compound
Ref<StaticCompoundShapeSettings> compound_shape2 = new StaticCompoundShapeSettings;
Quat rotation = Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI);
compound_shape2->AddShape(Vec3(10, 0, 0), rotation, new CapsuleShape(5, 1));
compound_shape2->AddShape(rotation * Vec3(10, -5, 0), Quat::sIdentity(), new SphereShape(4));
compound_shape2->AddShape(rotation * Vec3(10, 5, 0), Quat::sIdentity(), new SphereShape(2));
mBodyInterface->CreateAndAddBody(BodyCreationSettings(compound_shape2, RVec3(0, 10.0f, 40.0f), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
}

View File

@@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class CenterOfMassTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, CenterOfMassTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Spawns various shapes with the center of mass not in the center of the object.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,82 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/ChangeMotionQualityTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(ChangeMotionQualityTest)
{
JPH_ADD_BASE_CLASS(ChangeMotionQualityTest, Test)
}
void ChangeMotionQualityTest::Initialize()
{
// Floor
CreateFloor();
// Single shape that has 4 walls to surround fast moving sphere
BodyCreationSettings enclosing_settings;
Ref<BoxShapeSettings> box_shape = new BoxShapeSettings(Vec3(5.0f, 1.0f, 0.1f));
Ref<StaticCompoundShapeSettings> enclosing_shape = new StaticCompoundShapeSettings();
enclosing_shape->AddShape(Vec3(0, 0, 5), Quat::sIdentity(), box_shape);
enclosing_shape->AddShape(Vec3(0, 0, -5), Quat::sIdentity(), box_shape);
enclosing_shape->AddShape(Vec3(5, 0, 0), Quat::sRotation(Vec3::sAxisY(), 0.5f * JPH_PI), box_shape);
enclosing_shape->AddShape(Vec3(-5, 0, 0), Quat::sRotation(Vec3::sAxisY(), 0.5f * JPH_PI), box_shape);
enclosing_settings.SetShapeSettings(enclosing_shape);
enclosing_settings.mMotionType = EMotionType::Kinematic;
enclosing_settings.mObjectLayer = Layers::MOVING;
enclosing_settings.mPosition = RVec3(0, 1, 0);
mBodyInterface->CreateAndAddBody(enclosing_settings, EActivation::Activate);
// Create high speed sphere inside
BodyCreationSettings settings;
settings.SetShape(new SphereShape(1.0f));
settings.mPosition = RVec3(0, 0.5f, 0);
settings.mMotionType = EMotionType::Dynamic;
settings.mLinearVelocity = Vec3(-240, 0, -120);
settings.mFriction = 0.0f;
settings.mRestitution = 1.0f;
settings.mObjectLayer = Layers::MOVING;
mBody = mBodyInterface->CreateBody(settings);
mBodyInterface->AddBody(mBody->GetID(), EActivation::Activate);
UpdateMotionQuality();
}
void ChangeMotionQualityTest::UpdateMotionQuality()
{
static EMotionQuality qualities[] = { EMotionQuality::LinearCast, EMotionQuality::Discrete };
static const char *labels[] = { "LinearCast", "Discrete" };
// Calculate desired motion quality
int idx = int(mTime) & 1;
mBodyInterface->SetMotionQuality(mBody->GetID(), qualities[idx]);
SetBodyLabel(mBody->GetID(), labels[idx]);
}
void ChangeMotionQualityTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
{
// Increment time
mTime += inParams.mDeltaTime;
UpdateMotionQuality();
}
void ChangeMotionQualityTest::SaveState(StateRecorder &inStream) const
{
inStream.Write(mTime);
}
void ChangeMotionQualityTest::RestoreState(StateRecorder &inStream)
{
inStream.Read(mTime);
UpdateMotionQuality();
}

View File

@@ -0,0 +1,33 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
#include <Jolt/Physics/Body/Body.h>
class ChangeMotionQualityTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, ChangeMotionQualityTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Switches a body's motion quality from linear to discrete.\n"
"After the switch, the high speed body passes through the wall.";
}
// See: Test
virtual void Initialize() override;
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
virtual void SaveState(StateRecorder &inStream) const override;
virtual void RestoreState(StateRecorder &inStream) override;
private:
void UpdateMotionQuality();
Body * mBody = nullptr;
float mTime = 0.0f;
};

View File

@@ -0,0 +1,75 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/ChangeMotionTypeTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(ChangeMotionTypeTest)
{
JPH_ADD_BASE_CLASS(ChangeMotionTypeTest, Test)
}
void ChangeMotionTypeTest::Initialize()
{
// Floor
CreateFloor();
// Create body as static, but allow to become dynamic
BodyCreationSettings settings;
settings.SetShape(new BoxShape(Vec3(0.5f, 1.0f, 2.0f)));
settings.mPosition = RVec3(0, 10, 0);
settings.mMotionType = EMotionType::Static;
settings.mObjectLayer = Layers::MOVING; // Put in moving layer, this will result in some overhead when the body is static
settings.mAllowDynamicOrKinematic = true;
mBody = mBodyInterface->CreateBody(settings);
mBodyInterface->AddBody(mBody->GetID(), EActivation::Activate);
UpdateMotionType();
}
void ChangeMotionTypeTest::UpdateMotionType()
{
static const EMotionType cycle[] = { EMotionType::Dynamic, EMotionType::Kinematic, EMotionType::Static, EMotionType::Kinematic, EMotionType::Dynamic, EMotionType::Static };
static const char *label[] = { "Dynamic", "Kinematic", "Static", "Kinematic", "Dynamic", "Static" };
// Calculate desired motion type
int idx = int(mTime) % size(cycle);
EMotionType motion_type = cycle[idx];
// Update motion type and reactivate the body
if (motion_type != mBody->GetMotionType())
{
mBodyInterface->SetMotionType(mBody->GetID(), motion_type, EActivation::Activate);
SetBodyLabel(mBody->GetID(), label[idx]);
}
}
void ChangeMotionTypeTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
{
// Increment time
mTime += inParams.mDeltaTime;
UpdateMotionType();
// Provide kinematic body a target
if (mBody->IsKinematic())
mBody->MoveKinematic(RVec3(Sin(mTime), 10, 0), Quat::sRotation(Vec3::sAxisX(), Cos(mTime)), inParams.mDeltaTime);
}
void ChangeMotionTypeTest::SaveState(StateRecorder &inStream) const
{
inStream.Write(mTime);
}
void ChangeMotionTypeTest::RestoreState(StateRecorder &inStream)
{
inStream.Read(mTime);
UpdateMotionType();
}

View File

@@ -0,0 +1,32 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
#include <Jolt/Physics/Body/Body.h>
class ChangeMotionTypeTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, ChangeMotionTypeTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Switches a body's motion type between static, kinematic and dynamic.";
}
// See: Test
virtual void Initialize() override;
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
virtual void SaveState(StateRecorder &inStream) const override;
virtual void RestoreState(StateRecorder &inStream) override;
private:
void UpdateMotionType();
Body * mBody = nullptr;
float mTime = 0.0f;
};

View File

@@ -0,0 +1,81 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/ChangeObjectLayerTest.h>
#include <Layers.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(ChangeObjectLayerTest)
{
JPH_ADD_BASE_CLASS(ChangeObjectLayerTest, Test)
}
void ChangeObjectLayerTest::Initialize()
{
// Floor
CreateFloor();
// A dynamic box in the MOVING layer
mMoving = mBodyInterface->CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(5, 0.1f, 5)), RVec3(0, 1.5f, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
// Lots of dynamic objects in the DEBRIS layer
default_random_engine random;
uniform_real_distribution<float> position_variation(-10, 10);
for (int i = 0; i < 50; ++i)
{
RVec3 position(position_variation(random), 2.0f, position_variation(random));
Quat rotation = Quat::sRandom(random);
mDebris.push_back(mBodyInterface->CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3::sReplicate(0.1f)), position, rotation, EMotionType::Dynamic, Layers::DEBRIS), EActivation::Activate));
}
}
void ChangeObjectLayerTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
{
const float cSwitchTime = 2.0f;
// Increment time
mTime += inParams.mDeltaTime;
if (mTime >= cSwitchTime)
{
mIsDebris = !mIsDebris;
// Reposition moving object
mBodyInterface->SetPosition(mMoving, RVec3(0, 1.5f, 0), EActivation::Activate);
default_random_engine random;
uniform_real_distribution<float> position_variation(-7.5f, 7.5f);
for (BodyID id : mDebris)
{
// Reposition debris
RVec3 position(position_variation(random), 2.0f, position_variation(random));
Quat rotation = Quat::sRandom(random);
mBodyInterface->SetPositionAndRotation(id, position, rotation, EActivation::Activate);
// And update layer
mBodyInterface->SetObjectLayer(id, mIsDebris? Layers::DEBRIS : Layers::MOVING);
}
mTime = 0;
}
}
void ChangeObjectLayerTest::SaveState(StateRecorder &inStream) const
{
inStream.Write(mTime);
inStream.Write(mIsDebris);
}
void ChangeObjectLayerTest::RestoreState(StateRecorder &inStream)
{
inStream.Read(mTime);
inStream.Read(mIsDebris);
// Restore layer
for (BodyID id : mDebris)
mBodyInterface->SetObjectLayer(id, mIsDebris? Layers::DEBRIS : Layers::MOVING);
}

View File

@@ -0,0 +1,37 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
#include <Jolt/Physics/Body/BodyID.h>
class ChangeObjectLayerTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, ChangeObjectLayerTest)
// Description of the test
virtual const char * GetDescription() const override
{
return "Demonstrates how to use layers to disable collisions with other objects and how to change layers on the fly.\n"
"The small cubes will switch between the MOVING layer and the DEBRIS layer (debris only collides with the static floor).";
}
// Initialize the test
virtual void Initialize() override;
// Update the test, called before the physics update
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
// Saving / restoring state for replay
virtual void SaveState(StateRecorder &inStream) const override;
virtual void RestoreState(StateRecorder &inStream) override;
private:
BodyID mMoving;
BodyIDVector mDebris;
bool mIsDebris = true;
float mTime = 0.0f;
};

View File

@@ -0,0 +1,87 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/ChangeShapeTest.h>
#include <Layers.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
#include <Jolt/Physics/Collision/Shape/TaperedCapsuleShape.h>
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Application/DebugUI.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(ChangeShapeTest)
{
JPH_ADD_BASE_CLASS(ChangeShapeTest, Test)
}
void ChangeShapeTest::Initialize()
{
// Floor
CreateFloor();
// Shapes
mShapes.push_back(new BoxShape(Vec3(0.5f, 1.5f, 0.5f)));
mShapes.push_back(new SphereShape(0.5f));
mShapes.push_back(new CapsuleShape(1.0f, 0.5f));
mShapes.push_back(TaperedCapsuleShapeSettings(1.0f, 0.5f, 0.3f).Create().Get());
// Compound with center of mass shifted (this requires a correction of the position in the body)
StaticCompoundShapeSettings compound_settings;
compound_settings.AddShape(Vec3(0, 1.5f, 0), Quat::sIdentity(), new CapsuleShape(1.5f, 0.5f));
compound_settings.AddShape(Vec3(0, 3, 0), Quat::sIdentity(), new SphereShape(1));
RefConst<Shape> compound = compound_settings.Create().Get();
mShapes.push_back(compound);
// Create dynamic body that changes shape
BodyCreationSettings settings;
settings.SetShape(mShapes[mShapeIdx]);
settings.mPosition = RVec3(0, 10, 0);
settings.mMotionType = EMotionType::Dynamic;
settings.mObjectLayer = Layers::MOVING;
mBodyID = mBodyInterface->CreateBody(settings)->GetID();
mBodyInterface->AddBody(mBodyID, EActivation::Activate);
}
void ChangeShapeTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
{
const float cSwitchTime = 3.0f;
// Increment time
mTime += inParams.mDeltaTime;
// Get new shape
int shape_idx = int(mTime / cSwitchTime) % mShapes.size();
// Change shape
if (mShapeIdx != shape_idx)
{
mShapeIdx = shape_idx;
mBodyInterface->SetShape(mBodyID, mShapes[mShapeIdx], true, mActivateAfterSwitch? EActivation::Activate : EActivation::DontActivate);
}
}
void ChangeShapeTest::SaveState(StateRecorder &inStream) const
{
inStream.Write(mTime);
inStream.Write(mShapeIdx);
}
void ChangeShapeTest::RestoreState(StateRecorder &inStream)
{
inStream.Read(mTime);
inStream.Read(mShapeIdx);
// Reset the shape to what was stored
mBodyInterface->SetShape(mBodyID, mShapes[mShapeIdx], true, EActivation::DontActivate);
}
void ChangeShapeTest::CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu)
{
inUI->CreateCheckBox(inSubMenu, "Activate Body After Switch", mActivateAfterSwitch, [this](UICheckBox::EState inState) { mActivateAfterSwitch = inState == UICheckBox::STATE_CHECKED; });
}

View File

@@ -0,0 +1,42 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
#include <Jolt/Physics/Body/BodyID.h>
#include <Jolt/Physics/Collision/Shape/Shape.h>
class ChangeShapeTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, ChangeShapeTest)
// Description of the test
virtual const char * GetDescription() const override
{
return "Demonstrates how to dynamically update the shape of a body.";
}
// Initialize the test
virtual void Initialize() override;
// Update the test, called before the physics update
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
// Saving / restoring state for replay
virtual void SaveState(StateRecorder &inStream) const override;
virtual void RestoreState(StateRecorder &inStream) override;
// Optional settings menu
virtual bool HasSettingsMenu() const override { return true; }
virtual void CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu) override;
private:
bool mActivateAfterSwitch = true;
BodyID mBodyID;
Array<RefConst<Shape>> mShapes;
float mTime = 0.0f;
int mShapeIdx = 0;
};

View File

@@ -0,0 +1,151 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/ContactListenerTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
#include <Jolt/Physics/Collision/EstimateCollisionResponse.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
#include <Renderer/DebugRendererImp.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(ContactListenerTest)
{
JPH_ADD_BASE_CLASS(ContactListenerTest, Test)
}
void ContactListenerTest::Initialize()
{
// Floor
CreateFloor();
RefConst<Shape> box_shape = new BoxShape(Vec3(0.5f, 1.0f, 2.0f));
// Dynamic body 1, this body will have restitution 1 for new contacts and restitution 0 for persisting contacts
Body &body1 = *mBodyInterface->CreateBody(BodyCreationSettings(box_shape, RVec3(0, 10, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
body1.SetAllowSleeping(false);
mBodyInterface->AddBody(body1.GetID(), EActivation::Activate);
// Dynamic body 2
Body &body2 = *mBodyInterface->CreateBody(BodyCreationSettings(box_shape, RVec3(5, 10, 0), Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI), EMotionType::Dynamic, Layers::MOVING));
body2.SetAllowSleeping(false);
mBodyInterface->AddBody(body2.GetID(), EActivation::Activate);
// Dynamic body 3
Body &body3 = *mBodyInterface->CreateBody(BodyCreationSettings(new SphereShape(2.0f), RVec3(10, 10, 0), Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI), EMotionType::Dynamic, Layers::MOVING));
body3.SetAllowSleeping(false);
mBodyInterface->AddBody(body3.GetID(), EActivation::Activate);
// Dynamic body 4
Ref<StaticCompoundShapeSettings> compound_shape = new StaticCompoundShapeSettings;
compound_shape->AddShape(Vec3::sZero(), Quat::sIdentity(), new CapsuleShape(5, 1));
compound_shape->AddShape(Vec3(0, -5, 0), Quat::sIdentity(), new SphereShape(2));
compound_shape->AddShape(Vec3(0, 5, 0), Quat::sIdentity(), new SphereShape(2));
Body &body4 = *mBodyInterface->CreateBody(BodyCreationSettings(compound_shape, RVec3(15, 10, 0), Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI), EMotionType::Dynamic, Layers::MOVING));
body4.SetAllowSleeping(false);
mBodyInterface->AddBody(body4.GetID(), EActivation::Activate);
// Dynamic body 5, a cube with a bigger cube surrounding it that acts as a sensor
Ref<StaticCompoundShapeSettings> compound_shape2 = new StaticCompoundShapeSettings;
compound_shape2->AddShape(Vec3::sZero(), Quat::sIdentity(), new BoxShape(Vec3::sReplicate(1)));
compound_shape2->AddShape(Vec3::sZero(), Quat::sIdentity(), new BoxShape(Vec3::sReplicate(2))); // This will become a sensor in the contact callback
BodyCreationSettings bcs5(compound_shape2, RVec3(20, 10, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
bcs5.mUseManifoldReduction = false; // Needed in order to prevent the physics system from combining contacts between sensor and non-sensor sub shapes
Body &body5 = *mBodyInterface->CreateBody(bcs5);
body5.SetAllowSleeping(false);
mBodyInterface->AddBody(body5.GetID(), EActivation::Activate);
// Store bodies for later use
mBody[0] = &body1;
mBody[1] = &body2;
mBody[2] = &body3;
mBody[3] = &body4;
mBody[4] = &body5;
}
void ContactListenerTest::PostPhysicsUpdate(float inDeltaTime)
{
// Check that predicted velocities match actual velocities
lock_guard lock(mPredictedVelocitiesMutex);
for (const PredictedVelocity &v : mPredictedVelocities)
{
BodyLockRead body_lock(mPhysicsSystem->GetBodyLockInterface(), v.mBodyID);
if (body_lock.Succeeded())
{
const Body &body = body_lock.GetBody();
Vec3 linear_velocity = body.GetLinearVelocity();
Vec3 angular_velocity = body.GetAngularVelocity();
float diff_v = (v.mLinearVelocity - linear_velocity).Length();
float diff_w = (v.mAngularVelocity - angular_velocity).Length();
if (diff_v > 1.0e-3f || diff_w > 1.0e-3f)
Trace("Mispredicted collision for body: %08x, v=%s, w=%s, predicted_v=%s, predicted_w=%s, diff_v=%f, diff_w=%f",
body.GetID().GetIndex(),
ConvertToString(linear_velocity).c_str(), ConvertToString(angular_velocity).c_str(),
ConvertToString(v.mLinearVelocity).c_str(), ConvertToString(v.mAngularVelocity).c_str(),
(double)diff_v,
(double)diff_w);
}
}
mPredictedVelocities.clear();
}
ValidateResult ContactListenerTest::OnContactValidate(const Body &inBody1, const Body &inBody2, RVec3Arg inBaseOffset, const CollideShapeResult &inCollisionResult)
{
// Body 1 and 2 should never collide
return ((&inBody1 == mBody[0] && &inBody2 == mBody[1]) || (&inBody1 == mBody[1] && &inBody2 == mBody[0]))? ValidateResult::RejectAllContactsForThisBodyPair : ValidateResult::AcceptAllContactsForThisBodyPair;
}
void ContactListenerTest::MakeBody5PartialSensor(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings)
{
// Make the 2nd shape of body 5 a sensor
SubShapeID body5_subshape_1 = StaticCast<CompoundShape>(mBody[4]->GetShape())->GetSubShapeIDFromIndex(1, SubShapeIDCreator()).GetID();
if ((&inBody1 == mBody[4] && inManifold.mSubShapeID1 == body5_subshape_1)
|| (&inBody2 == mBody[4] && inManifold.mSubShapeID2 == body5_subshape_1))
{
Trace("Sensor contact detected between body %08x and body %08x", inBody1.GetID().GetIndexAndSequenceNumber(), inBody2.GetID().GetIndexAndSequenceNumber());
ioSettings.mIsSensor = true;
}
}
void ContactListenerTest::OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings)
{
// Make body 1 bounce only when a new contact point is added but not when it is persisted (its restitution is normally 0)
if (&inBody1 == mBody[0] || &inBody2 == mBody[0])
{
JPH_ASSERT(ioSettings.mCombinedRestitution == 0.0f);
ioSettings.mCombinedRestitution = 1.0f;
}
MakeBody5PartialSensor(inBody1, inBody2, inManifold, ioSettings);
// Estimate the contact impulses.
CollisionEstimationResult result;
EstimateCollisionResponse(inBody1, inBody2, inManifold, result, ioSettings.mCombinedFriction, ioSettings.mCombinedRestitution);
// Trace the result
String impulses_str;
for (const CollisionEstimationResult::Impulse &impulse : result.mImpulses)
impulses_str += StringFormat("(%f, %f, %f) ", (double)impulse.mContactImpulse, (double)impulse.mFrictionImpulse1, (double)impulse.mFrictionImpulse2);
Trace("Estimated velocity after collision, body1: %08x, v=%s, w=%s, body2: %08x, v=%s, w=%s, impulses: %s",
inBody1.GetID().GetIndex(), ConvertToString(result.mLinearVelocity1).c_str(), ConvertToString(result.mAngularVelocity1).c_str(),
inBody2.GetID().GetIndex(), ConvertToString(result.mLinearVelocity2).c_str(), ConvertToString(result.mAngularVelocity2).c_str(),
impulses_str.c_str());
// Log predicted velocities
{
lock_guard lock(mPredictedVelocitiesMutex);
mPredictedVelocities.push_back({ inBody1.GetID(), result.mLinearVelocity1, result.mAngularVelocity1 });
mPredictedVelocities.push_back({ inBody2.GetID(), result.mLinearVelocity2, result.mAngularVelocity2 });
}
}
void ContactListenerTest::OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings)
{
MakeBody5PartialSensor(inBody1, inBody2, inManifold, ioSettings);
}

View File

@@ -0,0 +1,51 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
#include <Jolt/Physics/Collision/ContactListener.h>
class ContactListenerTest : public Test, public ContactListener
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, ContactListenerTest)
// Description of the test
virtual const char * GetDescription() const override
{
return "Demonstrates how to listen for contact events.\n"
"Leftmost box ignores contacts with the 2nd box and overrides the restitution to 1 for non-persisted contacts.\n"
"Rightmost box contains an inner and an outer shape, the outer shape acts as a sensor.\n"
"The TTY will output estimated post collision velocities.";
}
// See: Test
virtual void Initialize() override;
virtual void PostPhysicsUpdate(float inDeltaTime) override;
// If this test implements a contact listener, it should be returned here
virtual ContactListener *GetContactListener() override { return this; }
// See: ContactListener
virtual ValidateResult OnContactValidate(const Body &inBody1, const Body &inBody2, RVec3Arg inBaseOffset, const CollideShapeResult &inCollisionResult) override;
virtual void OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override;
virtual void OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override;
private:
void MakeBody5PartialSensor(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings);
// The 5 bodies that we create
Body * mBody[5];
// Tracks predicted velocities so we can compare them with the actual velocities after time step
struct PredictedVelocity
{
BodyID mBodyID;
Vec3 mLinearVelocity;
Vec3 mAngularVelocity;
};
Mutex mPredictedVelocitiesMutex;
Array<PredictedVelocity> mPredictedVelocities;
};

View File

@@ -0,0 +1,36 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/ContactManifoldTest.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(ContactManifoldTest)
{
JPH_ADD_BASE_CLASS(ContactManifoldTest, Test)
}
void ContactManifoldTest::Initialize()
{
// Floor
CreateFloor();
RefConst<Shape> big_box = new BoxShape(Vec3(4, 4, 4), 0.0f);
RefConst<Shape> capsule = new CapsuleShape(5, 2);
RefConst<Shape> long_box = new BoxShape(Vec3(2, 7, 2));
for (int i = 0; i < 3; ++i)
for (int j = 0; j < 2; ++j)
{
// Create a box
mBodyInterface->CreateAndAddBody(BodyCreationSettings(big_box, RVec3(-20.0f + i * 10.0f, 4, -20.0f + j * 40.0f), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
// Place a dynamic body on it
mBodyInterface->CreateAndAddBody(BodyCreationSettings(j == 0? capsule : long_box, RVec3(-20.0f + i * 10.0f, 12, -5.0f + i * 5.0f - 20.0f + j * 40.0f), Quat::sRotation(Vec3::sAxisY(), 0.1f * JPH_PI) * Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
}
}

View File

@@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class ContactManifoldTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, ContactManifoldTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Spawns objects at an angle to test if the contact manifold is calculated correctly.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,111 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/ConveyorBeltTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/CylinderShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(ConveyorBeltTest)
{
JPH_ADD_BASE_CLASS(ConveyorBeltTest, Test)
}
void ConveyorBeltTest::Initialize()
{
// Floor
CreateFloor();
// Create conveyor belts
const float cBeltWidth = 10.0f;
const float cBeltLength = 50.0f;
BodyCreationSettings belt_settings(new BoxShape(Vec3(cBeltWidth, 0.1f, cBeltLength)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
for (int i = 0; i < 4; ++i)
{
belt_settings.mFriction = 0.25f * (i + 1);
belt_settings.mRotation = Quat::sRotation(Vec3::sAxisY(), 0.5f * JPH_PI * i) * Quat::sRotation(Vec3::sAxisX(), DegreesToRadians(1.0f));
belt_settings.mPosition = RVec3(belt_settings.mRotation * Vec3(cBeltLength, 6.0f, cBeltWidth));
mLinearBelts.push_back(mBodyInterface->CreateAndAddBody(belt_settings, EActivation::DontActivate));
}
// Bodies with decreasing friction
BodyCreationSettings cargo_settings(new BoxShape(Vec3::sReplicate(2.0f)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
for (int i = 0; i <= 10; ++i)
{
cargo_settings.mPosition = RVec3(-cBeltLength + i * 10.0f, 10.0f, -cBeltLength);
cargo_settings.mFriction = max(0.0f, 1.0f - 0.1f * i);
mBodyInterface->CreateAndAddBody(cargo_settings, EActivation::Activate);
}
// Create 2 cylinders
BodyCreationSettings cylinder_settings(new CylinderShape(6.0f, 1.0f), RVec3(-25.0f, 1.0f, -20.0f), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), EMotionType::Dynamic, Layers::MOVING);
mBodyInterface->CreateAndAddBody(cylinder_settings, EActivation::Activate);
cylinder_settings.mPosition.SetZ(20.0f);
mBodyInterface->CreateAndAddBody(cylinder_settings, EActivation::Activate);
// Let a dynamic belt rest on it
BodyCreationSettings dynamic_belt(new BoxShape(Vec3(5.0f, 0.1f, 25.0f), 0.0f), RVec3(-25.0f, 3.0f, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
mLinearBelts.push_back(mBodyInterface->CreateAndAddBody(dynamic_belt, EActivation::Activate));
// Create cargo on the dynamic belt
cargo_settings.mPosition = RVec3(-25.0f, 6.0f, 15.0f);
cargo_settings.mFriction = 1.0f;
mBodyInterface->CreateAndAddBody(cargo_settings, EActivation::Activate);
// Create an angular belt
BodyCreationSettings angular_belt(new BoxShape(Vec3(20.0f, 0.1f, 20.0f), 0.0f), RVec3(10.0f, 3.0f, 0), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
mAngularBelt = mBodyInterface->CreateAndAddBody(angular_belt, EActivation::Activate);
// Bodies with decreasing friction dropping on the angular belt
for (int i = 0; i <= 6; ++i)
{
cargo_settings.mPosition = RVec3(10.0f, 10.0f, -15.0f + 5.0f * i);
cargo_settings.mFriction = max(0.0f, 1.0f - 0.1f * i);
mBodyInterface->CreateAndAddBody(cargo_settings, EActivation::Activate);
}
}
void ConveyorBeltTest::OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings)
{
// Linear belts
bool body1_linear_belt = std::find(mLinearBelts.begin(), mLinearBelts.end(), inBody1.GetID()) != mLinearBelts.end();
bool body2_linear_belt = std::find(mLinearBelts.begin(), mLinearBelts.end(), inBody2.GetID()) != mLinearBelts.end();
if (body1_linear_belt || body2_linear_belt)
{
// Determine the world space surface velocity of both bodies
const Vec3 cLocalSpaceVelocity(0, 0, -10.0f);
Vec3 body1_linear_surface_velocity = body1_linear_belt? inBody1.GetRotation() * cLocalSpaceVelocity : Vec3::sZero();
Vec3 body2_linear_surface_velocity = body2_linear_belt? inBody2.GetRotation() * cLocalSpaceVelocity : Vec3::sZero();
// Calculate the relative surface velocity
ioSettings.mRelativeLinearSurfaceVelocity = body2_linear_surface_velocity - body1_linear_surface_velocity;
}
// Angular belt
bool body1_angular = inBody1.GetID() == mAngularBelt;
bool body2_angular = inBody2.GetID() == mAngularBelt;
if (body1_angular || body2_angular)
{
// Determine the world space angular surface velocity of both bodies
const Vec3 cLocalSpaceAngularVelocity(0, DegreesToRadians(10.0f), 0);
Vec3 body1_angular_surface_velocity = body1_angular? inBody1.GetRotation() * cLocalSpaceAngularVelocity : Vec3::sZero();
Vec3 body2_angular_surface_velocity = body2_angular? inBody2.GetRotation() * cLocalSpaceAngularVelocity : Vec3::sZero();
// Note that the angular velocity is the angular velocity around body 1's center of mass, so we need to add the linear velocity of body 2's center of mass
Vec3 body2_linear_surface_velocity = body2_angular? body2_angular_surface_velocity.Cross(Vec3(inBody1.GetCenterOfMassPosition() - inBody2.GetCenterOfMassPosition())) : Vec3::sZero();
// Calculate the relative angular surface velocity
ioSettings.mRelativeLinearSurfaceVelocity = body2_linear_surface_velocity;
ioSettings.mRelativeAngularSurfaceVelocity = body2_angular_surface_velocity - body1_angular_surface_velocity;
}
}
void ConveyorBeltTest::OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings)
{
// Same behavior as contact added
OnContactAdded(inBody1, inBody2, inManifold, ioSettings);
}

View File

@@ -0,0 +1,33 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class ConveyorBeltTest : public Test, public ContactListener
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, ConveyorBeltTest)
// Description of the test
virtual const char * GetDescription() const override
{
return "Demonstrates how to use a contact listener to implement a conveyor belt.";
}
// See: Test
virtual void Initialize() override;
// If this test implements a contact listener, it should be returned here
virtual ContactListener *GetContactListener() override { return this; }
// See: ContactListener
virtual void OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override;
virtual void OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override;
private:
BodyIDVector mLinearBelts;
BodyID mAngularBelt;
};

View File

@@ -0,0 +1,44 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/DampingTest.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(DampingTest)
{
JPH_ADD_BASE_CLASS(DampingTest, Test)
}
void DampingTest::Initialize()
{
// Floor
CreateFloor();
RefConst<Shape> sphere = new SphereShape(2.0f);
// Bodies with increasing damping
for (int i = 0; i <= 10; ++i)
{
Body &body = *mBodyInterface->CreateBody(BodyCreationSettings(sphere, RVec3(-50.0f + i * 10.0f, 2.0f, -80.0f), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
body.GetMotionProperties()->SetAngularDamping(0.0f);
body.GetMotionProperties()->SetLinearDamping(0.1f * i);
body.SetLinearVelocity(Vec3(0, 0, 10));
mBodyInterface->AddBody(body.GetID(), EActivation::Activate);
SetBodyLabel(body.GetID(), StringFormat("Linear damping: %.1f", double(body.GetMotionProperties()->GetLinearDamping())));
}
for (int i = 0; i <= 10; ++i)
{
Body &body = *mBodyInterface->CreateBody(BodyCreationSettings(sphere, RVec3(-50.0f + i * 10.0f, 2.0f, -90.0f), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
body.GetMotionProperties()->SetLinearDamping(0.0f);
body.GetMotionProperties()->SetAngularDamping(0.1f * i);
body.SetAngularVelocity(Vec3(0, 10, 0));
mBodyInterface->AddBody(body.GetID(), EActivation::Activate);
SetBodyLabel(body.GetID(), StringFormat("Angular damping: %.1f", double(body.GetMotionProperties()->GetAngularDamping())));
}
}

View File

@@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class DampingTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, DampingTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Tests various values for linear and angular damping.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,46 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/DynamicMeshTest.h>
#include <Jolt/Physics/Collision/Shape/MeshShape.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
#include <Utils/ShapeCreator.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(DynamicMeshTest)
{
JPH_ADD_BASE_CLASS(DynamicMeshTest, Test)
}
void DynamicMeshTest::Initialize()
{
// Floor
CreateFloor();
constexpr float cTorusRadius = 3.0f;
constexpr float cTubeRadius = 1.0f;
// Create torus
RefConst<Shape> mesh_shape = ShapeCreator::CreateTorusMesh(cTorusRadius, cTubeRadius);
BodyCreationSettings settings(mesh_shape, RVec3(0, 10, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
// Mesh cannot calculate its mass, we must provide it
settings.mOverrideMassProperties = EOverrideMassProperties::MassAndInertiaProvided;
settings.mMassPropertiesOverride.SetMassAndInertiaOfSolidBox(2.0f * Vec3(cTorusRadius, cTubeRadius, cTorusRadius), 1000.0f);
mBodyInterface->CreateAndAddBody(settings, EActivation::Activate);
// Wall of blocks
RefConst<Shape> box_shape = new BoxShape(Vec3::sReplicate(0.5f));
for (int i = 0; i < 7; ++i)
for (int j = i / 2; j < 7 - (i + 1) / 2; ++j)
{
RVec3 position(-3.5f + j * 1.0f + (i & 1? 0.5f : 0.0f), 0.5f + i * 1.0f, 0.0f);
Body &wall = *mBodyInterface->CreateBody(BodyCreationSettings(box_shape, position, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
mBodyInterface->AddBody(wall.GetID(), EActivation::Activate);
}
}

View File

@@ -0,0 +1,23 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class DynamicMeshTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, DynamicMeshTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Drops a dynamic body with a mesh shape on a pile of boxes.\n"
"Note that mesh vs mesh collisions are currently not supported.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,236 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/EnhancedInternalEdgeRemovalTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Collision/Shape/MeshShape.h>
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
#include <Jolt/Geometry/Triangle.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(EnhancedInternalEdgeRemovalTest)
{
JPH_ADD_BASE_CLASS(EnhancedInternalEdgeRemovalTest, Test)
}
void EnhancedInternalEdgeRemovalTest::CreateSlidingObjects(RVec3Arg inStart)
{
// Slide the shapes over the grid of boxes
RVec3 pos = inStart - RVec3(0, 0, 12.0_r);
static const char *labels[] = { "Normal", "Enhanced edge removal" };
for (int enhanced_removal = 0; enhanced_removal < 2; ++enhanced_removal)
{
// A box
BodyCreationSettings box_bcs(new BoxShape(Vec3::sReplicate(2.0f)), pos, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
box_bcs.mLinearVelocity = Vec3(20, 0, 0);
box_bcs.mEnhancedInternalEdgeRemoval = enhanced_removal == 1;
BodyID id = mBodyInterface->CreateAndAddBody(box_bcs, EActivation::Activate);
SetBodyLabel(id, labels[enhanced_removal]);
pos += RVec3(0, 0, 5.0_r);
// A sphere
BodyCreationSettings sphere_bcs(new SphereShape(2.0f), pos, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
sphere_bcs.mLinearVelocity = Vec3(20, 0, 0);
sphere_bcs.mEnhancedInternalEdgeRemoval = enhanced_removal == 1;
id = mBodyInterface->CreateAndAddBody(sphere_bcs, EActivation::Activate);
SetBodyLabel(id, labels[enhanced_removal]);
pos += RVec3(0, 0, 5.0_r);
// Compound
RefConst<Shape> box = new BoxShape(Vec3::sReplicate(0.1f));
StaticCompoundShapeSettings compound;
compound.SetEmbedded();
for (int x = 0; x < 2; ++x)
for (int y = 0; y < 2; ++y)
for (int z = 0; z < 2; ++z)
compound.AddShape(Vec3(x == 0? -1.9f : 1.9f, y == 0? -1.9f : 1.9f, z == 0? -1.9f : 1.9f), Quat::sIdentity(), box);
BodyCreationSettings compound_bcs(&compound, pos, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
compound_bcs.mLinearVelocity = Vec3(20, 0, 0);
compound_bcs.mEnhancedInternalEdgeRemoval = enhanced_removal == 1;
id = mBodyInterface->CreateAndAddBody(compound_bcs, EActivation::Activate);
SetBodyLabel(id, labels[enhanced_removal]);
pos += RVec3(0, 0, 7.0_r);
}
}
void EnhancedInternalEdgeRemovalTest::Initialize()
{
// This test creates a grid of connected boxes and tests that objects don't hit the internal edges
{
StaticCompoundShapeSettings compound_settings;
compound_settings.SetEmbedded();
constexpr float size = 2.0f;
RefConst<Shape> box_shape = new BoxShape(Vec3::sReplicate(0.5f * size));
for (int x = -10; x < 10; ++x)
for (int z = -10; z < 10; ++z)
compound_settings.AddShape(Vec3(size * x, 0, size * z), Quat::sIdentity(), box_shape);
BodyID id = mBodyInterface->CreateAndAddBody(BodyCreationSettings(&compound_settings, RVec3(0, -1, -40), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
SetBodyLabel(id, "Dense grid of boxes");
CreateSlidingObjects(RVec3(-18, 1.9_r, -40.0_r));
}
// This tests if objects do not collide with internal edges
{
// Create a dense grid of triangles so that we have a large chance of hitting an internal edge
constexpr float size = 2.0f;
TriangleList triangles;
for (int x = -10; x < 10; ++x)
for (int z = -10; z < 10; ++z)
{
float x1 = size * x;
float z1 = size * z;
float x2 = x1 + size;
float z2 = z1 + size;
Float3 v1 = Float3(x1, 0, z1);
Float3 v2 = Float3(x2, 0, z1);
Float3 v3 = Float3(x1, 0, z2);
Float3 v4 = Float3(x2, 0, z2);
triangles.push_back(Triangle(v1, v3, v4));
triangles.push_back(Triangle(v1, v4, v2));
}
MeshShapeSettings mesh_settings(triangles);
mesh_settings.mActiveEdgeCosThresholdAngle = -1.0f; // Turn off regular active edge determination so that we only rely on the mEnhancedInternalEdgeRemoval flag
mesh_settings.SetEmbedded();
BodyID id = mBodyInterface->CreateAndAddBody(BodyCreationSettings(&mesh_settings, RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
SetBodyLabel(id, "Dense triangle mesh");
CreateSlidingObjects(RVec3(-18, 1.9_r, 0));
}
// This test tests that we only ignore edges that are shared with voided triangles
{
// Create an L shape mesh lying on its back
TriangleList triangles;
constexpr float height = 0.5f;
constexpr float half_width = 5.0f;
constexpr float half_length = 2.0f;
triangles.push_back(Triangle(Float3(-half_length, 0, half_width), Float3(half_length, 0, -half_width), Float3(-half_length, 0, -half_width)));
triangles.push_back(Triangle(Float3(-half_length, 0, half_width), Float3(half_length, 0, half_width), Float3(half_length, 0, -half_width)));
triangles.push_back(Triangle(Float3(half_length, height, half_width), Float3(half_length, height, -half_width), Float3(half_length, 0, half_width)));
triangles.push_back(Triangle(Float3(half_length, 0, half_width), Float3(half_length, height, -half_width), Float3(half_length, 0, -half_width)));
mBodyInterface->CreateAndAddBody(BodyCreationSettings(new MeshShapeSettings(triangles), RVec3(0, 0, 30), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
// Roll a sphere towards the edge pointing upwards
float z = 28.0f;
for (int enhanced_removal = 0; enhanced_removal < 2; ++enhanced_removal)
{
// A sphere
BodyCreationSettings sphere_bcs(new SphereShape(1.0f), RVec3(0, 1, z), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
sphere_bcs.mLinearVelocity = Vec3(20, 0, 0);
sphere_bcs.mEnhancedInternalEdgeRemoval = enhanced_removal == 1;
mBodyInterface->CreateAndAddBody(sphere_bcs, EActivation::Activate);
z += 4.0f;
}
}
// This tests that fast moving spheres rolling over a triangle will not be affected by internal edges
{
// Create a flat plane
MeshShapeSettings plane_mesh({
{
Float3(-10, 0, -10),
Float3(-10, 0, 10),
Float3(10, 0, 10)
},
{
Float3(-10, 0, -10),
Float3(10, 0, 10),
Float3(10, 0, -10)
},
});
plane_mesh.SetEmbedded();
BodyCreationSettings level_plane(&plane_mesh, RVec3(-10, 0, 50), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
level_plane.mFriction = 1;
BodyID id = mBodyInterface->CreateAndAddBody(level_plane, EActivation::DontActivate);
SetBodyLabel(id, "Dense triangle mesh");
// Roll a ball over it
BodyCreationSettings level_ball(new SphereShape(0.5f), RVec3(-10, 1, 41), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
level_ball.mEnhancedInternalEdgeRemoval = true;
level_ball.mFriction = 1;
level_ball.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
level_ball.mMassPropertiesOverride.mMass = 1;
mLevelBall = mBodyInterface->CreateAndAddBody(level_ball, EActivation::Activate);
// Create a sloped plane
BodyCreationSettings slope_plane(&plane_mesh, RVec3(10, 0, 50), Quat::sRotation(Vec3::sAxisX(), DegreesToRadians(45)), EMotionType::Static, Layers::NON_MOVING);
slope_plane.mFriction = 1;
id = mBodyInterface->CreateAndAddBody(slope_plane, EActivation::DontActivate);
SetBodyLabel(id, "Dense triangle mesh");
// Roll a ball over it
BodyCreationSettings slope_ball(new SphereShape(0.5f), RVec3(10, 8, 44), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
slope_ball.mEnhancedInternalEdgeRemoval = true;
slope_ball.mFriction = 1;
slope_ball.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
slope_ball.mMassPropertiesOverride.mMass = 1;
mBodyInterface->CreateAndAddBody(slope_ball, EActivation::Activate);
}
// This tests a previous bug where a compound shape will fall through a box because features are voided by accident.
// This is because both boxes of the compound shape collide with the top face of the static box. The big box will have a normal
// that is aligned with the face so will be processed immediately. This will void the top face of the static box. The small box,
// which collides with an edge of the top face will not be processed. This will cause the small box to penetrate the face.
{
// A box
BodyCreationSettings box_bcs(new BoxShape(Vec3::sReplicate(2.5f)), RVec3(0, 0, 70), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
mBodyInterface->CreateAndAddBody(box_bcs, EActivation::DontActivate);
// Compound
StaticCompoundShapeSettings compound;
compound.SetEmbedded();
compound.AddShape(Vec3(-2.5f, 0, 0), Quat::sIdentity(), new BoxShape(Vec3(2.5f, 0.1f, 0.1f)));
compound.AddShape(Vec3(0.1f, 0, 0), Quat::sIdentity(), new BoxShape(Vec3(0.1f, 1, 1)));
BodyCreationSettings compound_bcs(&compound, RVec3(2, 5, 70), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
compound_bcs.mEnhancedInternalEdgeRemoval = true;
mBodyInterface->CreateAndAddBody(compound_bcs, EActivation::Activate);
}
// Create a super dense grid of triangles
{
constexpr float size = 0.25f;
TriangleList triangles;
for (int x = -100; x < 100; ++x)
for (int z = -5; z < 5; ++z)
{
float x1 = size * x;
float z1 = size * z;
float x2 = x1 + size;
float z2 = z1 + size;
Float3 v1 = Float3(x1, 0, z1);
Float3 v2 = Float3(x2, 0, z1);
Float3 v3 = Float3(x1, 0, z2);
Float3 v4 = Float3(x2, 0, z2);
triangles.push_back(Triangle(v1, v3, v4));
triangles.push_back(Triangle(v1, v4, v2));
}
MeshShapeSettings mesh_settings(triangles);
mesh_settings.mActiveEdgeCosThresholdAngle = -1.0f; // Turn off regular active edge determination so that we only rely on the mEnhancedInternalEdgeRemoval flag
mesh_settings.SetEmbedded();
BodyID id = mBodyInterface->CreateAndAddBody(BodyCreationSettings(&mesh_settings, RVec3(0, 0, 80), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
SetBodyLabel(id, "Dense triangle mesh");
BodyCreationSettings box_bcs(new BoxShape(Vec3::sReplicate(1.0f)), RVec3(-24, 0.9_r, 80), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
box_bcs.mLinearVelocity = Vec3(20, 0, 0);
box_bcs.mEnhancedInternalEdgeRemoval = true;
mBodyInterface->CreateAndAddBody(box_bcs, EActivation::Activate);
}
}
void EnhancedInternalEdgeRemovalTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
{
// Increase rotation speed of the ball on the flat plane
mBodyInterface->AddTorque(mLevelBall, Vec3(JPH_PI * 4, 0, 0));
}

View File

@@ -0,0 +1,28 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class EnhancedInternalEdgeRemovalTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, EnhancedInternalEdgeRemovalTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Shows bodies using enhanced edge removal vs bodies that don't.";
}
// See: Test
virtual void Initialize() override;
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
private:
void CreateSlidingObjects(RVec3Arg inStart);
BodyID mLevelBall;
};

View File

@@ -0,0 +1,95 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/FrictionPerTriangleTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/MeshShape.h>
#include <Jolt/Geometry/Triangle.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(FrictionPerTriangleTest)
{
JPH_ADD_BASE_CLASS(FrictionPerTriangleTest, Test)
}
void FrictionPerTriangleTest::Initialize()
{
const int num_sections = 5;
const float section_size = 50.0f;
// Create a strip of triangles
TriangleList triangles;
for (int z = 0; z <= num_sections; ++z)
{
float z1 = section_size * (z - 0.5f * num_sections);
float z2 = z1 + section_size;
Float3 v1 = Float3(-100.0f, 0, z1);
Float3 v2 = Float3(100.0f, 0, z1);
Float3 v3 = Float3(-100.0f, 0, z2);
Float3 v4 = Float3(100.0f, 0, z2);
triangles.push_back(Triangle(v1, v3, v4, z));
triangles.push_back(Triangle(v1, v4, v2, z));
}
// Create materials with increasing friction
PhysicsMaterialList materials;
for (uint i = 0; i <= num_sections; ++i)
{
float friction = float(i) / float(num_sections);
materials.push_back(new MyMaterial("Friction " + ConvertToString(friction), Color::sGetDistinctColor(i), friction, 0.0f));
}
// A ramp
mBodyInterface->CreateAndAddBody(BodyCreationSettings(new MeshShapeSettings(triangles, std::move(materials)), RVec3::sZero(), Quat::sRotation(Vec3::sAxisX(), 0.2f * JPH_PI), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
// A box with friction 1 that slides down the ramp
Ref<BoxShape> box_shape = new BoxShape(Vec3(2.0f, 2.0f, 2.0f), cDefaultConvexRadius, new MyMaterial("Box Friction 1", Color::sYellow, 1.0f, 0.0f));
mBodyInterface->CreateAndAddBody(BodyCreationSettings(box_shape, RVec3(0, 60.0f, -75.0f), Quat::sRotation(Vec3::sAxisX(), 0.2f * JPH_PI), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
}
void FrictionPerTriangleTest::sGetFrictionAndRestitution(const Body &inBody, const SubShapeID &inSubShapeID, float &outFriction, float &outRestitution)
{
// Get the material that corresponds to the sub shape ID
const PhysicsMaterial *material = inBody.GetShape()->GetMaterial(inSubShapeID);
if (material == PhysicsMaterial::sDefault)
{
// This is the default material, use the settings from the body (note all bodies in our test have a material so this should not happen)
outFriction = inBody.GetFriction();
outRestitution = inBody.GetRestitution();
}
else
{
// If it's not the default material we know its a material that we created so we can cast it and get the values
const MyMaterial *my_material = static_cast<const MyMaterial *>(material);
outFriction = my_material->mFriction;
outRestitution = my_material->mRestitution;
}
}
void FrictionPerTriangleTest::sOverrideContactSettings(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings)
{
// Get the custom friction and restitution for both bodies
float friction1, friction2, restitution1, restitution2;
sGetFrictionAndRestitution(inBody1, inManifold.mSubShapeID1, friction1, restitution1);
sGetFrictionAndRestitution(inBody2, inManifold.mSubShapeID2, friction2, restitution2);
// Use the default formulas for combining friction and restitution
ioSettings.mCombinedFriction = sqrt(friction1 * friction2);
ioSettings.mCombinedRestitution = max(restitution1, restitution2);
}
void FrictionPerTriangleTest::OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings)
{
sOverrideContactSettings(inBody1, inBody2, inManifold, ioSettings);
}
void FrictionPerTriangleTest::OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings)
{
sOverrideContactSettings(inBody1, inBody2, inManifold, ioSettings);
}

View File

@@ -0,0 +1,52 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
#include <Jolt/Physics/Collision/ContactListener.h>
#include <Jolt/Physics/Collision/PhysicsMaterialSimple.h>
class FrictionPerTriangleTest : public Test, public ContactListener
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, FrictionPerTriangleTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Demonstrates how you can use a contact listener and your own material definition to get friction and restitution per triangle or per sub shape of a compound shape.\n"
"Friction increases while going down the slope. Box should eventually stop.";
}
// See: Test
virtual void Initialize() override;
/// A custom material implementation that stores its own friction and restitution
/// Note: Make sure you set PhysicsMaterial::sDefault to something your application understands (explicitly check PhysicsMaterial::sDefault to prevent casting to the wrong type in sGetFrictionAndRestitution)
class MyMaterial : public PhysicsMaterialSimple
{
public:
// Note: Not implementing serialization because we don't serialize this material in this example!
/// Constructor
MyMaterial(const string_view &inName, ColorArg inColor, float inFriction, float inRestitution) : PhysicsMaterialSimple(inName, inColor), mFriction(inFriction), mRestitution(inRestitution) { }
float mFriction;
float mRestitution;
};
/// Extract custom friction and restitution from a body and sub shape ID
static void sGetFrictionAndRestitution(const Body &inBody, const SubShapeID &inSubShapeID, float &outFriction, float &outRestitution);
/// Calculates and overrides friction and restitution settings for a contact between two bodies
static void sOverrideContactSettings(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings);
// If this test implements a contact listener, it should be returned here
virtual ContactListener *GetContactListener() override { return this; }
// See: ContactListener
virtual void OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override;
virtual void OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override;
};

View File

@@ -0,0 +1,44 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/FrictionTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(FrictionTest)
{
JPH_ADD_BASE_CLASS(FrictionTest, Test)
}
void FrictionTest::Initialize()
{
// Floor
Body &floor = *mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3(100.0f, 1.0f, 100.0f), 0.0f), RVec3::sZero(), Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI), EMotionType::Static, Layers::NON_MOVING));
floor.SetFriction(1.0f);
mBodyInterface->AddBody(floor.GetID(), EActivation::DontActivate);
RefConst<Shape> box = new BoxShape(Vec3(2.0f, 2.0f, 2.0f));
RefConst<Shape> sphere = new SphereShape(2.0f);
// Bodies with increasing friction
for (int i = 0; i <= 10; ++i)
{
Body &body = *mBodyInterface->CreateBody(BodyCreationSettings(box, RVec3(-50.0f + i * 10.0f, 55.0f, -50.0f), Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI), EMotionType::Dynamic, Layers::MOVING));
body.SetFriction(0.1f * i);
mBodyInterface->AddBody(body.GetID(), EActivation::Activate);
SetBodyLabel(body.GetID(), StringFormat("Friction: %.1f", double(body.GetFriction())));
}
for (int i = 0; i <= 10; ++i)
{
Body &body = *mBodyInterface->CreateBody(BodyCreationSettings(sphere, RVec3(-50.0f + i * 10.0f, 47.0f, -40.0f), Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI), EMotionType::Dynamic, Layers::MOVING));
body.SetFriction(0.1f * i);
mBodyInterface->AddBody(body.GetID(), EActivation::Activate);
SetBodyLabel(body.GetID(), StringFormat("Friction: %.1f", double(body.GetFriction())));
}
}

View File

@@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class FrictionTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, FrictionTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Bodies with varying friction.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,157 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/FunnelTest.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/ConvexHullShape.h>
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
#include <Jolt/Physics/Collision/Shape/TaperedCapsuleShape.h>
#include <Jolt/Physics/Collision/Shape/CylinderShape.h>
#include <Jolt/Physics/Collision/Shape/TaperedCylinderShape.h>
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
#include <Jolt/Physics/Collision/Shape/ScaledShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <random>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(FunnelTest)
{
JPH_ADD_BASE_CLASS(FunnelTest, Test)
}
void FunnelTest::Initialize()
{
RefConst<Shape> box = new BoxShape(Vec3(50, 1, 50), 0.0f);
// Funnel
for (int i = 0; i < 4; ++i)
{
Quat rotation = Quat::sRotation(Vec3::sAxisY(), 0.5f * JPH_PI * i);
mBodyInterface->CreateAndAddBody(BodyCreationSettings(box, RVec3(rotation * Vec3(25, 25, 0)), rotation * Quat::sRotation(Vec3::sAxisZ(), 0.25f * JPH_PI), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
}
default_random_engine random;
uniform_real_distribution<float> feature_size(0.1f, 2.0f);
uniform_real_distribution<float> position_variation(-40, 40);
uniform_real_distribution<float> scale_variation(-1.5f, 1.5f);
// Random scale
Vec3 scale(scale_variation(random), scale_variation(random), scale_variation(random));
// Make it minimally -0.5 or 0.5 depending on the sign
scale += Vec3::sSelect(Vec3::sReplicate(-0.5f), Vec3::sReplicate(0.5f), Vec3::sGreaterOrEqual(scale, Vec3::sZero()));
RefConst<Shape> shape;
for (int i = 0; i < 1000; ++i)
{
switch (random() % 10)
{
case 0:
{
shape = new SphereShape(feature_size(random));
scale = scale.Swizzle<SWIZZLE_X, SWIZZLE_X, SWIZZLE_X>(); // Only uniform scale supported
break;
}
case 1:
{
shape = new BoxShape(Vec3(feature_size(random), feature_size(random), feature_size(random)));
break;
}
case 2:
{
// Create random points
Array<Vec3> points;
for (int j = 0; j < 20; ++j)
points.push_back(feature_size(random) * Vec3::sRandom(random));
shape = ConvexHullShapeSettings(points).Create().Get();
break;
}
case 3:
{
shape = new CapsuleShape(0.5f * feature_size(random), feature_size(random));
scale = scale.Swizzle<SWIZZLE_X, SWIZZLE_X, SWIZZLE_X>(); // Only uniform scale supported
break;
}
case 4:
{
float top = feature_size(random);
float bottom = feature_size(random);
float half_height = max(0.5f * feature_size(random), 0.5f * abs(top - bottom) + 0.001f);
shape = TaperedCapsuleShapeSettings(half_height, top, bottom).Create().Get();
scale = scale.Swizzle<SWIZZLE_X, SWIZZLE_X, SWIZZLE_X>(); // Only uniform scale supported
break;
}
case 5:
{
shape = new CylinderShape(0.5f * feature_size(random), feature_size(random));
scale = scale.Swizzle<SWIZZLE_X, SWIZZLE_Y, SWIZZLE_X>(); // Scale X must be same as Z
break;
}
case 6:
{
shape = TaperedCylinderShapeSettings(0.5f * feature_size(random), feature_size(random), feature_size(random)).Create().Get();
scale = scale.Swizzle<SWIZZLE_X, SWIZZLE_Y, SWIZZLE_X>(); // Scale X must be same as Z
break;
}
case 7:
{
shape = TaperedCylinderShapeSettings(0.5f * feature_size(random), 0.0f, feature_size(random), 0.0f).Create().Get();
scale = scale.Swizzle<SWIZZLE_X, SWIZZLE_Y, SWIZZLE_X>(); // Scale X must be same as Z
break;
}
case 8:
{
// Simple compound
StaticCompoundShapeSettings compound_shape_settings;
compound_shape_settings.AddShape(Vec3::sZero(), Quat::sIdentity(), new CapsuleShape(1, 0.1f));
compound_shape_settings.AddShape(Vec3(0, -1, 0), Quat::sIdentity(), new SphereShape(0.5f));
compound_shape_settings.AddShape(Vec3(0, 1, 0), Quat::sIdentity(), new SphereShape(0.5f));
shape = compound_shape_settings.Create().Get();
scale = scale.Swizzle<SWIZZLE_X, SWIZZLE_X, SWIZZLE_X>(); // Only uniform scale supported
break;
}
case 9:
{
// Compound with sub compound and rotation
Ref<StaticCompoundShapeSettings> sub_compound = new StaticCompoundShapeSettings();
sub_compound->AddShape(Vec3(0, 0.75f, 0), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), new BoxShape(Vec3(0.75f, 0.25f, 0.2f)));
sub_compound->AddShape(Vec3(0.75f, 0, 0), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), new CylinderShape(0.75f, 0.2f));
sub_compound->AddShape(Vec3(0, 0, 0.75f), Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI), new TaperedCapsuleShapeSettings(0.75f, 0.25f, 0.2f));
StaticCompoundShapeSettings compound_shape_settings;
compound_shape_settings.AddShape(Vec3(0, 0, 0), Quat::sRotation(Vec3::sAxisX(), -0.25f * JPH_PI) * Quat::sRotation(Vec3::sAxisZ(), 0.25f * JPH_PI), sub_compound);
compound_shape_settings.AddShape(Vec3(0, -0.1f, 0), Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI) * Quat::sRotation(Vec3::sAxisZ(), -0.75f * JPH_PI), sub_compound);
shape = compound_shape_settings.Create().Get();
scale = scale.Swizzle<SWIZZLE_X, SWIZZLE_X, SWIZZLE_X>(); // Only uniform scale supported
break;
}
}
// Scale the shape
if ((random() % 3) == 0)
shape = new ScaledShape(shape, scale);
RVec3 position(position_variation(random), 100.0f + position_variation(random), position_variation(random));
mBodyInterface->CreateAndAddBody(BodyCreationSettings(shape, position, Quat::sRandom(random), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
}
}
void FunnelTest::GetInitialCamera(CameraState &ioState) const
{
RVec3 cam_tgt = RVec3(0, 50, 0);
ioState.mPos = RVec3(50, 100, 50);
ioState.mForward = Vec3(cam_tgt - ioState.mPos).Normalized();
}

View File

@@ -0,0 +1,23 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class FunnelTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, FunnelTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Spawns a lot of objects and drops them into a funnel to check for performance / stability issues.";
}
// See: Test
virtual void Initialize() override;
virtual void GetInitialCamera(CameraState &ioState) const override;
};

View File

@@ -0,0 +1,32 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/GravityFactorTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(GravityFactorTest)
{
JPH_ADD_BASE_CLASS(GravityFactorTest, Test)
}
void GravityFactorTest::Initialize()
{
// Floor
CreateFloor();
RefConst<Shape> box = new BoxShape(Vec3(2.0f, 2.0f, 2.0f));
// Bodies with increasing gravity fraction
for (int i = 0; i <= 10; ++i)
{
Body &body = *mBodyInterface->CreateBody(BodyCreationSettings(box, RVec3(-50.0f + i * 10.0f, 25.0f, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
body.GetMotionProperties()->SetGravityFactor(0.1f * i);
mBodyInterface->AddBody(body.GetID(), EActivation::Activate);
SetBodyLabel(body.GetID(), StringFormat("Gravity: %.1f", double(body.GetMotionProperties()->GetGravityFactor())));
}
}

View File

@@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class GravityFactorTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, GravityFactorTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Bodies with varying gravity factor.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,42 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/GyroscopicForceTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(GyroscopicForceTest)
{
JPH_ADD_BASE_CLASS(GyroscopicForceTest, Test)
}
void GyroscopicForceTest::Initialize()
{
// Floor
CreateFloor();
StaticCompoundShapeSettings compound;
compound.AddShape(Vec3::sZero(), Quat::sIdentity(), new BoxShape(Vec3(0.5f, 5.0f, 0.5f)));
compound.AddShape(Vec3(1.5f, 0, 0), Quat::sIdentity(), new BoxShape(Vec3(1.0f, 0.5f, 0.5f)));
compound.SetEmbedded();
// One body without gyroscopic force
BodyCreationSettings bcs(&compound, RVec3(0, 10, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
bcs.mLinearDamping = 0.0f;
bcs.mAngularDamping = 0.0f;
bcs.mAngularVelocity = Vec3(10, 1, 0);
bcs.mGravityFactor = 0.0f;
BodyID id = mBodyInterface->CreateAndAddBody(bcs, EActivation::Activate);
SetBodyLabel(id, "Gyroscopic force off");
// One body with gyroscopic force
bcs.mPosition += RVec3(10, 0, 0);
bcs.mApplyGyroscopicForce = true;
id = mBodyInterface->CreateAndAddBody(bcs, EActivation::Activate);
SetBodyLabel(id, "Gyroscopic force on");
}

View File

@@ -0,0 +1,24 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
#include <Jolt/Physics/Body/BodyActivationListener.h>
class GyroscopicForceTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, GyroscopicForceTest)
// Description of the test
virtual const char *GetDescription() const override
{
// See: https://en.wikipedia.org/wiki/Tennis_racket_theorem
return "Shows how to enable gyroscopic forces to create the Dzhanibekov effect.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,36 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/HeavyOnLightTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(HeavyOnLightTest)
{
JPH_ADD_BASE_CLASS(HeavyOnLightTest, Test)
}
void HeavyOnLightTest::Initialize()
{
// Floor
CreateFloor();
Ref<BoxShape> box = new BoxShape(Vec3::sReplicate(5));
box->SetDensity(1000.0f);
for (int i = 1; i <= 10; ++i)
{
BodyID id = mBodyInterface->CreateAndAddBody(BodyCreationSettings(box, RVec3(-75.0f + i * 15.0f, 10.0f, 0.0f), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
SetBodyLabel(id, StringFormat("Mass: %g", double(box->GetMassProperties().mMass)));
Ref<BoxShape> box2 = new BoxShape(Vec3::sReplicate(5));
box2->SetDensity(5000.0f * i);
id = mBodyInterface->CreateAndAddBody(BodyCreationSettings(box2, RVec3(-75.0f + i * 15.0f, 30.0f, 0.0f), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
SetBodyLabel(id, StringFormat("Mass: %g", double(box2->GetMassProperties().mMass)));
}
}

View File

@@ -0,0 +1,23 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class HeavyOnLightTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, HeavyOnLightTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "This test spawns a number of heavy boxes (with increasing weight) on light boxes.\n"
"Shows that iterative solvers have issues with large mass differences.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,378 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/HighSpeedTest.h>
#include <Jolt/Physics/Collision/RayCast.h>
#include <Jolt/Physics/Collision/CastResult.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Collision/Shape/ScaledShape.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/Shape/ConvexHullShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/PhysicsScene.h>
#include <Jolt/ObjectStream/ObjectStreamIn.h>
#include <Application/DebugUI.h>
#include <Utils/Log.h>
#include <Utils/AssetStream.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(HighSpeedTest)
{
JPH_ADD_BASE_CLASS(HighSpeedTest, Test)
}
const char *HighSpeedTest::sScenes[] =
{
"Simple",
"Convex Hull On Large Triangles",
"Convex Hull On Terrain1",
};
int HighSpeedTest::sSelectedScene = 0;
void HighSpeedTest::CreateDominoBlocks(RVec3Arg inOffset, int inNumWalls, float inDensity, float inRadius)
{
BodyCreationSettings box_settings;
Ref<BoxShape> box_shape = new BoxShape(Vec3(0.9f, 1.0f, 0.1f));
box_shape->SetDensity(inDensity); // Make box more heavy so the bouncing ball keeps a higher velocity
box_settings.SetShape(box_shape);
box_settings.mObjectLayer = Layers::MOVING;
// U shaped set of thin boxes
for (int i = 0; i < inNumWalls; ++i)
{
box_settings.mPosition = inOffset + Vec3(2.0f * i, 1, -1.1f - inRadius);
mBodyInterface->CreateAndAddBody(box_settings, EActivation::DontActivate);
box_settings.mPosition = inOffset + Vec3(2.0f * i, 1, +1.1f + inRadius);
mBodyInterface->CreateAndAddBody(box_settings, EActivation::DontActivate);
}
box_settings.mPosition = inOffset + Vec3(-1.1f - inRadius, 1, 0);
box_settings.mRotation = Quat::sRotation(Vec3::sAxisY(), 0.5f * JPH_PI);
mBodyInterface->CreateAndAddBody(box_settings, EActivation::DontActivate);
}
void HighSpeedTest::CreateDynamicObject(RVec3Arg inPosition, Vec3Arg inVelocity, Shape *inShape, EMotionQuality inMotionQuality)
{
BodyCreationSettings creation_settings;
creation_settings.SetShape(inShape);
creation_settings.mFriction = 0.0f;
creation_settings.mRestitution = 1.0f;
creation_settings.mLinearDamping = 0.0f;
creation_settings.mAngularDamping = 0.0f;
creation_settings.mMotionQuality = inMotionQuality;
creation_settings.mObjectLayer = Layers::MOVING;
creation_settings.mPosition = inPosition;
Body &body = *mBodyInterface->CreateBody(creation_settings);
body.SetLinearVelocity(inVelocity);
mBodyInterface->AddBody(body.GetID(), inVelocity.IsNearZero()? EActivation::DontActivate : EActivation::Activate);
}
void HighSpeedTest::CreateSimpleScene()
{
// Floor
CreateFloor();
const float radius = 0.1f;
const int num_walls = 5;
const float density = 2000.0f;
const float speed = 240.0f;
RVec3 offset(0, 0, -30);
{
// U shaped set of thin walls
TriangleList triangles;
for (int i = 0; i < num_walls; ++i)
{
triangles.push_back(Triangle(Float3(2.0f*i-1,0,-1-radius), Float3(2.0f*i+1,0,-1-radius), Float3(2.0f*i,2,-1-radius)));
triangles.push_back(Triangle(Float3(2.0f*i-1,0,1+radius), Float3(2.0f*i,2,1+radius), Float3(2.0f*i+1,0,1+radius)));
}
triangles.push_back(Triangle(Float3(-1-radius,0,-1), Float3(-1-radius,2,0), Float3(-1-radius,0,1)));
Body &walls = *mBodyInterface->CreateBody(BodyCreationSettings(new MeshShapeSettings(triangles), offset, Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING));
walls.SetRestitution(1.0f);
walls.SetFriction(0.0f);
mBodyInterface->AddBody(walls.GetID(), EActivation::DontActivate);
// Fast moving sphere against mesh
CreateDynamicObject(offset + Vec3(2.0f * num_walls - 1, 1, 0), Vec3(-speed, 0, -speed), new SphereShape(radius));
}
offset += Vec3(0, 0, 5);
{
// Create wall of domino blocks
CreateDominoBlocks(offset, num_walls, density, radius);
// Fast moving sphere against domino blocks
CreateDynamicObject(offset + Vec3(2.0f * num_walls - 1, 1, 0), Vec3(-speed, 0, -speed), new SphereShape(radius));
}
offset += Vec3(0, 0, 5);
{
// Create wall of domino blocks
CreateDominoBlocks(offset, num_walls, density, radius);
// Fast moving scaled box against domino blocks
CreateDynamicObject(offset + Vec3(2.0f * num_walls - 1, 1, 0), Vec3(-speed, 0, -speed), new ScaledShape(new BoxShape(Vec3::sReplicate(0.5f * radius), 0.01f), Vec3::sReplicate(2.0f)));
}
offset += Vec3(0, 0, 5);
{
// Fast moving box stuck in ground moving, one moving up, one moving down
CreateDynamicObject(offset + Vec3(-1, 0, 0), Vec3(0, speed, 0), new BoxShape(Vec3::sReplicate(radius)));
CreateDynamicObject(offset + Vec3(1, 0, 0), Vec3(0, -speed, 0), new BoxShape(Vec3::sReplicate(radius)));
}
offset += Vec3(0, 0, 5);
{
// Single shape that has 4 walls to surround fast moving sphere
BodyCreationSettings enclosing_settings;
Ref<BoxShapeSettings> box_shape = new BoxShapeSettings(Vec3(1.0f, 1.0f, 0.1f));
Ref<StaticCompoundShapeSettings> enclosing_shape = new StaticCompoundShapeSettings();
enclosing_shape->AddShape(Vec3(0, 0, 1), Quat::sIdentity(), box_shape);
enclosing_shape->AddShape(Vec3(0, 0, -1), Quat::sIdentity(), box_shape);
enclosing_shape->AddShape(Vec3(1, 0, 0), Quat::sRotation(Vec3::sAxisY(), 0.5f * JPH_PI), box_shape);
enclosing_shape->AddShape(Vec3(-1, 0, 0), Quat::sRotation(Vec3::sAxisY(), 0.5f * JPH_PI), box_shape);
enclosing_settings.SetShapeSettings(enclosing_shape);
enclosing_settings.mMotionType = EMotionType::Kinematic;
enclosing_settings.mObjectLayer = Layers::MOVING;
enclosing_settings.mPosition = offset + Vec3(0, 1, 0);
mBodyInterface->CreateAndAddBody(enclosing_settings, EActivation::Activate);
// Fast moving sphere in box
CreateDynamicObject(offset + Vec3(0, 0.5f, 0), Vec3(-speed, 0, -0.5f * speed), new SphereShape(radius));
}
offset += Vec3(0, 0, 5);
{
// Two boxes on a collision course
CreateDynamicObject(offset + Vec3(1, 0.5f, 0), Vec3(-speed, 0, 0), new BoxShape(Vec3::sReplicate(radius)));
CreateDynamicObject(offset + Vec3(-1, 0.5f, 0), Vec3(speed, 0, 0), new BoxShape(Vec3::sReplicate(radius)));
}
offset += Vec3(0, 0, 5);
{
// Two boxes on a collision course, off center
CreateDynamicObject(offset + Vec3(1, 0.5f, 0), Vec3(-speed, 0, 0), new BoxShape(Vec3::sReplicate(radius)));
CreateDynamicObject(offset + Vec3(-1, 0.5f, radius), Vec3(speed, 0, 0), new BoxShape(Vec3::sReplicate(radius)));
}
offset += Vec3(0, 0, 5);
{
// Two boxes on a collision course, one discrete
CreateDynamicObject(offset + Vec3(1, 0.5f, 0), Vec3(-speed, 0, 0), new BoxShape(Vec3::sReplicate(radius)));
CreateDynamicObject(offset + Vec3(-1, 0.5f, 0), Vec3(60.0f, 0, 0), new BoxShape(Vec3::sReplicate(radius)), EMotionQuality::Discrete);
}
offset += Vec3(0, 0, 5);
{
// Two boxes on a collision course, one inactive
CreateDynamicObject(offset + Vec3(1, 0.5f, 0), Vec3(-speed, 0, 0), new BoxShape(Vec3::sReplicate(radius)));
CreateDynamicObject(offset + Vec3(0, 0.5f, 0), Vec3::sZero(), new BoxShape(Vec3::sReplicate(radius)));
}
offset += Vec3(0, 0, 5);
{
// Two boxes on a collision course, one inactive and discrete
CreateDynamicObject(offset + Vec3(1, 0.5f, 0), Vec3(-speed, 0, 0), new BoxShape(Vec3::sReplicate(radius)));
CreateDynamicObject(offset + Vec3(0, 0.5f, 0), Vec3::sZero(), new BoxShape(Vec3::sReplicate(radius)), EMotionQuality::Discrete);
}
offset += Vec3(0, 0, 5);
{
// Create long thin shape
BoxShapeSettings box_settings(Vec3(0.05f, 0.8f, 0.03f), 0.015f);
box_settings.SetEmbedded();
BodyCreationSettings body_settings(&box_settings, offset + Vec3(0, 1, 0), Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI), EMotionType::Dynamic, Layers::MOVING);
body_settings.mMotionQuality = EMotionQuality::LinearCast;
body_settings.mRestitution = 0.0f;
body_settings.mFriction = 1.0f;
Body &body = *mPhysicsSystem->GetBodyInterface().CreateBody(body_settings);
body.SetLinearVelocity(Vec3(0, -100.0f, 0));
mPhysicsSystem->GetBodyInterface().AddBody(body.GetID(), EActivation::Activate);
}
offset += Vec3(0, 0, 5);
{
// Create long thin shape under 45 degrees
BoxShapeSettings box_settings(Vec3(0.05f, 0.8f, 0.03f), 0.015f);
box_settings.SetEmbedded();
BodyCreationSettings body_settings(&box_settings, offset + Vec3(0, 1, 0), Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI), EMotionType::Dynamic, Layers::MOVING);
body_settings.mMotionQuality = EMotionQuality::LinearCast;
body_settings.mRestitution = 0.0f;
body_settings.mFriction = 1.0f;
Body &body = *mPhysicsSystem->GetBodyInterface().CreateBody(body_settings);
body.SetLinearVelocity(Vec3(0, -100.0f, 0));
mPhysicsSystem->GetBodyInterface().AddBody(body.GetID(), EActivation::Activate);
}
offset += Vec3(0, 0, 5);
{
// Create long thin shape with restitution
BoxShapeSettings box_settings(Vec3(0.05f, 0.8f, 0.03f), 0.015f);
box_settings.SetEmbedded();
BodyCreationSettings body_settings(&box_settings, offset + Vec3(0, 1, 0), Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI), EMotionType::Dynamic, Layers::MOVING);
body_settings.mMotionQuality = EMotionQuality::LinearCast;
body_settings.mRestitution = 1.0f;
body_settings.mFriction = 1.0f;
Body &body = *mPhysicsSystem->GetBodyInterface().CreateBody(body_settings);
body.SetLinearVelocity(Vec3(0, -100.0f, 0));
mPhysicsSystem->GetBodyInterface().AddBody(body.GetID(), EActivation::Activate);
}
offset += Vec3(0, 0, 5);
{
// Create long thin shape under 45 degrees with restitution
BoxShapeSettings box_settings(Vec3(0.05f, 0.8f, 0.03f), 0.015f);
box_settings.SetEmbedded();
BodyCreationSettings body_settings(&box_settings, offset + Vec3(0, 1, 0), Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI), EMotionType::Dynamic, Layers::MOVING);
body_settings.mMotionQuality = EMotionQuality::LinearCast;
body_settings.mRestitution = 1.0f;
body_settings.mFriction = 1.0f;
Body &body = *mPhysicsSystem->GetBodyInterface().CreateBody(body_settings);
body.SetLinearVelocity(Vec3(0, -100.0f, 0));
mPhysicsSystem->GetBodyInterface().AddBody(body.GetID(), EActivation::Activate);
}
}
void HighSpeedTest::CreateFastSmallConvexObjects()
{
// Create small convex hull
Array<Vec3> vertices = {
Vec3(-0.044661f, 0.001230f, 0.003877f),
Vec3(-0.024743f, -0.042562f, 0.003877f),
Vec3(-0.012336f, -0.021073f, 0.048484f),
Vec3(0.016066f, 0.028121f, -0.049904f),
Vec3(-0.023734f, 0.043275f, -0.024153f),
Vec3(0.020812f, 0.036341f, -0.019530f),
Vec3(0.012495f, 0.021936f, 0.045288f),
Vec3(0.026750f, 0.001230f, 0.049273f),
Vec3(0.045495f, 0.001230f, -0.022077f),
Vec3(0.022193f, -0.036274f, -0.021126f),
Vec3(0.022781f, -0.037291f, 0.029558f),
Vec3(0.014691f, -0.023280f, 0.052897f),
Vec3(-0.012187f, -0.020815f, -0.040214f),
Vec3(0.000541f, 0.001230f, -0.056224f),
Vec3(-0.039882f, 0.001230f, -0.019461f),
Vec3(0.000541f, 0.001230f, 0.056022f),
Vec3(-0.020614f, -0.035411f, -0.020551f),
Vec3(-0.019485f, 0.035916f, 0.027001f),
Vec3(-0.023968f, 0.043680f, 0.003877f),
Vec3(-0.020051f, 0.001230f, 0.039543f),
Vec3(0.026213f, 0.001230f, -0.040589f),
Vec3(-0.010797f, 0.020868f, 0.043152f),
Vec3(-0.012378f, 0.023607f, -0.040876f)
};
ConvexHullShapeSettings convex_settings(vertices);
convex_settings.SetEmbedded();
BodyCreationSettings body_settings(&convex_settings, RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
body_settings.mMotionQuality = EMotionQuality::LinearCast;
// Create many instances with high velocity
default_random_engine rnd;
uniform_real_distribution<float> restitution_distrib(0.0f, 0.1f);
uniform_real_distribution<float> velocity_distrib(-10.0f, 10.0f);
for (int x = -25; x < 25; ++x)
for (int y = -25 ; y < 25; ++y)
{
// Cast a ray to find the terrain
RVec3 origin(Real(x), 100.0_r, Real(y));
Vec3 direction(0, -100.0f, 0);
RRayCast ray { origin, direction };
RayCastResult hit;
if (mPhysicsSystem->GetNarrowPhaseQuery().CastRay(ray, hit, SpecifiedBroadPhaseLayerFilter(BroadPhaseLayers::NON_MOVING), SpecifiedObjectLayerFilter(Layers::NON_MOVING)))
{
// Place 10m above terrain
body_settings.mPosition = ray.GetPointOnRay(hit.mFraction) + RVec3(0, 10, 0);
body_settings.mRotation = Quat::sRandom(rnd);
body_settings.mRestitution = restitution_distrib(rnd);
Body &body = *mPhysicsSystem->GetBodyInterface().CreateBody(body_settings);
body.SetLinearVelocity(Vec3(velocity_distrib(rnd), -100.0f, velocity_distrib(rnd)));
mPhysicsSystem->GetBodyInterface().AddBody(body.GetID(), EActivation::Activate);
}
}
}
void HighSpeedTest::CreateConvexOnLargeTriangles()
{
// Create floor
CreateLargeTriangleFloor();
CreateFastSmallConvexObjects();
}
void HighSpeedTest::CreateConvexOnTerrain1()
{
#ifdef JPH_OBJECT_STREAM
// Load scene
Ref<PhysicsScene> scene;
AssetStream stream("terrain1.bof", std::ios::in | std::ios::binary);
if (!ObjectStreamIn::sReadObject(stream.Get(), scene))
FatalError("Failed to load scene");
for (BodyCreationSettings &body : scene->GetBodies())
body.mObjectLayer = Layers::NON_MOVING;
scene->FixInvalidScales();
scene->CreateBodies(mPhysicsSystem);
#else
CreateFloor();
#endif // JPH_OBJECT_STREAM
CreateFastSmallConvexObjects();
}
void HighSpeedTest::Initialize()
{
switch (sSelectedScene)
{
case 0:
CreateSimpleScene();
break;
case 1:
CreateConvexOnLargeTriangles();
break;
case 2:
CreateConvexOnTerrain1();
break;
default:
JPH_ASSERT(false);
break;
}
}
void HighSpeedTest::CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu)
{
inUI->CreateTextButton(inSubMenu, "Select Scene", [this, inUI]() {
UIElement *scene_name = inUI->CreateMenu();
for (uint i = 0; i < size(sScenes); ++i)
inUI->CreateTextButton(scene_name, sScenes[i], [this, i]() { sSelectedScene = i; RestartTest(); });
inUI->ShowMenu(scene_name);
});
}

View File

@@ -0,0 +1,41 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class HighSpeedTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, HighSpeedTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Spawns a number of high speed objects to check that they don't tunnel through geometry.";
}
// See: Test
virtual void Initialize() override;
// Number used to scale the terrain and camera movement to the scene
virtual float GetWorldScale() const override { return sSelectedScene == 0? 1.0f : 0.2f; }
// Optional settings menu
virtual bool HasSettingsMenu() const override { return true; }
virtual void CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu) override;
private:
void CreateDynamicObject(RVec3Arg inPosition, Vec3Arg inVelocity, Shape *inShape, EMotionQuality inMotionQuality = EMotionQuality::LinearCast);
void CreateDominoBlocks(RVec3Arg inOffset, int inNumWalls, float inDensity, float inRadius);
void CreateFastSmallConvexObjects();
void CreateSimpleScene();
void CreateConvexOnLargeTriangles();
void CreateConvexOnTerrain1();
static const char * sScenes[];
static int sSelectedScene;
};

View File

@@ -0,0 +1,32 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/IslandTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(IslandTest)
{
JPH_ADD_BASE_CLASS(IslandTest, Test)
}
void IslandTest::Initialize()
{
// Floor
CreateFloor();
RefConst<Shape> box_shape = new BoxShape(Vec3(1.0f, 1.0f, 1.0f));
// Walls
for (int i = 0; i < 10; ++i)
for (int j = i / 2; j < 10 - (i + 1) / 2; ++j)
for (int k = 0; k < 8; ++k)
{
RVec3 position(-10 + j * 2.0f + (i & 1? 1.0f : 0.0f), 1.0f + i * 2.0f, 8.0f * (k - 4));
mBodyInterface->CreateAndAddBody(BodyCreationSettings(box_shape, position, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
}
}

View File

@@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class IslandTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, IslandTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Creates a number of disjoint piles of blocks to see if the islands are properly determined and that the simulation spreads them out over multiple CPUs.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,50 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/KinematicTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(KinematicTest)
{
JPH_ADD_BASE_CLASS(KinematicTest, Test)
}
void KinematicTest::Initialize()
{
// Floor
CreateFloor();
// Wall
RefConst<Shape> box_shape = new BoxShape(Vec3(1.0f, 1.0f, 1.0f));
for (int i = 0; i < 3; ++i)
for (int j = i / 2; j < 10 - (i + 1) / 2; ++j)
{
RVec3 position(-10.0f + j * 2.0f + (i & 1? 1.0f : 0.0f), 1.0f + i * 2.0f, 0);
mBodyInterface->CreateAndAddBody(BodyCreationSettings(box_shape, position, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::DontActivate);
}
// Kinematic object
for (int i = 0; i < 2; ++i)
{
mKinematic[i] = mBodyInterface->CreateBody(BodyCreationSettings(new SphereShape(1.0f), RVec3(-10.0f, 2.0f, i == 0? 5.0f : -5.0f), Quat::sIdentity(), EMotionType::Kinematic, Layers::MOVING));
mBodyInterface->AddBody(mKinematic[i]->GetID(), EActivation::Activate);
}
}
void KinematicTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
{
for (int i = 0; i < 2; ++i)
{
RVec3 com = mKinematic[i]->GetCenterOfMassPosition();
if (com.GetZ() >= 5.0f)
mKinematic[i]->SetLinearVelocity(Vec3(2.0f, 0, -10.0f));
else if (com.GetZ() <= -5.0f)
mKinematic[i]->SetLinearVelocity(Vec3(2.0f, 0, 10.0f));
}
}

View File

@@ -0,0 +1,27 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
#include <Jolt/Physics/Body/Body.h>
class KinematicTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, KinematicTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Tests kinematic objects against a pile of dynamic boxes.";
}
// See: Test
virtual void Initialize() override;
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
private:
Body * mKinematic[2];
};

View File

@@ -0,0 +1,61 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/LoadSaveBinaryTest.h>
#include <Tests/General/LoadSaveSceneTest.h>
#include <Jolt/Physics/PhysicsScene.h>
#include <Utils/Log.h>
#include <Jolt/Core/StreamWrapper.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(LoadSaveBinaryTest)
{
JPH_ADD_BASE_CLASS(LoadSaveBinaryTest, Test)
}
void LoadSaveBinaryTest::Initialize()
{
// Create scene
Ref<PhysicsScene> scene = LoadSaveSceneTest::sCreateScene();
{
// Create a new scene by instantiating the scene in a physics system and then converting it back to a scene
PhysicsSystem system;
BPLayerInterfaceImpl layer_interface;
ObjectVsBroadPhaseLayerFilterImpl object_vs_broadphase_layer_filter;
ObjectLayerPairFilterImpl object_vs_object_layer_filter;
system.Init(mPhysicsSystem->GetMaxBodies(), 0, 1024, 1024, layer_interface, object_vs_broadphase_layer_filter, object_vs_object_layer_filter);
scene->CreateBodies(&system);
Ref<PhysicsScene> scene_copy = new PhysicsScene();
scene_copy->FromPhysicsSystem(&system);
// Replace the original scene
scene = scene_copy;
}
stringstream data;
// Write scene
{
StreamOutWrapper stream_out(data);
scene->SaveBinaryState(stream_out, true, true);
}
// Clear scene
scene = nullptr;
// Read scene back in
{
StreamInWrapper stream_in(data);
PhysicsScene::PhysicsSceneResult result = PhysicsScene::sRestoreFromBinaryState(stream_in);
if (result.HasError())
FatalError(result.GetError().c_str());
scene = result.Get();
}
// Instantiate scene
scene->CreateBodies(mPhysicsSystem);
}

View File

@@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class LoadSaveBinaryTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, LoadSaveBinaryTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Tests the binary serialization system by creating a number of shapes, storing them, loading them and then simulating them.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,236 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/LoadSaveSceneTest.h>
#include <External/Perlin.h>
#include <Jolt/ObjectStream/ObjectStreamOut.h>
#include <Jolt/ObjectStream/ObjectStreamIn.h>
#include <Jolt/Physics/Collision/Shape/MeshShape.h>
#include <Jolt/Physics/Collision/Shape/ScaledShape.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
#include <Jolt/Physics/Collision/Shape/TaperedCapsuleShape.h>
#include <Jolt/Physics/Collision/Shape/CylinderShape.h>
#include <Jolt/Physics/Collision/Shape/TaperedCylinderShape.h>
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
#include <Jolt/Physics/Collision/Shape/MutableCompoundShape.h>
#include <Jolt/Physics/Collision/Shape/ConvexHullShape.h>
#include <Jolt/Physics/Collision/Shape/HeightFieldShape.h>
#include <Jolt/Physics/Collision/Shape/TriangleShape.h>
#include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
#include <Jolt/Physics/Collision/Shape/EmptyShape.h>
#include <Jolt/Physics/Collision/PhysicsMaterialSimple.h>
#include <Jolt/Physics/Constraints/DistanceConstraint.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
#include <Utils/Log.h>
#include <Utils/SoftBodyCreator.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(LoadSaveSceneTest)
{
JPH_ADD_BASE_CLASS(LoadSaveSceneTest, Test)
}
static const float cMaxHeight = 4.0f;
static MeshShapeSettings *sCreateMesh()
{
const int n = 10;
const float cell_size = 2.0f;
// Create heights
float heights[n + 1][n + 1];
for (int x = 0; x <= n; ++x)
for (int z = 0; z <= n; ++z)
heights[x][z] = cMaxHeight * PerlinNoise3(float(x) / n, 0, float(z) / n, 256, 256, 256);
// Create 'wall' around grid
for (int x = 0; x <= n; ++x)
{
heights[x][0] += 2.0f;
heights[x][n] += 2.0f;
}
for (int y = 1; y < n; ++y)
{
heights[0][y] += 2.0f;
heights[n][y] += 2.0f;
}
// Create regular grid of triangles
uint32 max_material_index = 0;
TriangleList triangles;
for (int x = 0; x < n; ++x)
for (int z = 0; z < n; ++z)
{
float center = n * cell_size / 2;
float x1 = cell_size * x - center;
float z1 = cell_size * z - center;
float x2 = x1 + cell_size;
float z2 = z1 + cell_size;
Float3 v1 = Float3(x1, heights[x][z], z1);
Float3 v2 = Float3(x2, heights[x + 1][z], z1);
Float3 v3 = Float3(x1, heights[x][z + 1], z2);
Float3 v4 = Float3(x2, heights[x + 1][z + 1], z2);
uint32 material_index = uint32((Vec3(v1) + Vec3(v2) + Vec3(v3) + Vec3(v4)).Length() / 4.0f / cell_size);
max_material_index = max(max_material_index, material_index);
triangles.push_back(Triangle(v1, v3, v4, material_index));
triangles.push_back(Triangle(v1, v4, v2, material_index));
}
// Create materials
PhysicsMaterialList materials;
for (uint i = 0; i <= max_material_index; ++i)
materials.push_back(new PhysicsMaterialSimple("Mesh Material " + ConvertToString(i), Color::sGetDistinctColor(i)));
return new MeshShapeSettings(triangles, std::move(materials));
}
static HeightFieldShapeSettings *sCreateHeightField()
{
const int n = 32;
const float cell_size = 1.0f;
// Create height samples
float heights[n * n];
for (int y = 0; y < n; ++y)
for (int x = 0; x < n; ++x)
heights[y * n + x] = cMaxHeight * PerlinNoise3(float(x) / n, 0, float(y) / n, 256, 256, 256);
// Make a hole
heights[2 * n + 2] = HeightFieldShapeConstants::cNoCollisionValue;
// Make material indices
uint8 max_material_index = 0;
uint8 material_indices[Square(n - 1)];
for (int y = 0; y < n - 1; ++y)
for (int x = 0; x < n - 1; ++x)
{
uint8 material_index = uint8(round((Vec3(x * cell_size, 0, y * cell_size) - Vec3(n * cell_size / 2, 0, n * cell_size / 2)).Length() / 10.0f));
max_material_index = max(max_material_index, material_index);
material_indices[y * (n - 1) + x] = material_index;
}
// Create materials
PhysicsMaterialList materials;
for (uint8 i = 0; i <= max_material_index; ++i)
materials.push_back(new PhysicsMaterialSimple("HeightField Material " + ConvertToString(uint(i)), Color::sGetDistinctColor(i)));
// Create height field
return new HeightFieldShapeSettings(heights, Vec3(-0.5f * cell_size * n, 0.0f, -0.5f * cell_size * n), Vec3(cell_size, 1.0f, cell_size), n, material_indices, materials);
}
Ref<PhysicsScene> LoadSaveSceneTest::sCreateScene()
{
int color = 0;
auto next_color = [&color]() { return Color::sGetDistinctColor(color++); };
RVec3 pos(0, cMaxHeight, 0);
auto next_pos = [&pos]() { pos += RVec3(0, 1.0f, 0); return pos; };
// Create scene
Ref<PhysicsScene> scene = new PhysicsScene();
// A scaled mesh floor
scene->AddBody(BodyCreationSettings(new ScaledShapeSettings(sCreateMesh(), Vec3(2.5f, 1.0f, 1.5f)), RVec3(0, 0, 0), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING));
// A heightfield floor
scene->AddBody(BodyCreationSettings(sCreateHeightField(), RVec3(50, 0, 0), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING));
// Some simple primitives
scene->AddBody(BodyCreationSettings(new TriangleShapeSettings(Vec3(-2, 0, 0), Vec3(0, 1, 0), Vec3(2, 0, 0), 0.0f, new PhysicsMaterialSimple("Triangle Material", next_color())), next_pos(), Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI), EMotionType::Static, Layers::NON_MOVING));
scene->AddBody(BodyCreationSettings(new SphereShapeSettings(0.2f, new PhysicsMaterialSimple("Sphere Material", next_color())), next_pos(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
scene->AddBody(BodyCreationSettings(new BoxShapeSettings(Vec3(0.2f, 0.2f, 0.4f), 0.01f, new PhysicsMaterialSimple("Box Material", next_color())), next_pos(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
scene->AddBody(BodyCreationSettings(new CapsuleShapeSettings(1.5f, 0.2f, new PhysicsMaterialSimple("Capsule Material", next_color())), next_pos(), Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI), EMotionType::Dynamic, Layers::MOVING));
scene->AddBody(BodyCreationSettings(new TaperedCapsuleShapeSettings(0.5f, 0.1f, 0.2f, new PhysicsMaterialSimple("Tapered Capsule Material", next_color())), next_pos(), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), EMotionType::Dynamic, Layers::MOVING));
scene->AddBody(BodyCreationSettings(new CylinderShapeSettings(0.5f, 0.2f, cDefaultConvexRadius, new PhysicsMaterialSimple("Cylinder Material", next_color())), next_pos(), Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI), EMotionType::Dynamic, Layers::MOVING));
scene->AddBody(BodyCreationSettings(new TaperedCylinderShapeSettings(0.5f, 0.2f, 0.4f, cDefaultConvexRadius, new PhysicsMaterialSimple("Tapered Cylinder Material", next_color())), next_pos(), Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI), EMotionType::Dynamic, Layers::MOVING));
scene->AddBody(BodyCreationSettings(new TaperedCylinderShapeSettings(0.5f, 0.4f, 0.0f, 0.0f, new PhysicsMaterialSimple("Cone Material", next_color())), next_pos(), Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI), EMotionType::Dynamic, Layers::MOVING));
scene->AddBody(BodyCreationSettings(new EmptyShapeSettings(), next_pos(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
// Compound with sub compound and rotation
StaticCompoundShapeSettings *sub_compound = new StaticCompoundShapeSettings();
sub_compound->AddShape(Vec3(0, 0.5f, 0), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), new BoxShapeSettings(Vec3(0.5f, 0.1f, 0.2f), cDefaultConvexRadius, new PhysicsMaterialSimple("Compound Box Material", next_color())));
sub_compound->AddShape(Vec3(0.5f, 0, 0), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), new CylinderShapeSettings(0.5f, 0.2f, cDefaultConvexRadius, new PhysicsMaterialSimple("Compound Cylinder Material", next_color())));
sub_compound->AddShape(Vec3(0, 0, 0.5f), Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI), new TaperedCapsuleShapeSettings(0.5f, 0.1f, 0.2f, new PhysicsMaterialSimple("Compound Tapered Capsule Material", next_color())));
StaticCompoundShapeSettings *compound_shape = new StaticCompoundShapeSettings();
compound_shape->AddShape(Vec3(0, 0, 0), Quat::sRotation(Vec3::sAxisX(), -0.25f * JPH_PI) * Quat::sRotation(Vec3::sAxisZ(), 0.25f * JPH_PI), sub_compound);
compound_shape->AddShape(Vec3(0, -0.1f, 0), Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI) * Quat::sRotation(Vec3::sAxisZ(), -0.75f * JPH_PI), sub_compound);
scene->AddBody(BodyCreationSettings(compound_shape, next_pos(), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), EMotionType::Dynamic, Layers::MOVING));
// Convex hull shape
Array<Vec3> tetrahedron;
tetrahedron.push_back(Vec3(-0.5f, 0, -0.5f));
tetrahedron.push_back(Vec3(0, 0, 0.5f));
tetrahedron.push_back(Vec3(0.5f, 0, -0.5f));
tetrahedron.push_back(Vec3(0, -0.5f, 0));
Ref<ConvexHullShapeSettings> convex_hull = new ConvexHullShapeSettings(tetrahedron, cDefaultConvexRadius, new PhysicsMaterialSimple("Convex Hull Material", next_color()));
scene->AddBody(BodyCreationSettings(convex_hull, next_pos(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
// Rotated convex hull
scene->AddBody(BodyCreationSettings(new RotatedTranslatedShapeSettings(Vec3::sReplicate(0.5f), Quat::sRotation(Vec3::sAxisZ(), 0.25f * JPH_PI), convex_hull), next_pos(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
// Mutable compound
MutableCompoundShapeSettings *mutable_compound = new MutableCompoundShapeSettings();
mutable_compound->AddShape(Vec3(0, 0.5f, 0), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), new BoxShapeSettings(Vec3(0.5f, 0.1f, 0.2f), cDefaultConvexRadius, new PhysicsMaterialSimple("MutableCompound Box Material", next_color())));
mutable_compound->AddShape(Vec3(0.5f, 0, 0), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), new CapsuleShapeSettings(0.5f, 0.1f, new PhysicsMaterialSimple("MutableCompound Capsule Material", next_color())));
mutable_compound->AddShape(Vec3(0, 0, 0.5f), Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI), new TaperedCapsuleShapeSettings(0.5f, 0.2f, 0.1f, new PhysicsMaterialSimple("MutableCompound Tapered Capsule Material", next_color())));
scene->AddBody(BodyCreationSettings(mutable_compound, next_pos(), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), EMotionType::Dynamic, Layers::MOVING));
// Connect the first two dynamic bodies with a distance constraint
DistanceConstraintSettings *dist_constraint = new DistanceConstraintSettings();
dist_constraint->mSpace = EConstraintSpace::LocalToBodyCOM;
scene->AddConstraint(dist_constraint, 3, 4);
// Add soft body cube
Ref<SoftBodySharedSettings> sb_cube_settings = SoftBodySharedSettings::sCreateCube(5, 0.2f);
sb_cube_settings->mMaterials = { new PhysicsMaterialSimple("Soft Body Cube Material", next_color()) };
SoftBodyCreationSettings sb_cube(sb_cube_settings, next_pos(), Quat::sIdentity(), Layers::MOVING);
scene->AddSoftBody(sb_cube);
// Add the same shape again to test sharing
sb_cube.mPosition = next_pos();
scene->AddSoftBody(sb_cube);
// Add soft body sphere
Ref<SoftBodySharedSettings> sb_sphere_settings = SoftBodyCreator::CreateSphere(0.5f);
sb_sphere_settings->mMaterials = { new PhysicsMaterialSimple("Soft Body Sphere Material", next_color()) };
SoftBodyCreationSettings sb_sphere(sb_sphere_settings, next_pos(), Quat::sIdentity(), Layers::MOVING);
sb_sphere.mPressure = 2000.0f;
scene->AddSoftBody(sb_sphere);
return scene;
}
void LoadSaveSceneTest::Initialize()
{
#ifdef JPH_OBJECT_STREAM
Ref<PhysicsScene> scene = sCreateScene();
stringstream data;
// Write scene
if (!ObjectStreamOut::sWriteObject(data, ObjectStream::EStreamType::Text, *scene))
FatalError("Failed to save scene");
// Clear scene
scene = nullptr;
// Read scene back in
if (!ObjectStreamIn::sReadObject(data, scene))
FatalError("Failed to load scene");
// Ensure that the soft body shared settings have been optimized (this is not saved to a text file)
for (SoftBodyCreationSettings &soft_body : scene->GetSoftBodies())
const_cast<SoftBodySharedSettings *>(soft_body.mSettings.GetPtr())->Optimize();
// Instantiate scene
scene->CreateBodies(mPhysicsSystem);
#endif // JPH_OBJECT_STREAM
}

View File

@@ -0,0 +1,26 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
#include <Jolt/Physics/PhysicsScene.h>
class LoadSaveSceneTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, LoadSaveSceneTest)
// Description of the test
virtual const char * GetDescription() const override
{
return "Tests the object stream serialization system by creating a number of shapes, storing them, loading them and then simulating them.";
}
// See: Test
virtual void Initialize() override;
// Create a test scene
static Ref<PhysicsScene> sCreateScene();
};

View File

@@ -0,0 +1,136 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/ManifoldReductionTest.h>
#include <External/Perlin.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/Shape/ScaledShape.h>
#include <Jolt/Physics/Collision/PhysicsMaterialSimple.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(ManifoldReductionTest)
{
JPH_ADD_BASE_CLASS(ManifoldReductionTest, Test)
}
void ManifoldReductionTest::Initialize()
{
constexpr float cPerturbance = 0.02f;
// Create mesh of regular grid of triangles
TriangleList triangles;
for (int x = -10; x < 10; ++x)
for (int z = -10; z < 10; ++z)
{
float x1 = 0.1f * x;
float z1 = 0.1f * z;
float x2 = x1 + 0.1f;
float z2 = z1 + 0.1f;
Float3 v1 = Float3(x1, cPerturbance * PerlinNoise3(x1, 0, z1, 256, 256, 256), z1);
Float3 v2 = Float3(x2, cPerturbance * PerlinNoise3(x2, 0, z1, 256, 256, 256), z1);
Float3 v3 = Float3(x1, cPerturbance * PerlinNoise3(x1, 0, z2, 256, 256, 256), z2);
Float3 v4 = Float3(x2, cPerturbance * PerlinNoise3(x2, 0, z2, 256, 256, 256), z2);
triangles.push_back(Triangle(v1, v3, v4, 0));
triangles.push_back(Triangle(v1, v4, v2, 0));
}
PhysicsMaterialList materials;
materials.push_back(new PhysicsMaterialSimple());
Ref<ShapeSettings> mesh_shape = new MeshShapeSettings(triangles, std::move(materials));
// Floor
mBodyInterface->CreateAndAddBody(BodyCreationSettings(new ScaledShapeSettings(mesh_shape, Vec3::sReplicate(20)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
// Create a box made of meshes
Ref<StaticCompoundShapeSettings> mesh_box_shape = new StaticCompoundShapeSettings;
mesh_box_shape->AddShape(Vec3(0, -1, 0), Quat::sRotation(Vec3::sAxisX(), JPH_PI), mesh_shape);
mesh_box_shape->AddShape(Vec3(0, 1, 0), Quat::sIdentity(), mesh_shape);
mesh_box_shape->AddShape(Vec3(-1, 0, 0), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), mesh_shape);
mesh_box_shape->AddShape(Vec3(1, 0, 0), Quat::sRotation(Vec3::sAxisZ(), -0.5f * JPH_PI), mesh_shape);
mesh_box_shape->AddShape(Vec3(0, 0, -1), Quat::sRotation(Vec3::sAxisX(), -0.5f * JPH_PI), mesh_shape);
mesh_box_shape->AddShape(Vec3(0, 0, 1), Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI), mesh_shape);
// A convex box
RefConst<ShapeSettings> box_shape = new BoxShapeSettings(Vec3(1, 1, 1), 0.0f);
{
// Create a compound of 3 mesh boxes
Ref<StaticCompoundShapeSettings> three_mesh_box_shape = new StaticCompoundShapeSettings;
three_mesh_box_shape->AddShape(Vec3(-2.1f, 0, 0), Quat::sIdentity(), mesh_box_shape);
three_mesh_box_shape->AddShape(Vec3(0, -1, 0), Quat::sIdentity(), mesh_box_shape);
three_mesh_box_shape->AddShape(Vec3(2.1f, 0, 0), Quat::sIdentity(), mesh_box_shape);
// Create a compound of 3 convex boxes
Ref<StaticCompoundShapeSettings> three_box_shape = new StaticCompoundShapeSettings;
three_box_shape->AddShape(Vec3(-2.1f, 0, 0), Quat::sIdentity(), box_shape);
three_box_shape->AddShape(Vec3(0, -1.1f, 0), Quat::sIdentity(), box_shape);
three_box_shape->AddShape(Vec3(2.1f, 0, 0), Quat::sIdentity(), box_shape);
// A set of 3 mesh boxes to rest on
mBodyInterface->CreateAndAddBody(BodyCreationSettings(three_mesh_box_shape, RVec3(0, 1, 0), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
// A set of 3 boxes that are dynamic where the middle one penetrates more than the other two
BodyCreationSettings box_settings(three_box_shape, RVec3(0, 2.95f, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
box_settings.mAllowSleeping = false;
mBodyInterface->CreateAndAddBody(box_settings, EActivation::Activate);
}
{
// Create a compound of 2 mesh boxes
Ref<StaticCompoundShapeSettings> two_mesh_box_shape = new StaticCompoundShapeSettings;
two_mesh_box_shape->AddShape(Vec3(-2.1f, 0, 0), Quat::sIdentity(), mesh_box_shape);
two_mesh_box_shape->AddShape(Vec3(0, -1, 0), Quat::sIdentity(), mesh_box_shape);
// Create a compound of 2 convex boxes
Ref<StaticCompoundShapeSettings> two_box_shape = new StaticCompoundShapeSettings;
two_box_shape->AddShape(Vec3(-2.1f, 0, 0), Quat::sIdentity(), box_shape);
two_box_shape->AddShape(Vec3(0, -1, 0), Quat::sIdentity(), box_shape);
// A set of 2 mesh boxes to rest on
mBodyInterface->CreateAndAddBody(BodyCreationSettings(two_mesh_box_shape, RVec3(0, 1, 4), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
// A set of 2 boxes that are dynamic, one is lower than the other
BodyCreationSettings box_settings(two_box_shape, RVec3(0, 4, 4), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
box_settings.mAllowSleeping = false;
mBodyInterface->CreateAndAddBody(box_settings, EActivation::Activate);
}
{
// Create a compound of 2 meshes under small angle, small enough to combine the manifolds.
Ref<StaticCompoundShapeSettings> two_mesh_shape = new StaticCompoundShapeSettings;
two_mesh_shape->AddShape(Vec3(1, 0, 0), Quat::sRotation(Vec3::sAxisZ(), DegreesToRadians(2)), mesh_shape);
two_mesh_shape->AddShape(Vec3(-1, 0, 0), Quat::sRotation(Vec3::sAxisZ(), DegreesToRadians(-2)), mesh_shape);
// A set of 2 meshes to rest on
mBodyInterface->CreateAndAddBody(BodyCreationSettings(two_mesh_shape, RVec3(0, 1, -4), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
// A box that is dynamic, resting on the slightly sloped surface. The surface normals are close enough so that the manifold should be merged.
BodyCreationSettings box_settings(box_shape, RVec3(0, 4, -4), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
box_settings.mAllowSleeping = false;
mBodyInterface->CreateAndAddBody(box_settings, EActivation::Activate);
}
{
// Create a compound of 2 meshes under small angle, but bigger than the limit to combine the manifolds.
Ref<StaticCompoundShapeSettings> two_mesh_shape = new StaticCompoundShapeSettings();
two_mesh_shape->AddShape(Vec3(1, 0, 0), Quat::sRotation(Vec3::sAxisZ(), DegreesToRadians(3)), mesh_shape);
two_mesh_shape->AddShape(Vec3(-1, 0, 0), Quat::sRotation(Vec3::sAxisZ(), DegreesToRadians(-3)), mesh_shape);
// A set of 2 meshes to rest on
mBodyInterface->CreateAndAddBody(BodyCreationSettings(two_mesh_shape, RVec3(0, 1, -8), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
// A box that is dynamic, resting on the slightly sloped surface. The surface normals are not close enough so that the manifold should be merged.
BodyCreationSettings box_settings(box_shape, RVec3(0, 4, -8), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
box_settings.mAllowSleeping = false;
mBodyInterface->CreateAndAddBody(box_settings, EActivation::Activate);
}
}

View File

@@ -0,0 +1,23 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class ManifoldReductionTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, ManifoldReductionTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "This test shows how many coplanar triangles are reduced to a single contact manifold.\n"
"The static geometry in this test consists of a high density triangle grid.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,112 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/ModifyMassTest.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Renderer/DebugRendererImp.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(ModifyMassTest)
{
JPH_ADD_BASE_CLASS(ModifyMassTest, Test)
}
void ModifyMassTest::ResetBodies(int inCycle)
{
mBodyInterface->SetPositionAndRotation(mBodies[0], RVec3(-5, 5, 0), Quat::sIdentity(), EActivation::Activate);
mBodyInterface->SetLinearAndAngularVelocity(mBodies[0], Vec3(10, 0, 0), Vec3::sZero());
mBodyInterface->SetUserData(mBodies[0], inCycle << 1);
mBodyInterface->SetPositionAndRotation(mBodies[1], RVec3(5, 5, 0), Quat::sIdentity(), EActivation::Activate);
mBodyInterface->SetLinearAndAngularVelocity(mBodies[1], Vec3(-10, 0, 0), Vec3::sZero());
mBodyInterface->SetUserData(mBodies[1], (inCycle << 1) + 1);
}
void ModifyMassTest::UpdateLabels()
{
for (BodyID id : mBodies)
{
BodyLockRead body_lock(mPhysicsSystem->GetBodyLockInterface(), id);
if (body_lock.Succeeded())
{
const Body &body = body_lock.GetBody();
SetBodyLabel(id, StringFormat("Inv mass scale: %.1f\nVelocity X: %.1f", (double)sGetInvMassScale(body), (double)body.GetLinearVelocity().GetX()));
}
}
}
void ModifyMassTest::Initialize()
{
// Floor
CreateFloor();
// Create two spheres on a collision course
BodyCreationSettings bcs(new SphereShape(1.0f), RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
bcs.mRestitution = 1.0f;
mBodies[0] = mBodyInterface->CreateAndAddBody(bcs, EActivation::Activate);
mBodies[1] = mBodyInterface->CreateAndAddBody(bcs, EActivation::Activate);
ResetBodies(0);
UpdateLabels();
}
void ModifyMassTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
{
constexpr float cTimeBetweenTests = 2.0f;
int old_cycle = (int)(mTime / cTimeBetweenTests);
mTime += inParams.mDeltaTime;
int new_cycle = (int)(mTime / cTimeBetweenTests);
if (old_cycle != new_cycle)
ResetBodies(new_cycle);
UpdateLabels();
}
float ModifyMassTest::sGetInvMassScale(const Body &inBody)
{
uint64 ud = inBody.GetUserData();
int index = ((ud & 1) != 0? (ud >> 1) : (ud >> 3)) & 0b11;
float mass_overrides[] = { 1.0f, 0.0f, 0.5f, 2.0f };
return mass_overrides[index];
}
void ModifyMassTest::OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings)
{
// We're only concerned with dynamic bodies (floor gets normal collision response)
if (!inBody1.IsDynamic() || !inBody2.IsDynamic())
return;
// Override the mass of body 1
float scale1 = sGetInvMassScale(inBody1);
ioSettings.mInvMassScale1 = scale1;
ioSettings.mInvInertiaScale1 = scale1;
// Override the mass of body 2
float scale2 = sGetInvMassScale(inBody2);
ioSettings.mInvMassScale2 = scale2;
ioSettings.mInvInertiaScale2 = scale2;
}
void ModifyMassTest::OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings)
{
OnContactAdded(inBody1, inBody2, inManifold, ioSettings);
}
void ModifyMassTest::SaveState(StateRecorder &inStream) const
{
Test::SaveState(inStream);
inStream.Write(mTime);
}
void ModifyMassTest::RestoreState(StateRecorder &inStream)
{
Test::RestoreState(inStream);
inStream.Read(mTime);
}

View File

@@ -0,0 +1,48 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
#include <Jolt/Physics/Collision/ContactListener.h>
class ModifyMassTest : public Test, public ContactListener
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, ModifyMassTest)
// Description of the test
virtual const char * GetDescription() const override
{
return "Uses a contact listener to modify the mass of bodies per contacting body pair.\n"
"Can be used to e.g. make a dynamic body respond normally to one body and appear to have infinite mass for another.";
}
// See: Test
virtual void Initialize() override;
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
virtual void SaveState(StateRecorder &inStream) const override;
virtual void RestoreState(StateRecorder &inStream) override;
// If this test implements a contact listener, it should be returned here
virtual ContactListener * GetContactListener() override { return this; }
// See: ContactListener
virtual void OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override;
virtual void OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override;
private:
// Get the scale factor for a body based on its user data
static float sGetInvMassScale(const Body &inBody);
// Reset the bodies to their initial states
void ResetBodies(int inCycle);
// Update the labels on the bodies
void UpdateLabels();
float mTime = 0.0f;
BodyID mBodies[2];
};

View File

@@ -0,0 +1,268 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/MultithreadedTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/RayCast.h>
#include <Jolt/Physics/Collision/CastResult.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Skeleton/Skeleton.h>
#include <Jolt/Skeleton/SkeletalAnimation.h>
#include <Jolt/Skeleton/SkeletonPose.h>
#include <Jolt/Physics/Ragdoll/Ragdoll.h>
#include <Jolt/ObjectStream/ObjectStreamIn.h>
#include <Layers.h>
#include <Utils/RagdollLoader.h>
#include <Utils/Log.h>
#include <Utils/AssetStream.h>
#include <Renderer/DebugRendererImp.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(MultithreadedTest)
{
JPH_ADD_BASE_CLASS(MultithreadedTest, Test)
}
MultithreadedTest::~MultithreadedTest()
{
// Quit the threads
mIsQuitting = true;
mBoxSpawnerThread.join();
mRagdollSpawnerThread.join();
mCasterThread.join();
}
void MultithreadedTest::Initialize()
{
// Floor
CreateFloor();
// Start threads
mBoxSpawnerThread = thread([this]() { BoxSpawner(); });
mRagdollSpawnerThread = thread([this]() { RagdollSpawner(); });
mCasterThread = thread([this]() { CasterMain(); });
}
void MultithreadedTest::Execute(default_random_engine &ioRandom, const char *inName, function<void()> inFunction)
{
uniform_real_distribution<float> chance(0, 1);
if (chance(ioRandom) < 0.5f)
{
// Execute as a job and wait for it
JobHandle handle = mJobSystem->CreateJob(inName, Color::sGreen, inFunction);
while (!handle.IsDone())
this_thread::sleep_for(1ms);
}
else
{
// Execute in this separate thread (not part of the job system)
JPH_PROFILE(inName);
inFunction();
}
}
void MultithreadedTest::BoxSpawner()
{
JPH_PROFILE_THREAD_START("BoxSpawner");
#ifdef JPH_DEBUG
const int cMaxObjects = 100;
#else
const int cMaxObjects = 1000;
#endif
default_random_engine random;
Array<BodyID> bodies;
while (!mIsQuitting)
{
// Ensure there are enough objects at all times
if (bodies.size() < cMaxObjects)
{
BodyID body_id;
Execute(random, "AddBody", [this, &body_id, &random]() {
uniform_real_distribution<float> from_y(0, 10);
uniform_real_distribution<float> from_xz(-5, 5);
RVec3 position(from_xz(random), 1.0f + from_y(random), from_xz(random));
Quat orientation = Quat::sRandom(random);
Vec3 velocity = Vec3::sRandom(random);
Body &body = *mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3(0.5f, 0.2f, 0.3f)), position, orientation, EMotionType::Dynamic, Layers::MOVING));
body.SetLinearVelocity(velocity);
body_id = body.GetID();
mBodyInterface->AddBody(body_id, EActivation::Activate);
});
Execute(random, "Remove/AddBody", [this, body_id]() {
// Undo/redo add to trigger more race conditions
mBodyInterface->RemoveBody(body_id);
mBodyInterface->AddBody(body_id, EActivation::Activate);
});
bodies.push_back(body_id);
}
uniform_real_distribution<float> chance(0, 1);
if (bodies.size() > 0 && chance(random) < 0.5f)
{
// Pick random body
uniform_int_distribution<size_t> element(0, bodies.size() - 1);
size_t index = element(random);
BodyID body_id = bodies[index];
bodies.erase(bodies.begin() + index);
Execute(random, "Remove/DestroyBody", [this, body_id]() {
// Remove it
mBodyInterface->RemoveBody(body_id);
mBodyInterface->DestroyBody(body_id);
});
}
this_thread::sleep_for(1ms);
}
JPH_PROFILE_THREAD_END();
}
void MultithreadedTest::RagdollSpawner()
{
JPH_PROFILE_THREAD_START("RagdollSpawner");
#ifdef JPH_DEBUG
const int cMaxRagdolls = 10;
#else
const int cMaxRagdolls = 50;
#endif
#ifdef JPH_OBJECT_STREAM
// Load ragdoll
Ref<RagdollSettings> ragdoll_settings = RagdollLoader::sLoad("Human.tof", EMotionType::Dynamic);
if (ragdoll_settings == nullptr)
FatalError("Could not load ragdoll");
#else
// Create a ragdoll from code
Ref<RagdollSettings> ragdoll_settings = RagdollLoader::sCreate();
#endif // JPH_OBJECT_STREAM
// Create pose
SkeletonPose ragdoll_pose;
ragdoll_pose.SetSkeleton(ragdoll_settings->GetSkeleton());
{
#ifdef JPH_OBJECT_STREAM
Ref<SkeletalAnimation> animation;
AssetStream stream("Human/dead_pose1.tof", std::ios::in);
if (!ObjectStreamIn::sReadObject(stream.Get(), animation))
FatalError("Could not open animation");
animation->Sample(0.0f, ragdoll_pose);
#else
Ref<Ragdoll> temp_ragdoll = ragdoll_settings->CreateRagdoll(0, 0, mPhysicsSystem);
temp_ragdoll->GetPose(ragdoll_pose);
ragdoll_pose.CalculateJointStates();
#endif // JPH_OBJECT_STREAM
}
default_random_engine random;
uniform_real_distribution<float> from_y(0, 10);
uniform_real_distribution<float> from_xz(-5, 5);
CollisionGroup::GroupID group_id = 1;
Array<Ref<Ragdoll>> ragdolls;
while (!mIsQuitting)
{
// Ensure there are enough objects at all times
if (ragdolls.size() < cMaxRagdolls)
{
// Create ragdoll
Ref<Ragdoll> ragdoll = ragdoll_settings->CreateRagdoll(group_id++, 0, mPhysicsSystem);
// Override root
SkeletonPose::JointState &root = ragdoll_pose.GetJoint(0);
root.mRotation = Quat::sRandom(random);
ragdoll_pose.SetRootOffset(RVec3(from_xz(random), 1.0f + from_y(random), from_xz(random)));
ragdoll_pose.CalculateJointMatrices();
// Drive to pose
ragdoll->SetPose(ragdoll_pose);
ragdoll->DriveToPoseUsingMotors(ragdoll_pose);
Execute(random, "Activate", [ragdoll]() {
// Activate the ragdoll
ragdoll->AddToPhysicsSystem(EActivation::Activate);
});
Execute(random, "Deactivate/Activate", [ragdoll]() {
// Undo/redo add to trigger more race conditions
ragdoll->RemoveFromPhysicsSystem();
ragdoll->AddToPhysicsSystem(EActivation::Activate);
});
ragdolls.push_back(ragdoll);
}
uniform_real_distribution<float> chance(0, 1);
if (ragdolls.size() > 0 && chance(random) < 0.1f)
{
// Pick random body
uniform_int_distribution<size_t> element(0, ragdolls.size() - 1);
size_t index = element(random);
Ref<Ragdoll> ragdoll = ragdolls[index];
ragdolls.erase(ragdolls.begin() + index);
Execute(random, "Deactivate", [ragdoll]() {
// Deactivate it
ragdoll->RemoveFromPhysicsSystem();
});
}
this_thread::sleep_for(1ms);
}
for (Ragdoll *r : ragdolls)
r->RemoveFromPhysicsSystem();
JPH_PROFILE_THREAD_END();
}
void MultithreadedTest::CasterMain()
{
JPH_PROFILE_THREAD_START("CasterMain");
default_random_engine random;
Array<BodyID> bodies;
while (!mIsQuitting)
{
Execute(random, "CastRay", [this, &random]() {
// Cast a random ray
uniform_real_distribution<float> from_y(0, 10);
uniform_real_distribution<float> from_xz(-5, 5);
RVec3 from(from_xz(random), from_y(random), from_xz(random));
RVec3 to(from_xz(random), from_y(random), from_xz(random));
RRayCast ray { from, Vec3(to - from) };
RayCastResult hit;
if (mPhysicsSystem->GetNarrowPhaseQuery().CastRay(ray, hit, SpecifiedBroadPhaseLayerFilter(BroadPhaseLayers::MOVING), SpecifiedObjectLayerFilter(Layers::MOVING)))
{
// Draw hit position
RVec3 hit_position_world = ray.GetPointOnRay(hit.mFraction);
mDebugRenderer->DrawMarker(hit_position_world, Color::sYellow, 0.2f);
BodyLockRead lock(mPhysicsSystem->GetBodyLockInterface(), hit.mBodyID);
if (lock.SucceededAndIsInBroadPhase())
{
// Draw normal
const Body &hit_body = lock.GetBody();
RMat44 inv_com = hit_body.GetInverseCenterOfMassTransform();
Vec3 normal = inv_com.Multiply3x3Transposed(hit_body.GetShape()->GetSurfaceNormal(hit.mSubShapeID2, Vec3(inv_com * hit_position_world))).Normalized();
mDebugRenderer->DrawArrow(hit_position_world, hit_position_world + normal, Color::sGreen, 0.1f);
}
}
});
}
JPH_PROFILE_THREAD_END();
}

View File

@@ -0,0 +1,49 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class MultithreadedTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, MultithreadedTest)
// Description of the test
virtual const char * GetDescription() const override
{
return "This test spawns boxes and ragdolls and performs ray cast tests from threads / jobs to see if the simulation is thread safe.";
}
// Destructor
virtual ~MultithreadedTest() override;
// Number used to scale the terrain and camera movement to the scene
virtual float GetWorldScale() const override { return 0.2f; }
// Initialization
virtual void Initialize() override;
// Test will never be deterministic since various threads are trying to concurrently add / remove bodies
virtual bool IsDeterministic() const override { return false; }
private:
// Execute a lambda either on this thread or in a separate job
void Execute(default_random_engine &ioRandom, const char *inName, function<void()> inFunction);
// Thread main function that spawns boxes
void BoxSpawner();
// Thread main function that spawns ragdolls
void RagdollSpawner();
// Thread main function that casts rays
void CasterMain();
thread mBoxSpawnerThread;
thread mRagdollSpawnerThread;
thread mCasterThread;
atomic<bool> mIsQuitting = false;
};

View File

@@ -0,0 +1,37 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/PyramidTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(PyramidTest)
{
JPH_ADD_BASE_CLASS(PyramidTest, Test)
}
void PyramidTest::Initialize()
{
// Floor
CreateFloor();
const float cBoxSize = 2.0f;
const float cBoxSeparation = 0.5f;
const float cHalfBoxSize = 0.5f * cBoxSize;
const int cPyramidHeight = 15;
RefConst<Shape> box_shape = new BoxShape(Vec3::sReplicate(cHalfBoxSize));
// Pyramid
for (int i = 0; i < cPyramidHeight; ++i)
for (int j = i / 2; j < cPyramidHeight - (i + 1) / 2; ++j)
for (int k = i / 2; k < cPyramidHeight - (i + 1) / 2; ++k)
{
RVec3 position(-cPyramidHeight + cBoxSize * j + (i & 1? cHalfBoxSize : 0.0f), 1.0f + (cBoxSize + cBoxSeparation) * i, -cPyramidHeight + cBoxSize * k + (i & 1? cHalfBoxSize : 0.0f));
mBodyInterface->CreateAndAddBody(BodyCreationSettings(box_shape, position, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
}
}

View File

@@ -0,0 +1,23 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class PyramidTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, PyramidTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Tests a large pyramid of boxes to check stacking and performance behavior.\n"
"The large island splitter should ensure that contacts are solved on multiple CPUs in parallel.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,44 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/RestitutionTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(RestitutionTest)
{
JPH_ADD_BASE_CLASS(RestitutionTest, Test)
}
void RestitutionTest::Initialize()
{
// Floor
CreateFloor();
RefConst<Shape> sphere = new SphereShape(2.0f);
RefConst<Shape> box = new BoxShape(Vec3(2.0f, 2.0f, 2.0f));
// Bodies with increasing restitution
for (int i = 0; i <= 10; ++i)
{
BodyCreationSettings settings(sphere, RVec3(-50.0f + i * 10.0f, 20.0f, -20.0f), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
settings.mRestitution = 0.1f * i;
settings.mLinearDamping = 0.0f;
BodyID id = mBodyInterface->CreateAndAddBody(settings, EActivation::Activate);
SetBodyLabel(id, StringFormat("Restitution: %.1f", double(settings.mRestitution)));
}
for (int i = 0; i <= 10; ++i)
{
BodyCreationSettings settings(box, RVec3(-50.0f + i * 10.0f, 20.0f, 20.0f), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
settings.mRestitution = 0.1f * i;
settings.mLinearDamping = 0.0f;
BodyID id = mBodyInterface->CreateAndAddBody(settings, EActivation::Activate);
SetBodyLabel(id, StringFormat("Restitution: %.1f", double(settings.mRestitution)));
}
}

View File

@@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class RestitutionTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, RestitutionTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Bodies with varying restitutions.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,251 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/SensorTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/ObjectStream/ObjectStreamIn.h>
#include <Utils/RagdollLoader.h>
#include <Utils/Log.h>
#include <Utils/AssetStream.h>
#include <Layers.h>
#include <Renderer/DebugRendererImp.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SensorTest)
{
JPH_ADD_BASE_CLASS(SensorTest, Test)
}
SensorTest::~SensorTest()
{
// Destroy the ragdoll
mRagdoll->RemoveFromPhysicsSystem();
mRagdoll = nullptr;
}
void SensorTest::Initialize()
{
// Floor
CreateFloor(400.0f);
{
// A static sensor that attracts dynamic bodies that enter its area
BodyCreationSettings sensor_settings(new SphereShape(10.0f), RVec3(0, 10, 0), Quat::sIdentity(), EMotionType::Static, Layers::SENSOR);
sensor_settings.mIsSensor = true;
mSensorID[StaticAttractor] = mBodyInterface->CreateAndAddBody(sensor_settings, EActivation::DontActivate);
SetBodyLabel(mSensorID[StaticAttractor], "Static sensor that attracts dynamic bodies");
}
{
// A static sensor that only detects active bodies
BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(5.0f)), RVec3(-10, 5.1f, 0), Quat::sIdentity(), EMotionType::Static, Layers::SENSOR);
sensor_settings.mIsSensor = true;
mSensorID[StaticSensor] = mBodyInterface->CreateAndAddBody(sensor_settings, EActivation::DontActivate);
SetBodyLabel(mSensorID[StaticSensor], "Static sensor that detects active dynamic bodies");
}
{
// A kinematic sensor that also detects sleeping bodies
BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(5.0f)), RVec3(10, 5.1f, 0), Quat::sIdentity(), EMotionType::Kinematic, Layers::SENSOR);
sensor_settings.mIsSensor = true;
mSensorID[KinematicSensor] = mBodyInterface->CreateAndAddBody(sensor_settings, EActivation::Activate);
SetBodyLabel(mSensorID[KinematicSensor], "Kinematic sensor that also detects sleeping bodies");
}
{
// A kinematic sensor that also detects static bodies
BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(5.0f)), RVec3(25, 5.1f, 0), Quat::sIdentity(), EMotionType::Kinematic, Layers::MOVING); // Put in a layer that collides with static
sensor_settings.mIsSensor = true;
sensor_settings.mCollideKinematicVsNonDynamic = true;
mSensorID[SensorDetectingStatic] = mBodyInterface->CreateAndAddBody(sensor_settings, EActivation::Activate);
SetBodyLabel(mSensorID[SensorDetectingStatic], "Kinematic sensor that also detects sleeping and static bodies");
}
// Dynamic bodies
for (int i = 0; i < 15; ++i)
mBodyInterface->CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(0.1f, 0.5f, 0.2f)), RVec3(-15.0f + i * 3.0f, 25, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
// Static bodies
RVec3 static_positions[] = { RVec3(-14, 1, 4), RVec3(6, 1, 4), RVec3(21, 1, 4) };
for (const RVec3 &p : static_positions)
mBodyInterface->CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3::sReplicate(0.5f)), p, Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::Activate);
#ifdef JPH_OBJECT_STREAM
// Load ragdoll
Ref<RagdollSettings> ragdoll_settings = RagdollLoader::sLoad("Human.tof", EMotionType::Dynamic);
if (ragdoll_settings == nullptr)
FatalError("Could not load ragdoll");
#else
Ref<RagdollSettings> ragdoll_settings = RagdollLoader::sCreate();
#endif // JPH_OBJECT_STREAM
// Create pose
SkeletonPose ragdoll_pose;
ragdoll_pose.SetSkeleton(ragdoll_settings->GetSkeleton());
{
#ifdef JPH_OBJECT_STREAM
Ref<SkeletalAnimation> animation;
AssetStream stream("Human/dead_pose1.tof", std::ios::in);
if (!ObjectStreamIn::sReadObject(stream.Get(), animation))
FatalError("Could not open animation");
animation->Sample(0.0f, ragdoll_pose);
#else
Ref<Ragdoll> temp_ragdoll = ragdoll_settings->CreateRagdoll(0, 0, mPhysicsSystem);
temp_ragdoll->GetPose(ragdoll_pose);
ragdoll_pose.CalculateJointStates();
#endif // JPH_OBJECT_STREAM
}
ragdoll_pose.SetRootOffset(RVec3(0, 30, 0));
ragdoll_pose.CalculateJointMatrices();
// Create ragdoll
mRagdoll = ragdoll_settings->CreateRagdoll(1, 0, mPhysicsSystem);
mRagdoll->SetPose(ragdoll_pose);
mRagdoll->AddToPhysicsSystem(EActivation::Activate);
// Create kinematic body
BodyCreationSettings kinematic_settings(new BoxShape(Vec3(0.25f, 0.5f, 1.0f)), RVec3(-20, 10, 0), Quat::sIdentity(), EMotionType::Kinematic, Layers::MOVING);
Body &kinematic = *mBodyInterface->CreateBody(kinematic_settings);
mKinematicBodyID = kinematic.GetID();
mBodyInterface->AddBody(kinematic.GetID(), EActivation::Activate);
}
void SensorTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
{
// Update time
mTime += inParams.mDeltaTime;
// Move kinematic body
RVec3 kinematic_pos = RVec3(-20.0f * Cos(mTime), 10, 0);
mBodyInterface->MoveKinematic(mKinematicBodyID, kinematic_pos, Quat::sIdentity(), inParams.mDeltaTime);
// Draw if body is in sensor
Color sensor_color[] = { Color::sRed, Color::sGreen, Color::sBlue, Color::sPurple };
for (int sensor = 0; sensor < NumSensors; ++sensor)
for (const BodyAndCount &body_and_count : mBodiesInSensor[sensor])
{
AABox bounds = mBodyInterface->GetTransformedShape(body_and_count.mBodyID).GetWorldSpaceBounds();
bounds.ExpandBy(Vec3::sReplicate(0.01f * sensor));
mDebugRenderer->DrawWireBox(bounds, sensor_color[sensor]);
}
// Apply forces to dynamic bodies in sensor
lock_guard lock(mMutex);
RVec3 center(0, 10, 0);
float centrifugal_force = 10.0f;
Vec3 gravity = mPhysicsSystem->GetGravity();
for (const BodyAndCount &body_and_count : mBodiesInSensor[StaticAttractor])
{
BodyLockWrite body_lock(mPhysicsSystem->GetBodyLockInterface(), body_and_count.mBodyID);
if (body_lock.Succeeded())
{
Body &body = body_lock.GetBody();
if (body.IsKinematic())
continue;
// Calculate centrifugal acceleration
Vec3 acceleration = Vec3(center - body.GetPosition());
float length = acceleration.Length();
if (length > 0.0f)
acceleration *= centrifugal_force / length;
else
acceleration = Vec3::sZero();
// Draw acceleration
mDebugRenderer->DrawArrow(body.GetPosition(), body.GetPosition() + acceleration, Color::sGreen, 0.1f);
// Cancel gravity
acceleration -= gravity;
// Apply as force
body.AddForce(acceleration / body.GetMotionProperties()->GetInverseMass());
}
}
}
void SensorTest::OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings)
{
for (int sensor = 0; sensor < NumSensors; ++sensor)
{
BodyID sensor_id = mSensorID[sensor];
// Check which body is the sensor
BodyID body_id;
if (inBody1.GetID() == sensor_id)
body_id = inBody2.GetID();
else if (inBody2.GetID() == sensor_id)
body_id = inBody1.GetID();
else
continue;
lock_guard lock(mMutex);
// Add to list and make sure that the list remains sorted for determinism (contacts can be added from multiple threads)
BodyAndCount body_and_count { body_id, 1 };
BodiesInSensor &bodies_in_sensor = mBodiesInSensor[sensor];
BodiesInSensor::iterator b = lower_bound(bodies_in_sensor.begin(), bodies_in_sensor.end(), body_and_count);
if (b != bodies_in_sensor.end() && b->mBodyID == body_id)
{
// This is the right body, increment reference
b->mCount++;
return;
}
bodies_in_sensor.insert(b, body_and_count);
}
}
void SensorTest::OnContactRemoved(const SubShapeIDPair &inSubShapePair)
{
for (int sensor = 0; sensor < NumSensors; ++sensor)
{
BodyID sensor_id = mSensorID[sensor];
// Check which body is the sensor
BodyID body_id;
if (inSubShapePair.GetBody1ID() == sensor_id)
body_id = inSubShapePair.GetBody2ID();
else if (inSubShapePair.GetBody2ID() == sensor_id)
body_id = inSubShapePair.GetBody1ID();
else
continue;
lock_guard lock(mMutex);
// Remove from list
BodyAndCount body_and_count { body_id, 1 };
BodiesInSensor &bodies_in_sensor = mBodiesInSensor[sensor];
BodiesInSensor::iterator b = lower_bound(bodies_in_sensor.begin(), bodies_in_sensor.end(), body_and_count);
if (b != bodies_in_sensor.end() && b->mBodyID == body_id)
{
// This is the right body, increment reference
JPH_ASSERT(b->mCount > 0);
b->mCount--;
// When last reference remove from the list
if (b->mCount == 0)
bodies_in_sensor.erase(b);
return;
}
JPH_ASSERT(false, "Body pair not found");
}
}
void SensorTest::SaveState(StateRecorder &inStream) const
{
inStream.Write(mTime);
for (const BodiesInSensor &b : mBodiesInSensor)
inStream.Write(b);
}
void SensorTest::RestoreState(StateRecorder &inStream)
{
inStream.Read(mTime);
for (BodiesInSensor &b : mBodiesInSensor)
inStream.Read(b);
}

View File

@@ -0,0 +1,72 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
#include <Jolt/Physics/Ragdoll/Ragdoll.h>
class SensorTest : public Test, public ContactListener
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SensorTest)
virtual ~SensorTest() override;
// Description of the test
virtual const char *GetDescription() const override
{
return "Different types of sensors. See the description of each sensor.";
}
// Number used to scale the terrain and camera movement to the scene
virtual float GetWorldScale() const override { return 0.2f; }
// See: Test
virtual void Initialize() override;
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
// If this test implements a contact listener, it should be returned here
virtual ContactListener *GetContactListener() override { return this; }
// See: ContactListener
virtual void OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override;
virtual void OnContactRemoved(const SubShapeIDPair &inSubShapePair) override;
// Saving / restoring state for replay
virtual void SaveState(StateRecorder &inStream) const override;
virtual void RestoreState(StateRecorder &inStream) override;
private:
float mTime = 0.0f; // Total elapsed time
enum
{
StaticAttractor, // A static sensor that attracts dynamic bodies that enter its area
StaticSensor, // A static sensor that only detects active bodies
KinematicSensor, // A kinematic sensor that also detects sleeping bodies
SensorDetectingStatic, // A kinematic sensor that detects static bodies
NumSensors
};
BodyID mSensorID[NumSensors]; // Body ID of the various sensors
Ref<Ragdoll> mRagdoll; // Ragdoll that is falling into the sensor
BodyID mKinematicBodyID; // Body ID of a kinematic body that is animating in and out of the sensor
Mutex mMutex; // Mutex that protects mBodiesInSensor and mKinematicBodyInSensor
// Structure that keeps track of how many contact point each body has with the sensor
struct BodyAndCount
{
BodyID mBodyID;
int mCount;
bool operator < (const BodyAndCount &inRHS) const { return mBodyID < inRHS.mBodyID; }
};
using BodiesInSensor = Array<BodyAndCount>;
BodiesInSensor mBodiesInSensor[NumSensors]; // Dynamic bodies that are currently inside the sensor
};

View File

@@ -0,0 +1,112 @@
#include <TestFramework.h>
#include <Tests/General/ShapeFilterTest.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
#include <Jolt/Physics/Collision/ShapeCast.h>
#include <Renderer/DebugRendererImp.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(ShapeFilterTest)
{
JPH_ADD_BASE_CLASS(ShapeFilterTest, Test)
}
void ShapeFilterTest::Initialize()
{
// Create geometry to cast against
mBodyInterface->CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(20, 1, 3)), RVec3(0, -1, 0), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::Activate);
mBodyInterface->CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3::sReplicate(3)), RVec3(0, 3, 0), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::Activate);
// Create shape to cast
Ref box_shape = new BoxShapeSettings(Vec3::sReplicate(1));
box_shape->mUserData = (uint64)ShapeIdentifier::Box;
Ref sphere_shape = new SphereShapeSettings(1);
sphere_shape->mUserData = (uint64)ShapeIdentifier::Sphere;
StaticCompoundShapeSettings cast_shape;
cast_shape.AddShape(Vec3(3, 2, 0), Quat::sIdentity(), box_shape);
cast_shape.AddShape(Vec3(0, 0, 0), Quat::sIdentity(), sphere_shape);
cast_shape.mUserData = (uint64)ShapeIdentifier::Compound;
mCastShape = cast_shape.Create().Get();
}
void ShapeFilterTest::PostPhysicsUpdate(float inDeltaTime)
{
mElapsedTime += inDeltaTime;
float phase = mElapsedTime;
const RVec3 cast_origin = RVec3(Cos(phase) * 10, 10, 0);
const Vec3 cast_motion = Vec3(0, -15, 0);
ClosestHitCollisionCollector<CastShapeCollector> cast_shape_collector;
class MyShapeFilter : public ShapeFilter
{
public:
virtual bool ShouldCollide(const Shape *inShape1, const SubShapeID &inSubShapeID1, const Shape *inShape2, const SubShapeID &inSubShapeID2) const override
{
return inShape1->GetUserData() != mUserDataOfShapeToIgnore;
}
// We're not interested in the other overload as it is not used by ray casts
using ShapeFilter::ShouldCollide;
uint64 mUserDataOfShapeToIgnore = (uint64)ShapeIdentifier::Sphere;
};
MyShapeFilter shape_filter;
// Select which shape to ignore
float shape_select = fmod(phase, 6.0f * JPH_PI);
const char *text;
if (shape_select < 2.0f * JPH_PI)
{
shape_filter.mUserDataOfShapeToIgnore = (uint64)ShapeIdentifier::Box;
text = "Box";
}
else if (shape_select < 4.0f * JPH_PI)
{
shape_filter.mUserDataOfShapeToIgnore = (uint64)ShapeIdentifier::Sphere;
text = "Sphere";
}
else
{
shape_filter.mUserDataOfShapeToIgnore = (uint64)ShapeIdentifier::Compound;
text = "Compound";
}
mDebugRenderer->DrawText3D(cast_origin, StringFormat("Ignoring shape: %s", text), Color::sWhite);
// Do the cast
mPhysicsSystem->GetNarrowPhaseQuery().CastShape(
RShapeCast(mCastShape, Vec3::sReplicate(1), RMat44::sTranslation(cast_origin), cast_motion),
ShapeCastSettings(),
RVec3::sZero(),
cast_shape_collector,
{ },
{ },
{ },
shape_filter
);
// Show the result
RVec3 cast_point;
Color color;
if (cast_shape_collector.HadHit())
{
cast_point = cast_origin + cast_motion * cast_shape_collector.mHit.mFraction;
color = Color::sGreen;
}
else
{
cast_point = cast_origin + cast_motion;
color = Color::sRed;
}
mDebugRenderer->DrawArrow(cast_origin, cast_point, Color::sOrange, 0.1f);
JPH_IF_DEBUG_RENDERER(mCastShape->Draw(mDebugRenderer, RMat44::sTranslation(RVec3(cast_point)), Vec3::sOne(), color, false, true);)
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include <Tests/Test.h>
class ShapeFilterTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, ShapeFilterTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Demonstrates how to use a shape filter to filter out shapes during a collision query.";
}
// See: Test
virtual void Initialize() override;
virtual void PostPhysicsUpdate(float inDeltaTime) override;
private:
/// A value used as user data for a shape
enum class ShapeIdentifier : uint64
{
Box = 42,
Sphere = 43,
Compound = 44
};
float mElapsedTime = 0.0f;
ShapeRefC mCastShape;
};

View File

@@ -0,0 +1,178 @@
// 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/Collision/CollideShapeVsShapePerLeaf.h>
#include <Jolt/Physics/Body/BodyCreationSettings.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())
{
// 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 <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())
{
// A sensor will return 1 hit per leaf shape pair
SubShapeIDCreator part1, part2;
CollideShapeVsShapePerLeaf<LeafCollector>(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<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);
}

View File

@@ -0,0 +1,43 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class SimCollideBodyVsBodyTest : public Test, public ContactListener
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SimCollideBodyVsBodyTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Overrides the collide body vs body function on the simulation to reduce the number of contact points generated between sensors and other objects in the simulation.\n"
"This can be useful to improve performance if you don't need to know about all contact points and are only interested in an overlap/no-overlap result.\n"
"The static world consists of a single compound shape with many pyramid sub shapes.";
}
// See: Test
virtual void Initialize() override;
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
// If this test implements a contact listener, it should be returned here
virtual ContactListener *GetContactListener() override { return this; }
// See: ContactListener
virtual void OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override;
virtual void OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override;
// Saving / restoring state for replay
virtual void SaveState(StateRecorder &inStream) const override;
virtual void RestoreState(StateRecorder &inStream) override;
private:
int mPrevMode = -1; // Previous mode
float mTime = 0.0f; // Total elapsed time
BodyID mSensorID; // Body ID of the sensor
BodyIDVector mBodyIDs; // List of dynamic bodies
};

View File

@@ -0,0 +1,79 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/SimShapeFilterTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Utils/SoftBodyCreator.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SimShapeFilterTest)
{
JPH_ADD_BASE_CLASS(SimShapeFilterTest, Test)
}
SimShapeFilterTest::~SimShapeFilterTest()
{
// Unregister shape filter
mPhysicsSystem->SetSimShapeFilter(nullptr);
}
void SimShapeFilterTest::Initialize()
{
// Register shape filter
mPhysicsSystem->SetSimShapeFilter(&mShapeFilter);
// Floor
CreateFloor();
// Platform
mShapeFilter.mPlatformID = mBodyInterface->CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(5.0f, 0.5f, 5.0f)), RVec3(0, 7.5f, 0), Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
// Compound shape
Ref<Shape> capsule = new CapsuleShape(2, 0.1f);
capsule->SetUserData(1); // Don't want the capsule to collide with the platform
Ref<Shape> sphere = new SphereShape(0.5f);
sphere->SetUserData(1); // Don't want the sphere to collide with the platform
Ref<Shape> box = new BoxShape(Vec3::sReplicate(0.5f));
Ref<StaticCompoundShapeSettings> compound = new StaticCompoundShapeSettings;
compound->AddShape(Vec3::sZero(), Quat::sIdentity(), capsule);
compound->AddShape(Vec3(0, -2, 0), Quat::sIdentity(), sphere);
compound->AddShape(Vec3(0, 2, 0), Quat::sIdentity(), box);
// Create compound above the platform
BodyCreationSettings compound_body(compound, RVec3(0, 15, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
mShapeFilter.mCompoundID[0] = mBodyInterface->CreateAndAddBody(compound_body, EActivation::Activate);
// Create cloth that's fixated at the corners
SoftBodyCreationSettings cloth(SoftBodyCreator::CreateClothWithFixatedCorners(20, 20, 0.2f), RVec3(10, 10.0f, 0), Quat::sIdentity(), Layers::MOVING);
mShapeFilter.mClothID = mBodyInterface->CreateAndAddSoftBody(cloth, EActivation::Activate);
// Create compound above the cloth
compound_body.mPosition = RVec3(10, 15, 0);
mShapeFilter.mCompoundID[1] = mBodyInterface->CreateAndAddBody(compound_body, EActivation::Activate);
}
bool SimShapeFilterTest::Filter::ShouldCollide(const Body &inBody1, const Shape *inShape1, const SubShapeID &inSubShapeIDOfShape1, const Body &inBody2, const Shape *inShape2, const SubShapeID &inSubShapeIDOfShape2) const
{
// If the platform/cloth is colliding with the compound, filter out collisions where the shape has user data 1
if (inBody1.GetID() == mPlatformID || inBody1.GetID() == mClothID)
{
for (int i = 0; i < 2; ++i)
if (inBody2.GetID() == mCompoundID[i])
return inShape2->GetUserData() != 1;
}
else if (inBody2.GetID() == mPlatformID || inBody2.GetID() == mClothID)
{
for (int i = 0; i < 2; ++i)
if (inBody1.GetID() == mCompoundID[i])
return inShape1->GetUserData() != 1;
}
return true;
}

View File

@@ -0,0 +1,41 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
#include <Jolt/Physics/Collision/SimShapeFilter.h>
class SimShapeFilterTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SimShapeFilterTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Shows how to use a shape filter during the simulation to disable contacts between certain sub shapes.\n"
"The rod and sphere of the dynamic bodies only collide with the floor.";
}
// Destructor
virtual ~SimShapeFilterTest() override;
// See: Test
virtual void Initialize() override;
private:
// A simulation shape filter
class Filter : public SimShapeFilter
{
public:
virtual bool ShouldCollide(const Body &inBody1, const Shape *inShape1, const SubShapeID &inSubShapeIDOfShape1, const Body &inBody2, const Shape *inShape2, const SubShapeID &inSubShapeIDOfShape2) const override;
BodyID mPlatformID;
BodyID mClothID;
BodyID mCompoundID[2];
};
Filter mShapeFilter;
};

View File

@@ -0,0 +1,43 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/SimpleTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/Body/BodyActivationListener.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SimpleTest)
{
JPH_ADD_BASE_CLASS(SimpleTest, Test)
}
SimpleTest::~SimpleTest()
{
// Unregister activation listener
mPhysicsSystem->SetBodyActivationListener(nullptr);
}
void SimpleTest::Initialize()
{
// Register activation listener
mPhysicsSystem->SetBodyActivationListener(&mBodyActivationListener);
// Floor
CreateFloor();
RefConst<Shape> box_shape = new BoxShape(Vec3(0.5f, 1.0f, 2.0f));
// Dynamic body 1
mBodyInterface->CreateAndAddBody(BodyCreationSettings(box_shape, RVec3(0, 10, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
// Dynamic body 2
mBodyInterface->CreateAndAddBody(BodyCreationSettings(box_shape, RVec3(5, 10, 0), Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
// Dynamic body 3
mBodyInterface->CreateAndAddBody(BodyCreationSettings(new SphereShape(2.0f), RVec3(10, 10, 0), Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
}

View File

@@ -0,0 +1,44 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
#include <Jolt/Physics/Body/BodyActivationListener.h>
class SimpleTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SimpleTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Very basic test that just drops a few objects on the floor.";
}
// Destructor
virtual ~SimpleTest() override;
// See: Test
virtual void Initialize() override;
private:
// A demo of the activation listener
class Listener : public BodyActivationListener
{
public:
virtual void OnBodyActivated(const BodyID &inBodyID, uint64 inBodyUserData) override
{
Trace("Body %d activated", inBodyID.GetIndex());
}
virtual void OnBodyDeactivated(const BodyID &inBodyID, uint64 inBodyUserData) override
{
Trace("Body %d deactivated", inBodyID.GetIndex());
}
};
Listener mBodyActivationListener;
};

View File

@@ -0,0 +1,34 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/StackTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(StackTest)
{
JPH_ADD_BASE_CLASS(StackTest, Test)
}
void StackTest::Initialize()
{
// Floor
CreateFloor();
RefConst<Shape> box_shape = new BoxShape(Vec3(0.5f, 1.0f, 2.0f));
// Dynamic body stack
for (int i = 0; i < 10; ++i)
{
Quat rotation;
if ((i & 1) != 0)
rotation = Quat::sRotation(Vec3::sAxisY(), 0.5f * JPH_PI);
else
rotation = Quat::sIdentity();
mBodyInterface->CreateAndAddBody(BodyCreationSettings(box_shape, RVec3(10, 1.0f + i * 2.1f, 0), rotation, EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
}
}

View File

@@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class StackTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, StackTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Stacks a number of boxes to see if the simulation is stable.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,45 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2022 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/TwoDFunnelTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(TwoDFunnelTest)
{
JPH_ADD_BASE_CLASS(TwoDFunnelTest, Test)
}
void TwoDFunnelTest::Initialize()
{
// Floor
CreateFloor();
RefConst<Shape> wall = new BoxShape(Vec3(0.1f, 10, 1));
// 2D funnel
mBodyInterface->CreateAndAddBody(BodyCreationSettings(wall, RVec3(-12, 8, -5), Quat::sRotation(Vec3::sAxisZ(), 0.2f * JPH_PI), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
mBodyInterface->CreateAndAddBody(BodyCreationSettings(wall, RVec3(12, 8, -5), Quat::sRotation(Vec3::sAxisZ(), -0.2f * JPH_PI), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
// Shapes falling in 2D funnel
Ref<Shape> shapes[] = {
new SphereShape(0.5f),
new BoxShape(Vec3::sReplicate(0.5f)),
new CapsuleShape(0.2f, 0.3f)
};
BodyCreationSettings bcs(shapes[0], RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
bcs.mAllowedDOFs = EAllowedDOFs::Plane2D;
for (int x = 0; x < 20; ++x)
for (int y = 0; y < 10; ++y)
{
bcs.SetShape(shapes[(x * y) % size(shapes)]);
bcs.mPosition = RVec3(-10.0_r + x, 10.0_r + y, -5.0_r);
mBodyInterface->CreateAndAddBody(bcs, EActivation::Activate);
}
}

View File

@@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2022 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class TwoDFunnelTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, TwoDFunnelTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Shows how to create a 2D simulation.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,31 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/General/WallTest.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(WallTest)
{
JPH_ADD_BASE_CLASS(WallTest, Test)
}
void WallTest::Initialize()
{
// Floor
CreateFloor();
RefConst<Shape> box_shape = new BoxShape(Vec3(1.0f, 1.0f, 1.0f));
// Wall
for (int i = 0; i < 10; ++i)
for (int j = i / 2; j < 50 - (i + 1) / 2; ++j)
{
RVec3 position(-50 + j * 2.0f + (i & 1? 1.0f : 0.0f), 1.0f + i * 3.0f, 0);
mBodyInterface->CreateAndAddBody(BodyCreationSettings(box_shape, position, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
}
}

View File

@@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class WallTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, WallTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Tests a large pile of boxes to check stacking and performance behavior.";
}
// See: Test
virtual void Initialize() override;
};