diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index f1a467348..74c324bad 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -21,6 +21,7 @@ CharacterController::CharacterController(const SpawnParams& params) , _upDirection(Vector3::Up) , _gravityDisplacement(Vector3::Zero) , _nonWalkableMode(NonWalkableModes::PreventClimbing) + , _originMode(OriginModes::CapsuleCenter) , _lastFlags(CollisionFlags::None) { _contactOffset = 10.0f; @@ -35,9 +36,7 @@ void CharacterController::SetRadius(const float value) { if (value == _radius) return; - _radius = value; - UpdateSize(); UpdateBounds(); } @@ -51,9 +50,7 @@ void CharacterController::SetHeight(const float value) { if (value == _height) return; - _height = value; - UpdateSize(); UpdateBounds(); } @@ -87,6 +84,23 @@ void CharacterController::SetNonWalkableMode(NonWalkableModes value) PhysicsBackend::SetControllerNonWalkableMode(_controller, (int32)value); } +CharacterController::OriginModes CharacterController::GetOriginMode() const +{ + return _originMode; +} + +void CharacterController::SetOriginMode(OriginModes value) +{ + if (_originMode == value) + return; + _originMode = value; + if (_controller) + { + DeleteController(); + CreateController(); + } +} + float CharacterController::GetStepOffset() const { return _stepOffset; @@ -165,13 +179,23 @@ CharacterController::CollisionFlags CharacterController::SimpleMove(const Vector CharacterController::CollisionFlags CharacterController::Move(const Vector3& displacement) { CollisionFlags result = CollisionFlags::None; - if (_controller) + if (_controller && !_isUpdatingTransform) { + // Perform move const float deltaTime = Time::GetCurrentSafe()->DeltaTime.GetTotalSeconds(); result = (CollisionFlags)PhysicsBackend::MoveController(_controller, _shape, displacement, _minMoveDistance, deltaTime); _lastFlags = result; - Vector3 position = PhysicsBackend::GetControllerPosition(_controller) - _center; + + // Update position + Vector3 position; + if (_originMode == OriginModes::Base) + position = PhysicsBackend::GetControllerBasePosition(_controller); + else + position = PhysicsBackend::GetControllerPosition(_controller); + position -= _center; + _isUpdatingTransform = true; SetPosition(position); + _isUpdatingTransform = false; } return result; } @@ -194,10 +218,21 @@ void CharacterController::Resize(float height, float radius) 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); + Vector3 position; + switch (_originMode) + { + case OriginModes::CapsuleCenter: + position = PhysicsBackend::GetControllerPosition(_controller); + position += positionDelta; + _center += positionDelta; + PhysicsBackend::SetControllerPosition(_controller, position); + break; + case OriginModes::Base: + position = PhysicsBackend::GetControllerBasePosition(_controller); + position += positionDelta; + PhysicsBackend::SetControllerBasePosition(_controller, position); + break; + } // Change actor position _isUpdatingTransform = true; @@ -215,7 +250,7 @@ void CharacterController::Resize(float height, float radius) void CharacterController::DrawPhysicsDebug(RenderView& view) { Quaternion rotation = Quaternion::Euler(90, 0, 0); - const Vector3 position = _transform.LocalToWorld(_center); + const Vector3 position = GetControllerPosition(); if (view.Mode == ViewMode::PhysicsColliders) DEBUG_DRAW_CAPSULE(position, rotation, _radius, _height, Color::LightYellow, 0, true); else @@ -225,23 +260,25 @@ void CharacterController::DrawPhysicsDebug(RenderView& view) void CharacterController::OnDebugDrawSelected() { Quaternion rotation = Quaternion::Euler(90, 0, 0); - const Vector3 position = _transform.LocalToWorld(_center); + const Vector3 position = GetControllerPosition(); 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 +#if 1 // 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); + Vector3 base = PhysicsBackend::GetControllerBasePosition(_controller); + Vector3 pos = PhysicsBackend::GetControllerPosition(_controller); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(base, 5.0f), Color::Red, 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos, 4.0f), Color::Red, 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos + Vector3(0, height * 0.5f, 0), 2.0f), Color::Red, 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos - Vector3(0, height * 0.5f, 0), 2.0f), Color::Red, 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos + Vector3(0, height * 0.5f, 0), radius), Color::Red.AlphaMultiplied(0.5f), 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos - Vector3(0, height * 0.5f, 0), radius), Color::Red.AlphaMultiplied(0.5f), 0, false); + DEBUG_DRAW_WIRE_CYLINDER(pos, Quaternion::Identity, radius, height, Color::Red.AlphaMultiplied(0.2f), 0, false); } DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(position, 3.0f), Color::GreenYellow, 0, false); #else @@ -267,7 +304,10 @@ void CharacterController::CreateController() _cachedScale = GetScale().GetAbsolute().MaxValue(); float height, radius; GetControllerSize(height, radius); - const Vector3 position = _transform.LocalToWorld(_center); + Vector3 position = _center; + if (_originMode == OriginModes::Base) + position += _upDirection * (_height * 0.5f + _radius); + position = _transform.LocalToWorld(position); _controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material, radius, height, _stepOffset, _shape); // Setup @@ -297,14 +337,25 @@ void CharacterController::UpdateSize() const } } +Vector3 CharacterController::GetControllerPosition() const +{ + Vector3 position = _center; + if (_originMode == OriginModes::Base) + position += _upDirection * (_height * 0.5f + _radius); + position = _transform.LocalToWorld(position); + return position; +} + void CharacterController::GetControllerSize(float& height, float& radius) const { + // Use absolute values including scale 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); - } + + // Exclude contact offset around the capsule (otherwise character floats in the air) + radius = radius - Math::Max(_contactOffset, 0.0f); + + // Prevent too small controllers height = Math::Max(height, CC_MIN_SIZE); radius = Math::Max(radius, CC_MIN_SIZE); } @@ -319,7 +370,7 @@ void CharacterController::UpdateBounds() _cachedScale = GetScale().GetAbsolute().MaxValue(); float height, radius; GetControllerSize(height, radius); - const Vector3 position = _transform.LocalToWorld(_center); + const Vector3 position = GetControllerPosition(); const Vector3 extent(radius, height * 0.5f + radius, radius); _box = BoundingBox(position - extent, position + extent); BoundingSphere::FromBox(_box, _sphere); @@ -376,7 +427,12 @@ void CharacterController::OnActiveTransformChanged() // Change actor transform (but with locking) ASSERT(!_isUpdatingTransform); _isUpdatingTransform = true; - const Vector3 position = PhysicsBackend::GetControllerPosition(_controller) - _center; + Vector3 position; + if (_originMode == OriginModes::Base) + position = PhysicsBackend::GetControllerBasePosition(_controller); + else + position = PhysicsBackend::GetControllerPosition(_controller); + position -= _center; SetPosition(position); _isUpdatingTransform = false; @@ -458,7 +514,10 @@ void CharacterController::OnTransformChanged() const Vector3 position = _transform.LocalToWorld(_center); if (!_isUpdatingTransform && _controller) { - PhysicsBackend::SetControllerPosition(_controller, position); + if (_originMode == OriginModes::Base) + PhysicsBackend::SetControllerBasePosition(_controller, position); + else + PhysicsBackend::SetControllerPosition(_controller, position); const float scale = GetScale().GetAbsolute().MaxValue(); if (_cachedScale != scale) UpdateGeometry(); diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index a95c9eb7e..918f5beb9 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -41,6 +41,22 @@ public: Below = 1 << 2, }; + /// + /// Specifies how a character controller capsule placement. + /// + API_ENUM() enum class OriginModes + { + /// + /// Character origin starts at capsule center (including Center offset properly). + /// + CapsuleCenter, + + /// + /// Character origin starts at capsule base position aka character feet placement. + /// + Base, + }; + /// /// Specifies how a character controller interacts with non-walkable parts. /// @@ -69,6 +85,7 @@ private: Vector3 _upDirection; Vector3 _gravityDisplacement; NonWalkableModes _nonWalkableMode; + OriginModes _originMode; CollisionFlags _lastFlags; public: @@ -116,6 +133,17 @@ public: /// API_PROPERTY() void SetNonWalkableMode(NonWalkableModes value); + /// + /// Gets the position origin placement mode. + /// + API_PROPERTY(Attributes="EditorOrder(216), DefaultValue(OriginModes.CapsuleCenter), EditorDisplay(\"Character Controller\")") + OriginModes GetOriginMode() const; + + /// + /// Sets the position origin placement mode. + /// + API_PROPERTY() void SetOriginMode(OriginModes value); + /// /// Gets the step height. The character will step up a stair only if it is closer to the ground than the indicated value. This should not be greater than the Character Controller’s height or it will generate an error. /// @@ -218,6 +246,7 @@ protected: void UpdateSize() const; private: + Vector3 GetControllerPosition() const; void GetControllerSize(float& height, float& radius) const; public: diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 527138cc1..b023b49f7 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -3155,10 +3155,9 @@ void* PhysicsBackend::CreateController(void* scene, IPhysicsActor* actor, Physic desc.material = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial(); else desc.material = DefaultMaterial; - const float minSize = 0.001f; - desc.height = Math::Max(height, minSize); - desc.radius = Math::Max(radius, minSize); - desc.stepOffset = Math::Min(stepOffset, desc.height + desc.radius * 2.0f - minSize); + desc.height = height; + desc.radius = radius; + desc.stepOffset = Math::Min(stepOffset, desc.height + desc.radius * 2.0f - 0.001f); auto controllerPhysX = (PxCapsuleController*)scenePhysX->ControllerManager->createController(desc); PxRigidActor* actorPhysX = controllerPhysX->getActor(); ASSERT(actorPhysX && actorPhysX->getNbShapes() == 1); @@ -3207,7 +3206,15 @@ void PhysicsBackend::SetControllerStepOffset(void* controller, float value) Vector3 PhysicsBackend::GetControllerBasePosition(void* controller) { auto controllerPhysX = (PxCapsuleController*)controller; - return P2C(controllerPhysX->getFootPosition()); + const Vector3 origin = SceneOrigins[controllerPhysX->getScene()]; + return P2C(controllerPhysX->getFootPosition()) + origin; +} + +void PhysicsBackend::SetControllerBasePosition(void* controller, const Vector3& value) +{ + auto controllerPhysX = (PxCapsuleController*)controller; + const Vector3 sceneOrigin = SceneOrigins[controllerPhysX->getScene()]; + controllerPhysX->setFootPosition(PxExtendedVec3(value.X - sceneOrigin.X, value.Y - sceneOrigin.Y, value.Z - sceneOrigin.Z)); } Vector3 PhysicsBackend::GetControllerUpDirection(void* controller) diff --git a/Source/Engine/Physics/PhysicsBackend.h b/Source/Engine/Physics/PhysicsBackend.h index c219622f1..dacd6c7fd 100644 --- a/Source/Engine/Physics/PhysicsBackend.h +++ b/Source/Engine/Physics/PhysicsBackend.h @@ -249,6 +249,7 @@ public: static void SetControllerNonWalkableMode(void* controller, int32 value); static void SetControllerStepOffset(void* controller, float value); static Vector3 GetControllerBasePosition(void* controller); + static void SetControllerBasePosition(void* controller, const Vector3& value); static Vector3 GetControllerUpDirection(void* controller); static void SetControllerUpDirection(void* controller, const Vector3& value); static Vector3 GetControllerPosition(void* controller);