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,193 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodyBendConstraintTest.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Utils/SoftBodyCreator.h>
#include <Layers.h>
#include <Renderer/DebugRendererImp.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodyBendConstraintTest)
{
JPH_ADD_BASE_CLASS(SoftBodyBendConstraintTest, Test)
}
void SoftBodyBendConstraintTest::Initialize()
{
CreateFloor();
default_random_engine random;
uniform_real_distribution<float> random_float(-0.1f, 0.1f);
auto inv_mass = [](uint, uint inZ) { return inZ < 2? 0.0f : 1.0f; };
auto perturbation = [&random, &random_float](uint, uint inZ) { return Vec3(random_float(random), (inZ & 1)? 0.1f : -0.1f, random_float(random)); };
{
random.seed(1234);
// Cloth without bend constraints
Ref<SoftBodySharedSettings> cloth_settings = SoftBodyCreator::CreateCloth(cNumVerticesX, cNumVerticesZ, cVertexSpacing, inv_mass, perturbation, SoftBodySharedSettings::EBendType::None);
SoftBodyCreationSettings cloth(cloth_settings, RVec3(-5.0f, 5.0f, 0), Quat::sIdentity(), Layers::MOVING);
mBodyInterface->CreateAndAddSoftBody(cloth, EActivation::Activate);
}
{
random.seed(1234);
// Cloth with distance bend constraints
Ref<SoftBodySharedSettings> cloth_settings = SoftBodyCreator::CreateCloth(cNumVerticesX, cNumVerticesZ, cVertexSpacing, inv_mass, perturbation, SoftBodySharedSettings::EBendType::Distance);
SoftBodyCreationSettings cloth(cloth_settings, RVec3(0.0f, 5.0f, 0), Quat::sIdentity(), Layers::MOVING);
mBodyInterface->CreateAndAddSoftBody(cloth, EActivation::Activate);
}
{
random.seed(1234);
// Cloth with dihedral bend constraints
Ref<SoftBodySharedSettings> cloth_settings = SoftBodyCreator::CreateCloth(cNumVerticesX, cNumVerticesZ, cVertexSpacing, inv_mass, perturbation, SoftBodySharedSettings::EBendType::Dihedral);
SoftBodyCreationSettings cloth(cloth_settings, RVec3(5.0f, 5.0f, 0), Quat::sIdentity(), Layers::MOVING);
mBodyInterface->CreateAndAddSoftBody(cloth, EActivation::Activate);
}
{
random.seed(1234);
// Cloth with Cosserat rod constraints
Ref<SoftBodySharedSettings> cloth_settings = SoftBodyCreator::CreateCloth(cNumVerticesX, cNumVerticesZ, cVertexSpacing, inv_mass, perturbation, SoftBodySharedSettings::EBendType::None);
// Get rid of created edges, we're replacing them with rods
cloth_settings->mEdgeConstraints.clear();
// Copy of SoftBodyCreator::CreateCloth: Function to get the vertex index of a point on the cloth
auto vertex_index = [](uint inX, uint inY)
{
return inX + inY * cNumVerticesX;
};
// Create bend twist constraints
constexpr float cCompliance = 1.0e-5f;
auto get_rod = [&constraints = cloth_settings->mRodStretchShearConstraints, vertex_index, cCompliance](uint inX1, uint inZ1, uint inX2, uint inZ2)
{
uint32 v0 = vertex_index(inX1, inZ1);
uint32 v1 = vertex_index(inX2, inZ2);
JPH_ASSERT(v0 < v1);
for (uint i = 0; i < uint(constraints.size()); ++i)
if (constraints[i].mVertex[0] == v0 && constraints[i].mVertex[1] == v1)
return i;
constraints.emplace_back(v0, v1, cCompliance);
return uint(constraints.size() - 1);
};
for (uint z = 1; z < cNumVerticesZ - 1; ++z)
for (uint x = 0; x < cNumVerticesX - 1; ++x)
{
if (z > 1 && x < cNumVerticesX - 2)
cloth_settings->mRodBendTwistConstraints.emplace_back(get_rod(x, z, x + 1, z), get_rod(x + 1, z, x + 2, z), cCompliance);
if (z < cNumVerticesZ - 2)
cloth_settings->mRodBendTwistConstraints.emplace_back(get_rod(x, z, x, z + 1), get_rod(x, z + 1, x, z + 2), cCompliance);
if (x < cNumVerticesX - 2 && z < cNumVerticesZ - 2)
{
cloth_settings->mRodBendTwistConstraints.emplace_back(get_rod(x, z, x + 1, z + 1), get_rod(x + 1, z + 1, x + 2, z + 2), cCompliance);
cloth_settings->mRodBendTwistConstraints.emplace_back(get_rod(x + 2, z, x + 1, z + 1), get_rod(x + 1, z + 1, x, z + 2), cCompliance);
}
}
cloth_settings->CalculateRodProperties();
// Optimize the settings
cloth_settings->Optimize();
SoftBodyCreationSettings cloth(cloth_settings, RVec3(10.0f, 5.0f, 0), Quat::sIdentity(), Layers::MOVING);
mBodyInterface->CreateAndAddSoftBody(cloth, EActivation::Activate);
}
{
// Create sphere
SoftBodyCreationSettings sphere(SoftBodyCreator::CreateSphere(1.0f, 10, 20, SoftBodySharedSettings::EBendType::None), RVec3(-5.0f, 5.0f, 10.0f), Quat::sIdentity(), Layers::MOVING);
mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
}
{
// Create sphere with distance bend constraints
SoftBodyCreationSettings sphere(SoftBodyCreator::CreateSphere(1.0f, 10, 20, SoftBodySharedSettings::EBendType::Distance), RVec3(0.0f, 5.0f, 10.0f), Quat::sIdentity(), Layers::MOVING);
mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
}
{
// Create sphere with dihedral bend constraints
SoftBodyCreationSettings sphere(SoftBodyCreator::CreateSphere(1.0f, 10, 20, SoftBodySharedSettings::EBendType::Dihedral), RVec3(5.0f, 5.0f, 10.0f), Quat::sIdentity(), Layers::MOVING);
mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
}
{
// Create sphere with Cosserat rod constraints
uint cNumTheta = 10;
uint cNumPhi = 20;
Ref<SoftBodySharedSettings> sphere_settings = SoftBodyCreator::CreateSphere(1.0f, cNumTheta, cNumPhi, SoftBodySharedSettings::EBendType::None);
// Get rid of created edges, we're replacing them with rods
sphere_settings->mEdgeConstraints.clear();
// Copy of SoftBodyCreator::CreateSphere: Function to get the vertex index of a point on the sphere
auto vertex_index = [cNumTheta, cNumPhi](uint inTheta, uint inPhi) -> uint
{
if (inTheta == 0)
return 0;
else if (inTheta == cNumTheta - 1)
return 1;
else
return 2 + (inTheta - 1) * cNumPhi + inPhi % cNumPhi;
};
// Create bend twist constraints
constexpr float cCompliance = 1.0e-4f;
auto get_rod = [&constraints = sphere_settings->mRodStretchShearConstraints, vertex_index, cCompliance](uint inTheta1, uint inPhi1, uint inTheta2, uint inPhi2)
{
uint32 v0 = vertex_index(inTheta1, inPhi1);
uint32 v1 = vertex_index(inTheta2, inPhi2);
JPH_ASSERT(v0 != v1);
for (uint i = 0; i < uint(constraints.size()); ++i)
if ((constraints[i].mVertex[0] == v0 && constraints[i].mVertex[1] == v1)
|| (constraints[i].mVertex[0] == v1 && constraints[i].mVertex[1] == v0))
return i;
constraints.emplace_back(v0, v1, cCompliance);
return uint(constraints.size() - 1);
};
// Rings along the side
for (uint phi = 0; phi < cNumPhi; ++phi)
for (uint theta = 0; theta < cNumTheta - 1; ++theta)
{
if (theta < cNumTheta - 2)
sphere_settings->mRodBendTwistConstraints.emplace_back(get_rod(theta, phi, theta + 1, phi), get_rod(theta + 1, phi, theta + 2, phi), cCompliance);
if (theta > 0 && phi < cNumPhi - 1)
sphere_settings->mRodBendTwistConstraints.emplace_back(get_rod(theta, phi, theta, phi + 1), get_rod(theta, phi + 1, theta, (phi + 2) % cNumPhi), cCompliance);
}
// Close the caps
for (uint phi1 = 0, phi2 = cNumPhi / 2; phi1 < cNumPhi / 2; ++phi1, phi2 = (phi2 + 1) % cNumPhi)
{
sphere_settings->mRodBendTwistConstraints.emplace_back(get_rod(0, phi1, 1, phi1), get_rod(0, phi2, 1, phi2), cCompliance);
sphere_settings->mRodBendTwistConstraints.emplace_back(get_rod(cNumTheta - 2, phi1, cNumTheta - 1, phi1), get_rod(cNumTheta - 2, phi2, cNumTheta - 1, phi2), cCompliance);
}
sphere_settings->CalculateRodProperties();
// Optimize the settings
sphere_settings->Optimize();
SoftBodyCreationSettings sphere(sphere_settings, RVec3(10.0f, 5.0f, 10.0f), Quat::sIdentity(), Layers::MOVING);
mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
}
}
void SoftBodyBendConstraintTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
{
mDebugRenderer->DrawText3D(RVec3(-5.0f, 7.5f, 0), "No bend constraints", Color::sWhite);
mDebugRenderer->DrawText3D(RVec3(0.0f, 7.5f, 0), "Distance bend constraints", Color::sWhite);
mDebugRenderer->DrawText3D(RVec3(5.0f, 7.5f, 0), "Dihedral angle bend constraints", Color::sWhite);
mDebugRenderer->DrawText3D(RVec3(10.0f, 7.5f, 0), "Cosserat rod constraints", Color::sWhite);
}

View File

@@ -0,0 +1,29 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class SoftBodyBendConstraintTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SoftBodyBendConstraintTest)
// Description of the test
virtual const char * GetDescription() const override
{
return "Shows the effect of bend constraint type in a soft body.";
}
// See: Test
virtual void Initialize() override;
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
private:
// Size and spacing of the cloth
static constexpr int cNumVerticesX = 10;
static constexpr int cNumVerticesZ = 10;
static constexpr float cVertexSpacing = 0.5f;
};

View File

@@ -0,0 +1,158 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodyContactListenerTest.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Jolt/Physics/SoftBody/SoftBodyManifold.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Utils/SoftBodyCreator.h>
#include <Renderer/DebugRendererImp.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodyContactListenerTest)
{
JPH_ADD_BASE_CLASS(SoftBodyContactListenerTest, Test)
}
void SoftBodyContactListenerTest::Initialize()
{
// Install contact listener for soft bodies
mPhysicsSystem->SetSoftBodyContactListener(this);
// Floor
CreateFloor();
// Start the 1st cycle
StartCycle();
}
void SoftBodyContactListenerTest::UpdateLabel()
{
// Draw current state
const char *cycle_names[] = { "Accept contact", "Sphere 10x mass", "Cloth 10x mass", "Sphere infinite mass", "Cloth infinite mass", "Sensor contact", "Reject contact", "Kinematic Sphere", "Kinematic Sphere, cloth infinite mass", "Kinematic sphere, sensor contact", "Kinematic Sphere, reject contact" };
SetBodyLabel(mOtherBodyID, cycle_names[mCycle]);
}
void SoftBodyContactListenerTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
{
mTime += inParams.mDeltaTime;
if (mTime > 2.5f)
{
// Next cycle
mCycle = (mCycle + 1) % 10;
mTime = 0.0f;
// Remove the old scene
mBodyInterface->RemoveBody(mOtherBodyID);
mBodyInterface->DestroyBody(mOtherBodyID);
mBodyInterface->RemoveBody(mSoftBodyID);
mBodyInterface->DestroyBody(mSoftBodyID);
// Start the new
StartCycle();
}
UpdateLabel();
}
void SoftBodyContactListenerTest::StartCycle()
{
// Create the cloth
Ref<SoftBodySharedSettings> cloth_settings = SoftBodyCreator::CreateClothWithFixatedCorners(15, 15, 0.75f);
// Create cloth that's fixated at the corners
SoftBodyCreationSettings cloth(cloth_settings, RVec3(0, 5, 0), Quat::sRotation(Vec3::sAxisY(), 0.25f * JPH_PI), Layers::MOVING);
cloth.mUpdatePosition = false; // Don't update the position of the cloth as it is fixed to the world
cloth.mMakeRotationIdentity = false; // Test explicitly checks if soft bodies with a rotation collide with shapes properly
mSoftBodyID = mBodyInterface->CreateAndAddSoftBody(cloth, EActivation::Activate);
// If we want a kinematic sphere
bool kinematic = mCycle > 6;
// Create sphere
BodyCreationSettings bcs(new SphereShape(1.0f), RVec3(0, 7, 0), Quat::sIdentity(), kinematic? EMotionType::Kinematic : EMotionType::Dynamic, Layers::MOVING);
bcs.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
bcs.mMassPropertiesOverride.mMass = 100.0f;
if (kinematic)
bcs.mLinearVelocity = Vec3(0, -2.5f, 0);
mOtherBodyID = mBodyInterface->CreateAndAddBody(bcs, EActivation::Activate);
UpdateLabel();
}
SoftBodyValidateResult SoftBodyContactListenerTest::OnSoftBodyContactValidate(const Body &inSoftBody, const Body &inOtherBody, SoftBodyContactSettings &ioSettings)
{
switch (mCycle)
{
case 0:
// Normal
return SoftBodyValidateResult::AcceptContact;
case 1:
// Makes the sphere 10x as heavy
ioSettings.mInvMassScale2 = 0.1f;
ioSettings.mInvInertiaScale2 = 0.1f;
return SoftBodyValidateResult::AcceptContact;
case 2:
// Makes the cloth 10x as heavy
ioSettings.mInvMassScale1 = 0.1f;
return SoftBodyValidateResult::AcceptContact;
case 3:
// Makes the sphere have infinite mass
ioSettings.mInvMassScale2 = 0.0f;
ioSettings.mInvInertiaScale2 = 0.0f;
return SoftBodyValidateResult::AcceptContact;
case 4:
// Makes the cloth have infinite mass
ioSettings.mInvMassScale1 = 0.0f;
return SoftBodyValidateResult::AcceptContact;
case 5:
// Sensor contact
ioSettings.mIsSensor = true;
return SoftBodyValidateResult::AcceptContact;
case 6:
// No contacts
return SoftBodyValidateResult::RejectContact;
case 7:
// Kinematic sphere
return SoftBodyValidateResult::AcceptContact;
case 8:
// Kinematic sphere, cloth infinite mass
ioSettings.mInvMassScale1 = 0.0f;
return SoftBodyValidateResult::AcceptContact;
case 9:
// Kinematic sphere, sensor contact
ioSettings.mIsSensor = true;
return SoftBodyValidateResult::AcceptContact;
default:
// No contacts
return SoftBodyValidateResult::RejectContact;
}
}
void SoftBodyContactListenerTest::OnSoftBodyContactAdded(const Body &inSoftBody, const SoftBodyManifold &inManifold)
{
// Draw contacts
RMat44 com = inSoftBody.GetCenterOfMassTransform();
for (const SoftBodyVertex &vertex : inManifold.GetVertices())
if (inManifold.HasContact(vertex))
{
RVec3 position = com * inManifold.GetLocalContactPoint(vertex);
Vec3 normal = inManifold.GetContactNormal(vertex);
mDebugRenderer->DrawMarker(position, Color::sRed, 0.1f);
mDebugRenderer->DrawArrow(position, position + normal, Color::sGreen, 0.1f);
}
}

View File

@@ -0,0 +1,42 @@
// 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/SoftBody/SoftBodyContactListener.h>
class SoftBodyContactListenerTest : public Test, public SoftBodyContactListener
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SoftBodyContactListenerTest)
// Description of the test
virtual const char * GetDescription() const override
{
return "Shows how to use contact listeners for soft bodies to affect the simulation.";
}
// See: Test
virtual void Initialize() override;
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
virtual void GetInitialCamera(CameraState &ioState) const override { ioState.mPos = RVec3(15, 10, 15); }
// Test is not deterministic as it creates/removes bodies in a way that's not compatible with the determinism check
virtual bool IsDeterministic() const override { return false; }
// See: SoftBodyContactListener
virtual SoftBodyValidateResult OnSoftBodyContactValidate(const Body &inSoftBody, const Body &inOtherBody, SoftBodyContactSettings &ioSettings) override;
virtual void OnSoftBodyContactAdded(const Body &inSoftBody, const SoftBodyManifold &inManifold) override;
private:
void UpdateLabel();
void StartCycle();
float mTime = 0.0f;
int mCycle = 0;
BodyID mSoftBodyID;
BodyID mOtherBodyID;
};

View File

@@ -0,0 +1,186 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodyCosseratRodConstraintTest.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Jolt/Physics/SoftBody/SoftBodyMotionProperties.h>
#include <Utils/SoftBodyCreator.h>
#include <Layers.h>
#include <Renderer/DebugRendererImp.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodyCosseratRodConstraintTest)
{
JPH_ADD_BASE_CLASS(SoftBodyCosseratRodConstraintTest, Test)
}
void SoftBodyCosseratRodConstraintTest::Initialize()
{
CreateFloor();
// Create a hanging helix
{
constexpr float cRadius = 0.5f;
constexpr int cNumVertices = 128;
constexpr float cHeight = 5.0f;
constexpr float cNumCycles = 10;
Ref<SoftBodySharedSettings> helix_settings = new SoftBodySharedSettings;
for (int i = 0; i < cNumVertices; ++i)
{
float fraction = float(i) / (cNumVertices - 1);
SoftBodySharedSettings::Vertex v;
float alpha = cNumCycles * 2.0f * JPH_PI * fraction;
v.mPosition = Float3(cRadius * Sin(alpha), 0.5f * (1.0f - fraction * cHeight), cRadius * Cos(alpha));
v.mInvMass = i == 0? 0.0f : 1.0e-2f;
helix_settings->mVertices.push_back(v);
if (i > 0)
helix_settings->mRodStretchShearConstraints.push_back(SoftBodySharedSettings::RodStretchShear(i - 1, i));
if (i > 1)
helix_settings->mRodBendTwistConstraints.push_back(SoftBodySharedSettings::RodBendTwist(i - 2, i - 1));
}
helix_settings->CalculateRodProperties();
helix_settings->Optimize();
SoftBodyCreationSettings helix(helix_settings, RVec3(0, 10, 0), Quat::sIdentity(), Layers::MOVING);
mSoftBodies.push_back(mBodyInterface->CreateAndAddSoftBody(helix, EActivation::Activate));
}
// Create a tree with a static root
{
// Root particle
Ref<SoftBodySharedSettings> tree_settings = new SoftBodySharedSettings;
SoftBodySharedSettings::Vertex v;
v.mPosition = Float3(0, 0, 0);
v.mInvMass = 0.0f;
tree_settings->mVertices.push_back(v);
// Create branches
struct Branch
{
uint32 mPreviousVertex;
uint32 mPreviousRod;
Vec3 mDirection;
uint32 mDepth;
};
Array<Branch> branches;
branches.push_back({ 0, uint32(-1), Vec3::sAxisY(), 0 });
while (!branches.empty())
{
// Take the next branch
Branch branch = branches.front();
branches.erase(branches.begin());
// Create vertex
SoftBodySharedSettings::Vertex &previous_vertex = tree_settings->mVertices[branch.mPreviousVertex];
(Vec3(previous_vertex.mPosition) + branch.mDirection).StoreFloat3(&v.mPosition);
v.mInvMass = branch.mDepth > 0? 2.0f * previous_vertex.mInvMass : 1.0e-3f;
uint32 new_vertex = uint32(tree_settings->mVertices.size());
tree_settings->mVertices.push_back(v);
// Create rod
uint32 new_rod = uint32(tree_settings->mRodStretchShearConstraints.size());
tree_settings->mRodStretchShearConstraints.push_back(SoftBodySharedSettings::RodStretchShear(branch.mPreviousVertex, new_vertex));
if (branch.mPreviousRod != uint32(-1))
tree_settings->mRodBendTwistConstraints.push_back(SoftBodySharedSettings::RodBendTwist(branch.mPreviousRod, new_rod));
// Create sub branches
if (branch.mDepth < 10)
for (uint32 i = 0; i < 2; ++i)
{
// Determine new child direction
float angle = DegreesToRadians(-15.0f + i * 30.0f);
Vec3 new_direction = Quat::sRotation(branch.mDepth & 1? Vec3::sAxisZ() : Vec3::sAxisX(), angle) * branch.mDirection;
// Create new branch
branches.push_back({ new_vertex, new_rod, new_direction, branch.mDepth + 1 });
}
}
tree_settings->CalculateRodProperties();
tree_settings->Optimize();
SoftBodyCreationSettings tree(tree_settings, RVec3(10, 0, 0), Quat::sIdentity(), Layers::MOVING);
mSoftBodies.push_back(mBodyInterface->CreateAndAddSoftBody(tree, EActivation::Activate));
}
// Create a weed like structure
{
// Root particle
Ref<SoftBodySharedSettings> weed_settings = new SoftBodySharedSettings;
constexpr int cNumVertices = 64;
constexpr int cNumStrands = 50;
default_random_engine random;
uniform_real_distribution<float> radius_distribution(0, 1.0f);
uniform_real_distribution<float> phase_distribution(0, 2.0f * JPH_PI);
for (int strand = 0; strand < cNumStrands; ++strand)
{
// Place at a random location
float radius = radius_distribution(random);
float theta = phase_distribution(random);
Vec3 root_pos = Vec3(radius * Sin(theta), 0, radius * Cos(theta));
// Randomize the phase of the wave
float phase1 = phase_distribution(random);
float phase2 = phase_distribution(random);
uint32 first_vertex = uint32(weed_settings->mVertices.size());
for (int i = 0; i < cNumVertices; ++i)
{
// Generate a wavy pattern
float amplitude = 0.1f * Sin(phase1 + i * 2.0f * JPH_PI / 8);
Vec3 pos = root_pos + Vec3(Sin(phase2) * amplitude, 0.1f * i, Cos(phase2) * amplitude);
SoftBodySharedSettings::Vertex v;
pos.StoreFloat3(&v.mPosition);
v.mInvMass = i == 0? 0.0f : 0.1f;
weed_settings->mVertices.push_back(v);
}
uint32 first_rod = uint32(weed_settings->mRodStretchShearConstraints.size());
for (int i = 0; i < cNumVertices - 1; ++i)
weed_settings->mRodStretchShearConstraints.push_back(SoftBodySharedSettings::RodStretchShear(first_vertex + i, first_vertex + i + 1));
for (int i = 0; i < cNumVertices - 2; ++i)
weed_settings->mRodBendTwistConstraints.push_back(SoftBodySharedSettings::RodBendTwist(first_rod + i, first_rod + i + 1));
}
weed_settings->CalculateRodProperties();
weed_settings->Optimize();
SoftBodyCreationSettings weed(weed_settings, RVec3(20, 0, 0), Quat::sIdentity(), Layers::MOVING);
weed.mGravityFactor = 0.8f;
mSoftBodies.push_back(mBodyInterface->CreateAndAddSoftBody(weed, EActivation::Activate));
}
}
void SoftBodyCosseratRodConstraintTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
{
// Draw the soft body rods
for (BodyID id : mSoftBodies)
{
BodyLockRead lock(mPhysicsSystem->GetBodyLockInterface(), id);
if (lock.Succeeded())
{
const Body &body = lock.GetBody();
const SoftBodyMotionProperties *mp = static_cast<const SoftBodyMotionProperties *>(body.GetMotionProperties());
RMat44 com = body.GetCenterOfMassTransform();
for (const SoftBodySharedSettings::RodStretchShear &r : mp->GetSettings()->mRodStretchShearConstraints)
{
RVec3 x0 = com * mp->GetVertex(r.mVertex[0]).mPosition;
RVec3 x1 = com * mp->GetVertex(r.mVertex[1]).mPosition;
mDebugRenderer->DrawLine(x0, x1, Color::sWhite);
}
}
}
}

View File

@@ -0,0 +1,26 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class SoftBodyCosseratRodConstraintTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SoftBodyCosseratRodConstraintTest)
// Description of the test
virtual const char * GetDescription() const override
{
return "Shows the effect of Cosserat rod constraints in a soft body that control bend, twist and shear between particles.";
}
// See: Test
virtual void Initialize() override;
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
private:
BodyIDVector mSoftBodies;
};

View File

@@ -0,0 +1,49 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodyCustomUpdateTest.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Jolt/Physics/SoftBody/SoftBodyMotionProperties.h>
#include <Utils/SoftBodyCreator.h>
#include <Layers.h>
#include <Renderer/DebugRendererImp.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodyCustomUpdateTest)
{
JPH_ADD_BASE_CLASS(SoftBodyCustomUpdateTest, Test)
}
void SoftBodyCustomUpdateTest::Initialize()
{
// Floor
CreateFloor();
// Create a body but do not add it to the physics system (we're updating it ourselves)
SoftBodyCreationSettings sphere(SoftBodyCreator::CreateSphere(), RVec3(0, 5, 0), Quat::sIdentity(), Layers::MOVING);
sphere.mPressure = 2000.0f;
mBody = mBodyInterface->CreateSoftBody(sphere);
}
void SoftBodyCustomUpdateTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
{
// Note that passing a variable delta time results in differences in behavior, usually you want to have a fixed time step.
// For this demo we'll just clamp the delta time to 1/60th of a second and allow behavioral changes due to frame rate fluctuations.
float dt = min(inParams.mDeltaTime, 1.0f / 60.0f);
// Call the update now
SoftBodyMotionProperties *mp = static_cast<SoftBodyMotionProperties *>(mBody->GetMotionProperties());
mp->CustomUpdate(dt, *mBody, *mPhysicsSystem);
#ifdef JPH_DEBUG_RENDERER
// Draw it as well since it's not added to the world
mBody->GetShape()->Draw(mDebugRenderer, mBody->GetCenterOfMassTransform(), Vec3::sOne(), Color::sWhite, false, false);
#else
// Draw the vertices
RMat44 com = mBody->GetCenterOfMassTransform();
for (const SoftBodyVertex &v : mp->GetVertices())
mDebugRenderer->DrawMarker(com * v.mPosition, Color::sRed, 0.1f);
#endif // JPH_DEBUG_RENDERER
}

View File

@@ -0,0 +1,26 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class SoftBodyCustomUpdateTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SoftBodyCustomUpdateTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Shows how you can update a soft body outside of the main physics simulation step.";
}
// See: Test
virtual void Initialize() override;
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
private:
Body * mBody;
};

View File

@@ -0,0 +1,59 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodyForceTest.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Utils/SoftBodyCreator.h>
#include <Renderer/DebugRendererImp.h>
#include <External/Perlin.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodyForceTest)
{
JPH_ADD_BASE_CLASS(SoftBodyForceTest, Test)
}
void SoftBodyForceTest::Initialize()
{
CreateFloor();
static constexpr uint cGridSize = 30;
// Create hanging cloth
auto inv_mass = [](uint inX, uint inZ) {
return (inX == 0 && inZ == 0)
|| (inX == cGridSize - 1 && inZ == 0)? 0.0f : 1.0f;
};
SoftBodyCreationSettings cloth(SoftBodyCreator::CreateCloth(cGridSize, cGridSize, 0.75f, inv_mass), RVec3(0, 15.0f, 0), Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI), Layers::MOVING);
mBodyID = mBodyInterface->CreateAndAddSoftBody(cloth, EActivation::Activate);
}
void SoftBodyForceTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
{
mTime += inParams.mDeltaTime;
// Apply a fluctuating force
constexpr float cMaxForce = 10000.0f;
constexpr float cMaxAngle = DegreesToRadians(90.0f);
Vec3 force(0, 0, 0.5f * cMaxForce * (1.0f + PerlinNoise3(0, 0, mTime / 2.0f, 256, 256, 256)));
force = Mat44::sRotationY(cMaxAngle * PerlinNoise3(mTime / 10.0f, 0, 0, 256, 256, 256)) * force;
mBodyInterface->AddForce(mBodyID, force);
// Draw the force
RVec3 offset(0, 10, 0);
DebugRenderer::sInstance->DrawArrow(offset, offset + 10.0f * force.Normalized(), Color::sGreen, 0.1f);
}
void SoftBodyForceTest::SaveState(StateRecorder &inStream) const
{
inStream.Write(mTime);
}
void SoftBodyForceTest::RestoreState(StateRecorder &inStream)
{
inStream.Read(mTime);
}

View File

@@ -0,0 +1,29 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class SoftBodyForceTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SoftBodyForceTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Shows how to apply a global force to a soft body.";
}
// 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:
float mTime = 0.0f;
BodyID mBodyID;
};

View File

@@ -0,0 +1,50 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodyFrictionTest.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Utils/SoftBodyCreator.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodyFrictionTest)
{
JPH_ADD_BASE_CLASS(SoftBodyFrictionTest, Test)
}
void SoftBodyFrictionTest::Initialize()
{
// Floor
Body &floor = CreateFloor();
floor.SetFriction(1.0f);
// Bodies with increasing friction
Ref<SoftBodySharedSettings> sphere_settings = SoftBodyCreator::CreateSphere();
for (SoftBodySharedSettings::Vertex &v : sphere_settings->mVertices)
v.mVelocity = Float3(0, 0, 10);
SoftBodyCreationSettings sphere(sphere_settings, RVec3::sZero(), Quat::sIdentity(), Layers::MOVING);
sphere.mPressure = 2000.0f;
for (int i = 0; i <= 10; ++i)
{
sphere.mPosition = RVec3(-50.0f + i * 10.0f, 1.0f, 0);
sphere.mFriction = 0.1f * i;
BodyID id = mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
SetBodyLabel(id, StringFormat("Friction: %.1f", double(sphere.mFriction)));
}
Ref<SoftBodySharedSettings> cube_settings = SoftBodySharedSettings::sCreateCube(5, 0.5f);
for (SoftBodySharedSettings::Vertex &v : cube_settings->mVertices)
v.mVelocity = Float3(0, 0, 10);
SoftBodyCreationSettings cube(cube_settings, RVec3::sZero(), Quat::sIdentity(), Layers::MOVING);
for (int i = 0; i <= 10; ++i)
{
cube.mPosition = RVec3(-50.0f + i * 10.0f, 1.0f, -5.0f);
cube.mFriction = 0.1f * i;
BodyID id = mBodyInterface->CreateAndAddSoftBody(cube, EActivation::Activate);
SetBodyLabel(id, StringFormat("Friction: %.1f", double(cube.mFriction)));
}
}

View File

@@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class SoftBodyFrictionTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SoftBodyFrictionTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Tests soft bodies with various values for friction. Note that this has very little effect.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,43 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodyGravityFactorTest.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Utils/SoftBodyCreator.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodyGravityFactorTest)
{
JPH_ADD_BASE_CLASS(SoftBodyGravityFactorTest, Test)
}
void SoftBodyGravityFactorTest::Initialize()
{
// Floor
CreateFloor();
// Bodies with increasing gravity factor
SoftBodyCreationSettings sphere(SoftBodyCreator::CreateSphere(), RVec3::sZero(), Quat::sIdentity(), Layers::MOVING);
sphere.mPressure = 2000.0f;
for (int i = 0; i <= 10; ++i)
{
sphere.mPosition = RVec3(-50.0f + i * 10.0f, 10.0f, 0);
sphere.mGravityFactor = 0.1f * i;
BodyID id = mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
SetBodyLabel(id, StringFormat("GravityFactor: %.1f", double(sphere.mGravityFactor)));
}
SoftBodyCreationSettings cube(SoftBodySharedSettings::sCreateCube(5, 0.5f), RVec3::sZero(), Quat::sIdentity(), Layers::MOVING);
for (int i = 0; i <= 10; ++i)
{
cube.mPosition = RVec3(-50.0f + i * 10.0f, 10.0f, -5.0f);
cube.mGravityFactor = 0.1f * i;
BodyID id = mBodyInterface->CreateAndAddSoftBody(cube, EActivation::Activate);
SetBodyLabel(id, StringFormat("GravityFactor: %.1f", double(cube.mGravityFactor)));
}
}

View File

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

View File

@@ -0,0 +1,46 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodyKinematicTest.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Jolt/Physics/SoftBody/SoftBodyMotionProperties.h>
#include <Utils/SoftBodyCreator.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodyKinematicTest)
{
JPH_ADD_BASE_CLASS(SoftBodyKinematicTest, Test)
}
void SoftBodyKinematicTest::Initialize()
{
// Floor
CreateFloor();
// A sphere
Ref<SoftBodySharedSettings> sphere_settings = SoftBodyCreator::CreateSphere();
sphere_settings->mVertices[0].mInvMass = 0.0f;
sphere_settings->mVertices[0].mVelocity = Float3(0, 0, 5);
SoftBodyCreationSettings sphere(sphere_settings, RVec3(0, 5, 0), Quat::sIdentity(), Layers::MOVING);
sphere.mPressure = 2000.0f;
mSphereID = mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
}
void SoftBodyKinematicTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
{
// Update the velocity of the first vertex
BodyLockWrite body_lock(mPhysicsSystem->GetBodyLockInterface(), mSphereID);
if (body_lock.Succeeded())
{
Body &body = body_lock.GetBody();
SoftBodyMotionProperties *mp = static_cast<SoftBodyMotionProperties *>(body.GetMotionProperties());
RVec3 com = body.GetCenterOfMassPosition();
if (com.GetZ() >= 10.0f)
mp->GetVertex(0).mVelocity = Vec3(0, 0, -5);
else if (com.GetZ() <= -10.0f)
mp->GetVertex(0).mVelocity = Vec3(0, 0, 5);
}
}

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 SoftBodyKinematicTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SoftBodyKinematicTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Shows how to make a soft body vertex kinematic and control it.";
}
// See: Test
virtual void Initialize() override;
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
private:
BodyID mSphereID;
};

View File

@@ -0,0 +1,42 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodyLRAConstraintTest.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Utils/SoftBodyCreator.h>
#include <Layers.h>
#include <Renderer/DebugRendererImp.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodyLRAConstraintTest)
{
JPH_ADD_BASE_CLASS(SoftBodyLRAConstraintTest, Test)
}
void SoftBodyLRAConstraintTest::Initialize()
{
CreateFloor();
for (int i = 0; i < 2; ++i)
{
auto inv_mass = [](uint, uint inZ) { return inZ == 0? 0.0f : 1.0f; };
auto perturbation = [](uint, uint) { return Vec3::sZero(); };
SoftBodySharedSettings::VertexAttributes va;
va.mShearCompliance = va.mCompliance = 1.0e-3f; // Soften the edges a bit so that the effect of the LRA constraints is more visible
va.mLRAType = i == 0? SoftBodySharedSettings::ELRAType::None : SoftBodySharedSettings::ELRAType::EuclideanDistance;
Ref<SoftBodySharedSettings> cloth_settings = SoftBodyCreator::CreateCloth(cNumVerticesX, cNumVerticesZ, cVertexSpacing, inv_mass, perturbation, SoftBodySharedSettings::EBendType::None, va);
SoftBodyCreationSettings cloth(cloth_settings, RVec3(-10.0f + i * 20.0f, 25.0f, 0), Quat::sIdentity(), Layers::MOVING);
mBodyInterface->CreateAndAddSoftBody(cloth, EActivation::Activate);
}
}
void SoftBodyLRAConstraintTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
{
mDebugRenderer->DrawText3D(RVec3(-10, 26, -0.5f * cNumVerticesZ * cVertexSpacing), "Without LRA constraints", Color::sWhite);
mDebugRenderer->DrawText3D(RVec3(10, 26, -0.5f * cNumVerticesZ * cVertexSpacing), "With LRA constraints", Color::sWhite);
}

View File

@@ -0,0 +1,29 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class SoftBodyLRAConstraintTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SoftBodyLRAConstraintTest)
// Description of the test
virtual const char * GetDescription() const override
{
return "Shows the effect of Long Range Attachment (LRA) constraints in a soft body which can help reduce cloth stretching.";
}
// See: Test
virtual void Initialize() override;
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
private:
// Size and spacing of the cloth
static constexpr int cNumVerticesX = 10;
static constexpr int cNumVerticesZ = 50;
static constexpr float cVertexSpacing = 0.5f;
};

View File

@@ -0,0 +1,32 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodyPressureTest.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Utils/SoftBodyCreator.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodyPressureTest)
{
JPH_ADD_BASE_CLASS(SoftBodyPressureTest, Test)
}
void SoftBodyPressureTest::Initialize()
{
// Floor
CreateFloor();
// Bodies with increasing pressure
SoftBodyCreationSettings sphere(SoftBodyCreator::CreateSphere(2.0f), RVec3::sZero(), Quat::sIdentity(), Layers::MOVING);
for (int i = 0; i <= 10; ++i)
{
sphere.mPosition = RVec3(-50.0f + i * 10.0f, 10.0f, 0);
sphere.mPressure = 1000.0f * i;
BodyID id = mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
SetBodyLabel(id, StringFormat("Pressure: %g", double(sphere.mPressure)));
}
}

View File

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

View File

@@ -0,0 +1,44 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodyRestitutionTest.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Utils/SoftBodyCreator.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodyRestitutionTest)
{
JPH_ADD_BASE_CLASS(SoftBodyRestitutionTest, Test)
}
void SoftBodyRestitutionTest::Initialize()
{
// Floor
Body &floor = CreateFloor();
floor.SetRestitution(0.0f);
// Bodies with increasing restitution
SoftBodyCreationSettings sphere(SoftBodyCreator::CreateSphere(), RVec3::sZero(), Quat::sIdentity(), Layers::MOVING);
sphere.mPressure = 2000.0f;
for (int i = 0; i <= 10; ++i)
{
sphere.mPosition = RVec3(-50.0f + i * 10.0f, 10.0f, 0);
sphere.mRestitution = 0.1f * i;
BodyID id = mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
SetBodyLabel(id, StringFormat("Restitution: %.1f", double(sphere.mRestitution)));
}
SoftBodyCreationSettings cube(SoftBodySharedSettings::sCreateCube(5, 0.5f), RVec3::sZero(), Quat::sIdentity(), Layers::MOVING);
for (int i = 0; i <= 10; ++i)
{
cube.mPosition = RVec3(-50.0f + i * 10.0f, 10.0f, -5.0f);
cube.mRestitution = 0.1f * i;
BodyID id = mBodyInterface->CreateAndAddSoftBody(cube, EActivation::Activate);
SetBodyLabel(id, StringFormat("Restitution: %.1f", double(cube.mRestitution)));
}
}

View File

@@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class SoftBodyRestitutionTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SoftBodyRestitutionTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Tests soft bodies with various values for restitution. Note that this has very little effect.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,69 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodySensorTest.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Collision/Shape/TaperedCylinderShape.h>
#include <Jolt/Physics/SoftBody/SoftBodyManifold.h>
#include <Utils/SoftBodyCreator.h>
#include <Renderer/DebugRendererImp.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodySensorTest)
{
JPH_ADD_BASE_CLASS(SoftBodySensorTest, Test)
}
void SoftBodySensorTest::Initialize()
{
// Install contact listener for soft bodies
mPhysicsSystem->SetSoftBodyContactListener(this);
// Floor
CreateFloor();
// Create cloth that's fixated at the corners
SoftBodyCreationSettings cloth(SoftBodyCreator::CreateClothWithFixatedCorners(), RVec3(0, 10.0f, 0), Quat::sIdentity(), Layers::MOVING);
mBodyInterface->CreateAndAddSoftBody(cloth, EActivation::Activate);
// Some sensors to detect the cloth
BodyCreationSettings cylinder_sensor(new TaperedCylinderShapeSettings(4.0f, 1.0f, 2.0f), RVec3(0, 6, 0), Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI), EMotionType::Static, Layers::SENSOR);
cylinder_sensor.mIsSensor = true;
mBodyInterface->CreateAndAddBody(cylinder_sensor, EActivation::DontActivate);
BodyCreationSettings sphere_sensor(new SphereShape(4.0f), RVec3(4, 5, 0), Quat::sIdentity(), EMotionType::Static, Layers::SENSOR);
sphere_sensor.mIsSensor = true;
mBodyInterface->CreateAndAddBody(sphere_sensor, EActivation::DontActivate);
// Sphere that falls on the cloth to check that we don't ignore this collision
BodyCreationSettings bcs(new SphereShape(1.0f), RVec3(0, 15, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
bcs.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
bcs.mMassPropertiesOverride.mMass = 500.0f;
mBodyInterface->CreateAndAddBody(bcs, EActivation::Activate);
}
void SoftBodySensorTest::OnSoftBodyContactAdded(const Body &inSoftBody, const SoftBodyManifold &inManifold)
{
// Draw the vertices that are in contact
RMat44 com = inSoftBody.GetCenterOfMassTransform();
for (const SoftBodyVertex &v : inManifold.GetVertices())
if (inManifold.HasContact(v))
DebugRenderer::sInstance->DrawMarker(com * v.mPosition, Color::sGreen, 0.1f);
// Draw the sensors that are in contact with the soft body
for (uint i = 0; i < inManifold.GetNumSensorContacts(); ++i)
{
BodyID sensor_id = inManifold.GetSensorContactBodyID(i);
BodyLockRead lock(mPhysicsSystem->GetBodyLockInterfaceNoLock(), sensor_id); // Can't lock in a callback
if (lock.SucceededAndIsInBroadPhase())
{
AABox bounds = lock.GetBody().GetWorldSpaceBounds();
DebugRenderer::sInstance->DrawWireBox(bounds, Color::sGreen);
}
}
}

View File

@@ -0,0 +1,26 @@
// 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/SoftBody/SoftBodyContactListener.h>
class SoftBodySensorTest : public Test, public SoftBodyContactListener
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SoftBodySensorTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Shows interaction between a soft body and a sensor.";
}
// See: Test
virtual void Initialize() override;
// See: SoftBodyContactListener
virtual void OnSoftBodyContactAdded(const Body &inSoftBody, const SoftBodyManifold &inManifold) override;
};

View File

@@ -0,0 +1,87 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodyShapesTest.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.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/ConvexHullShape.h>
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
#include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
#include <Utils/SoftBodyCreator.h>
#include <Renderer/DebugRendererImp.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodyShapesTest)
{
JPH_ADD_BASE_CLASS(SoftBodyShapesTest, Test)
}
void SoftBodyShapesTest::Initialize()
{
const Quat cCubeOrientation = Quat::sRotation(Vec3::sReplicate(sqrt(1.0f / 3.0f)), DegreesToRadians(45.0f));
// Floor
CreateMeshTerrain();
// Create cloth that's fixated at the corners
SoftBodyCreationSettings cloth(SoftBodyCreator::CreateClothWithFixatedCorners(), RVec3(0, 10.0f, 0), Quat::sRotation(Vec3::sAxisY(), 0.25f * JPH_PI), Layers::MOVING);
cloth.mUpdatePosition = false; // Don't update the position of the cloth as it is fixed to the world
cloth.mMakeRotationIdentity = false; // Test explicitly checks if soft bodies with a rotation collide with shapes properly
mBodyInterface->CreateAndAddSoftBody(cloth, EActivation::Activate);
// Create cube
SoftBodyCreationSettings cube(SoftBodySharedSettings::sCreateCube(5, 0.5f), RVec3(20.0f, 10.0f, 0.0f), cCubeOrientation, Layers::MOVING);
cube.mRestitution = 0.0f;
mBodyInterface->CreateAndAddSoftBody(cube, EActivation::Activate);
// Create pressurized sphere
SoftBodyCreationSettings sphere(SoftBodyCreator::CreateSphere(), RVec3(15.0f, 10.0f, 15.0f), Quat::sIdentity(), Layers::MOVING);
sphere.mPressure = 2000.0f;
mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
// Sphere below pressurized sphere
RefConst<Shape> sphere_shape = new SphereShape(1.0f);
BodyCreationSettings bcs(sphere_shape, RVec3(15.5f, 7.0f, 15.0f), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
bcs.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
bcs.mMassPropertiesOverride.mMass = 100.0f;
mBodyInterface->CreateAndAddBody(bcs, EActivation::Activate);
// Various shapes above cloth
ConvexHullShapeSettings tetrahedron({ Vec3(-2, -2, -2), Vec3(0, -2, 2), Vec3(2, -2, -2), Vec3(0, 2, 0) });
tetrahedron.SetEmbedded();
StaticCompoundShapeSettings compound_shape;
compound_shape.SetEmbedded();
Quat rotate_x = Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI);
compound_shape.AddShape(Vec3::sZero(), rotate_x, new CapsuleShape(2, 0.5f));
compound_shape.AddShape(Vec3(0, 0, -2), Quat::sIdentity(), new SphereShape(1));
compound_shape.AddShape(Vec3(0, 0, 2), Quat::sIdentity(), new SphereShape(1));
RefConst<Shape> shapes[] = {
sphere_shape,
new BoxShape(Vec3(0.75f, 1.0f, 1.25f)),
new RotatedTranslatedShape(Vec3::sZero(), rotate_x, new CapsuleShape(1, 0.5f)),
new RotatedTranslatedShape(Vec3::sZero(), rotate_x, TaperedCapsuleShapeSettings(1.0f, 1.0f, 0.5f).Create().Get()),
new RotatedTranslatedShape(Vec3::sZero(), rotate_x, new CylinderShape(1, 0.5f)),
new RotatedTranslatedShape(Vec3::sZero(), rotate_x, TaperedCylinderShapeSettings(1, 0.5f, 1.0f).Create().Get()),
tetrahedron.Create().Get(),
compound_shape.Create().Get(),
};
int num_shapes = (int)std::size(shapes);
for (int i = 0; i < num_shapes; ++i)
{
bcs.SetShape(shapes[i % num_shapes]);
bcs.mPosition = RVec3(-float(num_shapes) + 2.0f * i, 15.0f, 0);
mBodyInterface->CreateAndAddBody(bcs, EActivation::Activate);
}
}

View File

@@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class SoftBodyShapesTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SoftBodyShapesTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Shows interaction between various collision shapes and soft bodies.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,174 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodySkinnedConstraintTest.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Jolt/Physics/SoftBody/SoftBodyMotionProperties.h>
#include <Utils/SoftBodyCreator.h>
#include <Layers.h>
#include <Renderer/DebugRendererImp.h>
#include <Application/DebugUI.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodySkinnedConstraintTest)
{
JPH_ADD_BASE_CLASS(SoftBodySkinnedConstraintTest, Test)
}
Array<Mat44> SoftBodySkinnedConstraintTest::GetWorldSpacePose(float inTime) const
{
Array<Mat44> pose;
pose.resize(cNumJoints);
// Create local space pose
pose[0] = Mat44::sTranslation(Vec3(0.0f, cBodyPosY, -0.5f * (cNumVerticesZ - 1) * cVertexSpacing));
for (int i = 1; i < cNumJoints; ++i)
{
float amplitude = 0.25f * min(inTime, 2.0f); // Fade effect in over time
Mat44 rotation = Mat44::sRotationX(amplitude * Sin(0.25f * JPH_PI * i + 2.0f * inTime));
Mat44 translation = Mat44::sTranslation(Vec3(0, 0, (cNumVerticesZ - 1) * cVertexSpacing / (cNumJoints - 1)));
pose[i] = rotation * translation;
}
// Convert to world space
for (int i = 1; i < cNumJoints; ++i)
pose[i] = pose[i - 1] * pose[i];
return pose;
}
void SoftBodySkinnedConstraintTest::SkinVertices(bool inHardSkinAll)
{
RMat44 com = mBody->GetCenterOfMassTransform();
// Make pose relative to the center of mass of the body
Array<Mat44> pose = GetWorldSpacePose(mTime);
Mat44 offset = com.InversedRotationTranslation().ToMat44();
for (Mat44 &m : pose)
m = offset * m;
SoftBodyMotionProperties *mp = static_cast<SoftBodyMotionProperties *>(mBody->GetMotionProperties());
mp->SetEnableSkinConstraints(sEnableSkinConstraints);
mp->SetSkinnedMaxDistanceMultiplier(sMaxDistanceMultiplier);
if (sUpdateSkinning || inHardSkinAll)
mp->SkinVertices(com, pose.data(), cNumJoints, inHardSkinAll, *mTempAllocator);
}
void SoftBodySkinnedConstraintTest::Initialize()
{
CreateFloor();
// Where we'll place the body
RVec3 body_translation(0.0f, cBodyPosY, 0);
// Make first and last row kinematic
auto inv_mass = [](uint, uint inZ) { return inZ == 0 || inZ == cNumVerticesZ - 1? 0.0f : 1.0f; };
Ref<SoftBodySharedSettings> settings = SoftBodyCreator::CreateCloth(cNumVerticesX, cNumVerticesZ, cVertexSpacing, inv_mass);
// Make edges soft
for (SoftBodySharedSettings::Edge &e : settings->mEdgeConstraints)
e.mCompliance = 1.0e-3f;
// Create inverse bind matrices by moving the bind pose to the center of mass space for the body
Array<Mat44> bind_pose = GetWorldSpacePose(0.0f);
Mat44 offset = Mat44::sTranslation(Vec3(-body_translation));
for (Mat44 &m : bind_pose)
m = offset * m;
for (int i = 0; i < cNumJoints; ++i)
settings->mInvBindMatrices.push_back(SoftBodySharedSettings::InvBind(i, bind_pose[i].Inversed()));
// Create skinned vertices
auto get_vertex = [](uint inX, uint inZ) { return inX + inZ * cNumVerticesX; };
for (int z = 0; z < cNumVerticesZ; ++z)
for (int x = 0; x < cNumVerticesX; ++x)
{
uint vertex_idx = get_vertex(x, z);
SoftBodySharedSettings::Skinned skinned(vertex_idx, settings->mVertices[vertex_idx].mInvMass > 0.0f? 2.0f : 0.0f, 0.1f, 40.0f);
// Find closest joints
int closest_joint = -1, prev_closest_joint = -1;
float closest_joint_dist = FLT_MAX, prev_closest_joint_dist = FLT_MAX;
for (int i = 0; i < cNumJoints; ++i)
{
float dist = abs(settings->mVertices[vertex_idx].mPosition.z - bind_pose[i].GetTranslation().GetZ());
if (dist < closest_joint_dist)
{
prev_closest_joint = closest_joint;
prev_closest_joint_dist = closest_joint_dist;
closest_joint = i;
closest_joint_dist = dist;
}
else if (dist < prev_closest_joint_dist)
{
prev_closest_joint = i;
prev_closest_joint_dist = dist;
}
}
if (closest_joint_dist == 0.0f)
{
// Hard skin to closest joint
skinned.mWeights[0] = SoftBodySharedSettings::SkinWeight(closest_joint, 1.0f);
}
else
{
// Skin to two closest joints
skinned.mWeights[0] = SoftBodySharedSettings::SkinWeight(closest_joint, 1.0f / closest_joint_dist);
skinned.mWeights[1] = SoftBodySharedSettings::SkinWeight(prev_closest_joint, 1.0f / prev_closest_joint_dist);
skinned.NormalizeWeights();
}
settings->mSkinnedConstraints.push_back(skinned);
}
// Calculate the information needed for skinned constraints
settings->CalculateSkinnedConstraintNormals();
// Optimize the settings (note that this is the second time we call this, the first time was in SoftBodyCreator::CreateCloth,
// this is a bit wasteful but we must do it because we added more constraints)
settings->Optimize();
// Create the body
SoftBodyCreationSettings cloth(settings, body_translation, Quat::sIdentity(), Layers::MOVING);
mBody = mBodyInterface->CreateSoftBody(cloth);
mBodyInterface->AddBody(mBody->GetID(), EActivation::Activate);
// Initially hard skin all vertices to the pose
SkinVertices(true);
}
void SoftBodySkinnedConstraintTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
{
// Draw the pose pre step
Array<Mat44> pose = GetWorldSpacePose(mTime);
for (int i = 1; i < cNumJoints; ++i)
{
mDebugRenderer->DrawArrow(RVec3(pose[i - 1].GetTranslation()), RVec3(pose[i].GetTranslation()), Color::sGreen, 0.1f);
mDebugRenderer->DrawCoordinateSystem(RMat44(pose[i]), 0.5f);
}
// Update time
mTime += sTimeScale * inParams.mDeltaTime;
// Calculate skinned vertices but do not hard skin them
SkinVertices(false);
}
void SoftBodySkinnedConstraintTest::SaveState(StateRecorder &inStream) const
{
inStream.Write(mTime);
}
void SoftBodySkinnedConstraintTest::RestoreState(StateRecorder &inStream)
{
inStream.Read(mTime);
}
void SoftBodySkinnedConstraintTest::CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu)
{
inUI->CreateSlider(inSubMenu, "Time Scale", sTimeScale, 0.0f, 10.0f, 0.1f, [](float inValue) { sTimeScale = inValue; });
inUI->CreateCheckBox(inSubMenu, "Update Skinning", sUpdateSkinning, [](UICheckBox::EState inState) { sUpdateSkinning = inState == UICheckBox::STATE_CHECKED; });
inUI->CreateCheckBox(inSubMenu, "Enable Skin Constraints", sEnableSkinConstraints, [](UICheckBox::EState inState) { sEnableSkinConstraints = inState == UICheckBox::STATE_CHECKED; });
inUI->CreateSlider(inSubMenu, "Max Distance Multiplier", sMaxDistanceMultiplier, 0.0f, 10.0f, 0.1f, [](float inValue) { sMaxDistanceMultiplier = inValue; });
}

View File

@@ -0,0 +1,59 @@
// 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/Body/Body.h>
class SoftBodySkinnedConstraintTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SoftBodySkinnedConstraintTest)
// Description of the test
virtual const char * GetDescription() const override
{
return "Shows how to attach a soft body to a skinned mesh and control the animation.";
}
// See: Test
virtual void Initialize() override;
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
virtual void GetInitialCamera(CameraState &ioState) const override { ioState.mPos = RVec3(15, 30, 15); }
virtual void SaveState(StateRecorder &inStream) const override;
virtual void RestoreState(StateRecorder &inStream) override;
virtual bool HasSettingsMenu() const override { return true; }
virtual void CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu) override;
private:
// Size and spacing of the cloth
static constexpr int cNumVerticesX = 10;
static constexpr int cNumVerticesZ = 50;
static constexpr float cVertexSpacing = 0.5f;
// Number of joints that drive the cloth
static constexpr int cNumJoints = 11;
// Position of the body
static constexpr float cBodyPosY = 20.0f;
// Get a procedurally generated pose
Array<Mat44> GetWorldSpacePose(float inTime) const;
// Skin the vertices of the soft body to the pose
void SkinVertices(bool inHardSkinAll);
// The soft body
Body * mBody;
// Current time
float mTime = 0.0f;
// Settings
static inline float sTimeScale = 1.0f;
static inline bool sUpdateSkinning = true;
static inline bool sEnableSkinConstraints = true;
static inline float sMaxDistanceMultiplier = 1.0f;
};

View File

@@ -0,0 +1,87 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodyStressTest.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Utils/SoftBodyCreator.h>
#include <Application/DebugUI.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodyStressTest)
{
JPH_ADD_BASE_CLASS(SoftBodyStressTest, Test)
}
const char *SoftBodyStressTest::sScenes[] =
{
"SpheresVsBoxes",
"LargeCloth"
};
const char *SoftBodyStressTest::sSceneName = "SpheresVsBoxes";
void SoftBodyStressTest::Initialize()
{
if (strcmp(sSceneName, "SpheresVsBoxes") == 0)
{
// Floor
CreateMeshTerrain();
// Pressurized sphere settings
SoftBodyCreationSettings sphere(SoftBodyCreator::CreateSphere(), RVec3::sZero(), Quat::sIdentity(), Layers::MOVING);
sphere.mPressure = 2000.0f;
// Box settings
BodyCreationSettings box(new BoxShape(Vec3::sOne()), RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
box.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
box.mMassPropertiesOverride.mMass = 100.0f;
for (int x = 0; x <= 10; ++x)
for (int z = 0; z <= 10; ++z)
{
sphere.mPosition = RVec3(-20.0_r + 4.0_r * x, 5.0_r, -20.0_r + 4.0_r * z);
mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
box.mPosition = sphere.mPosition + RVec3(0, 4, 0);
mBodyInterface->CreateAndAddBody(box, EActivation::Activate);
}
}
else if (strcmp(sSceneName, "LargeCloth") == 0)
{
// Floor
CreateFloor();
// Create cloth that's fixated at the corners
SoftBodyCreationSettings cloth(SoftBodyCreator::CreateClothWithFixatedCorners(100, 100, 0.25f), RVec3(0, 15.0f, 0), Quat::sIdentity(), Layers::MOVING);
cloth.mUpdatePosition = false; // Don't update the position of the cloth as it is fixed to the world
mBodyInterface->CreateAndAddSoftBody(cloth, EActivation::Activate);
// Box settings
BodyCreationSettings box(new BoxShape(Vec3::sReplicate(0.5f)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
box.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
box.mMassPropertiesOverride.mMass = 10.0f;
// Create a number of boxes that fall on the cloth
for (int x = 0; x <= 10; ++x)
for (int z = 0; z <= 10; ++z)
{
box.mPosition = cloth.mPosition + RVec3(-10.0_r + 2.0_r * x, 2.0_r, -10.0_r + 2.0_r * z);
mBodyInterface->CreateAndAddBody(box, EActivation::Activate);
}
}
}
void SoftBodyStressTest::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]() { sSceneName = sScenes[i]; RestartTest(); });
inUI->ShowMenu(scene_name);
});
}

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 SoftBodyStressTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SoftBodyStressTest)
// Description of the test
virtual const char * GetDescription() const override
{
return "Stresses the soft body system by creating a large number of soft bodies / a soft body with many vertices.";
}
// See: Test
virtual void Initialize() override;
// Optional settings menu
virtual bool HasSettingsMenu() const override { return true; }
virtual void CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu) override;
private:
// List of possible scene names
static const char * sScenes[];
// Filename of animation to load for this test
static const char * sSceneName;
};

View File

@@ -0,0 +1,34 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodyUpdatePositionTest.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Utils/SoftBodyCreator.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodyUpdatePositionTest)
{
JPH_ADD_BASE_CLASS(SoftBodyUpdatePositionTest, Test)
}
void SoftBodyUpdatePositionTest::Initialize()
{
// Floor
CreateFloor();
// Bodies with various settings for 'make rotation identity' and 'update position'
SoftBodyCreationSettings sphere(SoftBodySharedSettings::sCreateCube(5, 0.5f), RVec3::sZero(), Quat::sRotation(Vec3::sReplicate(1.0f / sqrt(3.0f)), 0.25f * JPH_PI), Layers::MOVING);
for (int update_position = 0; update_position < 2; ++update_position)
for (int make_rotation_identity = 0; make_rotation_identity < 2; ++make_rotation_identity)
{
sphere.mPosition = RVec3(update_position * 10.0f, 10.0f, make_rotation_identity * 10.0f);
sphere.mUpdatePosition = update_position != 0;
sphere.mMakeRotationIdentity = make_rotation_identity != 0;
BodyID id = mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
SetBodyLabel(id, StringFormat("UpdatePosition: %s\nMakeRotationIdentity: %s", update_position != 0? "On" : "Off", make_rotation_identity != 0? "On" : "Off"));
}
}

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>
class SoftBodyUpdatePositionTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SoftBodyUpdatePositionTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "This test tests soft bodies with and without 'update position' and 'make rotation identity'.\n"
"The labels of the bodies that don't update their position will stay in place.\n"
"If you turn on 'Draw Bounding Boxes' then you will see that the cubes that with 'make rotation identity' have a smaller bounding box.";
}
// See: Test
virtual void Initialize() override;
};

View File

@@ -0,0 +1,38 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodyVertexRadiusTest.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Utils/SoftBodyCreator.h>
#include <Layers.h>
#include <Application/DebugUI.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodyVertexRadiusTest)
{
JPH_ADD_BASE_CLASS(SoftBodyVertexRadiusTest, Test)
}
void SoftBodyVertexRadiusTest::Initialize()
{
// Floor
CreateFloor();
// Create sphere
mBodyInterface->CreateAndAddBody(BodyCreationSettings(new SphereShape(2.0f), RVec3(0, 0, 0), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
// Create cloth with specified vertex radius
mSharedSettings = SoftBodyCreator::CreateCloth(30, 30, 0.5f);
SoftBodyCreationSettings cloth(mSharedSettings, RVec3(0, 5, 0), Quat::sRotation(Vec3::sAxisY(), 0.25f * JPH_PI), Layers::MOVING);
cloth.mVertexRadius = sVertexRadius;
mBodyInterface->CreateAndAddSoftBody(cloth, EActivation::Activate);
}
void SoftBodyVertexRadiusTest::CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu)
{
inUI->CreateSlider(inSubMenu, "Vertex Radius", sVertexRadius, 0.0f, 0.5f, 0.01f, [](float inValue) { sVertexRadius = inValue; });
}

View File

@@ -0,0 +1,32 @@
// 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/SoftBody/SoftBodySharedSettings.h>
class SoftBodyVertexRadiusTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SoftBodyVertexRadiusTest)
// Description of the test
virtual const char * GetDescription() const override
{
return "Shows how you can use the vertex radius of a soft body to prevent z-fighting while rendering it.";
}
// See: Test
virtual void Initialize() override;
// Optional settings menu
virtual bool HasSettingsMenu() const override { return true; }
virtual void CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu) override;
private:
Ref<SoftBodySharedSettings> mSharedSettings;
static inline float sVertexRadius = 0.01f;
};

View File

@@ -0,0 +1,41 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Tests/SoftBody/SoftBodyVsFastMovingTest.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Utils/SoftBodyCreator.h>
#include <Layers.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodyVsFastMovingTest)
{
JPH_ADD_BASE_CLASS(SoftBodyVsFastMovingTest, Test)
}
void SoftBodyVsFastMovingTest::Initialize()
{
// Floor
CreateFloor();
// Create sphere moving towards the cloth
BodyCreationSettings bcs(new SphereShape(1.0f), RVec3(-2, 20, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
bcs.mMotionQuality = EMotionQuality::LinearCast;
bcs.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
bcs.mMassPropertiesOverride.mMass = 25.0f;
bcs.mLinearVelocity = Vec3(0, -250, 0);
mBodyInterface->CreateAndAddBody(bcs, EActivation::Activate);
// Create cloth that's fixated at the corners
SoftBodyCreationSettings cloth(SoftBodyCreator::CreateClothWithFixatedCorners(), RVec3(0, 15, 0), Quat::sRotation(Vec3::sAxisX(), 0.1f * JPH_PI) * Quat::sRotation(Vec3::sAxisY(), 0.25f * JPH_PI), Layers::MOVING);
cloth.mUpdatePosition = false; // Don't update the position of the cloth as it is fixed to the world
cloth.mMakeRotationIdentity = false; // Test explicitly checks if soft bodies with a rotation collide with shapes properly
mBodyInterface->CreateAndAddSoftBody(cloth, EActivation::Activate);
// Create another body with a higher ID than the cloth
bcs.mPosition = RVec3(2, 20, 0);
mBodyInterface->CreateAndAddBody(bcs, EActivation::Activate);
}

View File

@@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Tests/Test.h>
class SoftBodyVsFastMovingTest : public Test
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SoftBodyVsFastMovingTest)
// Description of the test
virtual const char *GetDescription() const override
{
return "Shows interaction between a fast moving (CCD) object and a soft body.";
}
// See: Test
virtual void Initialize() override;
};