Files
o3de/Gems/ScriptCanvasPhysics/Code/Tests/ScriptCanvasPhysicsTest.cpp
T
Aaron Ruiz Mora 00e860f326 Physics material system for spectra launch
- Invalidate 'Physics Materials From Mesh' boolean from collider component
- Removed material library from material selector. Default material library will always be used instead.
- Marking failing automated test as xfail
- Added default material to physics configuration.
- Moved material library asset from physx configuration to physics configuration, as it doesn't need to be physx specific.
- Refactor physics material system having into account that there is only one material library in the project.
- Renaming code from DefaultMaterialLibrary to MaterialLibrary.
- All queries about physics materials unified under PhysicsMaterialRequests bus.
- PhysXSystem only manages the material library asset.
- Saving and reloading the same physics material asset with different content didn't trigger a events that the material library has changed.
- Changing Physics Material Request interface to use shared_ptr instead of weak_ptr to be simpler to handle the returned materials and having a more consistent code.
- Refactored Material Manager to improve its implementation. Still following the same approach of "creating materials on the fly as they are requested", but now it's doing it consistently across the interface, with private helpers functions FindOrCreateMaterial that simplify vastly the implementation.
- Material Manager now listens to change event of material library asset and default material configuration so it updates its materials accordingly.
- Complete Material move constructor and operator.
2021-05-28 20:16:25 +01:00

411 lines
19 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "ScriptCanvasPhysics_precompiled.h"
#include <AzTest/GemTestEnvironment.h>
#include <gmock/gmock.h>
#include <AzFramework/Physics/Material.h>
#include <AzFramework/Physics/PhysicsScene.h>
#include <AzFramework/Physics/Shape.h>
#include <AzFramework/Physics/ShapeConfiguration.h>
#include <AzFramework/Physics/ShapeConfiguration.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzFramework/Physics/Collision/CollisionGroups.h>
#include <AzFramework/Physics/Collision/CollisionLayers.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
#include <AzFramework/Physics/Common/PhysicsTypes.h>
#include <WorldNodes.h>
namespace ScriptCanvasPhysics
{
namespace WorldNodes
{
using Result = AZStd::tuple<
bool /*true if an object was hit*/,
AZ::Vector3 /*world space position*/,
AZ::Vector3 /*surface normal*/,
float /*distance to the hit*/,
AZ::EntityId /*entity hit, if any*/,
AZ::Crc32 /*tag of material surface hit, if any*/
>;
using OverlapResult = AZStd::tuple<
bool, /*true if the overlap returned hits*/
AZStd::vector<AZ::EntityId> /*list of entityIds*/
>;
extern Result RayCastWorldSpaceWithGroup(const AZ::Vector3& start,
const AZ::Vector3& direction,
float distance,
const AZStd::string& collisionGroup,
AZ::EntityId ignore);
extern Result RayCastLocalSpaceWithGroup(const AZ::EntityId& fromEntityId,
const AZ::Vector3& direction,
float distance,
const AZStd::string& collisionGroup,
AZ::EntityId ignore);
extern AZStd::vector<AzPhysics::SceneQueryHit> RayCastMultipleLocalSpaceWithGroup(const AZ::EntityId& fromEntityId,
const AZ::Vector3& direction,
float distance,
const AZStd::string& collisionGroup,
AZ::EntityId ignore);
extern Result ShapecastQuery(float distance,
const AZ::Transform& pose,
const AZ::Vector3& direction,
AZStd::shared_ptr<Physics::ShapeConfiguration> shape,
const AZStd::string& collisionGroup,
AZ::EntityId ignore);
}
}
namespace ScriptCanvasPhysicsTests
{
using namespace ::testing;
//Mocked of the AzPhysics Scene Interface. To keep things simple just mocked functions that have a return value OR required for a test.
class MockPhysicsSceneInterface
: AZ::Interface<AzPhysics::SceneInterface>::Registrar
{
public:
void StartSimulation(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] float deltatime) override {}
void FinishSimulation([[maybe_unused]] AzPhysics::SceneHandle sceneHandle) override {}
void SetEnabled(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] bool enable) override {}
void RemoveSimulatedBody(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] AzPhysics::SimulatedBodyHandle& bodyHandle) override {}
void RemoveSimulatedBodies(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] AzPhysics::SimulatedBodyHandleList& bodyHandles) override {}
void EnableSimulationOfBody(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] AzPhysics::SimulatedBodyHandle bodyHandle) override {}
void DisableSimulationOfBody(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] AzPhysics::SimulatedBodyHandle bodyHandle) override {}
void SuppressCollisionEvents(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] const AzPhysics::SimulatedBodyHandle& bodyHandleA,
[[maybe_unused]] const AzPhysics::SimulatedBodyHandle& bodyHandleB) override {}
void UnsuppressCollisionEvents(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] const AzPhysics::SimulatedBodyHandle& bodyHandleA,
[[maybe_unused]] const AzPhysics::SimulatedBodyHandle& bodyHandleB) override {}
void SetGravity(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] const AZ::Vector3& gravity) override {}
void RegisterSceneConfigurationChangedEventHandler(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] AzPhysics::SceneEvents::OnSceneConfigurationChanged::Handler& handler) override {}
void RegisterSimulationBodyAddedHandler(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] AzPhysics::SceneEvents::OnSimulationBodyAdded::Handler& handler) override {}
void RegisterSimulationBodyRemovedHandler(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] AzPhysics::SceneEvents::OnSimulationBodyRemoved::Handler& handler) override {}
void RegisterSimulationBodySimulationEnabledHandler(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] AzPhysics::SceneEvents::OnSimulationBodySimulationEnabled::Handler& handler) override {}
void RegisterSimulationBodySimulationDisabledHandler(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] AzPhysics::SceneEvents::OnSimulationBodySimulationDisabled::Handler& handler) override {}
void RegisterSceneSimulationStartHandler(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] AzPhysics::SceneEvents::OnSceneSimulationStartHandler& handler) override {}
void RegisterSceneActiveSimulatedBodiesHandler(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] AzPhysics::SceneEvents::OnSceneActiveSimulatedBodiesEvent::Handler& handler) override {}
void RegisterSceneCollisionEventHandler(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] AzPhysics::SceneEvents::OnSceneCollisionsEvent::Handler& handler) override {}
void RegisterSceneTriggersEventHandler(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] AzPhysics::SceneEvents::OnSceneTriggersEvent::Handler& handler) override {}
void RegisterSceneGravityChangedEvent(
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
[[maybe_unused]] AzPhysics::SceneEvents::OnSceneGravityChangedEvent::Handler& handler) override {}
MOCK_METHOD1(GetSceneHandle, AzPhysics::SceneHandle(const AZStd::string& sceneName));
MOCK_CONST_METHOD1(IsEnabled, bool(AzPhysics::SceneHandle sceneHandle));
MOCK_METHOD2(AddSimulatedBody, AzPhysics::SimulatedBodyHandle(AzPhysics::SceneHandle sceneHandle, const AzPhysics::SimulatedBodyConfiguration* simulatedBodyConfig));
MOCK_METHOD2(AddSimulatedBodies, AzPhysics::SimulatedBodyHandleList(AzPhysics::SceneHandle sceneHandle, const AzPhysics::SimulatedBodyConfigurationList& simulatedBodyConfigs));
MOCK_METHOD2(GetSimulatedBodyFromHandle, AzPhysics::SimulatedBody* (AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle));
MOCK_METHOD2(GetSimulatedBodiesFromHandle, AzPhysics::SimulatedBodyList(AzPhysics::SceneHandle sceneHandle, const AzPhysics::SimulatedBodyHandleList& bodyHandles));
MOCK_CONST_METHOD1(GetGravity, AZ::Vector3(AzPhysics::SceneHandle sceneHandle));
MOCK_METHOD2(RegisterSceneSimulationFinishHandler, void(AzPhysics::SceneHandle sceneHandle, AzPhysics::SceneEvents::OnSceneSimulationFinishHandler& handler));
MOCK_CONST_METHOD2(GetLegacyBody, AzPhysics::SimulatedBody* (AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle handle));
MOCK_METHOD2(QueryScene, AzPhysics::SceneQueryHits(AzPhysics::SceneHandle sceneHandle, const AzPhysics::SceneQueryRequest* request));
MOCK_METHOD2(QuerySceneBatch, AzPhysics::SceneQueryHitsList(AzPhysics::SceneHandle sceneHandle, const AzPhysics::SceneQueryRequests& requests));
MOCK_METHOD4(QuerySceneAsync, bool(AzPhysics::SceneHandle sceneHandle, AzPhysics::SceneQuery::AsyncRequestId requestId,
const AzPhysics::SceneQueryRequest* request, AzPhysics::SceneQuery::AsyncCallback callback));
MOCK_METHOD4(QuerySceneAsyncBatch, bool(AzPhysics::SceneHandle sceneHandle, AzPhysics::SceneQuery::AsyncRequestId requestId,
const AzPhysics::SceneQueryRequests& requests, AzPhysics::SceneQuery::AsyncBatchCallback callback));
};
class MockSimulatedBody
: public AzPhysics::SimulatedBody
{
public:
MOCK_CONST_METHOD0(GetEntityId, AZ::EntityId());
MOCK_CONST_METHOD0(GetTransform, AZ::Transform());
MOCK_METHOD1(SetTransform, void(const AZ::Transform& transform));
MOCK_CONST_METHOD0(GetPosition, AZ::Vector3());
MOCK_CONST_METHOD0(GetOrientation, AZ::Quaternion());
MOCK_CONST_METHOD0(GetAabb, AZ::Aabb());
MOCK_METHOD1(RayCast, AzPhysics::SceneQueryHit(const AzPhysics::RayCastRequest& request));
MOCK_CONST_METHOD0(GetNativeType, AZ::Crc32());
MOCK_CONST_METHOD0(GetNativePointer, void*());
};
class MockShape
: public Physics::Shape
{
public:
MOCK_METHOD1(SetMaterial, void(const AZStd::shared_ptr<Physics::Material>& material));
MOCK_CONST_METHOD0(GetMaterial, AZStd::shared_ptr<Physics::Material>());
MOCK_METHOD1(SetCollisionLayer, void(const AzPhysics::CollisionLayer& layer));
MOCK_CONST_METHOD0(GetCollisionLayer, AzPhysics::CollisionLayer());
MOCK_METHOD1(SetCollisionGroup, void(const AzPhysics::CollisionGroup& group));
MOCK_CONST_METHOD0(GetCollisionGroup, AzPhysics::CollisionGroup());
MOCK_METHOD1(SetName, void(const char* name));
MOCK_METHOD2(SetLocalPose, void(const AZ::Vector3& offset, const AZ::Quaternion& rotation));
MOCK_CONST_METHOD0(GetLocalPose, AZStd::pair<AZ::Vector3, AZ::Quaternion>());
MOCK_METHOD0(GetNativePointer, void*());
MOCK_CONST_METHOD0(GetTag, AZ::Crc32());
MOCK_METHOD1(AttachedToActor, void(void* actor));
MOCK_METHOD0(DetachedFromActor, void());
MOCK_METHOD2(RayCast, AzPhysics::SceneQueryHit(const AzPhysics::RayCastRequest& worldSpaceRequest, const AZ::Transform& worldTransform));
MOCK_METHOD1(RayCastLocal, AzPhysics::SceneQueryHit(const AzPhysics::RayCastRequest& localSpaceRequest));
MOCK_METHOD3(GetGeometry, void(AZStd::vector<AZ::Vector3>&, AZStd::vector<AZ::u32>&, AZ::Aabb*));
MOCK_CONST_METHOD1(GetAabb, AZ::Aabb(const AZ::Transform& worldTransform));
MOCK_CONST_METHOD0(GetAabbLocal, AZ::Aabb());
MOCK_CONST_METHOD0(GetRestOffset, float());
MOCK_METHOD1(SetRestOffset, void(float));
MOCK_CONST_METHOD0(GetContactOffset, float());
MOCK_METHOD1(SetContactOffset, void(float));
};
class MockPhysicsMaterial
: public Physics::Material
{
public:
MOCK_CONST_METHOD0(GetSurfaceType, AZ::Crc32());
MOCK_CONST_METHOD0(GetSurfaceTypeName, const AZStd::string&());
MOCK_METHOD1(SetSurfaceTypeName, void(const AZStd::string&));
MOCK_CONST_METHOD0(GetDynamicFriction, float());
MOCK_METHOD1(SetDynamicFriction, void(float));
MOCK_CONST_METHOD0(GetStaticFriction, float());
MOCK_METHOD1(SetStaticFriction, void(float));
MOCK_CONST_METHOD0(GetRestitution, float());
MOCK_METHOD1(SetRestitution, void(float));
MOCK_CONST_METHOD0(GetFrictionCombineMode, CombineMode());
MOCK_METHOD1(SetFrictionCombineMode, void(CombineMode));
MOCK_CONST_METHOD0(GetRestitutionCombineMode, CombineMode());
MOCK_METHOD1(SetRestitutionCombineMode, void(CombineMode));
MOCK_CONST_METHOD0(GetCryEngineSurfaceId, AZ::u32());
MOCK_METHOD0(GetNativePointer, void*());
MOCK_CONST_METHOD0(GetDensity, float());
MOCK_METHOD1(SetDensity, void(float));
MOCK_CONST_METHOD0(GetDebugColor, AZ::Color());
MOCK_METHOD1(SetDebugColor, void(const AZ::Color&));
};
class ScriptCanvasPhysicsTestEnvironment
: public AZ::Test::GemTestEnvironment
{
void AddGemsAndComponents() override
{
AddComponentDescriptors({ AzFramework::TransformComponent::CreateDescriptor() });
}
};
class ScriptCanvasPhysicsTest
: public ::testing::Test
{
protected:
void SetUp() override
{
::testing::Test::SetUp();
ON_CALL(m_material, GetSurfaceType())
.WillByDefault(Return(AZ::Crc32("CustomSurface")));
m_hit.m_position = AZ::Vector3(1.f, 2.f, 3.f);
m_hit.m_distance = 2.5f;
m_hit.m_normal = AZ::Vector3(-1.f, 3.5f, 0.5f);
m_hit.m_shape = &m_shape;
m_hit.m_material = &m_material;
m_hit.m_resultFlags = AzPhysics::SceneQuery::ResultFlags::Position |
AzPhysics::SceneQuery::ResultFlags::Distance |
AzPhysics::SceneQuery::ResultFlags::Normal |
AzPhysics::SceneQuery::ResultFlags::Shape |
AzPhysics::SceneQuery::ResultFlags::Material;
m_hitResult.m_hits.push_back(m_hit);
}
NiceMock<MockSimulatedBody> m_worldBody;
NiceMock<MockShape> m_shape;
NiceMock<MockPhysicsMaterial> m_material;
NiceMock<MockPhysicsSceneInterface> m_sceneInterfaceMock;
AzPhysics::SceneQueryHit m_hit;
AzPhysics::SceneQueryHits m_hitResult;
bool ResultIsEqualToHit(const ScriptCanvasPhysics::WorldNodes::Result& result, const AzPhysics::SceneQueryHit& hit)
{
return
AZStd::get<0>(result) == hit.IsValid() &&
AZStd::get<1>(result) == hit.m_position &&
AZStd::get<2>(result) == hit.m_normal &&
AZStd::get<3>(result) == hit.m_distance &&
AZStd::get<4>(result) == hit.m_entityId &&
AZStd::get<5>(result) == (hit.m_material ? hit.m_material->GetSurfaceType() : AZ::Crc32())
;
}
};
TEST_F(ScriptCanvasPhysicsTest, WorldNodes_RayCastWorldSpaceWithGroup_FT)
{
ON_CALL(m_sceneInterfaceMock, QueryScene(_, _))
.WillByDefault(Return(m_hitResult));
// given raycast data
const AZ::Vector3 start = AZ::Vector3::CreateZero();
const AZ::Vector3 direction = AZ::Vector3(0.f,1.f,0.f);
const float distance = 1.f;
const AZStd::string collisionGroup = "default";
const AZ::EntityId ignoreEntityId;
// when a raycast is performed
auto result = ScriptCanvasPhysics::WorldNodes::RayCastWorldSpaceWithGroup(
start,
direction,
distance,
collisionGroup,
ignoreEntityId
);
// expect a valid hit is returned
EXPECT_TRUE(ResultIsEqualToHit(result, m_hit));
}
TEST_F(ScriptCanvasPhysicsTest, WorldNodes_RayCastLocalSpaceWithGroup_FT)
{
ON_CALL(m_sceneInterfaceMock, QueryScene(_, _))
.WillByDefault(Return(m_hitResult));
// given raycast data
const AZ::Vector3 start = AZ::Vector3::CreateZero();
const AZ::Vector3 direction = AZ::Vector3(0.f,1.f,0.f);
const float distance = 1.f;
const AZStd::string collisionGroup = "default";
const AZ::EntityId ignoreEntityId;
auto fromEntity = AZStd::make_unique<AZ::Entity>("Entity");
fromEntity->CreateComponent<AzFramework::TransformComponent>()->SetWorldTM(AZ::Transform::Identity());
fromEntity->Init();
fromEntity->Activate();
// when a raycast is performed
auto result = ScriptCanvasPhysics::WorldNodes::RayCastLocalSpaceWithGroup(
fromEntity->GetId(),
direction,
distance,
collisionGroup,
fromEntity->GetId()
);
// expect a valid hit is returned
EXPECT_TRUE(ResultIsEqualToHit(result, m_hit));
}
TEST_F(ScriptCanvasPhysicsTest, WorldNodes_RayCastMultipleLocalSpaceWithGroup_FT)
{
AZStd::vector<AzPhysics::SceneQueryHit> hits;
hits.push_back(m_hit);
ON_CALL(m_sceneInterfaceMock, QueryScene(_, _))
.WillByDefault(Return(m_hitResult));
// given raycast data
const AZ::Vector3 start = AZ::Vector3::CreateZero();
const AZ::Vector3 direction = AZ::Vector3(0.f,1.f,0.f);
const float distance = 1.f;
const AZStd::string collisionGroup = "default";
const AZ::EntityId ignoreEntityId;
auto fromEntity = AZStd::make_unique<AZ::Entity>("Entity");
fromEntity->CreateComponent<AzFramework::TransformComponent>()->SetWorldTM(AZ::Transform::Identity());
fromEntity->Init();
fromEntity->Activate();
// when a raycast is performed
auto results = ScriptCanvasPhysics::WorldNodes::RayCastMultipleLocalSpaceWithGroup(
fromEntity->GetId(),
direction,
distance,
collisionGroup,
fromEntity->GetId()
);
// expect valid hits are returned
EXPECT_FALSE(results.empty());
for (auto result : results)
{
EXPECT_EQ(result.m_distance, m_hit.m_distance);
EXPECT_EQ(result.m_material, m_hit.m_material);
EXPECT_EQ(result.m_normal, m_hit.m_normal);
EXPECT_EQ(result.m_position, m_hit.m_position);
EXPECT_EQ(result.m_shape, m_hit.m_shape);
}
}
TEST_F(ScriptCanvasPhysicsTest, WorldNodes_ShapecastQuery_FT)
{
ON_CALL(m_sceneInterfaceMock, QueryScene(_, _))
.WillByDefault(Return(m_hitResult));
// given shapecast data
const AZ::Vector3 start = AZ::Vector3::CreateZero();
const AZ::Vector3 direction = AZ::Vector3(0.f,1.f,0.f);
const float distance = 1.f;
const AZStd::string collisionGroup = "default";
const AZ::EntityId ignoreEntityId;
const AZ::Transform pose = AZ::Transform::CreateIdentity();
// when a shapecast is performed
auto result = ScriptCanvasPhysics::WorldNodes::ShapecastQuery(
distance,
pose,
direction,
AZStd::make_shared<Physics::BoxShapeConfiguration>(),
collisionGroup,
ignoreEntityId
);
// expect a valid hit is returned
EXPECT_TRUE(ResultIsEqualToHit(result, m_hit));
}
}
AZ_UNIT_TEST_HOOK(new ScriptCanvasPhysicsTests::ScriptCanvasPhysicsTestEnvironment);