ensure brute force ray intersection works (#1170)

* ensure brute force ray intersection works in the same space as kd-tree intersection

* add additional tests for ray casts against meshes using brute force approach

* update api and add some additional test cases

* comment tidy-up and other small updates/fixes for ray intersection code

* fix issue with values at the end of a ray
main
Tom Hulton-Harrop 5 years ago committed by GitHub
parent 80f62d0523
commit dd95d2b02e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -150,6 +150,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
PRIVATE
AZ::AtomCore
AZ::AzTest
AZ::AzTestShared
AZ::AzFramework
AZ::AzToolsFramework
Legacy::CryCommon

@ -61,12 +61,13 @@ namespace AZ
//! Important: only to be used in the Editor, it may kick off a job to calculate spatial information.
//! [GFX TODO][ATOM-4343 Bake mesh spatial during AP processing]
//!
//! @param rayStart position where the ray starts
//! @param dir direction where the ray ends (does not have to be unit length)
//! @param distanceFactor if an intersection is detected, this will be set such that distanceFactor * dir.length == distance to intersection
//! @param normal if an intersection is detected, this will be set to the normal at the point of intersection
//! @return true if the ray intersects the mesh
bool LocalRayIntersection(const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distanceFactor, AZ::Vector3& normal) const;
//! @param rayStart The starting point of the ray.
//! @param rayDir The direction and length of the ray (magnitude is encoded in the direction).
//! @param[out] distanceNormalized If an intersection is found, will be set to the normalized distance of the intersection
//! (in the range 0.0-1.0) - to calculate the actual distance, multiply distanceNormalized by the magnitude of rayDir.
//! @param[out] normal If an intersection is found, will be set to the normal at the point of collision.
//! @return True if the ray intersects the mesh.
bool LocalRayIntersection(const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const;
//! Checks a ray for intersection against this model, where the ray is in a different coordinate space.
//! Important: only to be used in the Editor, it may kick off a job to calculate spatial information.
@ -74,13 +75,19 @@ namespace AZ
//!
//! @param modelTransform a transform that puts the model into the ray's coordinate space
//! @param nonUniformScale Non-uniform scale applied in the model's local frame.
//! @param rayStart position where the ray starts
//! @param dir direction where the ray ends (does not have to be unit length)
//! @param distanceFactor if an intersection is detected, this will be set such that distanceFactor * dir.length == distance to intersection
//! @param normal if an intersection is detected, this will be set to the normal at the point of intersection
//! @return true if the ray intersects the mesh
bool RayIntersection(const AZ::Transform& modelTransform, const AZ::Vector3& nonUniformScale, const AZ::Vector3& rayStart,
const AZ::Vector3& dir, float& distanceFactor, AZ::Vector3& normal) const;
//! @param rayStart The starting point of the ray.
//! @param rayDir The direction and length of the ray (magnitude is encoded in the direction).
//! @param[out] distanceNormalized If an intersection is found, will be set to the normalized distance of the intersection
//! (in the range 0.0-1.0) - to calculate the actual distance, multiply distanceNormalized by the magnitude of rayDir.
//! @param[out] normal If an intersection is found, will be set to the normal at the point of collision.
//! @return True if the ray intersects the mesh.
bool RayIntersection(
const AZ::Transform& modelTransform,
const AZ::Vector3& nonUniformScale,
const AZ::Vector3& rayStart,
const AZ::Vector3& rayDir,
float& distanceNormalized,
AZ::Vector3& normal) const;
//! Get available UV names from the model and its lods.
const AZStd::unordered_set<AZ::Name>& GetUvNames() const;

@ -63,12 +63,14 @@ namespace AZ
//! Important: only to be used in the Editor, it may kick off a job to calculate spatial information.
//! [GFX TODO][ATOM-4343 Bake mesh spatial information during AP processing]
//!
//! @param rayStart position where the ray starts
//! @param dir direction where the ray ends (does not have to be unit length)
//! @param distance if an intersection is detected, this will be set such that distanceFactor * dir.length == distance to intersection
//! @param normal if an intersection is detected, this will be set to the normal at the point of collision
//! @return true if the ray intersects the mesh
virtual bool LocalRayIntersectionAgainstModel(const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const;
//! @param rayStart The starting point of the ray.
//! @param rayDir The direction and length of the ray (magnitude is encoded in the direction).
//! @param[out] distanceNormalized If an intersection is found, will be set to the normalized distance of the intersection
//! (in the range 0.0-1.0) - to calculate the actual distance, multiply distanceNormalized by the magnitude of rayDir.
//! @param[out] normal If an intersection is found, will be set to the normal at the point of collision.
//! @return True if the ray intersects the mesh.
virtual bool LocalRayIntersectionAgainstModel(
const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const;
private:
void SetReady();
@ -79,9 +81,15 @@ namespace AZ
// mutable method
void BuildKdTree() const;
bool BruteForceRayIntersect(const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const;
bool LocalRayIntersectionAgainstMesh(const ModelLodAsset::Mesh& mesh, const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const;
bool BruteForceRayIntersect(
const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const;
bool LocalRayIntersectionAgainstMesh(
const ModelLodAsset::Mesh& mesh,
const AZ::Vector3& rayStart,
const AZ::Vector3& rayDir,
float& distanceNormalized,
AZ::Vector3& normal) const;
// Various model information used in raycasting
AZ::Name m_positionName{ "POSITION" };

@ -137,12 +137,12 @@ namespace AZ
return m_modelAsset;
}
bool Model::LocalRayIntersection(const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const
bool Model::LocalRayIntersection(const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const
{
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
float start;
float end;
const int result = Intersect::IntersectRayAABB2(rayStart, dir.GetReciprocal(), m_aabb, start, end);
const int result = Intersect::IntersectRayAABB2(rayStart, rayDir.GetReciprocal(), m_aabb, start, end);
if (Intersect::ISECT_RAY_AABB_NONE != result)
{
if (ModelAsset* modelAssetPtr = m_modelAsset.Get())
@ -151,7 +151,7 @@ namespace AZ
AZ::Debug::Timer timer;
timer.Stamp();
#endif
const bool hit = modelAssetPtr->LocalRayIntersectionAgainstModel(rayStart, dir, distance, normal);
const bool hit = modelAssetPtr->LocalRayIntersectionAgainstModel(rayStart, rayDir, distanceNormalized, normal);
#if defined(AZ_RPI_PROFILE_RAYCASTING_AGAINST_MODELS)
if (hit)
{
@ -166,8 +166,12 @@ namespace AZ
}
bool Model::RayIntersection(
const AZ::Transform& modelTransform, const AZ::Vector3& nonUniformScale, const AZ::Vector3& rayStart, const AZ::Vector3& dir,
float& distanceFactor, AZ::Vector3& normal) const
const AZ::Transform& modelTransform,
const AZ::Vector3& nonUniformScale,
const AZ::Vector3& rayStart,
const AZ::Vector3& rayDir,
float& distanceNormalized,
AZ::Vector3& normal) const
{
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
const AZ::Vector3 clampedScale = nonUniformScale.GetMax(AZ::Vector3(AZ::MinTransformScale));
@ -175,12 +179,13 @@ namespace AZ
const AZ::Transform inverseTM = modelTransform.GetInverse();
const AZ::Vector3 raySrcLocal = inverseTM.TransformPoint(rayStart) / clampedScale;
// Instead of just rotating 'dir' we need it to be scaled too, so that 'distanceFactor' will be in the target units rather than object local units.
const AZ::Vector3 rayDest = rayStart + dir;
// Instead of just rotating 'rayDir' we need it to be scaled too, so that 'distanceNormalized' will be in the target units rather
// than object local units.
const AZ::Vector3 rayDest = rayStart + rayDir;
const AZ::Vector3 rayDestLocal = inverseTM.TransformPoint(rayDest) / clampedScale;
const AZ::Vector3 rayDirLocal = rayDestLocal - raySrcLocal;
bool result = LocalRayIntersection(raySrcLocal, rayDirLocal, distanceFactor, normal);
const bool result = LocalRayIntersection(raySrcLocal, rayDirLocal, distanceNormalized, normal);
normal = (normal * clampedScale).GetNormalized();
return result;
}

@ -15,6 +15,7 @@
#include <AzCore/Debug/EventTrace.h>
#include <AzCore/Jobs/JobFunction.h>
#include <AzCore/Math/IntersectSegment.h>
#include <AzCore/std/limits.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
@ -75,7 +76,8 @@ namespace AZ
m_status = Data::AssetData::AssetStatus::Ready;
}
bool ModelAsset::LocalRayIntersectionAgainstModel(const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const
bool ModelAsset::LocalRayIntersectionAgainstModel(
const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzRender);
@ -85,7 +87,7 @@ namespace AZ
m_modelTriangleCount = CalculateTriangleCount();
}
// check the total vertex count for this model and skip kdtree if the model is simple enough
// check the total vertex count for this model and skip kd-tree if the model is simple enough
if (*m_modelTriangleCount > s_minimumModelTriangleCountToOptimize)
{
if (!m_kdTree)
@ -97,11 +99,11 @@ namespace AZ
}
else
{
return m_kdTree->RayIntersection(rayStart, dir, distance, normal);
return m_kdTree->RayIntersection(rayStart, rayDir, distanceNormalized, normal);
}
}
return BruteForceRayIntersect(rayStart, dir, distance, normal);
return BruteForceRayIntersect(rayStart, rayDir, distanceNormalized, normal);
}
void ModelAsset::BuildKdTree() const
@ -136,7 +138,8 @@ namespace AZ
}
}
bool ModelAsset::BruteForceRayIntersect(const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const
bool ModelAsset::BruteForceRayIntersect(
const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const
{
// brute force - check every triangle
if (GetLodAssets().empty() == false)
@ -144,27 +147,27 @@ namespace AZ
// intersect against the highest level of detail
if (ModelLodAsset* loadAssetPtr = GetLodAssets()[0].Get())
{
float shortestDistance = std::numeric_limits<float>::max();
bool anyHit = false;
AZ::Vector3 intersectionNormal;
float shortestDistanceNormalized = AZStd::numeric_limits<float>::max();
for (const ModelLodAsset::Mesh& mesh : loadAssetPtr->GetMeshes())
{
if (LocalRayIntersectionAgainstMesh(mesh, rayStart, dir, distance, intersectionNormal))
float currentDistanceNormalized;
if (LocalRayIntersectionAgainstMesh(mesh, rayStart, rayDir, currentDistanceNormalized, intersectionNormal))
{
anyHit = true;
if (distance < shortestDistance)
if (currentDistanceNormalized < shortestDistanceNormalized)
{
normal = intersectionNormal;
shortestDistance = distance;
shortestDistanceNormalized = currentDistanceNormalized;
}
}
}
if (anyHit)
{
distance = shortestDistance;
distanceNormalized = shortestDistanceNormalized;
}
return anyHit;
@ -174,7 +177,12 @@ namespace AZ
return false;
}
bool ModelAsset::LocalRayIntersectionAgainstMesh(const ModelLodAsset::Mesh& mesh, const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const
bool ModelAsset::LocalRayIntersectionAgainstMesh(
const ModelLodAsset::Mesh& mesh,
const AZ::Vector3& rayStart,
const AZ::Vector3& rayDir,
float& distanceNormalized,
AZ::Vector3& normal) const
{
const BufferAssetView& indexBufferView = mesh.GetIndexBufferAssetView();
const AZStd::array_view<ModelLodAsset::Mesh::StreamBufferInfo>& streamBufferList = mesh.GetStreamBufferInfoList();
@ -217,14 +225,13 @@ namespace AZ
AZStd::array_view<uint8_t> indexRawBuffer = indexAssetViewPtr->GetBuffer();
RHI::BufferViewDescriptor indexRawDesc = indexAssetViewPtr->GetBufferViewDescriptor();
float closestNormalizedDistance = 1.f;
bool anyHit = false;
const AZ::Vector3 rayEnd = rayStart + dir * distance;
const AZ::Vector3 rayEnd = rayStart + rayDir;
AZ::Vector3 a, b, c;
AZ::Vector3 intersectionNormal;
float normalizedDistance = 1.f;
float shortestDistanceNormalized = AZStd::numeric_limits<float>::max();
const AZ::u32* indexPtr = reinterpret_cast<const AZ::u32*>(indexRawBuffer.data());
for (uint32_t indexIter = 0; indexIter <= indexRawDesc.m_elementCount - 3; indexIter += 3, indexPtr += 3)
{
@ -247,20 +254,22 @@ namespace AZ
p = reinterpret_cast<const float*>(&positionRawBuffer[index2 * positionElementSize]);
c.Set(const_cast<float*>(p));
if (AZ::Intersect::IntersectSegmentTriangleCCW(rayStart, rayEnd, a, b, c, intersectionNormal, normalizedDistance))
float currentDistanceNormalized;
if (AZ::Intersect::IntersectSegmentTriangleCCW(rayStart, rayEnd, a, b, c, intersectionNormal, currentDistanceNormalized))
{
if (normalizedDistance < closestNormalizedDistance)
anyHit = true;
if (currentDistanceNormalized < shortestDistanceNormalized)
{
normal = intersectionNormal;
closestNormalizedDistance = normalizedDistance;
shortestDistanceNormalized = currentDistanceNormalized;
}
anyHit = true;
}
}
if (anyHit)
{
distance = closestNormalizedDistance * distance;
distanceNormalized = shortestDistanceNormalized;
}
return anyHit;

@ -208,10 +208,10 @@ namespace AZ
bool ModelKdTree::RayIntersection(
const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const
{
float closestDistanceNormalized = AZStd::numeric_limits<float>::max();
if (RayIntersectionRecursively(m_pRootNode.get(), raySrc, rayDir, closestDistanceNormalized, normal))
float shortestDistanceNormalized = AZStd::numeric_limits<float>::max();
if (RayIntersectionRecursively(m_pRootNode.get(), raySrc, rayDir, shortestDistanceNormalized, normal))
{
distanceNormalized = closestDistanceNormalized;
distanceNormalized = shortestDistanceNormalized;
return true;
}

@ -22,6 +22,7 @@
#include <AzCore/std/limits.h>
#include <AzCore/Component/Entity.h>
#include <AZTestShared/Math/MathTestHelpers.h>
#include <AzTest/AzTest.h>
#include <Common/RPITestFixture.h>
@ -568,7 +569,7 @@ namespace UnitTest
ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
}
// Tests that if we try to set the name on a Model
// Tests that if we try to set the name on a Model
// before calling Begin that it will fail.
TEST_F(ModelTests, SetNameNoBegin)
{
@ -581,7 +582,7 @@ namespace UnitTest
creator.SetName("TestName");
}
// Tests that if we try to add a ModelLod to a Model
// Tests that if we try to add a ModelLod to a Model
// before calling Begin that it will fail.
TEST_F(ModelTests, AddLodNoBegin)
{
@ -598,7 +599,7 @@ namespace UnitTest
creator.AddLodAsset(AZStd::move(lod));
}
// Tests that if we create a ModelAsset without adding
// Tests that if we create a ModelAsset without adding
// any ModelLodAssets that the creator will properly fail to produce an asset.
TEST_F(ModelTests, CreateModelNoLods)
{
@ -618,8 +619,8 @@ namespace UnitTest
ASSERT_EQ(asset.Get(), nullptr);
}
// Tests that if we call SetLodIndexBuffer without calling
// Begin first on the ModelLodAssetCreator that it
// Tests that if we call SetLodIndexBuffer without calling
// Begin first on the ModelLodAssetCreator that it
// fails as expected.
TEST_F(ModelTests, SetLodIndexBufferNoBegin)
{
@ -633,8 +634,8 @@ namespace UnitTest
creator.SetLodIndexBuffer(validIndexBuffer);
}
// Tests that if we call AddLodStreamBuffer without calling
// Begin first on the ModelLodAssetCreator that it
// Tests that if we call AddLodStreamBuffer without calling
// Begin first on the ModelLodAssetCreator that it
// fails as expected.
TEST_F(ModelTests, AddLodStreamBufferNoBegin)
{
@ -648,8 +649,8 @@ namespace UnitTest
creator.AddLodStreamBuffer(validStreamBuffer);
}
// Tests that if we call BeginMesh without calling
// Begin first on the ModelLodAssetCreator that it
// Tests that if we call BeginMesh without calling
// Begin first on the ModelLodAssetCreator that it
// fails as expected.
TEST_F(ModelTests, BeginMeshNoBegin)
{
@ -662,13 +663,13 @@ namespace UnitTest
}
// Tests that if we try to set an AABB on a mesh
// without calling Begin or BeginMesh that it fails
// without calling Begin or BeginMesh that it fails
// as expected. Also tests the case that Begin *is*
// called but BeginMesh is not.
TEST_F(ModelTests, SetAabbNoBeginNoBeginMesh)
{
using namespace AZ;
RPI::ModelLodAssetCreator creator;
AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(AZ::Vector3::CreateZero(), 1.0f);
@ -691,13 +692,13 @@ namespace UnitTest
}
// Tests that if we try to set the material id on a mesh
// without calling Begin or BeginMesh that it fails
// without calling Begin or BeginMesh that it fails
// as expected. Also tests the case that Begin *is*
// called but BeginMesh is not.
TEST_F(ModelTests, SetMaterialIdNoBeginNoBeginMesh)
{
using namespace AZ;
RPI::ModelLodAssetCreator creator;
{
@ -715,7 +716,7 @@ namespace UnitTest
}
// Tests that if we try to set the index buffer on a mesh
// without calling Begin or BeginMesh that it fails
// without calling Begin or BeginMesh that it fails
// as expected. Also tests the case that Begin *is*
// called but BeginMesh is not.
TEST_F(ModelTests, SetIndexBufferNoBeginNoBeginMesh)
@ -751,7 +752,7 @@ namespace UnitTest
}
// Tests that if we try to add a stream buffer on a mesh
// without calling Begin or BeginMesh that it fails
// without calling Begin or BeginMesh that it fails
// as expected. Also tests the case that Begin *is*
// called but BeginMesh is not.
TEST_F(ModelTests, AddStreamBufferNoBeginNoBeginMesh)
@ -785,7 +786,7 @@ namespace UnitTest
}
}
// Tests that if we try to end the creation of a
// Tests that if we try to end the creation of a
// ModelLodAsset that has no meshes that it fails
// as expected.
TEST_F(ModelTests, CreateLodNoMeshes)
@ -804,7 +805,7 @@ namespace UnitTest
ASSERT_EQ(asset.Get(), nullptr);
}
// Tests that validation still fails when expected
// Tests that validation still fails when expected
// even after producing a valid mesh due to a missing
// BeginMesh call
TEST_F(ModelTests, SecondMeshFailureNoBeginMesh)
@ -862,8 +863,8 @@ namespace UnitTest
ASSERT_EQ(asset->GetMeshes().size(), 1);
}
// Tests that validation still fails when expected
// even after producing a valid mesh due to SetMeshX
// Tests that validation still fails when expected
// even after producing a valid mesh due to SetMeshX
// calls coming after End
TEST_F(ModelTests, SecondMeshAfterEnd)
{
@ -907,7 +908,7 @@ namespace UnitTest
AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(Vector3::CreateZero(), 1.0f);
ErrorMessageFinder messageFinder("Begin() was not called", 6);
creator.BeginMesh();
creator.SetMeshAabb(AZStd::move(aabb));
creator.SetMeshMaterialAsset(m_materialAsset);
@ -955,6 +956,20 @@ namespace UnitTest
EXPECT_EQ(uvStreamTangentBitmask.GetFullTangentBitmask(), 0x70000F51);
}
//
// +----+
// / /|
// +----+ |
// | | +
// | |/
// +----+
//
static constexpr AZStd::array CubePositions = { -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f };
static constexpr AZStd::array CubeIndices = {
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,
};
// 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.
@ -972,52 +987,75 @@ namespace UnitTest
// *---*---*---*
// \ / \ / \ / \
// *---*---*---*
static constexpr AZStd::array TwoSeparatedPlanesPositions{
-1.0f, -0.333f, -0.5f, -0.333f, -1.0f, -0.5f, -0.333f, -0.333f, -0.5f, 0.333f, -0.333f, -0.5f, 1.0f, -1.0f, -0.5f,
1.0f, -0.333f, -0.5f, 0.333f, -1.0f, -0.5f, 0.333f, 1.0f, -0.5f, 1.0f, 0.333f, -0.5f, 1.0f, 1.0f, -0.5f,
0.333f, 0.333f, -0.5f, -0.333f, 1.0f, -0.5f, -0.333f, 0.333f, -0.5f, -1.0f, 1.0f, -0.5f, -1.0f, 0.333f, -0.5f,
-1.0f, -0.333f, 0.5f, -0.333f, -1.0f, 0.5f, -0.333f, -0.333f, 0.5f, 0.333f, -0.333f, 0.5f, 1.0f, -1.0f, 0.5f,
1.0f, -0.333f, 0.5f, -0.333f, -0.333f, 0.5f, 0.333f, -1.0f, 0.5f, 0.333f, -0.333f, 0.5f, 0.333f, 1.0f, 0.5f,
1.0f, 0.333f, 0.5f, 1.0f, 1.0f, 0.5f, 0.333f, 0.333f, 0.5f, 1.0f, -0.333f, 0.5f, -0.333f, 1.0f, 0.5f,
-0.333f, 0.333f, 0.5f, 0.333f, -0.333f, 0.5f, 0.333f, 0.333f, 0.5f, -1.0f, 1.0f, 0.5f, -0.333f, 0.333f, 0.5f,
-1.0f, 0.333f, 0.5f, -1.0f, -1.0f, -0.5f, -1.0f, -1.0f, 0.5f, 0.333f, -0.333f, 0.5f, 0.333f, -1.0f, 0.5f,
1.0f, -1.0f, 0.5f, 0.333f, -1.0f, 0.5f, 0.333f, 0.333f, 0.5f, 0.333f, -0.333f, 0.5f, 1.0f, -0.333f, 0.5f,
-0.333f, 0.333f, 0.5f, -0.333f, -0.333f, 0.5f, 0.333f, -0.333f, 0.5f,
};
// clang-format off
static constexpr AZStd::array TwoSeparatedPlanesIndices{
uint32_t{ 0 }, 1, 2, 3, 4, 5, 2, 6, 3, 7, 8, 9, 10, 5, 8, 11, 10, 7, 12, 3, 10, 13, 12, 11, 14, 2, 12,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 25, 29, 27, 24, 30, 31, 32, 33, 34, 29, 35, 17, 34,
0, 36, 1, 3, 6, 4, 2, 1, 6, 7, 10, 8, 10, 3, 5, 11, 12, 10, 12, 2, 3, 13, 14, 12, 14, 0, 2,
15, 37, 16, 38, 39, 40, 17, 16, 41, 24, 27, 25, 42, 43, 44, 29, 34, 27, 45, 46, 47, 33, 35, 34, 35, 15, 17,
};
// clang-format on
// Ensure that the index buffer references all the positions in the position buffer
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 TwoSeparatedPlanesMesh
class TestMesh
{
public:
TwoSeparatedPlanesMesh()
TestMesh(const float* positions, size_t positionCount, const uint32_t* indices, size_t indicesCount)
{
using namespace AZ;
RPI::ModelLodAssetCreator lodCreator;
lodCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
AZ::RPI::ModelLodAssetCreator lodCreator;
lodCreator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom()));
lodCreator.BeginMesh();
lodCreator.SetMeshAabb(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.SetMeshMaterialAsset(
AZ::Data::Asset<AZ::RPI::MaterialAsset>(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0),
AZ::AzTypeInfo<AZ::RPI::MaterialAsset>::Uuid(), "")
);
{
AZ::Data::Asset<AZ::RPI::BufferAsset> indexBuffer = BuildTestBuffer(s_indexes.size(), sizeof(uint32_t));
AZStd::copy(s_indexes.begin(), s_indexes.end(), reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(indexBuffer->GetBuffer().data())));
AZ::Data::Asset<AZ::RPI::BufferAsset> indexBuffer = BuildTestBuffer(indicesCount, sizeof(uint32_t));
AZStd::copy(indices, indices + indicesCount, reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(indexBuffer->GetBuffer().data())));
lodCreator.SetMeshIndexBuffer({
indexBuffer,
RHI::BufferViewDescriptor::CreateStructured(0, s_indexes.size(), sizeof(uint32_t))
AZ::RHI::BufferViewDescriptor::CreateStructured(0, indicesCount, sizeof(uint32_t))
});
}
{
AZ::Data::Asset<AZ::RPI::BufferAsset> positionBuffer = BuildTestBuffer(s_positions.size() / 3, sizeof(float) * 3);
AZStd::copy(s_positions.begin(), s_positions.end(), reinterpret_cast<float*>(const_cast<uint8_t*>(positionBuffer->GetBuffer().data())));
AZ::Data::Asset<AZ::RPI::BufferAsset> positionBuffer = BuildTestBuffer(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,
RHI::BufferViewDescriptor::CreateStructured(0, s_positions.size() / 3, sizeof(float) * 3)
AZ::RHI::BufferViewDescriptor::CreateStructured(0, positionCount / 3, sizeof(float) * 3)
}
);
}
lodCreator.EndMesh();
Data::Asset<RPI::ModelLodAsset> lodAsset;
AZ::Data::Asset<AZ::RPI::ModelLodAsset> lodAsset;
lodCreator.End(lodAsset);
RPI::ModelAssetCreator modelCreator;
modelCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
AZ::RPI::ModelAssetCreator modelCreator;
modelCreator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom()));
modelCreator.SetName("TestModel");
modelCreator.AddLodAsset(AZStd::move(lodAsset));
modelCreator.End(m_modelAsset);
@ -1030,40 +1068,20 @@ namespace UnitTest
private:
AZ::Data::Asset<AZ::RPI::ModelAsset> m_modelAsset;
static constexpr AZStd::array s_positions{
-1.0f, -0.333f, -0.5f, -0.333f, -1.0f, -0.5f, -0.333f, -0.333f, -0.5f, 0.333f, -0.333f, -0.5f, 1.0f, -1.0f, -0.5f,
1.0f, -0.333f, -0.5f, 0.333f, -1.0f, -0.5f, 0.333f, 1.0f, -0.5f, 1.0f, 0.333f, -0.5f, 1.0f, 1.0f, -0.5f,
0.333f, 0.333f, -0.5f, -0.333f, 1.0f, -0.5f, -0.333f, 0.333f, -0.5f, -1.0f, 1.0f, -0.5f, -1.0f, 0.333f, -0.5f,
-1.0f, -0.333f, 0.5f, -0.333f, -1.0f, 0.5f, -0.333f, -0.333f, 0.5f, 0.333f, -0.333f, 0.5f, 1.0f, -1.0f, 0.5f,
1.0f, -0.333f, 0.5f, -0.333f, -0.333f, 0.5f, 0.333f, -1.0f, 0.5f, 0.333f, -0.333f, 0.5f, 0.333f, 1.0f, 0.5f,
1.0f, 0.333f, 0.5f, 1.0f, 1.0f, 0.5f, 0.333f, 0.333f, 0.5f, 1.0f, -0.333f, 0.5f, -0.333f, 1.0f, 0.5f,
-0.333f, 0.333f, 0.5f, 0.333f, -0.333f, 0.5f, 0.333f, 0.333f, 0.5f, -1.0f, 1.0f, 0.5f, -0.333f, 0.333f, 0.5f,
-1.0f, 0.333f, 0.5f, -1.0f, -1.0f, -0.5f, -1.0f, -1.0f, 0.5f, 0.333f, -0.333f, 0.5f, 0.333f, -1.0f, 0.5f,
1.0f, -1.0f, 0.5f, 0.333f, -1.0f, 0.5f, 0.333f, 0.333f, 0.5f, 0.333f, -0.333f, 0.5f, 1.0f, -0.333f, 0.5f,
-0.333f, 0.333f, 0.5f, -0.333f, -0.333f, 0.5f, 0.333f, -0.333f, 0.5f,
};
static constexpr AZStd::array s_indexes{
uint32_t{0}, 1, 2, 3, 4, 5, 2, 6, 3, 7, 8, 9, 10, 5, 8, 11, 10, 7, 12, 3, 10, 13, 12, 11, 14, 2, 12,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 25, 29, 27, 24, 30, 31, 32, 33, 34, 29, 35, 17, 34,
0, 36, 1, 3, 6, 4, 2, 1, 6, 7, 10, 8, 10, 3, 5, 11, 12, 10, 12, 2, 3, 13, 14, 12, 14, 0, 2,
15, 37, 16, 38, 39, 40, 17, 16, 41, 24, 27, 25, 42, 43, 44, 29, 34, 27, 45, 46, 47, 33, 35, 34, 35, 15, 17,
};
// Ensure that the index buffer references all the positions in the position buffer
static constexpr inline auto minmaxElement = AZStd::minmax_element(begin(s_indexes), end(s_indexes));
static_assert(*minmaxElement.second == (s_positions.size() / 3) - 1);
};
struct KdTreeIntersectParams
struct IntersectParams
{
float xpos;
float ypos;
float zpos;
float xdir;
float ydir;
float zdir;
float expectedDistance;
bool expectedShouldIntersect;
friend std::ostream& operator<<(std::ostream& os, const KdTreeIntersectParams& param)
friend std::ostream& operator<<(std::ostream& os, const IntersectParams& param)
{
return os
<< "xpos:" << param.xpos
@ -1076,13 +1094,15 @@ namespace UnitTest
class KdTreeIntersectsParameterizedFixture
: public ModelTests
, public ::testing::WithParamInterface<KdTreeIntersectParams>
, public ::testing::WithParamInterface<IntersectParams>
{
};
TEST_P(KdTreeIntersectsParameterizedFixture, KdTreeIntersects)
{
TwoSeparatedPlanesMesh mesh;
TestMesh mesh(
TwoSeparatedPlanesPositions.data(), TwoSeparatedPlanesPositions.size(), TwoSeparatedPlanesIndices.data(),
TwoSeparatedPlanesIndices.size());
AZ::RPI::ModelKdTree kdTree;
ASSERT_TRUE(kdTree.Build(mesh.GetModel().Get()));
@ -1092,38 +1112,40 @@ namespace UnitTest
EXPECT_THAT(
kdTree.RayIntersection(
AZ::Vector3(GetParam().xpos, GetParam().ypos, GetParam().zpos), AZ::Vector3::CreateAxisZ(-1.0f), distance, normal),
AZ::Vector3(GetParam().xpos, GetParam().ypos, GetParam().zpos),
AZ::Vector3(GetParam().xdir, GetParam().ydir, GetParam().zdir), distance, normal),
testing::Eq(GetParam().expectedShouldIntersect));
EXPECT_THAT(distance, testing::FloatEq(GetParam().expectedDistance));
}
static constexpr inline AZStd::array<KdTreeIntersectParams, 21> intersectTestData{
KdTreeIntersectParams{ -0.1f, 0.0f, 1.0f, 0.5f, true },
KdTreeIntersectParams{ 0.0f, 0.0f, 1.0f, 0.5f, true },
KdTreeIntersectParams{ 0.1f, 0.0f, 1.0f, 0.5f, true },
static constexpr AZStd::array KdTreeIntersectTestData{
IntersectParams{ -0.1f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ 0.1f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
// Test the center of each triangle
KdTreeIntersectParams{-0.111f, -0.111f, 1.0f, 0.5f, true},
KdTreeIntersectParams{-0.111f, -0.778f, 1.0f, 0.5f, true},
KdTreeIntersectParams{-0.111f, 0.555f, 1.0f, 0.5f, true}, // Should intersect triangle with indices {29, 34, 27} and {11, 12, 10}
KdTreeIntersectParams{-0.555f, -0.555f, 1.0f, 0.5f, true},
KdTreeIntersectParams{-0.555f, 0.111f, 1.0f, 0.5f, true},
KdTreeIntersectParams{-0.555f, 0.778f, 1.0f, 0.5f, true},
KdTreeIntersectParams{-0.778f, -0.111f, 1.0f, 0.5f, true},
KdTreeIntersectParams{-0.778f, -0.778f, 1.0f, 0.5f, true},
KdTreeIntersectParams{-0.778f, 0.555f, 1.0f, 0.5f, true},
KdTreeIntersectParams{0.111f, -0.555f, 1.0f, 0.5f, true},
KdTreeIntersectParams{0.111f, 0.111f, 1.0f, 0.5f, true},
KdTreeIntersectParams{0.111f, 0.778f, 1.0f, 0.5f, true},
KdTreeIntersectParams{0.555f, -0.111f, 1.0f, 0.5f, true},
KdTreeIntersectParams{0.555f, -0.778f, 1.0f, 0.5f, true},
KdTreeIntersectParams{0.555f, 0.555f, 1.0f, 0.5f, true},
KdTreeIntersectParams{0.778f, -0.555f, 1.0f, 0.5f, true},
KdTreeIntersectParams{0.778f, 0.111f, 1.0f, 0.5f, true},
KdTreeIntersectParams{0.778f, 0.778f, 1.0f, 0.5f, true},
IntersectParams{ -0.111f, -0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ -0.111f, -0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ -0.111f, 0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f,
true }, // Should intersect triangle with indices {29, 34, 27} and {11, 12, 10}
IntersectParams{ -0.555f, -0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ -0.555f, 0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ -0.555f, 0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ -0.778f, -0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ -0.778f, -0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ -0.778f, 0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ 0.111f, -0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ 0.111f, 0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ 0.111f, 0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ 0.555f, -0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ 0.555f, -0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ 0.555f, 0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ 0.778f, -0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ 0.778f, 0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ 0.778f, 0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
};
INSTANTIATE_TEST_CASE_P(KdTreeIntersectsPlane, KdTreeIntersectsParameterizedFixture, ::testing::ValuesIn(intersectTestData));
INSTANTIATE_TEST_CASE_P(KdTreeIntersectsPlane, KdTreeIntersectsParameterizedFixture, ::testing::ValuesIn(KdTreeIntersectTestData));
class KdTreeIntersectsFixture
: public ModelTests
@ -1133,7 +1155,10 @@ namespace UnitTest
{
ModelTests::SetUp();
m_mesh = AZStd::make_unique<TwoSeparatedPlanesMesh>();
m_mesh = AZStd::make_unique<TestMesh>(
TwoSeparatedPlanesPositions.data(), TwoSeparatedPlanesPositions.size(), TwoSeparatedPlanesIndices.data(),
TwoSeparatedPlanesIndices.size());
m_kdTree = AZStd::make_unique<AZ::RPI::ModelKdTree>();
ASSERT_TRUE(m_kdTree->Build(m_mesh->GetModel().Get()));
}
@ -1146,7 +1171,7 @@ namespace UnitTest
ModelTests::TearDown();
}
AZStd::unique_ptr<TwoSeparatedPlanesMesh> m_mesh;
AZStd::unique_ptr<TestMesh> m_mesh;
AZStd::unique_ptr<AZ::RPI::ModelKdTree> m_kdTree;
};
@ -1154,7 +1179,7 @@ namespace UnitTest
{
float t = AZStd::numeric_limits<float>::max();
AZ::Vector3 normal;
constexpr float rayLength = 100.0f;
EXPECT_THAT(
m_kdTree->RayIntersection(
@ -1181,4 +1206,85 @@ namespace UnitTest
EXPECT_THAT(
m_kdTree->RayIntersection(AZ::Vector3::CreateAxisZ(5.0f), -AZ::Vector3::CreateAxisZ(), t, normal), testing::Eq(false));
}
class BruteForceIntersectsParameterizedFixture
: public ModelTests
, public ::testing::WithParamInterface<IntersectParams>
{
};
TEST_P(BruteForceIntersectsParameterizedFixture, BruteForceIntersectsCube)
{
TestMesh mesh(CubePositions.data(), CubePositions.size(), CubeIndices.data(), CubeIndices.size());
float distance = AZStd::numeric_limits<float>::max();
AZ::Vector3 normal;
EXPECT_THAT(
mesh.GetModel()->LocalRayIntersectionAgainstModel(
AZ::Vector3(GetParam().xpos, GetParam().ypos, GetParam().zpos),
AZ::Vector3(GetParam().xdir, GetParam().ydir, GetParam().zdir), distance, normal),
testing::Eq(GetParam().expectedShouldIntersect));
EXPECT_THAT(distance, testing::FloatEq(GetParam().expectedDistance));
}
static constexpr AZStd::array BruteForceIntersectTestData{
IntersectParams{ 5.0f, 0.0f, 5.0f, 0.0f, 0.0f, -1.0f, AZStd::numeric_limits<float>::max(), false },
IntersectParams{ 0.0f, 0.0f, 1.5f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ 5.0f, 0.0f, 0.0f, -10.0f, 0.0f, 0.0f, 0.4f, true },
IntersectParams{ -5.0f, 0.0f, 0.0f, 20.0f, 0.0f, 0.0f, 0.2f, true },
IntersectParams{ 0.0f, -10.0f, 0.0f, 0.0f, 20.0f, 0.0f, 0.45f, true },
IntersectParams{ 0.0f, 20.0f, 0.0f, 0.0f, -40.0f, 0.0f, 0.475f, true },
IntersectParams{ 0.0f, 20.0f, 0.0f, 0.0f, -19.0f, 0.0f, 1.0f, true },
};
INSTANTIATE_TEST_CASE_P(
BruteForceIntersects, BruteForceIntersectsParameterizedFixture, ::testing::ValuesIn(BruteForceIntersectTestData));
class BruteForceModelIntersectsFixture
: public ModelTests
{
public:
void SetUp() override
{
ModelTests::SetUp();
m_mesh = AZStd::make_unique<TestMesh>(CubePositions.data(), CubePositions.size(), CubeIndices.data(), CubeIndices.size());
}
void TearDown() override
{
m_mesh.reset();
ModelTests::TearDown();
}
AZStd::unique_ptr<TestMesh> m_mesh;
};
TEST_F(BruteForceModelIntersectsFixture, BruteForceIntersectionDetectedWithCube)
{
float t = 0.0f;
AZ::Vector3 normal;
// firing down the negative z axis, positioned 5 units from cube (cube is 2x2x2 so intersection
// happens at 1 in z)
EXPECT_THAT(
m_mesh->GetModel()->LocalRayIntersectionAgainstModel(
AZ::Vector3::CreateAxisZ(5.0f), -AZ::Vector3::CreateAxisZ(10.0f), t, normal),
testing::Eq(true));
EXPECT_THAT(t, testing::FloatEq(0.4f));
}
TEST_F(BruteForceModelIntersectsFixture, BruteForceIntersectionDetectedAndNormalSetAtEndOfRay)
{
float t = 0.0f;
AZ::Vector3 normal = AZ::Vector3::CreateOne(); // invalid starting normal
// ensure the intersection happens right at the end of the ray
EXPECT_THAT(
m_mesh->GetModel()->LocalRayIntersectionAgainstModel(
AZ::Vector3::CreateAxisY(10.0f), -AZ::Vector3::CreateAxisY(9.0f), t, normal),
testing::Eq(true));
EXPECT_THAT(t, testing::FloatEq(1.0f));
EXPECT_THAT(normal, IsClose(AZ::Vector3::CreateAxisY()));
}
} // namespace UnitTest

Loading…
Cancel
Save