// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) // SPDX-FileCopyrightText: 2025 Jorrit Rouwe // SPDX-License-Identifier: MIT #include #include #include #include #include #include #include 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 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 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 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 weed_settings = new SoftBodySharedSettings; constexpr int cNumVertices = 64; constexpr int cNumStrands = 50; default_random_engine random; uniform_real_distribution radius_distribution(0, 1.0f); uniform_real_distribution 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(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); } } } }