Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Collision/ManifoldBetweenTwoFaces.cpp
9912 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#include <Jolt/Jolt.h>56#include <Jolt/Physics/Collision/ManifoldBetweenTwoFaces.h>7#include <Jolt/Physics/Constraints/ContactConstraintManager.h>8#include <Jolt/Geometry/ClipPoly.h>9#ifdef JPH_DEBUG_RENDERER10#include <Jolt/Renderer/DebugRenderer.h>11#endif // JPH_DEBUG_RENDERER1213JPH_NAMESPACE_BEGIN1415void PruneContactPoints(Vec3Arg inPenetrationAxis, ContactPoints &ioContactPointsOn1, ContactPoints &ioContactPointsOn2 JPH_IF_DEBUG_RENDERER(, RVec3Arg inCenterOfMass))16{17// Makes no sense to call this with 4 or less points18JPH_ASSERT(ioContactPointsOn1.size() > 4);1920// Both arrays should have the same size21JPH_ASSERT(ioContactPointsOn1.size() == ioContactPointsOn2.size());2223// Penetration axis must be normalized24JPH_ASSERT(inPenetrationAxis.IsNormalized());2526// We use a heuristic of (distance to center of mass) * (penetration depth) to find the contact point that we should keep27// Neither of those two terms should ever become zero, so we clamp against this minimum value28constexpr float cMinDistanceSq = 1.0e-6f; // 1 mm2930ContactPoints projected;31StaticArray<float, 64> penetration_depth_sq;32for (ContactPoints::size_type i = 0; i < ioContactPointsOn1.size(); ++i)33{34// Project contact points on the plane through inCenterOfMass with normal inPenetrationAxis and center around the center of mass of body 135// (note that since all points are relative to inCenterOfMass we can project onto the plane through the origin)36Vec3 v1 = ioContactPointsOn1[i];37projected.push_back(v1 - v1.Dot(inPenetrationAxis) * inPenetrationAxis);3839// Calculate penetration depth^2 of each point and clamp against the minimal distance40Vec3 v2 = ioContactPointsOn2[i];41penetration_depth_sq.push_back(max(cMinDistanceSq, (v2 - v1).LengthSq()));42}4344// Find the point that is furthest away from the center of mass (its torque will have the biggest influence)45// and the point that has the deepest penetration depth. Use the heuristic (distance to center of mass) * (penetration depth) for this.46uint point1 = 0;47float val = max(cMinDistanceSq, projected[0].LengthSq()) * penetration_depth_sq[0];48for (uint i = 0; i < projected.size(); ++i)49{50float v = max(cMinDistanceSq, projected[i].LengthSq()) * penetration_depth_sq[i];51if (v > val)52{53val = v;54point1 = i;55}56}57Vec3 point1v = projected[point1];5859// Find point furthest from the first point forming a line segment with point1. Again combine this with the heuristic60// for deepest point as per above.61uint point2 = uint(-1);62val = -FLT_MAX;63for (uint i = 0; i < projected.size(); ++i)64if (i != point1)65{66float v = max(cMinDistanceSq, (projected[i] - point1v).LengthSq()) * penetration_depth_sq[i];67if (v > val)68{69val = v;70point2 = i;71}72}73JPH_ASSERT(point2 != uint(-1));74Vec3 point2v = projected[point2];7576// Find furthest points on both sides of the line segment in order to maximize the area77uint point3 = uint(-1);78uint point4 = uint(-1);79float min_val = 0.0f;80float max_val = 0.0f;81Vec3 perp = (point2v - point1v).Cross(inPenetrationAxis);82for (uint i = 0; i < projected.size(); ++i)83if (i != point1 && i != point2)84{85float v = perp.Dot(projected[i] - point1v);86if (v < min_val)87{88min_val = v;89point3 = i;90}91else if (v > max_val)92{93max_val = v;94point4 = i;95}96}9798// Add points to array (in order so they form a polygon)99StaticArray<Vec3, 4> points_to_keep_on_1, points_to_keep_on_2;100points_to_keep_on_1.push_back(ioContactPointsOn1[point1]);101points_to_keep_on_2.push_back(ioContactPointsOn2[point1]);102if (point3 != uint(-1))103{104points_to_keep_on_1.push_back(ioContactPointsOn1[point3]);105points_to_keep_on_2.push_back(ioContactPointsOn2[point3]);106}107points_to_keep_on_1.push_back(ioContactPointsOn1[point2]);108points_to_keep_on_2.push_back(ioContactPointsOn2[point2]);109if (point4 != uint(-1))110{111JPH_ASSERT(point3 != point4);112points_to_keep_on_1.push_back(ioContactPointsOn1[point4]);113points_to_keep_on_2.push_back(ioContactPointsOn2[point4]);114}115116#ifdef JPH_DEBUG_RENDERER117if (ContactConstraintManager::sDrawContactPointReduction)118{119// Draw input polygon120DebugRenderer::sInstance->DrawWirePolygon(RMat44::sTranslation(inCenterOfMass), ioContactPointsOn1, Color::sOrange, 0.05f);121122// Draw primary axis123DebugRenderer::sInstance->DrawArrow(inCenterOfMass + ioContactPointsOn1[point1], inCenterOfMass + ioContactPointsOn1[point2], Color::sRed, 0.05f);124125// Draw contact points we kept126for (Vec3 p : points_to_keep_on_1)127DebugRenderer::sInstance->DrawMarker(inCenterOfMass + p, Color::sGreen, 0.1f);128}129#endif // JPH_DEBUG_RENDERER130131// Copy the points back to the input buffer132ioContactPointsOn1 = points_to_keep_on_1;133ioContactPointsOn2 = points_to_keep_on_2;134}135136void ManifoldBetweenTwoFaces(Vec3Arg inContactPoint1, Vec3Arg inContactPoint2, Vec3Arg inPenetrationAxis, float inMaxContactDistance, const ConvexShape::SupportingFace &inShape1Face, const ConvexShape::SupportingFace &inShape2Face, ContactPoints &outContactPoints1, ContactPoints &outContactPoints2 JPH_IF_DEBUG_RENDERER(, RVec3Arg inCenterOfMass))137{138JPH_ASSERT(inMaxContactDistance > 0.0f);139140#ifdef JPH_DEBUG_RENDERER141if (ContactConstraintManager::sDrawContactPoint)142{143RVec3 cp1 = inCenterOfMass + inContactPoint1;144RVec3 cp2 = inCenterOfMass + inContactPoint2;145146// Draw contact points147DebugRenderer::sInstance->DrawMarker(cp1, Color::sRed, 0.1f);148DebugRenderer::sInstance->DrawMarker(cp2, Color::sGreen, 0.1f);149150// Draw contact normal151DebugRenderer::sInstance->DrawArrow(cp1, cp1 + inPenetrationAxis.Normalized(), Color::sRed, 0.05f);152}153#endif // JPH_DEBUG_RENDERER154155// Remember size before adding new points, to check at the end if we added some156ContactPoints::size_type old_size = outContactPoints1.size();157158// Check if both shapes have polygon faces159if (inShape1Face.size() >= 2 // The dynamic shape needs to have at least 2 points or else there can never be more than 1 contact point160&& inShape2Face.size() >= 3) // The dynamic/static shape needs to have at least 3 points (in the case that it has 2 points only if the edges match exactly you can have 2 contact points, but this situation is unstable anyhow)161{162// Clip the polygon of face 2 against that of 1163ConvexShape::SupportingFace clipped_face;164if (inShape1Face.size() >= 3)165ClipPolyVsPoly(inShape2Face, inShape1Face, inPenetrationAxis, clipped_face);166else if (inShape1Face.size() == 2)167ClipPolyVsEdge(inShape2Face, inShape1Face[0], inShape1Face[1], inPenetrationAxis, clipped_face);168169// Determine plane origin and normal for shape 1170Vec3 plane_origin = inShape1Face[0];171Vec3 plane_normal;172Vec3 first_edge = inShape1Face[1] - plane_origin;173if (inShape1Face.size() >= 3)174{175// Three vertices, can just calculate the normal176plane_normal = first_edge.Cross(inShape1Face[2] - plane_origin);177}178else179{180// Two vertices, first find a perpendicular to the edge and penetration axis and then use the perpendicular together with the edge to form a normal181plane_normal = first_edge.Cross(inPenetrationAxis).Cross(first_edge);182}183184// If penetration axis and plane normal are perpendicular, fall back to the contact points185float penetration_axis_dot_plane_normal = inPenetrationAxis.Dot(plane_normal);186if (penetration_axis_dot_plane_normal != 0.0f)187{188float penetration_axis_len = inPenetrationAxis.Length();189190for (Vec3 p2 : clipped_face)191{192// Project clipped face back onto the plane of face 1, we do this by solving:193// p1 = p2 + distance * penetration_axis / |penetration_axis|194// (p1 - plane_origin) . plane_normal = 0195// This gives us:196// distance = -|penetration_axis| * (p2 - plane_origin) . plane_normal / penetration_axis . plane_normal197float distance = (p2 - plane_origin).Dot(plane_normal) / penetration_axis_dot_plane_normal; // note left out -|penetration_axis| term198199// If the point is less than inMaxContactDistance in front of the plane of face 2, add it as a contact point200if (distance * penetration_axis_len < inMaxContactDistance)201{202Vec3 p1 = p2 - distance * inPenetrationAxis;203outContactPoints1.push_back(p1);204outContactPoints2.push_back(p2);205}206}207}208209#ifdef JPH_DEBUG_RENDERER210if (ContactConstraintManager::sDrawSupportingFaces)211{212RMat44 com = RMat44::sTranslation(inCenterOfMass);213214// Draw clipped poly215DebugRenderer::sInstance->DrawWirePolygon(com, clipped_face, Color::sOrange);216217// Draw supporting faces218DebugRenderer::sInstance->DrawWirePolygon(com, inShape1Face, Color::sRed, 0.05f);219DebugRenderer::sInstance->DrawWirePolygon(com, inShape2Face, Color::sGreen, 0.05f);220221// Draw normal222float plane_normal_len = plane_normal.Length();223if (plane_normal_len > 0.0f)224{225RVec3 plane_origin_ws = inCenterOfMass + plane_origin;226DebugRenderer::sInstance->DrawArrow(plane_origin_ws, plane_origin_ws + plane_normal / plane_normal_len, Color::sYellow, 0.05f);227}228229// Draw contact points that remain after distance check230for (ContactPoints::size_type p = old_size; p < outContactPoints1.size(); ++p)231{232DebugRenderer::sInstance->DrawMarker(inCenterOfMass + outContactPoints1[p], Color::sYellow, 0.1f);233DebugRenderer::sInstance->DrawMarker(inCenterOfMass + outContactPoints2[p], Color::sOrange, 0.1f);234}235}236#endif // JPH_DEBUG_RENDERER237}238239// If the clipping result is empty, use the contact point itself240if (outContactPoints1.size() == old_size)241{242outContactPoints1.push_back(inContactPoint1);243outContactPoints2.push_back(inContactPoint2);244}245}246247JPH_NAMESPACE_END248249250