| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933 |
- // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
- // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
- // SPDX-License-Identifier: MIT
- #include <Jolt/Jolt.h>
- #include <Jolt/Physics/Character/CharacterVirtual.h>
- #include <Jolt/Physics/Body/Body.h>
- #include <Jolt/Physics/Body/BodyCreationSettings.h>
- #include <Jolt/Physics/PhysicsSystem.h>
- #include <Jolt/Physics/Collision/ShapeCast.h>
- #include <Jolt/Physics/Collision/CollideShape.h>
- #include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
- #include <Jolt/Physics/Collision/Shape/ScaledShape.h>
- #include <Jolt/Physics/Collision/Shape/CompoundShape.h>
- #include <Jolt/Physics/Collision/CollisionDispatch.h>
- #include <Jolt/Core/QuickSort.h>
- #include <Jolt/Core/ScopeExit.h>
- #include <Jolt/Geometry/ConvexSupport.h>
- #include <Jolt/Geometry/GJKClosestPoint.h>
- #include <Jolt/Geometry/RayAABox.h>
- #ifdef JPH_DEBUG_RENDERER
- #include <Jolt/Renderer/DebugRenderer.h>
- #endif // JPH_DEBUG_RENDERER
- JPH_NAMESPACE_BEGIN
- void CharacterVsCharacterCollisionSimple::Remove(const CharacterVirtual *inCharacter)
- {
- Array<CharacterVirtual *>::iterator i = std::find(mCharacters.begin(), mCharacters.end(), inCharacter);
- if (i != mCharacters.end())
- mCharacters.erase(i);
- }
- void CharacterVsCharacterCollisionSimple::CollideCharacter(const CharacterVirtual *inCharacter, RMat44Arg inCenterOfMassTransform, const CollideShapeSettings &inCollideShapeSettings, RVec3Arg inBaseOffset, CollideShapeCollector &ioCollector) const
- {
- // Make shape 1 relative to inBaseOffset
- Mat44 transform1 = inCenterOfMassTransform.PostTranslated(-inBaseOffset).ToMat44();
- const Shape *shape1 = inCharacter->GetShape();
- CollideShapeSettings settings = inCollideShapeSettings;
- // Get bounds for character
- AABox bounds1 = shape1->GetWorldSpaceBounds(transform1, Vec3::sOne());
- // Iterate over all characters
- for (const CharacterVirtual *c : mCharacters)
- if (c != inCharacter
- && !ioCollector.ShouldEarlyOut())
- {
- // Make shape 2 relative to inBaseOffset
- Mat44 transform2 = c->GetCenterOfMassTransform().PostTranslated(-inBaseOffset).ToMat44();
- // We need to add the padding of character 2 so that we will detect collision with its outer shell
- settings.mMaxSeparationDistance = inCollideShapeSettings.mMaxSeparationDistance + c->GetCharacterPadding();
- // Check if the bounding boxes of the characters overlap
- const Shape *shape2 = c->GetShape();
- AABox bounds2 = shape2->GetWorldSpaceBounds(transform2, Vec3::sOne());
- bounds2.ExpandBy(Vec3::sReplicate(settings.mMaxSeparationDistance));
- if (!bounds1.Overlaps(bounds2))
- continue;
- // Collector needs to know which character we're colliding with
- ioCollector.SetUserData(reinterpret_cast<uint64>(c));
- // Note that this collides against the character's shape without padding, this will be corrected for in CharacterVirtual::GetContactsAtPosition
- CollisionDispatch::sCollideShapeVsShape(shape1, shape2, Vec3::sOne(), Vec3::sOne(), transform1, transform2, SubShapeIDCreator(), SubShapeIDCreator(), settings, ioCollector);
- }
- // Reset the user data
- ioCollector.SetUserData(0);
- }
- void CharacterVsCharacterCollisionSimple::CastCharacter(const CharacterVirtual *inCharacter, RMat44Arg inCenterOfMassTransform, Vec3Arg inDirection, const ShapeCastSettings &inShapeCastSettings, RVec3Arg inBaseOffset, CastShapeCollector &ioCollector) const
- {
- // Convert shape cast relative to inBaseOffset
- Mat44 transform1 = inCenterOfMassTransform.PostTranslated(-inBaseOffset).ToMat44();
- ShapeCast shape_cast(inCharacter->GetShape(), Vec3::sOne(), transform1, inDirection);
- // Get world space bounds of the character in the form of center and extent
- Vec3 origin = shape_cast.mShapeWorldBounds.GetCenter();
- Vec3 extents = shape_cast.mShapeWorldBounds.GetExtent();
- // Iterate over all characters
- for (const CharacterVirtual *c : mCharacters)
- if (c != inCharacter
- && !ioCollector.ShouldEarlyOut())
- {
- // Make shape 2 relative to inBaseOffset
- Mat44 transform2 = c->GetCenterOfMassTransform().PostTranslated(-inBaseOffset).ToMat44();
- // Sweep bounding box of the character against the bounding box of the other character to see if they can collide
- const Shape *shape2 = c->GetShape();
- AABox bounds2 = shape2->GetWorldSpaceBounds(transform2, Vec3::sOne());
- bounds2.ExpandBy(extents);
- if (!RayAABoxHits(origin, inDirection, bounds2.mMin, bounds2.mMax))
- continue;
- // Collector needs to know which character we're colliding with
- ioCollector.SetUserData(reinterpret_cast<uint64>(c));
- // Note that this collides against the character's shape without padding, this will be corrected for in CharacterVirtual::GetFirstContactForSweep
- CollisionDispatch::sCastShapeVsShapeWorldSpace(shape_cast, inShapeCastSettings, shape2, Vec3::sOne(), { }, transform2, SubShapeIDCreator(), SubShapeIDCreator(), ioCollector);
- }
- // Reset the user data
- ioCollector.SetUserData(0);
- }
- CharacterVirtual::CharacterVirtual(const CharacterVirtualSettings *inSettings, RVec3Arg inPosition, QuatArg inRotation, uint64 inUserData, PhysicsSystem *inSystem) :
- CharacterBase(inSettings, inSystem),
- mID(inSettings->mID),
- mBackFaceMode(inSettings->mBackFaceMode),
- mPredictiveContactDistance(inSettings->mPredictiveContactDistance),
- mMaxCollisionIterations(inSettings->mMaxCollisionIterations),
- mMaxConstraintIterations(inSettings->mMaxConstraintIterations),
- mMinTimeRemaining(inSettings->mMinTimeRemaining),
- mCollisionTolerance(inSettings->mCollisionTolerance),
- mCharacterPadding(inSettings->mCharacterPadding),
- mMaxNumHits(inSettings->mMaxNumHits),
- mHitReductionCosMaxAngle(inSettings->mHitReductionCosMaxAngle),
- mPenetrationRecoverySpeed(inSettings->mPenetrationRecoverySpeed),
- mEnhancedInternalEdgeRemoval(inSettings->mEnhancedInternalEdgeRemoval),
- mShapeOffset(inSettings->mShapeOffset),
- mPosition(inPosition),
- mRotation(inRotation),
- mUserData(inUserData)
- {
- JPH_ASSERT(!mID.IsInvalid());
- // Copy settings
- SetMaxStrength(inSettings->mMaxStrength);
- SetMass(inSettings->mMass);
- // Create an inner rigid body if requested
- if (inSettings->mInnerBodyShape != nullptr)
- {
- BodyCreationSettings settings(inSettings->mInnerBodyShape, GetInnerBodyPosition(), mRotation, EMotionType::Kinematic, inSettings->mInnerBodyLayer);
- settings.mAllowSleeping = false; // Disable sleeping so that we will receive sensor callbacks
- settings.mUserData = inUserData;
- const Body *inner_body;
- BodyInterface &bi = inSystem->GetBodyInterface();
- if (inSettings->mInnerBodyIDOverride.IsInvalid())
- inner_body = bi.CreateBody(settings);
- else
- inner_body = bi.CreateBodyWithID(inSettings->mInnerBodyIDOverride, settings);
- if (inner_body != nullptr)
- {
- mInnerBodyID = inner_body->GetID();
- bi.AddBody(mInnerBodyID, EActivation::Activate);
- }
- }
- }
- CharacterVirtual::~CharacterVirtual()
- {
- if (!mInnerBodyID.IsInvalid())
- {
- mSystem->GetBodyInterface().RemoveBody(mInnerBodyID);
- mSystem->GetBodyInterface().DestroyBody(mInnerBodyID);
- }
- }
- void CharacterVirtual::UpdateInnerBodyTransform()
- {
- if (!mInnerBodyID.IsInvalid())
- mSystem->GetBodyInterface().SetPositionAndRotation(mInnerBodyID, GetInnerBodyPosition(), mRotation, EActivation::DontActivate);
- }
- void CharacterVirtual::GetAdjustedBodyVelocity(const Body& inBody, Vec3 &outLinearVelocity, Vec3 &outAngularVelocity) const
- {
- // Get real velocity of body
- if (!inBody.IsStatic())
- {
- const MotionProperties *mp = inBody.GetMotionPropertiesUnchecked();
- outLinearVelocity = mp->GetLinearVelocity();
- outAngularVelocity = mp->GetAngularVelocity();
- }
- else
- {
- outLinearVelocity = outAngularVelocity = Vec3::sZero();
- }
- // Allow application to override
- if (mListener != nullptr)
- mListener->OnAdjustBodyVelocity(this, inBody, outLinearVelocity, outAngularVelocity);
- }
- Vec3 CharacterVirtual::CalculateCharacterGroundVelocity(RVec3Arg inCenterOfMass, Vec3Arg inLinearVelocity, Vec3Arg inAngularVelocity, float inDeltaTime) const
- {
- // Get angular velocity
- float angular_velocity_len_sq = inAngularVelocity.LengthSq();
- if (angular_velocity_len_sq < 1.0e-12f)
- return inLinearVelocity;
- float angular_velocity_len = sqrt(angular_velocity_len_sq);
- // Calculate the rotation that the object will make in the time step
- Quat rotation = Quat::sRotation(inAngularVelocity / angular_velocity_len, angular_velocity_len * inDeltaTime);
- // Calculate where the new character position will be
- RVec3 new_position = inCenterOfMass + rotation * Vec3(mPosition - inCenterOfMass);
- // Calculate the velocity
- return inLinearVelocity + Vec3(new_position - mPosition) / inDeltaTime;
- }
- template <class taCollector>
- void CharacterVirtual::sFillContactProperties(const CharacterVirtual *inCharacter, Contact &outContact, const Body &inBody, Vec3Arg inUp, RVec3Arg inBaseOffset, const taCollector &inCollector, const CollideShapeResult &inResult)
- {
- // Get adjusted body velocity
- Vec3 linear_velocity, angular_velocity;
- inCharacter->GetAdjustedBodyVelocity(inBody, linear_velocity, angular_velocity);
- outContact.mPosition = inBaseOffset + inResult.mContactPointOn2;
- outContact.mLinearVelocity = linear_velocity + angular_velocity.Cross(Vec3(outContact.mPosition - inBody.GetCenterOfMassPosition())); // Calculate point velocity
- outContact.mContactNormal = -inResult.mPenetrationAxis.NormalizedOr(Vec3::sZero());
- outContact.mSurfaceNormal = inCollector.GetContext()->GetWorldSpaceSurfaceNormal(inResult.mSubShapeID2, outContact.mPosition);
- if (outContact.mContactNormal.Dot(outContact.mSurfaceNormal) < 0.0f)
- outContact.mSurfaceNormal = -outContact.mSurfaceNormal; // Flip surface normal if we're hitting a back face
- if (outContact.mContactNormal.Dot(inUp) > outContact.mSurfaceNormal.Dot(inUp))
- outContact.mSurfaceNormal = outContact.mContactNormal; // Replace surface normal with contact normal if the contact normal is pointing more upwards
- outContact.mDistance = -inResult.mPenetrationDepth;
- outContact.mBodyB = inResult.mBodyID2;
- outContact.mSubShapeIDB = inResult.mSubShapeID2;
- outContact.mMotionTypeB = inBody.GetMotionType();
- outContact.mIsSensorB = inBody.IsSensor();
- outContact.mUserData = inBody.GetUserData();
- outContact.mMaterial = inCollector.GetContext()->GetMaterial(inResult.mSubShapeID2);
- }
- void CharacterVirtual::sFillCharacterContactProperties(Contact &outContact, const CharacterVirtual *inOtherCharacter, RVec3Arg inBaseOffset, const CollideShapeResult &inResult)
- {
- outContact.mPosition = inBaseOffset + inResult.mContactPointOn2;
- outContact.mLinearVelocity = inOtherCharacter->GetLinearVelocity();
- outContact.mSurfaceNormal = outContact.mContactNormal = -inResult.mPenetrationAxis.NormalizedOr(Vec3::sZero());
- outContact.mDistance = -inResult.mPenetrationDepth;
- outContact.mCharacterIDB = inOtherCharacter->GetID();
- outContact.mCharacterB = inOtherCharacter;
- outContact.mSubShapeIDB = inResult.mSubShapeID2;
- outContact.mMotionTypeB = EMotionType::Kinematic; // Other character is kinematic, we can't directly move it
- outContact.mIsSensorB = false;
- outContact.mUserData = inOtherCharacter->GetUserData();
- outContact.mMaterial = PhysicsMaterial::sDefault;
- }
- void CharacterVirtual::ContactCollector::AddHit(const CollideShapeResult &inResult)
- {
- // If we exceed our contact limit, try to clean up near-duplicate contacts
- if (mContacts.size() == mMaxHits)
- {
- // Flag that we hit this code path
- mMaxHitsExceeded = true;
- // Check if we can do reduction
- if (mHitReductionCosMaxAngle > -1.0f)
- {
- // Loop all contacts and find similar contacts
- for (int i = (int)mContacts.size() - 1; i >= 0; --i)
- {
- Contact &contact_i = mContacts[i];
- for (int j = i - 1; j >= 0; --j)
- {
- Contact &contact_j = mContacts[j];
- if (contact_i.IsSameBody(contact_j)
- && contact_i.mContactNormal.Dot(contact_j.mContactNormal) > mHitReductionCosMaxAngle) // Very similar contact normals
- {
- // Remove the contact with the biggest distance
- bool i_is_last = i == (int)mContacts.size() - 1;
- if (contact_i.mDistance > contact_j.mDistance)
- {
- // Remove i
- if (!i_is_last)
- contact_i = mContacts.back();
- mContacts.pop_back();
- // Break out of the loop, i is now an element that we already processed
- break;
- }
- else
- {
- // Remove j
- contact_j = mContacts.back();
- mContacts.pop_back();
- // If i was the last element, we just moved it into position j. Break out of the loop, we'll see it again later.
- if (i_is_last)
- break;
- }
- }
- }
- }
- }
- if (mContacts.size() == mMaxHits)
- {
- // There are still too many hits, give up!
- ForceEarlyOut();
- return;
- }
- }
- if (inResult.mBodyID2.IsInvalid())
- {
- // Assuming this is a hit against another character
- JPH_ASSERT(mOtherCharacter != nullptr);
- // Create contact with other character
- mContacts.emplace_back();
- Contact &contact = mContacts.back();
- sFillCharacterContactProperties(contact, mOtherCharacter, mBaseOffset, inResult);
- contact.mFraction = 0.0f;
- }
- else
- {
- // Create contact with other body
- BodyLockRead lock(mSystem->GetBodyLockInterface(), inResult.mBodyID2);
- if (lock.SucceededAndIsInBroadPhase())
- {
- mContacts.emplace_back();
- Contact &contact = mContacts.back();
- sFillContactProperties(mCharacter, contact, lock.GetBody(), mUp, mBaseOffset, *this, inResult);
- contact.mFraction = 0.0f;
- }
- }
- }
- void CharacterVirtual::ContactCastCollector::AddHit(const ShapeCastResult &inResult)
- {
- if (inResult.mFraction < mContact.mFraction // Since we're doing checks against the world and against characters, we may get a hit with a higher fraction than the previous hit
- && inResult.mFraction > 0.0f // Ignore collisions at fraction = 0
- && inResult.mPenetrationAxis.Dot(mDisplacement) > 0.0f) // Ignore penetrations that we're moving away from
- {
- // Test if this contact should be ignored
- for (const ContactKey &c : mIgnoredContacts)
- if (c.mBodyB == inResult.mBodyID2 && c.mSubShapeIDB == inResult.mSubShapeID2)
- return;
- Contact contact;
- if (inResult.mBodyID2.IsInvalid())
- {
- // Assuming this is a hit against another character
- JPH_ASSERT(mOtherCharacter != nullptr);
- // Create contact with other character
- sFillCharacterContactProperties(contact, mOtherCharacter, mBaseOffset, inResult);
- }
- else
- {
- // Lock body only while we fetch contact properties
- BodyLockRead lock(mSystem->GetBodyLockInterface(), inResult.mBodyID2);
- if (!lock.SucceededAndIsInBroadPhase())
- return;
- // Sweeps don't result in OnContactAdded callbacks so we can ignore sensors here
- const Body &body = lock.GetBody();
- if (body.IsSensor())
- return;
- // Convert the hit result into a contact
- sFillContactProperties(mCharacter, contact, body, mUp, mBaseOffset, *this, inResult);
- }
- contact.mFraction = inResult.mFraction;
- // Check if the contact that will make us penetrate more than the allowed tolerance
- if (contact.mDistance + contact.mContactNormal.Dot(mDisplacement) < -mCharacter->mCollisionTolerance
- && mCharacter->ValidateContact(contact))
- {
- mContact = contact;
- UpdateEarlyOutFraction(contact.mFraction);
- }
- }
- }
- void CharacterVirtual::CheckCollision(RVec3Arg inPosition, QuatArg inRotation, Vec3Arg inMovementDirection, float inMaxSeparationDistance, const Shape *inShape, RVec3Arg inBaseOffset, CollideShapeCollector &ioCollector, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) const
- {
- // Query shape transform
- RMat44 transform = GetCenterOfMassTransform(inPosition, inRotation, inShape);
- // Settings for collide shape
- CollideShapeSettings settings;
- settings.mBackFaceMode = mBackFaceMode;
- settings.mActiveEdgeMovementDirection = inMovementDirection;
- settings.mMaxSeparationDistance = mCharacterPadding + inMaxSeparationDistance;
- settings.mActiveEdgeMode = EActiveEdgeMode::CollideOnlyWithActive;
- // Body filter
- IgnoreSingleBodyFilterChained body_filter(mInnerBodyID, inBodyFilter);
- // Select the right function
- auto collide_shape_function = mEnhancedInternalEdgeRemoval? &NarrowPhaseQuery::CollideShapeWithInternalEdgeRemoval : &NarrowPhaseQuery::CollideShape;
- // Collide shape
- (mSystem->GetNarrowPhaseQuery().*collide_shape_function)(inShape, Vec3::sOne(), transform, settings, inBaseOffset, ioCollector, inBroadPhaseLayerFilter, inObjectLayerFilter, body_filter, inShapeFilter);
- // Also collide with other characters
- if (mCharacterVsCharacterCollision != nullptr)
- {
- ioCollector.SetContext(nullptr); // We're no longer colliding with a transformed shape, reset
- mCharacterVsCharacterCollision->CollideCharacter(this, transform, settings, inBaseOffset, ioCollector);
- }
- }
- void CharacterVirtual::GetContactsAtPosition(RVec3Arg inPosition, Vec3Arg inMovementDirection, const Shape *inShape, TempContactList &outContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) const
- {
- // Remove previous results
- outContacts.clear();
- // Body filter
- IgnoreSingleBodyFilterChained body_filter(mInnerBodyID, inBodyFilter);
- // Collide shape
- ContactCollector collector(mSystem, this, mMaxNumHits, mHitReductionCosMaxAngle, mUp, mPosition, outContacts);
- CheckCollision(inPosition, mRotation, inMovementDirection, mPredictiveContactDistance, inShape, mPosition, collector, inBroadPhaseLayerFilter, inObjectLayerFilter, body_filter, inShapeFilter);
- // 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.
- // Therefore we need to sort the contacts to preserve determinism. Note that currently this will fail if we exceed mMaxNumHits hits.
- QuickSort(outContacts.begin(), outContacts.end(), ContactOrderingPredicate());
- // Flag if we exceeded the max number of hits
- mMaxHitsExceeded = collector.mMaxHitsExceeded;
- // Reduce distance to contact by padding to ensure we stay away from the object by a little margin
- // (this will make collision detection cheaper - especially for sweep tests as they won't hit the surface if we're properly sliding)
- for (Contact &c : outContacts)
- {
- c.mDistance -= mCharacterPadding;
- if (c.mCharacterB != nullptr)
- c.mDistance -= c.mCharacterB->mCharacterPadding;
- }
- }
- void CharacterVirtual::RemoveConflictingContacts(TempContactList &ioContacts, IgnoredContactList &outIgnoredContacts) const
- {
- // 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)
- // We do need to account for padding (see GetContactsAtPosition) that is removed from the contact distances, to compensate we add it to the cMinRequiredPenetration
- const float cMinRequiredPenetration = 1.25f * mCharacterPadding;
- // Discard conflicting penetrating contacts
- for (size_t c1 = 0; c1 < ioContacts.size(); c1++)
- {
- Contact &contact1 = ioContacts[c1];
- if (contact1.mDistance <= -cMinRequiredPenetration) // Only for penetrations
- for (size_t c2 = c1 + 1; c2 < ioContacts.size(); c2++)
- {
- Contact &contact2 = ioContacts[c2];
- if (contact1.IsSameBody(contact2)
- && contact2.mDistance <= -cMinRequiredPenetration // Only for penetrations
- && contact1.mContactNormal.Dot(contact2.mContactNormal) < 0.0f) // Only opposing normals
- {
- // Discard contacts with the least amount of penetration
- if (contact1.mDistance < contact2.mDistance)
- {
- // Discard the 2nd contact
- outIgnoredContacts.emplace_back(contact2);
- ioContacts.erase(ioContacts.begin() + c2);
- c2--;
- }
- else
- {
- // Discard the first contact
- outIgnoredContacts.emplace_back(contact1);
- ioContacts.erase(ioContacts.begin() + c1);
- c1--;
- break;
- }
- }
- }
- }
- }
- bool CharacterVirtual::ValidateContact(const Contact &inContact) const
- {
- if (mListener == nullptr)
- return true;
- if (inContact.mCharacterB != nullptr)
- return mListener->OnCharacterContactValidate(this, inContact.mCharacterB, inContact.mSubShapeIDB);
- else
- return mListener->OnContactValidate(this, inContact.mBodyB, inContact.mSubShapeIDB);
- }
- void CharacterVirtual::ContactAdded(const Contact &inContact, CharacterContactSettings &ioSettings)
- {
- if (mListener != nullptr)
- {
- // Check if we already know this contact
- ListenerContacts::iterator it = mListenerContacts.find(inContact);
- if (it != mListenerContacts.end())
- {
- // Max 1 contact persisted callback
- if (++it->second.mCount == 1)
- {
- if (inContact.mCharacterB != nullptr)
- mListener->OnCharacterContactPersisted(this, inContact.mCharacterB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);
- else
- mListener->OnContactPersisted(this, inContact.mBodyB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);
- it->second.mSettings = ioSettings;
- }
- else
- {
- // Reuse the settings from the last call
- ioSettings = it->second.mSettings;
- }
- }
- else
- {
- // New contact
- if (inContact.mCharacterB != nullptr)
- mListener->OnCharacterContactAdded(this, inContact.mCharacterB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);
- else
- mListener->OnContactAdded(this, inContact.mBodyB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);
- mListenerContacts.insert(ListenerContacts::value_type(inContact, ioSettings));
- }
- }
- }
- template <class T>
- inline static bool sCorrectFractionForCharacterPadding(const Shape *inShape, Mat44Arg inStart, Vec3Arg inDisplacement, Vec3Arg inScale, const T &inPolygon, float &ioFraction)
- {
- if (inShape->GetType() == EShapeType::Convex)
- {
- // Get the support function for the shape we're casting
- const ConvexShape *convex_shape = static_cast<const ConvexShape *>(inShape);
- ConvexShape::SupportBuffer buffer;
- const ConvexShape::Support *support = convex_shape->GetSupportFunction(ConvexShape::ESupportMode::IncludeConvexRadius, buffer, inScale);
- // Cast the shape against the polygon
- GJKClosestPoint gjk;
- return gjk.CastShape(inStart, inDisplacement, cDefaultCollisionTolerance, *support, inPolygon, ioFraction);
- }
- else if (inShape->GetSubType() == EShapeSubType::RotatedTranslated)
- {
- const RotatedTranslatedShape *rt_shape = static_cast<const RotatedTranslatedShape *>(inShape);
- return sCorrectFractionForCharacterPadding(rt_shape->GetInnerShape(), inStart * Mat44::sRotation(rt_shape->GetRotation()), inDisplacement, rt_shape->TransformScale(inScale), inPolygon, ioFraction);
- }
- else if (inShape->GetSubType() == EShapeSubType::Scaled)
- {
- const ScaledShape *scaled_shape = static_cast<const ScaledShape *>(inShape);
- return sCorrectFractionForCharacterPadding(scaled_shape->GetInnerShape(), inStart, inDisplacement, inScale * scaled_shape->GetScale(), inPolygon, ioFraction);
- }
- else if (inShape->GetType() == EShapeType::Compound)
- {
- const CompoundShape *compound = static_cast<const CompoundShape *>(inShape);
- bool return_value = false;
- for (const CompoundShape::SubShape &sub_shape : compound->GetSubShapes())
- return_value |= sCorrectFractionForCharacterPadding(sub_shape.mShape, inStart * sub_shape.GetLocalTransformNoScale(inScale), inDisplacement, sub_shape.TransformScale(inScale), inPolygon, ioFraction);
- return return_value;
- }
- else
- {
- JPH_ASSERT(false, "Not supported yet!");
- return false;
- }
- }
- bool CharacterVirtual::GetFirstContactForSweep(RVec3Arg inPosition, Vec3Arg inDisplacement, Contact &outContact, const IgnoredContactList &inIgnoredContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) const
- {
- // Too small distance -> skip checking
- float displacement_len_sq = inDisplacement.LengthSq();
- if (displacement_len_sq < 1.0e-8f)
- return false;
- // Calculate start transform
- RMat44 start = GetCenterOfMassTransform(inPosition, mRotation, mShape);
- // Settings for the cast
- ShapeCastSettings settings;
- settings.mBackFaceModeTriangles = mBackFaceMode;
- settings.mBackFaceModeConvex = EBackFaceMode::IgnoreBackFaces;
- settings.mActiveEdgeMode = EActiveEdgeMode::CollideOnlyWithActive;
- settings.mUseShrunkenShapeAndConvexRadius = true;
- settings.mReturnDeepestPoint = false;
- // Calculate how much extra fraction we need to add to the cast to account for the character padding
- float character_padding_fraction = mCharacterPadding / sqrt(displacement_len_sq);
- // Body filter
- IgnoreSingleBodyFilterChained body_filter(mInnerBodyID, inBodyFilter);
- // Cast shape
- Contact contact;
- contact.mFraction = 1.0f + character_padding_fraction;
- RVec3 base_offset = start.GetTranslation();
- ContactCastCollector collector(mSystem, this, inDisplacement, mUp, inIgnoredContacts, base_offset, contact);
- collector.ResetEarlyOutFraction(contact.mFraction);
- RShapeCast shape_cast(mShape, Vec3::sOne(), start, inDisplacement);
- mSystem->GetNarrowPhaseQuery().CastShape(shape_cast, settings, base_offset, collector, inBroadPhaseLayerFilter, inObjectLayerFilter, body_filter, inShapeFilter);
- // Also collide with other characters
- if (mCharacterVsCharacterCollision != nullptr)
- {
- collector.SetContext(nullptr); // We're no longer colliding with a transformed shape, reset
- mCharacterVsCharacterCollision->CastCharacter(this, start, inDisplacement, settings, base_offset, collector);
- }
- if (contact.mBodyB.IsInvalid() && contact.mCharacterIDB.IsInvalid())
- return false;
- // Store contact
- outContact = contact;
- TransformedShape ts;
- float character_padding = mCharacterPadding;
- if (outContact.mCharacterB != nullptr)
- {
- // Create a transformed shape for the character
- RMat44 com = outContact.mCharacterB->GetCenterOfMassTransform();
- ts = TransformedShape(com.GetTranslation(), com.GetQuaternion(), outContact.mCharacterB->GetShape(), BodyID(), SubShapeIDCreator());
- // We need to take the other character's padding into account as well
- character_padding += outContact.mCharacterB->mCharacterPadding;
- }
- else
- {
- // Create a transformed shape for the body
- ts = mSystem->GetBodyInterface().GetTransformedShape(outContact.mBodyB);
- }
- // Fetch the face we're colliding with
- Shape::SupportingFace face;
- ts.GetSupportingFace(outContact.mSubShapeIDB, -outContact.mContactNormal, base_offset, face);
- bool corrected = false;
- if (face.size() >= 2)
- {
- // Inflate the colliding face by the character padding
- PolygonConvexSupport polygon(face);
- AddConvexRadius add_cvx(polygon, character_padding);
- // Correct fraction to hit this inflated face instead of the inner shape
- corrected = sCorrectFractionForCharacterPadding(mShape, start.GetRotation(), inDisplacement, Vec3::sOne(), add_cvx, outContact.mFraction);
- }
- if (!corrected)
- {
- // When there's only a single contact point or when we were unable to correct the fraction,
- // we can just move the fraction back so that the character and its padding don't hit the contact point anymore
- outContact.mFraction = max(0.0f, outContact.mFraction - character_padding_fraction);
- }
- // Ensure that we never return a fraction that's bigger than 1 (which could happen due to float precision issues).
- outContact.mFraction = min(outContact.mFraction, 1.0f);
- return true;
- }
- void CharacterVirtual::DetermineConstraints(TempContactList &inContacts, float inDeltaTime, ConstraintList &outConstraints) const
- {
- for (Contact &c : inContacts)
- {
- Vec3 contact_velocity = c.mLinearVelocity;
- // Penetrating contact: Add a contact velocity that pushes the character out at the desired speed
- if (c.mDistance < 0.0f)
- contact_velocity -= c.mContactNormal * c.mDistance * mPenetrationRecoverySpeed / inDeltaTime;
- // Convert to a constraint
- outConstraints.emplace_back();
- Constraint &constraint = outConstraints.back();
- constraint.mContact = &c;
- constraint.mLinearVelocity = contact_velocity;
- constraint.mPlane = Plane(c.mContactNormal, c.mDistance);
- // Next check if the angle is too steep and if it is add an additional constraint that holds the character back
- if (IsSlopeTooSteep(c.mSurfaceNormal))
- {
- // Only take planes that point up.
- // Note that we use the contact normal to allow for better sliding as the surface normal may be in the opposite direction of movement.
- float dot = c.mContactNormal.Dot(mUp);
- if (dot > 1.0e-3f) // Add a little slack, if the normal is perfectly horizontal we already have our vertical plane.
- {
- // Mark the slope constraint as steep
- constraint.mIsSteepSlope = true;
- // Make horizontal normal
- Vec3 normal = (c.mContactNormal - dot * mUp).Normalized();
- // Create a secondary constraint that blocks horizontal movement
- outConstraints.emplace_back();
- Constraint &vertical_constraint = outConstraints.back();
- vertical_constraint.mContact = &c;
- vertical_constraint.mLinearVelocity = c.mLinearVelocity.Dot(normal) * normal; // Project the contact velocity on the new normal so that both planes push at an equal rate. We ignore velocity added to push characters out of collision as that can get characters stuck if they are surrounded on all sides by steep slopes.
- vertical_constraint.mPlane = Plane(normal, c.mDistance / normal.Dot(c.mContactNormal)); // Calculate the distance we have to travel horizontally to hit the contact plane
- }
- }
- }
- }
- bool CharacterVirtual::HandleContact(Vec3Arg inVelocity, Constraint &ioConstraint, float inDeltaTime)
- {
- Contact &contact = *ioConstraint.mContact;
- // Validate the contact point
- if (!ValidateContact(contact))
- return false;
- // We collided
- contact.mHadCollision = true;
- // Send contact added event
- CharacterContactSettings settings;
- ContactAdded(contact, settings);
- contact.mCanPushCharacter = settings.mCanPushCharacter;
- // We don't have any further interaction with sensors beyond an OnContactAdded notification
- if (contact.mIsSensorB)
- return false;
- // If body B cannot receive an impulse, we're done
- if (!settings.mCanReceiveImpulses || contact.mMotionTypeB != EMotionType::Dynamic)
- return true;
- // Lock the body we're colliding with
- BodyLockWrite lock(mSystem->GetBodyLockInterface(), contact.mBodyB);
- if (!lock.SucceededAndIsInBroadPhase())
- return false; // Body has been removed, we should not collide with it anymore
- const Body &body = lock.GetBody();
- // Calculate the velocity that we want to apply at B so that it will start moving at the character's speed at the contact point
- constexpr float cDamping = 0.9f;
- constexpr float cPenetrationResolution = 0.4f;
- Vec3 relative_velocity = inVelocity - contact.mLinearVelocity;
- float projected_velocity = relative_velocity.Dot(contact.mContactNormal);
- float delta_velocity = -projected_velocity * cDamping - min(contact.mDistance, 0.0f) * cPenetrationResolution / inDeltaTime;
- // Don't apply impulses if we're separating
- if (delta_velocity < 0.0f)
- return true;
- // Determine mass properties of the body we're colliding with
- const MotionProperties *motion_properties = body.GetMotionProperties();
- RVec3 center_of_mass = body.GetCenterOfMassPosition();
- Mat44 inverse_inertia = body.GetInverseInertia();
- float inverse_mass = motion_properties->GetInverseMass();
- // Calculate the inverse of the mass of body B as seen at the contact point in the direction of the contact normal
- Vec3 jacobian = Vec3(contact.mPosition - center_of_mass).Cross(contact.mContactNormal);
- float inv_effective_mass = inverse_inertia.Multiply3x3(jacobian).Dot(jacobian) + inverse_mass;
- // Impulse P = M dv
- float impulse = delta_velocity / inv_effective_mass;
- // Clamp the impulse according to the character strength, character strength is a force in newtons, P = F dt
- float max_impulse = mMaxStrength * inDeltaTime;
- impulse = min(impulse, max_impulse);
- // Calculate the world space impulse to apply
- Vec3 world_impulse = -impulse * contact.mContactNormal;
- // Cancel impulse in down direction (we apply gravity later)
- float impulse_dot_up = world_impulse.Dot(mUp);
- if (impulse_dot_up < 0.0f)
- world_impulse -= impulse_dot_up * mUp;
- // Now apply the impulse (body is already locked so we use the no-lock interface)
- mSystem->GetBodyInterfaceNoLock().AddImpulse(contact.mBodyB, world_impulse, contact.mPosition);
- return true;
- }
- void CharacterVirtual::SolveConstraints(Vec3Arg inVelocity, float inDeltaTime, float inTimeRemaining, ConstraintList &ioConstraints, IgnoredContactList &ioIgnoredContacts, float &outTimeSimulated, Vec3 &outDisplacement, TempAllocator &inAllocator
- #ifdef JPH_DEBUG_RENDERER
- , bool inDrawConstraints
- #endif // JPH_DEBUG_RENDERER
- )
- {
- // If there are no constraints we can immediately move to our target
- if (ioConstraints.empty())
- {
- outDisplacement = inVelocity * inTimeRemaining;
- outTimeSimulated = inTimeRemaining;
- return;
- }
- // Create array that holds the constraints in order of time of impact (sort will happen later)
- Array<Constraint *, STLTempAllocator<Constraint *>> sorted_constraints(inAllocator);
- sorted_constraints.resize(ioConstraints.size());
- for (size_t index = 0; index < sorted_constraints.size(); index++)
- sorted_constraints[index] = &ioConstraints[index];
- // This is the velocity we use for the displacement, if we hit something it will be shortened
- Vec3 velocity = inVelocity;
- // Keep track of the last velocity that was applied to the character so that we can detect when the velocity reverses
- Vec3 last_velocity = inVelocity;
- // Start with no displacement
- outDisplacement = Vec3::sZero();
- outTimeSimulated = 0.0f;
- // These are the contacts that we hit previously without moving a significant distance
- Array<Constraint *, STLTempAllocator<Constraint *>> previous_contacts(inAllocator);
- previous_contacts.resize(mMaxConstraintIterations);
- int num_previous_contacts = 0;
- // Loop for a max amount of iterations
- for (uint iteration = 0; iteration < mMaxConstraintIterations; iteration++)
- {
- // Calculate time of impact for all constraints
- for (Constraint &c : ioConstraints)
- {
- // Project velocity on plane direction
- c.mProjectedVelocity = c.mPlane.GetNormal().Dot(c.mLinearVelocity - velocity);
- if (c.mProjectedVelocity < 1.0e-6f)
- {
- c.mTOI = FLT_MAX;
- }
- else
- {
- // Distance to plane
- float dist = c.mPlane.SignedDistance(outDisplacement);
- if (dist - c.mProjectedVelocity * inTimeRemaining > -1.0e-4f)
- {
- // Too little penetration, accept the movement
- c.mTOI = FLT_MAX;
- }
- else
- {
- // Calculate time of impact
- c.mTOI = max(0.0f, dist / c.mProjectedVelocity);
- }
- }
- }
- // Sort constraints on proximity
- QuickSort(sorted_constraints.begin(), sorted_constraints.end(), [](const Constraint *inLHS, const Constraint *inRHS) {
- // If both constraints hit at t = 0 then order the one that will push the character furthest first
- // Note that because we add velocity to penetrating contacts, this will also resolve contacts that penetrate the most
- if (inLHS->mTOI <= 0.0f && inRHS->mTOI <= 0.0f)
- return inLHS->mProjectedVelocity > inRHS->mProjectedVelocity;
- // Then sort on time of impact
- if (inLHS->mTOI != inRHS->mTOI)
- return inLHS->mTOI < inRHS->mTOI;
- // As a tie breaker sort static first so it has the most influence
- return inLHS->mContact->mMotionTypeB > inRHS->mContact->mMotionTypeB;
- });
- // Find the first valid constraint
- Constraint *constraint = nullptr;
- for (Constraint *c : sorted_constraints)
- {
- // Take the first contact and see if we can reach it
- if (c->mTOI >= inTimeRemaining)
- {
- // We can reach our goal!
- outDisplacement += velocity * inTimeRemaining;
- outTimeSimulated += inTimeRemaining;
- return;
- }
- // Test if this contact was discarded by the contact callback before
- if (c->mContact->mWasDiscarded)
- continue;
- // Handle the contact
- if (!c->mContact->mHadCollision
- && !HandleContact(velocity, *c, inDeltaTime))
- {
- // Constraint should be ignored, remove it from the list
- c->mContact->mWasDiscarded = true;
- // Mark it as ignored for GetFirstContactForSweep
- ioIgnoredContacts.emplace_back(*c->mContact);
- continue;
- }
- // Cancel velocity of constraint if it cannot push the character
- if (!c->mContact->mCanPushCharacter)
- c->mLinearVelocity = Vec3::sZero();
- // We found the first constraint that we want to collide with
- constraint = c;
- break;
- }
- if (constraint == nullptr)
- {
- // All constraints were discarded, we can reach our goal!
- outDisplacement += velocity * inTimeRemaining;
- outTimeSimulated += inTimeRemaining;
- return;
- }
- // Move to the contact
- outDisplacement += velocity * constraint->mTOI;
- inTimeRemaining -= constraint->mTOI;
- outTimeSimulated += constraint->mTOI;
- // If there's not enough time left to be simulated, bail
- if (inTimeRemaining < mMinTimeRemaining)
- return;
- // If we've moved significantly, clear all previous contacts
- if (constraint->mTOI > 1.0e-4f)
- num_previous_contacts = 0;
- // Get the normal of the plane we're hitting
- Vec3 plane_normal = constraint->mPlane.GetNormal();
- // If we're hitting a steep slope we cancel the velocity towards the slope first so that we don't end up sliding up the slope
- // (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)
- if (constraint->mIsSteepSlope)
- {
- // We're hitting a steep slope, create a vertical plane that blocks any further movement up the slope (note: not normalized)
- Vec3 vertical_plane_normal = plane_normal - plane_normal.Dot(mUp) * mUp;
- // Get the relative velocity between the character and the constraint
- Vec3 relative_velocity = velocity - constraint->mLinearVelocity;
- // Remove velocity towards the slope
- velocity = velocity - min(0.0f, relative_velocity.Dot(vertical_plane_normal)) * vertical_plane_normal / vertical_plane_normal.LengthSq();
- }
- // Get the relative velocity between the character and the constraint
- Vec3 relative_velocity = velocity - constraint->mLinearVelocity;
- // Calculate new velocity if we cancel the relative velocity in the normal direction
- Vec3 new_velocity = velocity - relative_velocity.Dot(plane_normal) * plane_normal;
- // Find the normal of the previous contact that we will violate the most if we move in this new direction
- float highest_penetration = 0.0f;
- const Constraint *other_constraint = nullptr;
- for (Constraint **c = previous_contacts.data(); c < previous_contacts.data() + num_previous_contacts; ++c)
- if (*c != constraint)
- {
- // Calculate how much we will penetrate if we move in this direction
- Vec3 other_normal = (*c)->mPlane.GetNormal();
- float penetration = ((*c)->mLinearVelocity - new_velocity).Dot(other_normal);
- if (penetration > highest_penetration)
- {
- // 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.
- float dot = other_normal.Dot(plane_normal);
- if (dot < 0.984f && dot > -0.984f)
- {
- highest_penetration = penetration;
- other_constraint = *c;
- }
- }
- // Cancel the constraint velocity in the other constraint plane's direction so that we won't try to apply it again and keep ping ponging between planes
- constraint->mLinearVelocity -= min(0.0f, constraint->mLinearVelocity.Dot(other_normal)) * other_normal;
- // Cancel the other constraints velocity in this constraint plane's direction so that we won't try to apply it again and keep ping ponging between planes
- (*c)->mLinearVelocity -= min(0.0f, (*c)->mLinearVelocity.Dot(plane_normal)) * plane_normal;
- }
- // Check if we found a 2nd constraint
- if (other_constraint != nullptr)
- {
- // Calculate the sliding direction and project the new velocity onto that sliding direction
- Vec3 other_normal = other_constraint->mPlane.GetNormal();
- Vec3 slide_dir = plane_normal.Cross(other_normal).Normalized();
- Vec3 velocity_in_slide_dir = new_velocity.Dot(slide_dir) * slide_dir;
- // Calculate the velocity of this constraint perpendicular to the slide direction
- Vec3 perpendicular_velocity = constraint->mLinearVelocity - constraint->mLinearVelocity.Dot(slide_dir) * slide_dir;
- // Calculate the velocity of the other constraint perpendicular to the slide direction
- Vec3 other_perpendicular_velocity = other_constraint->mLinearVelocity - other_constraint->mLinearVelocity.Dot(slide_dir) * slide_dir;
- // Add all components together
- new_velocity = velocity_in_slide_dir + perpendicular_velocity + other_perpendicular_velocity;
- }
- // Allow application to modify calculated velocity
- if (mListener != nullptr)
- {
- if (constraint->mContact->mCharacterB != nullptr)
- mListener->OnCharacterContactSolve(this, constraint->mContact->mCharacterB, constraint->mContact->mSubShapeIDB, constraint->mContact->mPosition, constraint->mContact->mContactNormal, constraint->mContact->mLinearVelocity, constraint->mContact->mMaterial, velocity, new_velocity);
- else
- mListener->OnContactSolve(this, constraint->mContact->mBodyB, constraint->mContact->mSubShapeIDB, constraint->mContact->mPosition, constraint->mContact->mContactNormal, constraint->mContact->mLinearVelocity, constraint->mContact->mMaterial, velocity, new_velocity);
- }
- #ifdef JPH_DEBUG_RENDERER
- if (inDrawConstraints)
- {
- // Calculate where to draw
- RVec3 offset = mPosition + Vec3(0, 0, 2.5f * (iteration + 1));
- // Draw constraint plane
- DebugRenderer::sInstance->DrawPlane(offset, constraint->mPlane.GetNormal(), Color::sCyan, 1.0f);
- // Draw 2nd constraint plane
- if (other_constraint != nullptr)
- DebugRenderer::sInstance->DrawPlane(offset, other_constraint->mPlane.GetNormal(), Color::sBlue, 1.0f);
- // Draw starting velocity
- DebugRenderer::sInstance->DrawArrow(offset, offset + velocity, Color::sGreen, 0.05f);
- // Draw resulting velocity
- DebugRenderer::sInstance->DrawArrow(offset, offset + new_velocity, Color::sRed, 0.05f);
- }
- #endif // JPH_DEBUG_RENDERER
- // Update the velocity
- velocity = new_velocity;
- // Add the contact to the list so that next iteration we can avoid violating it again
- previous_contacts[num_previous_contacts] = constraint;
- num_previous_contacts++;
- // Check early out
- if (constraint->mProjectedVelocity < 1.0e-8f // Constraint should not be pushing, otherwise there may be other constraints that are pushing us
- && velocity.LengthSq() < 1.0e-8f) // There's not enough velocity left
- return;
- // If the constraint has velocity we accept the new velocity, otherwise check that we didn't reverse velocity
- if (!constraint->mLinearVelocity.IsNearZero(1.0e-8f))
- last_velocity = constraint->mLinearVelocity;
- else if (velocity.Dot(last_velocity) < 0.0f)
- return;
- }
- }
- void CharacterVirtual::UpdateSupportingContact(bool inSkipContactVelocityCheck, TempAllocator &inAllocator)
- {
- // Flag contacts as having a collision if they're close enough but ignore contacts we're moving away from.
- // Note that if we did MoveShape before we want to preserve any contacts that it marked as colliding
- for (Contact &c : mActiveContacts)
- if (!c.mWasDiscarded
- && !c.mHadCollision
- && c.mDistance < mCollisionTolerance
- && (inSkipContactVelocityCheck || c.mSurfaceNormal.Dot(mLinearVelocity - c.mLinearVelocity) <= 1.0e-4f))
- {
- if (ValidateContact(c))
- {
- CharacterContactSettings dummy;
- ContactAdded(c, dummy);
- c.mHadCollision = true;
- }
- else
- c.mWasDiscarded = true;
- }
- // Calculate transform that takes us to character local space
- RMat44 inv_transform = RMat44::sInverseRotationTranslation(mRotation, mPosition);
- // Determine if we're supported or not
- int num_supported = 0;
- int num_sliding = 0;
- int num_avg_normal = 0;
- Vec3 avg_normal = Vec3::sZero();
- Vec3 avg_velocity = Vec3::sZero();
- const Contact *supporting_contact = nullptr;
- float max_cos_angle = -FLT_MAX;
- const Contact *deepest_contact = nullptr;
- float smallest_distance = FLT_MAX;
- for (const Contact &c : mActiveContacts)
- if (c.mHadCollision && !c.mWasDiscarded)
- {
- // Calculate the angle between the plane normal and the up direction
- float cos_angle = c.mSurfaceNormal.Dot(mUp);
- // Find the deepest contact
- if (c.mDistance < smallest_distance)
- {
- deepest_contact = &c;
- smallest_distance = c.mDistance;
- }
- // If this contact is in front of our plane, we cannot be supported by it
- if (mSupportingVolume.SignedDistance(Vec3(inv_transform * c.mPosition)) > 0.0f)
- continue;
- // Find the contact with the normal that is pointing most upwards and store it
- if (max_cos_angle < cos_angle)
- {
- supporting_contact = &c;
- max_cos_angle = cos_angle;
- }
- // Check if this is a sliding or supported contact
- bool is_supported = mCosMaxSlopeAngle > cNoMaxSlopeAngle || cos_angle >= mCosMaxSlopeAngle;
- if (is_supported)
- num_supported++;
- else
- num_sliding++;
- // If the angle between the two is less than 85 degrees we also use it to calculate the average normal
- if (cos_angle >= 0.08f)
- {
- avg_normal += c.mSurfaceNormal;
- num_avg_normal++;
- // For static or dynamic objects or for contacts that don't support us just take the contact velocity
- if (c.mMotionTypeB != EMotionType::Kinematic || !is_supported)
- avg_velocity += c.mLinearVelocity;
- else
- {
- // For keyframed objects that support us calculate the velocity at our position rather than at the contact position so that we properly follow the object
- BodyLockRead lock(mSystem->GetBodyLockInterface(), c.mBodyB);
- if (lock.SucceededAndIsInBroadPhase())
- {
- const Body &body = lock.GetBody();
- // Get adjusted body velocity
- Vec3 linear_velocity, angular_velocity;
- GetAdjustedBodyVelocity(body, linear_velocity, angular_velocity);
- // Calculate the ground velocity
- avg_velocity += CalculateCharacterGroundVelocity(body.GetCenterOfMassPosition(), linear_velocity, angular_velocity, mLastDeltaTime);
- }
- else
- {
- // Fall back to contact velocity
- avg_velocity += c.mLinearVelocity;
- }
- }
- }
- }
- // Take either the most supporting contact or the deepest contact
- const Contact *best_contact = supporting_contact != nullptr? supporting_contact : deepest_contact;
- // Calculate average normal and velocity
- if (num_avg_normal >= 1)
- {
- mGroundNormal = avg_normal.Normalized();
- mGroundVelocity = avg_velocity / float(num_avg_normal);
- }
- else if (best_contact != nullptr)
- {
- mGroundNormal = best_contact->mSurfaceNormal;
- mGroundVelocity = best_contact->mLinearVelocity;
- }
- else
- {
- mGroundNormal = Vec3::sZero();
- mGroundVelocity = Vec3::sZero();
- }
- // Copy contact properties
- if (best_contact != nullptr)
- {
- mGroundBodyID = best_contact->mBodyB;
- mGroundBodySubShapeID = best_contact->mSubShapeIDB;
- mGroundPosition = best_contact->mPosition;
- mGroundMaterial = best_contact->mMaterial;
- mGroundUserData = best_contact->mUserData;
- }
- else
- {
- mGroundBodyID = BodyID();
- mGroundBodySubShapeID = SubShapeID();
- mGroundPosition = RVec3::sZero();
- mGroundMaterial = PhysicsMaterial::sDefault;
- mGroundUserData = 0;
- }
- // Determine ground state
- if (num_supported > 0)
- {
- // We made contact with something that supports us
- mGroundState = EGroundState::OnGround;
- }
- else if (num_sliding > 0)
- {
- if ((mLinearVelocity - deepest_contact->mLinearVelocity).Dot(mUp) > 1.0e-4f)
- {
- // We cannot be on ground if we're moving upwards relative to the ground
- mGroundState = EGroundState::OnSteepGround;
- }
- else
- {
- // If we're sliding down, we may actually be standing on multiple sliding contacts in such a way that we can't slide off, in this case we're also supported
- // Convert the contacts into constraints
- TempContactList contacts(mActiveContacts.begin(), mActiveContacts.end(), inAllocator);
- ConstraintList constraints(inAllocator);
- constraints.reserve(contacts.size() * 2);
- DetermineConstraints(contacts, mLastDeltaTime, constraints);
- // Solve the displacement using these constraints, this is used to check if we didn't move at all because we are supported
- Vec3 displacement;
- float time_simulated;
- IgnoredContactList ignored_contacts(inAllocator);
- ignored_contacts.reserve(contacts.size());
- SolveConstraints(-mUp, 1.0f, 1.0f, constraints, ignored_contacts, time_simulated, displacement, inAllocator);
- // If we're blocked then we're supported, otherwise we're sliding
- float min_required_displacement_sq = Square(0.6f * mLastDeltaTime);
- if (time_simulated < 0.001f || displacement.LengthSq() < min_required_displacement_sq)
- mGroundState = EGroundState::OnGround;
- else
- mGroundState = EGroundState::OnSteepGround;
- }
- }
- else
- {
- // Not supported by anything
- mGroundState = best_contact != nullptr? EGroundState::NotSupported : EGroundState::InAir;
- }
- }
- void CharacterVirtual::StoreActiveContacts(const TempContactList &inContacts, TempAllocator &inAllocator)
- {
- StartTrackingContactChanges();
- mActiveContacts.assign(inContacts.begin(), inContacts.end());
- UpdateSupportingContact(true, inAllocator);
- FinishTrackingContactChanges();
- }
- void CharacterVirtual::MoveShape(RVec3 &ioPosition, Vec3Arg inVelocity, float inDeltaTime, ContactList *outActiveContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator
- #ifdef JPH_DEBUG_RENDERER
- , bool inDrawConstraints
- #endif // JPH_DEBUG_RENDERER
- )
- {
- JPH_DET_LOG("CharacterVirtual::MoveShape: pos: " << ioPosition << " vel: " << inVelocity << " dt: " << inDeltaTime);
- Vec3 movement_direction = inVelocity.NormalizedOr(Vec3::sZero());
- float time_remaining = inDeltaTime;
- for (uint iteration = 0; iteration < mMaxCollisionIterations && time_remaining >= mMinTimeRemaining; iteration++)
- {
- JPH_DET_LOG("iter: " << iteration << " time: " << time_remaining);
- // Determine contacts in the neighborhood
- TempContactList contacts(inAllocator);
- contacts.reserve(mMaxNumHits);
- GetContactsAtPosition(ioPosition, movement_direction, mShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);
- #ifdef JPH_ENABLE_DETERMINISM_LOG
- for (const Contact &c : contacts)
- JPH_DET_LOG("contact: " << c.mPosition << " vel: " << c.mLinearVelocity << " cnormal: " << c.mContactNormal << " snormal: " << c.mSurfaceNormal << " dist: " << c.mDistance << " fraction: " << c.mFraction << " body: " << c.mBodyB << " subshape: " << c.mSubShapeIDB);
- #endif // JPH_ENABLE_DETERMINISM_LOG
- // Remove contacts with the same body that have conflicting normals
- IgnoredContactList ignored_contacts(inAllocator);
- ignored_contacts.reserve(contacts.size());
- RemoveConflictingContacts(contacts, ignored_contacts);
- // Convert contacts into constraints
- ConstraintList constraints(inAllocator);
- constraints.reserve(contacts.size() * 2);
- DetermineConstraints(contacts, inDeltaTime, constraints);
- #ifdef JPH_DEBUG_RENDERER
- bool draw_constraints = inDrawConstraints && iteration == 0;
- if (draw_constraints)
- {
- for (const Constraint &c : constraints)
- {
- // Draw contact point
- DebugRenderer::sInstance->DrawMarker(c.mContact->mPosition, Color::sYellow, 0.05f);
- Vec3 dist_to_plane = -c.mPlane.GetConstant() * c.mPlane.GetNormal();
- // Draw arrow towards surface that we're hitting
- DebugRenderer::sInstance->DrawArrow(c.mContact->mPosition, c.mContact->mPosition - dist_to_plane, Color::sYellow, 0.05f);
- // Draw plane around the player position indicating the space that we can move
- DebugRenderer::sInstance->DrawPlane(mPosition + dist_to_plane, c.mPlane.GetNormal(), Color::sCyan, 1.0f);
- DebugRenderer::sInstance->DrawArrow(mPosition + dist_to_plane, mPosition + dist_to_plane + c.mContact->mSurfaceNormal, Color::sRed, 0.05f);
- }
- }
- #endif // JPH_DEBUG_RENDERER
- // Solve the displacement using these constraints
- Vec3 displacement;
- float time_simulated;
- SolveConstraints(inVelocity, inDeltaTime, time_remaining, constraints, ignored_contacts, time_simulated, displacement, inAllocator
- #ifdef JPH_DEBUG_RENDERER
- , draw_constraints
- #endif // JPH_DEBUG_RENDERER
- );
- // Store the contacts now that the colliding ones have been marked
- if (outActiveContacts != nullptr)
- outActiveContacts->assign(contacts.begin(), contacts.end());
- // Do a sweep to test if the path is really unobstructed
- Contact cast_contact;
- if (GetFirstContactForSweep(ioPosition, displacement, cast_contact, ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))
- {
- displacement *= cast_contact.mFraction;
- time_simulated *= cast_contact.mFraction;
- }
- // Update the position
- ioPosition += displacement;
- time_remaining -= time_simulated;
- // If the displacement during this iteration was too small we assume we cannot further progress this update
- if (displacement.LengthSq() < 1.0e-8f)
- break;
- }
- }
- void CharacterVirtual::SetUserData(uint64 inUserData)
- {
- mUserData = inUserData;
- if (!mInnerBodyID.IsInvalid())
- mSystem->GetBodyInterface().SetUserData(mInnerBodyID, inUserData);
- }
- Vec3 CharacterVirtual::CancelVelocityTowardsSteepSlopes(Vec3Arg inDesiredVelocity) const
- {
- // If we're not pushing against a steep slope, return the desired velocity
- // Note: This is important as WalkStairs overrides the ground state to OnGround when its first check fails but the second succeeds
- if (mGroundState == CharacterVirtual::EGroundState::OnGround
- || mGroundState == CharacterVirtual::EGroundState::InAir)
- return inDesiredVelocity;
- Vec3 desired_velocity = inDesiredVelocity;
- for (const Contact &c : mActiveContacts)
- if (c.mHadCollision
- && !c.mWasDiscarded
- && IsSlopeTooSteep(c.mSurfaceNormal))
- {
- // Note that we use the contact normal to allow for better sliding as the surface normal may be in the opposite direction of movement.
- Vec3 normal = c.mContactNormal;
- // Remove normal vertical component
- normal -= normal.Dot(mUp) * mUp;
- // Cancel horizontal movement in opposite direction
- float dot = normal.Dot(desired_velocity);
- if (dot < 0.0f)
- desired_velocity -= (dot * normal) / normal.LengthSq();
- }
- return desired_velocity;
- }
- void CharacterVirtual::StartTrackingContactChanges()
- {
- // Check if we're starting for the first time
- if (++mTrackingContactChanges > 1)
- return;
- // No need to track anything if we don't have a listener
- JPH_ASSERT(mListenerContacts.empty());
- if (mListener == nullptr)
- return;
- // Mark all current contacts as not seen
- mListenerContacts.reserve(ListenerContacts::size_type(mActiveContacts.size()));
- for (const Contact &c : mActiveContacts)
- if (c.mHadCollision)
- mListenerContacts.insert(ListenerContacts::value_type(c, ListenerContactValue()));
- }
- void CharacterVirtual::FinishTrackingContactChanges()
- {
- // Check if we have to do anything
- int count = --mTrackingContactChanges;
- JPH_ASSERT(count >= 0, "Called FinishTrackingContactChanges more times than StartTrackingContactChanges");
- if (count > 0)
- return;
- // No need to track anything if we don't have a listener
- if (mListener == nullptr)
- return;
- // Since we can do multiple operations (e.g. Update followed by WalkStairs)
- // we can end up with contacts that were marked as active to the listener but that are
- // no longer in the active contact list. We go over all contacts and mark them again
- // to ensure that these lists are in sync.
- for (ListenerContacts::value_type &c : mListenerContacts)
- c.second.mCount = 0;
- for (const Contact &c : mActiveContacts)
- if (c.mHadCollision)
- {
- ListenerContacts::iterator it = mListenerContacts.find(c);
- JPH_ASSERT(it != mListenerContacts.end());
- it->second.mCount = 1;
- }
- // Call contact removal callbacks
- for (ListenerContacts::iterator it = mListenerContacts.begin(); it != mListenerContacts.end(); ++it)
- if (it->second.mCount == 0)
- {
- const ContactKey &c = it->first;
- if (!c.mCharacterIDB.IsInvalid())
- mListener->OnCharacterContactRemoved(this, c.mCharacterIDB, c.mSubShapeIDB);
- else
- mListener->OnContactRemoved(this, c.mBodyB, c.mSubShapeIDB);
- }
- mListenerContacts.ClearAndKeepMemory();
- }
- void CharacterVirtual::Update(float inDeltaTime, Vec3Arg inGravity, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
- {
- // If there's no delta time, we don't need to do anything
- if (inDeltaTime <= 0.0f)
- return;
- StartTrackingContactChanges();
- JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });
- // Remember delta time for checking if we're supported by the ground
- mLastDeltaTime = inDeltaTime;
- // Slide the shape through the world
- MoveShape(mPosition, mLinearVelocity, inDeltaTime, &mActiveContacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator
- #ifdef JPH_DEBUG_RENDERER
- , sDrawConstraints
- #endif // JPH_DEBUG_RENDERER
- );
- // Determine the object that we're standing on
- UpdateSupportingContact(false, inAllocator);
- // Ensure that the rigid body ends up at the new position
- UpdateInnerBodyTransform();
- // If we're on the ground
- if (!mGroundBodyID.IsInvalid() && mMass > 0.0f)
- {
- // Add the impulse to the ground due to gravity: P = F dt = M g dt
- float normal_dot_gravity = mGroundNormal.Dot(inGravity);
- if (normal_dot_gravity < 0.0f)
- {
- Vec3 world_impulse = -(mMass * normal_dot_gravity / inGravity.Length() * inDeltaTime) * inGravity;
- mSystem->GetBodyInterface().AddImpulse(mGroundBodyID, world_impulse, mGroundPosition);
- }
- }
- }
- void CharacterVirtual::RefreshContacts(const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
- {
- // Determine the contacts
- TempContactList contacts(inAllocator);
- contacts.reserve(mMaxNumHits);
- GetContactsAtPosition(mPosition, mLinearVelocity.NormalizedOr(Vec3::sZero()), mShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);
- StoreActiveContacts(contacts, inAllocator);
- }
- void CharacterVirtual::UpdateGroundVelocity()
- {
- BodyLockRead lock(mSystem->GetBodyLockInterface(), mGroundBodyID);
- if (lock.SucceededAndIsInBroadPhase())
- {
- const Body &body = lock.GetBody();
- // Get adjusted body velocity
- Vec3 linear_velocity, angular_velocity;
- GetAdjustedBodyVelocity(body, linear_velocity, angular_velocity);
- // Calculate the ground velocity
- mGroundVelocity = CalculateCharacterGroundVelocity(body.GetCenterOfMassPosition(), linear_velocity, angular_velocity, mLastDeltaTime);
- }
- }
- void CharacterVirtual::MoveToContact(RVec3Arg inPosition, const Contact &inContact, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
- {
- // Set the new position
- SetPosition(inPosition);
- // Trigger contact added callback
- CharacterContactSettings dummy;
- ContactAdded(inContact, dummy);
- // Determine the contacts
- TempContactList contacts(inAllocator);
- contacts.reserve(mMaxNumHits + 1); // +1 because we can add one extra below
- GetContactsAtPosition(mPosition, mLinearVelocity.NormalizedOr(Vec3::sZero()), mShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);
- // Ensure that we mark inContact as colliding
- bool found_contact = false;
- for (Contact &c : contacts)
- if (c.mBodyB == inContact.mBodyB
- && c.mSubShapeIDB == inContact.mSubShapeIDB)
- {
- c.mHadCollision = true;
- found_contact = true;
- }
- if (!found_contact)
- {
- contacts.push_back(inContact);
- Contact © = contacts.back();
- copy.mHadCollision = true;
- }
- StoreActiveContacts(contacts, inAllocator);
- JPH_ASSERT(mGroundState != EGroundState::InAir);
- // Ensure that the rigid body ends up at the new position
- UpdateInnerBodyTransform();
- }
- bool CharacterVirtual::SetShape(const Shape *inShape, float inMaxPenetrationDepth, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
- {
- if (mShape == nullptr || mSystem == nullptr)
- {
- // It hasn't been initialized yet
- mShape = inShape;
- return true;
- }
- if (inShape != mShape && inShape != nullptr)
- {
- if (inMaxPenetrationDepth < FLT_MAX)
- {
- // Check collision around the new shape
- TempContactList contacts(inAllocator);
- contacts.reserve(mMaxNumHits);
- GetContactsAtPosition(mPosition, mLinearVelocity.NormalizedOr(Vec3::sZero()), inShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);
- // Test if this results in penetration, if so cancel the transition
- for (const Contact &c : contacts)
- if (c.mDistance < -inMaxPenetrationDepth
- && !c.mIsSensorB)
- return false;
- StoreActiveContacts(contacts, inAllocator);
- }
- // Set new shape
- mShape = inShape;
- }
- return mShape == inShape;
- }
- void CharacterVirtual::SetInnerBodyShape(const Shape *inShape)
- {
- mSystem->GetBodyInterface().SetShape(mInnerBodyID, inShape, false, EActivation::DontActivate);
- }
- bool CharacterVirtual::CanWalkStairs(Vec3Arg inLinearVelocity) const
- {
- // We can only walk stairs if we're supported
- if (!IsSupported())
- return false;
- // Check if there's enough horizontal velocity to trigger a stair walk
- Vec3 horizontal_velocity = inLinearVelocity - inLinearVelocity.Dot(mUp) * mUp;
- if (horizontal_velocity.IsNearZero(1.0e-6f))
- return false;
- // Check contacts for steep slopes
- for (const Contact &c : mActiveContacts)
- if (c.mHadCollision
- && !c.mWasDiscarded
- && c.mSurfaceNormal.Dot(horizontal_velocity - c.mLinearVelocity) < 0.0f // Pushing into the contact
- && IsSlopeTooSteep(c.mSurfaceNormal)) // Slope too steep
- return true;
- return false;
- }
- bool CharacterVirtual::WalkStairs(float inDeltaTime, Vec3Arg inStepUp, Vec3Arg inStepForward, Vec3Arg inStepForwardTest, Vec3Arg inStepDownExtra, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
- {
- StartTrackingContactChanges();
- JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });
- // Move up
- Vec3 up = inStepUp;
- Contact contact;
- IgnoredContactList dummy_ignored_contacts(inAllocator);
- if (GetFirstContactForSweep(mPosition, up, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))
- {
- if (contact.mFraction < 1.0e-6f)
- return false; // No movement, cancel
- // Limit up movement to the first contact point
- up *= contact.mFraction;
- }
- RVec3 up_position = mPosition + up;
- #ifdef JPH_DEBUG_RENDERER
- // Draw sweep up
- if (sDrawWalkStairs)
- DebugRenderer::sInstance->DrawArrow(mPosition, up_position, Color::sWhite, 0.01f);
- #endif // JPH_DEBUG_RENDERER
- // Collect normals of steep slopes that we would like to walk stairs on.
- // We need to do this before calling MoveShape because it will update mActiveContacts.
- Vec3 character_velocity = inStepForward / inDeltaTime;
- Vec3 horizontal_velocity = character_velocity - character_velocity.Dot(mUp) * mUp;
- Array<Vec3, STLTempAllocator<Vec3>> steep_slope_normals(inAllocator);
- steep_slope_normals.reserve(mActiveContacts.size());
- for (const Contact &c : mActiveContacts)
- if (c.mHadCollision
- && !c.mWasDiscarded
- && c.mSurfaceNormal.Dot(horizontal_velocity - c.mLinearVelocity) < 0.0f // Pushing into the contact
- && IsSlopeTooSteep(c.mSurfaceNormal)) // Slope too steep
- steep_slope_normals.push_back(c.mSurfaceNormal);
- if (steep_slope_normals.empty())
- return false; // No steep slopes, cancel
- // Horizontal movement
- RVec3 new_position = up_position;
- MoveShape(new_position, character_velocity, inDeltaTime, nullptr, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);
- Vec3 horizontal_movement = Vec3(new_position - up_position);
- float horizontal_movement_sq = horizontal_movement.LengthSq();
- if (horizontal_movement_sq < 1.0e-8f)
- return false; // No movement, cancel
- // Check if we made any progress towards any of the steep slopes, if not we just slid along the slope
- // so we need to cancel the stair walk or else we will move faster than we should as we've done
- // normal movement first and then stair walk.
- bool made_progress = false;
- float max_dot = -0.05f * inStepForward.Length();
- for (const Vec3 &normal : steep_slope_normals)
- if (normal.Dot(horizontal_movement) < max_dot)
- {
- // We moved more than 5% of the forward step against a steep slope, accept this as progress
- made_progress = true;
- break;
- }
- if (!made_progress)
- return false;
- #ifdef JPH_DEBUG_RENDERER
- // Draw horizontal sweep
- if (sDrawWalkStairs)
- DebugRenderer::sInstance->DrawArrow(up_position, new_position, Color::sWhite, 0.01f);
- #endif // JPH_DEBUG_RENDERER
- // Move down towards the floor.
- // Note that we travel the same amount down as we traveled up with the specified extra
- Vec3 down = -up + inStepDownExtra;
- if (!GetFirstContactForSweep(new_position, down, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))
- return false; // No floor found, we're in mid air, cancel stair walk
- #ifdef JPH_DEBUG_RENDERER
- // Draw sweep down
- if (sDrawWalkStairs)
- {
- RVec3 debug_pos = new_position + contact.mFraction * down;
- DebugRenderer::sInstance->DrawArrow(new_position, debug_pos, Color::sWhite, 0.01f);
- DebugRenderer::sInstance->DrawArrow(contact.mPosition, contact.mPosition + contact.mSurfaceNormal, Color::sWhite, 0.01f);
- mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(debug_pos, mRotation, mShape), Vec3::sOne(), Color::sWhite, false, true);
- }
- #endif // JPH_DEBUG_RENDERER
- // Test for floor that will support the character
- if (IsSlopeTooSteep(contact.mSurfaceNormal))
- {
- // If no test position was provided, we cancel the stair walk
- if (inStepForwardTest.IsNearZero())
- return false;
- // Delta time may be very small, so it may be that we hit the edge of a step and the normal is too horizontal.
- // In order to judge if the floor is flat further along the sweep, we test again for a floor at inStepForwardTest
- // and check if the normal is valid there.
- RVec3 test_position = up_position;
- MoveShape(test_position, inStepForwardTest / inDeltaTime, inDeltaTime, nullptr, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);
- float test_horizontal_movement_sq = Vec3(test_position - up_position).LengthSq();
- if (test_horizontal_movement_sq <= horizontal_movement_sq + 1.0e-8f)
- return false; // We didn't move any further than in the previous test
- #ifdef JPH_DEBUG_RENDERER
- // Draw 2nd sweep horizontal
- if (sDrawWalkStairs)
- DebugRenderer::sInstance->DrawArrow(up_position, test_position, Color::sCyan, 0.01f);
- #endif // JPH_DEBUG_RENDERER
- // Then sweep down
- Contact test_contact;
- if (!GetFirstContactForSweep(test_position, down, test_contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))
- return false;
- #ifdef JPH_DEBUG_RENDERER
- // Draw 2nd sweep down
- if (sDrawWalkStairs)
- {
- RVec3 debug_pos = test_position + test_contact.mFraction * down;
- DebugRenderer::sInstance->DrawArrow(test_position, debug_pos, Color::sCyan, 0.01f);
- DebugRenderer::sInstance->DrawArrow(test_contact.mPosition, test_contact.mPosition + test_contact.mSurfaceNormal, Color::sCyan, 0.01f);
- mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(debug_pos, mRotation, mShape), Vec3::sOne(), Color::sCyan, false, true);
- }
- #endif // JPH_DEBUG_RENDERER
- if (IsSlopeTooSteep(test_contact.mSurfaceNormal))
- return false;
- }
- // Calculate new down position
- down *= contact.mFraction;
- new_position += down;
- // Move the character to the new location
- MoveToContact(new_position, contact, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);
- // Override ground state to 'on ground', it is possible that the contact normal is too steep, but in this case the inStepForwardTest has found a contact normal that is not too steep
- mGroundState = EGroundState::OnGround;
- return true;
- }
- bool CharacterVirtual::StickToFloor(Vec3Arg inStepDown, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
- {
- StartTrackingContactChanges();
- JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });
- // Try to find the floor
- Contact contact;
- IgnoredContactList dummy_ignored_contacts(inAllocator);
- if (!GetFirstContactForSweep(mPosition, inStepDown, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))
- return false; // If no floor found, don't update our position
- // Calculate new position
- RVec3 new_position = mPosition + contact.mFraction * inStepDown;
- #ifdef JPH_DEBUG_RENDERER
- // Draw sweep down
- if (sDrawStickToFloor)
- {
- DebugRenderer::sInstance->DrawArrow(mPosition, new_position, Color::sOrange, 0.01f);
- mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(new_position, mRotation, mShape), Vec3::sOne(), Color::sOrange, false, true);
- }
- #endif // JPH_DEBUG_RENDERER
- // Move the character to the new location
- MoveToContact(new_position, contact, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);
- return true;
- }
- void CharacterVirtual::ExtendedUpdate(float inDeltaTime, Vec3Arg inGravity, const ExtendedUpdateSettings &inSettings, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
- {
- StartTrackingContactChanges();
- JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });
- // Update the velocity
- Vec3 desired_velocity = mLinearVelocity;
- mLinearVelocity = CancelVelocityTowardsSteepSlopes(desired_velocity);
- // Remember old position
- RVec3 old_position = mPosition;
- // Track if on ground before the update
- bool ground_to_air = IsSupported();
- // Update the character position (instant, do not have to wait for physics update)
- Update(inDeltaTime, inGravity, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);
- // ... and that we got into air after
- if (IsSupported())
- ground_to_air = false;
- // If stick to floor enabled and we're going from supported to not supported
- if (ground_to_air && !inSettings.mStickToFloorStepDown.IsNearZero())
- {
- // If we're not moving up, stick to the floor
- float velocity = Vec3(mPosition - old_position).Dot(mUp) / inDeltaTime;
- if (velocity <= 1.0e-6f)
- StickToFloor(inSettings.mStickToFloorStepDown, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);
- }
- // If walk stairs enabled
- if (!inSettings.mWalkStairsStepUp.IsNearZero())
- {
- // Calculate how much we wanted to move horizontally
- Vec3 desired_horizontal_step = desired_velocity * inDeltaTime;
- desired_horizontal_step -= desired_horizontal_step.Dot(mUp) * mUp;
- float desired_horizontal_step_len = desired_horizontal_step.Length();
- if (desired_horizontal_step_len > 0.0f)
- {
- // Calculate how much we moved horizontally
- Vec3 achieved_horizontal_step = Vec3(mPosition - old_position);
- achieved_horizontal_step -= achieved_horizontal_step.Dot(mUp) * mUp;
- // Only count movement in the direction of the desired movement
- // (otherwise we find it ok if we're sliding downhill while we're trying to climb uphill)
- Vec3 step_forward_normalized = desired_horizontal_step / desired_horizontal_step_len;
- achieved_horizontal_step = max(0.0f, achieved_horizontal_step.Dot(step_forward_normalized)) * step_forward_normalized;
- float achieved_horizontal_step_len = achieved_horizontal_step.Length();
- // If we didn't move as far as we wanted and we're against a slope that's too steep
- if (achieved_horizontal_step_len + 1.0e-4f < desired_horizontal_step_len
- && CanWalkStairs(desired_velocity))
- {
- // Calculate how much we should step forward
- // Note that we clamp the step forward to a minimum distance. This is done because at very high frame rates the delta time
- // may be very small, causing a very small step forward. If the step becomes small enough, we may not move far enough
- // horizontally to actually end up at the top of the step.
- Vec3 step_forward = step_forward_normalized * max(inSettings.mWalkStairsMinStepForward, desired_horizontal_step_len - achieved_horizontal_step_len);
- // Calculate how far to scan ahead for a floor. This is only used in case the floor normal at step_forward is too steep.
- // In that case an additional check will be performed at this distance to check if that normal is not too steep.
- // Start with the ground normal in the horizontal plane and normalizing it
- Vec3 step_forward_test = -mGroundNormal;
- step_forward_test -= step_forward_test.Dot(mUp) * mUp;
- step_forward_test = step_forward_test.NormalizedOr(step_forward_normalized);
- // If this normalized vector and the character forward vector is bigger than a preset angle, we use the character forward vector instead of the ground normal
- // to do our forward test
- if (step_forward_test.Dot(step_forward_normalized) < inSettings.mWalkStairsCosAngleForwardContact)
- step_forward_test = step_forward_normalized;
- // Calculate the correct magnitude for the test vector
- step_forward_test *= inSettings.mWalkStairsStepForwardTest;
- WalkStairs(inDeltaTime, inSettings.mWalkStairsStepUp, step_forward, step_forward_test, inSettings.mWalkStairsStepDownExtra, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator);
- }
- }
- }
- }
- void CharacterVirtual::ContactKey::SaveState(StateRecorder &inStream) const
- {
- inStream.Write(mBodyB);
- inStream.Write(mCharacterIDB);
- inStream.Write(mSubShapeIDB);
- }
- void CharacterVirtual::ContactKey::RestoreState(StateRecorder &inStream)
- {
- inStream.Read(mBodyB);
- inStream.Read(mCharacterIDB);
- inStream.Read(mSubShapeIDB);
- }
- void CharacterVirtual::Contact::SaveState(StateRecorder &inStream) const
- {
- ContactKey::SaveState(inStream);
- inStream.Write(mPosition);
- inStream.Write(mLinearVelocity);
- inStream.Write(mContactNormal);
- inStream.Write(mSurfaceNormal);
- inStream.Write(mDistance);
- inStream.Write(mFraction);
- inStream.Write(mMotionTypeB);
- inStream.Write(mIsSensorB);
- inStream.Write(mHadCollision);
- inStream.Write(mWasDiscarded);
- inStream.Write(mCanPushCharacter);
- // Cannot store pointers to character B, user data and material
- }
- void CharacterVirtual::Contact::RestoreState(StateRecorder &inStream)
- {
- ContactKey::RestoreState(inStream);
- inStream.Read(mPosition);
- inStream.Read(mLinearVelocity);
- inStream.Read(mContactNormal);
- inStream.Read(mSurfaceNormal);
- inStream.Read(mDistance);
- inStream.Read(mFraction);
- inStream.Read(mMotionTypeB);
- inStream.Read(mIsSensorB);
- inStream.Read(mHadCollision);
- inStream.Read(mWasDiscarded);
- inStream.Read(mCanPushCharacter);
- mCharacterB = nullptr; // Cannot restore character B
- mUserData = 0; // Cannot restore user data
- mMaterial = PhysicsMaterial::sDefault; // Cannot restore material
- }
- void CharacterVirtual::SaveState(StateRecorder &inStream) const
- {
- CharacterBase::SaveState(inStream);
- inStream.Write(mPosition);
- inStream.Write(mRotation);
- inStream.Write(mLinearVelocity);
- inStream.Write(mLastDeltaTime);
- inStream.Write(mMaxHitsExceeded);
- // Store contacts that had collision, we're using it at the beginning of the step in CancelVelocityTowardsSteepSlopes
- uint32 num_contacts = 0;
- for (const Contact &c : mActiveContacts)
- if (c.mHadCollision)
- ++num_contacts;
- inStream.Write(num_contacts);
- for (const Contact &c : mActiveContacts)
- if (c.mHadCollision)
- c.SaveState(inStream);
- }
- void CharacterVirtual::RestoreState(StateRecorder &inStream)
- {
- CharacterBase::RestoreState(inStream);
- inStream.Read(mPosition);
- inStream.Read(mRotation);
- inStream.Read(mLinearVelocity);
- inStream.Read(mLastDeltaTime);
- inStream.Read(mMaxHitsExceeded);
- // When validating remove contacts that don't have collision since we didn't save them
- if (inStream.IsValidating())
- for (int i = (int)mActiveContacts.size() - 1; i >= 0; --i)
- if (!mActiveContacts[i].mHadCollision)
- mActiveContacts.erase(mActiveContacts.begin() + i);
- uint32 num_contacts = (uint32)mActiveContacts.size();
- inStream.Read(num_contacts);
- mActiveContacts.resize(num_contacts);
- for (Contact &c : mActiveContacts)
- c.RestoreState(inStream);
- }
- CharacterVirtualSettings CharacterVirtual::GetCharacterVirtualSettings() const
- {
- CharacterVirtualSettings settings;
- settings.mUp = mUp;
- settings.mSupportingVolume = mSupportingVolume;
- settings.mMaxSlopeAngle = ACos(mCosMaxSlopeAngle);
- settings.mEnhancedInternalEdgeRemoval = mEnhancedInternalEdgeRemoval;
- settings.mShape = mShape;
- settings.mID = mID;
- settings.mMass = mMass;
- settings.mMaxStrength = mMaxStrength;
- settings.mShapeOffset = mShapeOffset;
- settings.mBackFaceMode = mBackFaceMode;
- settings.mPredictiveContactDistance = mPredictiveContactDistance;
- settings.mMaxCollisionIterations = mMaxCollisionIterations;
- settings.mMaxConstraintIterations = mMaxConstraintIterations;
- settings.mMinTimeRemaining = mMinTimeRemaining;
- settings.mCollisionTolerance = mCollisionTolerance;
- settings.mCharacterPadding = mCharacterPadding;
- settings.mMaxNumHits = mMaxNumHits;
- settings.mHitReductionCosMaxAngle = mHitReductionCosMaxAngle;
- settings.mPenetrationRecoverySpeed = mPenetrationRecoverySpeed;
- BodyLockRead lock(mSystem->GetBodyLockInterface(), mInnerBodyID);
- if (lock.Succeeded())
- {
- const Body &body = lock.GetBody();
- settings.mInnerBodyShape = body.GetShape();
- settings.mInnerBodyIDOverride = body.GetID();
- settings.mInnerBodyLayer = body.GetObjectLayer();
- }
- return settings;
- }
- JPH_NAMESPACE_END
|