Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Character/CharacterVirtual.cpp
9912 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#include <Jolt/Jolt.h>56#include <Jolt/Physics/Character/CharacterVirtual.h>7#include <Jolt/Physics/Body/Body.h>8#include <Jolt/Physics/Body/BodyCreationSettings.h>9#include <Jolt/Physics/PhysicsSystem.h>10#include <Jolt/Physics/Collision/ShapeCast.h>11#include <Jolt/Physics/Collision/CollideShape.h>12#include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>13#include <Jolt/Physics/Collision/Shape/ScaledShape.h>14#include <Jolt/Physics/Collision/CollisionDispatch.h>15#include <Jolt/Core/QuickSort.h>16#include <Jolt/Core/ScopeExit.h>17#include <Jolt/Geometry/ConvexSupport.h>18#include <Jolt/Geometry/GJKClosestPoint.h>19#include <Jolt/Geometry/RayAABox.h>20#ifdef JPH_DEBUG_RENDERER21#include <Jolt/Renderer/DebugRenderer.h>22#endif // JPH_DEBUG_RENDERER2324JPH_NAMESPACE_BEGIN2526void CharacterVsCharacterCollisionSimple::Remove(const CharacterVirtual *inCharacter)27{28Array<CharacterVirtual *>::iterator i = std::find(mCharacters.begin(), mCharacters.end(), inCharacter);29if (i != mCharacters.end())30mCharacters.erase(i);31}3233void CharacterVsCharacterCollisionSimple::CollideCharacter(const CharacterVirtual *inCharacter, RMat44Arg inCenterOfMassTransform, const CollideShapeSettings &inCollideShapeSettings, RVec3Arg inBaseOffset, CollideShapeCollector &ioCollector) const34{35// Make shape 1 relative to inBaseOffset36Mat44 transform1 = inCenterOfMassTransform.PostTranslated(-inBaseOffset).ToMat44();3738const Shape *shape1 = inCharacter->GetShape();39CollideShapeSettings settings = inCollideShapeSettings;4041// Get bounds for character42AABox bounds1 = shape1->GetWorldSpaceBounds(transform1, Vec3::sOne());4344// Iterate over all characters45for (const CharacterVirtual *c : mCharacters)46if (c != inCharacter47&& !ioCollector.ShouldEarlyOut())48{49// Make shape 2 relative to inBaseOffset50Mat44 transform2 = c->GetCenterOfMassTransform().PostTranslated(-inBaseOffset).ToMat44();5152// We need to add the padding of character 2 so that we will detect collision with its outer shell53settings.mMaxSeparationDistance = inCollideShapeSettings.mMaxSeparationDistance + c->GetCharacterPadding();5455// Check if the bounding boxes of the characters overlap56const Shape *shape2 = c->GetShape();57AABox bounds2 = shape2->GetWorldSpaceBounds(transform2, Vec3::sOne());58bounds2.ExpandBy(Vec3::sReplicate(settings.mMaxSeparationDistance));59if (!bounds1.Overlaps(bounds2))60continue;6162// Collector needs to know which character we're colliding with63ioCollector.SetUserData(reinterpret_cast<uint64>(c));6465// Note that this collides against the character's shape without padding, this will be corrected for in CharacterVirtual::GetContactsAtPosition66CollisionDispatch::sCollideShapeVsShape(shape1, shape2, Vec3::sOne(), Vec3::sOne(), transform1, transform2, SubShapeIDCreator(), SubShapeIDCreator(), settings, ioCollector);67}6869// Reset the user data70ioCollector.SetUserData(0);71}7273void CharacterVsCharacterCollisionSimple::CastCharacter(const CharacterVirtual *inCharacter, RMat44Arg inCenterOfMassTransform, Vec3Arg inDirection, const ShapeCastSettings &inShapeCastSettings, RVec3Arg inBaseOffset, CastShapeCollector &ioCollector) const74{75// Convert shape cast relative to inBaseOffset76Mat44 transform1 = inCenterOfMassTransform.PostTranslated(-inBaseOffset).ToMat44();77ShapeCast shape_cast(inCharacter->GetShape(), Vec3::sOne(), transform1, inDirection);7879// Get world space bounds of the character in the form of center and extent80Vec3 origin = shape_cast.mShapeWorldBounds.GetCenter();81Vec3 extents = shape_cast.mShapeWorldBounds.GetExtent();8283// Iterate over all characters84for (const CharacterVirtual *c : mCharacters)85if (c != inCharacter86&& !ioCollector.ShouldEarlyOut())87{88// Make shape 2 relative to inBaseOffset89Mat44 transform2 = c->GetCenterOfMassTransform().PostTranslated(-inBaseOffset).ToMat44();9091// Sweep bounding box of the character against the bounding box of the other character to see if they can collide92const Shape *shape2 = c->GetShape();93AABox bounds2 = shape2->GetWorldSpaceBounds(transform2, Vec3::sOne());94bounds2.ExpandBy(extents);95if (!RayAABoxHits(origin, inDirection, bounds2.mMin, bounds2.mMax))96continue;9798// Collector needs to know which character we're colliding with99ioCollector.SetUserData(reinterpret_cast<uint64>(c));100101// Note that this collides against the character's shape without padding, this will be corrected for in CharacterVirtual::GetFirstContactForSweep102CollisionDispatch::sCastShapeVsShapeWorldSpace(shape_cast, inShapeCastSettings, shape2, Vec3::sOne(), { }, transform2, SubShapeIDCreator(), SubShapeIDCreator(), ioCollector);103}104105// Reset the user data106ioCollector.SetUserData(0);107}108109CharacterVirtual::CharacterVirtual(const CharacterVirtualSettings *inSettings, RVec3Arg inPosition, QuatArg inRotation, uint64 inUserData, PhysicsSystem *inSystem) :110CharacterBase(inSettings, inSystem),111mID(inSettings->mID),112mBackFaceMode(inSettings->mBackFaceMode),113mPredictiveContactDistance(inSettings->mPredictiveContactDistance),114mMaxCollisionIterations(inSettings->mMaxCollisionIterations),115mMaxConstraintIterations(inSettings->mMaxConstraintIterations),116mMinTimeRemaining(inSettings->mMinTimeRemaining),117mCollisionTolerance(inSettings->mCollisionTolerance),118mCharacterPadding(inSettings->mCharacterPadding),119mMaxNumHits(inSettings->mMaxNumHits),120mHitReductionCosMaxAngle(inSettings->mHitReductionCosMaxAngle),121mPenetrationRecoverySpeed(inSettings->mPenetrationRecoverySpeed),122mEnhancedInternalEdgeRemoval(inSettings->mEnhancedInternalEdgeRemoval),123mShapeOffset(inSettings->mShapeOffset),124mPosition(inPosition),125mRotation(inRotation),126mUserData(inUserData)127{128JPH_ASSERT(!mID.IsInvalid());129130// Copy settings131SetMaxStrength(inSettings->mMaxStrength);132SetMass(inSettings->mMass);133134// Create an inner rigid body if requested135if (inSettings->mInnerBodyShape != nullptr)136{137BodyCreationSettings settings(inSettings->mInnerBodyShape, GetInnerBodyPosition(), mRotation, EMotionType::Kinematic, inSettings->mInnerBodyLayer);138settings.mAllowSleeping = false; // Disable sleeping so that we will receive sensor callbacks139settings.mUserData = inUserData;140141const Body *inner_body;142BodyInterface &bi = inSystem->GetBodyInterface();143if (inSettings->mInnerBodyIDOverride.IsInvalid())144inner_body = bi.CreateBody(settings);145else146inner_body = bi.CreateBodyWithID(inSettings->mInnerBodyIDOverride, settings);147if (inner_body != nullptr)148{149mInnerBodyID = inner_body->GetID();150bi.AddBody(mInnerBodyID, EActivation::Activate);151}152}153}154155CharacterVirtual::~CharacterVirtual()156{157if (!mInnerBodyID.IsInvalid())158{159mSystem->GetBodyInterface().RemoveBody(mInnerBodyID);160mSystem->GetBodyInterface().DestroyBody(mInnerBodyID);161}162}163164void CharacterVirtual::UpdateInnerBodyTransform()165{166if (!mInnerBodyID.IsInvalid())167mSystem->GetBodyInterface().SetPositionAndRotation(mInnerBodyID, GetInnerBodyPosition(), mRotation, EActivation::DontActivate);168}169170void CharacterVirtual::GetAdjustedBodyVelocity(const Body& inBody, Vec3 &outLinearVelocity, Vec3 &outAngularVelocity) const171{172// Get real velocity of body173if (!inBody.IsStatic())174{175const MotionProperties *mp = inBody.GetMotionPropertiesUnchecked();176outLinearVelocity = mp->GetLinearVelocity();177outAngularVelocity = mp->GetAngularVelocity();178}179else180{181outLinearVelocity = outAngularVelocity = Vec3::sZero();182}183184// Allow application to override185if (mListener != nullptr)186mListener->OnAdjustBodyVelocity(this, inBody, outLinearVelocity, outAngularVelocity);187}188189Vec3 CharacterVirtual::CalculateCharacterGroundVelocity(RVec3Arg inCenterOfMass, Vec3Arg inLinearVelocity, Vec3Arg inAngularVelocity, float inDeltaTime) const190{191// Get angular velocity192float angular_velocity_len_sq = inAngularVelocity.LengthSq();193if (angular_velocity_len_sq < 1.0e-12f)194return inLinearVelocity;195float angular_velocity_len = sqrt(angular_velocity_len_sq);196197// Calculate the rotation that the object will make in the time step198Quat rotation = Quat::sRotation(inAngularVelocity / angular_velocity_len, angular_velocity_len * inDeltaTime);199200// Calculate where the new character position will be201RVec3 new_position = inCenterOfMass + rotation * Vec3(mPosition - inCenterOfMass);202203// Calculate the velocity204return inLinearVelocity + Vec3(new_position - mPosition) / inDeltaTime;205}206207template <class taCollector>208void CharacterVirtual::sFillContactProperties(const CharacterVirtual *inCharacter, Contact &outContact, const Body &inBody, Vec3Arg inUp, RVec3Arg inBaseOffset, const taCollector &inCollector, const CollideShapeResult &inResult)209{210// Get adjusted body velocity211Vec3 linear_velocity, angular_velocity;212inCharacter->GetAdjustedBodyVelocity(inBody, linear_velocity, angular_velocity);213214outContact.mPosition = inBaseOffset + inResult.mContactPointOn2;215outContact.mLinearVelocity = linear_velocity + angular_velocity.Cross(Vec3(outContact.mPosition - inBody.GetCenterOfMassPosition())); // Calculate point velocity216outContact.mContactNormal = -inResult.mPenetrationAxis.NormalizedOr(Vec3::sZero());217outContact.mSurfaceNormal = inCollector.GetContext()->GetWorldSpaceSurfaceNormal(inResult.mSubShapeID2, outContact.mPosition);218if (outContact.mContactNormal.Dot(outContact.mSurfaceNormal) < 0.0f)219outContact.mSurfaceNormal = -outContact.mSurfaceNormal; // Flip surface normal if we're hitting a back face220if (outContact.mContactNormal.Dot(inUp) > outContact.mSurfaceNormal.Dot(inUp))221outContact.mSurfaceNormal = outContact.mContactNormal; // Replace surface normal with contact normal if the contact normal is pointing more upwards222outContact.mDistance = -inResult.mPenetrationDepth;223outContact.mBodyB = inResult.mBodyID2;224outContact.mSubShapeIDB = inResult.mSubShapeID2;225outContact.mMotionTypeB = inBody.GetMotionType();226outContact.mIsSensorB = inBody.IsSensor();227outContact.mUserData = inBody.GetUserData();228outContact.mMaterial = inCollector.GetContext()->GetMaterial(inResult.mSubShapeID2);229}230231void CharacterVirtual::sFillCharacterContactProperties(Contact &outContact, const CharacterVirtual *inOtherCharacter, RVec3Arg inBaseOffset, const CollideShapeResult &inResult)232{233outContact.mPosition = inBaseOffset + inResult.mContactPointOn2;234outContact.mLinearVelocity = inOtherCharacter->GetLinearVelocity();235outContact.mSurfaceNormal = outContact.mContactNormal = -inResult.mPenetrationAxis.NormalizedOr(Vec3::sZero());236outContact.mDistance = -inResult.mPenetrationDepth;237outContact.mCharacterIDB = inOtherCharacter->GetID();238outContact.mCharacterB = inOtherCharacter;239outContact.mSubShapeIDB = inResult.mSubShapeID2;240outContact.mMotionTypeB = EMotionType::Kinematic; // Other character is kinematic, we can't directly move it241outContact.mIsSensorB = false;242outContact.mUserData = inOtherCharacter->GetUserData();243outContact.mMaterial = PhysicsMaterial::sDefault;244}245246void CharacterVirtual::ContactCollector::AddHit(const CollideShapeResult &inResult)247{248// If we exceed our contact limit, try to clean up near-duplicate contacts249if (mContacts.size() == mMaxHits)250{251// Flag that we hit this code path252mMaxHitsExceeded = true;253254// Check if we can do reduction255if (mHitReductionCosMaxAngle > -1.0f)256{257// Loop all contacts and find similar contacts258for (int i = (int)mContacts.size() - 1; i >= 0; --i)259{260Contact &contact_i = mContacts[i];261for (int j = i - 1; j >= 0; --j)262{263Contact &contact_j = mContacts[j];264if (contact_i.IsSameBody(contact_j)265&& contact_i.mContactNormal.Dot(contact_j.mContactNormal) > mHitReductionCosMaxAngle) // Very similar contact normals266{267// Remove the contact with the biggest distance268bool i_is_last = i == (int)mContacts.size() - 1;269if (contact_i.mDistance > contact_j.mDistance)270{271// Remove i272if (!i_is_last)273contact_i = mContacts.back();274mContacts.pop_back();275276// Break out of the loop, i is now an element that we already processed277break;278}279else280{281// Remove j282contact_j = mContacts.back();283mContacts.pop_back();284285// If i was the last element, we just moved it into position j. Break out of the loop, we'll see it again later.286if (i_is_last)287break;288}289}290}291}292}293294if (mContacts.size() == mMaxHits)295{296// There are still too many hits, give up!297ForceEarlyOut();298return;299}300}301302if (inResult.mBodyID2.IsInvalid())303{304// Assuming this is a hit against another character305JPH_ASSERT(mOtherCharacter != nullptr);306307// Create contact with other character308mContacts.emplace_back();309Contact &contact = mContacts.back();310sFillCharacterContactProperties(contact, mOtherCharacter, mBaseOffset, inResult);311contact.mFraction = 0.0f;312}313else314{315// Create contact with other body316BodyLockRead lock(mSystem->GetBodyLockInterface(), inResult.mBodyID2);317if (lock.SucceededAndIsInBroadPhase())318{319mContacts.emplace_back();320Contact &contact = mContacts.back();321sFillContactProperties(mCharacter, contact, lock.GetBody(), mUp, mBaseOffset, *this, inResult);322contact.mFraction = 0.0f;323}324}325}326327void CharacterVirtual::ContactCastCollector::AddHit(const ShapeCastResult &inResult)328{329if (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 hit330&& inResult.mFraction > 0.0f // Ignore collisions at fraction = 0331&& inResult.mPenetrationAxis.Dot(mDisplacement) > 0.0f) // Ignore penetrations that we're moving away from332{333// Test if this contact should be ignored334for (const ContactKey &c : mIgnoredContacts)335if (c.mBodyB == inResult.mBodyID2 && c.mSubShapeIDB == inResult.mSubShapeID2)336return;337338Contact contact;339340if (inResult.mBodyID2.IsInvalid())341{342// Assuming this is a hit against another character343JPH_ASSERT(mOtherCharacter != nullptr);344345// Create contact with other character346sFillCharacterContactProperties(contact, mOtherCharacter, mBaseOffset, inResult);347}348else349{350// Lock body only while we fetch contact properties351BodyLockRead lock(mSystem->GetBodyLockInterface(), inResult.mBodyID2);352if (!lock.SucceededAndIsInBroadPhase())353return;354355// Sweeps don't result in OnContactAdded callbacks so we can ignore sensors here356const Body &body = lock.GetBody();357if (body.IsSensor())358return;359360// Convert the hit result into a contact361sFillContactProperties(mCharacter, contact, body, mUp, mBaseOffset, *this, inResult);362}363364contact.mFraction = inResult.mFraction;365366// Check if the contact that will make us penetrate more than the allowed tolerance367if (contact.mDistance + contact.mContactNormal.Dot(mDisplacement) < -mCharacter->mCollisionTolerance368&& mCharacter->ValidateContact(contact))369{370mContact = contact;371UpdateEarlyOutFraction(contact.mFraction);372}373}374}375376void 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) const377{378// Query shape transform379RMat44 transform = GetCenterOfMassTransform(inPosition, inRotation, inShape);380381// Settings for collide shape382CollideShapeSettings settings;383settings.mBackFaceMode = mBackFaceMode;384settings.mActiveEdgeMovementDirection = inMovementDirection;385settings.mMaxSeparationDistance = mCharacterPadding + inMaxSeparationDistance;386settings.mActiveEdgeMode = EActiveEdgeMode::CollideOnlyWithActive;387388// Body filter389IgnoreSingleBodyFilterChained body_filter(mInnerBodyID, inBodyFilter);390391// Select the right function392auto collide_shape_function = mEnhancedInternalEdgeRemoval? &NarrowPhaseQuery::CollideShapeWithInternalEdgeRemoval : &NarrowPhaseQuery::CollideShape;393394// Collide shape395(mSystem->GetNarrowPhaseQuery().*collide_shape_function)(inShape, Vec3::sOne(), transform, settings, inBaseOffset, ioCollector, inBroadPhaseLayerFilter, inObjectLayerFilter, body_filter, inShapeFilter);396397// Also collide with other characters398if (mCharacterVsCharacterCollision != nullptr)399{400ioCollector.SetContext(nullptr); // We're no longer colliding with a transformed shape, reset401mCharacterVsCharacterCollision->CollideCharacter(this, transform, settings, inBaseOffset, ioCollector);402}403}404405void CharacterVirtual::GetContactsAtPosition(RVec3Arg inPosition, Vec3Arg inMovementDirection, const Shape *inShape, TempContactList &outContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) const406{407// Remove previous results408outContacts.clear();409410// Body filter411IgnoreSingleBodyFilterChained body_filter(mInnerBodyID, inBodyFilter);412413// Collide shape414ContactCollector collector(mSystem, this, mMaxNumHits, mHitReductionCosMaxAngle, mUp, mPosition, outContacts);415CheckCollision(inPosition, mRotation, inMovementDirection, mPredictiveContactDistance, inShape, mPosition, collector, inBroadPhaseLayerFilter, inObjectLayerFilter, body_filter, inShapeFilter);416417// 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.418// Therefore we need to sort the contacts to preserve determinism. Note that currently this will fail if we exceed mMaxNumHits hits.419QuickSort(outContacts.begin(), outContacts.end(), ContactOrderingPredicate());420421// Flag if we exceeded the max number of hits422mMaxHitsExceeded = collector.mMaxHitsExceeded;423424// Reduce distance to contact by padding to ensure we stay away from the object by a little margin425// (this will make collision detection cheaper - especially for sweep tests as they won't hit the surface if we're properly sliding)426for (Contact &c : outContacts)427{428c.mDistance -= mCharacterPadding;429430if (c.mCharacterB != nullptr)431c.mDistance -= c.mCharacterB->mCharacterPadding;432}433}434435void CharacterVirtual::RemoveConflictingContacts(TempContactList &ioContacts, IgnoredContactList &outIgnoredContacts) const436{437// 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)438// We do need to account for padding (see GetContactsAtPosition) that is removed from the contact distances, to compensate we add it to the cMinRequiredPenetration439const float cMinRequiredPenetration = 1.25f * mCharacterPadding;440441// Discard conflicting penetrating contacts442for (size_t c1 = 0; c1 < ioContacts.size(); c1++)443{444Contact &contact1 = ioContacts[c1];445if (contact1.mDistance <= -cMinRequiredPenetration) // Only for penetrations446for (size_t c2 = c1 + 1; c2 < ioContacts.size(); c2++)447{448Contact &contact2 = ioContacts[c2];449if (contact1.IsSameBody(contact2)450&& contact2.mDistance <= -cMinRequiredPenetration // Only for penetrations451&& contact1.mContactNormal.Dot(contact2.mContactNormal) < 0.0f) // Only opposing normals452{453// Discard contacts with the least amount of penetration454if (contact1.mDistance < contact2.mDistance)455{456// Discard the 2nd contact457outIgnoredContacts.emplace_back(contact2);458ioContacts.erase(ioContacts.begin() + c2);459c2--;460}461else462{463// Discard the first contact464outIgnoredContacts.emplace_back(contact1);465ioContacts.erase(ioContacts.begin() + c1);466c1--;467break;468}469}470}471}472}473474bool CharacterVirtual::ValidateContact(const Contact &inContact) const475{476if (mListener == nullptr)477return true;478479if (inContact.mCharacterB != nullptr)480return mListener->OnCharacterContactValidate(this, inContact.mCharacterB, inContact.mSubShapeIDB);481else482return mListener->OnContactValidate(this, inContact.mBodyB, inContact.mSubShapeIDB);483}484485void CharacterVirtual::ContactAdded(const Contact &inContact, CharacterContactSettings &ioSettings)486{487if (mListener != nullptr)488{489// Check if we already know this contact490ListenerContacts::iterator it = mListenerContacts.find(inContact);491if (it != mListenerContacts.end())492{493// Max 1 contact persisted callback494if (++it->second.mCount == 1)495{496if (inContact.mCharacterB != nullptr)497mListener->OnCharacterContactPersisted(this, inContact.mCharacterB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);498else499mListener->OnContactPersisted(this, inContact.mBodyB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);500it->second.mSettings = ioSettings;501}502else503{504// Reuse the settings from the last call505ioSettings = it->second.mSettings;506}507}508else509{510// New contact511if (inContact.mCharacterB != nullptr)512mListener->OnCharacterContactAdded(this, inContact.mCharacterB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);513else514mListener->OnContactAdded(this, inContact.mBodyB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);515mListenerContacts.insert(ListenerContacts::value_type(inContact, ioSettings));516}517}518}519520template <class T>521inline static bool sCorrectFractionForCharacterPadding(const Shape *inShape, Mat44Arg inStart, Vec3Arg inDisplacement, Vec3Arg inScale, const T &inPolygon, float &ioFraction)522{523if (inShape->GetType() == EShapeType::Convex)524{525// Get the support function for the shape we're casting526const ConvexShape *convex_shape = static_cast<const ConvexShape *>(inShape);527ConvexShape::SupportBuffer buffer;528const ConvexShape::Support *support = convex_shape->GetSupportFunction(ConvexShape::ESupportMode::IncludeConvexRadius, buffer, inScale);529530// Cast the shape against the polygon531GJKClosestPoint gjk;532return gjk.CastShape(inStart, inDisplacement, cDefaultCollisionTolerance, *support, inPolygon, ioFraction);533}534else if (inShape->GetSubType() == EShapeSubType::RotatedTranslated)535{536const RotatedTranslatedShape *rt_shape = static_cast<const RotatedTranslatedShape *>(inShape);537return sCorrectFractionForCharacterPadding(rt_shape->GetInnerShape(), inStart * Mat44::sRotation(rt_shape->GetRotation()), inDisplacement, rt_shape->TransformScale(inScale), inPolygon, ioFraction);538}539else if (inShape->GetSubType() == EShapeSubType::Scaled)540{541const ScaledShape *scaled_shape = static_cast<const ScaledShape *>(inShape);542return sCorrectFractionForCharacterPadding(scaled_shape->GetInnerShape(), inStart, inDisplacement, inScale * scaled_shape->GetScale(), inPolygon, ioFraction);543}544else545{546JPH_ASSERT(false, "Not supported yet!");547return false;548}549}550551bool CharacterVirtual::GetFirstContactForSweep(RVec3Arg inPosition, Vec3Arg inDisplacement, Contact &outContact, const IgnoredContactList &inIgnoredContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) const552{553// Too small distance -> skip checking554float displacement_len_sq = inDisplacement.LengthSq();555if (displacement_len_sq < 1.0e-8f)556return false;557558// Calculate start transform559RMat44 start = GetCenterOfMassTransform(inPosition, mRotation, mShape);560561// Settings for the cast562ShapeCastSettings settings;563settings.mBackFaceModeTriangles = mBackFaceMode;564settings.mBackFaceModeConvex = EBackFaceMode::IgnoreBackFaces;565settings.mActiveEdgeMode = EActiveEdgeMode::CollideOnlyWithActive;566settings.mUseShrunkenShapeAndConvexRadius = true;567settings.mReturnDeepestPoint = false;568569// Calculate how much extra fraction we need to add to the cast to account for the character padding570float character_padding_fraction = mCharacterPadding / sqrt(displacement_len_sq);571572// Body filter573IgnoreSingleBodyFilterChained body_filter(mInnerBodyID, inBodyFilter);574575// Cast shape576Contact contact;577contact.mFraction = 1.0f + character_padding_fraction;578RVec3 base_offset = start.GetTranslation();579ContactCastCollector collector(mSystem, this, inDisplacement, mUp, inIgnoredContacts, base_offset, contact);580collector.ResetEarlyOutFraction(contact.mFraction);581RShapeCast shape_cast(mShape, Vec3::sOne(), start, inDisplacement);582mSystem->GetNarrowPhaseQuery().CastShape(shape_cast, settings, base_offset, collector, inBroadPhaseLayerFilter, inObjectLayerFilter, body_filter, inShapeFilter);583584// Also collide with other characters585if (mCharacterVsCharacterCollision != nullptr)586{587collector.SetContext(nullptr); // We're no longer colliding with a transformed shape, reset588mCharacterVsCharacterCollision->CastCharacter(this, start, inDisplacement, settings, base_offset, collector);589}590591if (contact.mBodyB.IsInvalid() && contact.mCharacterIDB.IsInvalid())592return false;593594// Store contact595outContact = contact;596597TransformedShape ts;598float character_padding = mCharacterPadding;599if (outContact.mCharacterB != nullptr)600{601// Create a transformed shape for the character602RMat44 com = outContact.mCharacterB->GetCenterOfMassTransform();603ts = TransformedShape(com.GetTranslation(), com.GetQuaternion(), outContact.mCharacterB->GetShape(), BodyID(), SubShapeIDCreator());604605// We need to take the other character's padding into account as well606character_padding += outContact.mCharacterB->mCharacterPadding;607}608else609{610// Create a transformed shape for the body611ts = mSystem->GetBodyInterface().GetTransformedShape(outContact.mBodyB);612}613614// Fetch the face we're colliding with615Shape::SupportingFace face;616ts.GetSupportingFace(outContact.mSubShapeIDB, -outContact.mContactNormal, base_offset, face);617618bool corrected = false;619if (face.size() >= 2)620{621// Inflate the colliding face by the character padding622PolygonConvexSupport polygon(face);623AddConvexRadius add_cvx(polygon, character_padding);624625// Correct fraction to hit this inflated face instead of the inner shape626corrected = sCorrectFractionForCharacterPadding(mShape, start.GetRotation(), inDisplacement, Vec3::sOne(), add_cvx, outContact.mFraction);627}628if (!corrected)629{630// When there's only a single contact point or when we were unable to correct the fraction,631// we can just move the fraction back so that the character and its padding don't hit the contact point anymore632outContact.mFraction = max(0.0f, outContact.mFraction - character_padding_fraction);633}634635// Ensure that we never return a fraction that's bigger than 1 (which could happen due to float precision issues).636outContact.mFraction = min(outContact.mFraction, 1.0f);637638return true;639}640641void CharacterVirtual::DetermineConstraints(TempContactList &inContacts, float inDeltaTime, ConstraintList &outConstraints) const642{643for (Contact &c : inContacts)644{645Vec3 contact_velocity = c.mLinearVelocity;646647// Penetrating contact: Add a contact velocity that pushes the character out at the desired speed648if (c.mDistance < 0.0f)649contact_velocity -= c.mContactNormal * c.mDistance * mPenetrationRecoverySpeed / inDeltaTime;650651// Convert to a constraint652outConstraints.emplace_back();653Constraint &constraint = outConstraints.back();654constraint.mContact = &c;655constraint.mLinearVelocity = contact_velocity;656constraint.mPlane = Plane(c.mContactNormal, c.mDistance);657658// Next check if the angle is too steep and if it is add an additional constraint that holds the character back659if (IsSlopeTooSteep(c.mSurfaceNormal))660{661// Only take planes that point up.662// Note that we use the contact normal to allow for better sliding as the surface normal may be in the opposite direction of movement.663float dot = c.mContactNormal.Dot(mUp);664if (dot > 1.0e-3f) // Add a little slack, if the normal is perfectly horizontal we already have our vertical plane.665{666// Mark the slope constraint as steep667constraint.mIsSteepSlope = true;668669// Make horizontal normal670Vec3 normal = (c.mContactNormal - dot * mUp).Normalized();671672// Create a secondary constraint that blocks horizontal movement673outConstraints.emplace_back();674Constraint &vertical_constraint = outConstraints.back();675vertical_constraint.mContact = &c;676vertical_constraint.mLinearVelocity = contact_velocity.Dot(normal) * normal; // Project the contact velocity on the new normal so that both planes push at an equal rate677vertical_constraint.mPlane = Plane(normal, c.mDistance / normal.Dot(c.mContactNormal)); // Calculate the distance we have to travel horizontally to hit the contact plane678}679}680}681}682683bool CharacterVirtual::HandleContact(Vec3Arg inVelocity, Constraint &ioConstraint, float inDeltaTime)684{685Contact &contact = *ioConstraint.mContact;686687// Validate the contact point688if (!ValidateContact(contact))689return false;690691// We collided692contact.mHadCollision = true;693694// Send contact added event695CharacterContactSettings settings;696ContactAdded(contact, settings);697contact.mCanPushCharacter = settings.mCanPushCharacter;698699// We don't have any further interaction with sensors beyond an OnContactAdded notification700if (contact.mIsSensorB)701return false;702703// If body B cannot receive an impulse, we're done704if (!settings.mCanReceiveImpulses || contact.mMotionTypeB != EMotionType::Dynamic)705return true;706707// Lock the body we're colliding with708BodyLockWrite lock(mSystem->GetBodyLockInterface(), contact.mBodyB);709if (!lock.SucceededAndIsInBroadPhase())710return false; // Body has been removed, we should not collide with it anymore711const Body &body = lock.GetBody();712713// Calculate the velocity that we want to apply at B so that it will start moving at the character's speed at the contact point714constexpr float cDamping = 0.9f;715constexpr float cPenetrationResolution = 0.4f;716Vec3 relative_velocity = inVelocity - contact.mLinearVelocity;717float projected_velocity = relative_velocity.Dot(contact.mContactNormal);718float delta_velocity = -projected_velocity * cDamping - min(contact.mDistance, 0.0f) * cPenetrationResolution / inDeltaTime;719720// Don't apply impulses if we're separating721if (delta_velocity < 0.0f)722return true;723724// Determine mass properties of the body we're colliding with725const MotionProperties *motion_properties = body.GetMotionProperties();726RVec3 center_of_mass = body.GetCenterOfMassPosition();727Mat44 inverse_inertia = body.GetInverseInertia();728float inverse_mass = motion_properties->GetInverseMass();729730// Calculate the inverse of the mass of body B as seen at the contact point in the direction of the contact normal731Vec3 jacobian = Vec3(contact.mPosition - center_of_mass).Cross(contact.mContactNormal);732float inv_effective_mass = inverse_inertia.Multiply3x3(jacobian).Dot(jacobian) + inverse_mass;733734// Impulse P = M dv735float impulse = delta_velocity / inv_effective_mass;736737// Clamp the impulse according to the character strength, character strength is a force in newtons, P = F dt738float max_impulse = mMaxStrength * inDeltaTime;739impulse = min(impulse, max_impulse);740741// Calculate the world space impulse to apply742Vec3 world_impulse = -impulse * contact.mContactNormal;743744// Cancel impulse in down direction (we apply gravity later)745float impulse_dot_up = world_impulse.Dot(mUp);746if (impulse_dot_up < 0.0f)747world_impulse -= impulse_dot_up * mUp;748749// Now apply the impulse (body is already locked so we use the no-lock interface)750mSystem->GetBodyInterfaceNoLock().AddImpulse(contact.mBodyB, world_impulse, contact.mPosition);751return true;752}753754void CharacterVirtual::SolveConstraints(Vec3Arg inVelocity, float inDeltaTime, float inTimeRemaining, ConstraintList &ioConstraints, IgnoredContactList &ioIgnoredContacts, float &outTimeSimulated, Vec3 &outDisplacement, TempAllocator &inAllocator755#ifdef JPH_DEBUG_RENDERER756, bool inDrawConstraints757#endif // JPH_DEBUG_RENDERER758)759{760// If there are no constraints we can immediately move to our target761if (ioConstraints.empty())762{763outDisplacement = inVelocity * inTimeRemaining;764outTimeSimulated = inTimeRemaining;765return;766}767768// Create array that holds the constraints in order of time of impact (sort will happen later)769Array<Constraint *, STLTempAllocator<Constraint *>> sorted_constraints(inAllocator);770sorted_constraints.resize(ioConstraints.size());771for (size_t index = 0; index < sorted_constraints.size(); index++)772sorted_constraints[index] = &ioConstraints[index];773774// This is the velocity we use for the displacement, if we hit something it will be shortened775Vec3 velocity = inVelocity;776777// Keep track of the last velocity that was applied to the character so that we can detect when the velocity reverses778Vec3 last_velocity = inVelocity;779780// Start with no displacement781outDisplacement = Vec3::sZero();782outTimeSimulated = 0.0f;783784// These are the contacts that we hit previously without moving a significant distance785Array<Constraint *, STLTempAllocator<Constraint *>> previous_contacts(inAllocator);786previous_contacts.resize(mMaxConstraintIterations);787int num_previous_contacts = 0;788789// Loop for a max amount of iterations790for (uint iteration = 0; iteration < mMaxConstraintIterations; iteration++)791{792// Calculate time of impact for all constraints793for (Constraint &c : ioConstraints)794{795// Project velocity on plane direction796c.mProjectedVelocity = c.mPlane.GetNormal().Dot(c.mLinearVelocity - velocity);797if (c.mProjectedVelocity < 1.0e-6f)798{799c.mTOI = FLT_MAX;800}801else802{803// Distance to plane804float dist = c.mPlane.SignedDistance(outDisplacement);805806if (dist - c.mProjectedVelocity * inTimeRemaining > -1.0e-4f)807{808// Too little penetration, accept the movement809c.mTOI = FLT_MAX;810}811else812{813// Calculate time of impact814c.mTOI = max(0.0f, dist / c.mProjectedVelocity);815}816}817}818819// Sort constraints on proximity820QuickSort(sorted_constraints.begin(), sorted_constraints.end(), [](const Constraint *inLHS, const Constraint *inRHS) {821// If both constraints hit at t = 0 then order the one that will push the character furthest first822// Note that because we add velocity to penetrating contacts, this will also resolve contacts that penetrate the most823if (inLHS->mTOI <= 0.0f && inRHS->mTOI <= 0.0f)824return inLHS->mProjectedVelocity > inRHS->mProjectedVelocity;825826// Then sort on time of impact827if (inLHS->mTOI != inRHS->mTOI)828return inLHS->mTOI < inRHS->mTOI;829830// As a tie breaker sort static first so it has the most influence831return inLHS->mContact->mMotionTypeB > inRHS->mContact->mMotionTypeB;832});833834// Find the first valid constraint835Constraint *constraint = nullptr;836for (Constraint *c : sorted_constraints)837{838// Take the first contact and see if we can reach it839if (c->mTOI >= inTimeRemaining)840{841// We can reach our goal!842outDisplacement += velocity * inTimeRemaining;843outTimeSimulated += inTimeRemaining;844return;845}846847// Test if this contact was discarded by the contact callback before848if (c->mContact->mWasDiscarded)849continue;850851// Handle the contact852if (!c->mContact->mHadCollision853&& !HandleContact(velocity, *c, inDeltaTime))854{855// Constraint should be ignored, remove it from the list856c->mContact->mWasDiscarded = true;857858// Mark it as ignored for GetFirstContactForSweep859ioIgnoredContacts.emplace_back(*c->mContact);860continue;861}862863// Cancel velocity of constraint if it cannot push the character864if (!c->mContact->mCanPushCharacter)865c->mLinearVelocity = Vec3::sZero();866867// We found the first constraint that we want to collide with868constraint = c;869break;870}871872if (constraint == nullptr)873{874// All constraints were discarded, we can reach our goal!875outDisplacement += velocity * inTimeRemaining;876outTimeSimulated += inTimeRemaining;877return;878}879880// Move to the contact881outDisplacement += velocity * constraint->mTOI;882inTimeRemaining -= constraint->mTOI;883outTimeSimulated += constraint->mTOI;884885// If there's not enough time left to be simulated, bail886if (inTimeRemaining < mMinTimeRemaining)887return;888889// If we've moved significantly, clear all previous contacts890if (constraint->mTOI > 1.0e-4f)891num_previous_contacts = 0;892893// Get the normal of the plane we're hitting894Vec3 plane_normal = constraint->mPlane.GetNormal();895896// 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 slope897// (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)898if (constraint->mIsSteepSlope)899{900// We're hitting a steep slope, create a vertical plane that blocks any further movement up the slope (note: not normalized)901Vec3 vertical_plane_normal = plane_normal - plane_normal.Dot(mUp) * mUp;902903// Get the relative velocity between the character and the constraint904Vec3 relative_velocity = velocity - constraint->mLinearVelocity;905906// Remove velocity towards the slope907velocity = velocity - min(0.0f, relative_velocity.Dot(vertical_plane_normal)) * vertical_plane_normal / vertical_plane_normal.LengthSq();908}909910// Get the relative velocity between the character and the constraint911Vec3 relative_velocity = velocity - constraint->mLinearVelocity;912913// Calculate new velocity if we cancel the relative velocity in the normal direction914Vec3 new_velocity = velocity - relative_velocity.Dot(plane_normal) * plane_normal;915916// Find the normal of the previous contact that we will violate the most if we move in this new direction917float highest_penetration = 0.0f;918Constraint *other_constraint = nullptr;919for (Constraint **c = previous_contacts.data(); c < previous_contacts.data() + num_previous_contacts; ++c)920if (*c != constraint)921{922// Calculate how much we will penetrate if we move in this direction923Vec3 other_normal = (*c)->mPlane.GetNormal();924float penetration = ((*c)->mLinearVelocity - new_velocity).Dot(other_normal);925if (penetration > highest_penetration)926{927// 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.928float dot = other_normal.Dot(plane_normal);929if (dot < 0.984f && dot > -0.984f)930{931highest_penetration = penetration;932other_constraint = *c;933}934}935}936937// Check if we found a 2nd constraint938if (other_constraint != nullptr)939{940// Calculate the sliding direction and project the new velocity onto that sliding direction941Vec3 other_normal = other_constraint->mPlane.GetNormal();942Vec3 slide_dir = plane_normal.Cross(other_normal).Normalized();943Vec3 velocity_in_slide_dir = new_velocity.Dot(slide_dir) * slide_dir;944945// 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 planes946constraint->mLinearVelocity -= min(0.0f, constraint->mLinearVelocity.Dot(other_normal)) * other_normal;947948// 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 planes949other_constraint->mLinearVelocity -= min(0.0f, other_constraint->mLinearVelocity.Dot(plane_normal)) * plane_normal;950951// Calculate the velocity of this constraint perpendicular to the slide direction952Vec3 perpendicular_velocity = constraint->mLinearVelocity - constraint->mLinearVelocity.Dot(slide_dir) * slide_dir;953954// Calculate the velocity of the other constraint perpendicular to the slide direction955Vec3 other_perpendicular_velocity = other_constraint->mLinearVelocity - other_constraint->mLinearVelocity.Dot(slide_dir) * slide_dir;956957// Add all components together958new_velocity = velocity_in_slide_dir + perpendicular_velocity + other_perpendicular_velocity;959}960961// Allow application to modify calculated velocity962if (mListener != nullptr)963{964if (constraint->mContact->mCharacterB != nullptr)965mListener->OnCharacterContactSolve(this, constraint->mContact->mCharacterB, constraint->mContact->mSubShapeIDB, constraint->mContact->mPosition, constraint->mContact->mContactNormal, constraint->mContact->mLinearVelocity, constraint->mContact->mMaterial, velocity, new_velocity);966else967mListener->OnContactSolve(this, constraint->mContact->mBodyB, constraint->mContact->mSubShapeIDB, constraint->mContact->mPosition, constraint->mContact->mContactNormal, constraint->mContact->mLinearVelocity, constraint->mContact->mMaterial, velocity, new_velocity);968}969970#ifdef JPH_DEBUG_RENDERER971if (inDrawConstraints)972{973// Calculate where to draw974RVec3 offset = mPosition + Vec3(0, 0, 2.5f * (iteration + 1));975976// Draw constraint plane977DebugRenderer::sInstance->DrawPlane(offset, constraint->mPlane.GetNormal(), Color::sCyan, 1.0f);978979// Draw 2nd constraint plane980if (other_constraint != nullptr)981DebugRenderer::sInstance->DrawPlane(offset, other_constraint->mPlane.GetNormal(), Color::sBlue, 1.0f);982983// Draw starting velocity984DebugRenderer::sInstance->DrawArrow(offset, offset + velocity, Color::sGreen, 0.05f);985986// Draw resulting velocity987DebugRenderer::sInstance->DrawArrow(offset, offset + new_velocity, Color::sRed, 0.05f);988}989#endif // JPH_DEBUG_RENDERER990991// Update the velocity992velocity = new_velocity;993994// Add the contact to the list so that next iteration we can avoid violating it again995previous_contacts[num_previous_contacts] = constraint;996num_previous_contacts++;997998// Check early out999if (constraint->mProjectedVelocity < 1.0e-8f // Constraint should not be pushing, otherwise there may be other constraints that are pushing us1000&& velocity.LengthSq() < 1.0e-8f) // There's not enough velocity left1001return;10021003// If the constraint has velocity we accept the new velocity, otherwise check that we didn't reverse velocity1004if (!constraint->mLinearVelocity.IsNearZero(1.0e-8f))1005last_velocity = constraint->mLinearVelocity;1006else if (velocity.Dot(last_velocity) < 0.0f)1007return;1008}1009}10101011void CharacterVirtual::UpdateSupportingContact(bool inSkipContactVelocityCheck, TempAllocator &inAllocator)1012{1013// Flag contacts as having a collision if they're close enough but ignore contacts we're moving away from.1014// Note that if we did MoveShape before we want to preserve any contacts that it marked as colliding1015for (Contact &c : mActiveContacts)1016if (!c.mWasDiscarded1017&& !c.mHadCollision1018&& c.mDistance < mCollisionTolerance1019&& (inSkipContactVelocityCheck || c.mSurfaceNormal.Dot(mLinearVelocity - c.mLinearVelocity) <= 1.0e-4f))1020{1021if (ValidateContact(c))1022{1023CharacterContactSettings dummy;1024ContactAdded(c, dummy);1025c.mHadCollision = true;1026}1027else1028c.mWasDiscarded = true;1029}10301031// Calculate transform that takes us to character local space1032RMat44 inv_transform = RMat44::sInverseRotationTranslation(mRotation, mPosition);10331034// Determine if we're supported or not1035int num_supported = 0;1036int num_sliding = 0;1037int num_avg_normal = 0;1038Vec3 avg_normal = Vec3::sZero();1039Vec3 avg_velocity = Vec3::sZero();1040const Contact *supporting_contact = nullptr;1041float max_cos_angle = -FLT_MAX;1042const Contact *deepest_contact = nullptr;1043float smallest_distance = FLT_MAX;1044for (const Contact &c : mActiveContacts)1045if (c.mHadCollision && !c.mWasDiscarded)1046{1047// Calculate the angle between the plane normal and the up direction1048float cos_angle = c.mSurfaceNormal.Dot(mUp);10491050// Find the deepest contact1051if (c.mDistance < smallest_distance)1052{1053deepest_contact = &c;1054smallest_distance = c.mDistance;1055}10561057// If this contact is in front of our plane, we cannot be supported by it1058if (mSupportingVolume.SignedDistance(Vec3(inv_transform * c.mPosition)) > 0.0f)1059continue;10601061// Find the contact with the normal that is pointing most upwards and store it1062if (max_cos_angle < cos_angle)1063{1064supporting_contact = &c;1065max_cos_angle = cos_angle;1066}10671068// Check if this is a sliding or supported contact1069bool is_supported = mCosMaxSlopeAngle > cNoMaxSlopeAngle || cos_angle >= mCosMaxSlopeAngle;1070if (is_supported)1071num_supported++;1072else1073num_sliding++;10741075// If the angle between the two is less than 85 degrees we also use it to calculate the average normal1076if (cos_angle >= 0.08f)1077{1078avg_normal += c.mSurfaceNormal;1079num_avg_normal++;10801081// For static or dynamic objects or for contacts that don't support us just take the contact velocity1082if (c.mMotionTypeB != EMotionType::Kinematic || !is_supported)1083avg_velocity += c.mLinearVelocity;1084else1085{1086// For keyframed objects that support us calculate the velocity at our position rather than at the contact position so that we properly follow the object1087BodyLockRead lock(mSystem->GetBodyLockInterface(), c.mBodyB);1088if (lock.SucceededAndIsInBroadPhase())1089{1090const Body &body = lock.GetBody();10911092// Get adjusted body velocity1093Vec3 linear_velocity, angular_velocity;1094GetAdjustedBodyVelocity(body, linear_velocity, angular_velocity);10951096// Calculate the ground velocity1097avg_velocity += CalculateCharacterGroundVelocity(body.GetCenterOfMassPosition(), linear_velocity, angular_velocity, mLastDeltaTime);1098}1099else1100{1101// Fall back to contact velocity1102avg_velocity += c.mLinearVelocity;1103}1104}1105}1106}11071108// Take either the most supporting contact or the deepest contact1109const Contact *best_contact = supporting_contact != nullptr? supporting_contact : deepest_contact;11101111// Calculate average normal and velocity1112if (num_avg_normal >= 1)1113{1114mGroundNormal = avg_normal.Normalized();1115mGroundVelocity = avg_velocity / float(num_avg_normal);1116}1117else if (best_contact != nullptr)1118{1119mGroundNormal = best_contact->mSurfaceNormal;1120mGroundVelocity = best_contact->mLinearVelocity;1121}1122else1123{1124mGroundNormal = Vec3::sZero();1125mGroundVelocity = Vec3::sZero();1126}11271128// Copy contact properties1129if (best_contact != nullptr)1130{1131mGroundBodyID = best_contact->mBodyB;1132mGroundBodySubShapeID = best_contact->mSubShapeIDB;1133mGroundPosition = best_contact->mPosition;1134mGroundMaterial = best_contact->mMaterial;1135mGroundUserData = best_contact->mUserData;1136}1137else1138{1139mGroundBodyID = BodyID();1140mGroundBodySubShapeID = SubShapeID();1141mGroundPosition = RVec3::sZero();1142mGroundMaterial = PhysicsMaterial::sDefault;1143mGroundUserData = 0;1144}11451146// Determine ground state1147if (num_supported > 0)1148{1149// We made contact with something that supports us1150mGroundState = EGroundState::OnGround;1151}1152else if (num_sliding > 0)1153{1154if ((mLinearVelocity - deepest_contact->mLinearVelocity).Dot(mUp) > 1.0e-4f)1155{1156// We cannot be on ground if we're moving upwards relative to the ground1157mGroundState = EGroundState::OnSteepGround;1158}1159else1160{1161// 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 supported11621163// Convert the contacts into constraints1164TempContactList contacts(mActiveContacts.begin(), mActiveContacts.end(), inAllocator);1165ConstraintList constraints(inAllocator);1166constraints.reserve(contacts.size() * 2);1167DetermineConstraints(contacts, mLastDeltaTime, constraints);11681169// Solve the displacement using these constraints, this is used to check if we didn't move at all because we are supported1170Vec3 displacement;1171float time_simulated;1172IgnoredContactList ignored_contacts(inAllocator);1173ignored_contacts.reserve(contacts.size());1174SolveConstraints(-mUp, 1.0f, 1.0f, constraints, ignored_contacts, time_simulated, displacement, inAllocator);11751176// If we're blocked then we're supported, otherwise we're sliding1177float min_required_displacement_sq = Square(0.6f * mLastDeltaTime);1178if (time_simulated < 0.001f || displacement.LengthSq() < min_required_displacement_sq)1179mGroundState = EGroundState::OnGround;1180else1181mGroundState = EGroundState::OnSteepGround;1182}1183}1184else1185{1186// Not supported by anything1187mGroundState = best_contact != nullptr? EGroundState::NotSupported : EGroundState::InAir;1188}1189}11901191void CharacterVirtual::StoreActiveContacts(const TempContactList &inContacts, TempAllocator &inAllocator)1192{1193StartTrackingContactChanges();11941195mActiveContacts.assign(inContacts.begin(), inContacts.end());11961197UpdateSupportingContact(true, inAllocator);11981199FinishTrackingContactChanges();1200}12011202void CharacterVirtual::MoveShape(RVec3 &ioPosition, Vec3Arg inVelocity, float inDeltaTime, ContactList *outActiveContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator1203#ifdef JPH_DEBUG_RENDERER1204, bool inDrawConstraints1205#endif // JPH_DEBUG_RENDERER1206)1207{1208JPH_DET_LOG("CharacterVirtual::MoveShape: pos: " << ioPosition << " vel: " << inVelocity << " dt: " << inDeltaTime);12091210Vec3 movement_direction = inVelocity.NormalizedOr(Vec3::sZero());12111212float time_remaining = inDeltaTime;1213for (uint iteration = 0; iteration < mMaxCollisionIterations && time_remaining >= mMinTimeRemaining; iteration++)1214{1215JPH_DET_LOG("iter: " << iteration << " time: " << time_remaining);12161217// Determine contacts in the neighborhood1218TempContactList contacts(inAllocator);1219contacts.reserve(mMaxNumHits);1220GetContactsAtPosition(ioPosition, movement_direction, mShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);12211222#ifdef JPH_ENABLE_DETERMINISM_LOG1223for (const Contact &c : contacts)1224JPH_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);1225#endif // JPH_ENABLE_DETERMINISM_LOG12261227// Remove contacts with the same body that have conflicting normals1228IgnoredContactList ignored_contacts(inAllocator);1229ignored_contacts.reserve(contacts.size());1230RemoveConflictingContacts(contacts, ignored_contacts);12311232// Convert contacts into constraints1233ConstraintList constraints(inAllocator);1234constraints.reserve(contacts.size() * 2);1235DetermineConstraints(contacts, inDeltaTime, constraints);12361237#ifdef JPH_DEBUG_RENDERER1238bool draw_constraints = inDrawConstraints && iteration == 0;1239if (draw_constraints)1240{1241for (const Constraint &c : constraints)1242{1243// Draw contact point1244DebugRenderer::sInstance->DrawMarker(c.mContact->mPosition, Color::sYellow, 0.05f);1245Vec3 dist_to_plane = -c.mPlane.GetConstant() * c.mPlane.GetNormal();12461247// Draw arrow towards surface that we're hitting1248DebugRenderer::sInstance->DrawArrow(c.mContact->mPosition, c.mContact->mPosition - dist_to_plane, Color::sYellow, 0.05f);12491250// Draw plane around the player position indicating the space that we can move1251DebugRenderer::sInstance->DrawPlane(mPosition + dist_to_plane, c.mPlane.GetNormal(), Color::sCyan, 1.0f);1252DebugRenderer::sInstance->DrawArrow(mPosition + dist_to_plane, mPosition + dist_to_plane + c.mContact->mSurfaceNormal, Color::sRed, 0.05f);1253}1254}1255#endif // JPH_DEBUG_RENDERER12561257// Solve the displacement using these constraints1258Vec3 displacement;1259float time_simulated;1260SolveConstraints(inVelocity, inDeltaTime, time_remaining, constraints, ignored_contacts, time_simulated, displacement, inAllocator1261#ifdef JPH_DEBUG_RENDERER1262, draw_constraints1263#endif // JPH_DEBUG_RENDERER1264);12651266// Store the contacts now that the colliding ones have been marked1267if (outActiveContacts != nullptr)1268outActiveContacts->assign(contacts.begin(), contacts.end());12691270// Do a sweep to test if the path is really unobstructed1271Contact cast_contact;1272if (GetFirstContactForSweep(ioPosition, displacement, cast_contact, ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))1273{1274displacement *= cast_contact.mFraction;1275time_simulated *= cast_contact.mFraction;1276}12771278// Update the position1279ioPosition += displacement;1280time_remaining -= time_simulated;12811282// If the displacement during this iteration was too small we assume we cannot further progress this update1283if (displacement.LengthSq() < 1.0e-8f)1284break;1285}1286}12871288void CharacterVirtual::SetUserData(uint64 inUserData)1289{1290mUserData = inUserData;12911292if (!mInnerBodyID.IsInvalid())1293mSystem->GetBodyInterface().SetUserData(mInnerBodyID, inUserData);1294}12951296Vec3 CharacterVirtual::CancelVelocityTowardsSteepSlopes(Vec3Arg inDesiredVelocity) const1297{1298// If we're not pushing against a steep slope, return the desired velocity1299// Note: This is important as WalkStairs overrides the ground state to OnGround when its first check fails but the second succeeds1300if (mGroundState == CharacterVirtual::EGroundState::OnGround1301|| mGroundState == CharacterVirtual::EGroundState::InAir)1302return inDesiredVelocity;13031304Vec3 desired_velocity = inDesiredVelocity;1305for (const Contact &c : mActiveContacts)1306if (c.mHadCollision1307&& !c.mWasDiscarded1308&& IsSlopeTooSteep(c.mSurfaceNormal))1309{1310// Note that we use the contact normal to allow for better sliding as the surface normal may be in the opposite direction of movement.1311Vec3 normal = c.mContactNormal;13121313// Remove normal vertical component1314normal -= normal.Dot(mUp) * mUp;13151316// Cancel horizontal movement in opposite direction1317float dot = normal.Dot(desired_velocity);1318if (dot < 0.0f)1319desired_velocity -= (dot * normal) / normal.LengthSq();1320}1321return desired_velocity;1322}13231324void CharacterVirtual::StartTrackingContactChanges()1325{1326// Check if we're starting for the first time1327if (++mTrackingContactChanges > 1)1328return;13291330// No need to track anything if we don't have a listener1331JPH_ASSERT(mListenerContacts.empty());1332if (mListener == nullptr)1333return;13341335// Mark all current contacts as not seen1336mListenerContacts.reserve(ListenerContacts::size_type(mActiveContacts.size()));1337for (const Contact &c : mActiveContacts)1338if (c.mHadCollision)1339mListenerContacts.insert(ListenerContacts::value_type(c, ListenerContactValue()));1340}13411342void CharacterVirtual::FinishTrackingContactChanges()1343{1344// Check if we have to do anything1345int count = --mTrackingContactChanges;1346JPH_ASSERT(count >= 0, "Called FinishTrackingContactChanges more times than StartTrackingContactChanges");1347if (count > 0)1348return;13491350// No need to track anything if we don't have a listener1351if (mListener == nullptr)1352return;13531354// Since we can do multiple operations (e.g. Update followed by WalkStairs)1355// we can end up with contacts that were marked as active to the listener but that are1356// no longer in the active contact list. We go over all contacts and mark them again1357// to ensure that these lists are in sync.1358for (ListenerContacts::value_type &c : mListenerContacts)1359c.second.mCount = 0;1360for (const Contact &c : mActiveContacts)1361if (c.mHadCollision)1362{1363ListenerContacts::iterator it = mListenerContacts.find(c);1364JPH_ASSERT(it != mListenerContacts.end());1365it->second.mCount = 1;1366}13671368// Call contact removal callbacks1369for (ListenerContacts::iterator it = mListenerContacts.begin(); it != mListenerContacts.end(); ++it)1370if (it->second.mCount == 0)1371{1372const ContactKey &c = it->first;1373if (!c.mCharacterIDB.IsInvalid())1374mListener->OnCharacterContactRemoved(this, c.mCharacterIDB, c.mSubShapeIDB);1375else1376mListener->OnContactRemoved(this, c.mBodyB, c.mSubShapeIDB);1377}1378mListenerContacts.ClearAndKeepMemory();1379}13801381void CharacterVirtual::Update(float inDeltaTime, Vec3Arg inGravity, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)1382{1383// If there's no delta time, we don't need to do anything1384if (inDeltaTime <= 0.0f)1385return;13861387StartTrackingContactChanges();1388JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });13891390// Remember delta time for checking if we're supported by the ground1391mLastDeltaTime = inDeltaTime;13921393// Slide the shape through the world1394MoveShape(mPosition, mLinearVelocity, inDeltaTime, &mActiveContacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator1395#ifdef JPH_DEBUG_RENDERER1396, sDrawConstraints1397#endif // JPH_DEBUG_RENDERER1398);13991400// Determine the object that we're standing on1401UpdateSupportingContact(false, inAllocator);14021403// Ensure that the rigid body ends up at the new position1404UpdateInnerBodyTransform();14051406// If we're on the ground1407if (!mGroundBodyID.IsInvalid() && mMass > 0.0f)1408{1409// Add the impulse to the ground due to gravity: P = F dt = M g dt1410float normal_dot_gravity = mGroundNormal.Dot(inGravity);1411if (normal_dot_gravity < 0.0f)1412{1413Vec3 world_impulse = -(mMass * normal_dot_gravity / inGravity.Length() * inDeltaTime) * inGravity;1414mSystem->GetBodyInterface().AddImpulse(mGroundBodyID, world_impulse, mGroundPosition);1415}1416}1417}14181419void CharacterVirtual::RefreshContacts(const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)1420{1421// Determine the contacts1422TempContactList contacts(inAllocator);1423contacts.reserve(mMaxNumHits);1424GetContactsAtPosition(mPosition, mLinearVelocity.NormalizedOr(Vec3::sZero()), mShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);14251426StoreActiveContacts(contacts, inAllocator);1427}14281429void CharacterVirtual::UpdateGroundVelocity()1430{1431BodyLockRead lock(mSystem->GetBodyLockInterface(), mGroundBodyID);1432if (lock.SucceededAndIsInBroadPhase())1433{1434const Body &body = lock.GetBody();14351436// Get adjusted body velocity1437Vec3 linear_velocity, angular_velocity;1438GetAdjustedBodyVelocity(body, linear_velocity, angular_velocity);14391440// Calculate the ground velocity1441mGroundVelocity = CalculateCharacterGroundVelocity(body.GetCenterOfMassPosition(), linear_velocity, angular_velocity, mLastDeltaTime);1442}1443}14441445void CharacterVirtual::MoveToContact(RVec3Arg inPosition, const Contact &inContact, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)1446{1447// Set the new position1448SetPosition(inPosition);14491450// Trigger contact added callback1451CharacterContactSettings dummy;1452ContactAdded(inContact, dummy);14531454// Determine the contacts1455TempContactList contacts(inAllocator);1456contacts.reserve(mMaxNumHits + 1); // +1 because we can add one extra below1457GetContactsAtPosition(mPosition, mLinearVelocity.NormalizedOr(Vec3::sZero()), mShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);14581459// Ensure that we mark inContact as colliding1460bool found_contact = false;1461for (Contact &c : contacts)1462if (c.mBodyB == inContact.mBodyB1463&& c.mSubShapeIDB == inContact.mSubShapeIDB)1464{1465c.mHadCollision = true;1466found_contact = true;1467}1468if (!found_contact)1469{1470contacts.push_back(inContact);14711472Contact © = contacts.back();1473copy.mHadCollision = true;1474}14751476StoreActiveContacts(contacts, inAllocator);1477JPH_ASSERT(mGroundState != EGroundState::InAir);14781479// Ensure that the rigid body ends up at the new position1480UpdateInnerBodyTransform();1481}14821483bool CharacterVirtual::SetShape(const Shape *inShape, float inMaxPenetrationDepth, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)1484{1485if (mShape == nullptr || mSystem == nullptr)1486{1487// It hasn't been initialized yet1488mShape = inShape;1489return true;1490}14911492if (inShape != mShape && inShape != nullptr)1493{1494if (inMaxPenetrationDepth < FLT_MAX)1495{1496// Check collision around the new shape1497TempContactList contacts(inAllocator);1498contacts.reserve(mMaxNumHits);1499GetContactsAtPosition(mPosition, mLinearVelocity.NormalizedOr(Vec3::sZero()), inShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);15001501// Test if this results in penetration, if so cancel the transition1502for (const Contact &c : contacts)1503if (c.mDistance < -inMaxPenetrationDepth1504&& !c.mIsSensorB)1505return false;15061507StoreActiveContacts(contacts, inAllocator);1508}15091510// Set new shape1511mShape = inShape;1512}15131514return mShape == inShape;1515}15161517void CharacterVirtual::SetInnerBodyShape(const Shape *inShape)1518{1519mSystem->GetBodyInterface().SetShape(mInnerBodyID, inShape, false, EActivation::DontActivate);1520}15211522bool CharacterVirtual::CanWalkStairs(Vec3Arg inLinearVelocity) const1523{1524// We can only walk stairs if we're supported1525if (!IsSupported())1526return false;15271528// Check if there's enough horizontal velocity to trigger a stair walk1529Vec3 horizontal_velocity = inLinearVelocity - inLinearVelocity.Dot(mUp) * mUp;1530if (horizontal_velocity.IsNearZero(1.0e-6f))1531return false;15321533// Check contacts for steep slopes1534for (const Contact &c : mActiveContacts)1535if (c.mHadCollision1536&& !c.mWasDiscarded1537&& c.mSurfaceNormal.Dot(horizontal_velocity - c.mLinearVelocity) < 0.0f // Pushing into the contact1538&& IsSlopeTooSteep(c.mSurfaceNormal)) // Slope too steep1539return true;15401541return false;1542}15431544bool 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)1545{1546StartTrackingContactChanges();1547JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });15481549// Move up1550Vec3 up = inStepUp;1551Contact contact;1552IgnoredContactList dummy_ignored_contacts(inAllocator);1553if (GetFirstContactForSweep(mPosition, up, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))1554{1555if (contact.mFraction < 1.0e-6f)1556return false; // No movement, cancel15571558// Limit up movement to the first contact point1559up *= contact.mFraction;1560}1561RVec3 up_position = mPosition + up;15621563#ifdef JPH_DEBUG_RENDERER1564// Draw sweep up1565if (sDrawWalkStairs)1566DebugRenderer::sInstance->DrawArrow(mPosition, up_position, Color::sWhite, 0.01f);1567#endif // JPH_DEBUG_RENDERER15681569// Collect normals of steep slopes that we would like to walk stairs on.1570// We need to do this before calling MoveShape because it will update mActiveContacts.1571Vec3 character_velocity = inStepForward / inDeltaTime;1572Vec3 horizontal_velocity = character_velocity - character_velocity.Dot(mUp) * mUp;1573Array<Vec3, STLTempAllocator<Vec3>> steep_slope_normals(inAllocator);1574steep_slope_normals.reserve(mActiveContacts.size());1575for (const Contact &c : mActiveContacts)1576if (c.mHadCollision1577&& !c.mWasDiscarded1578&& c.mSurfaceNormal.Dot(horizontal_velocity - c.mLinearVelocity) < 0.0f // Pushing into the contact1579&& IsSlopeTooSteep(c.mSurfaceNormal)) // Slope too steep1580steep_slope_normals.push_back(c.mSurfaceNormal);1581if (steep_slope_normals.empty())1582return false; // No steep slopes, cancel15831584// Horizontal movement1585RVec3 new_position = up_position;1586MoveShape(new_position, character_velocity, inDeltaTime, nullptr, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);1587Vec3 horizontal_movement = Vec3(new_position - up_position);1588float horizontal_movement_sq = horizontal_movement.LengthSq();1589if (horizontal_movement_sq < 1.0e-8f)1590return false; // No movement, cancel15911592// Check if we made any progress towards any of the steep slopes, if not we just slid along the slope1593// so we need to cancel the stair walk or else we will move faster than we should as we've done1594// normal movement first and then stair walk.1595bool made_progress = false;1596float max_dot = -0.05f * inStepForward.Length();1597for (const Vec3 &normal : steep_slope_normals)1598if (normal.Dot(horizontal_movement) < max_dot)1599{1600// We moved more than 5% of the forward step against a steep slope, accept this as progress1601made_progress = true;1602break;1603}1604if (!made_progress)1605return false;16061607#ifdef JPH_DEBUG_RENDERER1608// Draw horizontal sweep1609if (sDrawWalkStairs)1610DebugRenderer::sInstance->DrawArrow(up_position, new_position, Color::sWhite, 0.01f);1611#endif // JPH_DEBUG_RENDERER16121613// Move down towards the floor.1614// Note that we travel the same amount down as we traveled up with the specified extra1615Vec3 down = -up + inStepDownExtra;1616if (!GetFirstContactForSweep(new_position, down, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))1617return false; // No floor found, we're in mid air, cancel stair walk16181619#ifdef JPH_DEBUG_RENDERER1620// Draw sweep down1621if (sDrawWalkStairs)1622{1623RVec3 debug_pos = new_position + contact.mFraction * down;1624DebugRenderer::sInstance->DrawArrow(new_position, debug_pos, Color::sWhite, 0.01f);1625DebugRenderer::sInstance->DrawArrow(contact.mPosition, contact.mPosition + contact.mSurfaceNormal, Color::sWhite, 0.01f);1626mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(debug_pos, mRotation, mShape), Vec3::sOne(), Color::sWhite, false, true);1627}1628#endif // JPH_DEBUG_RENDERER16291630// Test for floor that will support the character1631if (IsSlopeTooSteep(contact.mSurfaceNormal))1632{1633// If no test position was provided, we cancel the stair walk1634if (inStepForwardTest.IsNearZero())1635return false;16361637// Delta time may be very small, so it may be that we hit the edge of a step and the normal is too horizontal.1638// In order to judge if the floor is flat further along the sweep, we test again for a floor at inStepForwardTest1639// and check if the normal is valid there.1640RVec3 test_position = up_position;1641MoveShape(test_position, inStepForwardTest / inDeltaTime, inDeltaTime, nullptr, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);1642float test_horizontal_movement_sq = Vec3(test_position - up_position).LengthSq();1643if (test_horizontal_movement_sq <= horizontal_movement_sq + 1.0e-8f)1644return false; // We didn't move any further than in the previous test16451646#ifdef JPH_DEBUG_RENDERER1647// Draw 2nd sweep horizontal1648if (sDrawWalkStairs)1649DebugRenderer::sInstance->DrawArrow(up_position, test_position, Color::sCyan, 0.01f);1650#endif // JPH_DEBUG_RENDERER16511652// Then sweep down1653Contact test_contact;1654if (!GetFirstContactForSweep(test_position, down, test_contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))1655return false;16561657#ifdef JPH_DEBUG_RENDERER1658// Draw 2nd sweep down1659if (sDrawWalkStairs)1660{1661RVec3 debug_pos = test_position + test_contact.mFraction * down;1662DebugRenderer::sInstance->DrawArrow(test_position, debug_pos, Color::sCyan, 0.01f);1663DebugRenderer::sInstance->DrawArrow(test_contact.mPosition, test_contact.mPosition + test_contact.mSurfaceNormal, Color::sCyan, 0.01f);1664mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(debug_pos, mRotation, mShape), Vec3::sOne(), Color::sCyan, false, true);1665}1666#endif // JPH_DEBUG_RENDERER16671668if (IsSlopeTooSteep(test_contact.mSurfaceNormal))1669return false;1670}16711672// Calculate new down position1673down *= contact.mFraction;1674new_position += down;16751676// Move the character to the new location1677MoveToContact(new_position, contact, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);16781679// 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 steep1680mGroundState = EGroundState::OnGround;16811682return true;1683}16841685bool CharacterVirtual::StickToFloor(Vec3Arg inStepDown, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)1686{1687StartTrackingContactChanges();1688JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });16891690// Try to find the floor1691Contact contact;1692IgnoredContactList dummy_ignored_contacts(inAllocator);1693if (!GetFirstContactForSweep(mPosition, inStepDown, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))1694return false; // If no floor found, don't update our position16951696// Calculate new position1697RVec3 new_position = mPosition + contact.mFraction * inStepDown;16981699#ifdef JPH_DEBUG_RENDERER1700// Draw sweep down1701if (sDrawStickToFloor)1702{1703DebugRenderer::sInstance->DrawArrow(mPosition, new_position, Color::sOrange, 0.01f);1704mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(new_position, mRotation, mShape), Vec3::sOne(), Color::sOrange, false, true);1705}1706#endif // JPH_DEBUG_RENDERER17071708// Move the character to the new location1709MoveToContact(new_position, contact, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);1710return true;1711}17121713void CharacterVirtual::ExtendedUpdate(float inDeltaTime, Vec3Arg inGravity, const ExtendedUpdateSettings &inSettings, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)1714{1715StartTrackingContactChanges();1716JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });17171718// Update the velocity1719Vec3 desired_velocity = mLinearVelocity;1720mLinearVelocity = CancelVelocityTowardsSteepSlopes(desired_velocity);17211722// Remember old position1723RVec3 old_position = mPosition;17241725// Track if on ground before the update1726bool ground_to_air = IsSupported();17271728// Update the character position (instant, do not have to wait for physics update)1729Update(inDeltaTime, inGravity, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);17301731// ... and that we got into air after1732if (IsSupported())1733ground_to_air = false;17341735// If stick to floor enabled and we're going from supported to not supported1736if (ground_to_air && !inSettings.mStickToFloorStepDown.IsNearZero())1737{1738// If we're not moving up, stick to the floor1739float velocity = Vec3(mPosition - old_position).Dot(mUp) / inDeltaTime;1740if (velocity <= 1.0e-6f)1741StickToFloor(inSettings.mStickToFloorStepDown, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);1742}17431744// If walk stairs enabled1745if (!inSettings.mWalkStairsStepUp.IsNearZero())1746{1747// Calculate how much we wanted to move horizontally1748Vec3 desired_horizontal_step = desired_velocity * inDeltaTime;1749desired_horizontal_step -= desired_horizontal_step.Dot(mUp) * mUp;1750float desired_horizontal_step_len = desired_horizontal_step.Length();1751if (desired_horizontal_step_len > 0.0f)1752{1753// Calculate how much we moved horizontally1754Vec3 achieved_horizontal_step = Vec3(mPosition - old_position);1755achieved_horizontal_step -= achieved_horizontal_step.Dot(mUp) * mUp;17561757// Only count movement in the direction of the desired movement1758// (otherwise we find it ok if we're sliding downhill while we're trying to climb uphill)1759Vec3 step_forward_normalized = desired_horizontal_step / desired_horizontal_step_len;1760achieved_horizontal_step = max(0.0f, achieved_horizontal_step.Dot(step_forward_normalized)) * step_forward_normalized;1761float achieved_horizontal_step_len = achieved_horizontal_step.Length();17621763// If we didn't move as far as we wanted and we're against a slope that's too steep1764if (achieved_horizontal_step_len + 1.0e-4f < desired_horizontal_step_len1765&& CanWalkStairs(desired_velocity))1766{1767// Calculate how much we should step forward1768// Note that we clamp the step forward to a minimum distance. This is done because at very high frame rates the delta time1769// may be very small, causing a very small step forward. If the step becomes small enough, we may not move far enough1770// horizontally to actually end up at the top of the step.1771Vec3 step_forward = step_forward_normalized * max(inSettings.mWalkStairsMinStepForward, desired_horizontal_step_len - achieved_horizontal_step_len);17721773// Calculate how far to scan ahead for a floor. This is only used in case the floor normal at step_forward is too steep.1774// In that case an additional check will be performed at this distance to check if that normal is not too steep.1775// Start with the ground normal in the horizontal plane and normalizing it1776Vec3 step_forward_test = -mGroundNormal;1777step_forward_test -= step_forward_test.Dot(mUp) * mUp;1778step_forward_test = step_forward_test.NormalizedOr(step_forward_normalized);17791780// 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 normal1781// to do our forward test1782if (step_forward_test.Dot(step_forward_normalized) < inSettings.mWalkStairsCosAngleForwardContact)1783step_forward_test = step_forward_normalized;17841785// Calculate the correct magnitude for the test vector1786step_forward_test *= inSettings.mWalkStairsStepForwardTest;17871788WalkStairs(inDeltaTime, inSettings.mWalkStairsStepUp, step_forward, step_forward_test, inSettings.mWalkStairsStepDownExtra, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);1789}1790}1791}1792}17931794void CharacterVirtual::ContactKey::SaveState(StateRecorder &inStream) const1795{1796inStream.Write(mBodyB);1797inStream.Write(mCharacterIDB);1798inStream.Write(mSubShapeIDB);1799}18001801void CharacterVirtual::ContactKey::RestoreState(StateRecorder &inStream)1802{1803inStream.Read(mBodyB);1804inStream.Read(mCharacterIDB);1805inStream.Read(mSubShapeIDB);1806}18071808void CharacterVirtual::Contact::SaveState(StateRecorder &inStream) const1809{1810ContactKey::SaveState(inStream);18111812inStream.Write(mPosition);1813inStream.Write(mLinearVelocity);1814inStream.Write(mContactNormal);1815inStream.Write(mSurfaceNormal);1816inStream.Write(mDistance);1817inStream.Write(mFraction);1818inStream.Write(mMotionTypeB);1819inStream.Write(mIsSensorB);1820inStream.Write(mHadCollision);1821inStream.Write(mWasDiscarded);1822inStream.Write(mCanPushCharacter);1823// Cannot store pointers to character B, user data and material1824}18251826void CharacterVirtual::Contact::RestoreState(StateRecorder &inStream)1827{1828ContactKey::RestoreState(inStream);18291830inStream.Read(mPosition);1831inStream.Read(mLinearVelocity);1832inStream.Read(mContactNormal);1833inStream.Read(mSurfaceNormal);1834inStream.Read(mDistance);1835inStream.Read(mFraction);1836inStream.Read(mMotionTypeB);1837inStream.Read(mIsSensorB);1838inStream.Read(mHadCollision);1839inStream.Read(mWasDiscarded);1840inStream.Read(mCanPushCharacter);1841mCharacterB = nullptr; // Cannot restore character B1842mUserData = 0; // Cannot restore user data1843mMaterial = PhysicsMaterial::sDefault; // Cannot restore material1844}18451846void CharacterVirtual::SaveState(StateRecorder &inStream) const1847{1848CharacterBase::SaveState(inStream);18491850inStream.Write(mPosition);1851inStream.Write(mRotation);1852inStream.Write(mLinearVelocity);1853inStream.Write(mLastDeltaTime);1854inStream.Write(mMaxHitsExceeded);18551856// Store contacts that had collision, we're using it at the beginning of the step in CancelVelocityTowardsSteepSlopes1857uint32 num_contacts = 0;1858for (const Contact &c : mActiveContacts)1859if (c.mHadCollision)1860++num_contacts;1861inStream.Write(num_contacts);1862for (const Contact &c : mActiveContacts)1863if (c.mHadCollision)1864c.SaveState(inStream);1865}18661867void CharacterVirtual::RestoreState(StateRecorder &inStream)1868{1869CharacterBase::RestoreState(inStream);18701871inStream.Read(mPosition);1872inStream.Read(mRotation);1873inStream.Read(mLinearVelocity);1874inStream.Read(mLastDeltaTime);1875inStream.Read(mMaxHitsExceeded);18761877// When validating remove contacts that don't have collision since we didn't save them1878if (inStream.IsValidating())1879for (int i = (int)mActiveContacts.size() - 1; i >= 0; --i)1880if (!mActiveContacts[i].mHadCollision)1881mActiveContacts.erase(mActiveContacts.begin() + i);18821883uint32 num_contacts = (uint32)mActiveContacts.size();1884inStream.Read(num_contacts);1885mActiveContacts.resize(num_contacts);1886for (Contact &c : mActiveContacts)1887c.RestoreState(inStream);1888}18891890JPH_NAMESPACE_END189118921893