LYN-6431 Added materials support to TerrainPhysicsCollider
Signed-off-by: sphrose <82213493+sphrose@users.noreply.github.com>
This commit is contained in:
@@ -16,16 +16,47 @@
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
|
||||
#include <AzFramework/Physics/Material.h>
|
||||
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
void TerrainPhysicsColliderConfig::Reflect(AZ::ReflectContext* context)
|
||||
void TerrainPhysicsSurfaceMaterialMapping::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serialize->Class<TerrainPhysicsColliderConfig, AZ::ComponentConfig>()
|
||||
serialize->Class<TerrainPhysicsSurfaceMaterialMapping>()
|
||||
->Version(1)
|
||||
->Field("Surface", &TerrainPhysicsSurfaceMaterialMapping::m_surfaceTag)
|
||||
->Field("Material", &TerrainPhysicsSurfaceMaterialMapping::m_materialId);
|
||||
|
||||
if (auto edit = serialize->GetEditContext())
|
||||
{
|
||||
edit->Class<TerrainPhysicsSurfaceMaterialMapping>(
|
||||
"Terrain Surface Gradient Mapping", "Mapping between a surface and a physics material.")
|
||||
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
|
||||
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show)
|
||||
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::ComboBox, &TerrainPhysicsSurfaceMaterialMapping::m_surfaceTag, "Surface Tag",
|
||||
"Surface type to map to a physics material.")
|
||||
->DataElement(AZ::Edit::UIHandlers::Default, &TerrainPhysicsSurfaceMaterialMapping::m_materialId, "Material ID", "")
|
||||
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
|
||||
->Attribute(AZ::Edit::Attributes::ShowProductAssetFileName, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
void TerrainPhysicsColliderConfig::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
TerrainPhysicsSurfaceMaterialMapping::Reflect(context);
|
||||
|
||||
if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serialize->Class<TerrainPhysicsColliderConfig, AZ::ComponentConfig>()
|
||||
->Version(2)->Field(
|
||||
"Mappings", &TerrainPhysicsColliderConfig::m_surfaceMaterialMappings)
|
||||
;
|
||||
|
||||
if (auto edit = serialize->GetEditContext())
|
||||
@@ -35,7 +66,11 @@ namespace Terrain
|
||||
"Provides terrain data to a physics collider with configurable surface mappings.")
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
|
||||
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
|
||||
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::Default, &TerrainPhysicsColliderConfig::m_surfaceMaterialMappings,
|
||||
"Surface to Material Mappings", "Maps surfaces to physics materials")
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,6 +284,22 @@ namespace Terrain
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t TerrainPhysicsColliderComponent::FindSurfaceTagIndex(const SurfaceData::SurfaceTag tag) const
|
||||
{
|
||||
uint8_t index = 0;
|
||||
|
||||
for (auto& mapping : m_configuration.m_surfaceMaterialMappings)
|
||||
{
|
||||
if (mapping.m_surfaceTag == tag)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
return InvalidSurfaceTagIndex;
|
||||
}
|
||||
|
||||
void TerrainPhysicsColliderComponent::GenerateHeightsAndMaterialsInBounds(
|
||||
AZStd::vector<Physics::HeightMaterialPoint>& heightMaterials) const
|
||||
{
|
||||
@@ -286,9 +337,16 @@ namespace Terrain
|
||||
terrainExists = false;
|
||||
}
|
||||
|
||||
// Find the best surface tag at this point.
|
||||
AzFramework::SurfaceData::SurfaceTagWeight surfaceWeight;
|
||||
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
|
||||
surfaceWeight, &AzFramework::Terrain::TerrainDataRequests::GetMaxSurfaceWeightFromFloats, x, y,
|
||||
AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT, nullptr);
|
||||
|
||||
Physics::HeightMaterialPoint point;
|
||||
point.m_height = height - worldCenterZ;
|
||||
point.m_quadMeshType = terrainExists ? Physics::QuadMeshType::SubdivideUpperLeftToBottomRight : Physics::QuadMeshType::Hole;
|
||||
point.m_materialIndex = FindSurfaceTagIndex(surfaceWeight.m_surfaceType);
|
||||
heightMaterials.emplace_back(point);
|
||||
}
|
||||
}
|
||||
@@ -332,7 +390,15 @@ namespace Terrain
|
||||
|
||||
AZStd::vector<Physics::MaterialId> TerrainPhysicsColliderComponent::GetMaterialList() const
|
||||
{
|
||||
return AZStd::vector<Physics::MaterialId>();
|
||||
AZStd::vector<Physics::MaterialId> materialList;
|
||||
materialList.reserve(m_configuration.m_surfaceMaterialMappings.size());
|
||||
|
||||
for (auto& mapping : m_configuration.m_surfaceMaterialMappings)
|
||||
{
|
||||
materialList.emplace_back(mapping.m_materialId);
|
||||
}
|
||||
|
||||
return materialList;
|
||||
}
|
||||
|
||||
AZStd::vector<float> TerrainPhysicsColliderComponent::GetHeights() const
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include <AzFramework/Physics/HeightfieldProviderBus.h>
|
||||
#include <AzFramework/Physics/Material.h>
|
||||
#include <SurfaceData/SurfaceTag.h>
|
||||
#include <TerrainSystem/TerrainSystemBus.h>
|
||||
|
||||
#include <LmbrCentral/Shape/ShapeComponentBus.h>
|
||||
@@ -24,6 +25,19 @@ namespace LmbrCentral
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
static const uint8_t InvalidSurfaceTagIndex = 0xFF;
|
||||
|
||||
struct TerrainPhysicsSurfaceMaterialMapping final
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(TerrainPhysicsSurfaceMaterialMapping, AZ::SystemAllocator, 0);
|
||||
AZ_RTTI(TerrainPhysicsSurfaceMaterialMapping, "{A88B5289-DFCD-4564-8395-E2177DFE5B18}");
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
SurfaceData::SurfaceTag m_surfaceTag;
|
||||
Physics::MaterialId m_materialId;
|
||||
};
|
||||
|
||||
class TerrainPhysicsColliderConfig
|
||||
: public AZ::ComponentConfig
|
||||
{
|
||||
@@ -32,6 +46,7 @@ namespace Terrain
|
||||
AZ_RTTI(TerrainPhysicsColliderConfig, "{E9EADB8F-C3A5-4B9C-A62D-2DBC86B4CE59}", AZ::ComponentConfig);
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
AZStd::vector<TerrainPhysicsSurfaceMaterialMapping> m_surfaceMaterialMappings;
|
||||
};
|
||||
|
||||
|
||||
@@ -77,6 +92,8 @@ namespace Terrain
|
||||
bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override;
|
||||
bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override;
|
||||
|
||||
uint8_t FindSurfaceTagIndex(const SurfaceData::SurfaceTag tag) const;
|
||||
|
||||
void GenerateHeightsInBounds(AZStd::vector<float>& heights) const;
|
||||
void GenerateHeightsAndMaterialsInBounds(AZStd::vector<Physics::HeightMaterialPoint>& heightMaterials) const;
|
||||
|
||||
|
||||
@@ -290,3 +290,135 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderReturnsRelativ
|
||||
|
||||
m_entity->Reset();
|
||||
}
|
||||
|
||||
TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderReturnsMaterials)
|
||||
{
|
||||
// Check that the TerrainPhysicsCollider returns all the assigned materials.
|
||||
CreateEntity();
|
||||
|
||||
m_boxComponent = m_entity->CreateComponent<UnitTest::MockAxisAlignedBoxShapeComponent>();
|
||||
m_app.RegisterComponentDescriptor(m_boxComponent->CreateDescriptor());
|
||||
|
||||
// Create two SurfaceTag/Material mappings and add them to the collider.
|
||||
Terrain::TerrainPhysicsColliderConfig config;
|
||||
|
||||
const Physics::MaterialId mat1 = Physics::MaterialId::Create();
|
||||
const Physics::MaterialId mat2 = Physics::MaterialId::Create();
|
||||
|
||||
const SurfaceData::SurfaceTag tag1 = SurfaceData::SurfaceTag("tag1");
|
||||
const SurfaceData::SurfaceTag tag2 = SurfaceData::SurfaceTag("tag2");
|
||||
|
||||
Terrain::TerrainPhysicsSurfaceMaterialMapping mapping1;
|
||||
mapping1.m_materialId = mat1;
|
||||
mapping1.m_surfaceTag = tag1;
|
||||
config.m_surfaceMaterialMappings.emplace_back(mapping1);
|
||||
|
||||
Terrain::TerrainPhysicsSurfaceMaterialMapping mapping2;
|
||||
mapping2.m_materialId = mat2;
|
||||
mapping2.m_surfaceTag = tag2;
|
||||
config.m_surfaceMaterialMappings.emplace_back(mapping2);
|
||||
|
||||
m_colliderComponent = m_entity->CreateComponent<Terrain::TerrainPhysicsColliderComponent>(config);
|
||||
m_app.RegisterComponentDescriptor(m_colliderComponent->CreateDescriptor());
|
||||
|
||||
m_entity->Activate();
|
||||
|
||||
AZStd::vector<Physics::MaterialId> materialList;
|
||||
Physics::HeightfieldProviderRequestsBus::EventResult(
|
||||
materialList, m_entity->GetId(), &Physics::HeightfieldProviderRequestsBus::Events::GetMaterialList);
|
||||
|
||||
EXPECT_EQ(materialList.size(), 2);
|
||||
|
||||
EXPECT_EQ(materialList[0], mat1);
|
||||
EXPECT_EQ(materialList[1], mat2);
|
||||
|
||||
m_entity.reset();
|
||||
}
|
||||
|
||||
TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderGetHeightsAndMaterialsReturnsCorrectly)
|
||||
{
|
||||
// Check that the TerrainPhysicsCollider returns a heightfield of the expected size.
|
||||
CreateEntity();
|
||||
|
||||
m_boxComponent = m_entity->CreateComponent<UnitTest::MockAxisAlignedBoxShapeComponent>();
|
||||
m_app.RegisterComponentDescriptor(m_boxComponent->CreateDescriptor());
|
||||
|
||||
// Create two SurfaceTag/Material mappings and add them to the collider.
|
||||
Terrain::TerrainPhysicsColliderConfig config;
|
||||
|
||||
const Physics::MaterialId mat1 = Physics::MaterialId::Create();
|
||||
const Physics::MaterialId mat2 = Physics::MaterialId::Create();
|
||||
|
||||
const SurfaceData::SurfaceTag tag1 = SurfaceData::SurfaceTag("tag1");
|
||||
const SurfaceData::SurfaceTag tag2 = SurfaceData::SurfaceTag("tag2");
|
||||
|
||||
Terrain::TerrainPhysicsSurfaceMaterialMapping mapping1;
|
||||
mapping1.m_materialId = mat1;
|
||||
mapping1.m_surfaceTag = tag1;
|
||||
config.m_surfaceMaterialMappings.emplace_back(mapping1);
|
||||
|
||||
Terrain::TerrainPhysicsSurfaceMaterialMapping mapping2;
|
||||
mapping2.m_materialId = mat2;
|
||||
mapping2.m_surfaceTag = tag2;
|
||||
config.m_surfaceMaterialMappings.emplace_back(mapping2);
|
||||
|
||||
m_colliderComponent = m_entity->CreateComponent<Terrain::TerrainPhysicsColliderComponent>(config);
|
||||
m_app.RegisterComponentDescriptor(m_colliderComponent->CreateDescriptor());
|
||||
|
||||
m_entity->Activate();
|
||||
|
||||
const AZ::Vector3 boundsMin = AZ::Vector3(0.0f);
|
||||
const AZ::Vector3 boundsMax = AZ::Vector3(256.0f, 256.0f, 32768.0f);
|
||||
|
||||
NiceMock<UnitTest::MockShapeComponentRequests> boxShape(m_entity->GetId());
|
||||
const AZ::Aabb bounds = AZ::Aabb::CreateFromMinMax(boundsMin, boundsMax);
|
||||
ON_CALL(boxShape, GetEncompassingAabb).WillByDefault(Return(bounds));
|
||||
|
||||
const float mockHeight = 32768.0f;
|
||||
AZ::Vector2 mockHeightResolution = AZ::Vector2(1.0f);
|
||||
|
||||
AzFramework::SurfaceData::SurfaceTagWeight return1;
|
||||
return1.m_surfaceType = tag1;
|
||||
return1.m_weight = 1.0f;
|
||||
|
||||
AzFramework::SurfaceData::SurfaceTagWeight return2;
|
||||
return2.m_surfaceType = tag2;
|
||||
return2.m_weight = 1.0f;
|
||||
|
||||
NiceMock<UnitTest::MockTerrainDataRequests> terrainListener;
|
||||
ON_CALL(terrainListener, GetTerrainHeightQueryResolution).WillByDefault(Return(mockHeightResolution));
|
||||
ON_CALL(terrainListener, GetHeightFromFloats).WillByDefault(Return(mockHeight));
|
||||
ON_CALL(terrainListener, GetMaxSurfaceWeightFromFloats)
|
||||
.WillByDefault(
|
||||
[return1, return2](
|
||||
[[maybe_unused]] float x, [[maybe_unused]] float y,
|
||||
[[maybe_unused]] AzFramework::Terrain::TerrainDataRequests::Sampler sampleFilter, [[maybe_unused]] bool* terrainExistsPtr)
|
||||
{
|
||||
// return tag1 for the first half of the rows, tag2 for the rest.
|
||||
if (y < 128.0)
|
||||
{
|
||||
return return1;
|
||||
}
|
||||
return return2;
|
||||
});
|
||||
|
||||
AZStd::vector<Physics::HeightMaterialPoint> heightsAndMaterials;
|
||||
|
||||
Physics::HeightfieldProviderRequestsBus::EventResult(
|
||||
heightsAndMaterials, m_entity->GetId(), &Physics::HeightfieldProviderRequestsBus::Events::GetHeightsAndMaterials);
|
||||
|
||||
// We set the bounds to 256, so check that the correct number of entries are present.
|
||||
EXPECT_EQ(heightsAndMaterials.size(), 256 * 256);
|
||||
|
||||
const float expectedHeightValue = 16384.0f;
|
||||
|
||||
// Check an entry from the first half of the returned list.
|
||||
EXPECT_EQ(heightsAndMaterials[0].m_materialIndex, 0);
|
||||
EXPECT_NEAR(heightsAndMaterials[0].m_height, expectedHeightValue, 0.01f);
|
||||
|
||||
// Check an entry from the second half of the list
|
||||
EXPECT_EQ(heightsAndMaterials[256 * 128].m_materialIndex, 1);
|
||||
EXPECT_NEAR(heightsAndMaterials[256 * 128].m_height, expectedHeightValue, 0.01f);
|
||||
|
||||
m_entity.reset();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user