Fix brute force mesh intersection function (#5447)

* fix brute force mesh intersection function

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* add test for brute force ray intersection fix

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* refactor tests to remove as much duplication and provide API for future tests if required

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* small updates after review feedback

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* update following review feedback

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* fix for pointer offset

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>
This commit is contained in:
Tom Hulton-Harrop
2021-11-11 16:33:08 +00:00
committed by GitHub
parent 6e70097ad7
commit 2812ec2024
5 changed files with 178 additions and 63 deletions
+150 -29
View File
@@ -38,7 +38,7 @@ namespace UnitTest
bufferData.resize(bufferSize);
//The actual data doesn't matter
const uint8_t bufferDataSize = static_cast<uint8_t>(bufferData.size());
const uint8_t bufferDataSize = aznumeric_cast<uint8_t>(bufferData.size());
for (uint8_t i = 0; i < bufferDataSize; ++i)
{
bufferData[i] = i;
@@ -248,7 +248,8 @@ namespace UnitTest
return asset;
}
AZ::Data::Asset<AZ::RPI::ModelAsset> BuildTestModel(const uint32_t lodCount, const uint32_t sharedMeshCount, const uint32_t separateMeshCount, ExpectedModel& expectedModel)
AZ::Data::Asset<AZ::RPI::ModelAsset> BuildTestModel(
const uint32_t lodCount, const uint32_t sharedMeshCount, const uint32_t separateMeshCount, ExpectedModel& expectedModel)
{
using namespace AZ;
@@ -989,6 +990,9 @@ namespace UnitTest
uint32_t{ 0 }, 2, 1, 1, 2, 3, 4, 5, 6, 5, 7, 6, 0, 4, 2, 4, 6, 2, 1, 3, 5, 5, 3, 7, 0, 1, 4, 4, 1, 5, 2, 6, 3, 6, 7, 3,
};
static constexpr AZStd::array QuadPositions = { -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f };
static constexpr AZStd::array QuadIndices = { uint32_t{ 0 }, 2, 1, 1, 2, 3 };
// This class creates a Model with one LOD, whose mesh contains 2 planes. Plane 1 is in the XY plane at Z=-0.5, and
// plane 2 is in the XY plane at Z=0.5. The two planes each have 9 quads which have been triangulated. It only has
// a position and index buffer.
@@ -1031,42 +1035,80 @@ namespace UnitTest
static constexpr inline auto minmaxElement = AZStd::minmax_element(begin(TwoSeparatedPlanesIndices), end(TwoSeparatedPlanesIndices));
static_assert(*minmaxElement.second == (TwoSeparatedPlanesPositions.size() / 3) - 1);
template<class x> class TD;
class TestMesh
{
public:
TestMesh() = default;
TestMesh(const float* positions, size_t positionCount, const uint32_t* indices, size_t indicesCount)
{
AZ::RPI::ModelLodAssetCreator lodCreator;
lodCreator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom()));
Begin(lodCreator);
Add(lodCreator, positions, positionCount, /*positionOffset=*/0, indices, indicesCount, /*indexOffset=*/0);
End(lodCreator);
}
// initiate the asset lod creation process (note: End must be called after meshes have been added).
void Begin(AZ::RPI::ModelLodAssetCreator& lodCreator)
{
lodCreator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom()));
}
// add a sub mesh and reuse existing position/index buffer (be very careful with the offsets used)
void Add(
AZ::RPI::ModelLodAssetCreator& lodCreator,
const float* positions,
size_t positionCount,
size_t positionOffset,
AZ::Data::Asset<AZ::RPI::BufferAsset> positionBuffer,
const uint32_t* indices,
size_t indexCount,
size_t indexOffset,
AZ::Data::Asset<AZ::RPI::BufferAsset> indexBuffer)
{
lodCreator.BeginMesh();
lodCreator.SetMeshAabb(AZ::Aabb::CreateFromMinMax({-1.0f, -1.0f, -0.5f}, {1.0f, 1.0f, 0.5f}));
lodCreator.SetMeshAabb(AZ::Aabb::CreateFromMinMax({ -1.0f, -1.0f, -0.5f }, { 1.0f, 1.0f, 0.5f }));
lodCreator.SetMeshMaterialSlot(AZ::Sfmt::GetInstance().Rand32());
{
AZ::Data::Asset<AZ::RPI::BufferAsset> indexBuffer = BuildTestBuffer(static_cast<uint32_t>(indicesCount), sizeof(uint32_t));
AZStd::copy(indices, indices + indicesCount, reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(indexBuffer->GetBuffer().data())));
lodCreator.SetMeshIndexBuffer({
indexBuffer,
AZ::RHI::BufferViewDescriptor::CreateStructured(0, static_cast<uint32_t>(indicesCount), sizeof(uint32_t))
});
}
AZStd::copy(
indices, indices + indexCount,
reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(indexBuffer->GetBuffer().data())) + indexOffset);
lodCreator.SetMeshIndexBuffer(
{ indexBuffer,
AZ::RHI::BufferViewDescriptor::CreateStructured(
aznumeric_cast<uint32_t>(indexOffset), aznumeric_cast<uint32_t>(indexCount), sizeof(uint32_t)) });
AZStd::copy(
positions, positions + positionCount,
reinterpret_cast<float*>(const_cast<uint8_t*>(positionBuffer->GetBuffer().data())) + positionOffset);
lodCreator.AddMeshStreamBuffer(
AZ::RHI::ShaderSemantic(AZ::Name("POSITION")), AZ::Name(),
{ positionBuffer,
AZ::RHI::BufferViewDescriptor::CreateStructured(
aznumeric_cast<uint32_t>(positionOffset / 3), aznumeric_cast<uint32_t>(positionCount / 3), sizeof(float) * 3) });
{
AZ::Data::Asset<AZ::RPI::BufferAsset> positionBuffer = BuildTestBuffer(static_cast<uint32_t>(positionCount / 3), sizeof(float) * 3);
AZStd::copy(positions, positions + positionCount, reinterpret_cast<float*>(const_cast<uint8_t*>(positionBuffer->GetBuffer().data())));
lodCreator.AddMeshStreamBuffer(
AZ::RHI::ShaderSemantic(AZ::Name("POSITION")),
AZ::Name(),
{
positionBuffer,
AZ::RHI::BufferViewDescriptor::CreateStructured(0, static_cast<uint32_t>(positionCount / 3), sizeof(float) * 3)
}
);
}
lodCreator.EndMesh();
}
// overload of Add - here a new index/position buffer is created for the new data instead of potentially reusing an existing buffer
void Add(
AZ::RPI::ModelLodAssetCreator& lodCreator,
const float* positions,
size_t positionCount,
size_t positionOffset,
const uint32_t* indices,
size_t indexCount,
size_t indexOffset)
{
AZ::Data::Asset<AZ::RPI::BufferAsset> indexBuffer = BuildTestBuffer(aznumeric_cast<uint32_t>(indexCount), sizeof(uint32_t));
AZ::Data::Asset<AZ::RPI::BufferAsset> positionBuffer =
BuildTestBuffer(aznumeric_cast<uint32_t>(positionCount / 3), sizeof(float) * 3);
Add(lodCreator, positions, positionCount, positionOffset, positionBuffer, indices, indexCount, indexOffset, indexBuffer);
}
// complete the asset lod creation process
void End(AZ::RPI::ModelLodAssetCreator& lodCreator)
{
AZ::Data::Asset<AZ::RPI::ModelLodAsset> lodAsset;
lodCreator.End(lodAsset);
@@ -1199,7 +1241,7 @@ namespace UnitTest
constexpr float rayLength = 100.0f;
EXPECT_THAT(
m_kdTree->RayIntersection(
AZ::Vector3::CreateZero(), AZ::Vector3::CreateAxisZ(-rayLength), t, normal), testing::Eq(true));
AZ::Vector3::CreateZero(), AZ::Vector3::CreateAxisZ(-rayLength), t, normal), testing::IsTrue());
EXPECT_THAT(t, testing::FloatEq(0.005f));
}
@@ -1210,7 +1252,7 @@ namespace UnitTest
constexpr float rayLength = 10.0f;
EXPECT_THAT(
m_kdTree->RayIntersection(AZ::Vector3::CreateAxisZ(0.75f), AZ::Vector3::CreateAxisZ(-rayLength), t, normal), testing::Eq(true));
m_kdTree->RayIntersection(AZ::Vector3::CreateAxisZ(0.75f), AZ::Vector3::CreateAxisZ(-rayLength), t, normal), testing::IsTrue());
EXPECT_THAT(t, testing::FloatEq(0.025f));
}
@@ -1288,7 +1330,7 @@ namespace UnitTest
EXPECT_THAT(
m_mesh->GetModel()->LocalRayIntersectionAgainstModel(
AZ::Vector3::CreateAxisZ(5.0f), -AZ::Vector3::CreateAxisZ(10.0f), AllowBruteForce, t, normal),
testing::Eq(true));
testing::IsTrue());
EXPECT_THAT(t, testing::FloatEq(0.4f));
}
@@ -1302,8 +1344,87 @@ namespace UnitTest
EXPECT_THAT(
m_mesh->GetModel()->LocalRayIntersectionAgainstModel(
AZ::Vector3::CreateAxisY(10.0f), -AZ::Vector3::CreateAxisY(9.0f), AllowBruteForce, t, normal),
testing::Eq(true));
testing::IsTrue());
EXPECT_THAT(t, testing::FloatEq(1.0f));
EXPECT_THAT(normal, IsClose(AZ::Vector3::CreateAxisY()));
}
// test to verify that each secondary sub meshes are still intersected with correctly when using brute-force
// ray intersection
class BruteForceMultiModelIntersectsFixture : public ModelTests
{
public:
inline static const float QuadOffsetX = 15.0f;
void SetUp() override
{
ModelTests::SetUp();
m_mesh = AZStd::make_unique<TestMesh>();
AZ::RPI::ModelLodAssetCreator lodCreator;
m_mesh->Begin(lodCreator);
// take default quad positions and offset in X by set amount
AZStd::vector<float> offsetQuadPositions;
offsetQuadPositions.resize(QuadPositions.size());
AZStd::copy(QuadPositions.begin(), QuadPositions.end(), offsetQuadPositions.begin());
for (size_t xVertIndex = 0; xVertIndex < offsetQuadPositions.size(); xVertIndex += 3)
{
offsetQuadPositions[xVertIndex] += QuadOffsetX;
}
// create shared buffer to store cube and quad mesh in the same buffer
const size_t indicesCount = QuadIndices.size() + CubeIndices.size();
const size_t positionCount = QuadPositions.size() + CubePositions.size();
AZ::Data::Asset<AZ::RPI::BufferAsset> indexBuffer = BuildTestBuffer(aznumeric_cast<uint32_t>(indicesCount), sizeof(uint32_t));
AZ::Data::Asset<AZ::RPI::BufferAsset> positionBuffer =
BuildTestBuffer(aznumeric_cast<uint32_t>(positionCount / 3), sizeof(float) * 3);
// add the cube mesh
m_mesh->Add(
lodCreator, CubePositions.data(), CubePositions.size(), 0, positionBuffer, CubeIndices.data(), CubeIndices.size(), 0,
indexBuffer);
// add the quad mesh (offset by the cube position and index data into the same buffer)
m_mesh->Add(
lodCreator, offsetQuadPositions.data(), offsetQuadPositions.size(), /*offset=*/CubePositions.size(), positionBuffer,
QuadIndices.data(), QuadIndices.size(), /*offset=*/CubeIndices.size(), indexBuffer);
m_mesh->End(lodCreator);
}
void TearDown() override
{
m_mesh.reset();
ModelTests::TearDown();
}
AZStd::unique_ptr<TestMesh> m_mesh;
inline static constexpr bool AllowBruteForce = false;
};
TEST_F(BruteForceMultiModelIntersectsFixture, RayIntersectsWithFirstSubMesh)
{
float t = 0.0f;
AZ::Vector3 normal = AZ::Vector3::CreateOne(); // invalid starting normal
// fire a ray at the first sub mesh and ensure a successful hit is returned
EXPECT_THAT(
m_mesh->GetModel()->LocalRayIntersectionAgainstModel(
AZ::Vector3(0.0f, 0.0f, 5.0f), -AZ::Vector3::CreateAxisZ(10.0f), AllowBruteForce, t, normal),
testing::IsTrue());
EXPECT_THAT(t, testing::FloatEq(0.4f));
EXPECT_THAT(normal, IsClose(AZ::Vector3::CreateAxisZ()));
}
TEST_F(BruteForceMultiModelIntersectsFixture, RayIntersectsWithSecondSubMesh)
{
float t = 0.0f;
AZ::Vector3 normal = AZ::Vector3::CreateOne(); // invalid starting normal
// fire a ray at the second sub mesh and ensure a successful hit is returned
EXPECT_THAT(
m_mesh->GetModel()->LocalRayIntersectionAgainstModel(
AZ::Vector3(QuadOffsetX, 0.0f, 5.0f), -AZ::Vector3::CreateAxisZ(10.0f), AllowBruteForce, t, normal),
testing::IsTrue());
EXPECT_THAT(t, testing::FloatEq(0.5f));
EXPECT_THAT(normal, IsClose(AZ::Vector3::CreateAxisZ()));
}
} // namespace UnitTest