From e982a23ed387bbe74e334320d40c1a2d5d104881 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 5 Jun 2025 15:07:51 +0200 Subject: [PATCH] Add `CharacterController.Resize` for quick crouching implementation for characters --- .../Physics/Colliders/CharacterController.cpp | 106 ++++++++++++++---- .../Physics/Colliders/CharacterController.h | 15 ++- .../Physics/PhysX/PhysicsBackendPhysX.cpp | 10 +- Source/Engine/Physics/PhysicsBackend.h | 1 + 4 files changed, 106 insertions(+), 26 deletions(-) diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 629fe4be9..f1a467348 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -96,15 +96,11 @@ void CharacterController::SetStepOffset(float value) { if (value == _stepOffset) return; - _stepOffset = value; - if (_controller) { - const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float contactOffset = Math::Max(_contactOffset, ZeroTolerance); - const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE); - const float radius = Math::Max(Math::Abs(_radius) * scaling - contactOffset, CC_MIN_SIZE); + float height, radius; + GetControllerSize(height, radius); PhysicsBackend::SetControllerStepOffset(_controller, Math::Min(value, height + radius * 2.0f - CC_MIN_SIZE)); } } @@ -180,6 +176,37 @@ CharacterController::CollisionFlags CharacterController::Move(const Vector3& dis return result; } +void CharacterController::Resize(float height, float radius) +{ + const float heightDiff = height - _height; + const float radiusDiff = radius - _radius; + if (Math::IsZero(heightDiff) && Math::IsZero(radiusDiff)) + return; + _height = height; + _radius = radius; + if (_controller) + { + float centerDiff = heightDiff * 0.5f + radiusDiff; + + // Change physics size + GetControllerSize(height, radius); + PhysicsBackend::SetControllerSize(_controller, radius, height); + Vector3 positionDelta = _upDirection * centerDiff; + + // Change physics position to maintain feet placement (base) + Vector3 position = PhysicsBackend::GetControllerPosition(_controller); + position += positionDelta; + _center += positionDelta; + PhysicsBackend::SetControllerPosition(_controller, position); + + // Change actor position + _isUpdatingTransform = true; + SetPosition(position - _center); + _isUpdatingTransform = false; + } + UpdateBounds(); +} + #if USE_EDITOR #include "Engine/Debug/DebugDraw.h" @@ -187,23 +214,45 @@ CharacterController::CollisionFlags CharacterController::Move(const Vector3& dis void CharacterController::DrawPhysicsDebug(RenderView& view) { - const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE); - const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE); + Quaternion rotation = Quaternion::Euler(90, 0, 0); const Vector3 position = _transform.LocalToWorld(_center); if (view.Mode == ViewMode::PhysicsColliders) - DEBUG_DRAW_CAPSULE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::LightYellow, 0, true); + DEBUG_DRAW_CAPSULE(position, rotation, _radius, _height, Color::LightYellow, 0, true); else - DEBUG_DRAW_WIRE_CAPSULE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow * 0.8f, 0, true); + DEBUG_DRAW_WIRE_CAPSULE(position, rotation, _radius, _height, Color::GreenYellow * 0.8f, 0, true); } void CharacterController::OnDebugDrawSelected() { - const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE); - const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE); + Quaternion rotation = Quaternion::Euler(90, 0, 0); const Vector3 position = _transform.LocalToWorld(_center); - DEBUG_DRAW_WIRE_CAPSULE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow, 0, false); + DEBUG_DRAW_WIRE_CAPSULE(position, rotation, _radius, _height, Color::GreenYellow, 0, false); + if (_contactOffset > 0) + DEBUG_DRAW_WIRE_CAPSULE(position, rotation, _radius - _contactOffset, _height, Color::Blue.AlphaMultiplied(0.4f), 0, false); +#if 0 + // More technical visuals debugging + if (_controller) + { + float height, radius; + GetControllerSize(height, radius); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(PhysicsBackend::GetControllerBasePosition(_controller), 5.0f), Color::Red, 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(PhysicsBackend::GetControllerPosition(_controller), 4.0f), Color::Red, 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(PhysicsBackend::GetControllerPosition(_controller) + Vector3(0, height * 0.5f, 0), 2.0f), Color::Red, 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(PhysicsBackend::GetControllerPosition(_controller) - Vector3(0, height * 0.5f, 0), 2.0f), Color::Red, 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(PhysicsBackend::GetControllerPosition(_controller) + Vector3(0, height * 0.5f, 0), radius), Color::Red.AlphaMultiplied(0.5f), 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(PhysicsBackend::GetControllerPosition(_controller) - Vector3(0, height * 0.5f, 0), radius), Color::Red.AlphaMultiplied(0.5f), 0, false); + DEBUG_DRAW_WIRE_CYLINDER(PhysicsBackend::GetControllerPosition(_controller), Quaternion::Identity, radius, height, Color::Red.AlphaMultiplied(0.2f), 0, false); + } + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(position, 3.0f), Color::GreenYellow, 0, false); +#else + if (_controller) + { + // Physics backend capsule shape + float height, radius; + GetControllerSize(height, radius); + DEBUG_DRAW_WIRE_CAPSULE(PhysicsBackend::GetControllerPosition(_controller), rotation, radius, height, Color::Blue.AlphaMultiplied(0.2f), 0, false); + } +#endif // Base Collider::OnDebugDrawSelected(); @@ -216,8 +265,10 @@ void CharacterController::CreateController() // Create controller ASSERT(_controller == nullptr && _shape == nullptr); _cachedScale = GetScale().GetAbsolute().MaxValue(); + float height, radius; + GetControllerSize(height, radius); const Vector3 position = _transform.LocalToWorld(_center); - _controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material, Math::Abs(_radius) * scaling, Math::Abs(_height) * scaling, _stepOffset, _shape); + _controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material, radius, height, _stepOffset, _shape); // Setup PhysicsBackend::SetControllerUpDirection(_controller, _upDirection); @@ -240,13 +291,24 @@ void CharacterController::UpdateSize() const { if (_controller) { - const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float radius = Math::Max(Math::Abs(_radius) * scaling - Math::Max(_contactOffset, ZeroTolerance), CC_MIN_SIZE); - const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE); + float height, radius; + GetControllerSize(height, radius); PhysicsBackend::SetControllerSize(_controller, radius, height); } } +void CharacterController::GetControllerSize(float& height, float& radius) const +{ + height = Math::Abs(_height) * _cachedScale; + radius = Math::Abs(_radius) * _cachedScale; + { + // Exclude contact offset around the capsule (otherwise character floats in the air) + radius = radius - Math::Max(_contactOffset, 0.0f); + } + height = Math::Max(height, CC_MIN_SIZE); + radius = Math::Max(radius, CC_MIN_SIZE); +} + void CharacterController::CreateShape() { // Not used @@ -254,9 +316,9 @@ void CharacterController::CreateShape() void CharacterController::UpdateBounds() { - const float scaling = GetScale().GetAbsolute().MaxValue(); - const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE); - const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE); + _cachedScale = GetScale().GetAbsolute().MaxValue(); + float height, radius; + GetControllerSize(height, radius); const Vector3 position = _transform.LocalToWorld(_center); const Vector3 extent(radius, height * 0.5f + radius, radius); _box = BoundingBox(position - extent, position + extent); diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index 736e83951..a95c9eb7e 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -84,13 +84,13 @@ public: API_PROPERTY() void SetRadius(float value); /// - /// Gets the height of the capsule, measured in the object's local space. The capsule height will be scaled by the actor's world scale. + /// Gets the height of the capsule as a distance between the two sphere centers at the end of the capsule. The capsule height is measured in the object's local space and will be scaled by the actor's world scale. /// API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(150.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") float GetHeight() const; /// - /// Sets the height of the capsule, measured in the object's local space. The capsule height will be scaled by the actor's world scale. + /// Sets the height of the capsule as a distance between the two sphere centers at the end of the capsule. The capsule height is measured in the object's local space and will be scaled by the actor's world scale. /// API_PROPERTY() void SetHeight(float value); @@ -194,6 +194,13 @@ public: /// The collision flags. It can be used to trigger various character animations. API_FUNCTION() CollisionFlags Move(const Vector3& displacement); + /// + /// Updates the character height and center position to ensure its feet position stays the same. This can be used to implement a 'crouch' functionality for example. Maintains the same actor position to stay in the middle of capsule by adjusting center of collider accordingly to height difference. + /// + /// The height of the capsule, measured in the object's local space. + /// The radius of the capsule, measured in the object's local space. + API_FUNCTION() void Resize(float height, float radius); + protected: /// /// Creates the physics actor. @@ -210,6 +217,9 @@ protected: /// void UpdateSize() const; +private: + void GetControllerSize(float& height, float& radius) const; + public: // [Collider] #if USE_EDITOR @@ -220,6 +230,7 @@ public: void AddMovement(const Vector3& translation, const Quaternion& rotation) override; bool CanAttach(RigidBody* rigidBody) const override; RigidBody* GetAttachedRigidBody() const override; + void SetCenter(const Vector3& value) override; // [IPhysicsActor] void OnActiveTransformChanged() override; diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index e4636a568..527138cc1 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -3157,7 +3157,7 @@ void* PhysicsBackend::CreateController(void* scene, IPhysicsActor* actor, Physic desc.material = DefaultMaterial; const float minSize = 0.001f; desc.height = Math::Max(height, minSize); - desc.radius = Math::Max(radius - Math::Max(contactOffset, 0.0f), minSize); + desc.radius = Math::Max(radius, minSize); desc.stepOffset = Math::Min(stepOffset, desc.height + desc.radius * 2.0f - minSize); auto controllerPhysX = (PxCapsuleController*)scenePhysX->ControllerManager->createController(desc); PxRigidActor* actorPhysX = controllerPhysX->getActor(); @@ -3183,7 +3183,7 @@ void PhysicsBackend::SetControllerSize(void* controller, float radius, float hei { auto controllerPhysX = (PxCapsuleController*)controller; controllerPhysX->setRadius(radius); - controllerPhysX->resize(height); + controllerPhysX->setHeight(height); } void PhysicsBackend::SetControllerSlopeLimit(void* controller, float value) @@ -3204,6 +3204,12 @@ void PhysicsBackend::SetControllerStepOffset(void* controller, float value) controllerPhysX->setStepOffset(value); } +Vector3 PhysicsBackend::GetControllerBasePosition(void* controller) +{ + auto controllerPhysX = (PxCapsuleController*)controller; + return P2C(controllerPhysX->getFootPosition()); +} + Vector3 PhysicsBackend::GetControllerUpDirection(void* controller) { auto controllerPhysX = (PxCapsuleController*)controller; diff --git a/Source/Engine/Physics/PhysicsBackend.h b/Source/Engine/Physics/PhysicsBackend.h index e6c5ffecc..c219622f1 100644 --- a/Source/Engine/Physics/PhysicsBackend.h +++ b/Source/Engine/Physics/PhysicsBackend.h @@ -248,6 +248,7 @@ public: static void SetControllerSlopeLimit(void* controller, float value); static void SetControllerNonWalkableMode(void* controller, int32 value); static void SetControllerStepOffset(void* controller, float value); + static Vector3 GetControllerBasePosition(void* controller); static Vector3 GetControllerUpDirection(void* controller); static void SetControllerUpDirection(void* controller, const Vector3& value); static Vector3 GetControllerPosition(void* controller);