|
@@ -820,6 +820,116 @@ bool CharacterVirtual::SetShape(const Shape *inShape, float inMaxPenetrationDept
|
|
|
return mShape == inShape;
|
|
|
}
|
|
|
|
|
|
+bool CharacterVirtual::CanWalkStairs() const
|
|
|
+{
|
|
|
+ // Check if there's enough horizontal velocity to trigger a stair walk
|
|
|
+ Vec3 horizontal_velocity = mLinearVelocity - mLinearVelocity.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.mNormal.Dot(horizontal_velocity - c.mLinearVelocity) < 0.0f // Pushing into the contact
|
|
|
+ && c.mNormal.Dot(mUp) < mCosMaxSlopeAngle) // Slope too steep
|
|
|
+ return true;
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+bool CharacterVirtual::WalkStairs(float inDeltaTime, Vec3Arg inGravity, Vec3Arg inStepUp, Vec3Arg inStepForward, Vec3Arg inStepForwardTest, Vec3Arg inStepDownExtra, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator)
|
|
|
+{
|
|
|
+ // Move up
|
|
|
+ Vec3 up = inStepUp;
|
|
|
+ Contact contact;
|
|
|
+ IgnoredContactList dummy_ignored_contacts(inAllocator);
|
|
|
+ if (GetFirstContactForSweep(mPosition, up, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator))
|
|
|
+ {
|
|
|
+ if (contact.mFraction < 1.0e-6f)
|
|
|
+ return false; // No movement, cancel
|
|
|
+
|
|
|
+ // Limit up movement to the first contact point
|
|
|
+ up *= contact.mFraction;
|
|
|
+ }
|
|
|
+ Vec3 up_position = mPosition + up;
|
|
|
+
|
|
|
+#ifdef JPH_DEBUG_RENDERER
|
|
|
+ // Draw sweep up
|
|
|
+ if (sDrawWalkStairs)
|
|
|
+ DebugRenderer::sInstance->DrawArrow(mPosition, up_position, Color::sGrey, 0.01f);
|
|
|
+#endif // JPH_DEBUG_RENDERER
|
|
|
+
|
|
|
+ // Horizontal movement
|
|
|
+ Vec3 new_position = up_position;
|
|
|
+ MoveShape(new_position, inStepForward / inDeltaTime, inGravity, inDeltaTime, nullptr, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator);
|
|
|
+ if (new_position.IsClose(up_position, 1.0e-8f))
|
|
|
+ return false; // No movement, cancel
|
|
|
+
|
|
|
+#ifdef JPH_DEBUG_RENDERER
|
|
|
+ // Draw horizontal sweep
|
|
|
+ if (sDrawWalkStairs)
|
|
|
+ DebugRenderer::sInstance->DrawArrow(up_position, new_position, Color::sGrey, 0.01f);
|
|
|
+#endif // JPH_DEBUG_RENDERER
|
|
|
+
|
|
|
+ // Move down towards the floor.
|
|
|
+ // Note that we travel the same amount down as we travelled up with the specified extra
|
|
|
+ Vec3 down = -up + inStepDownExtra;
|
|
|
+ if (!GetFirstContactForSweep(new_position, down, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator))
|
|
|
+ return false; // No floor found, we're in mid air, cancel stair walk
|
|
|
+
|
|
|
+#ifdef JPH_DEBUG_RENDERER
|
|
|
+ // Draw sweep down
|
|
|
+ if (sDrawWalkStairs)
|
|
|
+ {
|
|
|
+ Vec3 debug_pos = new_position + contact.mFraction * down;
|
|
|
+ DebugRenderer::sInstance->DrawArrow(new_position, debug_pos, Color::sYellow, 0.01f);
|
|
|
+ DebugRenderer::sInstance->DrawArrow(contact.mPosition, contact.mPosition + contact.mNormal, Color::sYellow, 0.01f);
|
|
|
+ mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(debug_pos, mRotation, mShape), Vec3::sReplicate(1.0f), Color::sYellow, false, true);
|
|
|
+ }
|
|
|
+#endif // JPH_DEBUG_RENDERER
|
|
|
+
|
|
|
+ // Test for floor that will support the character
|
|
|
+ if (mCosMaxSlopeAngle < 0.999f // If cos(slope angle) is close to 1 then there's no limit
|
|
|
+ && contact.mNormal.Dot(mUp) < mCosMaxSlopeAngle) // Check slope angle
|
|
|
+ {
|
|
|
+ // 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.
|
|
|
+ Vec3 test_position = up_position + inStepForwardTest;
|
|
|
+ Contact test_contact;
|
|
|
+ bool hit = GetFirstContactForSweep(test_position, down, test_contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator);
|
|
|
+ if (!hit)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ #ifdef JPH_DEBUG_RENDERER
|
|
|
+ // Draw 2nd sweep down
|
|
|
+ if (sDrawWalkStairs)
|
|
|
+ {
|
|
|
+ Vec3 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.mNormal, Color::sCyan, 0.01f);
|
|
|
+ mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(debug_pos, mRotation, mShape), Vec3::sReplicate(1.0f), Color::sCyan, false, true);
|
|
|
+ }
|
|
|
+ #endif // JPH_DEBUG_RENDERER
|
|
|
+
|
|
|
+ if (test_contact.mNormal.Dot(mUp) < mCosMaxSlopeAngle)
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Calculate new down position
|
|
|
+ down *= contact.mFraction;
|
|
|
+ new_position += down;
|
|
|
+
|
|
|
+ // Move the character to the new location
|
|
|
+ SetPosition(new_position);
|
|
|
+ RefreshContacts(inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
void CharacterVirtual::SaveState(StateRecorder &inStream) const
|
|
|
{
|
|
|
CharacterBase::SaveState(inStream);
|