diff --git a/Code/Framework/AzFramework/AzFramework/Render/IntersectorInterface.h b/Code/Framework/AzFramework/AzFramework/Render/IntersectorInterface.h index 44e1290911..10ea51dd75 100644 --- a/Code/Framework/AzFramework/AzFramework/Render/IntersectorInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Render/IntersectorInterface.h @@ -24,7 +24,7 @@ namespace AzFramework //! IntersectorBus::EventResult(result, editorContextId, &IntersectorInterface::RayIntersect, ray); //! //! Raycast against all entities - //! RayResultAggregator rayResult; + //! AZ::EBusReduceResult rayResult; //! IntersectorBus::BroadCastResult(rayResult, &IntersectorInterface::RayIntersect, ray); // class IntersectorInterface diff --git a/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.h b/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.h index 8b29f2a554..f9ef264ab1 100644 --- a/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.h +++ b/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.h @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include namespace AzFramework @@ -185,6 +187,11 @@ namespace AzFramework SurfacePointRegionFillCallback perPositionCallback, Sampler sampleFilter = Sampler::DEFAULT) const = 0; + //! Get the terrain raycast entity context id. + virtual EntityContextId GetTerrainRaycastEntityContextId() const = 0; + + //! Given a ray, return the closest intersection with terrain. + virtual RenderGeometry::RayResult GetClosestIntersection(const RenderGeometry::RayRequest& ray) const = 0; private: // Private variations of the GetSurfacePoint API exposed to BehaviorContext that returns a value instead of diff --git a/Code/Framework/AzFramework/Tests/Mocks/Terrain/MockTerrainDataRequestBus.h b/Code/Framework/AzFramework/Tests/Mocks/Terrain/MockTerrainDataRequestBus.h index 52ac28ef63..f5af0ff486 100644 --- a/Code/Framework/AzFramework/Tests/Mocks/Terrain/MockTerrainDataRequestBus.h +++ b/Code/Framework/AzFramework/Tests/Mocks/Terrain/MockTerrainDataRequestBus.h @@ -102,5 +102,9 @@ namespace UnitTest ProcessSurfaceWeightsFromRegion, void(const AZ::Aabb&, const AZ::Vector2&, AzFramework::Terrain::SurfacePointRegionFillCallback, Sampler)); MOCK_CONST_METHOD4( ProcessSurfacePointsFromRegion, void(const AZ::Aabb&, const AZ::Vector2&, AzFramework::Terrain::SurfacePointRegionFillCallback, Sampler)); + MOCK_CONST_METHOD0( + GetTerrainRaycastEntityContextId, AzFramework::EntityContextId()); + MOCK_CONST_METHOD1( + GetClosestIntersection, AzFramework::RenderGeometry::RayResult(const AzFramework::RenderGeometry::RayRequest&)); }; } // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.cpp index 24b3c95310..610b9ec854 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.cpp @@ -7,6 +7,7 @@ */ #include +#include #include namespace AzToolsFramework @@ -66,15 +67,19 @@ namespace AzToolsFramework AZ::Vector3 FindClosestPickIntersection(const AzFramework::RenderGeometry::RayRequest& rayRequest, const float defaultDistance) { - AzFramework::RenderGeometry::RayResult renderGeometryIntersectionResult; + // attempt a ray intersection with any visible mesh or terrain and return the intersection position if successful + AZ::EBusReduceResult renderGeometryIntersectionResult; AzFramework::RenderGeometry::IntersectorBus::EventResult( renderGeometryIntersectionResult, AzToolsFramework::GetEntityContextId(), &AzFramework::RenderGeometry::IntersectorBus::Events::RayIntersect, rayRequest); + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + renderGeometryIntersectionResult, + &AzFramework::Terrain::TerrainDataRequests::GetClosestIntersection, + rayRequest); - // attempt a ray intersection with any visible mesh and return the intersection position if successful - if (renderGeometryIntersectionResult) + if (renderGeometryIntersectionResult.value) { - return renderGeometryIntersectionResult.m_worldPosition; + return renderGeometryIntersectionResult.value.m_worldPosition; } else { diff --git a/Gems/Terrain/Code/Source/TerrainRaycast/TerrainRaycastContext.cpp b/Gems/Terrain/Code/Source/TerrainRaycast/TerrainRaycastContext.cpp new file mode 100644 index 0000000000..d42388db60 --- /dev/null +++ b/Gems/Terrain/Code/Source/TerrainRaycast/TerrainRaycastContext.cpp @@ -0,0 +1,392 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +#include +#include + +using namespace Terrain; + +namespace +{ + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Convenience function to clamp a value to the given grid resolution, rounding up. + inline float ClampToGridRoundUp(float value, float gridResolution) + { + return ceil(value / gridResolution) * gridResolution; + } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Convenience function to clamp a value to the given grid resolution, rounding down. + inline float ClampToGridRoundDown(float value, float gridResolution) + { + return floor(value / gridResolution) * gridResolution; + } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Convenience function to find the nearest intersection (if any) between an AABB and a ray. + inline void FindNearestIntersection(const AZ::Aabb& aabb, + const AZ::Vector3& rayStart, + const AZ::Vector3& rayDirection, + const AZ::Vector3& rayDirectionReciprocal, + AzFramework::RenderGeometry::RayResult& result) + { + float intersectionT; + float intersectionEndT; + AZ::Vector3 intersectionNormal; + const int intersectionResult = AZ::Intersect::IntersectRayAABB(rayStart, + rayDirection, + rayDirectionReciprocal, + aabb, + intersectionT, + intersectionEndT, + intersectionNormal); + if (intersectionResult != AZ::Intersect::ISECT_RAY_AABB_NONE) + { + result.m_worldPosition = rayStart + (rayDirection * intersectionT); + result.m_worldNormal = intersectionNormal; + result.m_distance = rayDirection.GetLength() * intersectionT; + } + else + { + result.m_distance = FLT_MAX; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Convenience function to find the nearest intersection (if any) between a triangle and a ray. + // This is an implementation of the Moller-Trumbore intersection algorithm. I first attempted to use + // the existing AZ::Intersect::IntersectSegmentTriangleCCW, which appears to use the same algorithm, + // but it takes a line segment as opposed to a ray and was not returning the expected results. Once + // I've written some tests I can go back and try it again to figure out what is different, but this + // is all likely to get replaced with an optimized SIMD version anyway so this should be ok for now. + inline void FindNearestIntersection(const AZ::Vector3& vertexA, + const AZ::Vector3& vertexB, + const AZ::Vector3& vertexC, + const AZ::Vector3& rayStart, + const AZ::Vector3& rayDirection, + AzFramework::RenderGeometry::RayResult& result) + { + const AZ::Vector3 edgeAB = vertexB - vertexA; + const AZ::Vector3 edgeAC = vertexC - vertexA; + const AZ::Vector3 pVec = rayDirection.Cross(edgeAC); + const float det = edgeAB.Dot(pVec); + if (AZ::IsClose(det, 0.0f)) + { + // The ray is parallel to the triangle. + return; + } + + const float detInv = 1.0f / det; + const AZ::Vector3 tVec = rayStart - vertexA; + const float u = detInv * tVec.Dot(pVec); + if (u < 0.0f || u > 1.0f) + { + // No intersection. + return; + } + + const AZ::Vector3 qVec = tVec.Cross(edgeAB); + const float v = detInv * rayDirection.Dot(qVec); + if (v < 0.0 || u + v > 1.0) + { + // No intersection. + return; + } + + const float t = detInv * edgeAC.Dot(qVec); + if (t > FLT_EPSILON) + { + result.m_worldPosition = rayStart + (rayDirection * t); + result.m_worldNormal = edgeAB.Cross(edgeAC); + result.m_distance = rayDirection.GetLength() * t; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Convenience function to get the terrain height values at each corner of an AABB, triangulate them, + // and then find the nearest intersection (if any) between the resulting triangles and the given ray. + inline void TriangulateAndFindNearestIntersection(const TerrainSystem& terrainSystem, + const AZ::Aabb& aabb, + const AZ::Vector3& rayStart, + const AZ::Vector3& rayDirection, + const AZ::Vector3& rayDirectionReciprocal, + AzFramework::RenderGeometry::RayResult& result) + { + // Obtain the height values at each corner of the AABB. + const AZ::Vector3& aabbMin = aabb.GetMin(); + const AZ::Vector3& aabbMax = aabb.GetMax(); + AZ::Vector3 point0 = aabbMin; + AZ::Vector3 point2 = aabbMax; + AZ::Vector3 point1(point0.GetX(), point2.GetY(), 0.0f); + AZ::Vector3 point3(point2.GetX(), point0.GetY(), 0.0f); + point0.SetZ(terrainSystem.GetHeight(point0, AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT)); + point1.SetZ(terrainSystem.GetHeight(point1, AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT)); + point2.SetZ(terrainSystem.GetHeight(point2, AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT)); + point3.SetZ(terrainSystem.GetHeight(point3, AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT)); + + // Construct a smaller AABB that tightly encloses the four terrain points. + const float refinedMinZ = AZStd::GetMin(AZStd::GetMin(AZStd::GetMin(point0.GetZ(), point1.GetZ()), point2.GetZ()), point3.GetZ()); + const float refinedMaxZ = AZStd::GetMax(AZStd::GetMax(AZStd::GetMax(point0.GetZ(), point1.GetZ()), point2.GetZ()), point3.GetZ()); + const AZ::Vector3 refinedMin(aabbMin.GetX(), aabbMin.GetY(), refinedMinZ); + const AZ::Vector3 refinedMax(aabbMax.GetX(), aabbMax.GetY(), refinedMaxZ); + const AZ::Aabb refinedAABB = AZ::Aabb::CreateFromMinMax(refinedMin, refinedMax); + + // Check for a hit against the refined AABB. + float intersectionT; + float intersectionEndT; + const int intersectionResult = AZ::Intersect::IntersectRayAABB2(rayStart, + rayDirectionReciprocal, + refinedAABB, + intersectionT, + intersectionEndT); + if (intersectionResult == AZ::Intersect::ISECT_RAY_AABB_NONE) + { + return; + } + + // Finally, triangulate the four terrain points and check for a hit, + // splitting using the top-left -> bottom-right diagonal so to match + // the current behavior of the terrain physics and rendering systems. + AzFramework::RenderGeometry::RayResult bottomLeftIntersectionResult; + FindNearestIntersection(rayStart, + rayDirection, + point0, + point3, + point1, + bottomLeftIntersectionResult); + + AzFramework::RenderGeometry::RayResult topRightIntersectionResult; + FindNearestIntersection(rayStart, + rayDirection, + point2, + point1, + point3, + topRightIntersectionResult); + + if (bottomLeftIntersectionResult) + { + result = !topRightIntersectionResult || bottomLeftIntersectionResult.m_distance < topRightIntersectionResult.m_distance ? + bottomLeftIntersectionResult : + topRightIntersectionResult; + } + else if (topRightIntersectionResult) + { + result = topRightIntersectionResult; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Iterative function that divides an AABB encompasing terrain points into columns (or a voxel grid) + // of size equal to the given grid resolution, steps along the ray visiting each voxel it intersects + // in order from nearest to farthest, then obtains the terrain height values at each corner in order + // to triangulate them and find the nearest intersection (if any) between the triangles and the ray. + // + // Visualization: + // - X: Column intersection but no triangle hit found + // - T: Column intersection with a triangle hit found + // ________________________________________ + // | | | | | | | | | + // |____|____|____|____|____|____|____|____| Ray + // | | | | | | | | | / + // |____|____|____|____|____|____|____|____| / + // | | | | | | | | X |/ + // |____|____|____|____|____|____|____|____/ + // | | | | | | | | X /| + // |____|____|____|____|____|____|____|__/_| + // | | | | | | | | /X | + // |____|____|____|____|____|____|____|/___| + // | | | | | | | X / X | + // |____|____|____|____|____|____|___/|____| + // | | | | | | | T/ | | + // |____|____|____|____|____|____|____|____| + // | | | | | | | | | + // |____|____|____|____|____|____|____|____| + inline void FindNearestIntersectionIterative(const TerrainSystem& terrainSystem, + const AZ::Vector2& terrainResolution, + const AZ::Aabb& terrainWorldBounds, + const AZ::Vector3& rayStart, + const AZ::Vector3& rayEnd, + AzFramework::RenderGeometry::RayResult& result) + { + // Find the nearest intersection (if any) between the ray and terrain world bounds. + // Note that the ray might (and often will) start inside the terrain world bounds. + const AZ::Vector3 rayDirection = rayEnd - rayStart; + const AZ::Vector3 rayDirectionReciprocal = rayDirection.GetReciprocal(); + FindNearestIntersection(terrainWorldBounds, + rayStart, + rayDirection, + rayDirectionReciprocal, + result); + if (!result) + { + // The ray does not intersect the terrain world bounds. + return; + } + + // The terrain world can be visualized as a grid of columns, + // where the terrain resolution determines the dimensions of + // each column, or a voxel grid with one cell in z dimension. + // + // Starting at the voxel containing the initial intersection, + // we want to step along the ray and visit each voxel the ray + // intersects in order from nearest to furthest until we find + // an intersection with the terrain or the ray exits the grid. + const AZ::Vector3& initialIntersection = result.m_worldPosition; + const float initialIntersectionX = initialIntersection.GetX(); + const float initialIntersectionY = initialIntersection.GetY(); + const float initialIntersectionZ = initialIntersection.GetZ(); + const float gridResolutionX = terrainResolution.GetX(); + const float gridResolutionY = terrainResolution.GetY(); + const float gridResolutionZ = terrainWorldBounds.GetMax().GetZ() - terrainWorldBounds.GetMin().GetZ(); + float initialVoxelMinX = ClampToGridRoundDown(initialIntersectionX, gridResolutionX); + float initialVoxelMinY = ClampToGridRoundDown(initialIntersectionY, gridResolutionY); + float initialVoxelMinZ = terrainWorldBounds.GetMin().GetZ(); + float initialVoxelMaxX = ClampToGridRoundUp(initialIntersectionX, gridResolutionX); + float initialVoxelMaxY = ClampToGridRoundUp(initialIntersectionY, gridResolutionY); + float initialVoxelMaxZ = terrainWorldBounds.GetMax().GetZ(); + + // For each axis calculate the distance t we need to move along + // the ray in order to fully traverse a voxel in that dimension. + const float rayDirectionX = rayDirection.GetX(); + const float rayDirectionY = rayDirection.GetY(); + const float rayDirectionZ = rayDirection.GetZ(); + const float stepX = AZ::GetSign(rayDirectionX) * gridResolutionX; + const float stepY = AZ::GetSign(rayDirectionY) * gridResolutionY; + const float stepZ = AZ::GetSign(rayDirectionZ) * gridResolutionZ; + const float tDeltaX = rayDirectionX ? + stepX / rayDirectionX : + std::numeric_limits::max(); + const float tDeltaY = rayDirectionY ? + stepY / rayDirectionY : + std::numeric_limits::max(); + const float tDeltaZ = rayDirectionZ ? + stepZ / rayDirectionZ : + std::numeric_limits::max(); + + // For each axis, calculate the distance t we need to move along the ray + // from the initial intersection point to the next voxel along that axis. + const float offsetX = stepX < 0.0f ? + initialVoxelMinX - initialIntersectionX : + initialVoxelMaxX - initialIntersectionX; + const float offsetY = stepY < 0.0f ? + initialVoxelMinY - initialIntersectionY : + initialVoxelMaxY - initialIntersectionY; + const float offsetZ = stepZ < 0.0f ? + initialVoxelMinZ - initialIntersectionZ : + initialVoxelMaxZ - initialIntersectionZ; + float tMaxX = rayDirectionX ? + offsetX / rayDirectionX : + std::numeric_limits::max(); + float tMaxY = rayDirectionY ? + offsetY / rayDirectionY : + std::numeric_limits::max(); + float tMaxZ = rayDirectionZ ? + offsetZ / rayDirectionZ : + std::numeric_limits::max(); + + // Calculate the min/max voxel grid value on each axis by expanding + // the terrain world bounds so they align with the grid resolution. + const float voxelGridMinX = ClampToGridRoundDown(terrainWorldBounds.GetMin().GetX(), gridResolutionX); + const float voxelGridMinY = ClampToGridRoundDown(terrainWorldBounds.GetMin().GetY(), gridResolutionY); + const float voxelGridMinZ = terrainWorldBounds.GetMin().GetZ(); + const float voxelGridMaxX = ClampToGridRoundUp(terrainWorldBounds.GetMax().GetX(), gridResolutionX); + const float voxelGridMaxY = ClampToGridRoundUp(terrainWorldBounds.GetMax().GetY(), gridResolutionY); + const float voxelGridMaxZ = terrainWorldBounds.GetMax().GetZ(); + + // Using the initial voxel values, construct an AABB representing the current voxel, + // then grab references to AABBs min/max vectors so we can manipulate them directly. + AZ::Aabb currentVoxel = AZ::Aabb::CreateFromMinMax({initialVoxelMinX, initialVoxelMinY, initialVoxelMinZ}, + {initialVoxelMaxX, initialVoxelMaxY, initialVoxelMaxZ}); + AZ::Vector3& currentVoxelMin = const_cast(currentVoxel.GetMin()); + AZ::Vector3& currentVoxelMax = const_cast(currentVoxel.GetMax()); + const AZ::Vector3 stepVecX(stepX, 0.0f, 0.0f); + const AZ::Vector3 stepVecY(0.0f, stepY, 0.0f); + const AZ::Vector3 stepVecZ(0.0f, 0.0f, stepZ); + + // Now we can step along the ray and visit each voxel the ray + // intersects in order from nearest to furthest until we find + // an intersection with the terrain or the ray exits the grid. + result = AzFramework::RenderGeometry::RayResult(); + while (currentVoxel.GetMin().GetX() <= voxelGridMaxX && + currentVoxel.GetMax().GetX() >= voxelGridMinX && + currentVoxel.GetMin().GetY() <= voxelGridMaxY && + currentVoxel.GetMax().GetY() >= voxelGridMinY && + currentVoxel.GetMin().GetZ() <= voxelGridMaxZ && + currentVoxel.GetMax().GetZ() >= voxelGridMinZ && + tMaxX <= 1.0f && tMaxY <= 1.0f && tMaxZ <= 1.0f) + { + TriangulateAndFindNearestIntersection(terrainSystem, + currentVoxel, + rayStart, + rayDirection, + rayDirectionReciprocal, + result); + if (result) + { + // Intersection found. + break; + } + + // Step to the next voxel. + if (tMaxX < tMaxY && tMaxX < tMaxZ) + { + currentVoxelMin += stepVecX; + currentVoxelMax += stepVecX; + tMaxX += tDeltaX; + } + else if (tMaxY < tMaxZ) + { + currentVoxelMin += stepVecY; + currentVoxelMax += stepVecY; + tMaxY += tDeltaY; + } + else + { + currentVoxelMin += stepVecZ; + currentVoxelMax += stepVecZ; + tMaxZ += tDeltaZ; + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +TerrainRaycastContext::TerrainRaycastContext(TerrainSystem& terrainSystem) + : m_terrainSystem(terrainSystem) + , m_entityContextId(AzFramework::EntityContextId::CreateRandom()) +{ + AzFramework::RenderGeometry::IntersectorBus::Handler::BusConnect(m_entityContextId); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +TerrainRaycastContext::~TerrainRaycastContext() +{ + AzFramework::RenderGeometry::IntersectorBus::Handler::BusDisconnect(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +AzFramework::RenderGeometry::RayResult TerrainRaycastContext::RayIntersect( + const AzFramework::RenderGeometry::RayRequest& ray) +{ + const AZ::Aabb terrainWorldBounds = m_terrainSystem.GetTerrainAabb(); + const AZ::Vector2 terrainResolution = m_terrainSystem.GetTerrainHeightQueryResolution(); + AzFramework::RenderGeometry::RayResult rayIntersectionResult; + FindNearestIntersectionIterative(m_terrainSystem, + terrainResolution, + terrainWorldBounds, + ray.m_startWorldPosition, + ray.m_endWorldPosition, + rayIntersectionResult); + + // If needed we could call m_terrainSystem.FindBestAreaEntityAtPosition in order to set + // rayIntersectionResult.m_entityAndComponent, but I'm not sure whether that is correct. + return rayIntersectionResult; +} diff --git a/Gems/Terrain/Code/Source/TerrainRaycast/TerrainRaycastContext.h b/Gems/Terrain/Code/Source/TerrainRaycast/TerrainRaycastContext.h new file mode 100644 index 0000000000..a5a50848c3 --- /dev/null +++ b/Gems/Terrain/Code/Source/TerrainRaycast/TerrainRaycastContext.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// +namespace Terrain +{ + class TerrainSystem; + + //////////////////////////////////////////////////////////////////////////////////////////////// + class TerrainRaycastContext : public AzFramework::RenderGeometry::IntersectorBus::Handler + { + public: + //////////////////////////////////////////////////////////////////////////////////////////// + //! Constructor + //! \param[in] terrainSystem The terrain system that owns this terrain raycast context + TerrainRaycastContext(TerrainSystem& terrainSystem); + + //////////////////////////////////////////////////////////////////////////////////////////// + // Disable copying + AZ_DISABLE_COPY_MOVE(TerrainRaycastContext); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Destructor + ~TerrainRaycastContext(); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Access to the terrain raycast context's entity context id + //! \return The terrain raycast context's entity context id + inline AzFramework::EntityContextId GetEntityContextId() const { return m_entityContextId; } + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref AzFramework::RenderGeometry::RayIntersect + AzFramework::RenderGeometry::RayResult RayIntersect(const AzFramework::RenderGeometry::RayRequest& ray) override; + + protected: + //////////////////////////////////////////////////////////////////////////////////////////// + // RenderGeometry::IntersectorBus inherits from RenderGeometry::IntersectionNotifications, + // so we must override the following pure virtual functions. We could potentially implement + // them using TerrainSystem::m_registeredAreas, but right now that would not serve a purpose. + ///@{ + //! Unused pure virtual override + void OnEntityConnected(AZ::EntityId) override {} + void OnEntityDisconnected(AZ::EntityId) override {} + void OnGeometryChanged(AZ::EntityId) override {} + ///@} + + private: + //////////////////////////////////////////////////////////////////////////////////////////// + // Variables + TerrainSystem& m_terrainSystem; //!< Terrain system that owns this terrain raycast context + AzFramework::EntityContextId m_entityContextId; //!< This object's entity context id + }; +} // namespace Terrain diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp index 4dfa03ed53..35e6992db3 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp @@ -51,6 +51,7 @@ bool TerrainLayerPriorityComparator::operator()(const AZ::EntityId& layer1id, co } TerrainSystem::TerrainSystem() + : m_terrainRaycastContext(*this) { Terrain::TerrainSystemServiceRequestBus::Handler::BusConnect(); AZ::TickBus::Handler::BusConnect(); @@ -438,6 +439,16 @@ void TerrainSystem::GetSurfacePointFromFloats( GetSurfacePoint(AZ::Vector3(x, y, 0.0f), outSurfacePoint, sampleFilter, terrainExistsPtr); } +AzFramework::EntityContextId TerrainSystem::GetTerrainRaycastEntityContextId() const +{ + return m_terrainRaycastContext.GetEntityContextId(); +} + +AzFramework::RenderGeometry::RayResult TerrainSystem::GetClosestIntersection( + const AzFramework::RenderGeometry::RayRequest& ray) const +{ + return m_terrainRaycastContext.RayIntersect(ray); +} AZ::EntityId TerrainSystem::FindBestAreaEntityAtPosition(float x, float y, AZ::Aabb& bounds) const { diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h index 3e489730d0..1cdb6e52b1 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h @@ -23,6 +23,7 @@ #include #include +#include #include namespace Terrain @@ -187,6 +188,9 @@ namespace Terrain AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback, Sampler sampleFilter = Sampler::DEFAULT) const override; + AzFramework::EntityContextId GetTerrainRaycastEntityContextId() const override; + AzFramework::RenderGeometry::RayResult GetClosestIntersection( + const AzFramework::RenderGeometry::RayRequest& ray) const override; private: void ClampPosition(float x, float y, AZ::Vector2& outPosition, AZ::Vector2& normalizedDelta) const; @@ -230,5 +234,7 @@ namespace Terrain mutable AZStd::shared_mutex m_areaMutex; AZStd::map m_registeredAreas; + + mutable TerrainRaycastContext m_terrainRaycastContext; }; } // namespace Terrain diff --git a/Gems/Terrain/Code/terrain_files.cmake b/Gems/Terrain/Code/terrain_files.cmake index ab19d33618..4b994c2587 100644 --- a/Gems/Terrain/Code/terrain_files.cmake +++ b/Gems/Terrain/Code/terrain_files.cmake @@ -27,6 +27,8 @@ set(FILES Source/Components/TerrainWorldDebuggerComponent.h Source/Components/TerrainWorldRendererComponent.cpp Source/Components/TerrainWorldRendererComponent.h + Source/TerrainRaycast/TerrainRaycastContext.cpp + Source/TerrainRaycast/TerrainRaycastContext.h Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.cpp Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.cpp