Skip to content

Commit

Permalink
SoftBodyManifold now returns sensor contacts separately (#1276)
Browse files Browse the repository at this point in the history
Before this change, there was a limit of a single colliding body per soft body vertex. If the closest body happened to be a sensor this effectively disabled the collision with the world and caused artifacts. We can now also detect multiple sensor contacts per soft body and they are returned through a new interface SoftBodyManifold::GetSensorContactBodyID.

* Shape::CollideSoftBodyVertices is no longer using SoftBodyVertex but CollideSoftBodyVertexIterator which can write to other things than a SoftBodyVertex
* Removed delta time and displacement due to gravity from Shape::CollideSoftBodyVertices
* Added soft body vs sensor test that doesn't have a limit of 1 sensor per soft body

Fixes #1016
  • Loading branch information
jrouwe authored Sep 22, 2024
1 parent a487dac commit 4058e6a
Show file tree
Hide file tree
Showing 48 changed files with 665 additions and 313 deletions.
63 changes: 63 additions & 0 deletions Jolt/Core/StridedPtr.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT

#pragma once

JPH_NAMESPACE_BEGIN

/// A strided pointer behaves exactly like a normal pointer except that the
/// elements that the pointer points to can be part of a larger structure.
/// The stride gives the number of bytes from one element to the next.
template <class T>
class JPH_EXPORT StridedPtr
{
public:
using value_type = T;

/// Constructors
StridedPtr() = default;
StridedPtr(const StridedPtr &inRHS) = default;
StridedPtr(T *inPtr, int inStride = sizeof(T)) : mPtr(const_cast<uint8 *>(reinterpret_cast<const uint8 *>(inPtr))), mStride(inStride) { }

/// Assignment
inline StridedPtr & operator = (const StridedPtr &inRHS) = default;

/// Incrementing / decrementing
inline StridedPtr & operator ++ () { mPtr += mStride; return *this; }
inline StridedPtr & operator -- () { mPtr -= mStride; return *this; }
inline StridedPtr operator ++ (int) { StridedPtr old_ptr(*this); mPtr += mStride; return old_ptr; }
inline StridedPtr operator -- (int) { StridedPtr old_ptr(*this); mPtr -= mStride; return old_ptr; }
inline StridedPtr operator + (int inOffset) const { StridedPtr new_ptr(*this); new_ptr.mPtr += inOffset * mStride; return new_ptr; }
inline StridedPtr operator - (int inOffset) const { StridedPtr new_ptr(*this); new_ptr.mPtr -= inOffset * mStride; return new_ptr; }
inline void operator += (int inOffset) { mPtr += inOffset * mStride; }
inline void operator -= (int inOffset) { mPtr -= inOffset * mStride; }

/// Distance between two pointers in elements
inline int operator - (const StridedPtr &inRHS) const { JPH_ASSERT(inRHS.mStride == mStride); return (mPtr - inRHS.mPtr) / mStride; }

/// Comparison operators
inline bool operator == (const StridedPtr &inRHS) const { return mPtr == inRHS.mPtr; }
inline bool operator != (const StridedPtr &inRHS) const { return mPtr != inRHS.mPtr; }
inline bool operator <= (const StridedPtr &inRHS) const { return mPtr <= inRHS.mPtr; }
inline bool operator >= (const StridedPtr &inRHS) const { return mPtr >= inRHS.mPtr; }
inline bool operator < (const StridedPtr &inRHS) const { return mPtr < inRHS.mPtr; }
inline bool operator > (const StridedPtr &inRHS) const { return mPtr > inRHS.mPtr; }

/// Access value
inline T & operator * () const { return *reinterpret_cast<T *>(mPtr); }
inline T * operator -> () const { return reinterpret_cast<T *>(mPtr); }
inline T & operator [] (int inOffset) const { uint8 *ptr = mPtr + inOffset * mStride; return *reinterpret_cast<T *>(ptr); }

/// Explicit conversion
inline T * GetPtr() const { return reinterpret_cast<T *>(mPtr); }

/// Get stride in bytes
inline int GetStride() const { return mStride; }

private:
uint8 * mPtr = nullptr; /// Pointer to element
int mStride = 0; /// Stride (number of bytes) between elements
};

JPH_NAMESPACE_END
2 changes: 2 additions & 0 deletions Jolt/Jolt.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ set(JOLT_PHYSICS_SRC_FILES
${JOLT_PHYSICS_ROOT}/Core/StreamOut.h
${JOLT_PHYSICS_ROOT}/Core/StreamUtils.h
${JOLT_PHYSICS_ROOT}/Core/StreamWrapper.h
${JOLT_PHYSICS_ROOT}/Core/StridedPtr.h
${JOLT_PHYSICS_ROOT}/Core/StringTools.cpp
${JOLT_PHYSICS_ROOT}/Core/StringTools.h
${JOLT_PHYSICS_ROOT}/Core/TempAllocator.h
Expand Down Expand Up @@ -197,6 +198,7 @@ set(JOLT_PHYSICS_SRC_FILES
${JOLT_PHYSICS_ROOT}/Physics/Collision/CollideConvexVsTriangles.h
${JOLT_PHYSICS_ROOT}/Physics/Collision/CollidePointResult.h
${JOLT_PHYSICS_ROOT}/Physics/Collision/CollideShape.h
${JOLT_PHYSICS_ROOT}/Physics/Collision/CollideSoftBodyVertexIterator.h
${JOLT_PHYSICS_ROOT}/Physics/Collision/CollideSoftBodyVerticesVsTriangles.h
${JOLT_PHYSICS_ROOT}/Physics/Collision/CollideSphereVsTriangles.cpp
${JOLT_PHYSICS_ROOT}/Physics/Collision/CollideSphereVsTriangles.h
Expand Down
3 changes: 3 additions & 0 deletions Jolt/Jolt.natvis
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,7 @@
</ArrayItems>
</Expand>
</Type>
<Type Name="JPH::StridedPtr&lt;*&gt;">
<DisplayString>{(value_type *)mPtr}, stride={mStride}</DisplayString>
</Type>
</AutoVisualizer>
110 changes: 110 additions & 0 deletions Jolt/Physics/Collision/CollideSoftBodyVertexIterator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT

#pragma once

#include <Jolt/Physics/SoftBody/SoftBodyVertex.h>
#include <Jolt/Core/StridedPtr.h>

JPH_NAMESPACE_BEGIN

/// Class that allows iterating over the vertices of a soft body.
/// It tracks the largest penetration and allows storing the resulting collision in a different structure than the soft body vertex itself.
class CollideSoftBodyVertexIterator
{
public:
/// Default constructor
CollideSoftBodyVertexIterator() = default;
CollideSoftBodyVertexIterator(const CollideSoftBodyVertexIterator &) = default;

/// Construct using (strided) pointers
CollideSoftBodyVertexIterator(const StridedPtr<const Vec3> &inPosition, const StridedPtr<const float> &inInvMass, const StridedPtr<Plane> &inCollisionPlane, const StridedPtr<float> &inLargestPenetration, const StridedPtr<int> &inCollidingShapeIndex) :
mPosition(inPosition),
mInvMass(inInvMass),
mCollisionPlane(inCollisionPlane),
mLargestPenetration(inLargestPenetration),
mCollidingShapeIndex(inCollidingShapeIndex)
{
}

/// Construct using a soft body vertex
explicit CollideSoftBodyVertexIterator(SoftBodyVertex *inVertices) :
mPosition(&inVertices->mPosition, sizeof(SoftBodyVertex)),
mInvMass(&inVertices->mInvMass, sizeof(SoftBodyVertex)),
mCollisionPlane(&inVertices->mCollisionPlane, sizeof(SoftBodyVertex)),
mLargestPenetration(&inVertices->mLargestPenetration, sizeof(SoftBodyVertex)),
mCollidingShapeIndex(&inVertices->mCollidingShapeIndex, sizeof(SoftBodyVertex))
{
}

/// Default assignment
CollideSoftBodyVertexIterator & operator = (const CollideSoftBodyVertexIterator &) = default;

/// Equality operator.
/// Note: Only used to determine end iterator, so we only compare position.
bool operator != (const CollideSoftBodyVertexIterator &inRHS) const
{
return mPosition != inRHS.mPosition;
}

/// Next vertex
CollideSoftBodyVertexIterator & operator ++ ()
{
++mPosition;
++mInvMass;
++mCollisionPlane;
++mLargestPenetration;
++mCollidingShapeIndex;
return *this;
}

/// Add an offset
/// Note: Only used to determine end iterator, so we only set position.
CollideSoftBodyVertexIterator operator + (int inOffset) const
{
return CollideSoftBodyVertexIterator(mPosition + inOffset, StridedPtr<const float>(), StridedPtr<Plane>(), StridedPtr<float>(), StridedPtr<int>());
}

/// Get the position of the current vertex
Vec3 GetPosition() const
{
return *mPosition;
}

/// Get the inverse mass of the current vertex
float GetInvMass() const
{
return *mInvMass;
}

/// Update penetration of the current vertex
/// @return Returns true if the vertex has the largest penetration so far, this means you need to follow up by calling SetCollision
bool UpdatePenetration(float inLargestPenetration) const
{
float &penetration = *mLargestPenetration;
if (penetration >= inLargestPenetration)
return false;
penetration = inLargestPenetration;
return true;
}

/// Update the collision of the current vertex
void SetCollision(const Plane &inCollisionPlane, int inCollidingShapeIndex) const
{
*mCollisionPlane = inCollisionPlane;
*mCollidingShapeIndex = inCollidingShapeIndex;
}

private:
/// Input data
StridedPtr<const Vec3> mPosition;
StridedPtr<const float> mInvMass;

/// Output data
StridedPtr<Plane> mCollisionPlane;
StridedPtr<float> mLargestPenetration;
StridedPtr<int> mCollidingShapeIndex;
};

JPH_NAMESPACE_END
28 changes: 10 additions & 18 deletions Jolt/Physics/Collision/CollideSoftBodyVerticesVsTriangles.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

#pragma once

#include <Jolt/Physics/SoftBody/SoftBodyVertex.h>
#include <Jolt/Physics/Collision/CollideSoftBodyVertexIterator.h>
#include <Jolt/Geometry/ClosestPoint.h>

JPH_NAMESPACE_BEGIN
Expand All @@ -20,9 +20,9 @@ class JPH_EXPORT CollideSoftBodyVerticesVsTriangles
{
}

JPH_INLINE void StartVertex(const SoftBodyVertex &inVertex)
JPH_INLINE void StartVertex(const CollideSoftBodyVertexIterator &inVertex)
{
mLocalPosition = mInvTransform * inVertex.mPosition;
mLocalPosition = mInvTransform * inVertex.GetPosition();
mClosestDistanceSq = FLT_MAX;
}

Expand All @@ -43,7 +43,7 @@ class JPH_EXPORT CollideSoftBodyVerticesVsTriangles
}
}

JPH_INLINE void FinishVertex(SoftBodyVertex &ioVertex, int inCollidingShapeIndex) const
JPH_INLINE void FinishVertex(const CollideSoftBodyVertexIterator &ioVertex, int inCollidingShapeIndex) const
{
if (mClosestDistanceSq < FLT_MAX)
{
Expand All @@ -57,29 +57,21 @@ class JPH_EXPORT CollideSoftBodyVerticesVsTriangles
{
// Closest is interior to the triangle, use plane as collision plane but don't allow more than 0.1 m penetration
// because otherwise a triangle half a level a way will have a huge penetration if it is back facing
float penetration = min(triangle_normal.Dot(v0 - ioVertex.mPosition), 0.1f);
if (penetration > ioVertex.mLargestPenetration)
{
ioVertex.mLargestPenetration = penetration;
ioVertex.mCollisionPlane = Plane::sFromPointAndNormal(v0, triangle_normal);
ioVertex.mCollidingShapeIndex = inCollidingShapeIndex;
}
float penetration = min(triangle_normal.Dot(v0 - ioVertex.GetPosition()), 0.1f);
if (ioVertex.UpdatePenetration(penetration))
ioVertex.SetCollision(Plane::sFromPointAndNormal(v0, triangle_normal), inCollidingShapeIndex);
}
else
{
// Closest point is on an edge or vertex, use closest point as collision plane
Vec3 closest_point = mTransform * (mLocalPosition + mClosestPoint);
Vec3 normal = ioVertex.mPosition - closest_point;
Vec3 normal = ioVertex.GetPosition() - closest_point;
if (normal.Dot(triangle_normal) > 0.0f) // Ignore back facing edges
{
float normal_length = normal.Length();
float penetration = -normal_length;
if (penetration > ioVertex.mLargestPenetration)
{
ioVertex.mLargestPenetration = penetration;
ioVertex.mCollisionPlane = Plane::sFromPointAndNormal(closest_point, normal_length > 0.0f? normal / normal_length : triangle_normal);
ioVertex.mCollidingShapeIndex = inCollidingShapeIndex;
}
if (ioVertex.UpdatePenetration(penetration))
ioVertex.SetCollision(Plane::sFromPointAndNormal(closest_point, normal_length > 0.0f? normal / normal_length : triangle_normal), inCollidingShapeIndex);
}
}
}
Expand Down
24 changes: 9 additions & 15 deletions Jolt/Physics/Collision/Shape/BoxShape.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include <Jolt/Physics/Collision/CastResult.h>
#include <Jolt/Physics/Collision/CollidePointResult.h>
#include <Jolt/Physics/Collision/TransformedShape.h>
#include <Jolt/Physics/SoftBody/SoftBodyVertex.h>
#include <Jolt/Physics/Collision/CollideSoftBodyVertexIterator.h>
#include <Jolt/Geometry/RayAABox.h>
#include <Jolt/ObjectStream/TypeDeclarations.h>
#include <Jolt/Core/StreamIn.h>
Expand Down Expand Up @@ -225,16 +225,16 @@ void BoxShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShape
ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });
}

void BoxShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
void BoxShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const CollideSoftBodyVertexIterator &inVertices, uint inNumVertices, int inCollidingShapeIndex) const
{
Mat44 inverse_transform = inCenterOfMassTransform.InversedRotationTranslation();
Vec3 half_extent = inScale.Abs() * mHalfExtent;

for (SoftBodyVertex *v = ioVertices, *sbv_end = ioVertices + inNumVertices; v < sbv_end; ++v)
if (v->mInvMass > 0.0f)
for (CollideSoftBodyVertexIterator v = inVertices, sbv_end = inVertices + inNumVertices; v != sbv_end; ++v)
if (v.GetInvMass() > 0.0f)
{
// Convert to local space
Vec3 local_pos = inverse_transform * v->mPosition;
Vec3 local_pos = inverse_transform * v.GetPosition();

// Clamp point to inside box
Vec3 clamped_point = Vec3::sMax(Vec3::sMin(local_pos, half_extent), -half_extent);
Expand All @@ -246,18 +246,15 @@ void BoxShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg
Vec3 delta = half_extent - local_pos.Abs();
int index = delta.GetLowestComponentIndex();
float penetration = delta[index];
if (penetration > v->mLargestPenetration)
if (v.UpdatePenetration(penetration))
{
v->mLargestPenetration = penetration;

// Calculate contact point and normal
Vec3 possible_normals[] = { Vec3::sAxisX(), Vec3::sAxisY(), Vec3::sAxisZ() };
Vec3 normal = local_pos.GetSign() * possible_normals[index];
Vec3 point = normal * half_extent;

// Store collision
v->mCollisionPlane = Plane::sFromPointAndNormal(point, normal).GetTransformed(inCenterOfMassTransform);
v->mCollidingShapeIndex = inCollidingShapeIndex;
v.SetCollision(Plane::sFromPointAndNormal(point, normal).GetTransformed(inCenterOfMassTransform), inCollidingShapeIndex);
}
}
else
Expand All @@ -268,15 +265,12 @@ void BoxShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg

// Penetration will be negative since we're not penetrating
float penetration = -normal_length;
if (penetration > v->mLargestPenetration)
if (v.UpdatePenetration(penetration))
{
normal /= normal_length;

v->mLargestPenetration = penetration;

// Store collision
v->mCollisionPlane = Plane::sFromPointAndNormal(clamped_point, normal).GetTransformed(inCenterOfMassTransform);
v->mCollidingShapeIndex = inCollidingShapeIndex;
v.SetCollision(Plane::sFromPointAndNormal(clamped_point, normal).GetTransformed(inCenterOfMassTransform), inCollidingShapeIndex);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Jolt/Physics/Collision/Shape/BoxShape.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class JPH_EXPORT BoxShape final : public ConvexShape
virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;

// See: Shape::CollideSoftBodyVertices
virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const CollideSoftBodyVertexIterator &inVertices, uint inNumVertices, int inCollidingShapeIndex) const override;

// See Shape::GetTrianglesStart
virtual void GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const override;
Expand Down
Loading

0 comments on commit 4058e6a

Please sign in to comment.