initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
JPH_NAMESPACE_BEGIN
class Body;
class SoftBodyManifold;
/// Return value for the OnSoftBodyContactValidate callback. Determines if the contact will be processed or not.
enum class SoftBodyValidateResult
{
AcceptContact, ///< Accept this contact
RejectContact, ///< Reject this contact
};
/// Contact settings for a soft body contact.
/// The values are filled in with their defaults by the system so the callback doesn't need to modify anything, but it can if it wants to.
class SoftBodyContactSettings
{
public:
float mInvMassScale1 = 1.0f; ///< Scale factor for the inverse mass of the soft body (0 = infinite mass, 1 = use original mass, 2 = body has half the mass). For the same contact pair, you should strive to keep the value the same over time.
float mInvMassScale2 = 1.0f; ///< Scale factor for the inverse mass of the other body (0 = infinite mass, 1 = use original mass, 2 = body has half the mass). For the same contact pair, you should strive to keep the value the same over time.
float mInvInertiaScale2 = 1.0f; ///< Scale factor for the inverse inertia of the other body (usually same as mInvMassScale2)
bool mIsSensor; ///< If the contact should be treated as a sensor vs body contact (no collision response)
};
/// A listener class that receives collision contact events for soft bodies against rigid bodies.
/// It can be registered with the PhysicsSystem.
class SoftBodyContactListener
{
public:
/// Ensure virtual destructor
virtual ~SoftBodyContactListener() = default;
/// Called whenever the soft body's aabox overlaps with another body's aabox (so receiving this callback doesn't tell if any of the vertices will collide).
/// This callback can be used to change the behavior of the collision response for all vertices in the soft body or to completely reject the contact.
/// Note that this callback is called when all bodies are locked, so don't use any locking functions!
/// @param inSoftBody The soft body that collided. It is safe to access this as the soft body is only updated on the current thread.
/// @param inOtherBody The other body that collided. Note that accessing the position/orientation/velocity of inOtherBody may result in a race condition as other threads may be modifying the body at the same time.
/// @param ioSettings The settings for all contact points that are generated by this collision.
/// @return Whether the contact should be processed or not.
virtual SoftBodyValidateResult OnSoftBodyContactValidate([[maybe_unused]] const Body &inSoftBody, [[maybe_unused]] const Body &inOtherBody, [[maybe_unused]] SoftBodyContactSettings &ioSettings) { return SoftBodyValidateResult::AcceptContact; }
/// Called after all contact points for a soft body have been handled. You only receive one callback per body pair per simulation step and can use inManifold to iterate through all contacts.
/// Note that this callback is called when all bodies are locked, so don't use any locking functions!
/// You will receive a single callback for a soft body per simulation step for performance reasons, this callback will apply to all vertices in the soft body.
/// @param inSoftBody The soft body that collided. It is safe to access this as the soft body is only updated on the current thread.
/// @param inManifold The manifold that describes the contact surface between the two bodies. Other bodies may be modified by other threads during this callback.
virtual void OnSoftBodyContactAdded([[maybe_unused]] const Body &inSoftBody, const SoftBodyManifold &inManifold) { /* Do nothing */ }
};
JPH_NAMESPACE_END

View File

@@ -0,0 +1,122 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <Jolt/Jolt.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <Jolt/ObjectStream/TypeDeclarations.h>
#include <Jolt/Core/StreamIn.h>
#include <Jolt/Core/StreamOut.h>
JPH_NAMESPACE_BEGIN
JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodyCreationSettings)
{
JPH_ADD_ATTRIBUTE(SoftBodyCreationSettings, mSettings)
JPH_ADD_ATTRIBUTE(SoftBodyCreationSettings, mPosition)
JPH_ADD_ATTRIBUTE(SoftBodyCreationSettings, mRotation)
JPH_ADD_ATTRIBUTE(SoftBodyCreationSettings, mUserData)
JPH_ADD_ENUM_ATTRIBUTE(SoftBodyCreationSettings, mObjectLayer)
JPH_ADD_ATTRIBUTE(SoftBodyCreationSettings, mCollisionGroup)
JPH_ADD_ATTRIBUTE(SoftBodyCreationSettings, mNumIterations)
JPH_ADD_ATTRIBUTE(SoftBodyCreationSettings, mLinearDamping)
JPH_ADD_ATTRIBUTE(SoftBodyCreationSettings, mMaxLinearVelocity)
JPH_ADD_ATTRIBUTE(SoftBodyCreationSettings, mRestitution)
JPH_ADD_ATTRIBUTE(SoftBodyCreationSettings, mFriction)
JPH_ADD_ATTRIBUTE(SoftBodyCreationSettings, mPressure)
JPH_ADD_ATTRIBUTE(SoftBodyCreationSettings, mGravityFactor)
JPH_ADD_ATTRIBUTE(SoftBodyCreationSettings, mUpdatePosition)
JPH_ADD_ATTRIBUTE(SoftBodyCreationSettings, mMakeRotationIdentity)
JPH_ADD_ATTRIBUTE(SoftBodyCreationSettings, mAllowSleeping)
}
void SoftBodyCreationSettings::SaveBinaryState(StreamOut &inStream) const
{
inStream.Write(mPosition);
inStream.Write(mRotation);
inStream.Write(mUserData);
inStream.Write(mObjectLayer);
mCollisionGroup.SaveBinaryState(inStream);
inStream.Write(mNumIterations);
inStream.Write(mLinearDamping);
inStream.Write(mMaxLinearVelocity);
inStream.Write(mRestitution);
inStream.Write(mFriction);
inStream.Write(mPressure);
inStream.Write(mGravityFactor);
inStream.Write(mUpdatePosition);
inStream.Write(mMakeRotationIdentity);
inStream.Write(mAllowSleeping);
}
void SoftBodyCreationSettings::RestoreBinaryState(StreamIn &inStream)
{
inStream.Read(mPosition);
inStream.Read(mRotation);
inStream.Read(mUserData);
inStream.Read(mObjectLayer);
mCollisionGroup.RestoreBinaryState(inStream);
inStream.Read(mNumIterations);
inStream.Read(mLinearDamping);
inStream.Read(mMaxLinearVelocity);
inStream.Read(mRestitution);
inStream.Read(mFriction);
inStream.Read(mPressure);
inStream.Read(mGravityFactor);
inStream.Read(mUpdatePosition);
inStream.Read(mMakeRotationIdentity);
inStream.Read(mAllowSleeping);
}
void SoftBodyCreationSettings::SaveWithChildren(StreamOut &inStream, SharedSettingsToIDMap *ioSharedSettingsMap, MaterialToIDMap *ioMaterialMap, GroupFilterToIDMap *ioGroupFilterMap) const
{
// Save creation settings
SaveBinaryState(inStream);
// Save shared settings
if (ioSharedSettingsMap != nullptr && ioMaterialMap != nullptr)
mSettings->SaveWithMaterials(inStream, *ioSharedSettingsMap, *ioMaterialMap);
else
inStream.Write(~uint32(0));
// Save group filter
StreamUtils::SaveObjectReference(inStream, mCollisionGroup.GetGroupFilter(), ioGroupFilterMap);
}
SoftBodyCreationSettings::SBCSResult SoftBodyCreationSettings::sRestoreWithChildren(StreamIn &inStream, IDToSharedSettingsMap &ioSharedSettingsMap, IDToMaterialMap &ioMaterialMap, IDToGroupFilterMap &ioGroupFilterMap)
{
SBCSResult result;
// Read creation settings
SoftBodyCreationSettings settings;
settings.RestoreBinaryState(inStream);
if (inStream.IsEOF() || inStream.IsFailed())
{
result.SetError("Error reading body creation settings");
return result;
}
// Read shared settings
SoftBodySharedSettings::SettingsResult settings_result = SoftBodySharedSettings::sRestoreWithMaterials(inStream, ioSharedSettingsMap, ioMaterialMap);
if (settings_result.HasError())
{
result.SetError(settings_result.GetError());
return result;
}
settings.mSettings = settings_result.Get();
// Read group filter
Result gfresult = StreamUtils::RestoreObjectReference(inStream, ioGroupFilterMap);
if (gfresult.HasError())
{
result.SetError(gfresult.GetError());
return result;
}
settings.mCollisionGroup.SetGroupFilter(gfresult.Get());
result.Set(settings);
return result;
}
JPH_NAMESPACE_END

View File

@@ -0,0 +1,73 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Physics/SoftBody/SoftBodySharedSettings.h>
#include <Jolt/Physics/Collision/ObjectLayer.h>
#include <Jolt/Physics/Collision/CollisionGroup.h>
#include <Jolt/ObjectStream/SerializableObject.h>
#include <Jolt/Core/StreamUtils.h>
JPH_NAMESPACE_BEGIN
/// This class contains the information needed to create a soft body object
/// Note: Soft bodies are still in development and come with several caveats. Read the Architecture and API documentation for more information!
class JPH_EXPORT SoftBodyCreationSettings
{
JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(JPH_EXPORT, SoftBodyCreationSettings)
public:
/// Constructor
SoftBodyCreationSettings() = default;
SoftBodyCreationSettings(const SoftBodySharedSettings *inSettings, RVec3Arg inPosition, QuatArg inRotation, ObjectLayer inObjectLayer) : mSettings(inSettings), mPosition(inPosition), mRotation(inRotation), mObjectLayer(inObjectLayer) { }
/// Saves the state of this object in binary form to inStream. Doesn't store the shared settings nor the group filter.
void SaveBinaryState(StreamOut &inStream) const;
/// Restore the state of this object from inStream. Doesn't restore the shared settings nor the group filter.
void RestoreBinaryState(StreamIn &inStream);
using GroupFilterToIDMap = StreamUtils::ObjectToIDMap<GroupFilter>;
using IDToGroupFilterMap = StreamUtils::IDToObjectMap<GroupFilter>;
using SharedSettingsToIDMap = SoftBodySharedSettings::SharedSettingsToIDMap;
using IDToSharedSettingsMap = SoftBodySharedSettings::IDToSharedSettingsMap;
using MaterialToIDMap = StreamUtils::ObjectToIDMap<PhysicsMaterial>;
using IDToMaterialMap = StreamUtils::IDToObjectMap<PhysicsMaterial>;
/// Save this body creation settings, its shared settings and group filter. Pass in an empty map in ioSharedSettingsMap / ioMaterialMap / ioGroupFilterMap or reuse the same map while saving multiple shapes to the same stream in order to avoid writing duplicates.
/// Pass nullptr to ioSharedSettingsMap and ioMaterial map to skip saving shared settings and materials
/// Pass nullptr to ioGroupFilterMap to skip saving group filters
void SaveWithChildren(StreamOut &inStream, SharedSettingsToIDMap *ioSharedSettingsMap, MaterialToIDMap *ioMaterialMap, GroupFilterToIDMap *ioGroupFilterMap) const;
using SBCSResult = Result<SoftBodyCreationSettings>;
/// Restore a shape, all its children and materials. Pass in an empty map in ioSharedSettingsMap / ioMaterialMap / ioGroupFilterMap or reuse the same map while reading multiple shapes from the same stream in order to restore duplicates.
static SBCSResult sRestoreWithChildren(StreamIn &inStream, IDToSharedSettingsMap &ioSharedSettingsMap, IDToMaterialMap &ioMaterialMap, IDToGroupFilterMap &ioGroupFilterMap);
RefConst<SoftBodySharedSettings> mSettings; ///< Defines the configuration of this soft body
RVec3 mPosition { RVec3::sZero() }; ///< Initial position of the soft body
Quat mRotation { Quat::sIdentity() }; ///< Initial rotation of the soft body
/// User data value (can be used by application)
uint64 mUserData = 0;
///@name Collision settings
ObjectLayer mObjectLayer = 0; ///< The collision layer this body belongs to (determines if two objects can collide)
CollisionGroup mCollisionGroup; ///< The collision group this body belongs to (determines if two objects can collide)
uint32 mNumIterations = 5; ///< Number of solver iterations
float mLinearDamping = 0.1f; ///< Linear damping: dv/dt = -mLinearDamping * v
float mMaxLinearVelocity = 500.0f; ///< Maximum linear velocity that a vertex can reach (m/s)
float mRestitution = 0.0f; ///< Restitution when colliding
float mFriction = 0.2f; ///< Friction coefficient when colliding
float mPressure = 0.0f; ///< n * R * T, amount of substance * ideal gas constant * absolute temperature, see https://en.wikipedia.org/wiki/Pressure
float mGravityFactor = 1.0f; ///< Value to multiply gravity with for this body
bool mUpdatePosition = true; ///< Update the position of the body while simulating (set to false for something that is attached to the static world)
bool mMakeRotationIdentity = true; ///< Bake specified mRotation in the vertices and set the body rotation to identity (simulation is slightly more accurate if the rotation of a soft body is kept to identity)
bool mAllowSleeping = true; ///< If this body can go to sleep or not
};
JPH_NAMESPACE_END

View File

@@ -0,0 +1,74 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Physics/SoftBody/SoftBodyMotionProperties.h>
JPH_NAMESPACE_BEGIN
/// An interface to query which vertices of a soft body are colliding with other bodies
class SoftBodyManifold
{
public:
/// Get the vertices of the soft body for iterating
const Array<SoftBodyVertex> & GetVertices() const { return mVertices; }
/// Check if a vertex has collided with something in this update
JPH_INLINE bool HasContact(const SoftBodyVertex &inVertex) const
{
return inVertex.mHasContact;
}
/// Get the local space contact point (multiply by GetCenterOfMassTransform() of the soft body to get world space)
JPH_INLINE Vec3 GetLocalContactPoint(const SoftBodyVertex &inVertex) const
{
return inVertex.mPosition - inVertex.mCollisionPlane.SignedDistance(inVertex.mPosition) * inVertex.mCollisionPlane.GetNormal();
}
/// Get the contact normal for the vertex (assumes there is a contact).
JPH_INLINE Vec3 GetContactNormal(const SoftBodyVertex &inVertex) const
{
return -inVertex.mCollisionPlane.GetNormal();
}
/// Get the body with which the vertex has collided in this update
JPH_INLINE BodyID GetContactBodyID(const SoftBodyVertex &inVertex) const
{
return inVertex.mHasContact? mCollidingShapes[inVertex.mCollidingShapeIndex].mBodyID : BodyID();
}
/// Get the number of sensors that are in contact with the soft body
JPH_INLINE uint GetNumSensorContacts() const
{
return (uint)mCollidingSensors.size();
}
/// Get the i-th sensor that is in contact with the soft body
JPH_INLINE BodyID GetSensorContactBodyID(uint inIndex) const
{
return mCollidingSensors[inIndex].mBodyID;
}
private:
/// Allow SoftBodyMotionProperties to construct us
friend class SoftBodyMotionProperties;
/// Constructor
explicit SoftBodyManifold(const SoftBodyMotionProperties *inMotionProperties) :
mVertices(inMotionProperties->mVertices),
mCollidingShapes(inMotionProperties->mCollidingShapes),
mCollidingSensors(inMotionProperties->mCollidingSensors)
{
}
using CollidingShape = SoftBodyMotionProperties::CollidingShape;
using CollidingSensor = SoftBodyMotionProperties::CollidingSensor;
const Array<SoftBodyVertex> & mVertices;
const Array<CollidingShape> & mCollidingShapes;
const Array<CollidingSensor> & mCollidingSensors;
};
JPH_NAMESPACE_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,298 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Geometry/AABox.h>
#include <Jolt/Physics/Body/BodyID.h>
#include <Jolt/Physics/Body/MotionProperties.h>
#include <Jolt/Physics/Collision/TransformedShape.h>
#include <Jolt/Physics/SoftBody/SoftBodySharedSettings.h>
#include <Jolt/Physics/SoftBody/SoftBodyVertex.h>
#include <Jolt/Physics/SoftBody/SoftBodyUpdateContext.h>
JPH_NAMESPACE_BEGIN
class PhysicsSystem;
class BodyInterface;
class BodyLockInterface;
struct PhysicsSettings;
class Body;
class Shape;
class SoftBodyCreationSettings;
class TempAllocator;
#ifdef JPH_DEBUG_RENDERER
class DebugRenderer;
enum class ESoftBodyConstraintColor;
#endif // JPH_DEBUG_RENDERER
/// This class contains the runtime information of a soft body.
//
// Based on: XPBD, Extended Position Based Dynamics, Matthias Muller, Ten Minute Physics
// See: https://matthias-research.github.io/pages/tenMinutePhysics/09-xpbd.pdf
class JPH_EXPORT SoftBodyMotionProperties : public MotionProperties
{
public:
using Vertex = SoftBodyVertex;
using Edge = SoftBodySharedSettings::Edge;
using Face = SoftBodySharedSettings::Face;
using DihedralBend = SoftBodySharedSettings::DihedralBend;
using Volume = SoftBodySharedSettings::Volume;
using InvBind = SoftBodySharedSettings::InvBind;
using SkinWeight = SoftBodySharedSettings::SkinWeight;
using Skinned = SoftBodySharedSettings::Skinned;
using LRA = SoftBodySharedSettings::LRA;
/// Initialize the soft body motion properties
void Initialize(const SoftBodyCreationSettings &inSettings);
/// Get the shared settings of the soft body
const SoftBodySharedSettings * GetSettings() const { return mSettings; }
/// Get the vertices of the soft body
const Array<Vertex> & GetVertices() const { return mVertices; }
Array<Vertex> & GetVertices() { return mVertices; }
/// Access an individual vertex
const Vertex & GetVertex(uint inIndex) const { return mVertices[inIndex]; }
Vertex & GetVertex(uint inIndex) { return mVertices[inIndex]; }
/// Get the materials of the soft body
const PhysicsMaterialList & GetMaterials() const { return mSettings->mMaterials; }
/// Get the faces of the soft body
const Array<Face> & GetFaces() const { return mSettings->mFaces; }
/// Access to an individual face
const Face & GetFace(uint inIndex) const { return mSettings->mFaces[inIndex]; }
/// Get the number of solver iterations
uint32 GetNumIterations() const { return mNumIterations; }
void SetNumIterations(uint32 inNumIterations) { mNumIterations = inNumIterations; }
/// Get the pressure of the soft body
float GetPressure() const { return mPressure; }
void SetPressure(float inPressure) { mPressure = inPressure; }
/// Update the position of the body while simulating (set to false for something that is attached to the static world)
bool GetUpdatePosition() const { return mUpdatePosition; }
void SetUpdatePosition(bool inUpdatePosition) { mUpdatePosition = inUpdatePosition; }
/// Global setting to turn on/off skin constraints
bool GetEnableSkinConstraints() const { return mEnableSkinConstraints; }
void SetEnableSkinConstraints(bool inEnableSkinConstraints) { mEnableSkinConstraints = inEnableSkinConstraints; }
/// Multiplier applied to Skinned::mMaxDistance to allow tightening or loosening of the skin constraints. 0 to hard skin all vertices.
float GetSkinnedMaxDistanceMultiplier() const { return mSkinnedMaxDistanceMultiplier; }
void SetSkinnedMaxDistanceMultiplier(float inSkinnedMaxDistanceMultiplier) { mSkinnedMaxDistanceMultiplier = inSkinnedMaxDistanceMultiplier; }
/// Get local bounding box
const AABox & GetLocalBounds() const { return mLocalBounds; }
/// Get the volume of the soft body. Note can become negative if the shape is inside out!
float GetVolume() const { return GetVolumeTimesSix() / 6.0f; }
/// Calculate the total mass and inertia of this body based on the current state of the vertices
void CalculateMassAndInertia();
#ifdef JPH_DEBUG_RENDERER
/// Draw the state of a soft body
void DrawVertices(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const;
void DrawVertexVelocities(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const;
void DrawEdgeConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const;
void DrawBendConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const;
void DrawVolumeConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const;
void DrawSkinConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const;
void DrawLRAConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const;
void DrawPredictedBounds(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const;
#endif // JPH_DEBUG_RENDERER
/// Saving state for replay
void SaveState(StateRecorder &inStream) const;
/// Restoring state for replay
void RestoreState(StateRecorder &inStream);
/// Skin vertices to supplied joints, information is used by the skinned constraints.
/// @param inCenterOfMassTransform Value of Body::GetCenterOfMassTransform().
/// @param inJointMatrices The joint matrices must be expressed relative to inCenterOfMassTransform.
/// @param inNumJoints Indicates how large the inJointMatrices array is (used only for validating out of bounds).
/// @param inHardSkinAll Can be used to position all vertices on the skinned vertices and can be used to hard reset the soft body.
/// @param ioTempAllocator Allocator.
void SkinVertices(RMat44Arg inCenterOfMassTransform, const Mat44 *inJointMatrices, uint inNumJoints, bool inHardSkinAll, TempAllocator &ioTempAllocator);
/// This function allows you to update the soft body immediately without going through the PhysicsSystem.
/// This is useful if the soft body is teleported and needs to 'settle' or it can be used if a the soft body
/// is not added to the PhysicsSystem and needs to be updated manually. One reason for not adding it to the
/// PhysicsSystem is that you might want to update a soft body immediately after updating an animated object
/// that has the soft body attached to it. If the soft body is added to the PhysicsSystem it will be updated
/// by it, so calling this function will effectively update it twice. Note that when you use this function,
/// only the current thread will be used, whereas if you update through the PhysicsSystem, multiple threads may
/// be used.
/// Note that this will bypass any sleep checks. Since the dynamic objects that the soft body touches
/// will not move during this call, there can be simulation artifacts if you call this function multiple times
/// without running the physics simulation step.
void CustomUpdate(float inDeltaTime, Body &ioSoftBody, PhysicsSystem &inSystem);
////////////////////////////////////////////////////////////
// FUNCTIONS BELOW THIS LINE ARE FOR INTERNAL USE ONLY
////////////////////////////////////////////////////////////
/// Initialize the update context. Not part of the public API.
void InitializeUpdateContext(float inDeltaTime, Body &inSoftBody, const PhysicsSystem &inSystem, SoftBodyUpdateContext &ioContext);
/// Do a broad phase check and collect all bodies that can possibly collide with this soft body. Not part of the public API.
void DetermineCollidingShapes(const SoftBodyUpdateContext &inContext, const PhysicsSystem &inSystem, const BodyLockInterface &inBodyLockInterface);
/// Return code for ParallelUpdate
enum class EStatus
{
NoWork = 1 << 0, ///< No work was done because other threads were still working on a batch that cannot run concurrently
DidWork = 1 << 1, ///< Work was done to progress the update
Done = 1 << 2, ///< All work is done
};
/// Update the soft body, will process a batch of work. Not part of the public API.
EStatus ParallelUpdate(SoftBodyUpdateContext &ioContext, const PhysicsSettings &inPhysicsSettings);
/// Update the velocities of all rigid bodies that we collided with. Not part of the public API.
void UpdateRigidBodyVelocities(const SoftBodyUpdateContext &inContext, BodyInterface &inBodyInterface);
private:
// SoftBodyManifold needs to have access to CollidingShape
friend class SoftBodyManifold;
// Information about a leaf shape that we're colliding with
struct LeafShape
{
LeafShape() = default;
LeafShape(Mat44Arg inTransform, Vec3Arg inScale, const Shape *inShape) : mTransform(inTransform), mScale(inScale), mShape(inShape) { }
Mat44 mTransform; ///< Transform of the shape relative to the soft body
Vec3 mScale; ///< Scale of the shape
RefConst<Shape> mShape; ///< Shape
};
// Collect information about the colliding bodies
struct CollidingShape
{
/// Get the velocity of a point on this body
Vec3 GetPointVelocity(Vec3Arg inPointRelativeToCOM) const
{
return mLinearVelocity + mAngularVelocity.Cross(inPointRelativeToCOM);
}
Mat44 mCenterOfMassTransform; ///< Transform of the body relative to the soft body
Array<LeafShape> mShapes; ///< Leaf shapes of the body we hit
BodyID mBodyID; ///< Body ID of the body we hit
EMotionType mMotionType; ///< Motion type of the body we hit
float mInvMass; ///< Inverse mass of the body we hit
float mFriction; ///< Combined friction of the two bodies
float mRestitution; ///< Combined restitution of the two bodies
float mSoftBodyInvMassScale; ///< Scale factor for the inverse mass of the soft body vertices
bool mUpdateVelocities; ///< If the linear/angular velocity changed and the body needs to be updated
Mat44 mInvInertia; ///< Inverse inertia in local space to the soft body
Vec3 mLinearVelocity; ///< Linear velocity of the body in local space to the soft body
Vec3 mAngularVelocity; ///< Angular velocity of the body in local space to the soft body
Vec3 mOriginalLinearVelocity; ///< Linear velocity of the body in local space to the soft body at start
Vec3 mOriginalAngularVelocity; ///< Angular velocity of the body in local space to the soft body at start
};
// Collect information about the colliding sensors
struct CollidingSensor
{
Mat44 mCenterOfMassTransform; ///< Transform of the body relative to the soft body
Array<LeafShape> mShapes; ///< Leaf shapes of the body we hit
BodyID mBodyID; ///< Body ID of the body we hit
bool mHasContact; ///< If the sensor collided with the soft body
};
// Information about the state of all skinned vertices
struct SkinState
{
Vec3 mPreviousPosition = Vec3::sZero(); ///< Previous position of the skinned vertex, used to interpolate between the previous and current position
Vec3 mPosition = Vec3::sNaN(); ///< Current position of the skinned vertex
Vec3 mNormal = Vec3::sNaN(); ///< Normal of the skinned vertex
};
/// Do a narrow phase check and determine the closest feature that we can collide with
void DetermineCollisionPlanes(uint inVertexStart, uint inNumVertices);
/// Do a narrow phase check between a single sensor and the soft body
void DetermineSensorCollisions(CollidingSensor &ioSensor);
/// Apply pressure force and update the vertex velocities
void ApplyPressure(const SoftBodyUpdateContext &inContext);
/// Integrate the positions of all vertices by 1 sub step
void IntegratePositions(const SoftBodyUpdateContext &inContext);
/// Enforce all bend constraints
void ApplyDihedralBendConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex);
/// Enforce all volume constraints
void ApplyVolumeConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex);
/// Enforce all skin constraints
void ApplySkinConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex);
/// Enforce all edge constraints
void ApplyEdgeConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex);
/// Enforce all LRA constraints
void ApplyLRAConstraints(uint inStartIndex, uint inEndIndex);
/// Enforce all collision constraints & update all velocities according the XPBD algorithm
void ApplyCollisionConstraintsAndUpdateVelocities(const SoftBodyUpdateContext &inContext);
/// Update the state of the soft body (position, velocity, bounds)
void UpdateSoftBodyState(SoftBodyUpdateContext &ioContext, const PhysicsSettings &inPhysicsSettings);
/// Start the first solver iteration
void StartFirstIteration(SoftBodyUpdateContext &ioContext);
/// Executes tasks that need to run on the start of an iteration (i.e. the stuff that can't run in parallel)
void StartNextIteration(const SoftBodyUpdateContext &ioContext);
/// Helper function for ParallelUpdate that works on batches of collision planes
EStatus ParallelDetermineCollisionPlanes(SoftBodyUpdateContext &ioContext);
/// Helper function for ParallelUpdate that works on sensor collisions
EStatus ParallelDetermineSensorCollisions(SoftBodyUpdateContext &ioContext);
/// Helper function for ParallelUpdate that works on batches of constraints
EStatus ParallelApplyConstraints(SoftBodyUpdateContext &ioContext, const PhysicsSettings &inPhysicsSettings);
/// Helper function to update a single group of constraints
void ProcessGroup(const SoftBodyUpdateContext &ioContext, uint inGroupIndex);
/// Returns 6 times the volume of the soft body
float GetVolumeTimesSix() const;
#ifdef JPH_DEBUG_RENDERER
/// Helper function to draw constraints
template <typename GetEndIndex, typename DrawConstraint>
inline void DrawConstraints(ESoftBodyConstraintColor inConstraintColor, const GetEndIndex &inGetEndIndex, const DrawConstraint &inDrawConstraint, ColorArg inBaseColor) const;
RMat44 mSkinStateTransform = RMat44::sIdentity(); ///< The matrix that transforms mSkinState to world space
#endif // JPH_DEBUG_RENDERER
RefConst<SoftBodySharedSettings> mSettings; ///< Configuration of the particles and constraints
Array<Vertex> mVertices; ///< Current state of all vertices in the simulation
Array<CollidingShape> mCollidingShapes; ///< List of colliding shapes retrieved during the last update
Array<CollidingSensor> mCollidingSensors; ///< List of colliding sensors retrieved during the last update
Array<SkinState> mSkinState; ///< List of skinned positions (1-on-1 with mVertices but only those that are used by the skinning constraints are filled in)
AABox mLocalBounds; ///< Bounding box of all vertices
AABox mLocalPredictedBounds; ///< Predicted bounding box for all vertices using extrapolation of velocity by last step delta time
uint32 mNumIterations; ///< Number of solver iterations
uint mNumSensors; ///< Workaround for TSAN false positive: store mCollidingSensors.size() in a separate variable.
float mPressure; ///< n * R * T, amount of substance * ideal gas constant * absolute temperature, see https://en.wikipedia.org/wiki/Pressure
float mSkinnedMaxDistanceMultiplier = 1.0f; ///< Multiplier applied to Skinned::mMaxDistance to allow tightening or loosening of the skin constraints
bool mUpdatePosition; ///< Update the position of the body while simulating (set to false for something that is attached to the static world)
atomic<bool> mNeedContactCallback = false; ///< True if the soft body has collided with anything in the last update
bool mEnableSkinConstraints = true; ///< If skin constraints are enabled
bool mSkinStatePreviousPositionValid = false; ///< True if the skinning was updated in the last update so that the previous position of the skin state is valid
};
JPH_NAMESPACE_END

View File

@@ -0,0 +1,341 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <Jolt/Jolt.h>
#include <Jolt/Physics/SoftBody/SoftBodyShape.h>
#include <Jolt/Core/Profiler.h>
#include <Jolt/Geometry/RayTriangle.h>
#include <Jolt/Physics/Collision/RayCast.h>
#include <Jolt/Physics/Collision/CastResult.h>
#include <Jolt/Physics/Collision/TransformedShape.h>
#include <Jolt/Physics/SoftBody/SoftBodyMotionProperties.h>
#include <Jolt/Physics/Collision/CastConvexVsTriangles.h>
#include <Jolt/Physics/Collision/CastSphereVsTriangles.h>
#include <Jolt/Physics/Collision/CollideConvexVsTriangles.h>
#include <Jolt/Physics/Collision/CollideSphereVsTriangles.h>
#include <Jolt/Physics/Collision/CollisionDispatch.h>
#ifdef JPH_DEBUG_RENDERER
#include <Jolt/Renderer/DebugRenderer.h>
#endif // JPH_DEBUG_RENDERER
JPH_NAMESPACE_BEGIN
uint SoftBodyShape::GetSubShapeIDBits() const
{
// Ensure we have enough bits to encode our shape [0, n - 1]
uint32 n = (uint32)mSoftBodyMotionProperties->GetFaces().size() - 1;
return 32 - CountLeadingZeros(n);
}
uint32 SoftBodyShape::GetFaceIndex(const SubShapeID &inSubShapeID) const
{
SubShapeID remainder;
uint32 face_index = inSubShapeID.PopID(GetSubShapeIDBits(), remainder);
JPH_ASSERT(remainder.IsEmpty());
return face_index;
}
AABox SoftBodyShape::GetLocalBounds() const
{
return mSoftBodyMotionProperties->GetLocalBounds();
}
bool SoftBodyShape::CastRay(const RayCast &inRay, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) const
{
JPH_PROFILE_FUNCTION();
uint num_triangle_bits = GetSubShapeIDBits();
uint triangle_idx = uint(-1);
const Array<SoftBodyVertex> &vertices = mSoftBodyMotionProperties->GetVertices();
for (const SoftBodyMotionProperties::Face &f : mSoftBodyMotionProperties->GetFaces())
{
Vec3 x1 = vertices[f.mVertex[0]].mPosition;
Vec3 x2 = vertices[f.mVertex[1]].mPosition;
Vec3 x3 = vertices[f.mVertex[2]].mPosition;
float fraction = RayTriangle(inRay.mOrigin, inRay.mDirection, x1, x2, x3);
if (fraction < ioHit.mFraction)
{
// Store fraction
ioHit.mFraction = fraction;
// Store triangle index
triangle_idx = uint(&f - mSoftBodyMotionProperties->GetFaces().data());
}
}
if (triangle_idx == uint(-1))
return false;
ioHit.mSubShapeID2 = inSubShapeIDCreator.PushID(triangle_idx, num_triangle_bits).GetID();
return true;
}
void SoftBodyShape::CastRay(const RayCast &inRay, const RayCastSettings &inRayCastSettings, const SubShapeIDCreator &inSubShapeIDCreator, CastRayCollector &ioCollector, const ShapeFilter &inShapeFilter) const
{
JPH_PROFILE_FUNCTION();
// Test shape filter
if (!inShapeFilter.ShouldCollide(this, inSubShapeIDCreator.GetID()))
return;
uint num_triangle_bits = GetSubShapeIDBits();
const Array<SoftBodyVertex> &vertices = mSoftBodyMotionProperties->GetVertices();
for (const SoftBodyMotionProperties::Face &f : mSoftBodyMotionProperties->GetFaces())
{
Vec3 x1 = vertices[f.mVertex[0]].mPosition;
Vec3 x2 = vertices[f.mVertex[1]].mPosition;
Vec3 x3 = vertices[f.mVertex[2]].mPosition;
// Back facing check
if (inRayCastSettings.mBackFaceModeTriangles == EBackFaceMode::IgnoreBackFaces && (x2 - x1).Cross(x3 - x1).Dot(inRay.mDirection) > 0.0f)
continue;
// Test ray against triangle
float fraction = RayTriangle(inRay.mOrigin, inRay.mDirection, x1, x2, x3);
if (fraction < ioCollector.GetEarlyOutFraction())
{
// Better hit than the current hit
RayCastResult hit;
hit.mBodyID = TransformedShape::sGetBodyID(ioCollector.GetContext());
hit.mFraction = fraction;
hit.mSubShapeID2 = inSubShapeIDCreator.PushID(uint(&f - mSoftBodyMotionProperties->GetFaces().data()), num_triangle_bits).GetID();
ioCollector.AddHit(hit);
}
}
}
void SoftBodyShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter) const
{
sCollidePointUsingRayCast(*this, inPoint, inSubShapeIDCreator, ioCollector, inShapeFilter);
}
void SoftBodyShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const CollideSoftBodyVertexIterator &inVertices, uint inNumVertices, int inCollidingShapeIndex) const
{
/* Not implemented */
}
const PhysicsMaterial *SoftBodyShape::GetMaterial(const SubShapeID &inSubShapeID) const
{
SubShapeID remainder;
uint triangle_idx = inSubShapeID.PopID(GetSubShapeIDBits(), remainder);
JPH_ASSERT(remainder.IsEmpty());
const SoftBodyMotionProperties::Face &f = mSoftBodyMotionProperties->GetFace(triangle_idx);
return mSoftBodyMotionProperties->GetMaterials()[f.mMaterialIndex];
}
Vec3 SoftBodyShape::GetSurfaceNormal(const SubShapeID &inSubShapeID, Vec3Arg inLocalSurfacePosition) const
{
SubShapeID remainder;
uint triangle_idx = inSubShapeID.PopID(GetSubShapeIDBits(), remainder);
JPH_ASSERT(remainder.IsEmpty());
const SoftBodyMotionProperties::Face &f = mSoftBodyMotionProperties->GetFace(triangle_idx);
const Array<SoftBodyVertex> &vertices = mSoftBodyMotionProperties->GetVertices();
Vec3 x1 = vertices[f.mVertex[0]].mPosition;
Vec3 x2 = vertices[f.mVertex[1]].mPosition;
Vec3 x3 = vertices[f.mVertex[2]].mPosition;
return (x2 - x1).Cross(x3 - x1).NormalizedOr(Vec3::sAxisY());
}
void SoftBodyShape::GetSupportingFace(const SubShapeID &inSubShapeID, Vec3Arg inDirection, Vec3Arg inScale, Mat44Arg inCenterOfMassTransform, SupportingFace &outVertices) const
{
SubShapeID remainder;
uint triangle_idx = inSubShapeID.PopID(GetSubShapeIDBits(), remainder);
JPH_ASSERT(remainder.IsEmpty());
const SoftBodyMotionProperties::Face &f = mSoftBodyMotionProperties->GetFace(triangle_idx);
const Array<SoftBodyVertex> &vertices = mSoftBodyMotionProperties->GetVertices();
for (uint32 i : f.mVertex)
outVertices.push_back(inCenterOfMassTransform * (inScale * vertices[i].mPosition));
}
void SoftBodyShape::GetSubmergedVolume(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const Plane &inSurface, float &outTotalVolume, float &outSubmergedVolume, Vec3 &outCenterOfBuoyancy JPH_IF_DEBUG_RENDERER(, RVec3Arg inBaseOffset)) const
{
outSubmergedVolume = 0.0f;
outTotalVolume = mSoftBodyMotionProperties->GetVolume();
outCenterOfBuoyancy = Vec3::sZero();
}
#ifdef JPH_DEBUG_RENDERER
void SoftBodyShape::Draw(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inUseMaterialColors, bool inDrawWireframe) const
{
const Array<SoftBodyVertex> &vertices = mSoftBodyMotionProperties->GetVertices();
for (const SoftBodyMotionProperties::Face &f : mSoftBodyMotionProperties->GetFaces())
{
RVec3 x1 = inCenterOfMassTransform * vertices[f.mVertex[0]].mPosition;
RVec3 x2 = inCenterOfMassTransform * vertices[f.mVertex[1]].mPosition;
RVec3 x3 = inCenterOfMassTransform * vertices[f.mVertex[2]].mPosition;
inRenderer->DrawTriangle(x1, x2, x3, inColor, DebugRenderer::ECastShadow::On);
}
}
#endif // JPH_DEBUG_RENDERER
struct SoftBodyShape::SBSGetTrianglesContext
{
Mat44 mCenterOfMassTransform;
int mTriangleIndex;
};
void SoftBodyShape::GetTrianglesStart(GetTrianglesContext &ioContext, [[maybe_unused]] const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const
{
SBSGetTrianglesContext &context = reinterpret_cast<SBSGetTrianglesContext &>(ioContext);
context.mCenterOfMassTransform = Mat44::sRotationTranslation(inRotation, inPositionCOM) * Mat44::sScale(inScale);
context.mTriangleIndex = 0;
}
int SoftBodyShape::GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTrianglesRequested, Float3 *outTriangleVertices, const PhysicsMaterial **outMaterials) const
{
SBSGetTrianglesContext &context = reinterpret_cast<SBSGetTrianglesContext &>(ioContext);
const Array<SoftBodyMotionProperties::Face> &faces = mSoftBodyMotionProperties->GetFaces();
const Array<SoftBodyVertex> &vertices = mSoftBodyMotionProperties->GetVertices();
const PhysicsMaterialList &materials = mSoftBodyMotionProperties->GetMaterials();
int num_triangles = min(inMaxTrianglesRequested, (int)faces.size() - context.mTriangleIndex);
for (int i = 0; i < num_triangles; ++i)
{
const SoftBodyMotionProperties::Face &f = faces[context.mTriangleIndex + i];
Vec3 x1 = context.mCenterOfMassTransform * vertices[f.mVertex[0]].mPosition;
Vec3 x2 = context.mCenterOfMassTransform * vertices[f.mVertex[1]].mPosition;
Vec3 x3 = context.mCenterOfMassTransform * vertices[f.mVertex[2]].mPosition;
x1.StoreFloat3(outTriangleVertices++);
x2.StoreFloat3(outTriangleVertices++);
x3.StoreFloat3(outTriangleVertices++);
if (outMaterials != nullptr)
*outMaterials++ = materials[f.mMaterialIndex];
}
context.mTriangleIndex += num_triangles;
return num_triangles;
}
Shape::Stats SoftBodyShape::GetStats() const
{
return Stats(sizeof(*this), (uint)mSoftBodyMotionProperties->GetFaces().size());
}
float SoftBodyShape::GetVolume() const
{
return mSoftBodyMotionProperties->GetVolume();
}
void SoftBodyShape::sCollideConvexVsSoftBody(const Shape *inShape1, const Shape *inShape2, Vec3Arg inScale1, Vec3Arg inScale2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, const CollideShapeSettings &inCollideShapeSettings, CollideShapeCollector &ioCollector, [[maybe_unused]] const ShapeFilter &inShapeFilter)
{
JPH_ASSERT(inShape1->GetType() == EShapeType::Convex);
const ConvexShape *shape1 = static_cast<const ConvexShape *>(inShape1);
JPH_ASSERT(inShape2->GetSubType() == EShapeSubType::SoftBody);
const SoftBodyShape *shape2 = static_cast<const SoftBodyShape *>(inShape2);
const Array<SoftBodyVertex> &vertices = shape2->mSoftBodyMotionProperties->GetVertices();
const Array<SoftBodyMotionProperties::Face> &faces = shape2->mSoftBodyMotionProperties->GetFaces();
uint num_triangle_bits = shape2->GetSubShapeIDBits();
CollideConvexVsTriangles collider(shape1, inScale1, inScale2, inCenterOfMassTransform1, inCenterOfMassTransform2, inSubShapeIDCreator1.GetID(), inCollideShapeSettings, ioCollector);
for (const SoftBodyMotionProperties::Face &f : faces)
{
Vec3 x1 = vertices[f.mVertex[0]].mPosition;
Vec3 x2 = vertices[f.mVertex[1]].mPosition;
Vec3 x3 = vertices[f.mVertex[2]].mPosition;
collider.Collide(x1, x2, x3, 0b111, inSubShapeIDCreator2.PushID(uint(&f - faces.data()), num_triangle_bits).GetID());
}
}
void SoftBodyShape::sCollideSphereVsSoftBody(const Shape *inShape1, const Shape *inShape2, Vec3Arg inScale1, Vec3Arg inScale2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, const CollideShapeSettings &inCollideShapeSettings, CollideShapeCollector &ioCollector, [[maybe_unused]] const ShapeFilter &inShapeFilter)
{
JPH_ASSERT(inShape1->GetSubType() == EShapeSubType::Sphere);
const SphereShape *shape1 = static_cast<const SphereShape *>(inShape1);
JPH_ASSERT(inShape2->GetSubType() == EShapeSubType::SoftBody);
const SoftBodyShape *shape2 = static_cast<const SoftBodyShape *>(inShape2);
const Array<SoftBodyVertex> &vertices = shape2->mSoftBodyMotionProperties->GetVertices();
const Array<SoftBodyMotionProperties::Face> &faces = shape2->mSoftBodyMotionProperties->GetFaces();
uint num_triangle_bits = shape2->GetSubShapeIDBits();
CollideSphereVsTriangles collider(shape1, inScale1, inScale2, inCenterOfMassTransform1, inCenterOfMassTransform2, inSubShapeIDCreator1.GetID(), inCollideShapeSettings, ioCollector);
for (const SoftBodyMotionProperties::Face &f : faces)
{
Vec3 x1 = vertices[f.mVertex[0]].mPosition;
Vec3 x2 = vertices[f.mVertex[1]].mPosition;
Vec3 x3 = vertices[f.mVertex[2]].mPosition;
collider.Collide(x1, x2, x3, 0b111, inSubShapeIDCreator2.PushID(uint(&f - faces.data()), num_triangle_bits).GetID());
}
}
void SoftBodyShape::sCastConvexVsSoftBody(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, [[maybe_unused]] const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector)
{
JPH_ASSERT(inShape->GetSubType() == EShapeSubType::SoftBody);
const SoftBodyShape *shape = static_cast<const SoftBodyShape *>(inShape);
const Array<SoftBodyVertex> &vertices = shape->mSoftBodyMotionProperties->GetVertices();
const Array<SoftBodyMotionProperties::Face> &faces = shape->mSoftBodyMotionProperties->GetFaces();
uint num_triangle_bits = shape->GetSubShapeIDBits();
CastConvexVsTriangles caster(inShapeCast, inShapeCastSettings, inScale, inCenterOfMassTransform2, inSubShapeIDCreator1, ioCollector);
for (const SoftBodyMotionProperties::Face &f : faces)
{
Vec3 x1 = vertices[f.mVertex[0]].mPosition;
Vec3 x2 = vertices[f.mVertex[1]].mPosition;
Vec3 x3 = vertices[f.mVertex[2]].mPosition;
caster.Cast(x1, x2, x3, 0b111, inSubShapeIDCreator2.PushID(uint(&f - faces.data()), num_triangle_bits).GetID());
}
}
void SoftBodyShape::sCastSphereVsSoftBody(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, [[maybe_unused]] const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector)
{
JPH_ASSERT(inShape->GetSubType() == EShapeSubType::SoftBody);
const SoftBodyShape *shape = static_cast<const SoftBodyShape *>(inShape);
const Array<SoftBodyVertex> &vertices = shape->mSoftBodyMotionProperties->GetVertices();
const Array<SoftBodyMotionProperties::Face> &faces = shape->mSoftBodyMotionProperties->GetFaces();
uint num_triangle_bits = shape->GetSubShapeIDBits();
CastSphereVsTriangles caster(inShapeCast, inShapeCastSettings, inScale, inCenterOfMassTransform2, inSubShapeIDCreator1, ioCollector);
for (const SoftBodyMotionProperties::Face &f : faces)
{
Vec3 x1 = vertices[f.mVertex[0]].mPosition;
Vec3 x2 = vertices[f.mVertex[1]].mPosition;
Vec3 x3 = vertices[f.mVertex[2]].mPosition;
caster.Cast(x1, x2, x3, 0b111, inSubShapeIDCreator2.PushID(uint(&f - faces.data()), num_triangle_bits).GetID());
}
}
void SoftBodyShape::sRegister()
{
ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::SoftBody);
f.mConstruct = nullptr; // Not supposed to be constructed by users!
f.mColor = Color::sDarkGreen;
for (EShapeSubType s : sConvexSubShapeTypes)
{
CollisionDispatch::sRegisterCollideShape(s, EShapeSubType::SoftBody, sCollideConvexVsSoftBody);
CollisionDispatch::sRegisterCastShape(s, EShapeSubType::SoftBody, sCastConvexVsSoftBody);
CollisionDispatch::sRegisterCollideShape(EShapeSubType::SoftBody, s, CollisionDispatch::sReversedCollideShape);
CollisionDispatch::sRegisterCastShape(EShapeSubType::SoftBody, s, CollisionDispatch::sReversedCastShape);
}
// Specialized collision functions
CollisionDispatch::sRegisterCollideShape(EShapeSubType::Sphere, EShapeSubType::SoftBody, sCollideSphereVsSoftBody);
CollisionDispatch::sRegisterCastShape(EShapeSubType::Sphere, EShapeSubType::SoftBody, sCastSphereVsSoftBody);
}
JPH_NAMESPACE_END

View File

@@ -0,0 +1,73 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Physics/Collision/Shape/Shape.h>
JPH_NAMESPACE_BEGIN
class SoftBodyMotionProperties;
class CollideShapeSettings;
/// Shape used exclusively for soft bodies. Adds the ability to perform collision checks against soft bodies.
class JPH_EXPORT SoftBodyShape final : public Shape
{
public:
JPH_OVERRIDE_NEW_DELETE
/// Constructor
SoftBodyShape() : Shape(EShapeType::SoftBody, EShapeSubType::SoftBody) { }
/// Determine amount of bits needed to encode sub shape id
uint GetSubShapeIDBits() const;
/// Convert a sub shape ID back to a face index
uint32 GetFaceIndex(const SubShapeID &inSubShapeID) const;
// See Shape
virtual bool MustBeStatic() const override { return false; }
virtual Vec3 GetCenterOfMass() const override { return Vec3::sZero(); }
virtual AABox GetLocalBounds() const override;
virtual uint GetSubShapeIDBitsRecursive() const override { return GetSubShapeIDBits(); }
virtual float GetInnerRadius() const override { return 0.0f; }
virtual MassProperties GetMassProperties() const override { return MassProperties(); }
virtual const PhysicsMaterial * GetMaterial(const SubShapeID &inSubShapeID) const override;
virtual Vec3 GetSurfaceNormal(const SubShapeID &inSubShapeID, Vec3Arg inLocalSurfacePosition) const override;
virtual void GetSupportingFace(const SubShapeID &inSubShapeID, Vec3Arg inDirection, Vec3Arg inScale, Mat44Arg inCenterOfMassTransform, SupportingFace &outVertices) const override;
virtual void GetSubmergedVolume(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const Plane &inSurface, float &outTotalVolume, float &outSubmergedVolume, Vec3 &outCenterOfBuoyancy
#ifdef JPH_DEBUG_RENDERER // Not using JPH_IF_DEBUG_RENDERER for Doxygen
, RVec3Arg inBaseOffset
#endif
) const override;
#ifdef JPH_DEBUG_RENDERER
virtual void Draw(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inUseMaterialColors, bool inDrawWireframe) const override;
#endif // JPH_DEBUG_RENDERER
virtual bool CastRay(const RayCast &inRay, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) const override;
virtual void CastRay(const RayCast &inRay, const RayCastSettings &inRayCastSettings, const SubShapeIDCreator &inSubShapeIDCreator, CastRayCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;
virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;
virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const CollideSoftBodyVertexIterator &inVertices, uint inNumVertices, int inCollidingShapeIndex) const override;
virtual void GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const override;
virtual int GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTrianglesRequested, Float3 *outTriangleVertices, const PhysicsMaterial **outMaterials = nullptr) const override;
virtual Stats GetStats() const override;
virtual float GetVolume() const override;
// Register shape functions with the registry
static void sRegister();
private:
// Helper functions called by CollisionDispatch
static void sCollideConvexVsSoftBody(const Shape *inShape1, const Shape *inShape2, Vec3Arg inScale1, Vec3Arg inScale2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, const CollideShapeSettings &inCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter);
static void sCollideSphereVsSoftBody(const Shape *inShape1, const Shape *inShape2, Vec3Arg inScale1, Vec3Arg inScale2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, const CollideShapeSettings &inCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter);
static void sCastConvexVsSoftBody(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector);
static void sCastSphereVsSoftBody(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector);
struct SBSGetTrianglesContext;
friend class BodyManager;
const SoftBodyMotionProperties *mSoftBodyMotionProperties;
};
JPH_NAMESPACE_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,341 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/Reference.h>
#include <Jolt/Physics/Collision/PhysicsMaterial.h>
#include <Jolt/Core/StreamUtils.h>
JPH_NAMESPACE_BEGIN
/// This class defines the setup of all particles and their constraints.
/// It is used during the simulation and can be shared between multiple soft bodies.
class JPH_EXPORT SoftBodySharedSettings : public RefTarget<SoftBodySharedSettings>
{
JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(JPH_EXPORT, SoftBodySharedSettings)
public:
/// Which type of bend constraint should be created
enum class EBendType
{
None, ///< No bend constraints will be created
Distance, ///< A simple distance constraint
Dihedral, ///< A dihedral bend constraint (most expensive, but also supports triangles that are initially not in the same plane)
};
/// The type of long range attachment constraint to create
enum class ELRAType
{
None, ///< Don't create a LRA constraint
EuclideanDistance, ///< Create a LRA constraint based on Euclidean distance between the closest kinematic vertex and this vertex
GeodesicDistance, ///< Create a LRA constraint based on the geodesic distance between the closest kinematic vertex and this vertex (follows the edge constraints)
};
/// Per vertex attributes used during the CreateConstraints function.
/// For an edge or shear constraint, the compliance is averaged between the two attached vertices.
/// For a bend constraint, the compliance is averaged between the two vertices on the shared edge.
struct JPH_EXPORT VertexAttributes
{
/// Constructor
VertexAttributes() = default;
VertexAttributes(float inCompliance, float inShearCompliance, float inBendCompliance, ELRAType inLRAType = ELRAType::None, float inLRAMaxDistanceMultiplier = 1.0f) : mCompliance(inCompliance), mShearCompliance(inShearCompliance), mBendCompliance(inBendCompliance), mLRAType(inLRAType), mLRAMaxDistanceMultiplier(inLRAMaxDistanceMultiplier) { }
float mCompliance = 0.0f; ///< The compliance of the normal edges. Set to FLT_MAX to disable regular edges for any edge involving this vertex.
float mShearCompliance = 0.0f; ///< The compliance of the shear edges. Set to FLT_MAX to disable shear edges for any edge involving this vertex.
float mBendCompliance = FLT_MAX; ///< The compliance of the bend edges. Set to FLT_MAX to disable bend edges for any bend constraint involving this vertex.
ELRAType mLRAType = ELRAType::None; ///< The type of long range attachment constraint to create.
float mLRAMaxDistanceMultiplier = 1.0f; ///< Multiplier for the max distance of the LRA constraint, e.g. 1.01 means the max distance is 1% longer than the calculated distance in the rest pose.
};
/// Automatically create constraints based on the faces of the soft body
/// @param inVertexAttributes A list of attributes for each vertex (1-on-1 with mVertices, note that if the list is smaller than mVertices the last element will be repeated). This defines the properties of the constraints that are created.
/// @param inVertexAttributesLength The length of inVertexAttributes
/// @param inBendType The type of bend constraint to create
/// @param inAngleTolerance Shear edges are created when two connected triangles form a quad (are roughly in the same plane and form a square with roughly 90 degree angles). This defines the tolerance (in radians).
void CreateConstraints(const VertexAttributes *inVertexAttributes, uint inVertexAttributesLength, EBendType inBendType = EBendType::Distance, float inAngleTolerance = DegreesToRadians(8.0f));
/// Calculate the initial lengths of all springs of the edges of this soft body (if you use CreateConstraint, this is already done)
void CalculateEdgeLengths();
/// Calculate the max lengths for the long range attachment constraints based on Euclidean distance (if you use CreateConstraints, this is already done)
/// @param inMaxDistanceMultiplier Multiplier for the max distance of the LRA constraint, e.g. 1.01 means the max distance is 1% longer than the calculated distance in the rest pose.
void CalculateLRALengths(float inMaxDistanceMultiplier = 1.0f);
/// Calculate the constants for the bend constraints (if you use CreateConstraints, this is already done)
void CalculateBendConstraintConstants();
/// Calculates the initial volume of all tetrahedra of this soft body
void CalculateVolumeConstraintVolumes();
/// Calculate information needed to be able to calculate the skinned constraint normals at run-time
void CalculateSkinnedConstraintNormals();
/// Information about the optimization of the soft body, the indices of certain elements may have changed.
class OptimizationResults
{
public:
Array<uint> mEdgeRemap; ///< Maps old edge index to new edge index
Array<uint> mLRARemap; ///< Maps old LRA index to new LRA index
Array<uint> mDihedralBendRemap; ///< Maps old dihedral bend index to new dihedral bend index
Array<uint> mVolumeRemap; ///< Maps old volume constraint index to new volume constraint index
Array<uint> mSkinnedRemap; ///< Maps old skinned constraint index to new skinned constraint index
};
/// Optimize the soft body settings for simulation. This will reorder constraints so they can be executed in parallel.
void Optimize(OptimizationResults &outResults);
/// Optimize the soft body settings without results
void Optimize() { OptimizationResults results; Optimize(results); }
/// Clone this object
Ref<SoftBodySharedSettings> Clone() const;
/// Saves the state of this object in binary form to inStream. Doesn't store the material list.
void SaveBinaryState(StreamOut &inStream) const;
/// Restore the state of this object from inStream. Doesn't restore the material list.
void RestoreBinaryState(StreamIn &inStream);
using SharedSettingsToIDMap = StreamUtils::ObjectToIDMap<SoftBodySharedSettings>;
using IDToSharedSettingsMap = StreamUtils::IDToObjectMap<SoftBodySharedSettings>;
using MaterialToIDMap = StreamUtils::ObjectToIDMap<PhysicsMaterial>;
using IDToMaterialMap = StreamUtils::IDToObjectMap<PhysicsMaterial>;
/// Save this shared settings and its materials. Pass in an empty map ioSettingsMap / ioMaterialMap or reuse the same map while saving multiple settings objects to the same stream in order to avoid writing duplicates.
void SaveWithMaterials(StreamOut &inStream, SharedSettingsToIDMap &ioSettingsMap, MaterialToIDMap &ioMaterialMap) const;
using SettingsResult = Result<Ref<SoftBodySharedSettings>>;
/// Restore a shape and materials. Pass in an empty map in ioSettingsMap / ioMaterialMap or reuse the same map while reading multiple settings objects from the same stream in order to restore duplicates.
static SettingsResult sRestoreWithMaterials(StreamIn &inStream, IDToSharedSettingsMap &ioSettingsMap, IDToMaterialMap &ioMaterialMap);
/// Create a cube. This can be used to create a simple soft body for testing purposes.
/// It will contain edge constraints, volume constraints and faces.
/// @param inGridSize Number of points along each axis
/// @param inGridSpacing Distance between points
static Ref<SoftBodySharedSettings> sCreateCube(uint inGridSize, float inGridSpacing);
/// A vertex is a particle, the data in this structure is only used during creation of the soft body and not during simulation
struct JPH_EXPORT Vertex
{
JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(JPH_EXPORT, Vertex)
/// Constructor
Vertex() = default;
Vertex(const Float3 &inPosition, const Float3 &inVelocity = Float3(0, 0, 0), float inInvMass = 1.0f) : mPosition(inPosition), mVelocity(inVelocity), mInvMass(inInvMass) { }
Float3 mPosition { 0, 0, 0 }; ///< Initial position of the vertex
Float3 mVelocity { 0, 0, 0 }; ///< Initial velocity of the vertex
float mInvMass = 1.0f; ///< Initial inverse of the mass of the vertex
};
/// A face defines the surface of the body
struct JPH_EXPORT Face
{
JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(JPH_EXPORT, Face)
/// Constructor
Face() = default;
Face(uint32 inVertex1, uint32 inVertex2, uint32 inVertex3, uint32 inMaterialIndex = 0) : mVertex { inVertex1, inVertex2, inVertex3 }, mMaterialIndex(inMaterialIndex) { }
/// Check if this is a degenerate face (a face which points to the same vertex twice)
bool IsDegenerate() const { return mVertex[0] == mVertex[1] || mVertex[0] == mVertex[2] || mVertex[1] == mVertex[2]; }
uint32 mVertex[3]; ///< Indices of the vertices that form the face
uint32 mMaterialIndex = 0; ///< Index of the material of the face in SoftBodySharedSettings::mMaterials
};
/// An edge keeps two vertices at a constant distance using a spring: |x1 - x2| = rest length
struct JPH_EXPORT Edge
{
JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(JPH_EXPORT, Edge)
/// Constructor
Edge() = default;
Edge(uint32 inVertex1, uint32 inVertex2, float inCompliance = 0.0f) : mVertex { inVertex1, inVertex2 }, mCompliance(inCompliance) { }
/// Return the lowest vertex index of this constraint
uint32 GetMinVertexIndex() const { return min(mVertex[0], mVertex[1]); }
uint32 mVertex[2]; ///< Indices of the vertices that form the edge
float mRestLength = 1.0f; ///< Rest length of the spring
float mCompliance = 0.0f; ///< Inverse of the stiffness of the spring
};
/**
* A dihedral bend constraint keeps the angle between two triangles constant along their shared edge.
*
* x2
* / \
* / t0 \
* x0----x1
* \ t1 /
* \ /
* x3
*
* x0..x3 are the vertices, t0 and t1 are the triangles that share the edge x0..x1
*
* Based on:
* - "Position Based Dynamics" - Matthias Muller et al.
* - "Strain Based Dynamics" - Matthias Muller et al.
* - "Simulation of Clothing with Folds and Wrinkles" - R. Bridson et al.
*/
struct JPH_EXPORT DihedralBend
{
JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(JPH_EXPORT, DihedralBend)
/// Constructor
DihedralBend() = default;
DihedralBend(uint32 inVertex1, uint32 inVertex2, uint32 inVertex3, uint32 inVertex4, float inCompliance = 0.0f) : mVertex { inVertex1, inVertex2, inVertex3, inVertex4 }, mCompliance(inCompliance) { }
/// Return the lowest vertex index of this constraint
uint32 GetMinVertexIndex() const { return min(min(mVertex[0], mVertex[1]), min(mVertex[2], mVertex[3])); }
uint32 mVertex[4]; ///< Indices of the vertices of the 2 triangles that share an edge (the first 2 vertices are the shared edge)
float mCompliance = 0.0f; ///< Inverse of the stiffness of the constraint
float mInitialAngle = 0.0f; ///< Initial angle between the normals of the triangles (pi - dihedral angle).
};
/// Volume constraint, keeps the volume of a tetrahedron constant
struct JPH_EXPORT Volume
{
JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(JPH_EXPORT, Volume)
/// Constructor
Volume() = default;
Volume(uint32 inVertex1, uint32 inVertex2, uint32 inVertex3, uint32 inVertex4, float inCompliance = 0.0f) : mVertex { inVertex1, inVertex2, inVertex3, inVertex4 }, mCompliance(inCompliance) { }
/// Return the lowest vertex index of this constraint
uint32 GetMinVertexIndex() const { return min(min(mVertex[0], mVertex[1]), min(mVertex[2], mVertex[3])); }
uint32 mVertex[4]; ///< Indices of the vertices that form the tetrahedron
float mSixRestVolume = 1.0f; ///< 6 times the rest volume of the tetrahedron (calculated by CalculateVolumeConstraintVolumes())
float mCompliance = 0.0f; ///< Inverse of the stiffness of the constraint
};
/// An inverse bind matrix take a skinned vertex from its bind pose into joint local space
class JPH_EXPORT InvBind
{
JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(JPH_EXPORT, InvBind)
public:
/// Constructor
InvBind() = default;
InvBind(uint32 inJointIndex, Mat44Arg inInvBind) : mJointIndex(inJointIndex), mInvBind(inInvBind) { }
uint32 mJointIndex = 0; ///< Joint index to which this is attached
Mat44 mInvBind = Mat44::sIdentity(); ///< The inverse bind matrix, this takes a vertex in its bind pose (Vertex::mPosition) to joint local space
};
/// A joint and its skin weight
class JPH_EXPORT SkinWeight
{
JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(JPH_EXPORT, SkinWeight)
public:
/// Constructor
SkinWeight() = default;
SkinWeight(uint32 inInvBindIndex, float inWeight) : mInvBindIndex(inInvBindIndex), mWeight(inWeight) { }
uint32 mInvBindIndex = 0; ///< Index in mInvBindMatrices
float mWeight = 0.0f; ///< Weight with which it is skinned
};
/// A constraint that skins a vertex to joints and limits the distance that the simulated vertex can travel from this vertex
class JPH_EXPORT Skinned
{
JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(JPH_EXPORT, Skinned)
public:
/// Constructor
Skinned() = default;
Skinned(uint32 inVertex, float inMaxDistance, float inBackStopDistance, float inBackStopRadius) : mVertex(inVertex), mMaxDistance(inMaxDistance), mBackStopDistance(inBackStopDistance), mBackStopRadius(inBackStopRadius) { }
/// Normalize the weights so that they add up to 1
void NormalizeWeights()
{
// Get the total weight
float total = 0.0f;
for (const SkinWeight &w : mWeights)
total += w.mWeight;
// Normalize
if (total > 0.0f)
for (SkinWeight &w : mWeights)
w.mWeight /= total;
}
/// Maximum number of skin weights
static constexpr uint cMaxSkinWeights = 4;
uint32 mVertex = 0; ///< Index in mVertices which indicates which vertex is being skinned
SkinWeight mWeights[cMaxSkinWeights]; ///< Skin weights, the bind pose of the vertex is assumed to be stored in Vertex::mPosition. The first weight that is zero indicates the end of the list. Weights should add up to 1.
float mMaxDistance = FLT_MAX; ///< Maximum distance that this vertex can reach from the skinned vertex, disabled when FLT_MAX. 0 when you want to hard skin the vertex to the skinned vertex.
float mBackStopDistance = FLT_MAX; ///< Disabled if mBackStopDistance >= mMaxDistance. The faces surrounding mVertex determine an average normal. mBackStopDistance behind the vertex in the opposite direction of this normal, the back stop sphere starts. The simulated vertex will be pushed out of this sphere and it can be used to approximate the volume of the skinned mesh behind the skinned vertex.
float mBackStopRadius = 40.0f; ///< Radius of the backstop sphere. By default this is a fairly large radius so the sphere approximates a plane.
uint32 mNormalInfo = 0; ///< Information needed to calculate the normal of this vertex, lowest 24 bit is start index in mSkinnedConstraintNormals, highest 8 bit is number of faces (generated by CalculateSkinnedConstraintNormals())
};
/// A long range attachment constraint, this is a constraint that sets a max distance between a kinematic vertex and a dynamic vertex
/// See: "Long Range Attachments - A Method to Simulate Inextensible Clothing in Computer Games", Tae-Yong Kim, Nuttapong Chentanez and Matthias Mueller-Fischer
class JPH_EXPORT LRA
{
JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(JPH_EXPORT, LRA)
public:
/// Constructor
LRA() = default;
LRA(uint32 inVertex1, uint32 inVertex2, float inMaxDistance) : mVertex { inVertex1, inVertex2 }, mMaxDistance(inMaxDistance) { }
/// Return the lowest vertex index of this constraint
uint32 GetMinVertexIndex() const { return min(mVertex[0], mVertex[1]); }
uint32 mVertex[2]; ///< The vertices that are connected. The first vertex should be kinematic, the 2nd dynamic.
float mMaxDistance = 0.0f; ///< The maximum distance between the vertices
};
/// Add a face to this soft body
void AddFace(const Face &inFace) { JPH_ASSERT(!inFace.IsDegenerate()); mFaces.push_back(inFace); }
Array<Vertex> mVertices; ///< The list of vertices or particles of the body
Array<Face> mFaces; ///< The list of faces of the body
Array<Edge> mEdgeConstraints; ///< The list of edges or springs of the body
Array<DihedralBend> mDihedralBendConstraints; ///< The list of dihedral bend constraints of the body
Array<Volume> mVolumeConstraints; ///< The list of volume constraints of the body that keep the volume of tetrahedra in the soft body constant
Array<Skinned> mSkinnedConstraints; ///< The list of vertices that are constrained to a skinned vertex
Array<InvBind> mInvBindMatrices; ///< The list of inverse bind matrices for skinning vertices
Array<LRA> mLRAConstraints; ///< The list of long range attachment constraints
PhysicsMaterialList mMaterials { PhysicsMaterial::sDefault }; ///< The materials of the faces of the body, referenced by Face::mMaterialIndex
float mVertexRadius = 0.0f; ///< How big the particles are, can be used to push the vertices a little bit away from the surface of other bodies to prevent z-fighting
private:
friend class SoftBodyMotionProperties;
/// Calculate the closest kinematic vertex array
void CalculateClosestKinematic();
/// Tracks the closest kinematic vertex
struct ClosestKinematic
{
uint32 mVertex = 0xffffffff; ///< Vertex index of closest kinematic vertex
float mDistance = FLT_MAX; ///< Distance to the closest kinematic vertex
};
/// Tracks the end indices of the various constraint groups
struct UpdateGroup
{
uint mEdgeEndIndex; ///< The end index of the edge constraints in this group
uint mLRAEndIndex; ///< The end index of the LRA constraints in this group
uint mDihedralBendEndIndex; ///< The end index of the dihedral bend constraints in this group
uint mVolumeEndIndex; ///< The end index of the volume constraints in this group
uint mSkinnedEndIndex; ///< The end index of the skinned constraints in this group
};
Array<ClosestKinematic> mClosestKinematic; ///< The closest kinematic vertex to each vertex in mVertices
Array<UpdateGroup> mUpdateGroups; ///< The end indices for each group of constraints that can be updated in parallel
Array<uint32> mSkinnedConstraintNormals; ///< A list of indices in the mFaces array used by mSkinnedConstraints, calculated by CalculateSkinnedConstraintNormals()
};
JPH_NAMESPACE_END

View File

@@ -0,0 +1,59 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/NonCopyable.h>
#include <Jolt/Physics/Body/MotionProperties.h>
JPH_NAMESPACE_BEGIN
class Body;
class SoftBodyMotionProperties;
class SoftBodyContactListener;
class SimShapeFilter;
/// Temporary data used by the update of a soft body
class SoftBodyUpdateContext : public NonCopyable
{
public:
static constexpr uint cVertexCollisionBatch = 64; ///< Number of vertices to process in a batch in DetermineCollisionPlanes
static constexpr uint cVertexConstraintBatch = 256; ///< Number of vertices to group for processing batches of constraints in ApplyEdgeConstraints
// Input
Body * mBody; ///< Body that is being updated
SoftBodyMotionProperties * mMotionProperties; ///< Motion properties of that body
SoftBodyContactListener * mContactListener; ///< Contact listener to fire callbacks to
const SimShapeFilter * mSimShapeFilter; ///< Shape filter to use for collision detection
RMat44 mCenterOfMassTransform; ///< Transform of the body relative to the soft body
Vec3 mGravity; ///< Gravity vector in local space of the soft body
Vec3 mDisplacementDueToGravity; ///< Displacement of the center of mass due to gravity in the current time step
float mDeltaTime; ///< Delta time for the current time step
float mSubStepDeltaTime; ///< Delta time for each sub step
/// Describes progress in the current update
enum class EState
{
DetermineCollisionPlanes, ///< Determine collision planes for vertices in parallel
DetermineSensorCollisions, ///< Determine collisions with sensors in parallel
ApplyConstraints, ///< Apply constraints in parallel
Done ///< Update is finished
};
// State of the update
atomic<EState> mState { EState::DetermineCollisionPlanes };///< Current state of the update
atomic<uint> mNextCollisionVertex { 0 }; ///< Next vertex to process for DetermineCollisionPlanes
atomic<uint> mNumCollisionVerticesProcessed { 0 }; ///< Number of vertices processed by DetermineCollisionPlanes, used to determine if we can go to the next step
atomic<uint> mNextSensorIndex { 0 }; ///< Next sensor to process for DetermineCollisionPlanes
atomic<uint> mNumSensorsProcessed { 0 }; ///< Number of sensors processed by DetermineSensorCollisions, used to determine if we can go to the next step
atomic<uint> mNextIteration { 0 }; ///< Next simulation iteration to process
atomic<uint> mNextConstraintGroup { 0 }; ///< Next constraint group to process
atomic<uint> mNumConstraintGroupsProcessed { 0 }; ///< Number of groups processed, used to determine if we can go to the next iteration
// Output
Vec3 mDeltaPosition; ///< Delta position of the body in the current time step, should be applied after the update
ECanSleep mCanSleep; ///< Can the body sleep? Should be applied after the update
};
JPH_NAMESPACE_END

View File

@@ -0,0 +1,36 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Geometry/Plane.h>
JPH_NAMESPACE_BEGIN
/// Run time information for a single particle of a soft body
/// Note that at run-time you should only modify the inverse mass and/or velocity of a vertex to control the soft body.
/// Modifying the position can lead to missed collisions.
/// The other members are used internally by the soft body solver.
class SoftBodyVertex
{
public:
/// Reset collision information to prepare for a new collision check
inline void ResetCollision()
{
mLargestPenetration = -FLT_MAX;
mCollidingShapeIndex = -1;
mHasContact = false;
}
Vec3 mPreviousPosition; ///< Internal use only. Position at the previous time step
Vec3 mPosition; ///< Position, relative to the center of mass of the soft body
Vec3 mVelocity; ///< Velocity, relative to the center of mass of the soft body
Plane mCollisionPlane; ///< Internal use only. Nearest collision plane, relative to the center of mass of the soft body
int mCollidingShapeIndex; ///< Internal use only. Index in the colliding shapes list of the body we may collide with
bool mHasContact; ///< True if the vertex has collided with anything in the last update
float mLargestPenetration; ///< Internal use only. Used while finding the collision plane, stores the largest penetration found so far
float mInvMass; ///< Inverse mass (1 / mass)
};
JPH_NAMESPACE_END