Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/CapsuleShape.cpp
9913 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/Shape/CapsuleShape.h>
8
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
9
#include <Jolt/Physics/Collision/Shape/ScaleHelpers.h>
10
#include <Jolt/Physics/Collision/Shape/GetTrianglesContext.h>
11
#include <Jolt/Physics/Collision/RayCast.h>
12
#include <Jolt/Physics/Collision/CastResult.h>
13
#include <Jolt/Physics/Collision/CollidePointResult.h>
14
#include <Jolt/Physics/Collision/TransformedShape.h>
15
#include <Jolt/Physics/Collision/CollideSoftBodyVertexIterator.h>
16
#include <Jolt/Geometry/RayCapsule.h>
17
#include <Jolt/ObjectStream/TypeDeclarations.h>
18
#include <Jolt/Core/StreamIn.h>
19
#include <Jolt/Core/StreamOut.h>
20
#ifdef JPH_DEBUG_RENDERER
21
#include <Jolt/Renderer/DebugRenderer.h>
22
#endif // JPH_DEBUG_RENDERER
23
24
JPH_NAMESPACE_BEGIN
25
26
JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(CapsuleShapeSettings)
27
{
28
JPH_ADD_BASE_CLASS(CapsuleShapeSettings, ConvexShapeSettings)
29
30
JPH_ADD_ATTRIBUTE(CapsuleShapeSettings, mRadius)
31
JPH_ADD_ATTRIBUTE(CapsuleShapeSettings, mHalfHeightOfCylinder)
32
}
33
34
static const int cCapsuleDetailLevel = 2;
35
36
static const StaticArray<Vec3, 192> sCapsuleTopTriangles = []() {
37
StaticArray<Vec3, 192> verts;
38
GetTrianglesContextVertexList::sCreateHalfUnitSphereTop(verts, cCapsuleDetailLevel);
39
return verts;
40
}();
41
42
static const StaticArray<Vec3, 96> sCapsuleMiddleTriangles = []() {
43
StaticArray<Vec3, 96> verts;
44
GetTrianglesContextVertexList::sCreateUnitOpenCylinder(verts, cCapsuleDetailLevel);
45
return verts;
46
}();
47
48
static const StaticArray<Vec3, 192> sCapsuleBottomTriangles = []() {
49
StaticArray<Vec3, 192> verts;
50
GetTrianglesContextVertexList::sCreateHalfUnitSphereBottom(verts, cCapsuleDetailLevel);
51
return verts;
52
}();
53
54
ShapeSettings::ShapeResult CapsuleShapeSettings::Create() const
55
{
56
if (mCachedResult.IsEmpty())
57
{
58
Ref<Shape> shape;
59
if (IsValid() && IsSphere())
60
{
61
// If the capsule has no height, use a sphere instead
62
shape = new SphereShape(mRadius, mMaterial);
63
mCachedResult.Set(shape);
64
}
65
else
66
shape = new CapsuleShape(*this, mCachedResult);
67
}
68
return mCachedResult;
69
}
70
71
CapsuleShape::CapsuleShape(const CapsuleShapeSettings &inSettings, ShapeResult &outResult) :
72
ConvexShape(EShapeSubType::Capsule, inSettings, outResult),
73
mRadius(inSettings.mRadius),
74
mHalfHeightOfCylinder(inSettings.mHalfHeightOfCylinder)
75
{
76
if (inSettings.mHalfHeightOfCylinder <= 0.0f)
77
{
78
outResult.SetError("Invalid height");
79
return;
80
}
81
82
if (inSettings.mRadius <= 0.0f)
83
{
84
outResult.SetError("Invalid radius");
85
return;
86
}
87
88
outResult.Set(this);
89
}
90
91
class CapsuleShape::CapsuleNoConvex final : public Support
92
{
93
public:
94
CapsuleNoConvex(Vec3Arg inHalfHeightOfCylinder, float inConvexRadius) :
95
mHalfHeightOfCylinder(inHalfHeightOfCylinder),
96
mConvexRadius(inConvexRadius)
97
{
98
static_assert(sizeof(CapsuleNoConvex) <= sizeof(SupportBuffer), "Buffer size too small");
99
JPH_ASSERT(IsAligned(this, alignof(CapsuleNoConvex)));
100
}
101
102
virtual Vec3 GetSupport(Vec3Arg inDirection) const override
103
{
104
if (inDirection.GetY() > 0)
105
return mHalfHeightOfCylinder;
106
else
107
return -mHalfHeightOfCylinder;
108
}
109
110
virtual float GetConvexRadius() const override
111
{
112
return mConvexRadius;
113
}
114
115
private:
116
Vec3 mHalfHeightOfCylinder;
117
float mConvexRadius;
118
};
119
120
class CapsuleShape::CapsuleWithConvex final : public Support
121
{
122
public:
123
CapsuleWithConvex(Vec3Arg inHalfHeightOfCylinder, float inRadius) :
124
mHalfHeightOfCylinder(inHalfHeightOfCylinder),
125
mRadius(inRadius)
126
{
127
static_assert(sizeof(CapsuleWithConvex) <= sizeof(SupportBuffer), "Buffer size too small");
128
JPH_ASSERT(IsAligned(this, alignof(CapsuleWithConvex)));
129
}
130
131
virtual Vec3 GetSupport(Vec3Arg inDirection) const override
132
{
133
float len = inDirection.Length();
134
Vec3 radius = len > 0.0f? inDirection * (mRadius / len) : Vec3::sZero();
135
136
if (inDirection.GetY() > 0)
137
return radius + mHalfHeightOfCylinder;
138
else
139
return radius - mHalfHeightOfCylinder;
140
}
141
142
virtual float GetConvexRadius() const override
143
{
144
return 0.0f;
145
}
146
147
private:
148
Vec3 mHalfHeightOfCylinder;
149
float mRadius;
150
};
151
152
const ConvexShape::Support *CapsuleShape::GetSupportFunction(ESupportMode inMode, SupportBuffer &inBuffer, Vec3Arg inScale) const
153
{
154
JPH_ASSERT(IsValidScale(inScale));
155
156
// Get scaled capsule
157
Vec3 abs_scale = inScale.Abs();
158
float scale = abs_scale.GetX();
159
Vec3 scaled_half_height_of_cylinder = Vec3(0, scale * mHalfHeightOfCylinder, 0);
160
float scaled_radius = scale * mRadius;
161
162
switch (inMode)
163
{
164
case ESupportMode::IncludeConvexRadius:
165
return new (&inBuffer) CapsuleWithConvex(scaled_half_height_of_cylinder, scaled_radius);
166
167
case ESupportMode::ExcludeConvexRadius:
168
case ESupportMode::Default:
169
return new (&inBuffer) CapsuleNoConvex(scaled_half_height_of_cylinder, scaled_radius);
170
}
171
172
JPH_ASSERT(false);
173
return nullptr;
174
}
175
176
void CapsuleShape::GetSupportingFace(const SubShapeID &inSubShapeID, Vec3Arg inDirection, Vec3Arg inScale, Mat44Arg inCenterOfMassTransform, SupportingFace &outVertices) const
177
{
178
JPH_ASSERT(inSubShapeID.IsEmpty(), "Invalid subshape ID");
179
JPH_ASSERT(IsValidScale(inScale));
180
181
// Get direction in horizontal plane
182
Vec3 direction = inDirection;
183
direction.SetComponent(1, 0.0f);
184
185
// Check zero vector, in this case we're hitting from top/bottom so there's no supporting face
186
float len = direction.Length();
187
if (len == 0.0f)
188
return;
189
190
// Get scaled capsule
191
Vec3 abs_scale = inScale.Abs();
192
float scale = abs_scale.GetX();
193
Vec3 scaled_half_height_of_cylinder = Vec3(0, scale * mHalfHeightOfCylinder, 0);
194
float scaled_radius = scale * mRadius;
195
196
// Get support point for top and bottom sphere in the opposite of 'direction' (including convex radius)
197
Vec3 support = (scaled_radius / len) * direction;
198
Vec3 support_top = scaled_half_height_of_cylinder - support;
199
Vec3 support_bottom = -scaled_half_height_of_cylinder - support;
200
201
// Get projection on inDirection
202
// Note that inDirection is not normalized, so we need to divide by inDirection.Length() to get the actual projection
203
// We've multiplied both sides of the if below with inDirection.Length()
204
float proj_top = support_top.Dot(inDirection);
205
float proj_bottom = support_bottom.Dot(inDirection);
206
207
// If projection is roughly equal then return line, otherwise we return nothing as there's only 1 point
208
if (abs(proj_top - proj_bottom) < cCapsuleProjectionSlop * inDirection.Length())
209
{
210
outVertices.push_back(inCenterOfMassTransform * support_top);
211
outVertices.push_back(inCenterOfMassTransform * support_bottom);
212
}
213
}
214
215
MassProperties CapsuleShape::GetMassProperties() const
216
{
217
MassProperties p;
218
219
float density = GetDensity();
220
221
// Calculate inertia and mass according to:
222
// https://www.gamedev.net/resources/_/technical/math-and-physics/capsule-inertia-tensor-r3856
223
// Note that there is an error in eq 14, H^2/2 should be H^2/4 in Ixx and Izz, eq 12 does contain the correct value
224
float radius_sq = Square(mRadius);
225
float height = 2.0f * mHalfHeightOfCylinder;
226
float cylinder_mass = JPH_PI * height * radius_sq * density;
227
float hemisphere_mass = (2.0f * JPH_PI / 3.0f) * radius_sq * mRadius * density;
228
229
// From cylinder
230
float height_sq = Square(height);
231
float inertia_y = radius_sq * cylinder_mass * 0.5f;
232
float inertia_xz = inertia_y * 0.5f + cylinder_mass * height_sq / 12.0f;
233
234
// From hemispheres
235
float temp = hemisphere_mass * 4.0f * radius_sq / 5.0f;
236
inertia_y += temp;
237
inertia_xz += temp + hemisphere_mass * (0.5f * height_sq + (3.0f / 4.0f) * height * mRadius);
238
239
// Mass is cylinder + hemispheres
240
p.mMass = cylinder_mass + hemisphere_mass * 2.0f;
241
242
// Set inertia
243
p.mInertia = Mat44::sScale(Vec3(inertia_xz, inertia_y, inertia_xz));
244
245
return p;
246
}
247
248
Vec3 CapsuleShape::GetSurfaceNormal(const SubShapeID &inSubShapeID, Vec3Arg inLocalSurfacePosition) const
249
{
250
JPH_ASSERT(inSubShapeID.IsEmpty(), "Invalid subshape ID");
251
252
if (inLocalSurfacePosition.GetY() > mHalfHeightOfCylinder)
253
return (inLocalSurfacePosition - Vec3(0, mHalfHeightOfCylinder, 0)).Normalized();
254
else if (inLocalSurfacePosition.GetY() < -mHalfHeightOfCylinder)
255
return (inLocalSurfacePosition - Vec3(0, -mHalfHeightOfCylinder, 0)).Normalized();
256
else
257
return Vec3(inLocalSurfacePosition.GetX(), 0, inLocalSurfacePosition.GetZ()).NormalizedOr(Vec3::sAxisX());
258
}
259
260
AABox CapsuleShape::GetLocalBounds() const
261
{
262
Vec3 extent = Vec3::sReplicate(mRadius) + Vec3(0, mHalfHeightOfCylinder, 0);
263
return AABox(-extent, extent);
264
}
265
266
AABox CapsuleShape::GetWorldSpaceBounds(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale) const
267
{
268
JPH_ASSERT(IsValidScale(inScale));
269
270
Vec3 abs_scale = inScale.Abs();
271
float scale = abs_scale.GetX();
272
Vec3 extent = Vec3::sReplicate(scale * mRadius);
273
Vec3 height = Vec3(0, scale * mHalfHeightOfCylinder, 0);
274
Vec3 p1 = inCenterOfMassTransform * -height;
275
Vec3 p2 = inCenterOfMassTransform * height;
276
return AABox(Vec3::sMin(p1, p2) - extent, Vec3::sMax(p1, p2) + extent);
277
}
278
279
#ifdef JPH_DEBUG_RENDERER
280
void CapsuleShape::Draw(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inUseMaterialColors, bool inDrawWireframe) const
281
{
282
DebugRenderer::EDrawMode draw_mode = inDrawWireframe? DebugRenderer::EDrawMode::Wireframe : DebugRenderer::EDrawMode::Solid;
283
inRenderer->DrawCapsule(inCenterOfMassTransform * Mat44::sScale(inScale.Abs().GetX()), mHalfHeightOfCylinder, mRadius, inUseMaterialColors? GetMaterial()->GetDebugColor() : inColor, DebugRenderer::ECastShadow::On, draw_mode);
284
}
285
#endif // JPH_DEBUG_RENDERER
286
287
bool CapsuleShape::CastRay(const RayCast &inRay, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) const
288
{
289
// Test ray against capsule
290
float fraction = RayCapsule(inRay.mOrigin, inRay.mDirection, mHalfHeightOfCylinder, mRadius);
291
if (fraction < ioHit.mFraction)
292
{
293
ioHit.mFraction = fraction;
294
ioHit.mSubShapeID2 = inSubShapeIDCreator.GetID();
295
return true;
296
}
297
return false;
298
}
299
300
void CapsuleShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter) const
301
{
302
// Test shape filter
303
if (!inShapeFilter.ShouldCollide(this, inSubShapeIDCreator.GetID()))
304
return;
305
306
float radius_sq = Square(mRadius);
307
308
// Get vertical distance to the top/bottom sphere centers
309
float delta_y = abs(inPoint.GetY()) - mHalfHeightOfCylinder;
310
311
// Get distance in horizontal plane
312
float xz_sq = Square(inPoint.GetX()) + Square(inPoint.GetZ());
313
314
// Check if the point is in one of the two spheres
315
bool in_sphere = xz_sq + Square(delta_y) <= radius_sq;
316
317
// Check if the point is in the cylinder in the middle
318
bool in_cylinder = delta_y <= 0.0f && xz_sq <= radius_sq;
319
320
if (in_sphere || in_cylinder)
321
ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });
322
}
323
324
void CapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const CollideSoftBodyVertexIterator &inVertices, uint inNumVertices, int inCollidingShapeIndex) const
325
{
326
JPH_ASSERT(IsValidScale(inScale));
327
328
Mat44 inverse_transform = inCenterOfMassTransform.InversedRotationTranslation();
329
330
// Get scaled capsule
331
float scale = abs(inScale.GetX());
332
float half_height_of_cylinder = scale * mHalfHeightOfCylinder;
333
float radius = scale * mRadius;
334
335
for (CollideSoftBodyVertexIterator v = inVertices, sbv_end = inVertices + inNumVertices; v != sbv_end; ++v)
336
if (v.GetInvMass() > 0.0f)
337
{
338
// Calculate penetration
339
Vec3 local_pos = inverse_transform * v.GetPosition();
340
if (abs(local_pos.GetY()) <= half_height_of_cylinder)
341
{
342
// Near cylinder
343
Vec3 normal = local_pos;
344
normal.SetY(0.0f);
345
float normal_length = normal.Length();
346
float penetration = radius - normal_length;
347
if (v.UpdatePenetration(penetration))
348
{
349
// Calculate contact point and normal
350
normal = normal_length > 0.0f? normal / normal_length : Vec3::sAxisX();
351
Vec3 point = radius * normal;
352
353
// Store collision
354
v.SetCollision(Plane::sFromPointAndNormal(point, normal).GetTransformed(inCenterOfMassTransform), inCollidingShapeIndex);
355
}
356
}
357
else
358
{
359
// Near cap
360
Vec3 center = Vec3(0, Sign(local_pos.GetY()) * half_height_of_cylinder, 0);
361
Vec3 delta = local_pos - center;
362
float distance = delta.Length();
363
float penetration = radius - distance;
364
if (v.UpdatePenetration(penetration))
365
{
366
// Calculate contact point and normal
367
Vec3 normal = delta / distance;
368
Vec3 point = center + radius * normal;
369
370
// Store collision
371
v.SetCollision(Plane::sFromPointAndNormal(point, normal).GetTransformed(inCenterOfMassTransform), inCollidingShapeIndex);
372
}
373
}
374
}
375
}
376
377
void CapsuleShape::GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const
378
{
379
JPH_ASSERT(IsValidScale(inScale));
380
381
Vec3 abs_scale = inScale.Abs();
382
float scale = abs_scale.GetX();
383
384
GetTrianglesContextMultiVertexList *context = new (&ioContext) GetTrianglesContextMultiVertexList(false, GetMaterial());
385
386
Mat44 world_matrix = Mat44::sRotationTranslation(inRotation, inPositionCOM) * Mat44::sScale(scale);
387
388
Mat44 top_matrix = world_matrix * Mat44(Vec4(mRadius, 0, 0, 0), Vec4(0, mRadius, 0, 0), Vec4(0, 0, mRadius, 0), Vec4(0, mHalfHeightOfCylinder, 0, 1));
389
context->AddPart(top_matrix, sCapsuleTopTriangles.data(), sCapsuleTopTriangles.size());
390
391
Mat44 middle_matrix = world_matrix * Mat44::sScale(Vec3(mRadius, mHalfHeightOfCylinder, mRadius));
392
context->AddPart(middle_matrix, sCapsuleMiddleTriangles.data(), sCapsuleMiddleTriangles.size());
393
394
Mat44 bottom_matrix = world_matrix * Mat44(Vec4(mRadius, 0, 0, 0), Vec4(0, mRadius, 0, 0), Vec4(0, 0, mRadius, 0), Vec4(0, -mHalfHeightOfCylinder, 0, 1));
395
context->AddPart(bottom_matrix, sCapsuleBottomTriangles.data(), sCapsuleBottomTriangles.size());
396
}
397
398
int CapsuleShape::GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTrianglesRequested, Float3 *outTriangleVertices, const PhysicsMaterial **outMaterials) const
399
{
400
return ((GetTrianglesContextMultiVertexList &)ioContext).GetTrianglesNext(inMaxTrianglesRequested, outTriangleVertices, outMaterials);
401
}
402
403
void CapsuleShape::SaveBinaryState(StreamOut &inStream) const
404
{
405
ConvexShape::SaveBinaryState(inStream);
406
407
inStream.Write(mRadius);
408
inStream.Write(mHalfHeightOfCylinder);
409
}
410
411
void CapsuleShape::RestoreBinaryState(StreamIn &inStream)
412
{
413
ConvexShape::RestoreBinaryState(inStream);
414
415
inStream.Read(mRadius);
416
inStream.Read(mHalfHeightOfCylinder);
417
}
418
419
bool CapsuleShape::IsValidScale(Vec3Arg inScale) const
420
{
421
return ConvexShape::IsValidScale(inScale) && ScaleHelpers::IsUniformScale(inScale.Abs());
422
}
423
424
Vec3 CapsuleShape::MakeScaleValid(Vec3Arg inScale) const
425
{
426
Vec3 scale = ScaleHelpers::MakeNonZeroScale(inScale);
427
428
return scale.GetSign() * ScaleHelpers::MakeUniformScale(scale.Abs());
429
}
430
431
void CapsuleShape::sRegister()
432
{
433
ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::Capsule);
434
f.mConstruct = []() -> Shape * { return new CapsuleShape; };
435
f.mColor = Color::sGreen;
436
}
437
438
JPH_NAMESPACE_END
439
440