Browse Source

Made active edge threshold angle configurable (#670)

The default angle of 5 degrees is perhaps a little bit too small, so now it is configurable. With an angle of 50 degrees the colliding with edge artifacts in the ActiveEdgesTest are completely gone and I don't see any artifacts in penetration resolution.

Should fix #630
Jorrit Rouwe 2 years ago
parent
commit
12adfda962

+ 6 - 5
Jolt/Physics/Collision/ActiveEdges.h

@@ -15,19 +15,20 @@ namespace ActiveEdges
 	/// @param inNormal1 Triangle normal of triangle on the left side of the edge (when looking along the edge from the top)
 	/// @param inNormal1 Triangle normal of triangle on the left side of the edge (when looking along the edge from the top)
 	/// @param inNormal2 Triangle normal of triangle on the right side of the edge
 	/// @param inNormal2 Triangle normal of triangle on the right side of the edge
 	/// @param inEdgeDirection Vector that points along the edge
 	/// @param inEdgeDirection Vector that points along the edge
-	inline static bool					IsEdgeActive(Vec3Arg inNormal1, Vec3Arg inNormal2, Vec3Arg inEdgeDirection)
+	/// @param inCosThresholdAngle Cosine of the threshold angle (if the angle between the two triangles is bigger than this, the edge is active, note that a concave edge is always inactive)
+	inline static bool					IsEdgeActive(Vec3Arg inNormal1, Vec3Arg inNormal2, Vec3Arg inEdgeDirection, float inCosThresholdAngle)
 	{
 	{
 		// If normals are opposite the edges are active (the triangles are back to back)
 		// If normals are opposite the edges are active (the triangles are back to back)
 		float cos_angle_normals = inNormal1.Dot(inNormal2);
 		float cos_angle_normals = inNormal1.Dot(inNormal2);
-		if (cos_angle_normals < -0.99984769515639123915701155881391f) // cos(179 degrees)
+		if (cos_angle_normals < -0.999848f) // cos(179 degrees)
 			return true;
 			return true;
-			
+
 		// Check if concave edge, if so we are not active
 		// Check if concave edge, if so we are not active
 		if (inNormal1.Cross(inNormal2).Dot(inEdgeDirection) < 0.0f)
 		if (inNormal1.Cross(inNormal2).Dot(inEdgeDirection) < 0.0f)
 			return false;
 			return false;
 
 
 		// Convex edge, active when angle bigger than threshold
 		// Convex edge, active when angle bigger than threshold
-		return cos_angle_normals < 0.99619469809174553229501040247389f; // cos(5 degrees)
+		return cos_angle_normals < inCosThresholdAngle;
 	}
 	}
 
 
 	/// Replace normal by triangle normal if a hit is hitting an inactive edge
 	/// Replace normal by triangle normal if a hit is hitting an inactive edge
@@ -58,7 +59,7 @@ namespace ActiveEdges
 
 
 		// Some edges are active.
 		// Some edges are active.
 		// If normal is parallel to the triangle normal we don't need to check the active edges.
 		// If normal is parallel to the triangle normal we don't need to check the active edges.
-		if (inTriangleNormal.Dot(inNormal) > 0.99619469809174553229501040247389f * normal_length * triangle_normal_length) // cos(5 degrees)
+		if (inTriangleNormal.Dot(inNormal) > 0.996195f * normal_length * triangle_normal_length) // cos(5 degrees)
 			return inNormal;
 			return inNormal;
 
 
 		const float cEpsilon = 1.0e-4f;
 		const float cEpsilon = 1.0e-4f;

+ 6 - 5
Jolt/Physics/Collision/Shape/HeightFieldShape.cpp

@@ -53,6 +53,7 @@ JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(HeightFieldShapeSettings)
 	JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mBitsPerSample)
 	JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mBitsPerSample)
 	JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mMaterialIndices)
 	JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mMaterialIndices)
 	JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mMaterials)
 	JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mMaterials)
+	JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mActiveEdgeCosThresholdAngle)
 }
 }
 
 
 const uint HeightFieldShape::sGridOffsets[] =
 const uint HeightFieldShape::sGridOffsets[] =
@@ -194,7 +195,7 @@ uint32 HeightFieldShapeSettings::CalculateBitsPerSampleForError(float inMaxError
 	return bits_per_sample;
 	return bits_per_sample;
 }
 }
 
 
-void HeightFieldShape::CalculateActiveEdges()
+void HeightFieldShape::CalculateActiveEdges(const HeightFieldShapeSettings &inSettings)
 {
 {
 	// Store active edges. The triangles are organized like this:
 	// Store active edges. The triangles are organized like this:
 	//  +       +
 	//  +       +
@@ -255,9 +256,9 @@ void HeightFieldShape::CalculateActiveEdges()
 
 
 			// Calculate the edge flags (3 bits)
 			// Calculate the edge flags (3 bits)
 			uint offset = 2 * (count_min_1 * y + x);
 			uint offset = 2 * (count_min_1 * y + x);
-			bool edge0_active = x == 0 || ActiveEdges::IsEdgeActive(normals[offset], normals[offset - 1], x1y2 - x1y1);
-			bool edge1_active = y == count_min_1 - 1 || ActiveEdges::IsEdgeActive(normals[offset], normals[offset + 2 * count_min_1 + 1], x2y2 - x1y2);
-			bool edge2_active = ActiveEdges::IsEdgeActive(normals[offset], normals[offset + 1], x1y1 - x2y2);
+			bool edge0_active = x == 0 || ActiveEdges::IsEdgeActive(normals[offset], normals[offset - 1], x1y2 - x1y1, inSettings.mActiveEdgeCosThresholdAngle);
+			bool edge1_active = y == count_min_1 - 1 || ActiveEdges::IsEdgeActive(normals[offset], normals[offset + 2 * count_min_1 + 1], x2y2 - x1y2, inSettings.mActiveEdgeCosThresholdAngle);
+			bool edge2_active = ActiveEdges::IsEdgeActive(normals[offset], normals[offset + 1], x1y1 - x2y2, inSettings.mActiveEdgeCosThresholdAngle);
 			uint16 edge_flags = (edge0_active? 0b001 : 0) | (edge1_active? 0b010 : 0) | (edge2_active? 0b100 : 0);
 			uint16 edge_flags = (edge0_active? 0b001 : 0) | (edge1_active? 0b010 : 0) | (edge2_active? 0b100 : 0);
 
 
 			// Store the edge flags in the array
 			// Store the edge flags in the array
@@ -599,7 +600,7 @@ HeightFieldShape::HeightFieldShape(const HeightFieldShapeSettings &inSettings, S
 		}
 		}
 
 
 	// Calculate the active edges
 	// Calculate the active edges
-	CalculateActiveEdges();
+	CalculateActiveEdges(inSettings);
 
 
 	// Compress material indices
 	// Compress material indices
 	if (mMaterials.size() > 1)
 	if (mMaterials.size() > 1)

+ 6 - 1
Jolt/Physics/Collision/Shape/HeightFieldShape.h

@@ -86,6 +86,11 @@ public:
 
 
 	/// The materials of square at (x, y) is: mMaterials[mMaterialIndices[x + y * (mSampleCount - 1)]]
 	/// The materials of square at (x, y) is: mMaterials[mMaterialIndices[x + y * (mSampleCount - 1)]]
 	PhysicsMaterialList				mMaterials;
 	PhysicsMaterialList				mMaterials;
+
+	/// Cosine of the threshold angle (if the angle between the two triangles is bigger than this, the edge is active, note that a concave edge is always inactive).
+	/// Setting this value too small can cause ghost collisions with edges, setting it too big can cause depenetration artifacts (objects not depenetrating quickly).
+	/// Valid ranges are between cos(0 degrees) and cos(90 degrees). The default value is cos(5 degrees).
+	float							mActiveEdgeCosThresholdAngle = 0.996195f;							// cos(5 degrees)
 };
 };
 
 
 /// A height field shape. Cannot be used as a dynamic object.
 /// A height field shape. Cannot be used as a dynamic object.
@@ -191,7 +196,7 @@ private:
 	void							CacheValues();
 	void							CacheValues();
 
 
 	/// Calculate bit mask for all active edges in the heightfield
 	/// Calculate bit mask for all active edges in the heightfield
-	void							CalculateActiveEdges();
+	void							CalculateActiveEdges(const HeightFieldShapeSettings &inSettings);
 
 
 	/// Store material indices in the least amount of bits per index possible
 	/// Store material indices in the least amount of bits per index possible
 	void							StoreMaterialIndices(const HeightFieldShapeSettings &inSettings);
 	void							StoreMaterialIndices(const HeightFieldShapeSettings &inSettings);

+ 10 - 9
Jolt/Physics/Collision/Shape/MeshShape.cpp

@@ -52,6 +52,7 @@ JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(MeshShapeSettings)
 	JPH_ADD_ATTRIBUTE(MeshShapeSettings, mIndexedTriangles)
 	JPH_ADD_ATTRIBUTE(MeshShapeSettings, mIndexedTriangles)
 	JPH_ADD_ATTRIBUTE(MeshShapeSettings, mMaterials)
 	JPH_ADD_ATTRIBUTE(MeshShapeSettings, mMaterials)
 	JPH_ADD_ATTRIBUTE(MeshShapeSettings, mMaxTrianglesPerLeaf)
 	JPH_ADD_ATTRIBUTE(MeshShapeSettings, mMaxTrianglesPerLeaf)
+	JPH_ADD_ATTRIBUTE(MeshShapeSettings, mActiveEdgeCosThresholdAngle)
 }
 }
 
 
 // Codecs this mesh shape is using
 // Codecs this mesh shape is using
@@ -180,7 +181,7 @@ MeshShape::MeshShape(const MeshShapeSettings &inSettings, ShapeResult &outResult
 
 
 	// Fill in active edge bits
 	// Fill in active edge bits
 	IndexedTriangleList indexed_triangles = inSettings.mIndexedTriangles; // Copy indices since we're adding the 'active edge' flag
 	IndexedTriangleList indexed_triangles = inSettings.mIndexedTriangles; // Copy indices since we're adding the 'active edge' flag
-	sFindActiveEdges(inSettings.mTriangleVertices, indexed_triangles);
+	sFindActiveEdges(inSettings, indexed_triangles);
 
 
 	// Create triangle splitter
 	// Create triangle splitter
 	TriangleSplitterBinning splitter(inSettings.mTriangleVertices, indexed_triangles);
 	TriangleSplitterBinning splitter(inSettings.mTriangleVertices, indexed_triangles);
@@ -216,7 +217,7 @@ MeshShape::MeshShape(const MeshShapeSettings &inSettings, ShapeResult &outResult
 	outResult.Set(this);
 	outResult.Set(this);
 }
 }
 
 
-void MeshShape::sFindActiveEdges(const VertexList &inVertices, IndexedTriangleList &ioIndices)
+void MeshShape::sFindActiveEdges(const MeshShapeSettings &inSettings, IndexedTriangleList &ioIndices)
 {
 {
 	// A struct to hold the two vertex indices of an edge
 	// A struct to hold the two vertex indices of an edge
 	struct Edge
 	struct Edge
@@ -301,19 +302,19 @@ void MeshShape::sFindActiveEdges(const VertexList &inVertices, IndexedTriangleLi
 			uint edge_idx2 = edge.first.GetIndexInTriangle(triangle2);
 			uint edge_idx2 = edge.first.GetIndexInTriangle(triangle2);
 
 
 			// Construct a plane for triangle 1 (e1 = edge vertex 1, e2 = edge vertex 2, op = opposing vertex)
 			// Construct a plane for triangle 1 (e1 = edge vertex 1, e2 = edge vertex 2, op = opposing vertex)
-			Vec3 triangle1_e1 = Vec3(inVertices[triangle1.mIdx[edge_idx1]]);
-			Vec3 triangle1_e2 = Vec3(inVertices[triangle1.mIdx[(edge_idx1 + 1) % 3]]);
-			Vec3 triangle1_op = Vec3(inVertices[triangle1.mIdx[(edge_idx1 + 2) % 3]]);
+			Vec3 triangle1_e1 = Vec3(inSettings.mTriangleVertices[triangle1.mIdx[edge_idx1]]);
+			Vec3 triangle1_e2 = Vec3(inSettings.mTriangleVertices[triangle1.mIdx[(edge_idx1 + 1) % 3]]);
+			Vec3 triangle1_op = Vec3(inSettings.mTriangleVertices[triangle1.mIdx[(edge_idx1 + 2) % 3]]);
 			Plane triangle1_plane = Plane::sFromPointsCCW(triangle1_e1, triangle1_e2, triangle1_op);
 			Plane triangle1_plane = Plane::sFromPointsCCW(triangle1_e1, triangle1_e2, triangle1_op);
 
 
 			// Construct a plane for triangle 2
 			// Construct a plane for triangle 2
-			Vec3 triangle2_e1 = Vec3(inVertices[triangle2.mIdx[edge_idx2]]);
-			Vec3 triangle2_e2 = Vec3(inVertices[triangle2.mIdx[(edge_idx2 + 1) % 3]]);
-			Vec3 triangle2_op = Vec3(inVertices[triangle2.mIdx[(edge_idx2 + 2) % 3]]);
+			Vec3 triangle2_e1 = Vec3(inSettings.mTriangleVertices[triangle2.mIdx[edge_idx2]]);
+			Vec3 triangle2_e2 = Vec3(inSettings.mTriangleVertices[triangle2.mIdx[(edge_idx2 + 1) % 3]]);
+			Vec3 triangle2_op = Vec3(inSettings.mTriangleVertices[triangle2.mIdx[(edge_idx2 + 2) % 3]]);
 			Plane triangle2_plane = Plane::sFromPointsCCW(triangle2_e1, triangle2_e2, triangle2_op);
 			Plane triangle2_plane = Plane::sFromPointsCCW(triangle2_e1, triangle2_e2, triangle2_op);
 
 
 			// Determine if the edge is active
 			// Determine if the edge is active
-			num_active = ActiveEdges::IsEdgeActive(triangle1_plane.GetNormal(), triangle2_plane.GetNormal(), triangle1_e2 - triangle1_e1)? 2 : 0;
+			num_active = ActiveEdges::IsEdgeActive(triangle1_plane.GetNormal(), triangle2_plane.GetNormal(), triangle1_e2 - triangle1_e1, inSettings.mActiveEdgeCosThresholdAngle)? 2 : 0;
 		}
 		}
 		else
 		else
 		{
 		{

+ 6 - 1
Jolt/Physics/Collision/Shape/MeshShape.h

@@ -47,6 +47,11 @@ public:
 	/// Maximum number of triangles in each leaf of the axis aligned box tree. This is a balance between memory and performance. Can be in the range [1, MeshShape::MaxTrianglesPerLeaf].
 	/// Maximum number of triangles in each leaf of the axis aligned box tree. This is a balance between memory and performance. Can be in the range [1, MeshShape::MaxTrianglesPerLeaf].
 	/// Sensible values are between 4 (for better performance) and 8 (for less memory usage).
 	/// Sensible values are between 4 (for better performance) and 8 (for less memory usage).
 	uint							mMaxTrianglesPerLeaf = 8;
 	uint							mMaxTrianglesPerLeaf = 8;
+
+	/// Cosine of the threshold angle (if the angle between the two triangles is bigger than this, the edge is active, note that a concave edge is always inactive).
+	/// Setting this value too small can cause ghost collisions with edges, setting it too big can cause depenetration artifacts (objects not depenetrating quickly).
+	/// Valid ranges are between cos(0 degrees) and cos(90 degrees). The default value is cos(5 degrees).
+	float							mActiveEdgeCosThresholdAngle = 0.996195f;					// cos(5 degrees)
 };
 };
 
 
 /// A mesh shape, consisting of triangles. Mesh shapes are mostly used for static geometry.
 /// A mesh shape, consisting of triangles. Mesh shapes are mostly used for static geometry.
@@ -149,7 +154,7 @@ private:
 	static constexpr int			MaxTrianglesPerLeaf = 1 << NumTriangleBits;					///< Number of triangles that are stored max per leaf aabb node
 	static constexpr int			MaxTrianglesPerLeaf = 1 << NumTriangleBits;					///< Number of triangles that are stored max per leaf aabb node
 
 
 	/// Find and flag active edges
 	/// Find and flag active edges
-	static void						sFindActiveEdges(const VertexList &inVertices, IndexedTriangleList &ioIndices);
+	static void						sFindActiveEdges(const MeshShapeSettings &inSettings, IndexedTriangleList &ioIndices);
 
 
 	/// Visit the entire tree using a visitor pattern
 	/// Visit the entire tree using a visitor pattern
 	template <class Visitor>
 	template <class Visitor>

+ 10 - 5
Samples/Tests/General/ActiveEdgesTest.cpp

@@ -11,9 +11,9 @@
 #include <Jolt/Geometry/Triangle.h>
 #include <Jolt/Geometry/Triangle.h>
 #include <Layers.h>
 #include <Layers.h>
 
 
-JPH_IMPLEMENT_RTTI_VIRTUAL(ActiveEdgesTest) 
-{ 
-	JPH_ADD_BASE_CLASS(ActiveEdgesTest, Test) 
+JPH_IMPLEMENT_RTTI_VIRTUAL(ActiveEdgesTest)
+{
+	JPH_ADD_BASE_CLASS(ActiveEdgesTest, Test)
 }
 }
 
 
 void ActiveEdgesTest::Initialize()
 void ActiveEdgesTest::Initialize()
@@ -58,7 +58,7 @@ void ActiveEdgesTest::Initialize()
 				box_settings.mPosition = RVec3((v1 + v2 + v3 + v4) / 4 + normal);
 				box_settings.mPosition = RVec3((v1 + v2 + v3 + v4) / 4 + normal);
 				box_settings.mRotation = rotation;
 				box_settings.mRotation = rotation;
 			}
 			}
-				
+
 			// Add segment
 			// Add segment
 			triangles.push_back(Triangle(v1, v3, v4));
 			triangles.push_back(Triangle(v1, v3, v4));
 			triangles.push_back(Triangle(v1, v4, v2));
 			triangles.push_back(Triangle(v1, v4, v2));
@@ -85,8 +85,13 @@ void ActiveEdgesTest::Initialize()
 			body.SetLinearVelocity(Vec3(0, 0, 2.0f));
 			body.SetLinearVelocity(Vec3(0, 0, 2.0f));
 	}
 	}
 
 
+	// Mesh shape
+	MeshShapeSettings mesh_shape(triangles);
+	mesh_shape.SetEmbedded();
+	mesh_shape.mActiveEdgeCosThresholdAngle = Cos(DegreesToRadians(50.0f));
+
 	// Mesh
 	// Mesh
-	BodyCreationSettings mesh_settings(new MeshShapeSettings(triangles), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
+	BodyCreationSettings mesh_settings(&mesh_shape, RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
 	mesh_settings.mFriction = 0.0f;
 	mesh_settings.mFriction = 0.0f;
 	Body &mesh = *mBodyInterface->CreateBody(mesh_settings);
 	Body &mesh = *mBodyInterface->CreateBody(mesh_settings);
 	mBodyInterface->AddBody(mesh.GetID(), EActivation::DontActivate);
 	mBodyInterface->AddBody(mesh.GetID(), EActivation::DontActivate);