Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Collision/ManifoldBetweenTwoFaces.cpp
9912 views
1
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
2
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
3
// SPDX-License-Identifier: MIT
4
5
#include <Jolt/Jolt.h>
6
7
#include <Jolt/Physics/Collision/ManifoldBetweenTwoFaces.h>
8
#include <Jolt/Physics/Constraints/ContactConstraintManager.h>
9
#include <Jolt/Geometry/ClipPoly.h>
10
#ifdef JPH_DEBUG_RENDERER
11
#include <Jolt/Renderer/DebugRenderer.h>
12
#endif // JPH_DEBUG_RENDERER
13
14
JPH_NAMESPACE_BEGIN
15
16
void PruneContactPoints(Vec3Arg inPenetrationAxis, ContactPoints &ioContactPointsOn1, ContactPoints &ioContactPointsOn2 JPH_IF_DEBUG_RENDERER(, RVec3Arg inCenterOfMass))
17
{
18
// Makes no sense to call this with 4 or less points
19
JPH_ASSERT(ioContactPointsOn1.size() > 4);
20
21
// Both arrays should have the same size
22
JPH_ASSERT(ioContactPointsOn1.size() == ioContactPointsOn2.size());
23
24
// Penetration axis must be normalized
25
JPH_ASSERT(inPenetrationAxis.IsNormalized());
26
27
// We use a heuristic of (distance to center of mass) * (penetration depth) to find the contact point that we should keep
28
// Neither of those two terms should ever become zero, so we clamp against this minimum value
29
constexpr float cMinDistanceSq = 1.0e-6f; // 1 mm
30
31
ContactPoints projected;
32
StaticArray<float, 64> penetration_depth_sq;
33
for (ContactPoints::size_type i = 0; i < ioContactPointsOn1.size(); ++i)
34
{
35
// Project contact points on the plane through inCenterOfMass with normal inPenetrationAxis and center around the center of mass of body 1
36
// (note that since all points are relative to inCenterOfMass we can project onto the plane through the origin)
37
Vec3 v1 = ioContactPointsOn1[i];
38
projected.push_back(v1 - v1.Dot(inPenetrationAxis) * inPenetrationAxis);
39
40
// Calculate penetration depth^2 of each point and clamp against the minimal distance
41
Vec3 v2 = ioContactPointsOn2[i];
42
penetration_depth_sq.push_back(max(cMinDistanceSq, (v2 - v1).LengthSq()));
43
}
44
45
// Find the point that is furthest away from the center of mass (its torque will have the biggest influence)
46
// and the point that has the deepest penetration depth. Use the heuristic (distance to center of mass) * (penetration depth) for this.
47
uint point1 = 0;
48
float val = max(cMinDistanceSq, projected[0].LengthSq()) * penetration_depth_sq[0];
49
for (uint i = 0; i < projected.size(); ++i)
50
{
51
float v = max(cMinDistanceSq, projected[i].LengthSq()) * penetration_depth_sq[i];
52
if (v > val)
53
{
54
val = v;
55
point1 = i;
56
}
57
}
58
Vec3 point1v = projected[point1];
59
60
// Find point furthest from the first point forming a line segment with point1. Again combine this with the heuristic
61
// for deepest point as per above.
62
uint point2 = uint(-1);
63
val = -FLT_MAX;
64
for (uint i = 0; i < projected.size(); ++i)
65
if (i != point1)
66
{
67
float v = max(cMinDistanceSq, (projected[i] - point1v).LengthSq()) * penetration_depth_sq[i];
68
if (v > val)
69
{
70
val = v;
71
point2 = i;
72
}
73
}
74
JPH_ASSERT(point2 != uint(-1));
75
Vec3 point2v = projected[point2];
76
77
// Find furthest points on both sides of the line segment in order to maximize the area
78
uint point3 = uint(-1);
79
uint point4 = uint(-1);
80
float min_val = 0.0f;
81
float max_val = 0.0f;
82
Vec3 perp = (point2v - point1v).Cross(inPenetrationAxis);
83
for (uint i = 0; i < projected.size(); ++i)
84
if (i != point1 && i != point2)
85
{
86
float v = perp.Dot(projected[i] - point1v);
87
if (v < min_val)
88
{
89
min_val = v;
90
point3 = i;
91
}
92
else if (v > max_val)
93
{
94
max_val = v;
95
point4 = i;
96
}
97
}
98
99
// Add points to array (in order so they form a polygon)
100
StaticArray<Vec3, 4> points_to_keep_on_1, points_to_keep_on_2;
101
points_to_keep_on_1.push_back(ioContactPointsOn1[point1]);
102
points_to_keep_on_2.push_back(ioContactPointsOn2[point1]);
103
if (point3 != uint(-1))
104
{
105
points_to_keep_on_1.push_back(ioContactPointsOn1[point3]);
106
points_to_keep_on_2.push_back(ioContactPointsOn2[point3]);
107
}
108
points_to_keep_on_1.push_back(ioContactPointsOn1[point2]);
109
points_to_keep_on_2.push_back(ioContactPointsOn2[point2]);
110
if (point4 != uint(-1))
111
{
112
JPH_ASSERT(point3 != point4);
113
points_to_keep_on_1.push_back(ioContactPointsOn1[point4]);
114
points_to_keep_on_2.push_back(ioContactPointsOn2[point4]);
115
}
116
117
#ifdef JPH_DEBUG_RENDERER
118
if (ContactConstraintManager::sDrawContactPointReduction)
119
{
120
// Draw input polygon
121
DebugRenderer::sInstance->DrawWirePolygon(RMat44::sTranslation(inCenterOfMass), ioContactPointsOn1, Color::sOrange, 0.05f);
122
123
// Draw primary axis
124
DebugRenderer::sInstance->DrawArrow(inCenterOfMass + ioContactPointsOn1[point1], inCenterOfMass + ioContactPointsOn1[point2], Color::sRed, 0.05f);
125
126
// Draw contact points we kept
127
for (Vec3 p : points_to_keep_on_1)
128
DebugRenderer::sInstance->DrawMarker(inCenterOfMass + p, Color::sGreen, 0.1f);
129
}
130
#endif // JPH_DEBUG_RENDERER
131
132
// Copy the points back to the input buffer
133
ioContactPointsOn1 = points_to_keep_on_1;
134
ioContactPointsOn2 = points_to_keep_on_2;
135
}
136
137
void 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))
138
{
139
JPH_ASSERT(inMaxContactDistance > 0.0f);
140
141
#ifdef JPH_DEBUG_RENDERER
142
if (ContactConstraintManager::sDrawContactPoint)
143
{
144
RVec3 cp1 = inCenterOfMass + inContactPoint1;
145
RVec3 cp2 = inCenterOfMass + inContactPoint2;
146
147
// Draw contact points
148
DebugRenderer::sInstance->DrawMarker(cp1, Color::sRed, 0.1f);
149
DebugRenderer::sInstance->DrawMarker(cp2, Color::sGreen, 0.1f);
150
151
// Draw contact normal
152
DebugRenderer::sInstance->DrawArrow(cp1, cp1 + inPenetrationAxis.Normalized(), Color::sRed, 0.05f);
153
}
154
#endif // JPH_DEBUG_RENDERER
155
156
// Remember size before adding new points, to check at the end if we added some
157
ContactPoints::size_type old_size = outContactPoints1.size();
158
159
// Check if both shapes have polygon faces
160
if (inShape1Face.size() >= 2 // The dynamic shape needs to have at least 2 points or else there can never be more than 1 contact point
161
&& 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)
162
{
163
// Clip the polygon of face 2 against that of 1
164
ConvexShape::SupportingFace clipped_face;
165
if (inShape1Face.size() >= 3)
166
ClipPolyVsPoly(inShape2Face, inShape1Face, inPenetrationAxis, clipped_face);
167
else if (inShape1Face.size() == 2)
168
ClipPolyVsEdge(inShape2Face, inShape1Face[0], inShape1Face[1], inPenetrationAxis, clipped_face);
169
170
// Determine plane origin and normal for shape 1
171
Vec3 plane_origin = inShape1Face[0];
172
Vec3 plane_normal;
173
Vec3 first_edge = inShape1Face[1] - plane_origin;
174
if (inShape1Face.size() >= 3)
175
{
176
// Three vertices, can just calculate the normal
177
plane_normal = first_edge.Cross(inShape1Face[2] - plane_origin);
178
}
179
else
180
{
181
// Two vertices, first find a perpendicular to the edge and penetration axis and then use the perpendicular together with the edge to form a normal
182
plane_normal = first_edge.Cross(inPenetrationAxis).Cross(first_edge);
183
}
184
185
// If penetration axis and plane normal are perpendicular, fall back to the contact points
186
float penetration_axis_dot_plane_normal = inPenetrationAxis.Dot(plane_normal);
187
if (penetration_axis_dot_plane_normal != 0.0f)
188
{
189
float penetration_axis_len = inPenetrationAxis.Length();
190
191
for (Vec3 p2 : clipped_face)
192
{
193
// Project clipped face back onto the plane of face 1, we do this by solving:
194
// p1 = p2 + distance * penetration_axis / |penetration_axis|
195
// (p1 - plane_origin) . plane_normal = 0
196
// This gives us:
197
// distance = -|penetration_axis| * (p2 - plane_origin) . plane_normal / penetration_axis . plane_normal
198
float distance = (p2 - plane_origin).Dot(plane_normal) / penetration_axis_dot_plane_normal; // note left out -|penetration_axis| term
199
200
// If the point is less than inMaxContactDistance in front of the plane of face 2, add it as a contact point
201
if (distance * penetration_axis_len < inMaxContactDistance)
202
{
203
Vec3 p1 = p2 - distance * inPenetrationAxis;
204
outContactPoints1.push_back(p1);
205
outContactPoints2.push_back(p2);
206
}
207
}
208
}
209
210
#ifdef JPH_DEBUG_RENDERER
211
if (ContactConstraintManager::sDrawSupportingFaces)
212
{
213
RMat44 com = RMat44::sTranslation(inCenterOfMass);
214
215
// Draw clipped poly
216
DebugRenderer::sInstance->DrawWirePolygon(com, clipped_face, Color::sOrange);
217
218
// Draw supporting faces
219
DebugRenderer::sInstance->DrawWirePolygon(com, inShape1Face, Color::sRed, 0.05f);
220
DebugRenderer::sInstance->DrawWirePolygon(com, inShape2Face, Color::sGreen, 0.05f);
221
222
// Draw normal
223
float plane_normal_len = plane_normal.Length();
224
if (plane_normal_len > 0.0f)
225
{
226
RVec3 plane_origin_ws = inCenterOfMass + plane_origin;
227
DebugRenderer::sInstance->DrawArrow(plane_origin_ws, plane_origin_ws + plane_normal / plane_normal_len, Color::sYellow, 0.05f);
228
}
229
230
// Draw contact points that remain after distance check
231
for (ContactPoints::size_type p = old_size; p < outContactPoints1.size(); ++p)
232
{
233
DebugRenderer::sInstance->DrawMarker(inCenterOfMass + outContactPoints1[p], Color::sYellow, 0.1f);
234
DebugRenderer::sInstance->DrawMarker(inCenterOfMass + outContactPoints2[p], Color::sOrange, 0.1f);
235
}
236
}
237
#endif // JPH_DEBUG_RENDERER
238
}
239
240
// If the clipping result is empty, use the contact point itself
241
if (outContactPoints1.size() == old_size)
242
{
243
outContactPoints1.push_back(inContactPoint1);
244
outContactPoints2.push_back(inContactPoint2);
245
}
246
}
247
248
JPH_NAMESPACE_END
249
250