Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Character/CharacterVirtual.cpp
21103 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/Shape/CompoundShape.h>15#include <Jolt/Physics/Collision/CollisionDispatch.h>16#include <Jolt/Core/QuickSort.h>17#include <Jolt/Core/ScopeExit.h>18#include <Jolt/Geometry/ConvexSupport.h>19#include <Jolt/Geometry/GJKClosestPoint.h>20#include <Jolt/Geometry/RayAABox.h>21#ifdef JPH_DEBUG_RENDERER22#include <Jolt/Renderer/DebugRenderer.h>23#endif // JPH_DEBUG_RENDERER2425JPH_NAMESPACE_BEGIN2627void CharacterVsCharacterCollisionSimple::Remove(const CharacterVirtual *inCharacter)28{29Array<CharacterVirtual *>::iterator i = std::find(mCharacters.begin(), mCharacters.end(), inCharacter);30if (i != mCharacters.end())31mCharacters.erase(i);32}3334void CharacterVsCharacterCollisionSimple::CollideCharacter(const CharacterVirtual *inCharacter, RMat44Arg inCenterOfMassTransform, const CollideShapeSettings &inCollideShapeSettings, RVec3Arg inBaseOffset, CollideShapeCollector &ioCollector) const35{36// Make shape 1 relative to inBaseOffset37Mat44 transform1 = inCenterOfMassTransform.PostTranslated(-inBaseOffset).ToMat44();3839const Shape *shape1 = inCharacter->GetShape();40CollideShapeSettings settings = inCollideShapeSettings;4142// Get bounds for character43AABox bounds1 = shape1->GetWorldSpaceBounds(transform1, Vec3::sOne());4445// Iterate over all characters46for (const CharacterVirtual *c : mCharacters)47if (c != inCharacter48&& !ioCollector.ShouldEarlyOut())49{50// Make shape 2 relative to inBaseOffset51Mat44 transform2 = c->GetCenterOfMassTransform().PostTranslated(-inBaseOffset).ToMat44();5253// We need to add the padding of character 2 so that we will detect collision with its outer shell54settings.mMaxSeparationDistance = inCollideShapeSettings.mMaxSeparationDistance + c->GetCharacterPadding();5556// Check if the bounding boxes of the characters overlap57const Shape *shape2 = c->GetShape();58AABox bounds2 = shape2->GetWorldSpaceBounds(transform2, Vec3::sOne());59bounds2.ExpandBy(Vec3::sReplicate(settings.mMaxSeparationDistance));60if (!bounds1.Overlaps(bounds2))61continue;6263// Collector needs to know which character we're colliding with64ioCollector.SetUserData(reinterpret_cast<uint64>(c));6566// Note that this collides against the character's shape without padding, this will be corrected for in CharacterVirtual::GetContactsAtPosition67CollisionDispatch::sCollideShapeVsShape(shape1, shape2, Vec3::sOne(), Vec3::sOne(), transform1, transform2, SubShapeIDCreator(), SubShapeIDCreator(), settings, ioCollector);68}6970// Reset the user data71ioCollector.SetUserData(0);72}7374void CharacterVsCharacterCollisionSimple::CastCharacter(const CharacterVirtual *inCharacter, RMat44Arg inCenterOfMassTransform, Vec3Arg inDirection, const ShapeCastSettings &inShapeCastSettings, RVec3Arg inBaseOffset, CastShapeCollector &ioCollector) const75{76// Convert shape cast relative to inBaseOffset77Mat44 transform1 = inCenterOfMassTransform.PostTranslated(-inBaseOffset).ToMat44();78ShapeCast shape_cast(inCharacter->GetShape(), Vec3::sOne(), transform1, inDirection);7980// Get world space bounds of the character in the form of center and extent81Vec3 origin = shape_cast.mShapeWorldBounds.GetCenter();82Vec3 extents = shape_cast.mShapeWorldBounds.GetExtent();8384// Iterate over all characters85for (const CharacterVirtual *c : mCharacters)86if (c != inCharacter87&& !ioCollector.ShouldEarlyOut())88{89// Make shape 2 relative to inBaseOffset90Mat44 transform2 = c->GetCenterOfMassTransform().PostTranslated(-inBaseOffset).ToMat44();9192// Sweep bounding box of the character against the bounding box of the other character to see if they can collide93const Shape *shape2 = c->GetShape();94AABox bounds2 = shape2->GetWorldSpaceBounds(transform2, Vec3::sOne());95bounds2.ExpandBy(extents);96if (!RayAABoxHits(origin, inDirection, bounds2.mMin, bounds2.mMax))97continue;9899// Collector needs to know which character we're colliding with100ioCollector.SetUserData(reinterpret_cast<uint64>(c));101102// Note that this collides against the character's shape without padding, this will be corrected for in CharacterVirtual::GetFirstContactForSweep103CollisionDispatch::sCastShapeVsShapeWorldSpace(shape_cast, inShapeCastSettings, shape2, Vec3::sOne(), { }, transform2, SubShapeIDCreator(), SubShapeIDCreator(), ioCollector);104}105106// Reset the user data107ioCollector.SetUserData(0);108}109110CharacterVirtual::CharacterVirtual(const CharacterVirtualSettings *inSettings, RVec3Arg inPosition, QuatArg inRotation, uint64 inUserData, PhysicsSystem *inSystem) :111CharacterBase(inSettings, inSystem),112mID(inSettings->mID),113mBackFaceMode(inSettings->mBackFaceMode),114mPredictiveContactDistance(inSettings->mPredictiveContactDistance),115mMaxCollisionIterations(inSettings->mMaxCollisionIterations),116mMaxConstraintIterations(inSettings->mMaxConstraintIterations),117mMinTimeRemaining(inSettings->mMinTimeRemaining),118mCollisionTolerance(inSettings->mCollisionTolerance),119mCharacterPadding(inSettings->mCharacterPadding),120mMaxNumHits(inSettings->mMaxNumHits),121mHitReductionCosMaxAngle(inSettings->mHitReductionCosMaxAngle),122mPenetrationRecoverySpeed(inSettings->mPenetrationRecoverySpeed),123mEnhancedInternalEdgeRemoval(inSettings->mEnhancedInternalEdgeRemoval),124mShapeOffset(inSettings->mShapeOffset),125mPosition(inPosition),126mRotation(inRotation),127mUserData(inUserData)128{129JPH_ASSERT(!mID.IsInvalid());130131// Copy settings132SetMaxStrength(inSettings->mMaxStrength);133SetMass(inSettings->mMass);134135// Create an inner rigid body if requested136if (inSettings->mInnerBodyShape != nullptr)137{138BodyCreationSettings settings(inSettings->mInnerBodyShape, GetInnerBodyPosition(), mRotation, EMotionType::Kinematic, inSettings->mInnerBodyLayer);139settings.mAllowSleeping = false; // Disable sleeping so that we will receive sensor callbacks140settings.mUserData = inUserData;141142const Body *inner_body;143BodyInterface &bi = inSystem->GetBodyInterface();144if (inSettings->mInnerBodyIDOverride.IsInvalid())145inner_body = bi.CreateBody(settings);146else147inner_body = bi.CreateBodyWithID(inSettings->mInnerBodyIDOverride, settings);148if (inner_body != nullptr)149{150mInnerBodyID = inner_body->GetID();151bi.AddBody(mInnerBodyID, EActivation::Activate);152}153}154}155156CharacterVirtual::~CharacterVirtual()157{158if (!mInnerBodyID.IsInvalid())159{160mSystem->GetBodyInterface().RemoveBody(mInnerBodyID);161mSystem->GetBodyInterface().DestroyBody(mInnerBodyID);162}163}164165void CharacterVirtual::UpdateInnerBodyTransform()166{167if (!mInnerBodyID.IsInvalid())168mSystem->GetBodyInterface().SetPositionAndRotation(mInnerBodyID, GetInnerBodyPosition(), mRotation, EActivation::DontActivate);169}170171void CharacterVirtual::GetAdjustedBodyVelocity(const Body& inBody, Vec3 &outLinearVelocity, Vec3 &outAngularVelocity) const172{173// Get real velocity of body174if (!inBody.IsStatic())175{176const MotionProperties *mp = inBody.GetMotionPropertiesUnchecked();177outLinearVelocity = mp->GetLinearVelocity();178outAngularVelocity = mp->GetAngularVelocity();179}180else181{182outLinearVelocity = outAngularVelocity = Vec3::sZero();183}184185// Allow application to override186if (mListener != nullptr)187mListener->OnAdjustBodyVelocity(this, inBody, outLinearVelocity, outAngularVelocity);188}189190Vec3 CharacterVirtual::CalculateCharacterGroundVelocity(RVec3Arg inCenterOfMass, Vec3Arg inLinearVelocity, Vec3Arg inAngularVelocity, float inDeltaTime) const191{192// Get angular velocity193float angular_velocity_len_sq = inAngularVelocity.LengthSq();194if (angular_velocity_len_sq < 1.0e-12f)195return inLinearVelocity;196float angular_velocity_len = sqrt(angular_velocity_len_sq);197198// Calculate the rotation that the object will make in the time step199Quat rotation = Quat::sRotation(inAngularVelocity / angular_velocity_len, angular_velocity_len * inDeltaTime);200201// Calculate where the new character position will be202RVec3 new_position = inCenterOfMass + rotation * Vec3(mPosition - inCenterOfMass);203204// Calculate the velocity205return inLinearVelocity + Vec3(new_position - mPosition) / inDeltaTime;206}207208template <class taCollector>209void CharacterVirtual::sFillContactProperties(const CharacterVirtual *inCharacter, Contact &outContact, const Body &inBody, Vec3Arg inUp, RVec3Arg inBaseOffset, const taCollector &inCollector, const CollideShapeResult &inResult)210{211// Get adjusted body velocity212Vec3 linear_velocity, angular_velocity;213inCharacter->GetAdjustedBodyVelocity(inBody, linear_velocity, angular_velocity);214215outContact.mPosition = inBaseOffset + inResult.mContactPointOn2;216outContact.mLinearVelocity = linear_velocity + angular_velocity.Cross(Vec3(outContact.mPosition - inBody.GetCenterOfMassPosition())); // Calculate point velocity217outContact.mContactNormal = -inResult.mPenetrationAxis.NormalizedOr(Vec3::sZero());218outContact.mSurfaceNormal = inCollector.GetContext()->GetWorldSpaceSurfaceNormal(inResult.mSubShapeID2, outContact.mPosition);219if (outContact.mContactNormal.Dot(outContact.mSurfaceNormal) < 0.0f)220outContact.mSurfaceNormal = -outContact.mSurfaceNormal; // Flip surface normal if we're hitting a back face221if (outContact.mContactNormal.Dot(inUp) > outContact.mSurfaceNormal.Dot(inUp))222outContact.mSurfaceNormal = outContact.mContactNormal; // Replace surface normal with contact normal if the contact normal is pointing more upwards223outContact.mDistance = -inResult.mPenetrationDepth;224outContact.mBodyB = inResult.mBodyID2;225outContact.mSubShapeIDB = inResult.mSubShapeID2;226outContact.mMotionTypeB = inBody.GetMotionType();227outContact.mIsSensorB = inBody.IsSensor();228outContact.mUserData = inBody.GetUserData();229outContact.mMaterial = inCollector.GetContext()->GetMaterial(inResult.mSubShapeID2);230}231232void CharacterVirtual::sFillCharacterContactProperties(Contact &outContact, const CharacterVirtual *inOtherCharacter, RVec3Arg inBaseOffset, const CollideShapeResult &inResult)233{234outContact.mPosition = inBaseOffset + inResult.mContactPointOn2;235outContact.mLinearVelocity = inOtherCharacter->GetLinearVelocity();236outContact.mSurfaceNormal = outContact.mContactNormal = -inResult.mPenetrationAxis.NormalizedOr(Vec3::sZero());237outContact.mDistance = -inResult.mPenetrationDepth;238outContact.mCharacterIDB = inOtherCharacter->GetID();239outContact.mCharacterB = inOtherCharacter;240outContact.mSubShapeIDB = inResult.mSubShapeID2;241outContact.mMotionTypeB = EMotionType::Kinematic; // Other character is kinematic, we can't directly move it242outContact.mIsSensorB = false;243outContact.mUserData = inOtherCharacter->GetUserData();244outContact.mMaterial = PhysicsMaterial::sDefault;245}246247void CharacterVirtual::ContactCollector::AddHit(const CollideShapeResult &inResult)248{249// If we exceed our contact limit, try to clean up near-duplicate contacts250if (mContacts.size() == mMaxHits)251{252// Flag that we hit this code path253mMaxHitsExceeded = true;254255// Check if we can do reduction256if (mHitReductionCosMaxAngle > -1.0f)257{258// Loop all contacts and find similar contacts259for (int i = (int)mContacts.size() - 1; i >= 0; --i)260{261Contact &contact_i = mContacts[i];262for (int j = i - 1; j >= 0; --j)263{264Contact &contact_j = mContacts[j];265if (contact_i.IsSameBody(contact_j)266&& contact_i.mContactNormal.Dot(contact_j.mContactNormal) > mHitReductionCosMaxAngle) // Very similar contact normals267{268// Remove the contact with the biggest distance269bool i_is_last = i == (int)mContacts.size() - 1;270if (contact_i.mDistance > contact_j.mDistance)271{272// Remove i273if (!i_is_last)274contact_i = mContacts.back();275mContacts.pop_back();276277// Break out of the loop, i is now an element that we already processed278break;279}280else281{282// Remove j283contact_j = mContacts.back();284mContacts.pop_back();285286// If i was the last element, we just moved it into position j. Break out of the loop, we'll see it again later.287if (i_is_last)288break;289}290}291}292}293}294295if (mContacts.size() == mMaxHits)296{297// There are still too many hits, give up!298ForceEarlyOut();299return;300}301}302303if (inResult.mBodyID2.IsInvalid())304{305// Assuming this is a hit against another character306JPH_ASSERT(mOtherCharacter != nullptr);307308// Create contact with other character309mContacts.emplace_back();310Contact &contact = mContacts.back();311sFillCharacterContactProperties(contact, mOtherCharacter, mBaseOffset, inResult);312contact.mFraction = 0.0f;313}314else315{316// Create contact with other body317BodyLockRead lock(mSystem->GetBodyLockInterface(), inResult.mBodyID2);318if (lock.SucceededAndIsInBroadPhase())319{320mContacts.emplace_back();321Contact &contact = mContacts.back();322sFillContactProperties(mCharacter, contact, lock.GetBody(), mUp, mBaseOffset, *this, inResult);323contact.mFraction = 0.0f;324}325}326}327328void CharacterVirtual::ContactCastCollector::AddHit(const ShapeCastResult &inResult)329{330if (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 hit331&& inResult.mFraction > 0.0f // Ignore collisions at fraction = 0332&& inResult.mPenetrationAxis.Dot(mDisplacement) > 0.0f) // Ignore penetrations that we're moving away from333{334// Test if this contact should be ignored335for (const ContactKey &c : mIgnoredContacts)336if (c.mBodyB == inResult.mBodyID2 && c.mSubShapeIDB == inResult.mSubShapeID2)337return;338339Contact contact;340341if (inResult.mBodyID2.IsInvalid())342{343// Assuming this is a hit against another character344JPH_ASSERT(mOtherCharacter != nullptr);345346// Create contact with other character347sFillCharacterContactProperties(contact, mOtherCharacter, mBaseOffset, inResult);348}349else350{351// Lock body only while we fetch contact properties352BodyLockRead lock(mSystem->GetBodyLockInterface(), inResult.mBodyID2);353if (!lock.SucceededAndIsInBroadPhase())354return;355356// Sweeps don't result in OnContactAdded callbacks so we can ignore sensors here357const Body &body = lock.GetBody();358if (body.IsSensor())359return;360361// Convert the hit result into a contact362sFillContactProperties(mCharacter, contact, body, mUp, mBaseOffset, *this, inResult);363}364365contact.mFraction = inResult.mFraction;366367// Check if the contact that will make us penetrate more than the allowed tolerance368if (contact.mDistance + contact.mContactNormal.Dot(mDisplacement) < -mCharacter->mCollisionTolerance369&& mCharacter->ValidateContact(contact))370{371mContact = contact;372UpdateEarlyOutFraction(contact.mFraction);373}374}375}376377void 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) const378{379// Query shape transform380RMat44 transform = GetCenterOfMassTransform(inPosition, inRotation, inShape);381382// Settings for collide shape383CollideShapeSettings settings;384settings.mBackFaceMode = mBackFaceMode;385settings.mActiveEdgeMovementDirection = inMovementDirection;386settings.mMaxSeparationDistance = mCharacterPadding + inMaxSeparationDistance;387settings.mActiveEdgeMode = EActiveEdgeMode::CollideOnlyWithActive;388389// Body filter390IgnoreSingleBodyFilterChained body_filter(mInnerBodyID, inBodyFilter);391392// Select the right function393auto collide_shape_function = mEnhancedInternalEdgeRemoval? &NarrowPhaseQuery::CollideShapeWithInternalEdgeRemoval : &NarrowPhaseQuery::CollideShape;394395// Collide shape396(mSystem->GetNarrowPhaseQuery().*collide_shape_function)(inShape, Vec3::sOne(), transform, settings, inBaseOffset, ioCollector, inBroadPhaseLayerFilter, inObjectLayerFilter, body_filter, inShapeFilter);397398// Also collide with other characters399if (mCharacterVsCharacterCollision != nullptr)400{401ioCollector.SetContext(nullptr); // We're no longer colliding with a transformed shape, reset402mCharacterVsCharacterCollision->CollideCharacter(this, transform, settings, inBaseOffset, ioCollector);403}404}405406void CharacterVirtual::GetContactsAtPosition(RVec3Arg inPosition, Vec3Arg inMovementDirection, const Shape *inShape, TempContactList &outContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) const407{408// Remove previous results409outContacts.clear();410411// Body filter412IgnoreSingleBodyFilterChained body_filter(mInnerBodyID, inBodyFilter);413414// Collide shape415ContactCollector collector(mSystem, this, mMaxNumHits, mHitReductionCosMaxAngle, mUp, mPosition, outContacts);416CheckCollision(inPosition, mRotation, inMovementDirection, mPredictiveContactDistance, inShape, mPosition, collector, inBroadPhaseLayerFilter, inObjectLayerFilter, body_filter, inShapeFilter);417418// The broadphase bounding boxes will not be deterministic, which means that the order in which the contacts are received by the collector is not deterministic.419// Therefore we need to sort the contacts to preserve determinism. Note that currently this will fail if we exceed mMaxNumHits hits.420QuickSort(outContacts.begin(), outContacts.end(), ContactOrderingPredicate());421422// Flag if we exceeded the max number of hits423mMaxHitsExceeded = collector.mMaxHitsExceeded;424425// Reduce distance to contact by padding to ensure we stay away from the object by a little margin426// (this will make collision detection cheaper - especially for sweep tests as they won't hit the surface if we're properly sliding)427for (Contact &c : outContacts)428{429c.mDistance -= mCharacterPadding;430431if (c.mCharacterB != nullptr)432c.mDistance -= c.mCharacterB->mCharacterPadding;433}434}435436void CharacterVirtual::RemoveConflictingContacts(TempContactList &ioContacts, IgnoredContactList &outIgnoredContacts) const437{438// Only use this algorithm if we're penetrating further than this (due to numerical precision issues we can always penetrate a little bit and we don't want to discard contacts if they just have a tiny penetration)439// We do need to account for padding (see GetContactsAtPosition) that is removed from the contact distances, to compensate we add it to the cMinRequiredPenetration440const float cMinRequiredPenetration = 1.25f * mCharacterPadding;441442// Discard conflicting penetrating contacts443for (size_t c1 = 0; c1 < ioContacts.size(); c1++)444{445Contact &contact1 = ioContacts[c1];446if (contact1.mDistance <= -cMinRequiredPenetration) // Only for penetrations447for (size_t c2 = c1 + 1; c2 < ioContacts.size(); c2++)448{449Contact &contact2 = ioContacts[c2];450if (contact1.IsSameBody(contact2)451&& contact2.mDistance <= -cMinRequiredPenetration // Only for penetrations452&& contact1.mContactNormal.Dot(contact2.mContactNormal) < 0.0f) // Only opposing normals453{454// Discard contacts with the least amount of penetration455if (contact1.mDistance < contact2.mDistance)456{457// Discard the 2nd contact458outIgnoredContacts.emplace_back(contact2);459ioContacts.erase(ioContacts.begin() + c2);460c2--;461}462else463{464// Discard the first contact465outIgnoredContacts.emplace_back(contact1);466ioContacts.erase(ioContacts.begin() + c1);467c1--;468break;469}470}471}472}473}474475bool CharacterVirtual::ValidateContact(const Contact &inContact) const476{477if (mListener == nullptr)478return true;479480if (inContact.mCharacterB != nullptr)481return mListener->OnCharacterContactValidate(this, inContact.mCharacterB, inContact.mSubShapeIDB);482else483return mListener->OnContactValidate(this, inContact.mBodyB, inContact.mSubShapeIDB);484}485486void CharacterVirtual::ContactAdded(const Contact &inContact, CharacterContactSettings &ioSettings)487{488if (mListener != nullptr)489{490// Check if we already know this contact491ListenerContacts::iterator it = mListenerContacts.find(inContact);492if (it != mListenerContacts.end())493{494// Max 1 contact persisted callback495if (++it->second.mCount == 1)496{497if (inContact.mCharacterB != nullptr)498mListener->OnCharacterContactPersisted(this, inContact.mCharacterB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);499else500mListener->OnContactPersisted(this, inContact.mBodyB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);501it->second.mSettings = ioSettings;502}503else504{505// Reuse the settings from the last call506ioSettings = it->second.mSettings;507}508}509else510{511// New contact512if (inContact.mCharacterB != nullptr)513mListener->OnCharacterContactAdded(this, inContact.mCharacterB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);514else515mListener->OnContactAdded(this, inContact.mBodyB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);516mListenerContacts.insert(ListenerContacts::value_type(inContact, ioSettings));517}518}519}520521template <class T>522inline static bool sCorrectFractionForCharacterPadding(const Shape *inShape, Mat44Arg inStart, Vec3Arg inDisplacement, Vec3Arg inScale, const T &inPolygon, float &ioFraction)523{524if (inShape->GetType() == EShapeType::Convex)525{526// Get the support function for the shape we're casting527const ConvexShape *convex_shape = static_cast<const ConvexShape *>(inShape);528ConvexShape::SupportBuffer buffer;529const ConvexShape::Support *support = convex_shape->GetSupportFunction(ConvexShape::ESupportMode::IncludeConvexRadius, buffer, inScale);530531// Cast the shape against the polygon532GJKClosestPoint gjk;533return gjk.CastShape(inStart, inDisplacement, cDefaultCollisionTolerance, *support, inPolygon, ioFraction);534}535else if (inShape->GetSubType() == EShapeSubType::RotatedTranslated)536{537const RotatedTranslatedShape *rt_shape = static_cast<const RotatedTranslatedShape *>(inShape);538return sCorrectFractionForCharacterPadding(rt_shape->GetInnerShape(), inStart * Mat44::sRotation(rt_shape->GetRotation()), inDisplacement, rt_shape->TransformScale(inScale), inPolygon, ioFraction);539}540else if (inShape->GetSubType() == EShapeSubType::Scaled)541{542const ScaledShape *scaled_shape = static_cast<const ScaledShape *>(inShape);543return sCorrectFractionForCharacterPadding(scaled_shape->GetInnerShape(), inStart, inDisplacement, inScale * scaled_shape->GetScale(), inPolygon, ioFraction);544}545else if (inShape->GetType() == EShapeType::Compound)546{547const CompoundShape *compound = static_cast<const CompoundShape *>(inShape);548bool return_value = false;549for (const CompoundShape::SubShape &sub_shape : compound->GetSubShapes())550return_value |= sCorrectFractionForCharacterPadding(sub_shape.mShape, inStart * sub_shape.GetLocalTransformNoScale(inScale), inDisplacement, sub_shape.TransformScale(inScale), inPolygon, ioFraction);551return return_value;552}553else554{555JPH_ASSERT(false, "Not supported yet!");556return false;557}558}559560bool CharacterVirtual::GetFirstContactForSweep(RVec3Arg inPosition, Vec3Arg inDisplacement, Contact &outContact, const IgnoredContactList &inIgnoredContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) const561{562// Too small distance -> skip checking563float displacement_len_sq = inDisplacement.LengthSq();564if (displacement_len_sq < 1.0e-8f)565return false;566567// Calculate start transform568RMat44 start = GetCenterOfMassTransform(inPosition, mRotation, mShape);569570// Settings for the cast571ShapeCastSettings settings;572settings.mBackFaceModeTriangles = mBackFaceMode;573settings.mBackFaceModeConvex = EBackFaceMode::IgnoreBackFaces;574settings.mActiveEdgeMode = EActiveEdgeMode::CollideOnlyWithActive;575settings.mUseShrunkenShapeAndConvexRadius = true;576settings.mReturnDeepestPoint = false;577578// Calculate how much extra fraction we need to add to the cast to account for the character padding579float character_padding_fraction = mCharacterPadding / sqrt(displacement_len_sq);580581// Body filter582IgnoreSingleBodyFilterChained body_filter(mInnerBodyID, inBodyFilter);583584// Cast shape585Contact contact;586contact.mFraction = 1.0f + character_padding_fraction;587RVec3 base_offset = start.GetTranslation();588ContactCastCollector collector(mSystem, this, inDisplacement, mUp, inIgnoredContacts, base_offset, contact);589collector.ResetEarlyOutFraction(contact.mFraction);590RShapeCast shape_cast(mShape, Vec3::sOne(), start, inDisplacement);591mSystem->GetNarrowPhaseQuery().CastShape(shape_cast, settings, base_offset, collector, inBroadPhaseLayerFilter, inObjectLayerFilter, body_filter, inShapeFilter);592593// Also collide with other characters594if (mCharacterVsCharacterCollision != nullptr)595{596collector.SetContext(nullptr); // We're no longer colliding with a transformed shape, reset597mCharacterVsCharacterCollision->CastCharacter(this, start, inDisplacement, settings, base_offset, collector);598}599600if (contact.mBodyB.IsInvalid() && contact.mCharacterIDB.IsInvalid())601return false;602603// Store contact604outContact = contact;605606TransformedShape ts;607float character_padding = mCharacterPadding;608if (outContact.mCharacterB != nullptr)609{610// Create a transformed shape for the character611RMat44 com = outContact.mCharacterB->GetCenterOfMassTransform();612ts = TransformedShape(com.GetTranslation(), com.GetQuaternion(), outContact.mCharacterB->GetShape(), BodyID(), SubShapeIDCreator());613614// We need to take the other character's padding into account as well615character_padding += outContact.mCharacterB->mCharacterPadding;616}617else618{619// Create a transformed shape for the body620ts = mSystem->GetBodyInterface().GetTransformedShape(outContact.mBodyB);621}622623// Fetch the face we're colliding with624Shape::SupportingFace face;625ts.GetSupportingFace(outContact.mSubShapeIDB, -outContact.mContactNormal, base_offset, face);626627bool corrected = false;628if (face.size() >= 2)629{630// Inflate the colliding face by the character padding631PolygonConvexSupport polygon(face);632AddConvexRadius add_cvx(polygon, character_padding);633634// Correct fraction to hit this inflated face instead of the inner shape635corrected = sCorrectFractionForCharacterPadding(mShape, start.GetRotation(), inDisplacement, Vec3::sOne(), add_cvx, outContact.mFraction);636}637if (!corrected)638{639// When there's only a single contact point or when we were unable to correct the fraction,640// we can just move the fraction back so that the character and its padding don't hit the contact point anymore641outContact.mFraction = max(0.0f, outContact.mFraction - character_padding_fraction);642}643644// Ensure that we never return a fraction that's bigger than 1 (which could happen due to float precision issues).645outContact.mFraction = min(outContact.mFraction, 1.0f);646647return true;648}649650void CharacterVirtual::DetermineConstraints(TempContactList &inContacts, float inDeltaTime, ConstraintList &outConstraints) const651{652for (Contact &c : inContacts)653{654Vec3 contact_velocity = c.mLinearVelocity;655656// Penetrating contact: Add a contact velocity that pushes the character out at the desired speed657if (c.mDistance < 0.0f)658contact_velocity -= c.mContactNormal * c.mDistance * mPenetrationRecoverySpeed / inDeltaTime;659660// Convert to a constraint661outConstraints.emplace_back();662Constraint &constraint = outConstraints.back();663constraint.mContact = &c;664constraint.mLinearVelocity = contact_velocity;665constraint.mPlane = Plane(c.mContactNormal, c.mDistance);666667// Next check if the angle is too steep and if it is add an additional constraint that holds the character back668if (IsSlopeTooSteep(c.mSurfaceNormal))669{670// Only take planes that point up.671// Note that we use the contact normal to allow for better sliding as the surface normal may be in the opposite direction of movement.672float dot = c.mContactNormal.Dot(mUp);673if (dot > 1.0e-3f) // Add a little slack, if the normal is perfectly horizontal we already have our vertical plane.674{675// Mark the slope constraint as steep676constraint.mIsSteepSlope = true;677678// Make horizontal normal679Vec3 normal = (c.mContactNormal - dot * mUp).Normalized();680681// Create a secondary constraint that blocks horizontal movement682outConstraints.emplace_back();683Constraint &vertical_constraint = outConstraints.back();684vertical_constraint.mContact = &c;685vertical_constraint.mLinearVelocity = contact_velocity.Dot(normal) * normal; // Project the contact velocity on the new normal so that both planes push at an equal rate686vertical_constraint.mPlane = Plane(normal, c.mDistance / normal.Dot(c.mContactNormal)); // Calculate the distance we have to travel horizontally to hit the contact plane687}688}689}690}691692bool CharacterVirtual::HandleContact(Vec3Arg inVelocity, Constraint &ioConstraint, float inDeltaTime)693{694Contact &contact = *ioConstraint.mContact;695696// Validate the contact point697if (!ValidateContact(contact))698return false;699700// We collided701contact.mHadCollision = true;702703// Send contact added event704CharacterContactSettings settings;705ContactAdded(contact, settings);706contact.mCanPushCharacter = settings.mCanPushCharacter;707708// We don't have any further interaction with sensors beyond an OnContactAdded notification709if (contact.mIsSensorB)710return false;711712// If body B cannot receive an impulse, we're done713if (!settings.mCanReceiveImpulses || contact.mMotionTypeB != EMotionType::Dynamic)714return true;715716// Lock the body we're colliding with717BodyLockWrite lock(mSystem->GetBodyLockInterface(), contact.mBodyB);718if (!lock.SucceededAndIsInBroadPhase())719return false; // Body has been removed, we should not collide with it anymore720const Body &body = lock.GetBody();721722// Calculate the velocity that we want to apply at B so that it will start moving at the character's speed at the contact point723constexpr float cDamping = 0.9f;724constexpr float cPenetrationResolution = 0.4f;725Vec3 relative_velocity = inVelocity - contact.mLinearVelocity;726float projected_velocity = relative_velocity.Dot(contact.mContactNormal);727float delta_velocity = -projected_velocity * cDamping - min(contact.mDistance, 0.0f) * cPenetrationResolution / inDeltaTime;728729// Don't apply impulses if we're separating730if (delta_velocity < 0.0f)731return true;732733// Determine mass properties of the body we're colliding with734const MotionProperties *motion_properties = body.GetMotionProperties();735RVec3 center_of_mass = body.GetCenterOfMassPosition();736Mat44 inverse_inertia = body.GetInverseInertia();737float inverse_mass = motion_properties->GetInverseMass();738739// Calculate the inverse of the mass of body B as seen at the contact point in the direction of the contact normal740Vec3 jacobian = Vec3(contact.mPosition - center_of_mass).Cross(contact.mContactNormal);741float inv_effective_mass = inverse_inertia.Multiply3x3(jacobian).Dot(jacobian) + inverse_mass;742743// Impulse P = M dv744float impulse = delta_velocity / inv_effective_mass;745746// Clamp the impulse according to the character strength, character strength is a force in newtons, P = F dt747float max_impulse = mMaxStrength * inDeltaTime;748impulse = min(impulse, max_impulse);749750// Calculate the world space impulse to apply751Vec3 world_impulse = -impulse * contact.mContactNormal;752753// Cancel impulse in down direction (we apply gravity later)754float impulse_dot_up = world_impulse.Dot(mUp);755if (impulse_dot_up < 0.0f)756world_impulse -= impulse_dot_up * mUp;757758// Now apply the impulse (body is already locked so we use the no-lock interface)759mSystem->GetBodyInterfaceNoLock().AddImpulse(contact.mBodyB, world_impulse, contact.mPosition);760return true;761}762763void CharacterVirtual::SolveConstraints(Vec3Arg inVelocity, float inDeltaTime, float inTimeRemaining, ConstraintList &ioConstraints, IgnoredContactList &ioIgnoredContacts, float &outTimeSimulated, Vec3 &outDisplacement, TempAllocator &inAllocator764#ifdef JPH_DEBUG_RENDERER765, bool inDrawConstraints766#endif // JPH_DEBUG_RENDERER767)768{769// If there are no constraints we can immediately move to our target770if (ioConstraints.empty())771{772outDisplacement = inVelocity * inTimeRemaining;773outTimeSimulated = inTimeRemaining;774return;775}776777// Create array that holds the constraints in order of time of impact (sort will happen later)778Array<Constraint *, STLTempAllocator<Constraint *>> sorted_constraints(inAllocator);779sorted_constraints.resize(ioConstraints.size());780for (size_t index = 0; index < sorted_constraints.size(); index++)781sorted_constraints[index] = &ioConstraints[index];782783// This is the velocity we use for the displacement, if we hit something it will be shortened784Vec3 velocity = inVelocity;785786// Keep track of the last velocity that was applied to the character so that we can detect when the velocity reverses787Vec3 last_velocity = inVelocity;788789// Start with no displacement790outDisplacement = Vec3::sZero();791outTimeSimulated = 0.0f;792793// These are the contacts that we hit previously without moving a significant distance794Array<Constraint *, STLTempAllocator<Constraint *>> previous_contacts(inAllocator);795previous_contacts.resize(mMaxConstraintIterations);796int num_previous_contacts = 0;797798// Loop for a max amount of iterations799for (uint iteration = 0; iteration < mMaxConstraintIterations; iteration++)800{801// Calculate time of impact for all constraints802for (Constraint &c : ioConstraints)803{804// Project velocity on plane direction805c.mProjectedVelocity = c.mPlane.GetNormal().Dot(c.mLinearVelocity - velocity);806if (c.mProjectedVelocity < 1.0e-6f)807{808c.mTOI = FLT_MAX;809}810else811{812// Distance to plane813float dist = c.mPlane.SignedDistance(outDisplacement);814815if (dist - c.mProjectedVelocity * inTimeRemaining > -1.0e-4f)816{817// Too little penetration, accept the movement818c.mTOI = FLT_MAX;819}820else821{822// Calculate time of impact823c.mTOI = max(0.0f, dist / c.mProjectedVelocity);824}825}826}827828// Sort constraints on proximity829QuickSort(sorted_constraints.begin(), sorted_constraints.end(), [](const Constraint *inLHS, const Constraint *inRHS) {830// If both constraints hit at t = 0 then order the one that will push the character furthest first831// Note that because we add velocity to penetrating contacts, this will also resolve contacts that penetrate the most832if (inLHS->mTOI <= 0.0f && inRHS->mTOI <= 0.0f)833return inLHS->mProjectedVelocity > inRHS->mProjectedVelocity;834835// Then sort on time of impact836if (inLHS->mTOI != inRHS->mTOI)837return inLHS->mTOI < inRHS->mTOI;838839// As a tie breaker sort static first so it has the most influence840return inLHS->mContact->mMotionTypeB > inRHS->mContact->mMotionTypeB;841});842843// Find the first valid constraint844Constraint *constraint = nullptr;845for (Constraint *c : sorted_constraints)846{847// Take the first contact and see if we can reach it848if (c->mTOI >= inTimeRemaining)849{850// We can reach our goal!851outDisplacement += velocity * inTimeRemaining;852outTimeSimulated += inTimeRemaining;853return;854}855856// Test if this contact was discarded by the contact callback before857if (c->mContact->mWasDiscarded)858continue;859860// Handle the contact861if (!c->mContact->mHadCollision862&& !HandleContact(velocity, *c, inDeltaTime))863{864// Constraint should be ignored, remove it from the list865c->mContact->mWasDiscarded = true;866867// Mark it as ignored for GetFirstContactForSweep868ioIgnoredContacts.emplace_back(*c->mContact);869continue;870}871872// Cancel velocity of constraint if it cannot push the character873if (!c->mContact->mCanPushCharacter)874c->mLinearVelocity = Vec3::sZero();875876// We found the first constraint that we want to collide with877constraint = c;878break;879}880881if (constraint == nullptr)882{883// All constraints were discarded, we can reach our goal!884outDisplacement += velocity * inTimeRemaining;885outTimeSimulated += inTimeRemaining;886return;887}888889// Move to the contact890outDisplacement += velocity * constraint->mTOI;891inTimeRemaining -= constraint->mTOI;892outTimeSimulated += constraint->mTOI;893894// If there's not enough time left to be simulated, bail895if (inTimeRemaining < mMinTimeRemaining)896return;897898// If we've moved significantly, clear all previous contacts899if (constraint->mTOI > 1.0e-4f)900num_previous_contacts = 0;901902// Get the normal of the plane we're hitting903Vec3 plane_normal = constraint->mPlane.GetNormal();904905// 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 slope906// (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)907if (constraint->mIsSteepSlope)908{909// We're hitting a steep slope, create a vertical plane that blocks any further movement up the slope (note: not normalized)910Vec3 vertical_plane_normal = plane_normal - plane_normal.Dot(mUp) * mUp;911912// Get the relative velocity between the character and the constraint913Vec3 relative_velocity = velocity - constraint->mLinearVelocity;914915// Remove velocity towards the slope916velocity = velocity - min(0.0f, relative_velocity.Dot(vertical_plane_normal)) * vertical_plane_normal / vertical_plane_normal.LengthSq();917}918919// Get the relative velocity between the character and the constraint920Vec3 relative_velocity = velocity - constraint->mLinearVelocity;921922// Calculate new velocity if we cancel the relative velocity in the normal direction923Vec3 new_velocity = velocity - relative_velocity.Dot(plane_normal) * plane_normal;924925// Find the normal of the previous contact that we will violate the most if we move in this new direction926float highest_penetration = 0.0f;927Constraint *other_constraint = nullptr;928for (Constraint **c = previous_contacts.data(); c < previous_contacts.data() + num_previous_contacts; ++c)929if (*c != constraint)930{931// Calculate how much we will penetrate if we move in this direction932Vec3 other_normal = (*c)->mPlane.GetNormal();933float penetration = ((*c)->mLinearVelocity - new_velocity).Dot(other_normal);934if (penetration > highest_penetration)935{936// 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.937float dot = other_normal.Dot(plane_normal);938if (dot < 0.984f && dot > -0.984f)939{940highest_penetration = penetration;941other_constraint = *c;942}943}944}945946// Check if we found a 2nd constraint947if (other_constraint != nullptr)948{949// Calculate the sliding direction and project the new velocity onto that sliding direction950Vec3 other_normal = other_constraint->mPlane.GetNormal();951Vec3 slide_dir = plane_normal.Cross(other_normal).Normalized();952Vec3 velocity_in_slide_dir = new_velocity.Dot(slide_dir) * slide_dir;953954// 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 planes955constraint->mLinearVelocity -= min(0.0f, constraint->mLinearVelocity.Dot(other_normal)) * other_normal;956957// 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 planes958other_constraint->mLinearVelocity -= min(0.0f, other_constraint->mLinearVelocity.Dot(plane_normal)) * plane_normal;959960// Calculate the velocity of this constraint perpendicular to the slide direction961Vec3 perpendicular_velocity = constraint->mLinearVelocity - constraint->mLinearVelocity.Dot(slide_dir) * slide_dir;962963// Calculate the velocity of the other constraint perpendicular to the slide direction964Vec3 other_perpendicular_velocity = other_constraint->mLinearVelocity - other_constraint->mLinearVelocity.Dot(slide_dir) * slide_dir;965966// Add all components together967new_velocity = velocity_in_slide_dir + perpendicular_velocity + other_perpendicular_velocity;968}969970// Allow application to modify calculated velocity971if (mListener != nullptr)972{973if (constraint->mContact->mCharacterB != nullptr)974mListener->OnCharacterContactSolve(this, constraint->mContact->mCharacterB, constraint->mContact->mSubShapeIDB, constraint->mContact->mPosition, constraint->mContact->mContactNormal, constraint->mContact->mLinearVelocity, constraint->mContact->mMaterial, velocity, new_velocity);975else976mListener->OnContactSolve(this, constraint->mContact->mBodyB, constraint->mContact->mSubShapeIDB, constraint->mContact->mPosition, constraint->mContact->mContactNormal, constraint->mContact->mLinearVelocity, constraint->mContact->mMaterial, velocity, new_velocity);977}978979#ifdef JPH_DEBUG_RENDERER980if (inDrawConstraints)981{982// Calculate where to draw983RVec3 offset = mPosition + Vec3(0, 0, 2.5f * (iteration + 1));984985// Draw constraint plane986DebugRenderer::sInstance->DrawPlane(offset, constraint->mPlane.GetNormal(), Color::sCyan, 1.0f);987988// Draw 2nd constraint plane989if (other_constraint != nullptr)990DebugRenderer::sInstance->DrawPlane(offset, other_constraint->mPlane.GetNormal(), Color::sBlue, 1.0f);991992// Draw starting velocity993DebugRenderer::sInstance->DrawArrow(offset, offset + velocity, Color::sGreen, 0.05f);994995// Draw resulting velocity996DebugRenderer::sInstance->DrawArrow(offset, offset + new_velocity, Color::sRed, 0.05f);997}998#endif // JPH_DEBUG_RENDERER9991000// Update the velocity1001velocity = new_velocity;10021003// Add the contact to the list so that next iteration we can avoid violating it again1004previous_contacts[num_previous_contacts] = constraint;1005num_previous_contacts++;10061007// Check early out1008if (constraint->mProjectedVelocity < 1.0e-8f // Constraint should not be pushing, otherwise there may be other constraints that are pushing us1009&& velocity.LengthSq() < 1.0e-8f) // There's not enough velocity left1010return;10111012// If the constraint has velocity we accept the new velocity, otherwise check that we didn't reverse velocity1013if (!constraint->mLinearVelocity.IsNearZero(1.0e-8f))1014last_velocity = constraint->mLinearVelocity;1015else if (velocity.Dot(last_velocity) < 0.0f)1016return;1017}1018}10191020void CharacterVirtual::UpdateSupportingContact(bool inSkipContactVelocityCheck, TempAllocator &inAllocator)1021{1022// Flag contacts as having a collision if they're close enough but ignore contacts we're moving away from.1023// Note that if we did MoveShape before we want to preserve any contacts that it marked as colliding1024for (Contact &c : mActiveContacts)1025if (!c.mWasDiscarded1026&& !c.mHadCollision1027&& c.mDistance < mCollisionTolerance1028&& (inSkipContactVelocityCheck || c.mSurfaceNormal.Dot(mLinearVelocity - c.mLinearVelocity) <= 1.0e-4f))1029{1030if (ValidateContact(c))1031{1032CharacterContactSettings dummy;1033ContactAdded(c, dummy);1034c.mHadCollision = true;1035}1036else1037c.mWasDiscarded = true;1038}10391040// Calculate transform that takes us to character local space1041RMat44 inv_transform = RMat44::sInverseRotationTranslation(mRotation, mPosition);10421043// Determine if we're supported or not1044int num_supported = 0;1045int num_sliding = 0;1046int num_avg_normal = 0;1047Vec3 avg_normal = Vec3::sZero();1048Vec3 avg_velocity = Vec3::sZero();1049const Contact *supporting_contact = nullptr;1050float max_cos_angle = -FLT_MAX;1051const Contact *deepest_contact = nullptr;1052float smallest_distance = FLT_MAX;1053for (const Contact &c : mActiveContacts)1054if (c.mHadCollision && !c.mWasDiscarded)1055{1056// Calculate the angle between the plane normal and the up direction1057float cos_angle = c.mSurfaceNormal.Dot(mUp);10581059// Find the deepest contact1060if (c.mDistance < smallest_distance)1061{1062deepest_contact = &c;1063smallest_distance = c.mDistance;1064}10651066// If this contact is in front of our plane, we cannot be supported by it1067if (mSupportingVolume.SignedDistance(Vec3(inv_transform * c.mPosition)) > 0.0f)1068continue;10691070// Find the contact with the normal that is pointing most upwards and store it1071if (max_cos_angle < cos_angle)1072{1073supporting_contact = &c;1074max_cos_angle = cos_angle;1075}10761077// Check if this is a sliding or supported contact1078bool is_supported = mCosMaxSlopeAngle > cNoMaxSlopeAngle || cos_angle >= mCosMaxSlopeAngle;1079if (is_supported)1080num_supported++;1081else1082num_sliding++;10831084// If the angle between the two is less than 85 degrees we also use it to calculate the average normal1085if (cos_angle >= 0.08f)1086{1087avg_normal += c.mSurfaceNormal;1088num_avg_normal++;10891090// For static or dynamic objects or for contacts that don't support us just take the contact velocity1091if (c.mMotionTypeB != EMotionType::Kinematic || !is_supported)1092avg_velocity += c.mLinearVelocity;1093else1094{1095// For keyframed objects that support us calculate the velocity at our position rather than at the contact position so that we properly follow the object1096BodyLockRead lock(mSystem->GetBodyLockInterface(), c.mBodyB);1097if (lock.SucceededAndIsInBroadPhase())1098{1099const Body &body = lock.GetBody();11001101// Get adjusted body velocity1102Vec3 linear_velocity, angular_velocity;1103GetAdjustedBodyVelocity(body, linear_velocity, angular_velocity);11041105// Calculate the ground velocity1106avg_velocity += CalculateCharacterGroundVelocity(body.GetCenterOfMassPosition(), linear_velocity, angular_velocity, mLastDeltaTime);1107}1108else1109{1110// Fall back to contact velocity1111avg_velocity += c.mLinearVelocity;1112}1113}1114}1115}11161117// Take either the most supporting contact or the deepest contact1118const Contact *best_contact = supporting_contact != nullptr? supporting_contact : deepest_contact;11191120// Calculate average normal and velocity1121if (num_avg_normal >= 1)1122{1123mGroundNormal = avg_normal.Normalized();1124mGroundVelocity = avg_velocity / float(num_avg_normal);1125}1126else if (best_contact != nullptr)1127{1128mGroundNormal = best_contact->mSurfaceNormal;1129mGroundVelocity = best_contact->mLinearVelocity;1130}1131else1132{1133mGroundNormal = Vec3::sZero();1134mGroundVelocity = Vec3::sZero();1135}11361137// Copy contact properties1138if (best_contact != nullptr)1139{1140mGroundBodyID = best_contact->mBodyB;1141mGroundBodySubShapeID = best_contact->mSubShapeIDB;1142mGroundPosition = best_contact->mPosition;1143mGroundMaterial = best_contact->mMaterial;1144mGroundUserData = best_contact->mUserData;1145}1146else1147{1148mGroundBodyID = BodyID();1149mGroundBodySubShapeID = SubShapeID();1150mGroundPosition = RVec3::sZero();1151mGroundMaterial = PhysicsMaterial::sDefault;1152mGroundUserData = 0;1153}11541155// Determine ground state1156if (num_supported > 0)1157{1158// We made contact with something that supports us1159mGroundState = EGroundState::OnGround;1160}1161else if (num_sliding > 0)1162{1163if ((mLinearVelocity - deepest_contact->mLinearVelocity).Dot(mUp) > 1.0e-4f)1164{1165// We cannot be on ground if we're moving upwards relative to the ground1166mGroundState = EGroundState::OnSteepGround;1167}1168else1169{1170// 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 supported11711172// Convert the contacts into constraints1173TempContactList contacts(mActiveContacts.begin(), mActiveContacts.end(), inAllocator);1174ConstraintList constraints(inAllocator);1175constraints.reserve(contacts.size() * 2);1176DetermineConstraints(contacts, mLastDeltaTime, constraints);11771178// Solve the displacement using these constraints, this is used to check if we didn't move at all because we are supported1179Vec3 displacement;1180float time_simulated;1181IgnoredContactList ignored_contacts(inAllocator);1182ignored_contacts.reserve(contacts.size());1183SolveConstraints(-mUp, 1.0f, 1.0f, constraints, ignored_contacts, time_simulated, displacement, inAllocator);11841185// If we're blocked then we're supported, otherwise we're sliding1186float min_required_displacement_sq = Square(0.6f * mLastDeltaTime);1187if (time_simulated < 0.001f || displacement.LengthSq() < min_required_displacement_sq)1188mGroundState = EGroundState::OnGround;1189else1190mGroundState = EGroundState::OnSteepGround;1191}1192}1193else1194{1195// Not supported by anything1196mGroundState = best_contact != nullptr? EGroundState::NotSupported : EGroundState::InAir;1197}1198}11991200void CharacterVirtual::StoreActiveContacts(const TempContactList &inContacts, TempAllocator &inAllocator)1201{1202StartTrackingContactChanges();12031204mActiveContacts.assign(inContacts.begin(), inContacts.end());12051206UpdateSupportingContact(true, inAllocator);12071208FinishTrackingContactChanges();1209}12101211void CharacterVirtual::MoveShape(RVec3 &ioPosition, Vec3Arg inVelocity, float inDeltaTime, ContactList *outActiveContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator1212#ifdef JPH_DEBUG_RENDERER1213, bool inDrawConstraints1214#endif // JPH_DEBUG_RENDERER1215)1216{1217JPH_DET_LOG("CharacterVirtual::MoveShape: pos: " << ioPosition << " vel: " << inVelocity << " dt: " << inDeltaTime);12181219Vec3 movement_direction = inVelocity.NormalizedOr(Vec3::sZero());12201221float time_remaining = inDeltaTime;1222for (uint iteration = 0; iteration < mMaxCollisionIterations && time_remaining >= mMinTimeRemaining; iteration++)1223{1224JPH_DET_LOG("iter: " << iteration << " time: " << time_remaining);12251226// Determine contacts in the neighborhood1227TempContactList contacts(inAllocator);1228contacts.reserve(mMaxNumHits);1229GetContactsAtPosition(ioPosition, movement_direction, mShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);12301231#ifdef JPH_ENABLE_DETERMINISM_LOG1232for (const Contact &c : contacts)1233JPH_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);1234#endif // JPH_ENABLE_DETERMINISM_LOG12351236// Remove contacts with the same body that have conflicting normals1237IgnoredContactList ignored_contacts(inAllocator);1238ignored_contacts.reserve(contacts.size());1239RemoveConflictingContacts(contacts, ignored_contacts);12401241// Convert contacts into constraints1242ConstraintList constraints(inAllocator);1243constraints.reserve(contacts.size() * 2);1244DetermineConstraints(contacts, inDeltaTime, constraints);12451246#ifdef JPH_DEBUG_RENDERER1247bool draw_constraints = inDrawConstraints && iteration == 0;1248if (draw_constraints)1249{1250for (const Constraint &c : constraints)1251{1252// Draw contact point1253DebugRenderer::sInstance->DrawMarker(c.mContact->mPosition, Color::sYellow, 0.05f);1254Vec3 dist_to_plane = -c.mPlane.GetConstant() * c.mPlane.GetNormal();12551256// Draw arrow towards surface that we're hitting1257DebugRenderer::sInstance->DrawArrow(c.mContact->mPosition, c.mContact->mPosition - dist_to_plane, Color::sYellow, 0.05f);12581259// Draw plane around the player position indicating the space that we can move1260DebugRenderer::sInstance->DrawPlane(mPosition + dist_to_plane, c.mPlane.GetNormal(), Color::sCyan, 1.0f);1261DebugRenderer::sInstance->DrawArrow(mPosition + dist_to_plane, mPosition + dist_to_plane + c.mContact->mSurfaceNormal, Color::sRed, 0.05f);1262}1263}1264#endif // JPH_DEBUG_RENDERER12651266// Solve the displacement using these constraints1267Vec3 displacement;1268float time_simulated;1269SolveConstraints(inVelocity, inDeltaTime, time_remaining, constraints, ignored_contacts, time_simulated, displacement, inAllocator1270#ifdef JPH_DEBUG_RENDERER1271, draw_constraints1272#endif // JPH_DEBUG_RENDERER1273);12741275// Store the contacts now that the colliding ones have been marked1276if (outActiveContacts != nullptr)1277outActiveContacts->assign(contacts.begin(), contacts.end());12781279// Do a sweep to test if the path is really unobstructed1280Contact cast_contact;1281if (GetFirstContactForSweep(ioPosition, displacement, cast_contact, ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))1282{1283displacement *= cast_contact.mFraction;1284time_simulated *= cast_contact.mFraction;1285}12861287// Update the position1288ioPosition += displacement;1289time_remaining -= time_simulated;12901291// If the displacement during this iteration was too small we assume we cannot further progress this update1292if (displacement.LengthSq() < 1.0e-8f)1293break;1294}1295}12961297void CharacterVirtual::SetUserData(uint64 inUserData)1298{1299mUserData = inUserData;13001301if (!mInnerBodyID.IsInvalid())1302mSystem->GetBodyInterface().SetUserData(mInnerBodyID, inUserData);1303}13041305Vec3 CharacterVirtual::CancelVelocityTowardsSteepSlopes(Vec3Arg inDesiredVelocity) const1306{1307// If we're not pushing against a steep slope, return the desired velocity1308// Note: This is important as WalkStairs overrides the ground state to OnGround when its first check fails but the second succeeds1309if (mGroundState == CharacterVirtual::EGroundState::OnGround1310|| mGroundState == CharacterVirtual::EGroundState::InAir)1311return inDesiredVelocity;13121313Vec3 desired_velocity = inDesiredVelocity;1314for (const Contact &c : mActiveContacts)1315if (c.mHadCollision1316&& !c.mWasDiscarded1317&& IsSlopeTooSteep(c.mSurfaceNormal))1318{1319// Note that we use the contact normal to allow for better sliding as the surface normal may be in the opposite direction of movement.1320Vec3 normal = c.mContactNormal;13211322// Remove normal vertical component1323normal -= normal.Dot(mUp) * mUp;13241325// Cancel horizontal movement in opposite direction1326float dot = normal.Dot(desired_velocity);1327if (dot < 0.0f)1328desired_velocity -= (dot * normal) / normal.LengthSq();1329}1330return desired_velocity;1331}13321333void CharacterVirtual::StartTrackingContactChanges()1334{1335// Check if we're starting for the first time1336if (++mTrackingContactChanges > 1)1337return;13381339// No need to track anything if we don't have a listener1340JPH_ASSERT(mListenerContacts.empty());1341if (mListener == nullptr)1342return;13431344// Mark all current contacts as not seen1345mListenerContacts.reserve(ListenerContacts::size_type(mActiveContacts.size()));1346for (const Contact &c : mActiveContacts)1347if (c.mHadCollision)1348mListenerContacts.insert(ListenerContacts::value_type(c, ListenerContactValue()));1349}13501351void CharacterVirtual::FinishTrackingContactChanges()1352{1353// Check if we have to do anything1354int count = --mTrackingContactChanges;1355JPH_ASSERT(count >= 0, "Called FinishTrackingContactChanges more times than StartTrackingContactChanges");1356if (count > 0)1357return;13581359// No need to track anything if we don't have a listener1360if (mListener == nullptr)1361return;13621363// Since we can do multiple operations (e.g. Update followed by WalkStairs)1364// we can end up with contacts that were marked as active to the listener but that are1365// no longer in the active contact list. We go over all contacts and mark them again1366// to ensure that these lists are in sync.1367for (ListenerContacts::value_type &c : mListenerContacts)1368c.second.mCount = 0;1369for (const Contact &c : mActiveContacts)1370if (c.mHadCollision)1371{1372ListenerContacts::iterator it = mListenerContacts.find(c);1373JPH_ASSERT(it != mListenerContacts.end());1374it->second.mCount = 1;1375}13761377// Call contact removal callbacks1378for (ListenerContacts::iterator it = mListenerContacts.begin(); it != mListenerContacts.end(); ++it)1379if (it->second.mCount == 0)1380{1381const ContactKey &c = it->first;1382if (!c.mCharacterIDB.IsInvalid())1383mListener->OnCharacterContactRemoved(this, c.mCharacterIDB, c.mSubShapeIDB);1384else1385mListener->OnContactRemoved(this, c.mBodyB, c.mSubShapeIDB);1386}1387mListenerContacts.ClearAndKeepMemory();1388}13891390void CharacterVirtual::Update(float inDeltaTime, Vec3Arg inGravity, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)1391{1392// If there's no delta time, we don't need to do anything1393if (inDeltaTime <= 0.0f)1394return;13951396StartTrackingContactChanges();1397JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });13981399// Remember delta time for checking if we're supported by the ground1400mLastDeltaTime = inDeltaTime;14011402// Slide the shape through the world1403MoveShape(mPosition, mLinearVelocity, inDeltaTime, &mActiveContacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator1404#ifdef JPH_DEBUG_RENDERER1405, sDrawConstraints1406#endif // JPH_DEBUG_RENDERER1407);14081409// Determine the object that we're standing on1410UpdateSupportingContact(false, inAllocator);14111412// Ensure that the rigid body ends up at the new position1413UpdateInnerBodyTransform();14141415// If we're on the ground1416if (!mGroundBodyID.IsInvalid() && mMass > 0.0f)1417{1418// Add the impulse to the ground due to gravity: P = F dt = M g dt1419float normal_dot_gravity = mGroundNormal.Dot(inGravity);1420if (normal_dot_gravity < 0.0f)1421{1422Vec3 world_impulse = -(mMass * normal_dot_gravity / inGravity.Length() * inDeltaTime) * inGravity;1423mSystem->GetBodyInterface().AddImpulse(mGroundBodyID, world_impulse, mGroundPosition);1424}1425}1426}14271428void CharacterVirtual::RefreshContacts(const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)1429{1430// Determine the contacts1431TempContactList contacts(inAllocator);1432contacts.reserve(mMaxNumHits);1433GetContactsAtPosition(mPosition, mLinearVelocity.NormalizedOr(Vec3::sZero()), mShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);14341435StoreActiveContacts(contacts, inAllocator);1436}14371438void CharacterVirtual::UpdateGroundVelocity()1439{1440BodyLockRead lock(mSystem->GetBodyLockInterface(), mGroundBodyID);1441if (lock.SucceededAndIsInBroadPhase())1442{1443const Body &body = lock.GetBody();14441445// Get adjusted body velocity1446Vec3 linear_velocity, angular_velocity;1447GetAdjustedBodyVelocity(body, linear_velocity, angular_velocity);14481449// Calculate the ground velocity1450mGroundVelocity = CalculateCharacterGroundVelocity(body.GetCenterOfMassPosition(), linear_velocity, angular_velocity, mLastDeltaTime);1451}1452}14531454void CharacterVirtual::MoveToContact(RVec3Arg inPosition, const Contact &inContact, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)1455{1456// Set the new position1457SetPosition(inPosition);14581459// Trigger contact added callback1460CharacterContactSettings dummy;1461ContactAdded(inContact, dummy);14621463// Determine the contacts1464TempContactList contacts(inAllocator);1465contacts.reserve(mMaxNumHits + 1); // +1 because we can add one extra below1466GetContactsAtPosition(mPosition, mLinearVelocity.NormalizedOr(Vec3::sZero()), mShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);14671468// Ensure that we mark inContact as colliding1469bool found_contact = false;1470for (Contact &c : contacts)1471if (c.mBodyB == inContact.mBodyB1472&& c.mSubShapeIDB == inContact.mSubShapeIDB)1473{1474c.mHadCollision = true;1475found_contact = true;1476}1477if (!found_contact)1478{1479contacts.push_back(inContact);14801481Contact © = contacts.back();1482copy.mHadCollision = true;1483}14841485StoreActiveContacts(contacts, inAllocator);1486JPH_ASSERT(mGroundState != EGroundState::InAir);14871488// Ensure that the rigid body ends up at the new position1489UpdateInnerBodyTransform();1490}14911492bool CharacterVirtual::SetShape(const Shape *inShape, float inMaxPenetrationDepth, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)1493{1494if (mShape == nullptr || mSystem == nullptr)1495{1496// It hasn't been initialized yet1497mShape = inShape;1498return true;1499}15001501if (inShape != mShape && inShape != nullptr)1502{1503if (inMaxPenetrationDepth < FLT_MAX)1504{1505// Check collision around the new shape1506TempContactList contacts(inAllocator);1507contacts.reserve(mMaxNumHits);1508GetContactsAtPosition(mPosition, mLinearVelocity.NormalizedOr(Vec3::sZero()), inShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);15091510// Test if this results in penetration, if so cancel the transition1511for (const Contact &c : contacts)1512if (c.mDistance < -inMaxPenetrationDepth1513&& !c.mIsSensorB)1514return false;15151516StoreActiveContacts(contacts, inAllocator);1517}15181519// Set new shape1520mShape = inShape;1521}15221523return mShape == inShape;1524}15251526void CharacterVirtual::SetInnerBodyShape(const Shape *inShape)1527{1528mSystem->GetBodyInterface().SetShape(mInnerBodyID, inShape, false, EActivation::DontActivate);1529}15301531bool CharacterVirtual::CanWalkStairs(Vec3Arg inLinearVelocity) const1532{1533// We can only walk stairs if we're supported1534if (!IsSupported())1535return false;15361537// Check if there's enough horizontal velocity to trigger a stair walk1538Vec3 horizontal_velocity = inLinearVelocity - inLinearVelocity.Dot(mUp) * mUp;1539if (horizontal_velocity.IsNearZero(1.0e-6f))1540return false;15411542// Check contacts for steep slopes1543for (const Contact &c : mActiveContacts)1544if (c.mHadCollision1545&& !c.mWasDiscarded1546&& c.mSurfaceNormal.Dot(horizontal_velocity - c.mLinearVelocity) < 0.0f // Pushing into the contact1547&& IsSlopeTooSteep(c.mSurfaceNormal)) // Slope too steep1548return true;15491550return false;1551}15521553bool 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)1554{1555StartTrackingContactChanges();1556JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });15571558// Move up1559Vec3 up = inStepUp;1560Contact contact;1561IgnoredContactList dummy_ignored_contacts(inAllocator);1562if (GetFirstContactForSweep(mPosition, up, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))1563{1564if (contact.mFraction < 1.0e-6f)1565return false; // No movement, cancel15661567// Limit up movement to the first contact point1568up *= contact.mFraction;1569}1570RVec3 up_position = mPosition + up;15711572#ifdef JPH_DEBUG_RENDERER1573// Draw sweep up1574if (sDrawWalkStairs)1575DebugRenderer::sInstance->DrawArrow(mPosition, up_position, Color::sWhite, 0.01f);1576#endif // JPH_DEBUG_RENDERER15771578// Collect normals of steep slopes that we would like to walk stairs on.1579// We need to do this before calling MoveShape because it will update mActiveContacts.1580Vec3 character_velocity = inStepForward / inDeltaTime;1581Vec3 horizontal_velocity = character_velocity - character_velocity.Dot(mUp) * mUp;1582Array<Vec3, STLTempAllocator<Vec3>> steep_slope_normals(inAllocator);1583steep_slope_normals.reserve(mActiveContacts.size());1584for (const Contact &c : mActiveContacts)1585if (c.mHadCollision1586&& !c.mWasDiscarded1587&& c.mSurfaceNormal.Dot(horizontal_velocity - c.mLinearVelocity) < 0.0f // Pushing into the contact1588&& IsSlopeTooSteep(c.mSurfaceNormal)) // Slope too steep1589steep_slope_normals.push_back(c.mSurfaceNormal);1590if (steep_slope_normals.empty())1591return false; // No steep slopes, cancel15921593// Horizontal movement1594RVec3 new_position = up_position;1595MoveShape(new_position, character_velocity, inDeltaTime, nullptr, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);1596Vec3 horizontal_movement = Vec3(new_position - up_position);1597float horizontal_movement_sq = horizontal_movement.LengthSq();1598if (horizontal_movement_sq < 1.0e-8f)1599return false; // No movement, cancel16001601// Check if we made any progress towards any of the steep slopes, if not we just slid along the slope1602// so we need to cancel the stair walk or else we will move faster than we should as we've done1603// normal movement first and then stair walk.1604bool made_progress = false;1605float max_dot = -0.05f * inStepForward.Length();1606for (const Vec3 &normal : steep_slope_normals)1607if (normal.Dot(horizontal_movement) < max_dot)1608{1609// We moved more than 5% of the forward step against a steep slope, accept this as progress1610made_progress = true;1611break;1612}1613if (!made_progress)1614return false;16151616#ifdef JPH_DEBUG_RENDERER1617// Draw horizontal sweep1618if (sDrawWalkStairs)1619DebugRenderer::sInstance->DrawArrow(up_position, new_position, Color::sWhite, 0.01f);1620#endif // JPH_DEBUG_RENDERER16211622// Move down towards the floor.1623// Note that we travel the same amount down as we traveled up with the specified extra1624Vec3 down = -up + inStepDownExtra;1625if (!GetFirstContactForSweep(new_position, down, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))1626return false; // No floor found, we're in mid air, cancel stair walk16271628#ifdef JPH_DEBUG_RENDERER1629// Draw sweep down1630if (sDrawWalkStairs)1631{1632RVec3 debug_pos = new_position + contact.mFraction * down;1633DebugRenderer::sInstance->DrawArrow(new_position, debug_pos, Color::sWhite, 0.01f);1634DebugRenderer::sInstance->DrawArrow(contact.mPosition, contact.mPosition + contact.mSurfaceNormal, Color::sWhite, 0.01f);1635mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(debug_pos, mRotation, mShape), Vec3::sOne(), Color::sWhite, false, true);1636}1637#endif // JPH_DEBUG_RENDERER16381639// Test for floor that will support the character1640if (IsSlopeTooSteep(contact.mSurfaceNormal))1641{1642// If no test position was provided, we cancel the stair walk1643if (inStepForwardTest.IsNearZero())1644return false;16451646// Delta time may be very small, so it may be that we hit the edge of a step and the normal is too horizontal.1647// In order to judge if the floor is flat further along the sweep, we test again for a floor at inStepForwardTest1648// and check if the normal is valid there.1649RVec3 test_position = up_position;1650MoveShape(test_position, inStepForwardTest / inDeltaTime, inDeltaTime, nullptr, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);1651float test_horizontal_movement_sq = Vec3(test_position - up_position).LengthSq();1652if (test_horizontal_movement_sq <= horizontal_movement_sq + 1.0e-8f)1653return false; // We didn't move any further than in the previous test16541655#ifdef JPH_DEBUG_RENDERER1656// Draw 2nd sweep horizontal1657if (sDrawWalkStairs)1658DebugRenderer::sInstance->DrawArrow(up_position, test_position, Color::sCyan, 0.01f);1659#endif // JPH_DEBUG_RENDERER16601661// Then sweep down1662Contact test_contact;1663if (!GetFirstContactForSweep(test_position, down, test_contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))1664return false;16651666#ifdef JPH_DEBUG_RENDERER1667// Draw 2nd sweep down1668if (sDrawWalkStairs)1669{1670RVec3 debug_pos = test_position + test_contact.mFraction * down;1671DebugRenderer::sInstance->DrawArrow(test_position, debug_pos, Color::sCyan, 0.01f);1672DebugRenderer::sInstance->DrawArrow(test_contact.mPosition, test_contact.mPosition + test_contact.mSurfaceNormal, Color::sCyan, 0.01f);1673mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(debug_pos, mRotation, mShape), Vec3::sOne(), Color::sCyan, false, true);1674}1675#endif // JPH_DEBUG_RENDERER16761677if (IsSlopeTooSteep(test_contact.mSurfaceNormal))1678return false;1679}16801681// Calculate new down position1682down *= contact.mFraction;1683new_position += down;16841685// Move the character to the new location1686MoveToContact(new_position, contact, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);16871688// 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 steep1689mGroundState = EGroundState::OnGround;16901691return true;1692}16931694bool CharacterVirtual::StickToFloor(Vec3Arg inStepDown, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)1695{1696StartTrackingContactChanges();1697JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });16981699// Try to find the floor1700Contact contact;1701IgnoredContactList dummy_ignored_contacts(inAllocator);1702if (!GetFirstContactForSweep(mPosition, inStepDown, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))1703return false; // If no floor found, don't update our position17041705// Calculate new position1706RVec3 new_position = mPosition + contact.mFraction * inStepDown;17071708#ifdef JPH_DEBUG_RENDERER1709// Draw sweep down1710if (sDrawStickToFloor)1711{1712DebugRenderer::sInstance->DrawArrow(mPosition, new_position, Color::sOrange, 0.01f);1713mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(new_position, mRotation, mShape), Vec3::sOne(), Color::sOrange, false, true);1714}1715#endif // JPH_DEBUG_RENDERER17161717// Move the character to the new location1718MoveToContact(new_position, contact, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);1719return true;1720}17211722void CharacterVirtual::ExtendedUpdate(float inDeltaTime, Vec3Arg inGravity, const ExtendedUpdateSettings &inSettings, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)1723{1724StartTrackingContactChanges();1725JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });17261727// Update the velocity1728Vec3 desired_velocity = mLinearVelocity;1729mLinearVelocity = CancelVelocityTowardsSteepSlopes(desired_velocity);17301731// Remember old position1732RVec3 old_position = mPosition;17331734// Track if on ground before the update1735bool ground_to_air = IsSupported();17361737// Update the character position (instant, do not have to wait for physics update)1738Update(inDeltaTime, inGravity, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);17391740// ... and that we got into air after1741if (IsSupported())1742ground_to_air = false;17431744// If stick to floor enabled and we're going from supported to not supported1745if (ground_to_air && !inSettings.mStickToFloorStepDown.IsNearZero())1746{1747// If we're not moving up, stick to the floor1748float velocity = Vec3(mPosition - old_position).Dot(mUp) / inDeltaTime;1749if (velocity <= 1.0e-6f)1750StickToFloor(inSettings.mStickToFloorStepDown, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);1751}17521753// If walk stairs enabled1754if (!inSettings.mWalkStairsStepUp.IsNearZero())1755{1756// Calculate how much we wanted to move horizontally1757Vec3 desired_horizontal_step = desired_velocity * inDeltaTime;1758desired_horizontal_step -= desired_horizontal_step.Dot(mUp) * mUp;1759float desired_horizontal_step_len = desired_horizontal_step.Length();1760if (desired_horizontal_step_len > 0.0f)1761{1762// Calculate how much we moved horizontally1763Vec3 achieved_horizontal_step = Vec3(mPosition - old_position);1764achieved_horizontal_step -= achieved_horizontal_step.Dot(mUp) * mUp;17651766// Only count movement in the direction of the desired movement1767// (otherwise we find it ok if we're sliding downhill while we're trying to climb uphill)1768Vec3 step_forward_normalized = desired_horizontal_step / desired_horizontal_step_len;1769achieved_horizontal_step = max(0.0f, achieved_horizontal_step.Dot(step_forward_normalized)) * step_forward_normalized;1770float achieved_horizontal_step_len = achieved_horizontal_step.Length();17711772// If we didn't move as far as we wanted and we're against a slope that's too steep1773if (achieved_horizontal_step_len + 1.0e-4f < desired_horizontal_step_len1774&& CanWalkStairs(desired_velocity))1775{1776// Calculate how much we should step forward1777// Note that we clamp the step forward to a minimum distance. This is done because at very high frame rates the delta time1778// may be very small, causing a very small step forward. If the step becomes small enough, we may not move far enough1779// horizontally to actually end up at the top of the step.1780Vec3 step_forward = step_forward_normalized * max(inSettings.mWalkStairsMinStepForward, desired_horizontal_step_len - achieved_horizontal_step_len);17811782// Calculate how far to scan ahead for a floor. This is only used in case the floor normal at step_forward is too steep.1783// In that case an additional check will be performed at this distance to check if that normal is not too steep.1784// Start with the ground normal in the horizontal plane and normalizing it1785Vec3 step_forward_test = -mGroundNormal;1786step_forward_test -= step_forward_test.Dot(mUp) * mUp;1787step_forward_test = step_forward_test.NormalizedOr(step_forward_normalized);17881789// 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 normal1790// to do our forward test1791if (step_forward_test.Dot(step_forward_normalized) < inSettings.mWalkStairsCosAngleForwardContact)1792step_forward_test = step_forward_normalized;17931794// Calculate the correct magnitude for the test vector1795step_forward_test *= inSettings.mWalkStairsStepForwardTest;17961797WalkStairs(inDeltaTime, inSettings.mWalkStairsStepUp, step_forward, step_forward_test, inSettings.mWalkStairsStepDownExtra, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);1798}1799}1800}1801}18021803void CharacterVirtual::ContactKey::SaveState(StateRecorder &inStream) const1804{1805inStream.Write(mBodyB);1806inStream.Write(mCharacterIDB);1807inStream.Write(mSubShapeIDB);1808}18091810void CharacterVirtual::ContactKey::RestoreState(StateRecorder &inStream)1811{1812inStream.Read(mBodyB);1813inStream.Read(mCharacterIDB);1814inStream.Read(mSubShapeIDB);1815}18161817void CharacterVirtual::Contact::SaveState(StateRecorder &inStream) const1818{1819ContactKey::SaveState(inStream);18201821inStream.Write(mPosition);1822inStream.Write(mLinearVelocity);1823inStream.Write(mContactNormal);1824inStream.Write(mSurfaceNormal);1825inStream.Write(mDistance);1826inStream.Write(mFraction);1827inStream.Write(mMotionTypeB);1828inStream.Write(mIsSensorB);1829inStream.Write(mHadCollision);1830inStream.Write(mWasDiscarded);1831inStream.Write(mCanPushCharacter);1832// Cannot store pointers to character B, user data and material1833}18341835void CharacterVirtual::Contact::RestoreState(StateRecorder &inStream)1836{1837ContactKey::RestoreState(inStream);18381839inStream.Read(mPosition);1840inStream.Read(mLinearVelocity);1841inStream.Read(mContactNormal);1842inStream.Read(mSurfaceNormal);1843inStream.Read(mDistance);1844inStream.Read(mFraction);1845inStream.Read(mMotionTypeB);1846inStream.Read(mIsSensorB);1847inStream.Read(mHadCollision);1848inStream.Read(mWasDiscarded);1849inStream.Read(mCanPushCharacter);1850mCharacterB = nullptr; // Cannot restore character B1851mUserData = 0; // Cannot restore user data1852mMaterial = PhysicsMaterial::sDefault; // Cannot restore material1853}18541855void CharacterVirtual::SaveState(StateRecorder &inStream) const1856{1857CharacterBase::SaveState(inStream);18581859inStream.Write(mPosition);1860inStream.Write(mRotation);1861inStream.Write(mLinearVelocity);1862inStream.Write(mLastDeltaTime);1863inStream.Write(mMaxHitsExceeded);18641865// Store contacts that had collision, we're using it at the beginning of the step in CancelVelocityTowardsSteepSlopes1866uint32 num_contacts = 0;1867for (const Contact &c : mActiveContacts)1868if (c.mHadCollision)1869++num_contacts;1870inStream.Write(num_contacts);1871for (const Contact &c : mActiveContacts)1872if (c.mHadCollision)1873c.SaveState(inStream);1874}18751876void CharacterVirtual::RestoreState(StateRecorder &inStream)1877{1878CharacterBase::RestoreState(inStream);18791880inStream.Read(mPosition);1881inStream.Read(mRotation);1882inStream.Read(mLinearVelocity);1883inStream.Read(mLastDeltaTime);1884inStream.Read(mMaxHitsExceeded);18851886// When validating remove contacts that don't have collision since we didn't save them1887if (inStream.IsValidating())1888for (int i = (int)mActiveContacts.size() - 1; i >= 0; --i)1889if (!mActiveContacts[i].mHadCollision)1890mActiveContacts.erase(mActiveContacts.begin() + i);18911892uint32 num_contacts = (uint32)mActiveContacts.size();1893inStream.Read(num_contacts);1894mActiveContacts.resize(num_contacts);1895for (Contact &c : mActiveContacts)1896c.RestoreState(inStream);1897}18981899CharacterVirtualSettings CharacterVirtual::GetCharacterVirtualSettings() const1900{1901CharacterVirtualSettings settings;1902settings.mUp = mUp;1903settings.mSupportingVolume = mSupportingVolume;1904settings.mMaxSlopeAngle = ACos(mCosMaxSlopeAngle);1905settings.mEnhancedInternalEdgeRemoval = mEnhancedInternalEdgeRemoval;1906settings.mShape = mShape;1907settings.mID = mID;1908settings.mMass = mMass;1909settings.mMaxStrength = mMaxStrength;1910settings.mShapeOffset = mShapeOffset;1911settings.mBackFaceMode = mBackFaceMode;1912settings.mPredictiveContactDistance = mPredictiveContactDistance;1913settings.mMaxCollisionIterations = mMaxCollisionIterations;1914settings.mMaxConstraintIterations = mMaxConstraintIterations;1915settings.mMinTimeRemaining = mMinTimeRemaining;1916settings.mCollisionTolerance = mCollisionTolerance;1917settings.mCharacterPadding = mCharacterPadding;1918settings.mMaxNumHits = mMaxNumHits;1919settings.mHitReductionCosMaxAngle = mHitReductionCosMaxAngle;1920settings.mPenetrationRecoverySpeed = mPenetrationRecoverySpeed;1921BodyLockRead lock(mSystem->GetBodyLockInterface(), mInnerBodyID);1922if (lock.Succeeded())1923{1924const Body &body = lock.GetBody();1925settings.mInnerBodyShape = body.GetShape();1926settings.mInnerBodyIDOverride = body.GetID();1927settings.mInnerBodyLayer = body.GetObjectLayer();1928}1929return settings;1930}19311932JPH_NAMESPACE_END193319341935