Browse Source

Sorting edge and bend constraints based on distance to kinematic vertex. (#1028)

This makes the cloth stretch less by a small but measurable amount.
Jorrit Rouwe 1 year ago
parent
commit
259e7c8830

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

@@ -493,14 +493,25 @@ void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)
 			edge_groups[i].clear();
 		}
 
-	// Order the edges so that the ones with the smallest index go first (hoping to get better cache locality when we process the edges).
-	// Note we could also re-order the vertices but that would be much more of a burden to the end user
+	// Make sure we know the closest kinematic vertex so we can sort
+	CalculateClosestKinematic();
+
+	// Sort the edge constraints
 	for (Array<uint> &group : edge_groups)
 		QuickSort(group.begin(), group.end(), [this](uint inLHS, uint inRHS)
 			{
 				const Edge &e1 = mEdgeConstraints[inLHS];
 				const Edge &e2 = mEdgeConstraints[inRHS];
-				return min(e1.mVertex[0], e1.mVertex[1]) < min(e2.mVertex[0], e2.mVertex[1]);
+
+				// First sort so that the edge with the smallest distance to a kinematic vertex comes first
+				float d1 = min(mClosestKinematic[e1.mVertex[0]].mDistance, mClosestKinematic[e1.mVertex[1]].mDistance);
+				float d2 = min(mClosestKinematic[e2.mVertex[0]].mDistance, mClosestKinematic[e2.mVertex[1]].mDistance);
+				if (d1 != d2)
+					return d1 < d2;
+
+				// Order the edges so that the ones with the smallest index go first (hoping to get better cache locality when we process the edges).
+				// Note we could also re-order the vertices but that would be much more of a burden to the end user
+				return e1.GetMinVertexIndex() < e2.GetMinVertexIndex();
 			});
 
 	// Assign the edges to groups and reorder them
@@ -522,6 +533,36 @@ void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)
 	if (edge_groups[cNonParallelGroupIdx].empty())
 		mEdgeGroupEndIndices.push_back((uint)mEdgeConstraints.size());
 
+	// Sort the bend constraints
+	outResults.mDihedralBendRemap.resize(mDihedralBendConstraints.size());
+	for (int i = 0; i < (int)mDihedralBendConstraints.size(); ++i)
+		outResults.mDihedralBendRemap[i] = i;
+	QuickSort(outResults.mDihedralBendRemap.begin(), outResults.mDihedralBendRemap.end(), [this](uint inLHS, uint inRHS)
+		{
+			const DihedralBend &b1 = mDihedralBendConstraints[inLHS];
+			const DihedralBend &b2 = mDihedralBendConstraints[inRHS];
+
+			// First sort so that the constraint with the smallest distance to a kinematic vertex comes first
+			float d1 = min(
+						min(mClosestKinematic[b1.mVertex[0]].mDistance, mClosestKinematic[b1.mVertex[1]].mDistance),
+						min(mClosestKinematic[b1.mVertex[2]].mDistance, mClosestKinematic[b1.mVertex[3]].mDistance));
+			float d2 = min(
+						min(mClosestKinematic[b2.mVertex[0]].mDistance, mClosestKinematic[b2.mVertex[1]].mDistance),
+						min(mClosestKinematic[b2.mVertex[2]].mDistance, mClosestKinematic[b2.mVertex[3]].mDistance));
+			if (d1 != d2)
+				return d1 < d2;
+
+			// Order constraints so that the ones with the smallest index go first
+			return b1.GetMinVertexIndex() < b2.GetMinVertexIndex();
+		});
+
+	// Reorder the bend constraints
+	Array<DihedralBend> temp_bends;
+	temp_bends.swap(mDihedralBendConstraints);
+	mDihedralBendConstraints.reserve(temp_bends.size());
+	for (uint idx : outResults.mDihedralBendRemap)
+		mDihedralBendConstraints.push_back(temp_bends[idx]);
+
 	// Free closest kinematic buffer
 	mClosestKinematic.clear();
 	mClosestKinematic.shrink_to_fit();

+ 14 - 1
Jolt/Physics/SoftBody/SoftBodySharedSettings.h

@@ -77,6 +77,7 @@ public:
 	{
 	public:
 		Array<uint>		mEdgeRemap;									///< Maps old edge index to new edge index
+		Array<uint>		mDihedralBendRemap;							///< Maps old dihedral bend index to new dihedral bend index
 	};
 
 	/// Optimize the soft body settings for simulation. This will reorder constraints so they can be executed in parallel.
@@ -146,6 +147,9 @@ public:
 						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
@@ -177,6 +181,9 @@ public:
 						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).
@@ -191,6 +198,9 @@ public:
 						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 tetrhedron
 		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
@@ -267,6 +277,9 @@ public:
 						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
 	};
@@ -295,7 +308,7 @@ private:
 		float			mDistance = FLT_MAX;						///< Distance to the closest kinematic vertex
 	};
 
-	/// Calculate te closest kinematic vertex array
+	/// Calculate the closest kinematic vertex array
 	void				CalculateClosestKinematic();
 
 	/// Get the size of an edge group (edge groups can run in parallel)