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