Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Character/CharacterVirtual.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/Character/CharacterVirtual.h>
8
#include <Jolt/Physics/Body/Body.h>
9
#include <Jolt/Physics/Body/BodyCreationSettings.h>
10
#include <Jolt/Physics/PhysicsSystem.h>
11
#include <Jolt/Physics/Collision/ShapeCast.h>
12
#include <Jolt/Physics/Collision/CollideShape.h>
13
#include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
14
#include <Jolt/Physics/Collision/Shape/ScaledShape.h>
15
#include <Jolt/Physics/Collision/CollisionDispatch.h>
16
#include <Jolt/Core/QuickSort.h>
17
#include <Jolt/Core/ScopeExit.h>
18
#include <Jolt/Geometry/ConvexSupport.h>
19
#include <Jolt/Geometry/GJKClosestPoint.h>
20
#include <Jolt/Geometry/RayAABox.h>
21
#ifdef JPH_DEBUG_RENDERER
22
#include <Jolt/Renderer/DebugRenderer.h>
23
#endif // JPH_DEBUG_RENDERER
24
25
JPH_NAMESPACE_BEGIN
26
27
void CharacterVsCharacterCollisionSimple::Remove(const CharacterVirtual *inCharacter)
28
{
29
Array<CharacterVirtual *>::iterator i = std::find(mCharacters.begin(), mCharacters.end(), inCharacter);
30
if (i != mCharacters.end())
31
mCharacters.erase(i);
32
}
33
34
void CharacterVsCharacterCollisionSimple::CollideCharacter(const CharacterVirtual *inCharacter, RMat44Arg inCenterOfMassTransform, const CollideShapeSettings &inCollideShapeSettings, RVec3Arg inBaseOffset, CollideShapeCollector &ioCollector) const
35
{
36
// Make shape 1 relative to inBaseOffset
37
Mat44 transform1 = inCenterOfMassTransform.PostTranslated(-inBaseOffset).ToMat44();
38
39
const Shape *shape1 = inCharacter->GetShape();
40
CollideShapeSettings settings = inCollideShapeSettings;
41
42
// Get bounds for character
43
AABox bounds1 = shape1->GetWorldSpaceBounds(transform1, Vec3::sOne());
44
45
// Iterate over all characters
46
for (const CharacterVirtual *c : mCharacters)
47
if (c != inCharacter
48
&& !ioCollector.ShouldEarlyOut())
49
{
50
// Make shape 2 relative to inBaseOffset
51
Mat44 transform2 = c->GetCenterOfMassTransform().PostTranslated(-inBaseOffset).ToMat44();
52
53
// We need to add the padding of character 2 so that we will detect collision with its outer shell
54
settings.mMaxSeparationDistance = inCollideShapeSettings.mMaxSeparationDistance + c->GetCharacterPadding();
55
56
// Check if the bounding boxes of the characters overlap
57
const Shape *shape2 = c->GetShape();
58
AABox bounds2 = shape2->GetWorldSpaceBounds(transform2, Vec3::sOne());
59
bounds2.ExpandBy(Vec3::sReplicate(settings.mMaxSeparationDistance));
60
if (!bounds1.Overlaps(bounds2))
61
continue;
62
63
// Collector needs to know which character we're colliding with
64
ioCollector.SetUserData(reinterpret_cast<uint64>(c));
65
66
// Note that this collides against the character's shape without padding, this will be corrected for in CharacterVirtual::GetContactsAtPosition
67
CollisionDispatch::sCollideShapeVsShape(shape1, shape2, Vec3::sOne(), Vec3::sOne(), transform1, transform2, SubShapeIDCreator(), SubShapeIDCreator(), settings, ioCollector);
68
}
69
70
// Reset the user data
71
ioCollector.SetUserData(0);
72
}
73
74
void CharacterVsCharacterCollisionSimple::CastCharacter(const CharacterVirtual *inCharacter, RMat44Arg inCenterOfMassTransform, Vec3Arg inDirection, const ShapeCastSettings &inShapeCastSettings, RVec3Arg inBaseOffset, CastShapeCollector &ioCollector) const
75
{
76
// Convert shape cast relative to inBaseOffset
77
Mat44 transform1 = inCenterOfMassTransform.PostTranslated(-inBaseOffset).ToMat44();
78
ShapeCast shape_cast(inCharacter->GetShape(), Vec3::sOne(), transform1, inDirection);
79
80
// Get world space bounds of the character in the form of center and extent
81
Vec3 origin = shape_cast.mShapeWorldBounds.GetCenter();
82
Vec3 extents = shape_cast.mShapeWorldBounds.GetExtent();
83
84
// Iterate over all characters
85
for (const CharacterVirtual *c : mCharacters)
86
if (c != inCharacter
87
&& !ioCollector.ShouldEarlyOut())
88
{
89
// Make shape 2 relative to inBaseOffset
90
Mat44 transform2 = c->GetCenterOfMassTransform().PostTranslated(-inBaseOffset).ToMat44();
91
92
// Sweep bounding box of the character against the bounding box of the other character to see if they can collide
93
const Shape *shape2 = c->GetShape();
94
AABox bounds2 = shape2->GetWorldSpaceBounds(transform2, Vec3::sOne());
95
bounds2.ExpandBy(extents);
96
if (!RayAABoxHits(origin, inDirection, bounds2.mMin, bounds2.mMax))
97
continue;
98
99
// Collector needs to know which character we're colliding with
100
ioCollector.SetUserData(reinterpret_cast<uint64>(c));
101
102
// Note that this collides against the character's shape without padding, this will be corrected for in CharacterVirtual::GetFirstContactForSweep
103
CollisionDispatch::sCastShapeVsShapeWorldSpace(shape_cast, inShapeCastSettings, shape2, Vec3::sOne(), { }, transform2, SubShapeIDCreator(), SubShapeIDCreator(), ioCollector);
104
}
105
106
// Reset the user data
107
ioCollector.SetUserData(0);
108
}
109
110
CharacterVirtual::CharacterVirtual(const CharacterVirtualSettings *inSettings, RVec3Arg inPosition, QuatArg inRotation, uint64 inUserData, PhysicsSystem *inSystem) :
111
CharacterBase(inSettings, inSystem),
112
mID(inSettings->mID),
113
mBackFaceMode(inSettings->mBackFaceMode),
114
mPredictiveContactDistance(inSettings->mPredictiveContactDistance),
115
mMaxCollisionIterations(inSettings->mMaxCollisionIterations),
116
mMaxConstraintIterations(inSettings->mMaxConstraintIterations),
117
mMinTimeRemaining(inSettings->mMinTimeRemaining),
118
mCollisionTolerance(inSettings->mCollisionTolerance),
119
mCharacterPadding(inSettings->mCharacterPadding),
120
mMaxNumHits(inSettings->mMaxNumHits),
121
mHitReductionCosMaxAngle(inSettings->mHitReductionCosMaxAngle),
122
mPenetrationRecoverySpeed(inSettings->mPenetrationRecoverySpeed),
123
mEnhancedInternalEdgeRemoval(inSettings->mEnhancedInternalEdgeRemoval),
124
mShapeOffset(inSettings->mShapeOffset),
125
mPosition(inPosition),
126
mRotation(inRotation),
127
mUserData(inUserData)
128
{
129
JPH_ASSERT(!mID.IsInvalid());
130
131
// Copy settings
132
SetMaxStrength(inSettings->mMaxStrength);
133
SetMass(inSettings->mMass);
134
135
// Create an inner rigid body if requested
136
if (inSettings->mInnerBodyShape != nullptr)
137
{
138
BodyCreationSettings settings(inSettings->mInnerBodyShape, GetInnerBodyPosition(), mRotation, EMotionType::Kinematic, inSettings->mInnerBodyLayer);
139
settings.mAllowSleeping = false; // Disable sleeping so that we will receive sensor callbacks
140
settings.mUserData = inUserData;
141
142
const Body *inner_body;
143
BodyInterface &bi = inSystem->GetBodyInterface();
144
if (inSettings->mInnerBodyIDOverride.IsInvalid())
145
inner_body = bi.CreateBody(settings);
146
else
147
inner_body = bi.CreateBodyWithID(inSettings->mInnerBodyIDOverride, settings);
148
if (inner_body != nullptr)
149
{
150
mInnerBodyID = inner_body->GetID();
151
bi.AddBody(mInnerBodyID, EActivation::Activate);
152
}
153
}
154
}
155
156
CharacterVirtual::~CharacterVirtual()
157
{
158
if (!mInnerBodyID.IsInvalid())
159
{
160
mSystem->GetBodyInterface().RemoveBody(mInnerBodyID);
161
mSystem->GetBodyInterface().DestroyBody(mInnerBodyID);
162
}
163
}
164
165
void CharacterVirtual::UpdateInnerBodyTransform()
166
{
167
if (!mInnerBodyID.IsInvalid())
168
mSystem->GetBodyInterface().SetPositionAndRotation(mInnerBodyID, GetInnerBodyPosition(), mRotation, EActivation::DontActivate);
169
}
170
171
void CharacterVirtual::GetAdjustedBodyVelocity(const Body& inBody, Vec3 &outLinearVelocity, Vec3 &outAngularVelocity) const
172
{
173
// Get real velocity of body
174
if (!inBody.IsStatic())
175
{
176
const MotionProperties *mp = inBody.GetMotionPropertiesUnchecked();
177
outLinearVelocity = mp->GetLinearVelocity();
178
outAngularVelocity = mp->GetAngularVelocity();
179
}
180
else
181
{
182
outLinearVelocity = outAngularVelocity = Vec3::sZero();
183
}
184
185
// Allow application to override
186
if (mListener != nullptr)
187
mListener->OnAdjustBodyVelocity(this, inBody, outLinearVelocity, outAngularVelocity);
188
}
189
190
Vec3 CharacterVirtual::CalculateCharacterGroundVelocity(RVec3Arg inCenterOfMass, Vec3Arg inLinearVelocity, Vec3Arg inAngularVelocity, float inDeltaTime) const
191
{
192
// Get angular velocity
193
float angular_velocity_len_sq = inAngularVelocity.LengthSq();
194
if (angular_velocity_len_sq < 1.0e-12f)
195
return inLinearVelocity;
196
float angular_velocity_len = sqrt(angular_velocity_len_sq);
197
198
// Calculate the rotation that the object will make in the time step
199
Quat rotation = Quat::sRotation(inAngularVelocity / angular_velocity_len, angular_velocity_len * inDeltaTime);
200
201
// Calculate where the new character position will be
202
RVec3 new_position = inCenterOfMass + rotation * Vec3(mPosition - inCenterOfMass);
203
204
// Calculate the velocity
205
return inLinearVelocity + Vec3(new_position - mPosition) / inDeltaTime;
206
}
207
208
template <class taCollector>
209
void CharacterVirtual::sFillContactProperties(const CharacterVirtual *inCharacter, Contact &outContact, const Body &inBody, Vec3Arg inUp, RVec3Arg inBaseOffset, const taCollector &inCollector, const CollideShapeResult &inResult)
210
{
211
// Get adjusted body velocity
212
Vec3 linear_velocity, angular_velocity;
213
inCharacter->GetAdjustedBodyVelocity(inBody, linear_velocity, angular_velocity);
214
215
outContact.mPosition = inBaseOffset + inResult.mContactPointOn2;
216
outContact.mLinearVelocity = linear_velocity + angular_velocity.Cross(Vec3(outContact.mPosition - inBody.GetCenterOfMassPosition())); // Calculate point velocity
217
outContact.mContactNormal = -inResult.mPenetrationAxis.NormalizedOr(Vec3::sZero());
218
outContact.mSurfaceNormal = inCollector.GetContext()->GetWorldSpaceSurfaceNormal(inResult.mSubShapeID2, outContact.mPosition);
219
if (outContact.mContactNormal.Dot(outContact.mSurfaceNormal) < 0.0f)
220
outContact.mSurfaceNormal = -outContact.mSurfaceNormal; // Flip surface normal if we're hitting a back face
221
if (outContact.mContactNormal.Dot(inUp) > outContact.mSurfaceNormal.Dot(inUp))
222
outContact.mSurfaceNormal = outContact.mContactNormal; // Replace surface normal with contact normal if the contact normal is pointing more upwards
223
outContact.mDistance = -inResult.mPenetrationDepth;
224
outContact.mBodyB = inResult.mBodyID2;
225
outContact.mSubShapeIDB = inResult.mSubShapeID2;
226
outContact.mMotionTypeB = inBody.GetMotionType();
227
outContact.mIsSensorB = inBody.IsSensor();
228
outContact.mUserData = inBody.GetUserData();
229
outContact.mMaterial = inCollector.GetContext()->GetMaterial(inResult.mSubShapeID2);
230
}
231
232
void CharacterVirtual::sFillCharacterContactProperties(Contact &outContact, const CharacterVirtual *inOtherCharacter, RVec3Arg inBaseOffset, const CollideShapeResult &inResult)
233
{
234
outContact.mPosition = inBaseOffset + inResult.mContactPointOn2;
235
outContact.mLinearVelocity = inOtherCharacter->GetLinearVelocity();
236
outContact.mSurfaceNormal = outContact.mContactNormal = -inResult.mPenetrationAxis.NormalizedOr(Vec3::sZero());
237
outContact.mDistance = -inResult.mPenetrationDepth;
238
outContact.mCharacterIDB = inOtherCharacter->GetID();
239
outContact.mCharacterB = inOtherCharacter;
240
outContact.mSubShapeIDB = inResult.mSubShapeID2;
241
outContact.mMotionTypeB = EMotionType::Kinematic; // Other character is kinematic, we can't directly move it
242
outContact.mIsSensorB = false;
243
outContact.mUserData = inOtherCharacter->GetUserData();
244
outContact.mMaterial = PhysicsMaterial::sDefault;
245
}
246
247
void CharacterVirtual::ContactCollector::AddHit(const CollideShapeResult &inResult)
248
{
249
// If we exceed our contact limit, try to clean up near-duplicate contacts
250
if (mContacts.size() == mMaxHits)
251
{
252
// Flag that we hit this code path
253
mMaxHitsExceeded = true;
254
255
// Check if we can do reduction
256
if (mHitReductionCosMaxAngle > -1.0f)
257
{
258
// Loop all contacts and find similar contacts
259
for (int i = (int)mContacts.size() - 1; i >= 0; --i)
260
{
261
Contact &contact_i = mContacts[i];
262
for (int j = i - 1; j >= 0; --j)
263
{
264
Contact &contact_j = mContacts[j];
265
if (contact_i.IsSameBody(contact_j)
266
&& contact_i.mContactNormal.Dot(contact_j.mContactNormal) > mHitReductionCosMaxAngle) // Very similar contact normals
267
{
268
// Remove the contact with the biggest distance
269
bool i_is_last = i == (int)mContacts.size() - 1;
270
if (contact_i.mDistance > contact_j.mDistance)
271
{
272
// Remove i
273
if (!i_is_last)
274
contact_i = mContacts.back();
275
mContacts.pop_back();
276
277
// Break out of the loop, i is now an element that we already processed
278
break;
279
}
280
else
281
{
282
// Remove j
283
contact_j = mContacts.back();
284
mContacts.pop_back();
285
286
// If i was the last element, we just moved it into position j. Break out of the loop, we'll see it again later.
287
if (i_is_last)
288
break;
289
}
290
}
291
}
292
}
293
}
294
295
if (mContacts.size() == mMaxHits)
296
{
297
// There are still too many hits, give up!
298
ForceEarlyOut();
299
return;
300
}
301
}
302
303
if (inResult.mBodyID2.IsInvalid())
304
{
305
// Assuming this is a hit against another character
306
JPH_ASSERT(mOtherCharacter != nullptr);
307
308
// Create contact with other character
309
mContacts.emplace_back();
310
Contact &contact = mContacts.back();
311
sFillCharacterContactProperties(contact, mOtherCharacter, mBaseOffset, inResult);
312
contact.mFraction = 0.0f;
313
}
314
else
315
{
316
// Create contact with other body
317
BodyLockRead lock(mSystem->GetBodyLockInterface(), inResult.mBodyID2);
318
if (lock.SucceededAndIsInBroadPhase())
319
{
320
mContacts.emplace_back();
321
Contact &contact = mContacts.back();
322
sFillContactProperties(mCharacter, contact, lock.GetBody(), mUp, mBaseOffset, *this, inResult);
323
contact.mFraction = 0.0f;
324
}
325
}
326
}
327
328
void CharacterVirtual::ContactCastCollector::AddHit(const ShapeCastResult &inResult)
329
{
330
if (inResult.mFraction < mContact.mFraction // Since we're doing checks against the world and against characters, we may get a hit with a higher fraction than the previous hit
331
&& inResult.mFraction > 0.0f // Ignore collisions at fraction = 0
332
&& inResult.mPenetrationAxis.Dot(mDisplacement) > 0.0f) // Ignore penetrations that we're moving away from
333
{
334
// Test if this contact should be ignored
335
for (const ContactKey &c : mIgnoredContacts)
336
if (c.mBodyB == inResult.mBodyID2 && c.mSubShapeIDB == inResult.mSubShapeID2)
337
return;
338
339
Contact contact;
340
341
if (inResult.mBodyID2.IsInvalid())
342
{
343
// Assuming this is a hit against another character
344
JPH_ASSERT(mOtherCharacter != nullptr);
345
346
// Create contact with other character
347
sFillCharacterContactProperties(contact, mOtherCharacter, mBaseOffset, inResult);
348
}
349
else
350
{
351
// Lock body only while we fetch contact properties
352
BodyLockRead lock(mSystem->GetBodyLockInterface(), inResult.mBodyID2);
353
if (!lock.SucceededAndIsInBroadPhase())
354
return;
355
356
// Sweeps don't result in OnContactAdded callbacks so we can ignore sensors here
357
const Body &body = lock.GetBody();
358
if (body.IsSensor())
359
return;
360
361
// Convert the hit result into a contact
362
sFillContactProperties(mCharacter, contact, body, mUp, mBaseOffset, *this, inResult);
363
}
364
365
contact.mFraction = inResult.mFraction;
366
367
// Check if the contact that will make us penetrate more than the allowed tolerance
368
if (contact.mDistance + contact.mContactNormal.Dot(mDisplacement) < -mCharacter->mCollisionTolerance
369
&& mCharacter->ValidateContact(contact))
370
{
371
mContact = contact;
372
UpdateEarlyOutFraction(contact.mFraction);
373
}
374
}
375
}
376
377
void CharacterVirtual::CheckCollision(RVec3Arg inPosition, QuatArg inRotation, Vec3Arg inMovementDirection, float inMaxSeparationDistance, const Shape *inShape, RVec3Arg inBaseOffset, CollideShapeCollector &ioCollector, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) const
378
{
379
// Query shape transform
380
RMat44 transform = GetCenterOfMassTransform(inPosition, inRotation, inShape);
381
382
// Settings for collide shape
383
CollideShapeSettings settings;
384
settings.mBackFaceMode = mBackFaceMode;
385
settings.mActiveEdgeMovementDirection = inMovementDirection;
386
settings.mMaxSeparationDistance = mCharacterPadding + inMaxSeparationDistance;
387
settings.mActiveEdgeMode = EActiveEdgeMode::CollideOnlyWithActive;
388
389
// Body filter
390
IgnoreSingleBodyFilterChained body_filter(mInnerBodyID, inBodyFilter);
391
392
// Select the right function
393
auto collide_shape_function = mEnhancedInternalEdgeRemoval? &NarrowPhaseQuery::CollideShapeWithInternalEdgeRemoval : &NarrowPhaseQuery::CollideShape;
394
395
// Collide shape
396
(mSystem->GetNarrowPhaseQuery().*collide_shape_function)(inShape, Vec3::sOne(), transform, settings, inBaseOffset, ioCollector, inBroadPhaseLayerFilter, inObjectLayerFilter, body_filter, inShapeFilter);
397
398
// Also collide with other characters
399
if (mCharacterVsCharacterCollision != nullptr)
400
{
401
ioCollector.SetContext(nullptr); // We're no longer colliding with a transformed shape, reset
402
mCharacterVsCharacterCollision->CollideCharacter(this, transform, settings, inBaseOffset, ioCollector);
403
}
404
}
405
406
void CharacterVirtual::GetContactsAtPosition(RVec3Arg inPosition, Vec3Arg inMovementDirection, const Shape *inShape, TempContactList &outContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) const
407
{
408
// Remove previous results
409
outContacts.clear();
410
411
// Body filter
412
IgnoreSingleBodyFilterChained body_filter(mInnerBodyID, inBodyFilter);
413
414
// Collide shape
415
ContactCollector collector(mSystem, this, mMaxNumHits, mHitReductionCosMaxAngle, mUp, mPosition, outContacts);
416
CheckCollision(inPosition, mRotation, inMovementDirection, mPredictiveContactDistance, inShape, mPosition, collector, inBroadPhaseLayerFilter, inObjectLayerFilter, body_filter, inShapeFilter);
417
418
// The broadphase bounding boxes will not be deterministic, which means that the order in which the contacts are received by the collector is not deterministic.
419
// Therefore we need to sort the contacts to preserve determinism. Note that currently this will fail if we exceed mMaxNumHits hits.
420
QuickSort(outContacts.begin(), outContacts.end(), ContactOrderingPredicate());
421
422
// Flag if we exceeded the max number of hits
423
mMaxHitsExceeded = collector.mMaxHitsExceeded;
424
425
// Reduce distance to contact by padding to ensure we stay away from the object by a little margin
426
// (this will make collision detection cheaper - especially for sweep tests as they won't hit the surface if we're properly sliding)
427
for (Contact &c : outContacts)
428
{
429
c.mDistance -= mCharacterPadding;
430
431
if (c.mCharacterB != nullptr)
432
c.mDistance -= c.mCharacterB->mCharacterPadding;
433
}
434
}
435
436
void CharacterVirtual::RemoveConflictingContacts(TempContactList &ioContacts, IgnoredContactList &outIgnoredContacts) const
437
{
438
// Only use this algorithm if we're penetrating further than this (due to numerical precision issues we can always penetrate a little bit and we don't want to discard contacts if they just have a tiny penetration)
439
// We do need to account for padding (see GetContactsAtPosition) that is removed from the contact distances, to compensate we add it to the cMinRequiredPenetration
440
const float cMinRequiredPenetration = 1.25f * mCharacterPadding;
441
442
// Discard conflicting penetrating contacts
443
for (size_t c1 = 0; c1 < ioContacts.size(); c1++)
444
{
445
Contact &contact1 = ioContacts[c1];
446
if (contact1.mDistance <= -cMinRequiredPenetration) // Only for penetrations
447
for (size_t c2 = c1 + 1; c2 < ioContacts.size(); c2++)
448
{
449
Contact &contact2 = ioContacts[c2];
450
if (contact1.IsSameBody(contact2)
451
&& contact2.mDistance <= -cMinRequiredPenetration // Only for penetrations
452
&& contact1.mContactNormal.Dot(contact2.mContactNormal) < 0.0f) // Only opposing normals
453
{
454
// Discard contacts with the least amount of penetration
455
if (contact1.mDistance < contact2.mDistance)
456
{
457
// Discard the 2nd contact
458
outIgnoredContacts.emplace_back(contact2);
459
ioContacts.erase(ioContacts.begin() + c2);
460
c2--;
461
}
462
else
463
{
464
// Discard the first contact
465
outIgnoredContacts.emplace_back(contact1);
466
ioContacts.erase(ioContacts.begin() + c1);
467
c1--;
468
break;
469
}
470
}
471
}
472
}
473
}
474
475
bool CharacterVirtual::ValidateContact(const Contact &inContact) const
476
{
477
if (mListener == nullptr)
478
return true;
479
480
if (inContact.mCharacterB != nullptr)
481
return mListener->OnCharacterContactValidate(this, inContact.mCharacterB, inContact.mSubShapeIDB);
482
else
483
return mListener->OnContactValidate(this, inContact.mBodyB, inContact.mSubShapeIDB);
484
}
485
486
void CharacterVirtual::ContactAdded(const Contact &inContact, CharacterContactSettings &ioSettings)
487
{
488
if (mListener != nullptr)
489
{
490
// Check if we already know this contact
491
ListenerContacts::iterator it = mListenerContacts.find(inContact);
492
if (it != mListenerContacts.end())
493
{
494
// Max 1 contact persisted callback
495
if (++it->second.mCount == 1)
496
{
497
if (inContact.mCharacterB != nullptr)
498
mListener->OnCharacterContactPersisted(this, inContact.mCharacterB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);
499
else
500
mListener->OnContactPersisted(this, inContact.mBodyB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);
501
it->second.mSettings = ioSettings;
502
}
503
else
504
{
505
// Reuse the settings from the last call
506
ioSettings = it->second.mSettings;
507
}
508
}
509
else
510
{
511
// New contact
512
if (inContact.mCharacterB != nullptr)
513
mListener->OnCharacterContactAdded(this, inContact.mCharacterB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);
514
else
515
mListener->OnContactAdded(this, inContact.mBodyB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);
516
mListenerContacts.insert(ListenerContacts::value_type(inContact, ioSettings));
517
}
518
}
519
}
520
521
template <class T>
522
inline static bool sCorrectFractionForCharacterPadding(const Shape *inShape, Mat44Arg inStart, Vec3Arg inDisplacement, Vec3Arg inScale, const T &inPolygon, float &ioFraction)
523
{
524
if (inShape->GetType() == EShapeType::Convex)
525
{
526
// Get the support function for the shape we're casting
527
const ConvexShape *convex_shape = static_cast<const ConvexShape *>(inShape);
528
ConvexShape::SupportBuffer buffer;
529
const ConvexShape::Support *support = convex_shape->GetSupportFunction(ConvexShape::ESupportMode::IncludeConvexRadius, buffer, inScale);
530
531
// Cast the shape against the polygon
532
GJKClosestPoint gjk;
533
return gjk.CastShape(inStart, inDisplacement, cDefaultCollisionTolerance, *support, inPolygon, ioFraction);
534
}
535
else if (inShape->GetSubType() == EShapeSubType::RotatedTranslated)
536
{
537
const RotatedTranslatedShape *rt_shape = static_cast<const RotatedTranslatedShape *>(inShape);
538
return sCorrectFractionForCharacterPadding(rt_shape->GetInnerShape(), inStart * Mat44::sRotation(rt_shape->GetRotation()), inDisplacement, rt_shape->TransformScale(inScale), inPolygon, ioFraction);
539
}
540
else if (inShape->GetSubType() == EShapeSubType::Scaled)
541
{
542
const ScaledShape *scaled_shape = static_cast<const ScaledShape *>(inShape);
543
return sCorrectFractionForCharacterPadding(scaled_shape->GetInnerShape(), inStart, inDisplacement, inScale * scaled_shape->GetScale(), inPolygon, ioFraction);
544
}
545
else
546
{
547
JPH_ASSERT(false, "Not supported yet!");
548
return false;
549
}
550
}
551
552
bool CharacterVirtual::GetFirstContactForSweep(RVec3Arg inPosition, Vec3Arg inDisplacement, Contact &outContact, const IgnoredContactList &inIgnoredContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) const
553
{
554
// Too small distance -> skip checking
555
float displacement_len_sq = inDisplacement.LengthSq();
556
if (displacement_len_sq < 1.0e-8f)
557
return false;
558
559
// Calculate start transform
560
RMat44 start = GetCenterOfMassTransform(inPosition, mRotation, mShape);
561
562
// Settings for the cast
563
ShapeCastSettings settings;
564
settings.mBackFaceModeTriangles = mBackFaceMode;
565
settings.mBackFaceModeConvex = EBackFaceMode::IgnoreBackFaces;
566
settings.mActiveEdgeMode = EActiveEdgeMode::CollideOnlyWithActive;
567
settings.mUseShrunkenShapeAndConvexRadius = true;
568
settings.mReturnDeepestPoint = false;
569
570
// Calculate how much extra fraction we need to add to the cast to account for the character padding
571
float character_padding_fraction = mCharacterPadding / sqrt(displacement_len_sq);
572
573
// Body filter
574
IgnoreSingleBodyFilterChained body_filter(mInnerBodyID, inBodyFilter);
575
576
// Cast shape
577
Contact contact;
578
contact.mFraction = 1.0f + character_padding_fraction;
579
RVec3 base_offset = start.GetTranslation();
580
ContactCastCollector collector(mSystem, this, inDisplacement, mUp, inIgnoredContacts, base_offset, contact);
581
collector.ResetEarlyOutFraction(contact.mFraction);
582
RShapeCast shape_cast(mShape, Vec3::sOne(), start, inDisplacement);
583
mSystem->GetNarrowPhaseQuery().CastShape(shape_cast, settings, base_offset, collector, inBroadPhaseLayerFilter, inObjectLayerFilter, body_filter, inShapeFilter);
584
585
// Also collide with other characters
586
if (mCharacterVsCharacterCollision != nullptr)
587
{
588
collector.SetContext(nullptr); // We're no longer colliding with a transformed shape, reset
589
mCharacterVsCharacterCollision->CastCharacter(this, start, inDisplacement, settings, base_offset, collector);
590
}
591
592
if (contact.mBodyB.IsInvalid() && contact.mCharacterIDB.IsInvalid())
593
return false;
594
595
// Store contact
596
outContact = contact;
597
598
TransformedShape ts;
599
float character_padding = mCharacterPadding;
600
if (outContact.mCharacterB != nullptr)
601
{
602
// Create a transformed shape for the character
603
RMat44 com = outContact.mCharacterB->GetCenterOfMassTransform();
604
ts = TransformedShape(com.GetTranslation(), com.GetQuaternion(), outContact.mCharacterB->GetShape(), BodyID(), SubShapeIDCreator());
605
606
// We need to take the other character's padding into account as well
607
character_padding += outContact.mCharacterB->mCharacterPadding;
608
}
609
else
610
{
611
// Create a transformed shape for the body
612
ts = mSystem->GetBodyInterface().GetTransformedShape(outContact.mBodyB);
613
}
614
615
// Fetch the face we're colliding with
616
Shape::SupportingFace face;
617
ts.GetSupportingFace(outContact.mSubShapeIDB, -outContact.mContactNormal, base_offset, face);
618
619
bool corrected = false;
620
if (face.size() >= 2)
621
{
622
// Inflate the colliding face by the character padding
623
PolygonConvexSupport polygon(face);
624
AddConvexRadius add_cvx(polygon, character_padding);
625
626
// Correct fraction to hit this inflated face instead of the inner shape
627
corrected = sCorrectFractionForCharacterPadding(mShape, start.GetRotation(), inDisplacement, Vec3::sOne(), add_cvx, outContact.mFraction);
628
}
629
if (!corrected)
630
{
631
// When there's only a single contact point or when we were unable to correct the fraction,
632
// we can just move the fraction back so that the character and its padding don't hit the contact point anymore
633
outContact.mFraction = max(0.0f, outContact.mFraction - character_padding_fraction);
634
}
635
636
// Ensure that we never return a fraction that's bigger than 1 (which could happen due to float precision issues).
637
outContact.mFraction = min(outContact.mFraction, 1.0f);
638
639
return true;
640
}
641
642
void CharacterVirtual::DetermineConstraints(TempContactList &inContacts, float inDeltaTime, ConstraintList &outConstraints) const
643
{
644
for (Contact &c : inContacts)
645
{
646
Vec3 contact_velocity = c.mLinearVelocity;
647
648
// Penetrating contact: Add a contact velocity that pushes the character out at the desired speed
649
if (c.mDistance < 0.0f)
650
contact_velocity -= c.mContactNormal * c.mDistance * mPenetrationRecoverySpeed / inDeltaTime;
651
652
// Convert to a constraint
653
outConstraints.emplace_back();
654
Constraint &constraint = outConstraints.back();
655
constraint.mContact = &c;
656
constraint.mLinearVelocity = contact_velocity;
657
constraint.mPlane = Plane(c.mContactNormal, c.mDistance);
658
659
// Next check if the angle is too steep and if it is add an additional constraint that holds the character back
660
if (IsSlopeTooSteep(c.mSurfaceNormal))
661
{
662
// Only take planes that point up.
663
// Note that we use the contact normal to allow for better sliding as the surface normal may be in the opposite direction of movement.
664
float dot = c.mContactNormal.Dot(mUp);
665
if (dot > 1.0e-3f) // Add a little slack, if the normal is perfectly horizontal we already have our vertical plane.
666
{
667
// Mark the slope constraint as steep
668
constraint.mIsSteepSlope = true;
669
670
// Make horizontal normal
671
Vec3 normal = (c.mContactNormal - dot * mUp).Normalized();
672
673
// Create a secondary constraint that blocks horizontal movement
674
outConstraints.emplace_back();
675
Constraint &vertical_constraint = outConstraints.back();
676
vertical_constraint.mContact = &c;
677
vertical_constraint.mLinearVelocity = contact_velocity.Dot(normal) * normal; // Project the contact velocity on the new normal so that both planes push at an equal rate
678
vertical_constraint.mPlane = Plane(normal, c.mDistance / normal.Dot(c.mContactNormal)); // Calculate the distance we have to travel horizontally to hit the contact plane
679
}
680
}
681
}
682
}
683
684
bool CharacterVirtual::HandleContact(Vec3Arg inVelocity, Constraint &ioConstraint, float inDeltaTime)
685
{
686
Contact &contact = *ioConstraint.mContact;
687
688
// Validate the contact point
689
if (!ValidateContact(contact))
690
return false;
691
692
// We collided
693
contact.mHadCollision = true;
694
695
// Send contact added event
696
CharacterContactSettings settings;
697
ContactAdded(contact, settings);
698
contact.mCanPushCharacter = settings.mCanPushCharacter;
699
700
// We don't have any further interaction with sensors beyond an OnContactAdded notification
701
if (contact.mIsSensorB)
702
return false;
703
704
// If body B cannot receive an impulse, we're done
705
if (!settings.mCanReceiveImpulses || contact.mMotionTypeB != EMotionType::Dynamic)
706
return true;
707
708
// Lock the body we're colliding with
709
BodyLockWrite lock(mSystem->GetBodyLockInterface(), contact.mBodyB);
710
if (!lock.SucceededAndIsInBroadPhase())
711
return false; // Body has been removed, we should not collide with it anymore
712
const Body &body = lock.GetBody();
713
714
// Calculate the velocity that we want to apply at B so that it will start moving at the character's speed at the contact point
715
constexpr float cDamping = 0.9f;
716
constexpr float cPenetrationResolution = 0.4f;
717
Vec3 relative_velocity = inVelocity - contact.mLinearVelocity;
718
float projected_velocity = relative_velocity.Dot(contact.mContactNormal);
719
float delta_velocity = -projected_velocity * cDamping - min(contact.mDistance, 0.0f) * cPenetrationResolution / inDeltaTime;
720
721
// Don't apply impulses if we're separating
722
if (delta_velocity < 0.0f)
723
return true;
724
725
// Determine mass properties of the body we're colliding with
726
const MotionProperties *motion_properties = body.GetMotionProperties();
727
RVec3 center_of_mass = body.GetCenterOfMassPosition();
728
Mat44 inverse_inertia = body.GetInverseInertia();
729
float inverse_mass = motion_properties->GetInverseMass();
730
731
// Calculate the inverse of the mass of body B as seen at the contact point in the direction of the contact normal
732
Vec3 jacobian = Vec3(contact.mPosition - center_of_mass).Cross(contact.mContactNormal);
733
float inv_effective_mass = inverse_inertia.Multiply3x3(jacobian).Dot(jacobian) + inverse_mass;
734
735
// Impulse P = M dv
736
float impulse = delta_velocity / inv_effective_mass;
737
738
// Clamp the impulse according to the character strength, character strength is a force in newtons, P = F dt
739
float max_impulse = mMaxStrength * inDeltaTime;
740
impulse = min(impulse, max_impulse);
741
742
// Calculate the world space impulse to apply
743
Vec3 world_impulse = -impulse * contact.mContactNormal;
744
745
// Cancel impulse in down direction (we apply gravity later)
746
float impulse_dot_up = world_impulse.Dot(mUp);
747
if (impulse_dot_up < 0.0f)
748
world_impulse -= impulse_dot_up * mUp;
749
750
// Now apply the impulse (body is already locked so we use the no-lock interface)
751
mSystem->GetBodyInterfaceNoLock().AddImpulse(contact.mBodyB, world_impulse, contact.mPosition);
752
return true;
753
}
754
755
void CharacterVirtual::SolveConstraints(Vec3Arg inVelocity, float inDeltaTime, float inTimeRemaining, ConstraintList &ioConstraints, IgnoredContactList &ioIgnoredContacts, float &outTimeSimulated, Vec3 &outDisplacement, TempAllocator &inAllocator
756
#ifdef JPH_DEBUG_RENDERER
757
, bool inDrawConstraints
758
#endif // JPH_DEBUG_RENDERER
759
)
760
{
761
// If there are no constraints we can immediately move to our target
762
if (ioConstraints.empty())
763
{
764
outDisplacement = inVelocity * inTimeRemaining;
765
outTimeSimulated = inTimeRemaining;
766
return;
767
}
768
769
// Create array that holds the constraints in order of time of impact (sort will happen later)
770
Array<Constraint *, STLTempAllocator<Constraint *>> sorted_constraints(inAllocator);
771
sorted_constraints.resize(ioConstraints.size());
772
for (size_t index = 0; index < sorted_constraints.size(); index++)
773
sorted_constraints[index] = &ioConstraints[index];
774
775
// This is the velocity we use for the displacement, if we hit something it will be shortened
776
Vec3 velocity = inVelocity;
777
778
// Keep track of the last velocity that was applied to the character so that we can detect when the velocity reverses
779
Vec3 last_velocity = inVelocity;
780
781
// Start with no displacement
782
outDisplacement = Vec3::sZero();
783
outTimeSimulated = 0.0f;
784
785
// These are the contacts that we hit previously without moving a significant distance
786
Array<Constraint *, STLTempAllocator<Constraint *>> previous_contacts(inAllocator);
787
previous_contacts.resize(mMaxConstraintIterations);
788
int num_previous_contacts = 0;
789
790
// Loop for a max amount of iterations
791
for (uint iteration = 0; iteration < mMaxConstraintIterations; iteration++)
792
{
793
// Calculate time of impact for all constraints
794
for (Constraint &c : ioConstraints)
795
{
796
// Project velocity on plane direction
797
c.mProjectedVelocity = c.mPlane.GetNormal().Dot(c.mLinearVelocity - velocity);
798
if (c.mProjectedVelocity < 1.0e-6f)
799
{
800
c.mTOI = FLT_MAX;
801
}
802
else
803
{
804
// Distance to plane
805
float dist = c.mPlane.SignedDistance(outDisplacement);
806
807
if (dist - c.mProjectedVelocity * inTimeRemaining > -1.0e-4f)
808
{
809
// Too little penetration, accept the movement
810
c.mTOI = FLT_MAX;
811
}
812
else
813
{
814
// Calculate time of impact
815
c.mTOI = max(0.0f, dist / c.mProjectedVelocity);
816
}
817
}
818
}
819
820
// Sort constraints on proximity
821
QuickSort(sorted_constraints.begin(), sorted_constraints.end(), [](const Constraint *inLHS, const Constraint *inRHS) {
822
// If both constraints hit at t = 0 then order the one that will push the character furthest first
823
// Note that because we add velocity to penetrating contacts, this will also resolve contacts that penetrate the most
824
if (inLHS->mTOI <= 0.0f && inRHS->mTOI <= 0.0f)
825
return inLHS->mProjectedVelocity > inRHS->mProjectedVelocity;
826
827
// Then sort on time of impact
828
if (inLHS->mTOI != inRHS->mTOI)
829
return inLHS->mTOI < inRHS->mTOI;
830
831
// As a tie breaker sort static first so it has the most influence
832
return inLHS->mContact->mMotionTypeB > inRHS->mContact->mMotionTypeB;
833
});
834
835
// Find the first valid constraint
836
Constraint *constraint = nullptr;
837
for (Constraint *c : sorted_constraints)
838
{
839
// Take the first contact and see if we can reach it
840
if (c->mTOI >= inTimeRemaining)
841
{
842
// We can reach our goal!
843
outDisplacement += velocity * inTimeRemaining;
844
outTimeSimulated += inTimeRemaining;
845
return;
846
}
847
848
// Test if this contact was discarded by the contact callback before
849
if (c->mContact->mWasDiscarded)
850
continue;
851
852
// Handle the contact
853
if (!c->mContact->mHadCollision
854
&& !HandleContact(velocity, *c, inDeltaTime))
855
{
856
// Constraint should be ignored, remove it from the list
857
c->mContact->mWasDiscarded = true;
858
859
// Mark it as ignored for GetFirstContactForSweep
860
ioIgnoredContacts.emplace_back(*c->mContact);
861
continue;
862
}
863
864
// Cancel velocity of constraint if it cannot push the character
865
if (!c->mContact->mCanPushCharacter)
866
c->mLinearVelocity = Vec3::sZero();
867
868
// We found the first constraint that we want to collide with
869
constraint = c;
870
break;
871
}
872
873
if (constraint == nullptr)
874
{
875
// All constraints were discarded, we can reach our goal!
876
outDisplacement += velocity * inTimeRemaining;
877
outTimeSimulated += inTimeRemaining;
878
return;
879
}
880
881
// Move to the contact
882
outDisplacement += velocity * constraint->mTOI;
883
inTimeRemaining -= constraint->mTOI;
884
outTimeSimulated += constraint->mTOI;
885
886
// If there's not enough time left to be simulated, bail
887
if (inTimeRemaining < mMinTimeRemaining)
888
return;
889
890
// If we've moved significantly, clear all previous contacts
891
if (constraint->mTOI > 1.0e-4f)
892
num_previous_contacts = 0;
893
894
// Get the normal of the plane we're hitting
895
Vec3 plane_normal = constraint->mPlane.GetNormal();
896
897
// If we're hitting a steep slope we cancel the velocity towards the slope first so that we don't end up sliding up the slope
898
// (we may hit the slope before the vertical wall constraint we added which will result in a small movement up causing jitter in the character movement)
899
if (constraint->mIsSteepSlope)
900
{
901
// We're hitting a steep slope, create a vertical plane that blocks any further movement up the slope (note: not normalized)
902
Vec3 vertical_plane_normal = plane_normal - plane_normal.Dot(mUp) * mUp;
903
904
// Get the relative velocity between the character and the constraint
905
Vec3 relative_velocity = velocity - constraint->mLinearVelocity;
906
907
// Remove velocity towards the slope
908
velocity = velocity - min(0.0f, relative_velocity.Dot(vertical_plane_normal)) * vertical_plane_normal / vertical_plane_normal.LengthSq();
909
}
910
911
// Get the relative velocity between the character and the constraint
912
Vec3 relative_velocity = velocity - constraint->mLinearVelocity;
913
914
// Calculate new velocity if we cancel the relative velocity in the normal direction
915
Vec3 new_velocity = velocity - relative_velocity.Dot(plane_normal) * plane_normal;
916
917
// Find the normal of the previous contact that we will violate the most if we move in this new direction
918
float highest_penetration = 0.0f;
919
Constraint *other_constraint = nullptr;
920
for (Constraint **c = previous_contacts.data(); c < previous_contacts.data() + num_previous_contacts; ++c)
921
if (*c != constraint)
922
{
923
// Calculate how much we will penetrate if we move in this direction
924
Vec3 other_normal = (*c)->mPlane.GetNormal();
925
float penetration = ((*c)->mLinearVelocity - new_velocity).Dot(other_normal);
926
if (penetration > highest_penetration)
927
{
928
// We don't want parallel or anti-parallel normals as that will cause our cross product below to become zero. Slack is approx 10 degrees.
929
float dot = other_normal.Dot(plane_normal);
930
if (dot < 0.984f && dot > -0.984f)
931
{
932
highest_penetration = penetration;
933
other_constraint = *c;
934
}
935
}
936
}
937
938
// Check if we found a 2nd constraint
939
if (other_constraint != nullptr)
940
{
941
// Calculate the sliding direction and project the new velocity onto that sliding direction
942
Vec3 other_normal = other_constraint->mPlane.GetNormal();
943
Vec3 slide_dir = plane_normal.Cross(other_normal).Normalized();
944
Vec3 velocity_in_slide_dir = new_velocity.Dot(slide_dir) * slide_dir;
945
946
// Cancel the constraint velocity in the other constraint plane's direction so that we won't try to apply it again and keep ping ponging between planes
947
constraint->mLinearVelocity -= min(0.0f, constraint->mLinearVelocity.Dot(other_normal)) * other_normal;
948
949
// Cancel the other constraints velocity in this constraint plane's direction so that we won't try to apply it again and keep ping ponging between planes
950
other_constraint->mLinearVelocity -= min(0.0f, other_constraint->mLinearVelocity.Dot(plane_normal)) * plane_normal;
951
952
// Calculate the velocity of this constraint perpendicular to the slide direction
953
Vec3 perpendicular_velocity = constraint->mLinearVelocity - constraint->mLinearVelocity.Dot(slide_dir) * slide_dir;
954
955
// Calculate the velocity of the other constraint perpendicular to the slide direction
956
Vec3 other_perpendicular_velocity = other_constraint->mLinearVelocity - other_constraint->mLinearVelocity.Dot(slide_dir) * slide_dir;
957
958
// Add all components together
959
new_velocity = velocity_in_slide_dir + perpendicular_velocity + other_perpendicular_velocity;
960
}
961
962
// Allow application to modify calculated velocity
963
if (mListener != nullptr)
964
{
965
if (constraint->mContact->mCharacterB != nullptr)
966
mListener->OnCharacterContactSolve(this, constraint->mContact->mCharacterB, constraint->mContact->mSubShapeIDB, constraint->mContact->mPosition, constraint->mContact->mContactNormal, constraint->mContact->mLinearVelocity, constraint->mContact->mMaterial, velocity, new_velocity);
967
else
968
mListener->OnContactSolve(this, constraint->mContact->mBodyB, constraint->mContact->mSubShapeIDB, constraint->mContact->mPosition, constraint->mContact->mContactNormal, constraint->mContact->mLinearVelocity, constraint->mContact->mMaterial, velocity, new_velocity);
969
}
970
971
#ifdef JPH_DEBUG_RENDERER
972
if (inDrawConstraints)
973
{
974
// Calculate where to draw
975
RVec3 offset = mPosition + Vec3(0, 0, 2.5f * (iteration + 1));
976
977
// Draw constraint plane
978
DebugRenderer::sInstance->DrawPlane(offset, constraint->mPlane.GetNormal(), Color::sCyan, 1.0f);
979
980
// Draw 2nd constraint plane
981
if (other_constraint != nullptr)
982
DebugRenderer::sInstance->DrawPlane(offset, other_constraint->mPlane.GetNormal(), Color::sBlue, 1.0f);
983
984
// Draw starting velocity
985
DebugRenderer::sInstance->DrawArrow(offset, offset + velocity, Color::sGreen, 0.05f);
986
987
// Draw resulting velocity
988
DebugRenderer::sInstance->DrawArrow(offset, offset + new_velocity, Color::sRed, 0.05f);
989
}
990
#endif // JPH_DEBUG_RENDERER
991
992
// Update the velocity
993
velocity = new_velocity;
994
995
// Add the contact to the list so that next iteration we can avoid violating it again
996
previous_contacts[num_previous_contacts] = constraint;
997
num_previous_contacts++;
998
999
// Check early out
1000
if (constraint->mProjectedVelocity < 1.0e-8f // Constraint should not be pushing, otherwise there may be other constraints that are pushing us
1001
&& velocity.LengthSq() < 1.0e-8f) // There's not enough velocity left
1002
return;
1003
1004
// If the constraint has velocity we accept the new velocity, otherwise check that we didn't reverse velocity
1005
if (!constraint->mLinearVelocity.IsNearZero(1.0e-8f))
1006
last_velocity = constraint->mLinearVelocity;
1007
else if (velocity.Dot(last_velocity) < 0.0f)
1008
return;
1009
}
1010
}
1011
1012
void CharacterVirtual::UpdateSupportingContact(bool inSkipContactVelocityCheck, TempAllocator &inAllocator)
1013
{
1014
// Flag contacts as having a collision if they're close enough but ignore contacts we're moving away from.
1015
// Note that if we did MoveShape before we want to preserve any contacts that it marked as colliding
1016
for (Contact &c : mActiveContacts)
1017
if (!c.mWasDiscarded
1018
&& !c.mHadCollision
1019
&& c.mDistance < mCollisionTolerance
1020
&& (inSkipContactVelocityCheck || c.mSurfaceNormal.Dot(mLinearVelocity - c.mLinearVelocity) <= 1.0e-4f))
1021
{
1022
if (ValidateContact(c))
1023
{
1024
CharacterContactSettings dummy;
1025
ContactAdded(c, dummy);
1026
c.mHadCollision = true;
1027
}
1028
else
1029
c.mWasDiscarded = true;
1030
}
1031
1032
// Calculate transform that takes us to character local space
1033
RMat44 inv_transform = RMat44::sInverseRotationTranslation(mRotation, mPosition);
1034
1035
// Determine if we're supported or not
1036
int num_supported = 0;
1037
int num_sliding = 0;
1038
int num_avg_normal = 0;
1039
Vec3 avg_normal = Vec3::sZero();
1040
Vec3 avg_velocity = Vec3::sZero();
1041
const Contact *supporting_contact = nullptr;
1042
float max_cos_angle = -FLT_MAX;
1043
const Contact *deepest_contact = nullptr;
1044
float smallest_distance = FLT_MAX;
1045
for (const Contact &c : mActiveContacts)
1046
if (c.mHadCollision && !c.mWasDiscarded)
1047
{
1048
// Calculate the angle between the plane normal and the up direction
1049
float cos_angle = c.mSurfaceNormal.Dot(mUp);
1050
1051
// Find the deepest contact
1052
if (c.mDistance < smallest_distance)
1053
{
1054
deepest_contact = &c;
1055
smallest_distance = c.mDistance;
1056
}
1057
1058
// If this contact is in front of our plane, we cannot be supported by it
1059
if (mSupportingVolume.SignedDistance(Vec3(inv_transform * c.mPosition)) > 0.0f)
1060
continue;
1061
1062
// Find the contact with the normal that is pointing most upwards and store it
1063
if (max_cos_angle < cos_angle)
1064
{
1065
supporting_contact = &c;
1066
max_cos_angle = cos_angle;
1067
}
1068
1069
// Check if this is a sliding or supported contact
1070
bool is_supported = mCosMaxSlopeAngle > cNoMaxSlopeAngle || cos_angle >= mCosMaxSlopeAngle;
1071
if (is_supported)
1072
num_supported++;
1073
else
1074
num_sliding++;
1075
1076
// If the angle between the two is less than 85 degrees we also use it to calculate the average normal
1077
if (cos_angle >= 0.08f)
1078
{
1079
avg_normal += c.mSurfaceNormal;
1080
num_avg_normal++;
1081
1082
// For static or dynamic objects or for contacts that don't support us just take the contact velocity
1083
if (c.mMotionTypeB != EMotionType::Kinematic || !is_supported)
1084
avg_velocity += c.mLinearVelocity;
1085
else
1086
{
1087
// For keyframed objects that support us calculate the velocity at our position rather than at the contact position so that we properly follow the object
1088
BodyLockRead lock(mSystem->GetBodyLockInterface(), c.mBodyB);
1089
if (lock.SucceededAndIsInBroadPhase())
1090
{
1091
const Body &body = lock.GetBody();
1092
1093
// Get adjusted body velocity
1094
Vec3 linear_velocity, angular_velocity;
1095
GetAdjustedBodyVelocity(body, linear_velocity, angular_velocity);
1096
1097
// Calculate the ground velocity
1098
avg_velocity += CalculateCharacterGroundVelocity(body.GetCenterOfMassPosition(), linear_velocity, angular_velocity, mLastDeltaTime);
1099
}
1100
else
1101
{
1102
// Fall back to contact velocity
1103
avg_velocity += c.mLinearVelocity;
1104
}
1105
}
1106
}
1107
}
1108
1109
// Take either the most supporting contact or the deepest contact
1110
const Contact *best_contact = supporting_contact != nullptr? supporting_contact : deepest_contact;
1111
1112
// Calculate average normal and velocity
1113
if (num_avg_normal >= 1)
1114
{
1115
mGroundNormal = avg_normal.Normalized();
1116
mGroundVelocity = avg_velocity / float(num_avg_normal);
1117
}
1118
else if (best_contact != nullptr)
1119
{
1120
mGroundNormal = best_contact->mSurfaceNormal;
1121
mGroundVelocity = best_contact->mLinearVelocity;
1122
}
1123
else
1124
{
1125
mGroundNormal = Vec3::sZero();
1126
mGroundVelocity = Vec3::sZero();
1127
}
1128
1129
// Copy contact properties
1130
if (best_contact != nullptr)
1131
{
1132
mGroundBodyID = best_contact->mBodyB;
1133
mGroundBodySubShapeID = best_contact->mSubShapeIDB;
1134
mGroundPosition = best_contact->mPosition;
1135
mGroundMaterial = best_contact->mMaterial;
1136
mGroundUserData = best_contact->mUserData;
1137
}
1138
else
1139
{
1140
mGroundBodyID = BodyID();
1141
mGroundBodySubShapeID = SubShapeID();
1142
mGroundPosition = RVec3::sZero();
1143
mGroundMaterial = PhysicsMaterial::sDefault;
1144
mGroundUserData = 0;
1145
}
1146
1147
// Determine ground state
1148
if (num_supported > 0)
1149
{
1150
// We made contact with something that supports us
1151
mGroundState = EGroundState::OnGround;
1152
}
1153
else if (num_sliding > 0)
1154
{
1155
if ((mLinearVelocity - deepest_contact->mLinearVelocity).Dot(mUp) > 1.0e-4f)
1156
{
1157
// We cannot be on ground if we're moving upwards relative to the ground
1158
mGroundState = EGroundState::OnSteepGround;
1159
}
1160
else
1161
{
1162
// If we're sliding down, we may actually be standing on multiple sliding contacts in such a way that we can't slide off, in this case we're also supported
1163
1164
// Convert the contacts into constraints
1165
TempContactList contacts(mActiveContacts.begin(), mActiveContacts.end(), inAllocator);
1166
ConstraintList constraints(inAllocator);
1167
constraints.reserve(contacts.size() * 2);
1168
DetermineConstraints(contacts, mLastDeltaTime, constraints);
1169
1170
// Solve the displacement using these constraints, this is used to check if we didn't move at all because we are supported
1171
Vec3 displacement;
1172
float time_simulated;
1173
IgnoredContactList ignored_contacts(inAllocator);
1174
ignored_contacts.reserve(contacts.size());
1175
SolveConstraints(-mUp, 1.0f, 1.0f, constraints, ignored_contacts, time_simulated, displacement, inAllocator);
1176
1177
// If we're blocked then we're supported, otherwise we're sliding
1178
float min_required_displacement_sq = Square(0.6f * mLastDeltaTime);
1179
if (time_simulated < 0.001f || displacement.LengthSq() < min_required_displacement_sq)
1180
mGroundState = EGroundState::OnGround;
1181
else
1182
mGroundState = EGroundState::OnSteepGround;
1183
}
1184
}
1185
else
1186
{
1187
// Not supported by anything
1188
mGroundState = best_contact != nullptr? EGroundState::NotSupported : EGroundState::InAir;
1189
}
1190
}
1191
1192
void CharacterVirtual::StoreActiveContacts(const TempContactList &inContacts, TempAllocator &inAllocator)
1193
{
1194
StartTrackingContactChanges();
1195
1196
mActiveContacts.assign(inContacts.begin(), inContacts.end());
1197
1198
UpdateSupportingContact(true, inAllocator);
1199
1200
FinishTrackingContactChanges();
1201
}
1202
1203
void CharacterVirtual::MoveShape(RVec3 &ioPosition, Vec3Arg inVelocity, float inDeltaTime, ContactList *outActiveContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator
1204
#ifdef JPH_DEBUG_RENDERER
1205
, bool inDrawConstraints
1206
#endif // JPH_DEBUG_RENDERER
1207
)
1208
{
1209
JPH_DET_LOG("CharacterVirtual::MoveShape: pos: " << ioPosition << " vel: " << inVelocity << " dt: " << inDeltaTime);
1210
1211
Vec3 movement_direction = inVelocity.NormalizedOr(Vec3::sZero());
1212
1213
float time_remaining = inDeltaTime;
1214
for (uint iteration = 0; iteration < mMaxCollisionIterations && time_remaining >= mMinTimeRemaining; iteration++)
1215
{
1216
JPH_DET_LOG("iter: " << iteration << " time: " << time_remaining);
1217
1218
// Determine contacts in the neighborhood
1219
TempContactList contacts(inAllocator);
1220
contacts.reserve(mMaxNumHits);
1221
GetContactsAtPosition(ioPosition, movement_direction, mShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);
1222
1223
#ifdef JPH_ENABLE_DETERMINISM_LOG
1224
for (const Contact &c : contacts)
1225
JPH_DET_LOG("contact: " << c.mPosition << " vel: " << c.mLinearVelocity << " cnormal: " << c.mContactNormal << " snormal: " << c.mSurfaceNormal << " dist: " << c.mDistance << " fraction: " << c.mFraction << " body: " << c.mBodyB << " subshape: " << c.mSubShapeIDB);
1226
#endif // JPH_ENABLE_DETERMINISM_LOG
1227
1228
// Remove contacts with the same body that have conflicting normals
1229
IgnoredContactList ignored_contacts(inAllocator);
1230
ignored_contacts.reserve(contacts.size());
1231
RemoveConflictingContacts(contacts, ignored_contacts);
1232
1233
// Convert contacts into constraints
1234
ConstraintList constraints(inAllocator);
1235
constraints.reserve(contacts.size() * 2);
1236
DetermineConstraints(contacts, inDeltaTime, constraints);
1237
1238
#ifdef JPH_DEBUG_RENDERER
1239
bool draw_constraints = inDrawConstraints && iteration == 0;
1240
if (draw_constraints)
1241
{
1242
for (const Constraint &c : constraints)
1243
{
1244
// Draw contact point
1245
DebugRenderer::sInstance->DrawMarker(c.mContact->mPosition, Color::sYellow, 0.05f);
1246
Vec3 dist_to_plane = -c.mPlane.GetConstant() * c.mPlane.GetNormal();
1247
1248
// Draw arrow towards surface that we're hitting
1249
DebugRenderer::sInstance->DrawArrow(c.mContact->mPosition, c.mContact->mPosition - dist_to_plane, Color::sYellow, 0.05f);
1250
1251
// Draw plane around the player position indicating the space that we can move
1252
DebugRenderer::sInstance->DrawPlane(mPosition + dist_to_plane, c.mPlane.GetNormal(), Color::sCyan, 1.0f);
1253
DebugRenderer::sInstance->DrawArrow(mPosition + dist_to_plane, mPosition + dist_to_plane + c.mContact->mSurfaceNormal, Color::sRed, 0.05f);
1254
}
1255
}
1256
#endif // JPH_DEBUG_RENDERER
1257
1258
// Solve the displacement using these constraints
1259
Vec3 displacement;
1260
float time_simulated;
1261
SolveConstraints(inVelocity, inDeltaTime, time_remaining, constraints, ignored_contacts, time_simulated, displacement, inAllocator
1262
#ifdef JPH_DEBUG_RENDERER
1263
, draw_constraints
1264
#endif // JPH_DEBUG_RENDERER
1265
);
1266
1267
// Store the contacts now that the colliding ones have been marked
1268
if (outActiveContacts != nullptr)
1269
outActiveContacts->assign(contacts.begin(), contacts.end());
1270
1271
// Do a sweep to test if the path is really unobstructed
1272
Contact cast_contact;
1273
if (GetFirstContactForSweep(ioPosition, displacement, cast_contact, ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))
1274
{
1275
displacement *= cast_contact.mFraction;
1276
time_simulated *= cast_contact.mFraction;
1277
}
1278
1279
// Update the position
1280
ioPosition += displacement;
1281
time_remaining -= time_simulated;
1282
1283
// If the displacement during this iteration was too small we assume we cannot further progress this update
1284
if (displacement.LengthSq() < 1.0e-8f)
1285
break;
1286
}
1287
}
1288
1289
void CharacterVirtual::SetUserData(uint64 inUserData)
1290
{
1291
mUserData = inUserData;
1292
1293
if (!mInnerBodyID.IsInvalid())
1294
mSystem->GetBodyInterface().SetUserData(mInnerBodyID, inUserData);
1295
}
1296
1297
Vec3 CharacterVirtual::CancelVelocityTowardsSteepSlopes(Vec3Arg inDesiredVelocity) const
1298
{
1299
// If we're not pushing against a steep slope, return the desired velocity
1300
// Note: This is important as WalkStairs overrides the ground state to OnGround when its first check fails but the second succeeds
1301
if (mGroundState == CharacterVirtual::EGroundState::OnGround
1302
|| mGroundState == CharacterVirtual::EGroundState::InAir)
1303
return inDesiredVelocity;
1304
1305
Vec3 desired_velocity = inDesiredVelocity;
1306
for (const Contact &c : mActiveContacts)
1307
if (c.mHadCollision
1308
&& !c.mWasDiscarded
1309
&& IsSlopeTooSteep(c.mSurfaceNormal))
1310
{
1311
// Note that we use the contact normal to allow for better sliding as the surface normal may be in the opposite direction of movement.
1312
Vec3 normal = c.mContactNormal;
1313
1314
// Remove normal vertical component
1315
normal -= normal.Dot(mUp) * mUp;
1316
1317
// Cancel horizontal movement in opposite direction
1318
float dot = normal.Dot(desired_velocity);
1319
if (dot < 0.0f)
1320
desired_velocity -= (dot * normal) / normal.LengthSq();
1321
}
1322
return desired_velocity;
1323
}
1324
1325
void CharacterVirtual::StartTrackingContactChanges()
1326
{
1327
// Check if we're starting for the first time
1328
if (++mTrackingContactChanges > 1)
1329
return;
1330
1331
// No need to track anything if we don't have a listener
1332
JPH_ASSERT(mListenerContacts.empty());
1333
if (mListener == nullptr)
1334
return;
1335
1336
// Mark all current contacts as not seen
1337
mListenerContacts.reserve(ListenerContacts::size_type(mActiveContacts.size()));
1338
for (const Contact &c : mActiveContacts)
1339
if (c.mHadCollision)
1340
mListenerContacts.insert(ListenerContacts::value_type(c, ListenerContactValue()));
1341
}
1342
1343
void CharacterVirtual::FinishTrackingContactChanges()
1344
{
1345
// Check if we have to do anything
1346
int count = --mTrackingContactChanges;
1347
JPH_ASSERT(count >= 0, "Called FinishTrackingContactChanges more times than StartTrackingContactChanges");
1348
if (count > 0)
1349
return;
1350
1351
// No need to track anything if we don't have a listener
1352
if (mListener == nullptr)
1353
return;
1354
1355
// Since we can do multiple operations (e.g. Update followed by WalkStairs)
1356
// we can end up with contacts that were marked as active to the listener but that are
1357
// no longer in the active contact list. We go over all contacts and mark them again
1358
// to ensure that these lists are in sync.
1359
for (ListenerContacts::value_type &c : mListenerContacts)
1360
c.second.mCount = 0;
1361
for (const Contact &c : mActiveContacts)
1362
if (c.mHadCollision)
1363
{
1364
ListenerContacts::iterator it = mListenerContacts.find(c);
1365
JPH_ASSERT(it != mListenerContacts.end());
1366
it->second.mCount = 1;
1367
}
1368
1369
// Call contact removal callbacks
1370
for (ListenerContacts::iterator it = mListenerContacts.begin(); it != mListenerContacts.end(); ++it)
1371
if (it->second.mCount == 0)
1372
{
1373
const ContactKey &c = it->first;
1374
if (!c.mCharacterIDB.IsInvalid())
1375
mListener->OnCharacterContactRemoved(this, c.mCharacterIDB, c.mSubShapeIDB);
1376
else
1377
mListener->OnContactRemoved(this, c.mBodyB, c.mSubShapeIDB);
1378
}
1379
mListenerContacts.ClearAndKeepMemory();
1380
}
1381
1382
void CharacterVirtual::Update(float inDeltaTime, Vec3Arg inGravity, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
1383
{
1384
// If there's no delta time, we don't need to do anything
1385
if (inDeltaTime <= 0.0f)
1386
return;
1387
1388
StartTrackingContactChanges();
1389
JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });
1390
1391
// Remember delta time for checking if we're supported by the ground
1392
mLastDeltaTime = inDeltaTime;
1393
1394
// Slide the shape through the world
1395
MoveShape(mPosition, mLinearVelocity, inDeltaTime, &mActiveContacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator
1396
#ifdef JPH_DEBUG_RENDERER
1397
, sDrawConstraints
1398
#endif // JPH_DEBUG_RENDERER
1399
);
1400
1401
// Determine the object that we're standing on
1402
UpdateSupportingContact(false, inAllocator);
1403
1404
// Ensure that the rigid body ends up at the new position
1405
UpdateInnerBodyTransform();
1406
1407
// If we're on the ground
1408
if (!mGroundBodyID.IsInvalid() && mMass > 0.0f)
1409
{
1410
// Add the impulse to the ground due to gravity: P = F dt = M g dt
1411
float normal_dot_gravity = mGroundNormal.Dot(inGravity);
1412
if (normal_dot_gravity < 0.0f)
1413
{
1414
Vec3 world_impulse = -(mMass * normal_dot_gravity / inGravity.Length() * inDeltaTime) * inGravity;
1415
mSystem->GetBodyInterface().AddImpulse(mGroundBodyID, world_impulse, mGroundPosition);
1416
}
1417
}
1418
}
1419
1420
void CharacterVirtual::RefreshContacts(const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
1421
{
1422
// Determine the contacts
1423
TempContactList contacts(inAllocator);
1424
contacts.reserve(mMaxNumHits);
1425
GetContactsAtPosition(mPosition, mLinearVelocity.NormalizedOr(Vec3::sZero()), mShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);
1426
1427
StoreActiveContacts(contacts, inAllocator);
1428
}
1429
1430
void CharacterVirtual::UpdateGroundVelocity()
1431
{
1432
BodyLockRead lock(mSystem->GetBodyLockInterface(), mGroundBodyID);
1433
if (lock.SucceededAndIsInBroadPhase())
1434
{
1435
const Body &body = lock.GetBody();
1436
1437
// Get adjusted body velocity
1438
Vec3 linear_velocity, angular_velocity;
1439
GetAdjustedBodyVelocity(body, linear_velocity, angular_velocity);
1440
1441
// Calculate the ground velocity
1442
mGroundVelocity = CalculateCharacterGroundVelocity(body.GetCenterOfMassPosition(), linear_velocity, angular_velocity, mLastDeltaTime);
1443
}
1444
}
1445
1446
void CharacterVirtual::MoveToContact(RVec3Arg inPosition, const Contact &inContact, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
1447
{
1448
// Set the new position
1449
SetPosition(inPosition);
1450
1451
// Trigger contact added callback
1452
CharacterContactSettings dummy;
1453
ContactAdded(inContact, dummy);
1454
1455
// Determine the contacts
1456
TempContactList contacts(inAllocator);
1457
contacts.reserve(mMaxNumHits + 1); // +1 because we can add one extra below
1458
GetContactsAtPosition(mPosition, mLinearVelocity.NormalizedOr(Vec3::sZero()), mShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);
1459
1460
// Ensure that we mark inContact as colliding
1461
bool found_contact = false;
1462
for (Contact &c : contacts)
1463
if (c.mBodyB == inContact.mBodyB
1464
&& c.mSubShapeIDB == inContact.mSubShapeIDB)
1465
{
1466
c.mHadCollision = true;
1467
found_contact = true;
1468
}
1469
if (!found_contact)
1470
{
1471
contacts.push_back(inContact);
1472
1473
Contact &copy = contacts.back();
1474
copy.mHadCollision = true;
1475
}
1476
1477
StoreActiveContacts(contacts, inAllocator);
1478
JPH_ASSERT(mGroundState != EGroundState::InAir);
1479
1480
// Ensure that the rigid body ends up at the new position
1481
UpdateInnerBodyTransform();
1482
}
1483
1484
bool CharacterVirtual::SetShape(const Shape *inShape, float inMaxPenetrationDepth, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
1485
{
1486
if (mShape == nullptr || mSystem == nullptr)
1487
{
1488
// It hasn't been initialized yet
1489
mShape = inShape;
1490
return true;
1491
}
1492
1493
if (inShape != mShape && inShape != nullptr)
1494
{
1495
if (inMaxPenetrationDepth < FLT_MAX)
1496
{
1497
// Check collision around the new shape
1498
TempContactList contacts(inAllocator);
1499
contacts.reserve(mMaxNumHits);
1500
GetContactsAtPosition(mPosition, mLinearVelocity.NormalizedOr(Vec3::sZero()), inShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);
1501
1502
// Test if this results in penetration, if so cancel the transition
1503
for (const Contact &c : contacts)
1504
if (c.mDistance < -inMaxPenetrationDepth
1505
&& !c.mIsSensorB)
1506
return false;
1507
1508
StoreActiveContacts(contacts, inAllocator);
1509
}
1510
1511
// Set new shape
1512
mShape = inShape;
1513
}
1514
1515
return mShape == inShape;
1516
}
1517
1518
void CharacterVirtual::SetInnerBodyShape(const Shape *inShape)
1519
{
1520
mSystem->GetBodyInterface().SetShape(mInnerBodyID, inShape, false, EActivation::DontActivate);
1521
}
1522
1523
bool CharacterVirtual::CanWalkStairs(Vec3Arg inLinearVelocity) const
1524
{
1525
// We can only walk stairs if we're supported
1526
if (!IsSupported())
1527
return false;
1528
1529
// Check if there's enough horizontal velocity to trigger a stair walk
1530
Vec3 horizontal_velocity = inLinearVelocity - inLinearVelocity.Dot(mUp) * mUp;
1531
if (horizontal_velocity.IsNearZero(1.0e-6f))
1532
return false;
1533
1534
// Check contacts for steep slopes
1535
for (const Contact &c : mActiveContacts)
1536
if (c.mHadCollision
1537
&& !c.mWasDiscarded
1538
&& c.mSurfaceNormal.Dot(horizontal_velocity - c.mLinearVelocity) < 0.0f // Pushing into the contact
1539
&& IsSlopeTooSteep(c.mSurfaceNormal)) // Slope too steep
1540
return true;
1541
1542
return false;
1543
}
1544
1545
bool CharacterVirtual::WalkStairs(float inDeltaTime, Vec3Arg inStepUp, Vec3Arg inStepForward, Vec3Arg inStepForwardTest, Vec3Arg inStepDownExtra, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
1546
{
1547
StartTrackingContactChanges();
1548
JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });
1549
1550
// Move up
1551
Vec3 up = inStepUp;
1552
Contact contact;
1553
IgnoredContactList dummy_ignored_contacts(inAllocator);
1554
if (GetFirstContactForSweep(mPosition, up, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))
1555
{
1556
if (contact.mFraction < 1.0e-6f)
1557
return false; // No movement, cancel
1558
1559
// Limit up movement to the first contact point
1560
up *= contact.mFraction;
1561
}
1562
RVec3 up_position = mPosition + up;
1563
1564
#ifdef JPH_DEBUG_RENDERER
1565
// Draw sweep up
1566
if (sDrawWalkStairs)
1567
DebugRenderer::sInstance->DrawArrow(mPosition, up_position, Color::sWhite, 0.01f);
1568
#endif // JPH_DEBUG_RENDERER
1569
1570
// Collect normals of steep slopes that we would like to walk stairs on.
1571
// We need to do this before calling MoveShape because it will update mActiveContacts.
1572
Vec3 character_velocity = inStepForward / inDeltaTime;
1573
Vec3 horizontal_velocity = character_velocity - character_velocity.Dot(mUp) * mUp;
1574
Array<Vec3, STLTempAllocator<Vec3>> steep_slope_normals(inAllocator);
1575
steep_slope_normals.reserve(mActiveContacts.size());
1576
for (const Contact &c : mActiveContacts)
1577
if (c.mHadCollision
1578
&& !c.mWasDiscarded
1579
&& c.mSurfaceNormal.Dot(horizontal_velocity - c.mLinearVelocity) < 0.0f // Pushing into the contact
1580
&& IsSlopeTooSteep(c.mSurfaceNormal)) // Slope too steep
1581
steep_slope_normals.push_back(c.mSurfaceNormal);
1582
if (steep_slope_normals.empty())
1583
return false; // No steep slopes, cancel
1584
1585
// Horizontal movement
1586
RVec3 new_position = up_position;
1587
MoveShape(new_position, character_velocity, inDeltaTime, nullptr, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);
1588
Vec3 horizontal_movement = Vec3(new_position - up_position);
1589
float horizontal_movement_sq = horizontal_movement.LengthSq();
1590
if (horizontal_movement_sq < 1.0e-8f)
1591
return false; // No movement, cancel
1592
1593
// Check if we made any progress towards any of the steep slopes, if not we just slid along the slope
1594
// so we need to cancel the stair walk or else we will move faster than we should as we've done
1595
// normal movement first and then stair walk.
1596
bool made_progress = false;
1597
float max_dot = -0.05f * inStepForward.Length();
1598
for (const Vec3 &normal : steep_slope_normals)
1599
if (normal.Dot(horizontal_movement) < max_dot)
1600
{
1601
// We moved more than 5% of the forward step against a steep slope, accept this as progress
1602
made_progress = true;
1603
break;
1604
}
1605
if (!made_progress)
1606
return false;
1607
1608
#ifdef JPH_DEBUG_RENDERER
1609
// Draw horizontal sweep
1610
if (sDrawWalkStairs)
1611
DebugRenderer::sInstance->DrawArrow(up_position, new_position, Color::sWhite, 0.01f);
1612
#endif // JPH_DEBUG_RENDERER
1613
1614
// Move down towards the floor.
1615
// Note that we travel the same amount down as we traveled up with the specified extra
1616
Vec3 down = -up + inStepDownExtra;
1617
if (!GetFirstContactForSweep(new_position, down, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))
1618
return false; // No floor found, we're in mid air, cancel stair walk
1619
1620
#ifdef JPH_DEBUG_RENDERER
1621
// Draw sweep down
1622
if (sDrawWalkStairs)
1623
{
1624
RVec3 debug_pos = new_position + contact.mFraction * down;
1625
DebugRenderer::sInstance->DrawArrow(new_position, debug_pos, Color::sWhite, 0.01f);
1626
DebugRenderer::sInstance->DrawArrow(contact.mPosition, contact.mPosition + contact.mSurfaceNormal, Color::sWhite, 0.01f);
1627
mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(debug_pos, mRotation, mShape), Vec3::sOne(), Color::sWhite, false, true);
1628
}
1629
#endif // JPH_DEBUG_RENDERER
1630
1631
// Test for floor that will support the character
1632
if (IsSlopeTooSteep(contact.mSurfaceNormal))
1633
{
1634
// If no test position was provided, we cancel the stair walk
1635
if (inStepForwardTest.IsNearZero())
1636
return false;
1637
1638
// Delta time may be very small, so it may be that we hit the edge of a step and the normal is too horizontal.
1639
// In order to judge if the floor is flat further along the sweep, we test again for a floor at inStepForwardTest
1640
// and check if the normal is valid there.
1641
RVec3 test_position = up_position;
1642
MoveShape(test_position, inStepForwardTest / inDeltaTime, inDeltaTime, nullptr, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);
1643
float test_horizontal_movement_sq = Vec3(test_position - up_position).LengthSq();
1644
if (test_horizontal_movement_sq <= horizontal_movement_sq + 1.0e-8f)
1645
return false; // We didn't move any further than in the previous test
1646
1647
#ifdef JPH_DEBUG_RENDERER
1648
// Draw 2nd sweep horizontal
1649
if (sDrawWalkStairs)
1650
DebugRenderer::sInstance->DrawArrow(up_position, test_position, Color::sCyan, 0.01f);
1651
#endif // JPH_DEBUG_RENDERER
1652
1653
// Then sweep down
1654
Contact test_contact;
1655
if (!GetFirstContactForSweep(test_position, down, test_contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))
1656
return false;
1657
1658
#ifdef JPH_DEBUG_RENDERER
1659
// Draw 2nd sweep down
1660
if (sDrawWalkStairs)
1661
{
1662
RVec3 debug_pos = test_position + test_contact.mFraction * down;
1663
DebugRenderer::sInstance->DrawArrow(test_position, debug_pos, Color::sCyan, 0.01f);
1664
DebugRenderer::sInstance->DrawArrow(test_contact.mPosition, test_contact.mPosition + test_contact.mSurfaceNormal, Color::sCyan, 0.01f);
1665
mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(debug_pos, mRotation, mShape), Vec3::sOne(), Color::sCyan, false, true);
1666
}
1667
#endif // JPH_DEBUG_RENDERER
1668
1669
if (IsSlopeTooSteep(test_contact.mSurfaceNormal))
1670
return false;
1671
}
1672
1673
// Calculate new down position
1674
down *= contact.mFraction;
1675
new_position += down;
1676
1677
// Move the character to the new location
1678
MoveToContact(new_position, contact, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);
1679
1680
// Override ground state to 'on ground', it is possible that the contact normal is too steep, but in this case the inStepForwardTest has found a contact normal that is not too steep
1681
mGroundState = EGroundState::OnGround;
1682
1683
return true;
1684
}
1685
1686
bool CharacterVirtual::StickToFloor(Vec3Arg inStepDown, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
1687
{
1688
StartTrackingContactChanges();
1689
JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });
1690
1691
// Try to find the floor
1692
Contact contact;
1693
IgnoredContactList dummy_ignored_contacts(inAllocator);
1694
if (!GetFirstContactForSweep(mPosition, inStepDown, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))
1695
return false; // If no floor found, don't update our position
1696
1697
// Calculate new position
1698
RVec3 new_position = mPosition + contact.mFraction * inStepDown;
1699
1700
#ifdef JPH_DEBUG_RENDERER
1701
// Draw sweep down
1702
if (sDrawStickToFloor)
1703
{
1704
DebugRenderer::sInstance->DrawArrow(mPosition, new_position, Color::sOrange, 0.01f);
1705
mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(new_position, mRotation, mShape), Vec3::sOne(), Color::sOrange, false, true);
1706
}
1707
#endif // JPH_DEBUG_RENDERER
1708
1709
// Move the character to the new location
1710
MoveToContact(new_position, contact, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);
1711
return true;
1712
}
1713
1714
void CharacterVirtual::ExtendedUpdate(float inDeltaTime, Vec3Arg inGravity, const ExtendedUpdateSettings &inSettings, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
1715
{
1716
StartTrackingContactChanges();
1717
JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });
1718
1719
// Update the velocity
1720
Vec3 desired_velocity = mLinearVelocity;
1721
mLinearVelocity = CancelVelocityTowardsSteepSlopes(desired_velocity);
1722
1723
// Remember old position
1724
RVec3 old_position = mPosition;
1725
1726
// Track if on ground before the update
1727
bool ground_to_air = IsSupported();
1728
1729
// Update the character position (instant, do not have to wait for physics update)
1730
Update(inDeltaTime, inGravity, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);
1731
1732
// ... and that we got into air after
1733
if (IsSupported())
1734
ground_to_air = false;
1735
1736
// If stick to floor enabled and we're going from supported to not supported
1737
if (ground_to_air && !inSettings.mStickToFloorStepDown.IsNearZero())
1738
{
1739
// If we're not moving up, stick to the floor
1740
float velocity = Vec3(mPosition - old_position).Dot(mUp) / inDeltaTime;
1741
if (velocity <= 1.0e-6f)
1742
StickToFloor(inSettings.mStickToFloorStepDown, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);
1743
}
1744
1745
// If walk stairs enabled
1746
if (!inSettings.mWalkStairsStepUp.IsNearZero())
1747
{
1748
// Calculate how much we wanted to move horizontally
1749
Vec3 desired_horizontal_step = desired_velocity * inDeltaTime;
1750
desired_horizontal_step -= desired_horizontal_step.Dot(mUp) * mUp;
1751
float desired_horizontal_step_len = desired_horizontal_step.Length();
1752
if (desired_horizontal_step_len > 0.0f)
1753
{
1754
// Calculate how much we moved horizontally
1755
Vec3 achieved_horizontal_step = Vec3(mPosition - old_position);
1756
achieved_horizontal_step -= achieved_horizontal_step.Dot(mUp) * mUp;
1757
1758
// Only count movement in the direction of the desired movement
1759
// (otherwise we find it ok if we're sliding downhill while we're trying to climb uphill)
1760
Vec3 step_forward_normalized = desired_horizontal_step / desired_horizontal_step_len;
1761
achieved_horizontal_step = max(0.0f, achieved_horizontal_step.Dot(step_forward_normalized)) * step_forward_normalized;
1762
float achieved_horizontal_step_len = achieved_horizontal_step.Length();
1763
1764
// If we didn't move as far as we wanted and we're against a slope that's too steep
1765
if (achieved_horizontal_step_len + 1.0e-4f < desired_horizontal_step_len
1766
&& CanWalkStairs(desired_velocity))
1767
{
1768
// Calculate how much we should step forward
1769
// Note that we clamp the step forward to a minimum distance. This is done because at very high frame rates the delta time
1770
// may be very small, causing a very small step forward. If the step becomes small enough, we may not move far enough
1771
// horizontally to actually end up at the top of the step.
1772
Vec3 step_forward = step_forward_normalized * max(inSettings.mWalkStairsMinStepForward, desired_horizontal_step_len - achieved_horizontal_step_len);
1773
1774
// Calculate how far to scan ahead for a floor. This is only used in case the floor normal at step_forward is too steep.
1775
// In that case an additional check will be performed at this distance to check if that normal is not too steep.
1776
// Start with the ground normal in the horizontal plane and normalizing it
1777
Vec3 step_forward_test = -mGroundNormal;
1778
step_forward_test -= step_forward_test.Dot(mUp) * mUp;
1779
step_forward_test = step_forward_test.NormalizedOr(step_forward_normalized);
1780
1781
// If this normalized vector and the character forward vector is bigger than a preset angle, we use the character forward vector instead of the ground normal
1782
// to do our forward test
1783
if (step_forward_test.Dot(step_forward_normalized) < inSettings.mWalkStairsCosAngleForwardContact)
1784
step_forward_test = step_forward_normalized;
1785
1786
// Calculate the correct magnitude for the test vector
1787
step_forward_test *= inSettings.mWalkStairsStepForwardTest;
1788
1789
WalkStairs(inDeltaTime, inSettings.mWalkStairsStepUp, step_forward, step_forward_test, inSettings.mWalkStairsStepDownExtra, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);
1790
}
1791
}
1792
}
1793
}
1794
1795
void CharacterVirtual::ContactKey::SaveState(StateRecorder &inStream) const
1796
{
1797
inStream.Write(mBodyB);
1798
inStream.Write(mCharacterIDB);
1799
inStream.Write(mSubShapeIDB);
1800
}
1801
1802
void CharacterVirtual::ContactKey::RestoreState(StateRecorder &inStream)
1803
{
1804
inStream.Read(mBodyB);
1805
inStream.Read(mCharacterIDB);
1806
inStream.Read(mSubShapeIDB);
1807
}
1808
1809
void CharacterVirtual::Contact::SaveState(StateRecorder &inStream) const
1810
{
1811
ContactKey::SaveState(inStream);
1812
1813
inStream.Write(mPosition);
1814
inStream.Write(mLinearVelocity);
1815
inStream.Write(mContactNormal);
1816
inStream.Write(mSurfaceNormal);
1817
inStream.Write(mDistance);
1818
inStream.Write(mFraction);
1819
inStream.Write(mMotionTypeB);
1820
inStream.Write(mIsSensorB);
1821
inStream.Write(mHadCollision);
1822
inStream.Write(mWasDiscarded);
1823
inStream.Write(mCanPushCharacter);
1824
// Cannot store pointers to character B, user data and material
1825
}
1826
1827
void CharacterVirtual::Contact::RestoreState(StateRecorder &inStream)
1828
{
1829
ContactKey::RestoreState(inStream);
1830
1831
inStream.Read(mPosition);
1832
inStream.Read(mLinearVelocity);
1833
inStream.Read(mContactNormal);
1834
inStream.Read(mSurfaceNormal);
1835
inStream.Read(mDistance);
1836
inStream.Read(mFraction);
1837
inStream.Read(mMotionTypeB);
1838
inStream.Read(mIsSensorB);
1839
inStream.Read(mHadCollision);
1840
inStream.Read(mWasDiscarded);
1841
inStream.Read(mCanPushCharacter);
1842
mCharacterB = nullptr; // Cannot restore character B
1843
mUserData = 0; // Cannot restore user data
1844
mMaterial = PhysicsMaterial::sDefault; // Cannot restore material
1845
}
1846
1847
void CharacterVirtual::SaveState(StateRecorder &inStream) const
1848
{
1849
CharacterBase::SaveState(inStream);
1850
1851
inStream.Write(mPosition);
1852
inStream.Write(mRotation);
1853
inStream.Write(mLinearVelocity);
1854
inStream.Write(mLastDeltaTime);
1855
inStream.Write(mMaxHitsExceeded);
1856
1857
// Store contacts that had collision, we're using it at the beginning of the step in CancelVelocityTowardsSteepSlopes
1858
uint32 num_contacts = 0;
1859
for (const Contact &c : mActiveContacts)
1860
if (c.mHadCollision)
1861
++num_contacts;
1862
inStream.Write(num_contacts);
1863
for (const Contact &c : mActiveContacts)
1864
if (c.mHadCollision)
1865
c.SaveState(inStream);
1866
}
1867
1868
void CharacterVirtual::RestoreState(StateRecorder &inStream)
1869
{
1870
CharacterBase::RestoreState(inStream);
1871
1872
inStream.Read(mPosition);
1873
inStream.Read(mRotation);
1874
inStream.Read(mLinearVelocity);
1875
inStream.Read(mLastDeltaTime);
1876
inStream.Read(mMaxHitsExceeded);
1877
1878
// When validating remove contacts that don't have collision since we didn't save them
1879
if (inStream.IsValidating())
1880
for (int i = (int)mActiveContacts.size() - 1; i >= 0; --i)
1881
if (!mActiveContacts[i].mHadCollision)
1882
mActiveContacts.erase(mActiveContacts.begin() + i);
1883
1884
uint32 num_contacts = (uint32)mActiveContacts.size();
1885
inStream.Read(num_contacts);
1886
mActiveContacts.resize(num_contacts);
1887
for (Contact &c : mActiveContacts)
1888
c.RestoreState(inStream);
1889
}
1890
1891
JPH_NAMESPACE_END
1892
1893