/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace PhysXEditorTests { namespace { struct TestData { const AZStd::vector polygonHShape = { AZ::Vector2(0.0f, 0.0f), AZ::Vector2(0.0f, 3.0f), AZ::Vector2(1.0f, 3.0f), AZ::Vector2(1.0f, 2.0f), AZ::Vector2(2.0f, 2.0f), AZ::Vector2(2.0f, 3.0f), AZ::Vector2(3.0f, 3.0f), AZ::Vector2(3.0f, 0.0f), AZ::Vector2(2.0f, 0.0f), AZ::Vector2(2.0f, 1.0f), AZ::Vector2(1.0f, 1.0f), AZ::Vector2(1.0f, 0.0f) }; }; } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeDependencySatisfied_EntityIsValid) { EntityPtr entity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity"); entity->CreateComponent(); entity->CreateComponent(LmbrCentral::EditorBoxShapeComponentTypeId); // the entity should be in a valid state because the shape component requirement is satisfied AZ::Entity::DependencySortOutcome sortOutcome = entity->EvaluateDependenciesGetDetails(); EXPECT_TRUE(sortOutcome.IsSuccess()); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeDependencyMissing_EntityIsInvalid) { EntityPtr entity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity"); entity->CreateComponent(); // the entity should not be in a valid state because the shape collider component requires a shape component AZ::Entity::DependencySortOutcome sortOutcome = entity->EvaluateDependenciesGetDetails(); EXPECT_FALSE(sortOutcome.IsSuccess()); EXPECT_TRUE(sortOutcome.GetError().m_code == AZ::Entity::DependencySortResult::MissingRequiredService); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_MultipleShapeColliderComponents_EntityIsInvalid) { EntityPtr entity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity"); entity->CreateComponent(); entity->CreateComponent(LmbrCentral::EditorBoxShapeComponentTypeId); // adding a second shape collider component should make the entity invalid entity->CreateComponent(); // the entity should be in a valid state because the shape component requirement is satisfied AZ::Entity::DependencySortOutcome sortOutcome = entity->EvaluateDependenciesGetDetails(); EXPECT_FALSE(sortOutcome.IsSuccess()); EXPECT_TRUE(sortOutcome.GetError().m_code == AZ::Entity::DependencySortResult::HasIncompatibleServices); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderPlusMultipleColliderComponents_EntityIsValid) { EntityPtr entity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity"); entity->CreateComponent(); entity->CreateComponent(LmbrCentral::EditorBoxShapeComponentTypeId); // the shape collider component should be compatible with multiple collider components entity->CreateComponent(); entity->CreateComponent(); // the entity should be in a valid state because the shape component requirement is satisfied AZ::Entity::DependencySortOutcome sortOutcome = entity->EvaluateDependenciesGetDetails(); EXPECT_TRUE(sortOutcome.IsSuccess()); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithBox_CorrectRuntimeComponents) { // create an editor entity with a shape collider component and a box shape component EntityPtr editorEntity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity"); editorEntity->CreateComponent(); editorEntity->CreateComponent(LmbrCentral::EditorBoxShapeComponentTypeId); editorEntity->Activate(); EntityPtr gameEntity = CreateActiveGameEntityFromEditorEntity(editorEntity.get()); // check that the runtime entity has the expected components EXPECT_TRUE(gameEntity->FindComponent() != nullptr); EXPECT_TRUE(gameEntity->FindComponent(LmbrCentral::BoxShapeComponentTypeId) != nullptr); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithBox_CorrectRuntimeGeometry) { // create an editor entity with a shape collider component and a box shape component EntityPtr editorEntity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity"); editorEntity->CreateComponent(); editorEntity->CreateComponent(LmbrCentral::EditorBoxShapeComponentTypeId); editorEntity->Activate(); const AZ::Vector3 boxDimensions(2.0f, 3.0f, 4.0f); LmbrCentral::BoxShapeComponentRequestsBus::Event(editorEntity->GetId(), &LmbrCentral::BoxShapeComponentRequests::SetBoxDimensions, boxDimensions); EntityPtr gameEntity = CreateActiveGameEntityFromEditorEntity(editorEntity.get()); // since there was no editor rigid body component, the runtime entity should have a static rigid body const auto* staticBody = azdynamic_cast(gameEntity->FindComponent()->GetSimulatedBody()); const auto* pxRigidStatic = static_cast(staticBody->GetNativePointer()); PHYSX_SCENE_READ_LOCK(pxRigidStatic->getScene()); // there should be a single shape on the rigid body and it should be a box EXPECT_EQ(pxRigidStatic->getNbShapes(), 1); physx::PxShape* shape = nullptr; pxRigidStatic->getShapes(&shape, 1, 0); EXPECT_EQ(shape->getGeometryType(), physx::PxGeometryType::eBOX); // the bounding box of the rigid body should reflect the dimensions of the box set above AZ::Aabb aabb = staticBody->GetAabb(); EXPECT_TRUE(aabb.GetMax().IsClose(0.5f * boxDimensions)); EXPECT_TRUE(aabb.GetMin().IsClose(-0.5f * boxDimensions)); } void SetPolygonPrismVertices(AZ::EntityId entityId, const AZStd::vector& vertices) { AZ::PolygonPrismPtr polygonPrism; LmbrCentral::PolygonPrismShapeComponentRequestBus::EventResult(polygonPrism, entityId, &LmbrCentral::PolygonPrismShapeComponentRequests::GetPolygonPrism); if (polygonPrism) { polygonPrism->m_vertexContainer.SetVertices(vertices); } } void SetPolygonPrismHeight(AZ::EntityId entityId, float height) { AZ::PolygonPrismPtr polygonPrism; LmbrCentral::PolygonPrismShapeComponentRequestBus::EventResult(polygonPrism, entityId, &LmbrCentral::PolygonPrismShapeComponentRequests::GetPolygonPrism); if (polygonPrism) { polygonPrism->SetHeight(height); } } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithPolygonPrism_CorrectRuntimeGeometry) { // create an editor entity with a shape collider component and a polygon prism shape component EntityPtr editorEntity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity"); editorEntity->CreateComponent(); editorEntity->CreateComponent(LmbrCentral::EditorPolygonPrismShapeComponentTypeId); // suppress the shape collider error that will be raised because the polygon prism vertices have not been set yet UnitTest::ErrorHandler polygonPrismErrorHandler("Invalid polygon prism"); editorEntity->Activate(); // modify the geometry of the polygon prism TestData testData; SetPolygonPrismVertices(editorEntity->GetId(), testData.polygonHShape); SetPolygonPrismHeight(editorEntity->GetId(), 2.0f); EntityPtr gameEntity = CreateActiveGameEntityFromEditorEntity(editorEntity.get()); // since there was no editor rigid body component, the runtime entity should have a static rigid body const auto* staticBody = azdynamic_cast(gameEntity->FindComponent()->GetSimulatedBody()); const auto* pxRigidStatic = static_cast(staticBody->GetNativePointer()); PHYSX_SCENE_READ_LOCK(pxRigidStatic->getScene()); // the input polygon prism was not convex, so should have been decomposed into multiple shapes const int numShapes = pxRigidStatic->getNbShapes(); EXPECT_TRUE(numShapes > 1); // the shapes should all be convex meshes for (int shapeIndex = 0; shapeIndex < numShapes; shapeIndex++) { physx::PxShape* shape = nullptr; pxRigidStatic->getShapes(&shape, 1, shapeIndex); EXPECT_EQ(shape->getGeometryType(), physx::PxGeometryType::eCONVEXMESH); } // the vertices of the input polygon prism ranged from (0, 0) to (3, 3) and the height was set to 2 // the bounding box of the static rigid body should reflect those values AZ::Aabb aabb = staticBody->GetAabb(); EXPECT_TRUE(aabb.GetMax().IsClose(AZ::Vector3(3.0f, 3.0f, 2.0f))); EXPECT_TRUE(aabb.GetMin().IsClose(AZ::Vector3::CreateZero())); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithPolygonPrismAndNonUniformScale_CorrectRunAabb) { // create an editor entity with a shape collider component and a polygon prism shape component EntityPtr editorEntity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity"); editorEntity->CreateComponent(); editorEntity->CreateComponent(LmbrCentral::EditorPolygonPrismShapeComponentTypeId); // add a non-uniform scale component editorEntity->CreateComponent(); // suppress the shape collider error that will be raised because the polygon prism vertices have not been set yet UnitTest::ErrorHandler polygonPrismErrorHandler("Invalid polygon prism"); editorEntity->Activate(); // modify the geometry of the polygon prism TestData testData; AZ::EntityId entityId = editorEntity->GetId(); SetPolygonPrismVertices(entityId, testData.polygonHShape); SetPolygonPrismHeight(entityId, 2.0f); // update the transform scale and non-uniform scale AZ::TransformBus::Event(entityId, &AZ::TransformBus::Events::SetLocalUniformScale, 2.0f); AZ::NonUniformScaleRequestBus::Event(entityId, &AZ::NonUniformScaleRequests::SetScale, AZ::Vector3(0.5f, 1.5f, 2.0f)); EntityPtr gameEntity = CreateActiveGameEntityFromEditorEntity(editorEntity.get()); // since there was no editor rigid body component, the runtime entity should have a static rigid body const auto* staticBody = azdynamic_cast(gameEntity->FindComponent()->GetSimulatedBody()); // the vertices of the input polygon prism ranged from (0, 0) to (3, 3) and the height was set to 2 // the bounding box of the static rigid body should reflect those values combined with the scale values above AZ::Aabb aabb = staticBody->GetAabb(); EXPECT_TRUE(aabb.GetMax().IsClose(AZ::Vector3(3.0f, 9.0f, 8.0f))); EXPECT_TRUE(aabb.GetMin().IsClose(AZ::Vector3::CreateZero())); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithCylinder_CorrectRuntimeComponents) { // create an editor entity with a shape collider component and a cylinder shape component EntityPtr editorEntity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity"); editorEntity->CreateComponent(); editorEntity->CreateComponent(LmbrCentral::EditorCylinderShapeComponentTypeId); editorEntity->Activate(); EntityPtr gameEntity = CreateActiveGameEntityFromEditorEntity(editorEntity.get()); // check that the runtime entity has the expected components EXPECT_TRUE(gameEntity->FindComponent() != nullptr); EXPECT_TRUE(gameEntity->FindComponent(LmbrCentral::CylinderShapeComponentTypeId) != nullptr); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithCylinderWithValidRadiusAndValidHeight_CorrectRuntimeGeometry) { // create an editor entity with a shape collider component and a cylinder shape component EntityPtr editorEntity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity"); editorEntity->CreateComponent(); editorEntity->CreateComponent(LmbrCentral::EditorCylinderShapeComponentTypeId); editorEntity->Activate(); const float validRadius = 1.0f; const float validHeight = 1.0f; LmbrCentral::CylinderShapeComponentRequestsBus::Event(editorEntity->GetId(), &LmbrCentral::CylinderShapeComponentRequests::SetRadius, validRadius); LmbrCentral::CylinderShapeComponentRequestsBus::Event(editorEntity->GetId(), &LmbrCentral::CylinderShapeComponentRequests::SetHeight, validHeight); EntityPtr gameEntity = CreateActiveGameEntityFromEditorEntity(editorEntity.get()); // since there was no editor rigid body component, the runtime entity should have a static rigid body const auto* staticBody = azdynamic_cast(gameEntity->FindComponent()->GetSimulatedBody()); const auto* pxRigidStatic = static_cast(staticBody->GetNativePointer()); PHYSX_SCENE_READ_LOCK(pxRigidStatic->getScene()); // there should be a single shape on the rigid body and it should be a convex mesh EXPECT_EQ(pxRigidStatic->getNbShapes(), 1); physx::PxShape* shape = nullptr; pxRigidStatic->getShapes(&shape, 1, 0); EXPECT_EQ(shape->getGeometryType(), physx::PxGeometryType::eCONVEXMESH); // the bounding box of the rigid body should reflect the dimensions of the cylinder set above AZ::Aabb aabb = staticBody->GetAabb(); // Check that the z positions of the bounding box match that of the cylinder EXPECT_NEAR(aabb.GetMin().GetZ(), -0.5f * validHeight, AZ::Constants::Tolerance); EXPECT_NEAR(aabb.GetMax().GetZ(), 0.5f * validHeight, AZ::Constants::Tolerance); // check that the xy points are not outside the radius of the cylinder AZ::Vector2 vecMin(aabb.GetMin().GetX(), aabb.GetMin().GetY()); AZ::Vector2 vecMax(aabb.GetMax().GetX(), aabb.GetMax().GetY()); EXPECT_TRUE(AZ::GetAbs(vecMin.GetX()) <= validRadius); EXPECT_TRUE(AZ::GetAbs(vecMin.GetY()) <= validRadius); EXPECT_TRUE(AZ::GetAbs(vecMax.GetX()) <= validRadius); EXPECT_TRUE(AZ::GetAbs(vecMax.GetX()) <= validRadius); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithCylinderWithNullRadius_HandledGracefully) { ValidateInvalidEditorShapeColliderComponentParams(0.f, 1.f); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithCylinderWithNullHeight_HandledGracefully) { ValidateInvalidEditorShapeColliderComponentParams(1.f, 0.f); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithCylinderWithNullRadiusAndNullHeight_HandledGracefully) { ValidateInvalidEditorShapeColliderComponentParams(0.f, 0.f); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithCylinderWithNegativeRadiusAndNullHeight_HandledGracefully) { ValidateInvalidEditorShapeColliderComponentParams(-1.f, 0.f); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithCylinderWithNullRadiusAndNegativeHeight_HandledGracefully) { ValidateInvalidEditorShapeColliderComponentParams(0.f, -1.f); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithCylinderSwitchingFromNullHeightToValidHeight_HandledGracefully) { // create an editor entity with a shape collider component and a cylinder shape component EntityPtr editorEntity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity"); editorEntity->CreateComponent(); editorEntity->CreateComponent(LmbrCentral::EditorCylinderShapeComponentTypeId); editorEntity->Activate(); const float validRadius = 1.0f; const float nullHeight = 0.0f; const float validHeight = 1.0f; LmbrCentral::CylinderShapeComponentRequestsBus::Event(editorEntity->GetId(), &LmbrCentral::CylinderShapeComponentRequests::SetRadius, validRadius); { UnitTest::ErrorHandler dimensionWarningHandler("Negative or zero cylinder dimensions are invalid"); UnitTest::ErrorHandler colliderWarningHandler("No Collider or Shape information found when creating Rigid body"); LmbrCentral::CylinderShapeComponentRequestsBus::Event(editorEntity->GetId(), &LmbrCentral::CylinderShapeComponentRequests::SetHeight, nullHeight); EXPECT_EQ(dimensionWarningHandler.GetExpectedWarningCount(), 1); EXPECT_EQ(colliderWarningHandler.GetExpectedWarningCount(), 1); } { UnitTest::ErrorHandler dimensionWarningHandler("Negative or zero cylinder dimensions are invalid"); UnitTest::ErrorHandler colliderWarningHandler("No Collider or Shape information found when creating Rigid body"); LmbrCentral::CylinderShapeComponentRequestsBus::Event(editorEntity->GetId(), &LmbrCentral::CylinderShapeComponentRequests::SetHeight, validHeight); EXPECT_EQ(dimensionWarningHandler.GetExpectedWarningCount(), 0); EXPECT_EQ(colliderWarningHandler.GetExpectedWarningCount(), 0); } } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithBoxAndRigidBody_CorrectRuntimeComponents) { // create an editor entity with a shape collider component and a box shape component EntityPtr editorEntity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity"); editorEntity->CreateComponent(); editorEntity->CreateComponent(); editorEntity->CreateComponent(LmbrCentral::EditorBoxShapeComponentTypeId); editorEntity->Activate(); EntityPtr gameEntity = CreateActiveGameEntityFromEditorEntity(editorEntity.get()); // check that the runtime entity has the expected components EXPECT_TRUE(gameEntity->FindComponent() != nullptr); EXPECT_TRUE(gameEntity->FindComponent(LmbrCentral::BoxShapeComponentTypeId) != nullptr); EXPECT_TRUE(gameEntity->FindComponent() != nullptr); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithBoxAndRigidBody_CorrectRuntimeEntity) { // create an editor entity with a shape collider component and a box shape component EntityPtr editorEntity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity"); editorEntity->CreateComponent(); editorEntity->CreateComponent(); editorEntity->CreateComponent(LmbrCentral::EditorBoxShapeComponentTypeId); editorEntity->Activate(); const AZ::Vector3 boxDimensions(2.0f, 3.0f, 4.0f); LmbrCentral::BoxShapeComponentRequestsBus::Event(editorEntity->GetId(), &LmbrCentral::BoxShapeComponentRequests::SetBoxDimensions, boxDimensions); EntityPtr gameEntity = CreateActiveGameEntityFromEditorEntity(editorEntity.get()); // since there was an editor rigid body component, the runtime entity should have a dynamic rigid body const auto* rigidBody = gameEntity->FindComponent()->GetRigidBody(); const auto* pxRigidDynamic = static_cast(rigidBody->GetNativePointer()); PHYSX_SCENE_READ_LOCK(pxRigidDynamic->getScene()); // there should be a single shape on the rigid body and it should be a box EXPECT_EQ(pxRigidDynamic->getNbShapes(), 1); physx::PxShape* shape = nullptr; pxRigidDynamic->getShapes(&shape, 1, 0); EXPECT_EQ(shape->getGeometryType(), physx::PxGeometryType::eBOX); // the bounding box of the rigid body should reflect the dimensions of the box set above AZ::Aabb aabb = rigidBody->GetAabb(); EXPECT_TRUE(aabb.GetMax().IsClose(0.5f * boxDimensions)); EXPECT_TRUE(aabb.GetMin().IsClose(-0.5f * boxDimensions)); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_TransformChanged_ColliderUpdated) { // create an editor entity with a shape collider component and a box shape component EntityPtr editorEntity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity"); editorEntity->CreateComponent(); editorEntity->CreateComponent(LmbrCentral::EditorBoxShapeComponentTypeId); editorEntity->Activate(); AZ::EntityId editorEntityId = editorEntity->GetId(); AZ::Vector3 boxDimensions = AZ::Vector3::CreateOne(); LmbrCentral::BoxShapeComponentRequestsBus::EventResult(boxDimensions, editorEntityId, &LmbrCentral::BoxShapeComponentRequests::GetBoxDimensions); // update the transform const float scale = 2.0f; AZ::TransformBus::Event(editorEntityId, &AZ::TransformInterface::SetLocalUniformScale, scale); const AZ::Vector3 translation(10.0f, 20.0f, 30.0f); AZ::TransformBus::Event(editorEntityId, &AZ::TransformInterface::SetWorldTranslation, translation); // make a game entity and check its bounding box is consistent with the changed transform EntityPtr gameEntity = CreateActiveGameEntityFromEditorEntity(editorEntity.get()); const auto* staticBody = azdynamic_cast(gameEntity->FindComponent()->GetSimulatedBody()); AZ::Aabb aabb = staticBody->GetAabb(); EXPECT_TRUE(aabb.GetMax().IsClose(translation + 0.5f * scale * boxDimensions)); EXPECT_TRUE(aabb.GetMin().IsClose(translation - 0.5f * scale * boxDimensions)); } void SetTrigger(PhysX::EditorShapeColliderComponent* editorShapeColliderComponent, bool isTrigger) { AZ::SerializeContext* serializeContext = nullptr; AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); AzToolsFramework::InstanceDataHierarchy instanceDataHierarchy; instanceDataHierarchy.AddRootInstance(editorShapeColliderComponent); instanceDataHierarchy.Build(serializeContext, AZ::SerializeContext::ENUM_ACCESS_FOR_WRITE); AzToolsFramework::InstanceDataHierarchy::InstanceDataNode* instanceNode = instanceDataHierarchy.FindNodeByPartialAddress({ AZ_CRC("Trigger", 0x1a6b0f5d) }); if (instanceNode) { instanceNode->Write(isTrigger); } } EntityPtr CreateRigidBox(const AZ::Vector3& boxDimensions, const AZ::Vector3& position) { EntityPtr rigidBodyEditorEntity = CreateInactiveEditorEntity("RigidBodyEditorEntity"); rigidBodyEditorEntity->CreateComponent(); rigidBodyEditorEntity->CreateComponent(LmbrCentral::EditorBoxShapeComponentTypeId); rigidBodyEditorEntity->CreateComponent(); rigidBodyEditorEntity->Activate(); LmbrCentral::BoxShapeComponentRequestsBus::Event(rigidBodyEditorEntity->GetId(), &LmbrCentral::BoxShapeComponentRequests::SetBoxDimensions, boxDimensions); AZ::TransformBus::Event(rigidBodyEditorEntity->GetId(), &AZ::TransformInterface::SetWorldTranslation, position); AzToolsFramework::ToolsApplicationRequestBus::Broadcast( &AzToolsFramework::ToolsApplicationRequests::RemoveDirtyEntity, rigidBodyEditorEntity->GetId()); return CreateActiveGameEntityFromEditorEntity(rigidBodyEditorEntity.get()); } // LYN-1241 - Test disabled due to AZ_Error reports about MaterialLibrary being not found in the AssetCatalog TEST_F(PhysXEditorFixture, DISABLED_EditorShapeColliderComponent_PolygonPrismForceRegion_AppliesForceAtRuntime) { // create an editor entity with shape collider, polygon prism shape and force region components EntityPtr forceRegionEditorEntity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity"); auto* shapeColliderComponent = forceRegionEditorEntity->CreateComponent(); SetTrigger(shapeColliderComponent, true); forceRegionEditorEntity->CreateComponent(LmbrCentral::EditorPolygonPrismShapeComponentTypeId); forceRegionEditorEntity->CreateComponent(); // suppress the shape collider error that will be raised because the polygon prism vertices have not been set yet UnitTest::ErrorHandler polygonPrismErrorHandler("Invalid polygon prism"); forceRegionEditorEntity->Activate(); // modify the geometry of the polygon prism TestData testData; SetPolygonPrismVertices(forceRegionEditorEntity->GetId(), testData.polygonHShape); SetPolygonPrismHeight(forceRegionEditorEntity->GetId(), 2.0f); EntityPtr forceRegionGameEntity = CreateActiveGameEntityFromEditorEntity(forceRegionEditorEntity.get()); // add a force to the force region PhysX::ForceRegionRequestBus::Event(forceRegionGameEntity->GetId(), &PhysX::ForceRegionRequests::AddForceWorldSpace, AZ::Vector3::CreateAxisX(), 100.0f); const AZ::Vector3 boxDimensions(0.5f, 0.5f, 0.5f); // create one box over the centre of the polygon prism const AZ::Vector3 box1Position(1.5f, 1.5f, 3.0f); // create another box over one of the notches in the H const AZ::Vector3 box2Position(1.5f, 0.5f, 3.0f); EntityPtr rigidBodyGameEntity1 = CreateRigidBox(boxDimensions, box1Position); EntityPtr rigidBodyGameEntity2 = CreateRigidBox(boxDimensions, box2Position); PhysX::TestUtils::UpdateScene(m_defaultScene, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 100); // the first rigid body should have been moved in the positive x direction by the force region EXPECT_TRUE(rigidBodyGameEntity1->GetTransform()->GetWorldTranslation().GetX() > box1Position.GetX() + AZ::Constants::FloatEpsilon); // the second rigid body should not have entered the force region and so its X position should not have been affected EXPECT_NEAR(rigidBodyGameEntity2->GetTransform()->GetWorldTranslation().GetX(), box2Position.GetX(), 1e-3f); } TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithScaleSetToParentEntity_CorrectRuntimeScale) { // create an editor parent entity (empty, need transform component only) EntityPtr editorParentEntity = CreateInactiveEditorEntity("ParentEntity"); editorParentEntity->Activate(); // set some scale to parent entity const float parentScale = 2.0f; AZ::TransformBus::Event(editorParentEntity->GetId(), &AZ::TransformInterface::SetLocalUniformScale, parentScale); // create an editor child entity with a shape collider component and a box shape component EntityPtr editorChildEntity = CreateInactiveEditorEntity("ChildEntity"); editorChildEntity->CreateComponent(); editorChildEntity->CreateComponent(); editorChildEntity->CreateComponent(LmbrCentral::EditorBoxShapeComponentTypeId); editorChildEntity->Activate(); // set some dimensions to child entity box component const AZ::Vector3 boxDimensions(2.0f, 3.0f, 4.0f); LmbrCentral::BoxShapeComponentRequestsBus::Event(editorChildEntity->GetId(), &LmbrCentral::BoxShapeComponentRequests::SetBoxDimensions, boxDimensions); // set one entity as parent of another AZ::TransformBus::Event(editorChildEntity->GetId(), &AZ::TransformBus::Events::SetParentRelative, editorParentEntity->GetId()); // build child game entity (parent will be built implicitly) EntityPtr gameChildEntity = CreateActiveGameEntityFromEditorEntity(editorChildEntity.get()); // since there was an editor rigid body component, the runtime entity should have a dynamic rigid body const AzPhysics::RigidBody* rigidBody = gameChildEntity->FindComponent()->GetRigidBody(); // the bounding box of the rigid body should reflect the dimensions of the box set above // and parent entity scale const AZ::Aabb aabb = rigidBody->GetAabb(); EXPECT_THAT(aabb.GetMax(), UnitTest::IsClose(0.5f * boxDimensions * parentScale)); EXPECT_THAT(aabb.GetMin(), UnitTest::IsClose(-0.5f * boxDimensions * parentScale)); } } // namespace PhysXEditorTests