Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp
9912 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2023 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#include <Jolt/Jolt.h>56#include <Jolt/Physics/SoftBody/SoftBodyMotionProperties.h>7#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>8#include <Jolt/Physics/SoftBody/SoftBodyContactListener.h>9#include <Jolt/Physics/SoftBody/SoftBodyManifold.h>10#include <Jolt/Physics/Collision/CollideSoftBodyVertexIterator.h>11#include <Jolt/Physics/Collision/SimShapeFilterWrapper.h>12#include <Jolt/Physics/PhysicsSystem.h>13#include <Jolt/Physics/Body/BodyManager.h>14#include <Jolt/Core/ScopeExit.h>15#ifdef JPH_DEBUG_RENDERER16#include <Jolt/Renderer/DebugRenderer.h>17#endif // JPH_DEBUG_RENDERER1819JPH_NAMESPACE_BEGIN2021using namespace JPH::literals;2223void SoftBodyMotionProperties::CalculateMassAndInertia()24{25MassProperties mp;2627for (const Vertex &v : mVertices)28if (v.mInvMass > 0.0f)29{30Vec3 pos = v.mPosition;3132// Accumulate mass33float mass = 1.0f / v.mInvMass;34mp.mMass += mass;3536// Inertia tensor, diagonal37// See equations https://en.wikipedia.org/wiki/Moment_of_inertia section 'Inertia Tensor'38for (int i = 0; i < 3; ++i)39mp.mInertia(i, i) += mass * (Square(pos[(i + 1) % 3]) + Square(pos[(i + 2) % 3]));4041// Inertia tensor off diagonal42for (int i = 0; i < 3; ++i)43for (int j = 0; j < 3; ++j)44if (i != j)45mp.mInertia(i, j) -= mass * pos[i] * pos[j];46}47else48{49// If one vertex is kinematic, the entire body will have infinite mass and inertia50SetInverseMass(0.0f);51SetInverseInertia(Vec3::sZero(), Quat::sIdentity());52return;53}5455SetMassProperties(EAllowedDOFs::All, mp);56}5758void SoftBodyMotionProperties::Initialize(const SoftBodyCreationSettings &inSettings)59{60// Store settings61mSettings = inSettings.mSettings;62mNumIterations = inSettings.mNumIterations;63mPressure = inSettings.mPressure;64mUpdatePosition = inSettings.mUpdatePosition;6566// Initialize vertices67mVertices.resize(inSettings.mSettings->mVertices.size());68Mat44 rotation = inSettings.mMakeRotationIdentity? Mat44::sRotation(inSettings.mRotation) : Mat44::sIdentity();69for (Array<Vertex>::size_type v = 0, s = mVertices.size(); v < s; ++v)70{71const SoftBodySharedSettings::Vertex &in_vertex = inSettings.mSettings->mVertices[v];72Vertex &out_vertex = mVertices[v];73out_vertex.mPreviousPosition = out_vertex.mPosition = rotation * Vec3(in_vertex.mPosition);74out_vertex.mVelocity = rotation.Multiply3x3(Vec3(in_vertex.mVelocity));75out_vertex.ResetCollision();76out_vertex.mInvMass = in_vertex.mInvMass;77mLocalBounds.Encapsulate(out_vertex.mPosition);78}7980// Allocate space for skinned vertices81if (!inSettings.mSettings->mSkinnedConstraints.empty())82mSkinState.resize(mVertices.size());8384// We don't know delta time yet, so we can't predict the bounds and use the local bounds as the predicted bounds85mLocalPredictedBounds = mLocalBounds;8687CalculateMassAndInertia();88}8990float SoftBodyMotionProperties::GetVolumeTimesSix() const91{92float six_volume = 0.0f;93for (const Face &f : mSettings->mFaces)94{95Vec3 x1 = mVertices[f.mVertex[0]].mPosition;96Vec3 x2 = mVertices[f.mVertex[1]].mPosition;97Vec3 x3 = mVertices[f.mVertex[2]].mPosition;98six_volume += x1.Cross(x2).Dot(x3); // We pick zero as the origin as this is the center of the bounding box so should give good accuracy99}100return six_volume;101}102103void SoftBodyMotionProperties::DetermineCollidingShapes(const SoftBodyUpdateContext &inContext, const PhysicsSystem &inSystem, const BodyLockInterface &inBodyLockInterface)104{105JPH_PROFILE_FUNCTION();106107// Reset flag prior to collision detection108mNeedContactCallback.store(false, memory_order_relaxed);109110struct Collector : public CollideShapeBodyCollector111{112Collector(const SoftBodyUpdateContext &inContext, const PhysicsSystem &inSystem, const BodyLockInterface &inBodyLockInterface, const AABox &inLocalBounds, SimShapeFilterWrapper &inShapeFilter, Array<CollidingShape> &ioHits, Array<CollidingSensor> &ioSensors) :113mContext(inContext),114mInverseTransform(inContext.mCenterOfMassTransform.InversedRotationTranslation()),115mLocalBounds(inLocalBounds),116mBodyLockInterface(inBodyLockInterface),117mCombineFriction(inSystem.GetCombineFriction()),118mCombineRestitution(inSystem.GetCombineRestitution()),119mShapeFilter(inShapeFilter),120mHits(ioHits),121mSensors(ioSensors)122{123}124125virtual void AddHit(const BodyID &inResult) override126{127BodyLockRead lock(mBodyLockInterface, inResult);128if (lock.Succeeded())129{130const Body &soft_body = *mContext.mBody;131const Body &body = lock.GetBody();132if (body.IsRigidBody() // TODO: We should support soft body vs soft body133&& soft_body.GetCollisionGroup().CanCollide(body.GetCollisionGroup()))134{135SoftBodyContactSettings settings;136settings.mIsSensor = body.IsSensor();137138if (mContext.mContactListener == nullptr)139{140// If we have no contact listener, we can ignore sensors141if (settings.mIsSensor)142return;143}144else145{146// Call the contact listener to see if we should accept this contact147if (mContext.mContactListener->OnSoftBodyContactValidate(soft_body, body, settings) != SoftBodyValidateResult::AcceptContact)148return;149150// Check if there will be any interaction151if (!settings.mIsSensor152&& settings.mInvMassScale1 == 0.0f153&& (body.GetMotionType() != EMotionType::Dynamic || settings.mInvMassScale2 == 0.0f))154return;155}156157// Calculate transform of this body relative to the soft body158Mat44 com = (mInverseTransform * body.GetCenterOfMassTransform()).ToMat44();159160// Collect leaf shapes161mShapeFilter.SetBody2(&body);162struct LeafShapeCollector : public TransformedShapeCollector163{164virtual void AddHit(const TransformedShape &inResult) override165{166mHits.emplace_back(Mat44::sRotationTranslation(inResult.mShapeRotation, Vec3(inResult.mShapePositionCOM)), inResult.GetShapeScale(), inResult.mShape);167}168169Array<LeafShape> mHits;170};171LeafShapeCollector collector;172body.GetShape()->CollectTransformedShapes(mLocalBounds, com.GetTranslation(), com.GetQuaternion(), Vec3::sOne(), SubShapeIDCreator(), collector, mShapeFilter);173if (collector.mHits.empty())174return;175176if (settings.mIsSensor)177{178CollidingSensor cs;179cs.mCenterOfMassTransform = com;180cs.mShapes = std::move(collector.mHits);181cs.mBodyID = inResult;182mSensors.push_back(cs);183}184else185{186CollidingShape cs;187cs.mCenterOfMassTransform = com;188cs.mShapes = std::move(collector.mHits);189cs.mBodyID = inResult;190cs.mMotionType = body.GetMotionType();191cs.mUpdateVelocities = false;192cs.mFriction = mCombineFriction(soft_body, SubShapeID(), body, SubShapeID());193cs.mRestitution = mCombineRestitution(soft_body, SubShapeID(), body, SubShapeID());194cs.mSoftBodyInvMassScale = settings.mInvMassScale1;195if (cs.mMotionType == EMotionType::Dynamic)196{197const MotionProperties *mp = body.GetMotionProperties();198cs.mInvMass = settings.mInvMassScale2 * mp->GetInverseMass();199cs.mInvInertia = settings.mInvInertiaScale2 * mp->GetInverseInertiaForRotation(cs.mCenterOfMassTransform.GetRotation());200cs.mOriginalLinearVelocity = cs.mLinearVelocity = mInverseTransform.Multiply3x3(mp->GetLinearVelocity());201cs.mOriginalAngularVelocity = cs.mAngularVelocity = mInverseTransform.Multiply3x3(mp->GetAngularVelocity());202}203mHits.push_back(cs);204}205}206}207}208209private:210const SoftBodyUpdateContext &mContext;211RMat44 mInverseTransform;212AABox mLocalBounds;213const BodyLockInterface & mBodyLockInterface;214ContactConstraintManager::CombineFunction mCombineFriction;215ContactConstraintManager::CombineFunction mCombineRestitution;216SimShapeFilterWrapper & mShapeFilter;217Array<CollidingShape> & mHits;218Array<CollidingSensor> & mSensors;219};220221// Calculate local bounding box222AABox local_bounds = mLocalBounds;223local_bounds.Encapsulate(mLocalPredictedBounds);224local_bounds.ExpandBy(Vec3::sReplicate(mSettings->mVertexRadius));225226// Calculate world space bounding box227AABox world_bounds = local_bounds.Transformed(inContext.mCenterOfMassTransform);228229// Create shape filter230SimShapeFilterWrapperUnion shape_filter_union(inContext.mSimShapeFilter, inContext.mBody);231SimShapeFilterWrapper &shape_filter = shape_filter_union.GetSimShapeFilterWrapper();232233Collector collector(inContext, inSystem, inBodyLockInterface, local_bounds, shape_filter, mCollidingShapes, mCollidingSensors);234ObjectLayer layer = inContext.mBody->GetObjectLayer();235DefaultBroadPhaseLayerFilter broadphase_layer_filter = inSystem.GetDefaultBroadPhaseLayerFilter(layer);236DefaultObjectLayerFilter object_layer_filter = inSystem.GetDefaultLayerFilter(layer);237inSystem.GetBroadPhaseQuery().CollideAABox(world_bounds, collector, broadphase_layer_filter, object_layer_filter);238mNumSensors = uint(mCollidingSensors.size()); // Workaround for TSAN false positive: store mCollidingSensors.size() in a separate variable.239}240241void SoftBodyMotionProperties::DetermineCollisionPlanes(uint inVertexStart, uint inNumVertices)242{243JPH_PROFILE_FUNCTION();244245// Generate collision planes246for (const CollidingShape &cs : mCollidingShapes)247for (const LeafShape &shape : cs.mShapes)248shape.mShape->CollideSoftBodyVertices(shape.mTransform, shape.mScale, CollideSoftBodyVertexIterator(mVertices.data() + inVertexStart), inNumVertices, int(&cs - mCollidingShapes.data()));249}250251void SoftBodyMotionProperties::DetermineSensorCollisions(CollidingSensor &ioSensor)252{253JPH_PROFILE_FUNCTION();254255Plane collision_plane;256float largest_penetration = -FLT_MAX;257int colliding_shape_idx = -1;258259// Collide sensor against all vertices260CollideSoftBodyVertexIterator vertex_iterator(261StridedPtr<const Vec3>(&mVertices[0].mPosition, sizeof(SoftBodyVertex)), // The position and mass come from the soft body vertex262StridedPtr<const float>(&mVertices[0].mInvMass, sizeof(SoftBodyVertex)),263StridedPtr<Plane>(&collision_plane, 0), // We want all vertices to result in a single collision so we pass stride 0264StridedPtr<float>(&largest_penetration, 0),265StridedPtr<int>(&colliding_shape_idx, 0));266for (const LeafShape &shape : ioSensor.mShapes)267shape.mShape->CollideSoftBodyVertices(shape.mTransform, shape.mScale, vertex_iterator, uint(mVertices.size()), 0);268ioSensor.mHasContact = largest_penetration > 0.0f;269270// We need a contact callback if one of the sensors collided271if (ioSensor.mHasContact)272mNeedContactCallback.store(true, memory_order_relaxed);273}274275void SoftBodyMotionProperties::ApplyPressure(const SoftBodyUpdateContext &inContext)276{277JPH_PROFILE_FUNCTION();278279float dt = inContext.mSubStepDeltaTime;280float pressure_coefficient = mPressure;281if (pressure_coefficient > 0.0f)282{283// Calculate total volume284float six_volume = GetVolumeTimesSix();285if (six_volume > 0.0f)286{287// Apply pressure288// p = F / A = n R T / V (see https://en.wikipedia.org/wiki/Pressure)289// Our pressure coefficient is n R T so the impulse is:290// P = F dt = pressure_coefficient / V * A * dt291float coefficient = pressure_coefficient * dt / six_volume; // Need to still multiply by 6 for the volume292for (const Face &f : mSettings->mFaces)293{294Vec3 x1 = mVertices[f.mVertex[0]].mPosition;295Vec3 x2 = mVertices[f.mVertex[1]].mPosition;296Vec3 x3 = mVertices[f.mVertex[2]].mPosition;297298Vec3 impulse = coefficient * (x2 - x1).Cross(x3 - x1); // Area is half the cross product so need to still divide by 2299for (uint32 i : f.mVertex)300{301Vertex &v = mVertices[i];302v.mVelocity += v.mInvMass * impulse; // Want to divide by 3 because we spread over 3 vertices303}304}305}306}307}308309void SoftBodyMotionProperties::IntegratePositions(const SoftBodyUpdateContext &inContext)310{311JPH_PROFILE_FUNCTION();312313float dt = inContext.mSubStepDeltaTime;314float linear_damping = max(0.0f, 1.0f - GetLinearDamping() * dt); // See: MotionProperties::ApplyForceTorqueAndDragInternal315316// Integrate317Vec3 sub_step_gravity = inContext.mGravity * dt;318Vec3 sub_step_impulse = GetAccumulatedForce() * dt / max(float(mVertices.size()), 1.0f);319for (Vertex &v : mVertices)320if (v.mInvMass > 0.0f)321{322// Gravity323v.mVelocity += sub_step_gravity + sub_step_impulse * v.mInvMass;324325// Damping326v.mVelocity *= linear_damping;327328// Integrate329v.mPreviousPosition = v.mPosition;330v.mPosition += v.mVelocity * dt;331}332else333{334// Integrate335v.mPreviousPosition = v.mPosition;336v.mPosition += v.mVelocity * dt;337}338}339340void SoftBodyMotionProperties::ApplyDihedralBendConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex)341{342JPH_PROFILE_FUNCTION();343344float inv_dt_sq = 1.0f / Square(inContext.mSubStepDeltaTime);345346for (const DihedralBend *b = mSettings->mDihedralBendConstraints.data() + inStartIndex, *b_end = mSettings->mDihedralBendConstraints.data() + inEndIndex; b < b_end; ++b)347{348Vertex &v0 = mVertices[b->mVertex[0]];349Vertex &v1 = mVertices[b->mVertex[1]];350Vertex &v2 = mVertices[b->mVertex[2]];351Vertex &v3 = mVertices[b->mVertex[3]];352353// Get positions354Vec3 x0 = v0.mPosition;355Vec3 x1 = v1.mPosition;356Vec3 x2 = v2.mPosition;357Vec3 x3 = v3.mPosition;358359/*360x2361e1/ \e3362/ \363x0----x1364\ e0 /365e2\ /e4366x3367*/368369// Calculate the shared edge of the triangles370Vec3 e = x1 - x0;371float e_len = e.Length();372if (e_len < 1.0e-6f)373continue;374375// Calculate the normals of the triangles376Vec3 x1x2 = x2 - x1;377Vec3 x1x3 = x3 - x1;378Vec3 n1 = (x2 - x0).Cross(x1x2);379Vec3 n2 = x1x3.Cross(x3 - x0);380float n1_len_sq = n1.LengthSq();381float n2_len_sq = n2.LengthSq();382float n1_len_sq_n2_len_sq = n1_len_sq * n2_len_sq;383if (n1_len_sq_n2_len_sq < 1.0e-24f)384continue;385386// Calculate constraint equation387// As per "Strain Based Dynamics" Appendix A we need to negate the gradients when (n1 x n2) . e > 0, instead we make sure that the sign of the constraint equation is correct388float sign = Sign(n2.Cross(n1).Dot(e));389float d = n1.Dot(n2) / sqrt(n1_len_sq_n2_len_sq);390float c = sign * ACosApproximate(d) - b->mInitialAngle;391392// Ensure the range is -PI to PI393if (c > JPH_PI)394c -= 2.0f * JPH_PI;395else if (c < -JPH_PI)396c += 2.0f * JPH_PI;397398// Calculate gradient of constraint equation399// Taken from "Strain Based Dynamics" - Matthias Muller et al. (Appendix A)400// with p1 = x2, p2 = x3, p3 = x0 and p4 = x1401// which in turn is based on "Simulation of Clothing with Folds and Wrinkles" - R. Bridson et al. (Section 4)402n1 /= n1_len_sq;403n2 /= n2_len_sq;404Vec3 d0c = (x1x2.Dot(e) * n1 + x1x3.Dot(e) * n2) / e_len;405Vec3 d2c = e_len * n1;406Vec3 d3c = e_len * n2;407408// The sum of the gradients must be zero (see "Strain Based Dynamics" section 4)409Vec3 d1c = -d0c - d2c - d3c;410411// Get masses412float w0 = v0.mInvMass;413float w1 = v1.mInvMass;414float w2 = v2.mInvMass;415float w3 = v3.mInvMass;416417// Calculate -lambda418float denom = w0 * d0c.LengthSq() + w1 * d1c.LengthSq() + w2 * d2c.LengthSq() + w3 * d3c.LengthSq() + b->mCompliance * inv_dt_sq;419if (denom < 1.0e-12f)420continue;421float minus_lambda = c / denom;422423// Apply correction424v0.mPosition = x0 - minus_lambda * w0 * d0c;425v1.mPosition = x1 - minus_lambda * w1 * d1c;426v2.mPosition = x2 - minus_lambda * w2 * d2c;427v3.mPosition = x3 - minus_lambda * w3 * d3c;428}429}430431void SoftBodyMotionProperties::ApplyVolumeConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex)432{433JPH_PROFILE_FUNCTION();434435float inv_dt_sq = 1.0f / Square(inContext.mSubStepDeltaTime);436437// Satisfy volume constraints438for (const Volume *v = mSettings->mVolumeConstraints.data() + inStartIndex, *v_end = mSettings->mVolumeConstraints.data() + inEndIndex; v < v_end; ++v)439{440Vertex &v1 = mVertices[v->mVertex[0]];441Vertex &v2 = mVertices[v->mVertex[1]];442Vertex &v3 = mVertices[v->mVertex[2]];443Vertex &v4 = mVertices[v->mVertex[3]];444445Vec3 x1 = v1.mPosition;446Vec3 x2 = v2.mPosition;447Vec3 x3 = v3.mPosition;448Vec3 x4 = v4.mPosition;449450// Calculate constraint equation451Vec3 x1x2 = x2 - x1;452Vec3 x1x3 = x3 - x1;453Vec3 x1x4 = x4 - x1;454float c = abs(x1x2.Cross(x1x3).Dot(x1x4)) - v->mSixRestVolume;455456// Calculate gradient of constraint equation457Vec3 d1c = (x4 - x2).Cross(x3 - x2);458Vec3 d2c = x1x3.Cross(x1x4);459Vec3 d3c = x1x4.Cross(x1x2);460Vec3 d4c = x1x2.Cross(x1x3);461462// Get masses463float w1 = v1.mInvMass;464float w2 = v2.mInvMass;465float w3 = v3.mInvMass;466float w4 = v4.mInvMass;467468// Calculate -lambda469float denom = w1 * d1c.LengthSq() + w2 * d2c.LengthSq() + w3 * d3c.LengthSq() + w4 * d4c.LengthSq() + v->mCompliance * inv_dt_sq;470if (denom < 1.0e-12f)471continue;472float minus_lambda = c / denom;473474// Apply correction475v1.mPosition = x1 - minus_lambda * w1 * d1c;476v2.mPosition = x2 - minus_lambda * w2 * d2c;477v3.mPosition = x3 - minus_lambda * w3 * d3c;478v4.mPosition = x4 - minus_lambda * w4 * d4c;479}480}481482void SoftBodyMotionProperties::ApplySkinConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex)483{484// Early out if nothing to do485if (mSettings->mSkinnedConstraints.empty() || !mEnableSkinConstraints)486return;487488JPH_PROFILE_FUNCTION();489490// We're going to iterate multiple times over the skin constraints, update the skinned position accordingly.491// If we don't do this, the simulation will see a big jump and the first iteration will cause a big velocity change in the system.492float factor = mSkinStatePreviousPositionValid? inContext.mNextIteration.load(std::memory_order_relaxed) / float(mNumIterations) : 1.0f;493float prev_factor = 1.0f - factor;494495// Apply the constraints496Vertex *vertices = mVertices.data();497const SkinState *skin_states = mSkinState.data();498for (const Skinned *s = mSettings->mSkinnedConstraints.data() + inStartIndex, *s_end = mSettings->mSkinnedConstraints.data() + inEndIndex; s < s_end; ++s)499{500Vertex &vertex = vertices[s->mVertex];501const SkinState &skin_state = skin_states[s->mVertex];502float max_distance = s->mMaxDistance * mSkinnedMaxDistanceMultiplier;503504// Calculate the skinned position by interpolating from previous to current position505Vec3 skin_pos = prev_factor * skin_state.mPreviousPosition + factor * skin_state.mPosition;506507if (max_distance > 0.0f)508{509// Move vertex if it violated the back stop510if (s->mBackStopDistance < max_distance)511{512// Center of the back stop sphere513Vec3 center = skin_pos - skin_state.mNormal * (s->mBackStopDistance + s->mBackStopRadius);514515// Check if we're inside the back stop sphere516Vec3 delta = vertex.mPosition - center;517float delta_len_sq = delta.LengthSq();518if (delta_len_sq < Square(s->mBackStopRadius))519{520// Push the vertex to the surface of the back stop sphere521float delta_len = sqrt(delta_len_sq);522vertex.mPosition = delta_len > 0.0f?523center + delta * (s->mBackStopRadius / delta_len)524: center + skin_state.mNormal * s->mBackStopRadius;525}526}527528// Clamp vertex distance to max distance from skinned position529if (max_distance < FLT_MAX)530{531Vec3 delta = vertex.mPosition - skin_pos;532float delta_len_sq = delta.LengthSq();533float max_distance_sq = Square(max_distance);534if (delta_len_sq > max_distance_sq)535vertex.mPosition = skin_pos + delta * sqrt(max_distance_sq / delta_len_sq);536}537}538else539{540// Kinematic: Just update the vertex position541vertex.mPosition = skin_pos;542}543}544}545546void SoftBodyMotionProperties::ApplyEdgeConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex)547{548JPH_PROFILE_FUNCTION();549550float inv_dt_sq = 1.0f / Square(inContext.mSubStepDeltaTime);551552// Satisfy edge constraints553for (const Edge *e = mSettings->mEdgeConstraints.data() + inStartIndex, *e_end = mSettings->mEdgeConstraints.data() + inEndIndex; e < e_end; ++e)554{555Vertex &v0 = mVertices[e->mVertex[0]];556Vertex &v1 = mVertices[e->mVertex[1]];557558// Get positions559Vec3 x0 = v0.mPosition;560Vec3 x1 = v1.mPosition;561562// Calculate current length563Vec3 delta = x1 - x0;564float length = delta.Length();565566// Apply correction567float denom = length * (v0.mInvMass + v1.mInvMass + e->mCompliance * inv_dt_sq);568if (denom < 1.0e-12f)569continue;570Vec3 correction = delta * (length - e->mRestLength) / denom;571v0.mPosition = x0 + v0.mInvMass * correction;572v1.mPosition = x1 - v1.mInvMass * correction;573}574}575576void SoftBodyMotionProperties::ApplyLRAConstraints(uint inStartIndex, uint inEndIndex)577{578JPH_PROFILE_FUNCTION();579580// Satisfy LRA constraints581Vertex *vertices = mVertices.data();582for (const LRA *lra = mSettings->mLRAConstraints.data() + inStartIndex, *lra_end = mSettings->mLRAConstraints.data() + inEndIndex; lra < lra_end; ++lra)583{584JPH_ASSERT(lra->mVertex[0] < mVertices.size());585JPH_ASSERT(lra->mVertex[1] < mVertices.size());586const Vertex &vertex0 = vertices[lra->mVertex[0]];587Vertex &vertex1 = vertices[lra->mVertex[1]];588589Vec3 x0 = vertex0.mPosition;590Vec3 delta = vertex1.mPosition - x0;591float delta_len_sq = delta.LengthSq();592if (delta_len_sq > Square(lra->mMaxDistance))593vertex1.mPosition = x0 + delta * lra->mMaxDistance / sqrt(delta_len_sq);594}595}596597void SoftBodyMotionProperties::ApplyCollisionConstraintsAndUpdateVelocities(const SoftBodyUpdateContext &inContext)598{599JPH_PROFILE_FUNCTION();600601float dt = inContext.mSubStepDeltaTime;602float restitution_threshold = -2.0f * inContext.mGravity.Length() * dt;603float vertex_radius = mSettings->mVertexRadius;604for (Vertex &v : mVertices)605if (v.mInvMass > 0.0f)606{607// Remember previous velocity for restitution calculations608Vec3 prev_v = v.mVelocity;609610// XPBD velocity update611v.mVelocity = (v.mPosition - v.mPreviousPosition) / dt;612613// Satisfy collision constraint614if (v.mCollidingShapeIndex >= 0)615{616// Check if there is a collision617float projected_distance = -v.mCollisionPlane.SignedDistance(v.mPosition) + vertex_radius;618if (projected_distance > 0.0f)619{620// Remember that there was a collision621v.mHasContact = true;622623// We need a contact callback if one of the vertices collided624mNeedContactCallback.store(true, memory_order_relaxed);625626// Note that we already calculated the velocity, so this does not affect the velocity (next iteration starts by setting previous position to current position)627CollidingShape &cs = mCollidingShapes[v.mCollidingShapeIndex];628Vec3 contact_normal = v.mCollisionPlane.GetNormal();629v.mPosition += contact_normal * projected_distance;630631// Apply friction as described in Detailed Rigid Body Simulation with Extended Position Based Dynamics - Matthias Muller et al.632// See section 3.6:633// Inverse mass: w1 = 1 / m1, w2 = 1 / m2 + (r2 x n)^T I^-1 (r2 x n) = 0 for a static object634// r2 are the contact point relative to the center of mass of body 2635// Lagrange multiplier for contact: lambda = -c / (w1 + w2)636// Where c is the constraint equation (the distance to the plane, negative because penetrating)637// Contact normal force: fn = lambda / dt^2638// Delta velocity due to friction dv = -vt / |vt| * min(dt * friction * fn * (w1 + w2), |vt|) = -vt * min(-friction * c / (|vt| * dt), 1)639// Note that I think there is an error in the paper, I added a mass term, see: https://github.com/matthias-research/pages/issues/29640// Relative velocity: vr = v1 - v2 - omega2 x r2641// Normal velocity: vn = vr . contact_normal642// Tangential velocity: vt = vr - contact_normal * vn643// Impulse: p = dv / (w1 + w2)644// Changes in particle velocities:645// v1 = v1 + p / m1646// v2 = v2 - p / m2 (no change when colliding with a static body)647// w2 = w2 - I^-1 (r2 x p) (no change when colliding with a static body)648if (cs.mMotionType == EMotionType::Dynamic)649{650// Calculate normal and tangential velocity (equation 30)651Vec3 r2 = v.mPosition - cs.mCenterOfMassTransform.GetTranslation();652Vec3 v2 = cs.GetPointVelocity(r2);653Vec3 relative_velocity = v.mVelocity - v2;654Vec3 v_normal = contact_normal * contact_normal.Dot(relative_velocity);655Vec3 v_tangential = relative_velocity - v_normal;656float v_tangential_length = v_tangential.Length();657658// Calculate resulting inverse mass of vertex659float vertex_inv_mass = cs.mSoftBodyInvMassScale * v.mInvMass;660661// Calculate inverse effective mass662Vec3 r2_cross_n = r2.Cross(contact_normal);663float w2 = cs.mInvMass + r2_cross_n.Dot(cs.mInvInertia * r2_cross_n);664float w1_plus_w2 = vertex_inv_mass + w2;665if (w1_plus_w2 > 0.0f)666{667// Calculate delta relative velocity due to friction (modified equation 31)668Vec3 dv;669if (v_tangential_length > 0.0f)670dv = v_tangential * min(cs.mFriction * projected_distance / (v_tangential_length * dt), 1.0f);671else672dv = Vec3::sZero();673674// Calculate delta relative velocity due to restitution (equation 35)675dv += v_normal;676float prev_v_normal = (prev_v - v2).Dot(contact_normal);677if (prev_v_normal < restitution_threshold)678dv += cs.mRestitution * prev_v_normal * contact_normal;679680// Calculate impulse681Vec3 p = dv / w1_plus_w2;682683// Apply impulse to particle684v.mVelocity -= p * vertex_inv_mass;685686// Apply impulse to rigid body687cs.mLinearVelocity += p * cs.mInvMass;688cs.mAngularVelocity += cs.mInvInertia * r2.Cross(p);689690// Mark that the velocities of the body we hit need to be updated691cs.mUpdateVelocities = true;692}693}694else if (cs.mSoftBodyInvMassScale > 0.0f)695{696// Body is not movable, equations are simpler697698// Calculate normal and tangential velocity (equation 30)699Vec3 v_normal = contact_normal * contact_normal.Dot(v.mVelocity);700Vec3 v_tangential = v.mVelocity - v_normal;701float v_tangential_length = v_tangential.Length();702703// Apply friction (modified equation 31)704if (v_tangential_length > 0.0f)705v.mVelocity -= v_tangential * min(cs.mFriction * projected_distance / (v_tangential_length * dt), 1.0f);706707// Apply restitution (equation 35)708v.mVelocity -= v_normal;709float prev_v_normal = prev_v.Dot(contact_normal);710if (prev_v_normal < restitution_threshold)711v.mVelocity -= cs.mRestitution * prev_v_normal * contact_normal;712}713}714}715}716}717718void SoftBodyMotionProperties::UpdateSoftBodyState(SoftBodyUpdateContext &ioContext, const PhysicsSettings &inPhysicsSettings)719{720JPH_PROFILE_FUNCTION();721722// Contact callback723if (mNeedContactCallback.load(memory_order_relaxed) && ioContext.mContactListener != nullptr)724{725// Remove non-colliding sensors from the list726for (int i = int(mCollidingSensors.size()) - 1; i >= 0; --i)727if (!mCollidingSensors[i].mHasContact)728{729mCollidingSensors[i] = std::move(mCollidingSensors.back());730mCollidingSensors.pop_back();731}732733ioContext.mContactListener->OnSoftBodyContactAdded(*ioContext.mBody, SoftBodyManifold(this));734}735736// Loop through vertices once more to update the global state737float dt = ioContext.mDeltaTime;738float max_linear_velocity_sq = Square(GetMaxLinearVelocity());739float max_v_sq = 0.0f;740Vec3 linear_velocity = Vec3::sZero(), angular_velocity = Vec3::sZero();741mLocalPredictedBounds = mLocalBounds = { };742for (Vertex &v : mVertices)743{744// Calculate max square velocity745float v_sq = v.mVelocity.LengthSq();746max_v_sq = max(max_v_sq, v_sq);747748// Clamp if velocity is too high749if (v_sq > max_linear_velocity_sq)750v.mVelocity *= sqrt(max_linear_velocity_sq / v_sq);751752// Calculate local linear/angular velocity753linear_velocity += v.mVelocity;754angular_velocity += v.mPosition.Cross(v.mVelocity);755756// Update local bounding box757mLocalBounds.Encapsulate(v.mPosition);758759// Create predicted position for the next frame in order to detect collisions before they happen760mLocalPredictedBounds.Encapsulate(v.mPosition + v.mVelocity * dt + ioContext.mDisplacementDueToGravity);761762// Reset collision data for the next iteration763v.ResetCollision();764}765766// Calculate linear/angular velocity of the body by averaging all vertices and bringing the value to world space767float num_vertices_divider = float(max(int(mVertices.size()), 1));768SetLinearVelocityClamped(ioContext.mCenterOfMassTransform.Multiply3x3(linear_velocity / num_vertices_divider));769SetAngularVelocity(ioContext.mCenterOfMassTransform.Multiply3x3(angular_velocity / num_vertices_divider));770771if (mUpdatePosition)772{773// Shift the body so that the position is the center of the local bounds774Vec3 delta = mLocalBounds.GetCenter();775ioContext.mDeltaPosition = ioContext.mCenterOfMassTransform.Multiply3x3(delta);776for (Vertex &v : mVertices)777v.mPosition -= delta;778779// Update the skin state too since we will use this position as the previous position in the next update780for (SkinState &s : mSkinState)781s.mPosition -= delta;782JPH_IF_DEBUG_RENDERER(mSkinStateTransform.SetTranslation(mSkinStateTransform.GetTranslation() + ioContext.mDeltaPosition);)783784// Offset bounds to match new position785mLocalBounds.Translate(-delta);786mLocalPredictedBounds.Translate(-delta);787}788else789ioContext.mDeltaPosition = Vec3::sZero();790791// Test if we should go to sleep792if (GetAllowSleeping())793{794if (max_v_sq > inPhysicsSettings.mPointVelocitySleepThreshold)795{796ResetSleepTestTimer();797ioContext.mCanSleep = ECanSleep::CannotSleep;798}799else800ioContext.mCanSleep = AccumulateSleepTime(dt, inPhysicsSettings.mTimeBeforeSleep);801}802else803ioContext.mCanSleep = ECanSleep::CannotSleep;804805// If SkinVertices is not called after this then don't use the previous position as the skin is static806mSkinStatePreviousPositionValid = false;807808// Reset force accumulator809ResetForce();810}811812void SoftBodyMotionProperties::UpdateRigidBodyVelocities(const SoftBodyUpdateContext &inContext, BodyInterface &inBodyInterface)813{814JPH_PROFILE_FUNCTION();815816// Write back velocity deltas817for (const CollidingShape &cs : mCollidingShapes)818if (cs.mUpdateVelocities)819inBodyInterface.AddLinearAndAngularVelocity(cs.mBodyID, inContext.mCenterOfMassTransform.Multiply3x3(cs.mLinearVelocity - cs.mOriginalLinearVelocity), inContext.mCenterOfMassTransform.Multiply3x3(cs.mAngularVelocity - cs.mOriginalAngularVelocity));820821// Clear colliding shapes/sensors to avoid hanging on to references to shapes822mCollidingShapes.clear();823mCollidingSensors.clear();824}825826void SoftBodyMotionProperties::InitializeUpdateContext(float inDeltaTime, Body &inSoftBody, const PhysicsSystem &inSystem, SoftBodyUpdateContext &ioContext)827{828JPH_PROFILE_FUNCTION();829830// Store body831ioContext.mBody = &inSoftBody;832ioContext.mMotionProperties = this;833ioContext.mContactListener = inSystem.GetSoftBodyContactListener();834ioContext.mSimShapeFilter = inSystem.GetSimShapeFilter();835836// Convert gravity to local space837ioContext.mCenterOfMassTransform = inSoftBody.GetCenterOfMassTransform();838ioContext.mGravity = ioContext.mCenterOfMassTransform.Multiply3x3Transposed(GetGravityFactor() * inSystem.GetGravity());839840// Calculate delta time for sub step841ioContext.mDeltaTime = inDeltaTime;842ioContext.mSubStepDeltaTime = inDeltaTime / mNumIterations;843844// Calculate total displacement we'll have due to gravity over all sub steps845// The total displacement as produced by our integrator can be written as: Sum(i * g * dt^2, i = 0..mNumIterations).846// This is bigger than 0.5 * g * dt^2 because we first increment the velocity and then update the position847// Using Sum(i, i = 0..n) = n * (n + 1) / 2 we can write this as:848ioContext.mDisplacementDueToGravity = (0.5f * mNumIterations * (mNumIterations + 1) * Square(ioContext.mSubStepDeltaTime)) * ioContext.mGravity;849}850851void SoftBodyMotionProperties::StartNextIteration(const SoftBodyUpdateContext &ioContext)852{853ApplyPressure(ioContext);854855IntegratePositions(ioContext);856}857858void SoftBodyMotionProperties::StartFirstIteration(SoftBodyUpdateContext &ioContext)859{860// Start the first iteration861JPH_IF_ENABLE_ASSERTS(uint iteration =) ioContext.mNextIteration.fetch_add(1, memory_order_relaxed);862JPH_ASSERT(iteration == 0);863StartNextIteration(ioContext);864ioContext.mState.store(SoftBodyUpdateContext::EState::ApplyConstraints, memory_order_release);865}866867SoftBodyMotionProperties::EStatus SoftBodyMotionProperties::ParallelDetermineCollisionPlanes(SoftBodyUpdateContext &ioContext)868{869// Do a relaxed read first to see if there is any work to do (this prevents us from doing expensive atomic operations and also prevents us from continuously incrementing the counter and overflowing it)870uint num_vertices = (uint)mVertices.size();871if (ioContext.mNextCollisionVertex.load(memory_order_relaxed) < num_vertices)872{873// Fetch next batch of vertices to process874uint next_vertex = ioContext.mNextCollisionVertex.fetch_add(SoftBodyUpdateContext::cVertexCollisionBatch, memory_order_acquire);875if (next_vertex < num_vertices)876{877// Process collision planes878uint num_vertices_to_process = min(SoftBodyUpdateContext::cVertexCollisionBatch, num_vertices - next_vertex);879DetermineCollisionPlanes(next_vertex, num_vertices_to_process);880uint vertices_processed = ioContext.mNumCollisionVerticesProcessed.fetch_add(SoftBodyUpdateContext::cVertexCollisionBatch, memory_order_acq_rel) + num_vertices_to_process;881if (vertices_processed >= num_vertices)882{883// Determine next state884if (mCollidingSensors.empty())885StartFirstIteration(ioContext);886else887ioContext.mState.store(SoftBodyUpdateContext::EState::DetermineSensorCollisions, memory_order_release);888}889return EStatus::DidWork;890}891}892893return EStatus::NoWork;894}895896SoftBodyMotionProperties::EStatus SoftBodyMotionProperties::ParallelDetermineSensorCollisions(SoftBodyUpdateContext &ioContext)897{898// Do a relaxed read to see if there are more sensors to process899if (ioContext.mNextSensorIndex.load(memory_order_relaxed) < mNumSensors)900{901// Fetch next sensor to process902uint sensor_index = ioContext.mNextSensorIndex.fetch_add(1, memory_order_acquire);903if (sensor_index < mNumSensors)904{905// Process this sensor906DetermineSensorCollisions(mCollidingSensors[sensor_index]);907908// Determine next state909uint sensors_processed = ioContext.mNumSensorsProcessed.fetch_add(1, memory_order_acq_rel) + 1;910if (sensors_processed >= mNumSensors)911StartFirstIteration(ioContext);912return EStatus::DidWork;913}914}915916return EStatus::NoWork;917}918919void SoftBodyMotionProperties::ProcessGroup(const SoftBodyUpdateContext &ioContext, uint inGroupIndex)920{921// Determine start and end922SoftBodySharedSettings::UpdateGroup start { 0, 0, 0, 0, 0 };923const SoftBodySharedSettings::UpdateGroup &prev = inGroupIndex > 0? mSettings->mUpdateGroups[inGroupIndex - 1] : start;924const SoftBodySharedSettings::UpdateGroup ¤t = mSettings->mUpdateGroups[inGroupIndex];925926// Process volume constraints927ApplyVolumeConstraints(ioContext, prev.mVolumeEndIndex, current.mVolumeEndIndex);928929// Process bend constraints930ApplyDihedralBendConstraints(ioContext, prev.mDihedralBendEndIndex, current.mDihedralBendEndIndex);931932// Process skinned constraints933ApplySkinConstraints(ioContext, prev.mSkinnedEndIndex, current.mSkinnedEndIndex);934935// Process edges936ApplyEdgeConstraints(ioContext, prev.mEdgeEndIndex, current.mEdgeEndIndex);937938// Process LRA constraints939ApplyLRAConstraints(prev.mLRAEndIndex, current.mLRAEndIndex);940}941942SoftBodyMotionProperties::EStatus SoftBodyMotionProperties::ParallelApplyConstraints(SoftBodyUpdateContext &ioContext, const PhysicsSettings &inPhysicsSettings)943{944uint num_groups = (uint)mSettings->mUpdateGroups.size();945JPH_ASSERT(num_groups > 0, "SoftBodySharedSettings::Optimize should have been called!");946--num_groups; // Last group is the non-parallel group, we don't want to execute it in parallel947948// Do a relaxed read first to see if there is any work to do (this prevents us from doing expensive atomic operations and also prevents us from continuously incrementing the counter and overflowing it)949uint next_group = ioContext.mNextConstraintGroup.load(memory_order_relaxed);950if (next_group < num_groups || (num_groups == 0 && next_group == 0))951{952// Fetch the next group process953next_group = ioContext.mNextConstraintGroup.fetch_add(1, memory_order_acquire);954if (next_group < num_groups || (num_groups == 0 && next_group == 0))955{956uint num_groups_processed = 0;957if (num_groups > 0)958{959// Process this group960ProcessGroup(ioContext, next_group);961962// Increment total number of groups processed963num_groups_processed = ioContext.mNumConstraintGroupsProcessed.fetch_add(1, memory_order_acq_rel) + 1;964}965966if (num_groups_processed >= num_groups)967{968// Finish the iteration969JPH_PROFILE("FinishIteration");970971// Process non-parallel group972ProcessGroup(ioContext, num_groups);973974ApplyCollisionConstraintsAndUpdateVelocities(ioContext);975976uint iteration = ioContext.mNextIteration.fetch_add(1, memory_order_relaxed);977if (iteration < mNumIterations)978{979// Start a new iteration980StartNextIteration(ioContext);981982// Reset group logic983ioContext.mNumConstraintGroupsProcessed.store(0, memory_order_release);984ioContext.mNextConstraintGroup.store(0, memory_order_release);985}986else987{988// On final iteration we update the state989UpdateSoftBodyState(ioContext, inPhysicsSettings);990991ioContext.mState.store(SoftBodyUpdateContext::EState::Done, memory_order_release);992return EStatus::Done;993}994}995996return EStatus::DidWork;997}998}999return EStatus::NoWork;1000}10011002SoftBodyMotionProperties::EStatus SoftBodyMotionProperties::ParallelUpdate(SoftBodyUpdateContext &ioContext, const PhysicsSettings &inPhysicsSettings)1003{1004switch (ioContext.mState.load(memory_order_acquire))1005{1006case SoftBodyUpdateContext::EState::DetermineCollisionPlanes:1007return ParallelDetermineCollisionPlanes(ioContext);10081009case SoftBodyUpdateContext::EState::DetermineSensorCollisions:1010return ParallelDetermineSensorCollisions(ioContext);10111012case SoftBodyUpdateContext::EState::ApplyConstraints:1013return ParallelApplyConstraints(ioContext, inPhysicsSettings);10141015case SoftBodyUpdateContext::EState::Done:1016return EStatus::Done;10171018default:1019JPH_ASSERT(false);1020return EStatus::NoWork;1021}1022}10231024void SoftBodyMotionProperties::SkinVertices([[maybe_unused]] RMat44Arg inCenterOfMassTransform, const Mat44 *inJointMatrices, [[maybe_unused]] uint inNumJoints, bool inHardSkinAll, TempAllocator &ioTempAllocator)1025{1026// Calculate the skin matrices1027uint num_skin_matrices = uint(mSettings->mInvBindMatrices.size());1028uint skin_matrices_size = num_skin_matrices * sizeof(Mat44);1029Mat44 *skin_matrices = (Mat44 *)ioTempAllocator.Allocate(skin_matrices_size);1030JPH_SCOPE_EXIT([&ioTempAllocator, skin_matrices, skin_matrices_size]{ ioTempAllocator.Free(skin_matrices, skin_matrices_size); });1031const Mat44 *skin_matrices_end = skin_matrices + num_skin_matrices;1032const InvBind *inv_bind_matrix = mSettings->mInvBindMatrices.data();1033for (Mat44 *s = skin_matrices; s < skin_matrices_end; ++s, ++inv_bind_matrix)1034{1035JPH_ASSERT(inv_bind_matrix->mJointIndex < inNumJoints);1036*s = inJointMatrices[inv_bind_matrix->mJointIndex] * inv_bind_matrix->mInvBind;1037}10381039// Skin the vertices1040JPH_IF_DEBUG_RENDERER(mSkinStateTransform = inCenterOfMassTransform;)1041JPH_IF_ENABLE_ASSERTS(uint num_vertices = uint(mSettings->mVertices.size());)1042JPH_ASSERT(mSkinState.size() == num_vertices);1043const SoftBodySharedSettings::Vertex *in_vertices = mSettings->mVertices.data();1044for (const Skinned &s : mSettings->mSkinnedConstraints)1045{1046// Get bind pose1047JPH_ASSERT(s.mVertex < num_vertices);1048Vec3 bind_pos = Vec3::sLoadFloat3Unsafe(in_vertices[s.mVertex].mPosition);10491050// Skin vertex1051Vec3 pos = Vec3::sZero();1052for (const SkinWeight &w : s.mWeights)1053{1054// We assume that the first zero weight is the end of the list1055if (w.mWeight == 0.0f)1056break;10571058JPH_ASSERT(w.mInvBindIndex < num_skin_matrices);1059pos += w.mWeight * (skin_matrices[w.mInvBindIndex] * bind_pos);1060}1061SkinState &skin_state = mSkinState[s.mVertex];1062skin_state.mPreviousPosition = skin_state.mPosition;1063skin_state.mPosition = pos;1064}10651066// Calculate the normals1067for (const Skinned &s : mSettings->mSkinnedConstraints)1068{1069Vec3 normal = Vec3::sZero();1070uint32 num_faces = s.mNormalInfo >> 24;1071if (num_faces > 0)1072{1073// Calculate normal1074const uint32 *f = &mSettings->mSkinnedConstraintNormals[s.mNormalInfo & 0xffffff];1075const uint32 *f_end = f + num_faces;1076while (f < f_end)1077{1078const Face &face = mSettings->mFaces[*f];1079Vec3 v0 = mSkinState[face.mVertex[0]].mPosition;1080Vec3 v1 = mSkinState[face.mVertex[1]].mPosition;1081Vec3 v2 = mSkinState[face.mVertex[2]].mPosition;1082normal += (v1 - v0).Cross(v2 - v0).NormalizedOr(Vec3::sZero());1083++f;1084}1085normal = normal.NormalizedOr(Vec3::sZero());1086}1087mSkinState[s.mVertex].mNormal = normal;1088}10891090if (inHardSkinAll)1091{1092// Hard skin all vertices and reset their velocities1093for (const Skinned &s : mSettings->mSkinnedConstraints)1094{1095Vertex &vertex = mVertices[s.mVertex];1096SkinState &skin_state = mSkinState[s.mVertex];1097skin_state.mPreviousPosition = skin_state.mPosition;1098vertex.mPosition = skin_state.mPosition;1099vertex.mVelocity = Vec3::sZero();1100}1101}1102else if (!mEnableSkinConstraints)1103{1104// Hard skin only the kinematic vertices as we will not solve the skin constraints later1105for (const Skinned &s : mSettings->mSkinnedConstraints)1106if (s.mMaxDistance == 0.0f)1107{1108Vertex &vertex = mVertices[s.mVertex];1109vertex.mPosition = mSkinState[s.mVertex].mPosition;1110}1111}11121113// Indicate that the previous positions are valid for the coming update1114mSkinStatePreviousPositionValid = true;1115}11161117void SoftBodyMotionProperties::CustomUpdate(float inDeltaTime, Body &ioSoftBody, PhysicsSystem &inSystem)1118{1119JPH_PROFILE_FUNCTION();11201121// Create update context1122SoftBodyUpdateContext context;1123InitializeUpdateContext(inDeltaTime, ioSoftBody, inSystem, context);11241125// Determine bodies we're colliding with1126DetermineCollidingShapes(context, inSystem, inSystem.GetBodyLockInterface());11271128// Call the internal update until it finishes1129EStatus status;1130const PhysicsSettings &settings = inSystem.GetPhysicsSettings();1131while ((status = ParallelUpdate(context, settings)) == EStatus::DidWork)1132continue;1133JPH_ASSERT(status == EStatus::Done);11341135// Update the state of the bodies we've collided with1136UpdateRigidBodyVelocities(context, inSystem.GetBodyInterface());11371138// Update position of the soft body1139if (mUpdatePosition)1140inSystem.GetBodyInterface().SetPosition(ioSoftBody.GetID(), ioSoftBody.GetPosition() + context.mDeltaPosition, EActivation::DontActivate);1141}11421143#ifdef JPH_DEBUG_RENDERER11441145void SoftBodyMotionProperties::DrawVertices(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const1146{1147for (const Vertex &v : mVertices)1148inRenderer->DrawMarker(inCenterOfMassTransform * v.mPosition, v.mInvMass > 0.0f? Color::sGreen : Color::sRed, 0.05f);1149}11501151void SoftBodyMotionProperties::DrawVertexVelocities(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const1152{1153for (const Vertex &v : mVertices)1154inRenderer->DrawArrow(inCenterOfMassTransform * v.mPosition, inCenterOfMassTransform * (v.mPosition + v.mVelocity), Color::sYellow, 0.01f);1155}11561157template <typename GetEndIndex, typename DrawConstraint>1158inline void SoftBodyMotionProperties::DrawConstraints(ESoftBodyConstraintColor inConstraintColor, const GetEndIndex &inGetEndIndex, const DrawConstraint &inDrawConstraint, ColorArg inBaseColor) const1159{1160uint start = 0;1161for (uint i = 0; i < (uint)mSettings->mUpdateGroups.size(); ++i)1162{1163uint end = inGetEndIndex(mSettings->mUpdateGroups[i]);11641165Color base_color;1166if (inConstraintColor != ESoftBodyConstraintColor::ConstraintType)1167base_color = Color::sGetDistinctColor((uint)mSettings->mUpdateGroups.size() - i - 1); // Ensure that color 0 is always the last group1168else1169base_color = inBaseColor;11701171for (uint idx = start; idx < end; ++idx)1172{1173Color color = inConstraintColor == ESoftBodyConstraintColor::ConstraintOrder? base_color * (float(idx - start) / (end - start)) : base_color;1174inDrawConstraint(idx, color);1175}11761177start = end;1178}1179}11801181void SoftBodyMotionProperties::DrawEdgeConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const1182{1183DrawConstraints(inConstraintColor,1184[](const SoftBodySharedSettings::UpdateGroup &inGroup) {1185return inGroup.mEdgeEndIndex;1186},1187[this, inRenderer, &inCenterOfMassTransform](uint inIndex, ColorArg inColor) {1188const Edge &e = mSettings->mEdgeConstraints[inIndex];1189inRenderer->DrawLine(inCenterOfMassTransform * mVertices[e.mVertex[0]].mPosition, inCenterOfMassTransform * mVertices[e.mVertex[1]].mPosition, inColor);1190},1191Color::sWhite);1192}11931194void SoftBodyMotionProperties::DrawBendConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const1195{1196DrawConstraints(inConstraintColor,1197[](const SoftBodySharedSettings::UpdateGroup &inGroup) {1198return inGroup.mDihedralBendEndIndex;1199},1200[this, inRenderer, &inCenterOfMassTransform](uint inIndex, ColorArg inColor) {1201const DihedralBend &b = mSettings->mDihedralBendConstraints[inIndex];12021203RVec3 x0 = inCenterOfMassTransform * mVertices[b.mVertex[0]].mPosition;1204RVec3 x1 = inCenterOfMassTransform * mVertices[b.mVertex[1]].mPosition;1205RVec3 x2 = inCenterOfMassTransform * mVertices[b.mVertex[2]].mPosition;1206RVec3 x3 = inCenterOfMassTransform * mVertices[b.mVertex[3]].mPosition;1207RVec3 c_edge = 0.5_r * (x0 + x1);1208RVec3 c0 = (x0 + x1 + x2) / 3.0_r;1209RVec3 c1 = (x0 + x1 + x3) / 3.0_r;12101211inRenderer->DrawArrow(0.9_r * x0 + 0.1_r * x1, 0.1_r * x0 + 0.9_r * x1, inColor, 0.01f);1212inRenderer->DrawLine(c_edge, 0.1_r * c_edge + 0.9_r * c0, inColor);1213inRenderer->DrawLine(c_edge, 0.1_r * c_edge + 0.9_r * c1, inColor);1214},1215Color::sGreen);1216}12171218void SoftBodyMotionProperties::DrawVolumeConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const1219{1220DrawConstraints(inConstraintColor,1221[](const SoftBodySharedSettings::UpdateGroup &inGroup) {1222return inGroup.mVolumeEndIndex;1223},1224[this, inRenderer, &inCenterOfMassTransform](uint inIndex, ColorArg inColor) {1225const Volume &v = mSettings->mVolumeConstraints[inIndex];12261227RVec3 x1 = inCenterOfMassTransform * mVertices[v.mVertex[0]].mPosition;1228RVec3 x2 = inCenterOfMassTransform * mVertices[v.mVertex[1]].mPosition;1229RVec3 x3 = inCenterOfMassTransform * mVertices[v.mVertex[2]].mPosition;1230RVec3 x4 = inCenterOfMassTransform * mVertices[v.mVertex[3]].mPosition;12311232inRenderer->DrawTriangle(x1, x3, x2, inColor, DebugRenderer::ECastShadow::On);1233inRenderer->DrawTriangle(x2, x3, x4, inColor, DebugRenderer::ECastShadow::On);1234inRenderer->DrawTriangle(x1, x4, x3, inColor, DebugRenderer::ECastShadow::On);1235inRenderer->DrawTriangle(x1, x2, x4, inColor, DebugRenderer::ECastShadow::On);1236},1237Color::sYellow);1238}12391240void SoftBodyMotionProperties::DrawSkinConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const1241{1242DrawConstraints(inConstraintColor,1243[](const SoftBodySharedSettings::UpdateGroup &inGroup) {1244return inGroup.mSkinnedEndIndex;1245},1246[this, inRenderer, &inCenterOfMassTransform](uint inIndex, ColorArg inColor) {1247const Skinned &s = mSettings->mSkinnedConstraints[inIndex];1248const SkinState &skin_state = mSkinState[s.mVertex];1249inRenderer->DrawArrow(mSkinStateTransform * skin_state.mPosition, mSkinStateTransform * (skin_state.mPosition + 0.1f * skin_state.mNormal), inColor, 0.01f);1250inRenderer->DrawLine(mSkinStateTransform * skin_state.mPosition, inCenterOfMassTransform * mVertices[s.mVertex].mPosition, Color::sBlue);1251},1252Color::sOrange);1253}12541255void SoftBodyMotionProperties::DrawLRAConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const1256{1257DrawConstraints(inConstraintColor,1258[](const SoftBodySharedSettings::UpdateGroup &inGroup) {1259return inGroup.mLRAEndIndex;1260},1261[this, inRenderer, &inCenterOfMassTransform](uint inIndex, ColorArg inColor) {1262const LRA &l = mSettings->mLRAConstraints[inIndex];1263inRenderer->DrawLine(inCenterOfMassTransform * mVertices[l.mVertex[0]].mPosition, inCenterOfMassTransform * mVertices[l.mVertex[1]].mPosition, inColor);1264},1265Color::sGrey);1266}12671268void SoftBodyMotionProperties::DrawPredictedBounds(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const1269{1270inRenderer->DrawWireBox(inCenterOfMassTransform, mLocalPredictedBounds, Color::sRed);1271}12721273#endif // JPH_DEBUG_RENDERER12741275void SoftBodyMotionProperties::SaveState(StateRecorder &inStream) const1276{1277MotionProperties::SaveState(inStream);12781279for (const Vertex &v : mVertices)1280{1281inStream.Write(v.mPreviousPosition);1282inStream.Write(v.mPosition);1283inStream.Write(v.mVelocity);1284}12851286for (const SkinState &s : mSkinState)1287{1288inStream.Write(s.mPreviousPosition);1289inStream.Write(s.mPosition);1290inStream.Write(s.mNormal);1291}12921293inStream.Write(mLocalBounds.mMin);1294inStream.Write(mLocalBounds.mMax);1295inStream.Write(mLocalPredictedBounds.mMin);1296inStream.Write(mLocalPredictedBounds.mMax);1297}12981299void SoftBodyMotionProperties::RestoreState(StateRecorder &inStream)1300{1301MotionProperties::RestoreState(inStream);13021303for (Vertex &v : mVertices)1304{1305inStream.Read(v.mPreviousPosition);1306inStream.Read(v.mPosition);1307inStream.Read(v.mVelocity);1308}13091310for (SkinState &s : mSkinState)1311{1312inStream.Read(s.mPreviousPosition);1313inStream.Read(s.mPosition);1314inStream.Read(s.mNormal);1315}13161317inStream.Read(mLocalBounds.mMin);1318inStream.Read(mLocalBounds.mMax);1319inStream.Read(mLocalPredictedBounds.mMin);1320inStream.Read(mLocalPredictedBounds.mMax);1321}13221323JPH_NAMESPACE_END132413251326