فهرست منبع

Improvements to soft body skinning constraints (#1054)

- Multithreading skinned constraints
- Fixed bug where the the skinned position would update in the first iteration, causing a large velocity spike
- Fixed bug where the velocity of vertices would increase indefinitely when resting on the back stop
Jorrit Rouwe 1 سال پیش
والد
کامیت
744900a4be

+ 1 - 1
Jolt/Physics/Body/BodyManager.cpp

@@ -1092,7 +1092,7 @@ void BodyManager::Draw(const DrawSettings &inDrawSettings, const PhysicsSettings
 					mp->DrawVolumeConstraints(inRenderer, com, inDrawSettings.mDrawSoftBodyConstraintColor);
 					mp->DrawVolumeConstraints(inRenderer, com, inDrawSettings.mDrawSoftBodyConstraintColor);
 
 
 				if (inDrawSettings.mDrawSoftBodySkinConstraints)
 				if (inDrawSettings.mDrawSoftBodySkinConstraints)
-					mp->DrawSkinConstraints(inRenderer, com);
+					mp->DrawSkinConstraints(inRenderer, com, inDrawSettings.mDrawSoftBodyConstraintColor);
 
 
 				if (inDrawSettings.mDrawSoftBodyLRAConstraints)
 				if (inDrawSettings.mDrawSoftBodyLRAConstraints)
 					mp->DrawLRAConstraints(inRenderer, com, inDrawSettings.mDrawSoftBodyConstraintColor);
 					mp->DrawLRAConstraints(inRenderer, com, inDrawSettings.mDrawSoftBodyConstraintColor);

+ 59 - 35
Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp

@@ -255,7 +255,7 @@ void SoftBodyMotionProperties::ApplyDihedralBendConstraints(const SoftBodyUpdate
 
 
 	float inv_dt_sq = 1.0f / Square(inContext.mSubStepDeltaTime);
 	float inv_dt_sq = 1.0f / Square(inContext.mSubStepDeltaTime);
 
 
-	for (const DihedralBend *b = mSettings->mDihedralBendConstraints.data() + inStartIndex; b < mSettings->mDihedralBendConstraints.data() + inEndIndex; ++b)
+	for (const DihedralBend *b = mSettings->mDihedralBendConstraints.data() + inStartIndex, *b_end = mSettings->mDihedralBendConstraints.data() + inEndIndex; b < b_end; ++b)
 	{
 	{
 		Vertex &v0 = mVertices[b->mVertex[0]];
 		Vertex &v0 = mVertices[b->mVertex[0]];
 		Vertex &v1 = mVertices[b->mVertex[1]];
 		Vertex &v1 = mVertices[b->mVertex[1]];
@@ -347,7 +347,7 @@ void SoftBodyMotionProperties::ApplyVolumeConstraints(const SoftBodyUpdateContex
 	float inv_dt_sq = 1.0f / Square(inContext.mSubStepDeltaTime);
 	float inv_dt_sq = 1.0f / Square(inContext.mSubStepDeltaTime);
 
 
 	// Satisfy volume constraints
 	// Satisfy volume constraints
-	for (const Volume *v = mSettings->mVolumeConstraints.data() + inStartIndex; v < mSettings->mVolumeConstraints.data() + inEndIndex; ++v)
+	for (const Volume *v = mSettings->mVolumeConstraints.data() + inStartIndex, *v_end = mSettings->mVolumeConstraints.data() + inEndIndex; v < v_end; ++v)
 	{
 	{
 		Vertex &v1 = mVertices[v->mVertex[0]];
 		Vertex &v1 = mVertices[v->mVertex[0]];
 		Vertex &v2 = mVertices[v->mVertex[1]];
 		Vertex &v2 = mVertices[v->mVertex[1]];
@@ -391,57 +391,66 @@ void SoftBodyMotionProperties::ApplyVolumeConstraints(const SoftBodyUpdateContex
 	}
 	}
 }
 }
 
 
-void SoftBodyMotionProperties::ApplySkinConstraints([[maybe_unused]] const SoftBodyUpdateContext &inContext)
+void SoftBodyMotionProperties::ApplySkinConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex)
 {
 {
 	// Early out if nothing to do
 	// Early out if nothing to do
 	if (mSettings->mSkinnedConstraints.empty() || !mEnableSkinConstraints)
 	if (mSettings->mSkinnedConstraints.empty() || !mEnableSkinConstraints)
 		return;
 		return;
 
 
-	JPH_ASSERT(mSkinStateTransform == inContext.mCenterOfMassTransform, "Skinning state is stale, artifacts will show!");
+	JPH_PROFILE_FUNCTION();
+
+	// We're going to iterate multiple times over the skin constraints, update the skinned position accordingly.
+	// 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.
+	float factor = inContext.mNextIteration.load(std::memory_order_relaxed) / float(mNumIterations);
+	float prev_factor = 1.0f - factor;
 
 
 	// Apply the constraints
 	// Apply the constraints
 	Vertex *vertices = mVertices.data();
 	Vertex *vertices = mVertices.data();
 	const SkinState *skin_states = mSkinState.data();
 	const SkinState *skin_states = mSkinState.data();
-	for (const Skinned &s : mSettings->mSkinnedConstraints)
+	for (const Skinned *s = mSettings->mSkinnedConstraints.data() + inStartIndex, *s_end = mSettings->mSkinnedConstraints.data() + inEndIndex; s < s_end; ++s)
 	{
 	{
-		Vertex &vertex = vertices[s.mVertex];
-		const SkinState &skin_state = skin_states[s.mVertex];
-		float max_distance = s.mMaxDistance * mSkinnedMaxDistanceMultiplier;
+		Vertex &vertex = vertices[s->mVertex];
+		const SkinState &skin_state = skin_states[s->mVertex];
+		float max_distance = s->mMaxDistance * mSkinnedMaxDistanceMultiplier;
+
+		// Calculate the skinned position by interpolating from previous to current position
+		Vec3 skin_pos = prev_factor * skin_state.mPreviousPosition + factor * skin_state.mPosition;
+
 		if (max_distance > 0.0f)
 		if (max_distance > 0.0f)
 		{
 		{
 			// Move vertex if it violated the back stop
 			// Move vertex if it violated the back stop
-			if (s.mBackStopDistance < max_distance)
+			if (s->mBackStopDistance < max_distance)
 			{
 			{
 				// Center of the back stop sphere
 				// Center of the back stop sphere
-				Vec3 center = skin_state.mPosition - skin_state.mNormal * (s.mBackStopDistance + s.mBackStopRadius);
+				Vec3 center = skin_pos - skin_state.mNormal * (s->mBackStopDistance + s->mBackStopRadius);
 
 
 				// Check if we're inside the back stop sphere
 				// Check if we're inside the back stop sphere
 				Vec3 delta = vertex.mPosition - center;
 				Vec3 delta = vertex.mPosition - center;
 				float delta_len_sq = delta.LengthSq();
 				float delta_len_sq = delta.LengthSq();
-				if (delta_len_sq < Square(s.mBackStopRadius))
+				if (delta_len_sq < Square(s->mBackStopRadius))
 				{
 				{
 					// Push the vertex to the surface of the back stop sphere
 					// Push the vertex to the surface of the back stop sphere
 					float delta_len = sqrt(delta_len_sq);
 					float delta_len = sqrt(delta_len_sq);
 					vertex.mPosition = delta_len > 0.0f?
 					vertex.mPosition = delta_len > 0.0f?
-						center + delta * (s.mBackStopRadius / delta_len)
-						: center + skin_state.mNormal * s.mBackStopRadius;
+						center + delta * (s->mBackStopRadius / delta_len)
+						: center + skin_state.mNormal * s->mBackStopRadius;
 				}
 				}
 			}
 			}
 
 
 			// Clamp vertex distance to max distance from skinned position
 			// Clamp vertex distance to max distance from skinned position
 			if (max_distance < FLT_MAX)
 			if (max_distance < FLT_MAX)
 			{
 			{
-				Vec3 delta = vertex.mPosition - skin_state.mPosition;
+				Vec3 delta = vertex.mPosition - skin_pos;
 				float delta_len_sq = delta.LengthSq();
 				float delta_len_sq = delta.LengthSq();
 				float max_distance_sq = Square(max_distance);
 				float max_distance_sq = Square(max_distance);
 				if (delta_len_sq > max_distance_sq)
 				if (delta_len_sq > max_distance_sq)
-					vertex.mPosition = skin_state.mPosition + delta * sqrt(max_distance_sq / delta_len_sq);
+					vertex.mPosition = skin_pos + delta * sqrt(max_distance_sq / delta_len_sq);
 			}
 			}
 		}
 		}
 		else
 		else
 		{
 		{
 			// Kinematic: Just update the vertex position
 			// Kinematic: Just update the vertex position
-			vertex.mPosition = skin_state.mPosition;
+			vertex.mPosition = skin_pos;
 		}
 		}
 	}
 	}
 }
 }
@@ -453,7 +462,7 @@ void SoftBodyMotionProperties::ApplyEdgeConstraints(const SoftBodyUpdateContext
 	float inv_dt_sq = 1.0f / Square(inContext.mSubStepDeltaTime);
 	float inv_dt_sq = 1.0f / Square(inContext.mSubStepDeltaTime);
 
 
 	// Satisfy edge constraints
 	// Satisfy edge constraints
-	for (const Edge *e = mSettings->mEdgeConstraints.data() + inStartIndex; e < mSettings->mEdgeConstraints.data() + inEndIndex; ++e)
+	for (const Edge *e = mSettings->mEdgeConstraints.data() + inStartIndex, *e_end = mSettings->mEdgeConstraints.data() + inEndIndex; e < e_end; ++e)
 	{
 	{
 		Vertex &v0 = mVertices[e->mVertex[0]];
 		Vertex &v0 = mVertices[e->mVertex[0]];
 		Vertex &v1 = mVertices[e->mVertex[1]];
 		Vertex &v1 = mVertices[e->mVertex[1]];
@@ -482,7 +491,7 @@ void SoftBodyMotionProperties::ApplyLRAConstraints(uint inStartIndex, uint inEnd
 
 
 	// Satisfy LRA constraints
 	// Satisfy LRA constraints
 	Vertex *vertices = mVertices.data();
 	Vertex *vertices = mVertices.data();
-	for (const LRA *lra = mSettings->mLRAConstraints.data() + inStartIndex; lra < mSettings->mLRAConstraints.data() + inEndIndex; ++lra)
+	for (const LRA *lra = mSettings->mLRAConstraints.data() + inStartIndex, *lra_end = mSettings->mLRAConstraints.data() + inEndIndex; lra < lra_end; ++lra)
 	{
 	{
 		JPH_ASSERT(lra->mVertex[0] < mVertices.size());
 		JPH_ASSERT(lra->mVertex[0] < mVertices.size());
 		JPH_ASSERT(lra->mVertex[1] < mVertices.size());
 		JPH_ASSERT(lra->mVertex[1] < mVertices.size());
@@ -672,6 +681,11 @@ void SoftBodyMotionProperties::UpdateSoftBodyState(SoftBodyUpdateContext &ioCont
 		for (Vertex &v : mVertices)
 		for (Vertex &v : mVertices)
 			v.mPosition -= delta;
 			v.mPosition -= delta;
 
 
+		// Update the skin state too since we will use this position as the previous position in the next update
+		for (SkinState &s : mSkinState)
+			s.mPosition -= delta;
+		mSkinStateTransform.SetTranslation(mSkinStateTransform.GetTranslation() - delta);
+
 		// Offset bounds to match new position
 		// Offset bounds to match new position
 		mLocalBounds.Translate(-delta);
 		mLocalBounds.Translate(-delta);
 		mLocalPredictedBounds.Translate(-delta);
 		mLocalPredictedBounds.Translate(-delta);
@@ -769,7 +783,7 @@ SoftBodyMotionProperties::EStatus SoftBodyMotionProperties::ParallelDetermineCol
 void SoftBodyMotionProperties::ProcessGroup(const SoftBodyUpdateContext &ioContext, uint inGroupIndex)
 void SoftBodyMotionProperties::ProcessGroup(const SoftBodyUpdateContext &ioContext, uint inGroupIndex)
 {
 {
 	// Determine start and end
 	// Determine start and end
-	SoftBodySharedSettings::UpdateGroup start { 0, 0, 0, 0 };
+	SoftBodySharedSettings::UpdateGroup start { 0, 0, 0, 0, 0 };
 	const SoftBodySharedSettings::UpdateGroup &prev = inGroupIndex > 0? mSettings->mUpdateGroups[inGroupIndex - 1] : start;
 	const SoftBodySharedSettings::UpdateGroup &prev = inGroupIndex > 0? mSettings->mUpdateGroups[inGroupIndex - 1] : start;
 	const SoftBodySharedSettings::UpdateGroup &current = mSettings->mUpdateGroups[inGroupIndex];
 	const SoftBodySharedSettings::UpdateGroup &current = mSettings->mUpdateGroups[inGroupIndex];
 
 
@@ -779,6 +793,9 @@ void SoftBodyMotionProperties::ProcessGroup(const SoftBodyUpdateContext &ioConte
 	// Process bend constraints
 	// Process bend constraints
 	ApplyDihedralBendConstraints(ioContext, prev.mDihedralBendEndIndex, current.mDihedralBendEndIndex);
 	ApplyDihedralBendConstraints(ioContext, prev.mDihedralBendEndIndex, current.mDihedralBendEndIndex);
 
 
+	// Process skinned constraints
+	ApplySkinConstraints(ioContext, prev.mSkinnedEndIndex, current.mSkinnedEndIndex);
+
 	// Process edges
 	// Process edges
 	ApplyEdgeConstraints(ioContext, prev.mEdgeEndIndex, current.mEdgeEndIndex);
 	ApplyEdgeConstraints(ioContext, prev.mEdgeEndIndex, current.mEdgeEndIndex);
 
 
@@ -791,7 +808,7 @@ SoftBodyMotionProperties::EStatus SoftBodyMotionProperties::ParallelApplyConstra
 	uint num_groups = (uint)mSettings->mUpdateGroups.size();
 	uint num_groups = (uint)mSettings->mUpdateGroups.size();
 	JPH_ASSERT(num_groups > 0, "SoftBodySharedSettings::Optimize should have been called!");
 	JPH_ASSERT(num_groups > 0, "SoftBodySharedSettings::Optimize should have been called!");
 	--num_groups; // Last group is the non-parallel group, we don't want to execute it in parallel
 	--num_groups; // Last group is the non-parallel group, we don't want to execute it in parallel
-		
+
 	// 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)
 	// 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)
 	uint next_group = ioContext.mNextConstraintGroup.load(memory_order_relaxed);
 	uint next_group = ioContext.mNextConstraintGroup.load(memory_order_relaxed);
 	if (next_group < num_groups || (num_groups == 0 && next_group == 0))
 	if (next_group < num_groups || (num_groups == 0 && next_group == 0))
@@ -820,8 +837,6 @@ SoftBodyMotionProperties::EStatus SoftBodyMotionProperties::ParallelApplyConstra
 
 
 				ApplyCollisionConstraintsAndUpdateVelocities(ioContext);
 				ApplyCollisionConstraintsAndUpdateVelocities(ioContext);
 
 
-				ApplySkinConstraints(ioContext);
-
 				uint iteration = ioContext.mNextIteration.fetch_add(1, memory_order_relaxed);
 				uint iteration = ioContext.mNextIteration.fetch_add(1, memory_order_relaxed);
 				if (iteration < mNumIterations)
 				if (iteration < mNumIterations)
 				{
 				{
@@ -900,7 +915,9 @@ void SoftBodyMotionProperties::SkinVertices(RMat44Arg inCenterOfMassTransform, c
 			JPH_ASSERT(w.mInvBindIndex < num_skin_matrices);
 			JPH_ASSERT(w.mInvBindIndex < num_skin_matrices);
 			pos += w.mWeight * (skin_matrices[w.mInvBindIndex] * bind_pos);
 			pos += w.mWeight * (skin_matrices[w.mInvBindIndex] * bind_pos);
 		}
 		}
-		mSkinState[s.mVertex].mPosition = pos;
+		SkinState &skin_state = mSkinState[s.mVertex];
+		skin_state.mPreviousPosition = skin_state.mPosition;
+		skin_state.mPosition = pos;
 	}
 	}
 
 
 	// Calculate the normals
 	// Calculate the normals
@@ -935,7 +952,9 @@ void SoftBodyMotionProperties::SkinVertices(RMat44Arg inCenterOfMassTransform, c
 		for (const Skinned &s : mSettings->mSkinnedConstraints)
 		for (const Skinned &s : mSettings->mSkinnedConstraints)
 		{
 		{
 			Vertex &vertex = mVertices[s.mVertex];
 			Vertex &vertex = mVertices[s.mVertex];
-			vertex.mPosition = mSkinState[s.mVertex].mPosition;
+			SkinState &skin_state = mSkinState[s.mVertex];
+			skin_state.mPreviousPosition = skin_state.mPosition;
+			vertex.mPosition = skin_state.mPosition;
 			vertex.mVelocity = Vec3::sZero();
 			vertex.mVelocity = Vec3::sZero();
 		}
 		}
 	}
 	}
@@ -1017,7 +1036,7 @@ inline void SoftBodyMotionProperties::DrawConstraints(ESoftBodyConstraintColor i
 
 
 void SoftBodyMotionProperties::DrawEdgeConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const
 void SoftBodyMotionProperties::DrawEdgeConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const
 {
 {
-	DrawConstraints(inConstraintColor, 
+	DrawConstraints(inConstraintColor,
 		[](const SoftBodySharedSettings::UpdateGroup &inGroup) {
 		[](const SoftBodySharedSettings::UpdateGroup &inGroup) {
 			return inGroup.mEdgeEndIndex;
 			return inGroup.mEdgeEndIndex;
 		},
 		},
@@ -1030,7 +1049,7 @@ void SoftBodyMotionProperties::DrawEdgeConstraints(DebugRenderer *inRenderer, RM
 
 
 void SoftBodyMotionProperties::DrawBendConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const
 void SoftBodyMotionProperties::DrawBendConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const
 {
 {
-	DrawConstraints(inConstraintColor, 
+	DrawConstraints(inConstraintColor,
 		[](const SoftBodySharedSettings::UpdateGroup &inGroup) {
 		[](const SoftBodySharedSettings::UpdateGroup &inGroup) {
 			return inGroup.mDihedralBendEndIndex;
 			return inGroup.mDihedralBendEndIndex;
 		},
 		},
@@ -1054,7 +1073,7 @@ void SoftBodyMotionProperties::DrawBendConstraints(DebugRenderer *inRenderer, RM
 
 
 void SoftBodyMotionProperties::DrawVolumeConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const
 void SoftBodyMotionProperties::DrawVolumeConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const
 {
 {
-	DrawConstraints(inConstraintColor, 
+	DrawConstraints(inConstraintColor,
 		[](const SoftBodySharedSettings::UpdateGroup &inGroup) {
 		[](const SoftBodySharedSettings::UpdateGroup &inGroup) {
 			return inGroup.mVolumeEndIndex;
 			return inGroup.mVolumeEndIndex;
 		},
 		},
@@ -1074,19 +1093,24 @@ void SoftBodyMotionProperties::DrawVolumeConstraints(DebugRenderer *inRenderer,
 		Color::sYellow);
 		Color::sYellow);
 }
 }
 
 
-void SoftBodyMotionProperties::DrawSkinConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const
+void SoftBodyMotionProperties::DrawSkinConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const
 {
 {
-	for (const Skinned &s : mSettings->mSkinnedConstraints)
-	{
-		const SkinState &skin_state = mSkinState[s.mVertex];
-		DebugRenderer::sInstance->DrawArrow(mSkinStateTransform * skin_state.mPosition, mSkinStateTransform * (skin_state.mPosition + 0.1f * skin_state.mNormal), Color::sOrange, 0.01f);
-		DebugRenderer::sInstance->DrawLine(mSkinStateTransform * skin_state.mPosition, inCenterOfMassTransform * mVertices[s.mVertex].mPosition, Color::sBlue);
-	}
+	DrawConstraints(inConstraintColor,
+		[](const SoftBodySharedSettings::UpdateGroup &inGroup) {
+			return inGroup.mSkinnedEndIndex;
+		},
+		[this, inRenderer, &inCenterOfMassTransform](uint inIndex, ColorArg inColor) {
+			const Skinned &s = mSettings->mSkinnedConstraints[inIndex];
+			const SkinState &skin_state = mSkinState[s.mVertex];
+			inRenderer->DrawArrow(mSkinStateTransform * skin_state.mPosition, mSkinStateTransform * (skin_state.mPosition + 0.1f * skin_state.mNormal), inColor, 0.01f);
+			inRenderer->DrawLine(mSkinStateTransform * skin_state.mPosition, inCenterOfMassTransform * mVertices[s.mVertex].mPosition, Color::sBlue);
+		},
+		Color::sOrange);
 }
 }
 
 
 void SoftBodyMotionProperties::DrawLRAConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const
 void SoftBodyMotionProperties::DrawLRAConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const
 {
 {
-	DrawConstraints(inConstraintColor, 
+	DrawConstraints(inConstraintColor,
 		[](const SoftBodySharedSettings::UpdateGroup &inGroup) {
 		[](const SoftBodySharedSettings::UpdateGroup &inGroup) {
 			return inGroup.mLRAEndIndex;
 			return inGroup.mLRAEndIndex;
 		},
 		},

+ 3 - 2
Jolt/Physics/SoftBody/SoftBodyMotionProperties.h

@@ -102,7 +102,7 @@ public:
 	void								DrawEdgeConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const;
 	void								DrawEdgeConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const;
 	void								DrawBendConstraints(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								DrawVolumeConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const;
-	void								DrawSkinConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const;
+	void								DrawSkinConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const;
 	void								DrawLRAConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const;
 	void								DrawLRAConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const;
 	void								DrawPredictedBounds(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const;
 	void								DrawPredictedBounds(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const;
 #endif // JPH_DEBUG_RENDERER
 #endif // JPH_DEBUG_RENDERER
@@ -191,6 +191,7 @@ private:
 	// Information about the state of all skinned vertices
 	// Information about the state of all skinned vertices
 	struct SkinState
 	struct SkinState
 	{
 	{
+		Vec3							mPreviousPosition = Vec3::sNaN();
 		Vec3							mPosition = Vec3::sNaN();
 		Vec3							mPosition = Vec3::sNaN();
 		Vec3							mNormal = Vec3::sNaN();
 		Vec3							mNormal = Vec3::sNaN();
 	};
 	};
@@ -211,7 +212,7 @@ private:
 	void								ApplyVolumeConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex);
 	void								ApplyVolumeConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex);
 
 
 	/// Enforce all skin constraints
 	/// Enforce all skin constraints
-	void								ApplySkinConstraints(const SoftBodyUpdateContext &inContext);
+	void								ApplySkinConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex);
 
 
 	/// Enforce all edge constraints
 	/// Enforce all edge constraints
 	void								ApplyEdgeConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex);
 	void								ApplyEdgeConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex);

+ 39 - 3
Jolt/Physics/SoftBody/SoftBodySharedSettings.cpp

@@ -463,6 +463,9 @@ void SoftBodySharedSettings::CalculateSkinnedConstraintNormals()
 
 
 void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)
 void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)
 {
 {
+	// Clear any previous results
+	mUpdateGroups.clear();
+
 	// Create a list of connected vertices
 	// Create a list of connected vertices
 	struct Connection
 	struct Connection
 	{
 	{
@@ -487,7 +490,7 @@ void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)
 
 
 				swap(inV1, inV2);
 				swap(inV1, inV2);
 			}
 			}
-		}; 
+		};
 	for (const Edge &c : mEdgeConstraints)
 	for (const Edge &c : mEdgeConstraints)
 		add_connection(c.mVertex[0], c.mVertex[1]);
 		add_connection(c.mVertex[0], c.mVertex[1]);
 	for (const LRA &c : mLRAConstraints)
 	for (const LRA &c : mLRAConstraints)
@@ -510,6 +513,7 @@ void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)
 		add_connection(c.mVertex[1], c.mVertex[3]);
 		add_connection(c.mVertex[1], c.mVertex[3]);
 		add_connection(c.mVertex[2], c.mVertex[3]);
 		add_connection(c.mVertex[2], c.mVertex[3]);
 	}
 	}
+	// Skinned constraints only update 1 vertex, so we don't need special logic here
 
 
 	// Maps each of the vertices to a group index
 	// Maps each of the vertices to a group index
 	Array<int> group_idx;
 	Array<int> group_idx;
@@ -636,13 +640,14 @@ void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)
 	{
 	{
 		uint			GetSize() const
 		uint			GetSize() const
 		{
 		{
-			return (uint)mEdgeConstraints.size() + (uint)mLRAConstraints.size() + (uint)mDihedralBendConstraints.size() + (uint)mVolumeConstraints.size();
+			return (uint)mEdgeConstraints.size() + (uint)mLRAConstraints.size() + (uint)mDihedralBendConstraints.size() + (uint)mVolumeConstraints.size() + (uint)mSkinnedConstraints.size();
 		}
 		}
 
 
 		Array<uint>		mEdgeConstraints;
 		Array<uint>		mEdgeConstraints;
 		Array<uint>		mLRAConstraints;
 		Array<uint>		mLRAConstraints;
 		Array<uint>		mDihedralBendConstraints;
 		Array<uint>		mDihedralBendConstraints;
 		Array<uint>		mVolumeConstraints;
 		Array<uint>		mVolumeConstraints;
+		Array<uint>		mSkinnedConstraints;
 	};
 	};
 	Array<Group> groups;
 	Array<Group> groups;
 	groups.resize(current_group_idx + 1); // + non parallel group
 	groups.resize(current_group_idx + 1); // + non parallel group
@@ -690,6 +695,12 @@ void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)
 		else // In different groups -> parallel group
 		else // In different groups -> parallel group
 			groups.back().mVolumeConstraints.push_back(uint(&v - mVolumeConstraints.data()));
 			groups.back().mVolumeConstraints.push_back(uint(&v - mVolumeConstraints.data()));
 	}
 	}
+	for (const Skinned &s : mSkinnedConstraints)
+	{
+		int g1 = group_idx[s.mVertex];
+		JPH_ASSERT(g1 >= 0);
+		groups[g1].mSkinnedConstraints.push_back(uint(&s - mSkinnedConstraints.data()));
+	}
 
 
 	// Sort the parallel groups from big to small (this means the big groups will be scheduled first and have more time to complete)
 	// Sort the parallel groups from big to small (this means the big groups will be scheduled first and have more time to complete)
 	QuickSort(groups.begin(), groups.end() - 1, [](const Group &inLHS, const Group &inRHS) { return inLHS.GetSize() > inRHS.GetSize(); });
 	QuickSort(groups.begin(), groups.end() - 1, [](const Group &inLHS, const Group &inRHS) { return inLHS.GetSize() > inRHS.GetSize(); });
@@ -792,6 +803,19 @@ void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)
 
 
 			return inLHS < inRHS;
 			return inLHS < inRHS;
 		});
 		});
+
+		// Sort the skinned constraints
+		QuickSort(group.mSkinnedConstraints.begin(), group.mSkinnedConstraints.end(), [this](uint inLHS, uint inRHS)
+			{
+				const Skinned &s1 = mSkinnedConstraints[inLHS];
+				const Skinned &s2 = mSkinnedConstraints[inRHS];
+
+				// Order the skinned constraints so that the ones with the smallest index go first (hoping to get better cache locality when we process the edges).
+				if (s1.mVertex != s2.mVertex)
+					return s1.mVertex < s2.mVertex;
+
+				return inLHS < inRHS;
+			});
 	}
 	}
 
 
 	// Temporary store constraints as we reorder them
 	// Temporary store constraints as we reorder them
@@ -815,6 +839,11 @@ void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)
 	mVolumeConstraints.reserve(temp_volume.size());
 	mVolumeConstraints.reserve(temp_volume.size());
 	outResults.mVolumeRemap.reserve(temp_volume.size());
 	outResults.mVolumeRemap.reserve(temp_volume.size());
 
 
+	Array<Skinned> temp_skinned;
+	temp_skinned.swap(mSkinnedConstraints);
+	mSkinnedConstraints.reserve(temp_skinned.size());
+	outResults.mSkinnedRemap.reserve(temp_skinned.size());
+
 	// Finalize update groups
 	// Finalize update groups
 	for (const Group &group : groups)
 	for (const Group &group : groups)
 	{
 	{
@@ -846,8 +875,15 @@ void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)
 			outResults.mVolumeRemap.push_back(idx);
 			outResults.mVolumeRemap.push_back(idx);
 		}
 		}
 
 
+		// Reorder skinned constraints for this group
+		for (uint idx : group.mSkinnedConstraints)
+		{
+			mSkinnedConstraints.push_back(temp_skinned[idx]);
+			outResults.mSkinnedRemap.push_back(idx);
+		}
+
 		// Store end indices
 		// Store end indices
-		mUpdateGroups.push_back({ (uint)mEdgeConstraints.size(), (uint)mLRAConstraints.size(), (uint)mDihedralBendConstraints.size(), (uint)mVolumeConstraints.size() });
+		mUpdateGroups.push_back({ (uint)mEdgeConstraints.size(), (uint)mLRAConstraints.size(), (uint)mDihedralBendConstraints.size(), (uint)mVolumeConstraints.size(), (uint)mSkinnedConstraints.size() });
 	}
 	}
 
 
 	// Free closest kinematic buffer
 	// Free closest kinematic buffer

+ 2 - 0
Jolt/Physics/SoftBody/SoftBodySharedSettings.h

@@ -80,6 +80,7 @@ public:
 		Array<uint>		mLRARemap;									///< Maps old LRA index to new LRA 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>		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>		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.
 	/// Optimize the soft body settings for simulation. This will reorder constraints so they can be executed in parallel.
@@ -320,6 +321,7 @@ private:
 		uint			mLRAEndIndex;								///< The end index of the LRA 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			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			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<ClosestKinematic> mClosestKinematic;						///< The closest kinematic vertex to each vertex in mVertices

+ 4 - 0
Samples/Tests/SoftBody/SoftBodySkinnedConstraintTest.cpp

@@ -124,6 +124,10 @@ void SoftBodySkinnedConstraintTest::Initialize()
 	// Calculate the information needed for skinned constraints
 	// Calculate the information needed for skinned constraints
 	settings->CalculateSkinnedConstraintNormals();
 	settings->CalculateSkinnedConstraintNormals();
 
 
+	// Optimize the settings (note that this is the second time we call this, the first time was in SoftBodyCreator::CreateCloth,
+	// this is a bit wasteful but we must do it because we added more constraints)
+	settings->Optimize();
+
 	// Create the body
 	// Create the body
 	SoftBodyCreationSettings cloth(settings, body_translation, Quat::sIdentity(), Layers::MOVING);
 	SoftBodyCreationSettings cloth(settings, body_translation, Quat::sIdentity(), Layers::MOVING);
 	mBody = mBodyInterface->CreateSoftBody(cloth);
 	mBody = mBodyInterface->CreateSoftBody(cloth);