Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.cpp
9917 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/TaperedCapsuleShape.h>
8
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
9
#include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
10
#include <Jolt/Physics/Collision/Shape/ScaleHelpers.h>
11
#include <Jolt/Physics/Collision/TransformedShape.h>
12
#include <Jolt/Physics/Collision/CollideSoftBodyVertexIterator.h>
13
#include <Jolt/Geometry/RayCapsule.h>
14
#include <Jolt/ObjectStream/TypeDeclarations.h>
15
#include <Jolt/Core/StreamIn.h>
16
#include <Jolt/Core/StreamOut.h>
17
#ifdef JPH_DEBUG_RENDERER
18
#include <Jolt/Renderer/DebugRenderer.h>
19
#endif // JPH_DEBUG_RENDERER
20
21
JPH_NAMESPACE_BEGIN
22
23
JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(TaperedCapsuleShapeSettings)
24
{
25
JPH_ADD_BASE_CLASS(TaperedCapsuleShapeSettings, ConvexShapeSettings)
26
27
JPH_ADD_ATTRIBUTE(TaperedCapsuleShapeSettings, mHalfHeightOfTaperedCylinder)
28
JPH_ADD_ATTRIBUTE(TaperedCapsuleShapeSettings, mTopRadius)
29
JPH_ADD_ATTRIBUTE(TaperedCapsuleShapeSettings, mBottomRadius)
30
}
31
32
bool TaperedCapsuleShapeSettings::IsSphere() const
33
{
34
return max(mTopRadius, mBottomRadius) >= 2.0f * mHalfHeightOfTaperedCylinder + min(mTopRadius, mBottomRadius);
35
}
36
37
ShapeSettings::ShapeResult TaperedCapsuleShapeSettings::Create() const
38
{
39
if (mCachedResult.IsEmpty())
40
{
41
Ref<Shape> shape;
42
if (IsValid() && IsSphere())
43
{
44
// Determine sphere center and radius
45
float radius, center;
46
if (mTopRadius > mBottomRadius)
47
{
48
radius = mTopRadius;
49
center = mHalfHeightOfTaperedCylinder;
50
}
51
else
52
{
53
radius = mBottomRadius;
54
center = -mHalfHeightOfTaperedCylinder;
55
}
56
57
// Create sphere
58
shape = new SphereShape(radius, mMaterial);
59
60
// Offset sphere if needed
61
if (abs(center) > 1.0e-6f)
62
{
63
RotatedTranslatedShapeSettings rot_trans(Vec3(0, center, 0), Quat::sIdentity(), shape);
64
mCachedResult = rot_trans.Create();
65
}
66
else
67
mCachedResult.Set(shape);
68
}
69
else
70
{
71
// Normal tapered capsule shape
72
shape = new TaperedCapsuleShape(*this, mCachedResult);
73
}
74
}
75
return mCachedResult;
76
}
77
78
TaperedCapsuleShapeSettings::TaperedCapsuleShapeSettings(float inHalfHeightOfTaperedCylinder, float inTopRadius, float inBottomRadius, const PhysicsMaterial *inMaterial) :
79
ConvexShapeSettings(inMaterial),
80
mHalfHeightOfTaperedCylinder(inHalfHeightOfTaperedCylinder),
81
mTopRadius(inTopRadius),
82
mBottomRadius(inBottomRadius)
83
{
84
}
85
86
TaperedCapsuleShape::TaperedCapsuleShape(const TaperedCapsuleShapeSettings &inSettings, ShapeResult &outResult) :
87
ConvexShape(EShapeSubType::TaperedCapsule, inSettings, outResult),
88
mTopRadius(inSettings.mTopRadius),
89
mBottomRadius(inSettings.mBottomRadius)
90
{
91
if (mTopRadius <= 0.0f)
92
{
93
outResult.SetError("Invalid top radius");
94
return;
95
}
96
97
if (mBottomRadius <= 0.0f)
98
{
99
outResult.SetError("Invalid bottom radius");
100
return;
101
}
102
103
if (inSettings.mHalfHeightOfTaperedCylinder <= 0.0f)
104
{
105
outResult.SetError("Invalid height");
106
return;
107
}
108
109
// If this goes off one of the sphere ends falls totally inside the other and you should use a sphere instead
110
if (inSettings.IsSphere())
111
{
112
outResult.SetError("One sphere embedded in other sphere, please use sphere shape instead");
113
return;
114
}
115
116
// Approximation: The center of mass is exactly half way between the top and bottom cap of the tapered capsule
117
mTopCenter = inSettings.mHalfHeightOfTaperedCylinder + 0.5f * (mBottomRadius - mTopRadius);
118
mBottomCenter = -inSettings.mHalfHeightOfTaperedCylinder + 0.5f * (mBottomRadius - mTopRadius);
119
120
// Calculate center of mass
121
mCenterOfMass = Vec3(0, inSettings.mHalfHeightOfTaperedCylinder - mTopCenter, 0);
122
123
// Calculate convex radius
124
mConvexRadius = min(mTopRadius, mBottomRadius);
125
JPH_ASSERT(mConvexRadius > 0.0f);
126
127
// Calculate the sin and tan of the angle that the cone surface makes with the Y axis
128
// See: TaperedCapsuleShape.gliffy
129
mSinAlpha = (mBottomRadius - mTopRadius) / (mTopCenter - mBottomCenter);
130
JPH_ASSERT(mSinAlpha >= -1.0f && mSinAlpha <= 1.0f);
131
mTanAlpha = Tan(ASin(mSinAlpha));
132
133
outResult.Set(this);
134
}
135
136
class TaperedCapsuleShape::TaperedCapsule final : public Support
137
{
138
public:
139
TaperedCapsule(Vec3Arg inTopCenter, Vec3Arg inBottomCenter, float inTopRadius, float inBottomRadius, float inConvexRadius) :
140
mTopCenter(inTopCenter),
141
mBottomCenter(inBottomCenter),
142
mTopRadius(inTopRadius),
143
mBottomRadius(inBottomRadius),
144
mConvexRadius(inConvexRadius)
145
{
146
static_assert(sizeof(TaperedCapsule) <= sizeof(SupportBuffer), "Buffer size too small");
147
JPH_ASSERT(IsAligned(this, alignof(TaperedCapsule)));
148
}
149
150
virtual Vec3 GetSupport(Vec3Arg inDirection) const override
151
{
152
// Check zero vector
153
float len = inDirection.Length();
154
if (len == 0.0f)
155
return mTopCenter + Vec3(0, mTopRadius, 0); // Return top
156
157
// Check if the support of the top sphere or bottom sphere is bigger
158
Vec3 support_top = mTopCenter + (mTopRadius / len) * inDirection;
159
Vec3 support_bottom = mBottomCenter + (mBottomRadius / len) * inDirection;
160
if (support_top.Dot(inDirection) > support_bottom.Dot(inDirection))
161
return support_top;
162
else
163
return support_bottom;
164
}
165
166
virtual float GetConvexRadius() const override
167
{
168
return mConvexRadius;
169
}
170
171
private:
172
Vec3 mTopCenter;
173
Vec3 mBottomCenter;
174
float mTopRadius;
175
float mBottomRadius;
176
float mConvexRadius;
177
};
178
179
const ConvexShape::Support *TaperedCapsuleShape::GetSupportFunction(ESupportMode inMode, SupportBuffer &inBuffer, Vec3Arg inScale) const
180
{
181
JPH_ASSERT(IsValidScale(inScale));
182
183
// Get scaled tapered capsule
184
Vec3 abs_scale = inScale.Abs();
185
float scale_xz = abs_scale.GetX();
186
float scale_y = inScale.GetY(); // The sign of y is important as it flips the tapered capsule
187
Vec3 scaled_top_center = Vec3(0, scale_y * mTopCenter, 0);
188
Vec3 scaled_bottom_center = Vec3(0, scale_y * mBottomCenter, 0);
189
float scaled_top_radius = scale_xz * mTopRadius;
190
float scaled_bottom_radius = scale_xz * mBottomRadius;
191
float scaled_convex_radius = scale_xz * mConvexRadius;
192
193
switch (inMode)
194
{
195
case ESupportMode::IncludeConvexRadius:
196
return new (&inBuffer) TaperedCapsule(scaled_top_center, scaled_bottom_center, scaled_top_radius, scaled_bottom_radius, 0.0f);
197
198
case ESupportMode::ExcludeConvexRadius:
199
case ESupportMode::Default:
200
{
201
// Get radii reduced by convex radius
202
float tr = scaled_top_radius - scaled_convex_radius;
203
float br = scaled_bottom_radius - scaled_convex_radius;
204
JPH_ASSERT(tr >= 0.0f && br >= 0.0f);
205
JPH_ASSERT(tr == 0.0f || br == 0.0f, "Convex radius should be that of the smallest sphere");
206
return new (&inBuffer) TaperedCapsule(scaled_top_center, scaled_bottom_center, tr, br, scaled_convex_radius);
207
}
208
}
209
210
JPH_ASSERT(false);
211
return nullptr;
212
}
213
214
void TaperedCapsuleShape::GetSupportingFace(const SubShapeID &inSubShapeID, Vec3Arg inDirection, Vec3Arg inScale, Mat44Arg inCenterOfMassTransform, SupportingFace &outVertices) const
215
{
216
JPH_ASSERT(inSubShapeID.IsEmpty(), "Invalid subshape ID");
217
JPH_ASSERT(IsValidScale(inScale));
218
219
// Check zero vector
220
float len = inDirection.Length();
221
if (len == 0.0f)
222
return;
223
224
// Get scaled tapered capsule
225
Vec3 abs_scale = inScale.Abs();
226
float scale_xz = abs_scale.GetX();
227
float scale_y = inScale.GetY(); // The sign of y is important as it flips the tapered capsule
228
Vec3 scaled_top_center = Vec3(0, scale_y * mTopCenter, 0);
229
Vec3 scaled_bottom_center = Vec3(0, scale_y * mBottomCenter, 0);
230
float scaled_top_radius = scale_xz * mTopRadius;
231
float scaled_bottom_radius = scale_xz * mBottomRadius;
232
233
// Get support point for top and bottom sphere in the opposite of inDirection (including convex radius)
234
Vec3 support_top = scaled_top_center - (scaled_top_radius / len) * inDirection;
235
Vec3 support_bottom = scaled_bottom_center - (scaled_bottom_radius / len) * inDirection;
236
237
// Get projection on inDirection
238
float proj_top = support_top.Dot(inDirection);
239
float proj_bottom = support_bottom.Dot(inDirection);
240
241
// If projection is roughly equal then return line, otherwise we return nothing as there's only 1 point
242
if (abs(proj_top - proj_bottom) < cCapsuleProjectionSlop * len)
243
{
244
outVertices.push_back(inCenterOfMassTransform * support_top);
245
outVertices.push_back(inCenterOfMassTransform * support_bottom);
246
}
247
}
248
249
MassProperties TaperedCapsuleShape::GetMassProperties() const
250
{
251
AABox box = GetInertiaApproximation();
252
253
MassProperties p;
254
p.SetMassAndInertiaOfSolidBox(box.GetSize(), GetDensity());
255
return p;
256
}
257
258
Vec3 TaperedCapsuleShape::GetSurfaceNormal(const SubShapeID &inSubShapeID, Vec3Arg inLocalSurfacePosition) const
259
{
260
JPH_ASSERT(inSubShapeID.IsEmpty(), "Invalid subshape ID");
261
262
// See: TaperedCapsuleShape.gliffy
263
// We need to calculate ty and by in order to see if the position is on the top or bottom sphere
264
// sin(alpha) = by / br = ty / tr
265
// => by = sin(alpha) * br, ty = sin(alpha) * tr
266
267
if (inLocalSurfacePosition.GetY() > mTopCenter + mSinAlpha * mTopRadius)
268
return (inLocalSurfacePosition - Vec3(0, mTopCenter, 0)).Normalized();
269
else if (inLocalSurfacePosition.GetY() < mBottomCenter + mSinAlpha * mBottomRadius)
270
return (inLocalSurfacePosition - Vec3(0, mBottomCenter, 0)).Normalized();
271
else
272
{
273
// Get perpendicular vector to the surface in the xz plane
274
Vec3 perpendicular = Vec3(inLocalSurfacePosition.GetX(), 0, inLocalSurfacePosition.GetZ()).NormalizedOr(Vec3::sAxisX());
275
276
// We know that the perpendicular has length 1 and that it needs a y component where tan(alpha) = y / 1 in order to align it to the surface
277
perpendicular.SetY(mTanAlpha);
278
return perpendicular.Normalized();
279
}
280
}
281
282
AABox TaperedCapsuleShape::GetLocalBounds() const
283
{
284
float max_radius = max(mTopRadius, mBottomRadius);
285
return AABox(Vec3(-max_radius, mBottomCenter - mBottomRadius, -max_radius), Vec3(max_radius, mTopCenter + mTopRadius, max_radius));
286
}
287
288
AABox TaperedCapsuleShape::GetWorldSpaceBounds(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale) const
289
{
290
JPH_ASSERT(IsValidScale(inScale));
291
292
Vec3 abs_scale = inScale.Abs();
293
float scale_xz = abs_scale.GetX();
294
float scale_y = inScale.GetY(); // The sign of y is important as it flips the tapered capsule
295
Vec3 bottom_extent = Vec3::sReplicate(scale_xz * mBottomRadius);
296
Vec3 bottom_center = inCenterOfMassTransform * Vec3(0, scale_y * mBottomCenter, 0);
297
Vec3 top_extent = Vec3::sReplicate(scale_xz * mTopRadius);
298
Vec3 top_center = inCenterOfMassTransform * Vec3(0, scale_y * mTopCenter, 0);
299
Vec3 p1 = Vec3::sMin(top_center - top_extent, bottom_center - bottom_extent);
300
Vec3 p2 = Vec3::sMax(top_center + top_extent, bottom_center + bottom_extent);
301
return AABox(p1, p2);
302
}
303
304
void TaperedCapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const CollideSoftBodyVertexIterator &inVertices, uint inNumVertices, int inCollidingShapeIndex) const
305
{
306
JPH_ASSERT(IsValidScale(inScale));
307
308
Mat44 inverse_transform = inCenterOfMassTransform.InversedRotationTranslation();
309
310
// Get scaled tapered capsule
311
Vec3 abs_scale = inScale.Abs();
312
float scale_y = abs_scale.GetY();
313
float scale_xz = abs_scale.GetX();
314
Vec3 scale_y_flip(1, Sign(inScale.GetY()), 1);
315
Vec3 scaled_top_center(0, scale_y * mTopCenter, 0);
316
Vec3 scaled_bottom_center(0, scale_y * mBottomCenter, 0);
317
float scaled_top_radius = scale_xz * mTopRadius;
318
float scaled_bottom_radius = scale_xz * mBottomRadius;
319
320
for (CollideSoftBodyVertexIterator v = inVertices, sbv_end = inVertices + inNumVertices; v != sbv_end; ++v)
321
if (v.GetInvMass() > 0.0f)
322
{
323
Vec3 local_pos = scale_y_flip * (inverse_transform * v.GetPosition());
324
325
Vec3 position, normal;
326
327
// If the vertex is inside the cone starting at the top center pointing along the y-axis with angle PI/2 - alpha then the closest point is on the top sphere
328
// This corresponds to: Dot(y-axis, (local_pos - top_center) / |local_pos - top_center|) >= cos(PI/2 - alpha)
329
// <=> (local_pos - top_center).y >= sin(alpha) * |local_pos - top_center|
330
Vec3 top_center_to_local_pos = local_pos - scaled_top_center;
331
float top_center_to_local_pos_len = top_center_to_local_pos.Length();
332
if (top_center_to_local_pos.GetY() >= mSinAlpha * top_center_to_local_pos_len)
333
{
334
// Top sphere
335
normal = top_center_to_local_pos_len != 0.0f? top_center_to_local_pos / top_center_to_local_pos_len : Vec3::sAxisY();
336
position = scaled_top_center + scaled_top_radius * normal;
337
}
338
else
339
{
340
// If the vertex is outside the cone starting at the bottom center pointing along the y-axis with angle PI/2 - alpha then the closest point is on the bottom sphere
341
// This corresponds to: Dot(y-axis, (local_pos - bottom_center) / |local_pos - bottom_center|) <= cos(PI/2 - alpha)
342
// <=> (local_pos - bottom_center).y <= sin(alpha) * |local_pos - bottom_center|
343
Vec3 bottom_center_to_local_pos = local_pos - scaled_bottom_center;
344
float bottom_center_to_local_pos_len = bottom_center_to_local_pos.Length();
345
if (bottom_center_to_local_pos.GetY() <= mSinAlpha * bottom_center_to_local_pos_len)
346
{
347
// Bottom sphere
348
normal = bottom_center_to_local_pos_len != 0.0f? bottom_center_to_local_pos / bottom_center_to_local_pos_len : -Vec3::sAxisY();
349
}
350
else
351
{
352
// Tapered cylinder
353
normal = Vec3(local_pos.GetX(), 0, local_pos.GetZ()).NormalizedOr(Vec3::sAxisX());
354
normal.SetY(mTanAlpha);
355
normal = normal.NormalizedOr(Vec3::sAxisX());
356
}
357
position = scaled_bottom_center + scaled_bottom_radius * normal;
358
}
359
360
Plane plane = Plane::sFromPointAndNormal(position, normal);
361
float penetration = -plane.SignedDistance(local_pos);
362
if (v.UpdatePenetration(penetration))
363
{
364
// Need to flip the normal's y if capsule is flipped (this corresponds to flipping both the point and the normal around y)
365
plane.SetNormal(scale_y_flip * plane.GetNormal());
366
367
// Store collision
368
v.SetCollision(plane.GetTransformed(inCenterOfMassTransform), inCollidingShapeIndex);
369
}
370
}
371
}
372
373
#ifdef JPH_DEBUG_RENDERER
374
void TaperedCapsuleShape::Draw(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inUseMaterialColors, bool inDrawWireframe) const
375
{
376
if (mGeometry == nullptr)
377
{
378
SupportBuffer buffer;
379
const Support *support = GetSupportFunction(ESupportMode::IncludeConvexRadius, buffer, Vec3::sOne());
380
mGeometry = inRenderer->CreateTriangleGeometryForConvex([support](Vec3Arg inDirection) { return support->GetSupport(inDirection); });
381
}
382
383
// Preserve flip along y axis but make sure we're not inside out
384
Vec3 scale = ScaleHelpers::IsInsideOut(inScale)? Vec3(-1, 1, 1) * inScale : inScale;
385
RMat44 world_transform = inCenterOfMassTransform * Mat44::sScale(scale);
386
387
AABox bounds = Shape::GetWorldSpaceBounds(inCenterOfMassTransform, inScale);
388
389
float lod_scale_sq = Square(max(mTopRadius, mBottomRadius));
390
391
Color color = inUseMaterialColors? GetMaterial()->GetDebugColor() : inColor;
392
393
DebugRenderer::EDrawMode draw_mode = inDrawWireframe? DebugRenderer::EDrawMode::Wireframe : DebugRenderer::EDrawMode::Solid;
394
395
inRenderer->DrawGeometry(world_transform, bounds, lod_scale_sq, color, mGeometry, DebugRenderer::ECullMode::CullBackFace, DebugRenderer::ECastShadow::On, draw_mode);
396
}
397
#endif // JPH_DEBUG_RENDERER
398
399
AABox TaperedCapsuleShape::GetInertiaApproximation() const
400
{
401
// TODO: For now the mass and inertia is that of a box
402
float avg_radius = 0.5f * (mTopRadius + mBottomRadius);
403
return AABox(Vec3(-avg_radius, mBottomCenter - mBottomRadius, -avg_radius), Vec3(avg_radius, mTopCenter + mTopRadius, avg_radius));
404
}
405
406
void TaperedCapsuleShape::SaveBinaryState(StreamOut &inStream) const
407
{
408
ConvexShape::SaveBinaryState(inStream);
409
410
inStream.Write(mCenterOfMass);
411
inStream.Write(mTopRadius);
412
inStream.Write(mBottomRadius);
413
inStream.Write(mTopCenter);
414
inStream.Write(mBottomCenter);
415
inStream.Write(mConvexRadius);
416
inStream.Write(mSinAlpha);
417
inStream.Write(mTanAlpha);
418
}
419
420
void TaperedCapsuleShape::RestoreBinaryState(StreamIn &inStream)
421
{
422
ConvexShape::RestoreBinaryState(inStream);
423
424
inStream.Read(mCenterOfMass);
425
inStream.Read(mTopRadius);
426
inStream.Read(mBottomRadius);
427
inStream.Read(mTopCenter);
428
inStream.Read(mBottomCenter);
429
inStream.Read(mConvexRadius);
430
inStream.Read(mSinAlpha);
431
inStream.Read(mTanAlpha);
432
}
433
434
bool TaperedCapsuleShape::IsValidScale(Vec3Arg inScale) const
435
{
436
return ConvexShape::IsValidScale(inScale) && ScaleHelpers::IsUniformScale(inScale.Abs());
437
}
438
439
Vec3 TaperedCapsuleShape::MakeScaleValid(Vec3Arg inScale) const
440
{
441
Vec3 scale = ScaleHelpers::MakeNonZeroScale(inScale);
442
443
return scale.GetSign() * ScaleHelpers::MakeUniformScale(scale.Abs());
444
}
445
446
void TaperedCapsuleShape::sRegister()
447
{
448
ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::TaperedCapsule);
449
f.mConstruct = []() -> Shape * { return new TaperedCapsuleShape; };
450
f.mColor = Color::sGreen;
451
}
452
453
JPH_NAMESPACE_END
454
455