You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
559 lines
26 KiB
C++
559 lines
26 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include <AzFramework/Physics/PhysicsScene.h>
|
|
|
|
#include <PhysXCharacters/API/CharacterController.h>
|
|
#include <PhysXCharacters/API/CharacterUtils.h>
|
|
#include <PhysXCharacters/Components/CharacterControllerComponent.h>
|
|
#include <PhysXCharacters/Components/CharacterGameplayComponent.h>
|
|
|
|
#include <AZTestShared/Math/MathTestHelpers.h>
|
|
#include <AZTestShared/Utils/Utils.h>
|
|
#include <AzFramework/Components/TransformComponent.h>
|
|
#include <PhysX/ComponentTypeIds.h>
|
|
#include <PhysX/SystemComponentBus.h>
|
|
#include <Source/SphereColliderComponent.h>
|
|
#include <Source/CapsuleColliderComponent.h>
|
|
#include <System/PhysXSystem.h>
|
|
#include <Tests/PhysXTestFixtures.h>
|
|
#include <Tests/PhysXTestUtil.h>
|
|
#include <Tests/PhysXTestCommon.h>
|
|
|
|
namespace PhysX
|
|
{
|
|
namespace Internal
|
|
{
|
|
void AddColliderComponentToEntity(AZ::Entity* entity, const Physics::ColliderConfiguration& colliderConfiguration, const Physics::ShapeConfiguration& shapeConfiguration)
|
|
{
|
|
Physics::ShapeType shapeType = shapeConfiguration.GetShapeType();
|
|
|
|
switch (shapeType)
|
|
{
|
|
case Physics::ShapeType::Sphere:
|
|
{
|
|
const Physics::SphereShapeConfiguration& sphereConfiguration = static_cast<const Physics::SphereShapeConfiguration&>(shapeConfiguration);
|
|
auto sphereColliderComponent = entity->CreateComponent<SphereColliderComponent>();
|
|
sphereColliderComponent->SetShapeConfigurationList({ AZStd::make_pair(
|
|
AZStd::make_shared<Physics::ColliderConfiguration>(colliderConfiguration),
|
|
AZStd::make_shared<Physics::SphereShapeConfiguration>(sphereConfiguration)) });
|
|
}
|
|
break;
|
|
case Physics::ShapeType::Box:
|
|
{
|
|
const Physics::BoxShapeConfiguration& boxConfiguration = static_cast<const Physics::BoxShapeConfiguration&>(shapeConfiguration);
|
|
auto boxColliderComponent = entity->CreateComponent<BoxColliderComponent>();
|
|
boxColliderComponent->SetShapeConfigurationList({ AZStd::make_pair(
|
|
AZStd::make_shared<Physics::ColliderConfiguration>(colliderConfiguration),
|
|
AZStd::make_shared<Physics::BoxShapeConfiguration>(boxConfiguration)) });
|
|
}
|
|
break;
|
|
case Physics::ShapeType::Capsule:
|
|
{
|
|
const Physics::CapsuleShapeConfiguration& capsuleConfiguration = static_cast<const Physics::CapsuleShapeConfiguration&>(shapeConfiguration);
|
|
auto capsuleColliderComponent = entity->CreateComponent<CapsuleColliderComponent>();
|
|
capsuleColliderComponent->SetShapeConfigurationList({ AZStd::make_pair(
|
|
AZStd::make_shared<Physics::ColliderConfiguration>(colliderConfiguration),
|
|
AZStd::make_shared<Physics::CapsuleShapeConfiguration>(capsuleConfiguration)) });
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
AZ_Error("PhysX", false,
|
|
"AddColliderComponentToEntity(): Using Shape of type %d is not implemented.", static_cast<AZ::u8>(shapeType));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// transform for a floor centred at x = 0, y = 0, with top at level z = 0
|
|
static const AZ::Transform DefaultFloorTransform = AZ::Transform::CreateTranslation(AZ::Vector3::CreateAxisZ(-0.5f));
|
|
|
|
class ControllerTestBasis
|
|
{
|
|
public:
|
|
ControllerTestBasis(AzPhysics::SceneHandle sceneHandle,
|
|
const Physics::ShapeType shapeType = Physics::ShapeType::Capsule,
|
|
const AZ::Transform& floorTransform = DefaultFloorTransform)
|
|
: m_sceneHandle(sceneHandle)
|
|
{
|
|
if (auto* physicsSystem = AZ::Interface<AzPhysics::SystemInterface>::Get())
|
|
{
|
|
m_testScene = physicsSystem->GetScene(m_sceneHandle);
|
|
}
|
|
AZ_Assert(m_testScene != nullptr, "ControllerTestBasis: the Test scene is null.");
|
|
SetUp(shapeType, floorTransform);
|
|
}
|
|
|
|
void SetUp(const Physics::ShapeType shapeType = Physics::ShapeType::Capsule,
|
|
const AZ::Transform& floorTransform = DefaultFloorTransform)
|
|
{
|
|
m_floor = PhysX::TestUtils::AddStaticFloorToScene(m_sceneHandle, floorTransform);
|
|
|
|
m_controllerEntity = AZStd::make_unique<AZ::Entity>("CharacterEntity");
|
|
m_controllerEntity->CreateComponent<AzFramework::TransformComponent>()->SetWorldTM(AZ::Transform::Identity());
|
|
|
|
auto characterConfiguration = AZStd::make_unique<Physics::CharacterConfiguration>();
|
|
characterConfiguration->m_maximumSlopeAngle = 25.0f;
|
|
characterConfiguration->m_stepHeight = 0.2f;
|
|
|
|
if (shapeType == Physics::ShapeType::Capsule)
|
|
{
|
|
auto capsuleShapeConfiguration = AZStd::make_unique<Physics::CapsuleShapeConfiguration>();
|
|
m_controllerEntity->CreateComponent<CharacterControllerComponent>(AZStd::move(characterConfiguration),
|
|
AZStd::move(capsuleShapeConfiguration));
|
|
}
|
|
else
|
|
{
|
|
auto boxShapeConfiguration = AZStd::make_unique<Physics::BoxShapeConfiguration>(AZ::Vector3(0.5f, 0.5f, 1.0f));
|
|
m_controllerEntity->CreateComponent<CharacterControllerComponent>(AZStd::move(characterConfiguration),
|
|
AZStd::move(boxShapeConfiguration));
|
|
}
|
|
|
|
m_controllerEntity->Init();
|
|
m_controllerEntity->Activate();
|
|
|
|
Physics::CharacterRequestBus::EventResult(m_controller, m_controllerEntity->GetId(), &Physics::CharacterRequests::GetCharacter);
|
|
}
|
|
|
|
void Update(const AZ::Vector3& velocity, AZ::u32 numTimeSteps = 1)
|
|
{
|
|
if (auto* physXSystem = GetPhysXSystem())
|
|
{
|
|
for (AZ::u32 i = 0; i < numTimeSteps; i++)
|
|
{
|
|
Physics::CharacterRequestBus::Event(m_controllerEntity->GetId(), &Physics::CharacterRequests::AddVelocity, velocity);
|
|
physXSystem->Simulate(m_timeStep);
|
|
}
|
|
}
|
|
}
|
|
|
|
AzPhysics::Scene* m_testScene;
|
|
AzPhysics::SceneHandle m_sceneHandle;
|
|
AzPhysics::StaticRigidBody* m_floor;
|
|
AZStd::unique_ptr<AZ::Entity> m_controllerEntity;
|
|
Physics::Character* m_controller = nullptr;
|
|
float m_timeStep = AzPhysics::SystemConfiguration::DefaultFixedTimestep;
|
|
};
|
|
|
|
Physics::ShapeType controllerShapeTypes[] = { Physics::ShapeType::Capsule, Physics::ShapeType::Box };
|
|
|
|
TEST_F(PhysXDefaultWorldTest, CharacterController_UnimpededController_MovesAtDesiredVelocity)
|
|
{
|
|
ControllerTestBasis basis(m_testSceneHandle);
|
|
basis.Update(AZ::Vector3::CreateZero());
|
|
AZ::Vector3 desiredVelocity = AZ::Vector3::CreateAxisX();
|
|
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
AZ::Vector3 basePosition = basis.m_controller->GetBasePosition();
|
|
EXPECT_TRUE(basePosition.IsClose(AZ::Vector3::CreateAxisX(basis.m_timeStep * i)));
|
|
basis.Update(desiredVelocity);
|
|
EXPECT_TRUE(basis.m_controller->GetVelocity().IsClose(desiredVelocity));
|
|
}
|
|
}
|
|
|
|
TEST_F(PhysXDefaultWorldTest, CharacterController_MovingDirectlyTowardsStaticBox_StoppedByBox)
|
|
{
|
|
ControllerTestBasis basis(m_testSceneHandle);
|
|
AZ::Vector3 velocity = AZ::Vector3::CreateAxisX();
|
|
|
|
PhysX::TestUtils::AddStaticUnitBoxToScene(basis.m_sceneHandle, AZ::Vector3(1.5f, 0.0f, 0.5f));
|
|
|
|
// run the simulation for a while so the controller should get to the box and stop
|
|
basis.Update(velocity, 50);
|
|
|
|
// the edge of the box is at x = 1.0, we expect to stop a distance short of that given by the sum of the
|
|
// capsule radius (0.25) and the contact offset (0.1)
|
|
AZ::Vector3 basePosition = basis.m_controller->GetBasePosition();
|
|
EXPECT_TRUE(basePosition.IsClose(AZ::Vector3::CreateAxisX(0.65f)));
|
|
|
|
// run the simulation some more and check that the controller is not moving in the direction of the box
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
AZ::Vector3 newBasePosition = basis.m_controller->GetBasePosition();
|
|
EXPECT_TRUE(newBasePosition.IsClose(basePosition));
|
|
EXPECT_TRUE(basis.m_controller->GetVelocity().IsClose(AZ::Vector3::CreateZero()));
|
|
basePosition = newBasePosition;
|
|
basis.Update(velocity);
|
|
}
|
|
}
|
|
|
|
TEST_F(PhysXDefaultWorldTest, CharacterController_MovingDiagonallyTowardsStaticBox_SlidesAlongBox)
|
|
{
|
|
ControllerTestBasis basis(m_testSceneHandle);
|
|
AZ::Vector3 velocity = AZ::Vector3(1.0f, 1.0f, 0.0f);
|
|
|
|
PhysX::TestUtils::AddStaticUnitBoxToScene(basis.m_sceneHandle, AZ::Vector3(1.0f, 0.5f, 0.5f));
|
|
|
|
// run the simulation for a while so the controller should get to the box and start sliding
|
|
basis.Update(velocity, 20);
|
|
|
|
// the controller should be sliding in the y direction now
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
velocity = basis.m_controller->GetVelocity();
|
|
float vx = velocity.GetX();
|
|
float vy = velocity.GetY();
|
|
EXPECT_NEAR(vx, 0.0f, 1e-3f);
|
|
EXPECT_NEAR(vy, 1.0f, 1e-3f);
|
|
basis.Update(velocity);
|
|
}
|
|
}
|
|
|
|
TEST_F(PhysXDefaultWorldTest, CharacterController_MovingOnSlope_CannotMoveAboveMaximumSlopeAngle)
|
|
{
|
|
// create a floor sloped at 30 degrees which should just be touching a controller with base position at the
|
|
// origin, with radius + contact offset = 0.25 + 0.1 = 0.35
|
|
AZ::Transform slopedFloorTransform = AZ::Transform::CreateRotationY(-AZ::Constants::Pi / 6.0f);
|
|
slopedFloorTransform.SetTranslation(
|
|
AZ::Vector3::CreateAxisZ(0.35f) + slopedFloorTransform.TransformPoint(AZ::Vector3::CreateAxisZ(-0.85f)));
|
|
ControllerTestBasis basis(m_testSceneHandle, Physics::ShapeType::Capsule, slopedFloorTransform);
|
|
|
|
// we should be able to travel at right angles to the slope
|
|
AZ::Vector3 desiredVelocity = AZ::Vector3::CreateAxisY();
|
|
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
basis.Update(desiredVelocity);
|
|
EXPECT_TRUE(basis.m_controller->GetVelocity().IsClose(desiredVelocity));
|
|
}
|
|
|
|
// we should slide if we try to travel diagonally up the slope as it is steeper than our maximum of 25 degrees
|
|
desiredVelocity = AZ::Vector3(1.0f, 1.0f, 0.0f);
|
|
|
|
// run a few frames to adjust to the change in direction
|
|
basis.Update(desiredVelocity, 10);
|
|
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
basis.Update(desiredVelocity);
|
|
AZ::Vector3 velocity = basis.m_controller->GetVelocity();
|
|
float vx = velocity.GetX();
|
|
float vy = velocity.GetY();
|
|
EXPECT_NEAR(vx, 0.0f, 1e-3f);
|
|
EXPECT_NEAR(vy, 1.0f, 1e-3f);
|
|
}
|
|
|
|
// shouldn't be able to travel directly up the 30 degree slope as our maximum slope angle is 25 degrees
|
|
desiredVelocity = AZ::Vector3(1.0f, 0.0f, 0.0f);
|
|
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
basis.Update(desiredVelocity);
|
|
EXPECT_TRUE(basis.m_controller->GetVelocity().IsClose(AZ::Vector3::CreateZero()));
|
|
}
|
|
|
|
// should be able to move down the slope
|
|
desiredVelocity = AZ::Vector3(-1.0f, 0.0f, -0.5f);
|
|
|
|
// run a few frames to adjust to the change in direction
|
|
basis.Update(desiredVelocity, 10);
|
|
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
basis.Update(desiredVelocity);
|
|
EXPECT_TRUE(basis.m_controller->GetVelocity().IsClose(desiredVelocity));
|
|
}
|
|
}
|
|
|
|
TEST_F(PhysXDefaultWorldTest, CharacterController_Steps_StoppedByTallStep)
|
|
{
|
|
ControllerTestBasis basis(m_testSceneHandle);
|
|
|
|
PhysX::TestUtils::AddStaticUnitBoxToScene(basis.m_sceneHandle, AZ::Vector3(1.0f, 0.0f, -0.3f));
|
|
PhysX::TestUtils::AddStaticUnitBoxToScene(basis.m_sceneHandle, AZ::Vector3(2.0f, 0.0f, 0.5f));
|
|
|
|
AZ::Vector3 desiredVelocity = AZ::Vector3::CreateAxisX();
|
|
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
basis.Update(desiredVelocity);
|
|
AZ::Vector3 velocity = basis.m_controller->GetVelocity();
|
|
float vx = velocity.GetX();
|
|
float vy = velocity.GetY();
|
|
EXPECT_NEAR(vx, 1.0f, 1e-3f);
|
|
EXPECT_NEAR(vy, 0.0f, 1e-3f);
|
|
}
|
|
|
|
// expect the base of the controller to now be at the height of the short step (0.2)
|
|
float expectedBaseHeight = 0.2f;
|
|
float baseHeight = basis.m_controller->GetBasePosition().GetZ();
|
|
EXPECT_NEAR(baseHeight, expectedBaseHeight, 1e-3f);
|
|
|
|
// after another 50 updates, we should have been stopped by the tall step
|
|
basis.Update(desiredVelocity, 50);
|
|
EXPECT_TRUE(basis.m_controller->GetVelocity().IsClose(AZ::Vector3::CreateZero()));
|
|
baseHeight = basis.m_controller->GetBasePosition().GetZ();
|
|
EXPECT_NEAR(baseHeight, expectedBaseHeight, 1e-3f);
|
|
}
|
|
|
|
using CharacterControllerFixture = PhysXDefaultWorldTestWithParam<Physics::ShapeType>;
|
|
|
|
TEST_P(CharacterControllerFixture, CharacterController_ResizedController_CannotFitUnderLowBox)
|
|
{
|
|
Physics::ShapeType shapeType = GetParam();
|
|
ControllerTestBasis basis(m_testSceneHandle, shapeType);
|
|
|
|
// the bottom of the box will be at height 1.0
|
|
PhysX::TestUtils::AddStaticUnitBoxToScene(basis.m_sceneHandle, AZ::Vector3(1.0f, 0.0f, 1.5f));
|
|
|
|
// resize the controller so that it is too tall to fit under the box
|
|
auto controller = static_cast<CharacterController*>(basis.m_controller);
|
|
controller->Resize(1.3f);
|
|
EXPECT_NEAR(controller->GetHeight(), 1.3f, 1e-3f);
|
|
|
|
const AZ::Vector3 desiredVelocity = AZ::Vector3::CreateAxisX();
|
|
|
|
basis.Update(desiredVelocity, 50);
|
|
// movement should be impeded by the box because the controller is too tall to go under it
|
|
EXPECT_TRUE(basis.m_controller->GetVelocity().IsClose(AZ::Vector3::CreateZero()));
|
|
|
|
// resize the controller to a bit less tall than the height of the bottom of the box
|
|
// leave some leeway under the box to account for the contact offset of the controller
|
|
controller->Resize(0.6f);
|
|
EXPECT_NEAR(controller->GetHeight(), 0.6f, 1e-3f);
|
|
|
|
basis.Update(desiredVelocity, 50);
|
|
// movement should now be unimpeded because the controller is short enough to go under the box
|
|
const AZ::Vector3 velocity = basis.m_controller->GetVelocity();
|
|
const float vx = velocity.GetX();
|
|
const float vy = velocity.GetY();
|
|
EXPECT_NEAR(vx, 1.0f, 1e-3f);
|
|
EXPECT_NEAR(vy, 0.0f, 1e-3f);
|
|
}
|
|
|
|
TEST_P(CharacterControllerFixture, CharacterController_ResizingToNegativeHeight_EmitsError)
|
|
{
|
|
Physics::ShapeType shapeType = GetParam();
|
|
ControllerTestBasis basis(m_testSceneHandle, shapeType);
|
|
auto controller = static_cast<CharacterController*>(basis.m_controller);
|
|
UnitTest::ErrorHandler errorHandler("PhysX requires controller height to be positive");
|
|
controller->Resize(-0.2f);
|
|
EXPECT_EQ(errorHandler.GetErrorCount(), 1);
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(PhysXCharacters, CharacterControllerFixture, ::testing::ValuesIn(controllerShapeTypes));
|
|
|
|
TEST_F(PhysXDefaultWorldTest, CharacterController_ResizingCapsuleControllerBelowTwiceRadius_EmitsError)
|
|
{
|
|
ControllerTestBasis basis(m_testSceneHandle);
|
|
|
|
auto controller = static_cast<CharacterController*>(basis.m_controller);
|
|
// the controller will have been made with the default radius of 0.25, so any height under 0.5 should
|
|
// be impossible
|
|
UnitTest::ErrorHandler errorHandler("Capsule height must exceed twice its radius");
|
|
controller->Resize(0.45f);
|
|
EXPECT_EQ(errorHandler.GetErrorCount(), 1);
|
|
|
|
// the controller should still have the default height of 1
|
|
EXPECT_NEAR(controller->GetHeight(), 1.0f, 1e-3f);
|
|
}
|
|
|
|
TEST_F(PhysXDefaultWorldTest, CharacterController_DroppingBox_CollidesWithController)
|
|
{
|
|
ControllerTestBasis basis(m_testSceneHandle);
|
|
|
|
AzPhysics::RigidBody* box = PhysX::TestUtils::AddUnitBoxToScene(m_testSceneHandle, AZ::Vector3(0.5f, 0.0f, 5.0f));
|
|
|
|
basis.Update(AZ::Vector3::CreateZero(), 200);
|
|
|
|
// the box and controller have default collision layer and group so should collide
|
|
// the box was positioned to land on its edge on the controller
|
|
// so expect the box to have bounced off the controller and traveled in the x direction
|
|
AZ::Vector3 boxPosition = box->GetPosition();
|
|
float x = boxPosition.GetX();
|
|
EXPECT_GT(x, 2.0f);
|
|
}
|
|
|
|
TEST_F(PhysXDefaultWorldTest, CharacterController_RaycastAgainstController_ReturnsHit)
|
|
{
|
|
auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get();
|
|
|
|
// raycast on an empty scene should return no hits
|
|
AzPhysics::RayCastRequest request;
|
|
request.m_start = AZ::Vector3(-100.0f, 0.0f, 0.25f);
|
|
request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f);
|
|
request.m_distance = 200.0f;
|
|
|
|
AzPhysics::SceneQueryHits result = sceneInterface->QueryScene(m_testSceneHandle, &request);
|
|
EXPECT_FALSE(result);
|
|
|
|
// now add a controller and raycast again
|
|
ControllerTestBasis basis(m_testSceneHandle);
|
|
|
|
// the controller won't move to its initial position with its base at the origin until one update has happened
|
|
basis.Update(AZ::Vector3::CreateZero());
|
|
|
|
result = sceneInterface->QueryScene(m_testSceneHandle, &request);
|
|
EXPECT_TRUE(result);
|
|
}
|
|
|
|
TEST_F(PhysXDefaultWorldTest, CharacterController_DeleteCharacterInsideTrigger_RaisesExitEvent)
|
|
{
|
|
// Create trigger
|
|
Physics::ColliderConfiguration triggerConfig;
|
|
triggerConfig.m_isTrigger = true;
|
|
Physics::BoxShapeConfiguration boxConfig;
|
|
boxConfig.m_dimensions = AZ::Vector3(10.0f, 10.0f, 10.0f);
|
|
|
|
auto triggerEntity = AZStd::make_unique<AZ::Entity>("TriggerEntity");
|
|
triggerEntity->CreateComponent<AzFramework::TransformComponent>()->SetWorldTM(AZ::Transform::Identity());
|
|
triggerEntity->CreateComponent(PhysX::StaticRigidBodyComponentTypeId);
|
|
Internal::AddColliderComponentToEntity(triggerEntity.get(), triggerConfig, boxConfig);
|
|
triggerEntity->Init();
|
|
triggerEntity->Activate();
|
|
|
|
TestTriggerAreaNotificationListener triggerListener(triggerEntity->GetId());
|
|
|
|
// Create character
|
|
auto characterConfiguration = AZStd::make_unique<Physics::CharacterConfiguration>();
|
|
auto characterShapeConfiguration = AZStd::make_unique<Physics::CapsuleShapeConfiguration>();
|
|
characterShapeConfiguration->m_height = 5.0f;
|
|
characterShapeConfiguration->m_radius = 1.0f;
|
|
|
|
auto characterEntity = AZStd::make_unique<AZ::Entity>("CharacterEntity");
|
|
characterEntity->CreateComponent<AzFramework::TransformComponent>()->SetWorldTM(AZ::Transform::Identity());
|
|
characterEntity->CreateComponent<CharacterControllerComponent>(
|
|
AZStd::move(characterConfiguration), AZStd::move(characterShapeConfiguration));
|
|
characterEntity->Init();
|
|
characterEntity->Activate();
|
|
|
|
// Update the world a bit to trigger Enter events
|
|
TestUtils::UpdateScene(m_defaultScene, 0.1f, 10);
|
|
|
|
// Delete the entity, and update the world to receive exit events
|
|
characterEntity.reset();
|
|
TestUtils::UpdateScene(m_defaultScene, 0.1f, 1);
|
|
|
|
EXPECT_EQ(triggerListener.GetEnteredEvents().size(), 1);
|
|
EXPECT_EQ(triggerListener.GetExitedEvents().size(), 1);
|
|
}
|
|
|
|
TEST_F(PhysXDefaultWorldTest, CharacterController_DisabledPhysics_DoesNotCauseError_FT)
|
|
{
|
|
// given a character controller
|
|
auto characterConfiguration = AZStd::make_unique<Physics::CharacterConfiguration>();
|
|
auto characterShapeConfiguration = AZStd::make_unique<Physics::CapsuleShapeConfiguration>();
|
|
characterShapeConfiguration->m_height = 5.0f;
|
|
characterShapeConfiguration->m_radius = 1.0f;
|
|
|
|
auto characterEntity = AZStd::make_unique<AZ::Entity>("CharacterEntity");
|
|
characterEntity->CreateComponent<AzFramework::TransformComponent>()->SetWorldTM(AZ::Transform::Identity());
|
|
characterEntity->CreateComponent<CharacterControllerComponent>(
|
|
AZStd::move(characterConfiguration), AZStd::move(characterShapeConfiguration));
|
|
characterEntity->Init();
|
|
characterEntity->Activate();
|
|
|
|
bool physicsEnabled = false;
|
|
AzPhysics::SimulatedBodyComponentRequestsBus::EventResult(physicsEnabled, characterEntity->GetId(),
|
|
&AzPhysics::SimulatedBodyComponentRequestsBus::Events::IsPhysicsEnabled);
|
|
EXPECT_TRUE(physicsEnabled);
|
|
|
|
// when physics is disabled
|
|
AzPhysics::SimulatedBodyComponentRequestsBus::Event(characterEntity->GetId(), &AzPhysics::SimulatedBodyComponentRequestsBus::Events::DisablePhysics);
|
|
AzPhysics::SimulatedBodyComponentRequestsBus::EventResult(physicsEnabled, characterEntity->GetId(),
|
|
&AzPhysics::SimulatedBodyComponentRequestsBus::Events::IsPhysicsEnabled);
|
|
EXPECT_FALSE(physicsEnabled);
|
|
|
|
// expect no error occurs when sending common events
|
|
AZ::Vector3 result;
|
|
UnitTest::ErrorHandler errorHandler("Invalid character controller.");
|
|
Physics::CharacterRequestBus::Event(
|
|
characterEntity->GetId(),&Physics::CharacterRequestBus::Events::AddVelocity, AZ::Vector3::CreateZero());
|
|
EXPECT_EQ(errorHandler.GetErrorCount(), 0);
|
|
|
|
Physics::CharacterRequestBus::EventResult(
|
|
result, characterEntity->GetId(), &Physics::CharacterRequestBus::Events::GetBasePosition);
|
|
EXPECT_EQ(errorHandler.GetErrorCount(), 0);
|
|
|
|
Physics::CharacterRequestBus::EventResult(
|
|
result, characterEntity->GetId(), &Physics::CharacterRequestBus::Events::GetCenterPosition);
|
|
EXPECT_EQ(errorHandler.GetErrorCount(), 0);
|
|
|
|
Physics::CharacterRequestBus::EventResult(
|
|
result, characterEntity->GetId(), &Physics::CharacterRequestBus::Events::GetVelocity);
|
|
EXPECT_EQ(errorHandler.GetErrorCount(), 0);
|
|
|
|
CharacterControllerRequestBus::Event(
|
|
characterEntity->GetId(), &CharacterControllerRequestBus::Events::Resize, 2.f);
|
|
EXPECT_EQ(errorHandler.GetErrorCount(), 0);
|
|
|
|
float height = -1.f;
|
|
CharacterControllerRequestBus::EventResult(
|
|
height, characterEntity->GetId(), &CharacterControllerRequestBus::Events::GetHeight);
|
|
EXPECT_EQ(errorHandler.GetErrorCount(), 0);
|
|
|
|
AZ::TransformNotificationBus::Event(
|
|
characterEntity->GetId(), &AZ::TransformNotificationBus::Events::OnTransformChanged,
|
|
AZ::Transform::CreateIdentity(), AZ::Transform::CreateIdentity());
|
|
EXPECT_EQ(errorHandler.GetErrorCount(), 0);
|
|
}
|
|
|
|
TEST_F(PhysXDefaultWorldTest, CharacterController_SetNoneCollisionGroupAfterCreation_DoesNotTrigger)
|
|
{
|
|
// Create character
|
|
auto characterEntity = AZStd::make_unique<AZ::Entity>("CharacterEntity");
|
|
{
|
|
auto characterConfiguration = AZStd::make_unique<Physics::CharacterConfiguration>();
|
|
auto characterShapeConfiguration = AZStd::make_unique<Physics::CapsuleShapeConfiguration>();
|
|
characterShapeConfiguration->m_height = 1.5f;
|
|
characterShapeConfiguration->m_radius = 0.5f;
|
|
characterEntity->CreateComponent<AzFramework::TransformComponent>()->SetWorldTM(AZ::Transform::Identity());
|
|
characterEntity->CreateComponent<CharacterControllerComponent>(
|
|
AZStd::move(characterConfiguration), AZStd::move(characterShapeConfiguration));
|
|
}
|
|
characterEntity->Init();
|
|
characterEntity->Activate();
|
|
|
|
// Set the callback so that collision groups determine what the character interacts with
|
|
Physics::Character* character = nullptr;
|
|
Physics::CharacterRequestBus::EventResult(character, characterEntity->GetId(), &Physics::CharacterRequests::GetCharacter);
|
|
if (character)
|
|
{
|
|
auto controller = static_cast<PhysX::CharacterController*>(character);
|
|
controller->SetFilterFlags(physx::PxQueryFlag::eSTATIC | physx::PxQueryFlag::eDYNAMIC | physx::PxQueryFlag::ePREFILTER);
|
|
if (auto callbackManager = controller->GetCallbackManager())
|
|
{
|
|
callbackManager->SetObjectPreFilter(CollisionLayerBasedObjectPreFilter);
|
|
}
|
|
}
|
|
|
|
// Create unit box located near character, collides with character by default
|
|
PhysX::TestUtils::AddStaticUnitBoxToScene(m_testSceneHandle, AZ::Vector3(1.0f, 0.0f, 0.0f));
|
|
|
|
// Assign 'None' collision group to character controller - it should not collide with the box
|
|
AZStd::string collisionGroupName;
|
|
Physics::CollisionRequestBus::BroadcastResult(collisionGroupName,
|
|
&Physics::CollisionRequests::GetCollisionGroupName, AzPhysics::CollisionGroup::None);
|
|
|
|
Physics::CollisionFilteringRequestBus::Event(
|
|
characterEntity->GetId(), &Physics::CollisionFilteringRequests::SetCollisionGroup, collisionGroupName, AZ::Crc32());
|
|
|
|
// Try to move character in direction of the box
|
|
const AZ::Vector3 velocity(2.0f, 0.0f, 0.0f);
|
|
float totalTime = 0.0f;
|
|
float timeStep = AzPhysics::SystemConfiguration::DefaultFixedTimestep;
|
|
|
|
if (auto* physXSystem = GetPhysXSystem())
|
|
{
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
Physics::CharacterRequestBus::Event(characterEntity->GetId(), &Physics::CharacterRequestBus::Events::AddVelocity, velocity);
|
|
physXSystem->Simulate(timeStep);
|
|
totalTime += timeStep;
|
|
}
|
|
}
|
|
|
|
// With 'None' collision group assigned, character is expected to pass through the box to target position
|
|
AZ::Vector3 characterTranslation = characterEntity->GetTransform()->GetWorldTranslation();
|
|
EXPECT_THAT(characterTranslation, UnitTest::IsCloseTolerance(velocity * totalTime, 0.01f));
|
|
}
|
|
} // namespace PhysX
|