LYN-6431 Added materials support to TerrainPhysicsCollider

Signed-off-by: sphrose <82213493+sphrose@users.noreply.github.com>
This commit is contained in:
sphrose
2021-12-01 12:56:19 +00:00
parent 58be7c27ed
commit 7e93addd53
3 changed files with 219 additions and 4 deletions
@@ -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();
}