Browse Source

Support mesh intersection for camera orbit (#982)

* wip support for mesh intersection with intersector bus

* WIP camera mesh intersection orbit logic

* remove unneeded template argument

* add bus connect/disconnect

* fix intersection logic

* small updates, additional comments, some tidy-up

* update formatting options slightly

* use aznumeric_cast

* temp workaround for negative distances with RayIntersection
Tom Hulton-Harrop 4 năm trước cách đây
mục cha
commit
36ceff84c9

+ 1 - 1
.clang-format

@@ -46,7 +46,7 @@ SortIncludes: true
 SpaceAfterLogicalNot: false
 SpaceAfterTemplateKeyword: false
 SpaceBeforeAssignmentOperators: true
-SpaceBeforeCpp11BracedList: true
+SpaceBeforeCpp11BracedList: false
 SpaceBeforeCtorInitializerColon: true
 SpaceBeforeInheritanceColon: true
 SpaceBeforeParens: ControlStatements

+ 4 - 3
Code/Framework/AzFramework/AzFramework/Render/GeometryIntersectionBus.h

@@ -12,6 +12,7 @@
 #pragma once
 
 #include <AzCore/EBus/EBus.h>
+#include <AzFramework/Entity/EntityContextBus.h>
 #include <AzFramework/Render/GeometryIntersectionStructures.h>
 
 namespace AzFramework
@@ -35,12 +36,12 @@ namespace AzFramework
             AzFramework::EntityContextId m_contextId;
         };
 
-        //! Interface for intersection requests, implement this interface for making your component
-        //! render geometry intersectable.
+        //! Interface for intersection requests.
+        //! Implement this interface to make your component 'intersectable'.
         class IntersectionRequests
             : public AZ::EBusTraits
         {
-            //! Policy for notifying the Intersector bus of entities connected/disconnected to this ebus
+            //! Policy for notifying the Intersector bus of entities connected/disconnected to this EBus
             //! so it updates the internal data of the entities
             template<class Bus>
             struct IntersectionRequestsConnectionPolicy

+ 40 - 20
Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp

@@ -144,7 +144,7 @@ namespace AzFramework
             z = AZStd::atan2(-orientation.GetElement(1, 2), orientation.GetElement(1, 1));
         }
 
-        return {x, y, z};
+        return { x, y, z };
     }
 
     void UpdateCameraFromTransform(Camera& camera, const AZ::Transform& transform)
@@ -179,7 +179,7 @@ namespace AzFramework
     {
         const auto nextCamera = m_cameras.StepCamera(targetCamera, m_motionDelta, m_scrollDelta, deltaTime);
 
-        m_motionDelta = ScreenVector{0, 0};
+        m_motionDelta = ScreenVector{ 0, 0 };
         m_scrollDelta = 0.0f;
 
         return nextCamera;
@@ -213,7 +213,10 @@ namespace AzFramework
             auto& cameraInput = m_idleCameraInputs[i];
             const bool canBegin = cameraInput->Beginning() &&
                 AZStd::all_of(m_activeCameraInputs.cbegin(), m_activeCameraInputs.cend(),
-                              [](const auto& input) { return !input->Exclusive(); }) &&
+                              [](const auto& input)
+                              {
+                                  return !input->Exclusive();
+                              }) &&
                 (!cameraInput->Exclusive() || (cameraInput->Exclusive() && m_activeCameraInputs.empty()));
 
             if (canBegin)
@@ -231,7 +234,8 @@ namespace AzFramework
 
         const Camera nextCamera = AZStd::accumulate(
             AZStd::begin(m_activeCameraInputs), AZStd::end(m_activeCameraInputs), targetCamera,
-            [cursorDelta, scrollDelta, deltaTime](Camera acc, auto& camera) {
+            [cursorDelta, scrollDelta, deltaTime](Camera acc, auto& camera)
+            {
                 acc = camera->StepCamera(acc, cursorDelta, scrollDelta, deltaTime);
                 return acc;
             });
@@ -284,7 +288,8 @@ namespace AzFramework
 
     bool RotateCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
     {
-        const ClickDetector::ClickEvent clickEvent = [&event, this] {
+        const ClickDetector::ClickEvent clickEvent = [&event, this]
+        {
             if (const auto& input = AZStd::get_if<DiscreteInputEvent>(&event))
             {
                 if (input->m_channelId == m_rotateChannelId)
@@ -330,7 +335,10 @@ namespace AzFramework
         nextCamera.m_pitch -= float(cursorDelta.m_y) * ed_cameraSystemRotateSpeed;
         nextCamera.m_yaw -= float(cursorDelta.m_x) * ed_cameraSystemRotateSpeed;
 
-        const auto clampRotation = [](const float angle) { return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi); };
+        const auto clampRotation = [](const float angle)
+        {
+            return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi);
+        };
 
         nextCamera.m_yaw = clampRotation(nextCamera.m_yaw);
         // clamp pitch to be +-90 degrees
@@ -377,9 +385,10 @@ namespace AzFramework
         const auto deltaPanX = float(cursorDelta.m_x) * panAxes.m_horizontalAxis * ed_cameraSystemPanSpeed;
         const auto deltaPanY = float(cursorDelta.m_y) * panAxes.m_verticalAxis * ed_cameraSystemPanSpeed;
 
-        const auto inv = [](const bool invert) {
-            constexpr float Dir[] = {1.0f, -1.0f};
-            return Dir[static_cast<int>(invert)];
+        const auto inv = [](const bool invert)
+        {
+            constexpr float Dir[] = { 1.0f, -1.0f };
+            return Dir[aznumeric_cast<int>(invert)];
         };
 
         nextCamera.m_lookAt += deltaPanX * inv(ed_cameraSystemPanInvertX);
@@ -475,7 +484,8 @@ namespace AzFramework
         const auto axisY = translationBasis.GetBasisY();
         const auto axisZ = translationBasis.GetBasisZ();
 
-        const float speed = [boost = m_boost]() {
+        const float speed = [boost = m_boost]()
+        {
             return ed_cameraSystemTranslateSpeed * (boost ? ed_cameraSystemBoostMultiplier : 1.0f);
         }();
 
@@ -555,10 +565,12 @@ namespace AzFramework
 
         if (Beginning())
         {
-            const auto hasLookAt = [&nextCamera, &targetCamera, &lookAtFn = m_lookAtFn] {
+            const auto hasLookAt = [&nextCamera, &targetCamera, &lookAtFn = m_lookAtFn]
+            {
                 if (lookAtFn)
                 {
-                    if (const auto lookAt = lookAtFn())
+                    // pass through the camera's position and look vector for use in the lookAt function
+                    if (const auto lookAt = lookAtFn(targetCamera.Translation(), targetCamera.Rotation().GetBasisY()))
                     {
                         auto transform = AZ::Transform::CreateLookAt(targetCamera.m_lookAt, *lookAt);
                         nextCamera.m_lookDist = -lookAt->GetDistance(targetCamera.m_lookAt);
@@ -692,14 +704,20 @@ namespace AzFramework
 
     Camera SmoothCamera(const Camera& currentCamera, const Camera& targetCamera, const float deltaTime)
     {
-        const auto clamp_rotation = [](const float angle) { return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi); };
+        const auto clamp_rotation = [](const float angle)
+        {
+            return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi);
+        };
 
         // keep yaw in 0 - 360 range
         float targetYaw = clamp_rotation(targetCamera.m_yaw);
         const float currentYaw = clamp_rotation(currentCamera.m_yaw);
 
         // return the sign of the float input (-1, 0, 1)
-        const auto sign = [](const float value) { return aznumeric_cast<float>((0.0f < value) - (value < 0.0f)); };
+        const auto sign = [](const float value)
+        {
+            return aznumeric_cast<float>((0.0f < value) - (value < 0.0f));
+        };
 
         // ensure smooth transition when moving across 0 - 360 boundary
         const float yawDelta = targetYaw - currentYaw;
@@ -727,26 +745,28 @@ namespace AzFramework
         const auto& inputChannelId = inputChannel.GetInputChannelId();
         const auto& inputDeviceId = inputChannel.GetInputDevice().GetInputDeviceId();
 
-        const bool wasMouseButton =
-            AZStd::any_of(InputDeviceMouse::Button::All.begin(), InputDeviceMouse::Button::All.end(), [inputChannelId](const auto& button) {
+        const bool wasMouseButton = AZStd::any_of(
+            InputDeviceMouse::Button::All.begin(), InputDeviceMouse::Button::All.end(),
+            [inputChannelId](const auto& button)
+            {
                 return button == inputChannelId;
             });
 
         if (inputChannelId == InputDeviceMouse::Movement::X)
         {
-            return HorizontalMotionEvent{(int)inputChannel.GetValue()};
+            return HorizontalMotionEvent{ aznumeric_cast<int>(inputChannel.GetValue()) };
         }
         else if (inputChannelId == InputDeviceMouse::Movement::Y)
         {
-            return VerticalMotionEvent{(int)inputChannel.GetValue()};
+            return VerticalMotionEvent{ aznumeric_cast<int>(inputChannel.GetValue()) };
         }
         else if (inputChannelId == InputDeviceMouse::Movement::Z)
         {
-            return ScrollEvent{inputChannel.GetValue()};
+            return ScrollEvent{ inputChannel.GetValue() };
         }
         else if (wasMouseButton || InputDeviceKeyboard::IsKeyboardDevice(inputDeviceId))
         {
-            return DiscreteInputEvent{inputChannelId, inputChannel.GetState()};
+            return DiscreteInputEvent{ inputChannelId, inputChannel.GetState() };
         }
 
         return AZStd::monostate{};

+ 16 - 10
Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h

@@ -34,9 +34,9 @@ namespace AzFramework
         AZ::Vector3 m_lookAt = AZ::Vector3::CreateZero(); //!< Position of camera when m_lookDist is zero,
                                                           //!< or position of m_lookAt when m_lookDist is greater
                                                           //!< than zero.
-        float m_yaw{0.0};
-        float m_pitch{0.0};
-        float m_lookDist{0.0}; //!< Zero gives first person free look, otherwise orbit about m_lookAt
+        float m_yaw{ 0.0 };
+        float m_pitch{ 0.0 };
+        float m_lookDist{ 0.0 }; //!< Zero gives first person free look, otherwise orbit about m_lookAt
 
         //! View camera transform (v in MVP).
         AZ::Transform View() const;
@@ -195,7 +195,11 @@ namespace AzFramework
     inline bool Cameras::Exclusive() const
     {
         return AZStd::any_of(
-            m_activeCameraInputs.begin(), m_activeCameraInputs.end(), [](const auto& cameraInput) { return cameraInput->Exclusive(); });
+            m_activeCameraInputs.begin(), m_activeCameraInputs.end(),
+            [](const auto& cameraInput)
+            {
+                return cameraInput->Exclusive();
+            });
     }
 
     //! Responsible for updating a series of cameras given various inputs.
@@ -209,7 +213,7 @@ namespace AzFramework
 
     private:
         ScreenVector m_motionDelta; //!< The delta used for look/orbit/pan (rotation + translation) - two dimensional.
-        float m_scrollDelta = 0.0f; //!< The delta used for dolly/movement (translation) - one dimensional. 
+        float m_scrollDelta = 0.0f; //!< The delta used for dolly/movement (translation) - one dimensional.
     };
 
     class RotateCameraInput : public CameraInput
@@ -237,7 +241,7 @@ namespace AzFramework
     inline PanAxes LookPan(const Camera& camera)
     {
         const AZ::Matrix3x3 orientation = camera.Rotation();
-        return {orientation.GetBasisX(), orientation.GetBasisZ()};
+        return { orientation.GetBasisX(), orientation.GetBasisZ() };
     }
 
     inline PanAxes OrbitPan(const Camera& camera)
@@ -245,12 +249,13 @@ namespace AzFramework
         const AZ::Matrix3x3 orientation = camera.Rotation();
 
         const auto basisX = orientation.GetBasisX();
-        const auto basisY = [&orientation] {
+        const auto basisY = [&orientation]
+        {
             const auto forward = orientation.GetBasisY();
             return AZ::Vector3(forward.GetX(), forward.GetY(), 0.0f).GetNormalized();
         }();
 
-        return {basisX, basisY};
+        return { basisX, basisY };
     }
 
     class PanCameraInput : public CameraInput
@@ -285,7 +290,8 @@ namespace AzFramework
         const AZ::Matrix3x3 orientation = camera.Rotation();
 
         const auto basisX = orientation.GetBasisX();
-        const auto basisY = [&orientation] {
+        const auto basisY = [&orientation]
+        {
             const auto forward = orientation.GetBasisY();
             return AZ::Vector3(forward.GetX(), forward.GetY(), 0.0f).GetNormalized();
         }();
@@ -398,7 +404,7 @@ namespace AzFramework
     class OrbitCameraInput : public CameraInput
     {
     public:
-        using LookAtFn = AZStd::function<AZStd::optional<AZ::Vector3>()>;
+        using LookAtFn = AZStd::function<AZStd::optional<AZ::Vector3>(const AZ::Vector3& position, const AZ::Vector3& direction)>;
 
         // CameraInput overrides ...
         bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;

+ 65 - 42
Code/Sandbox/Editor/EditorViewportWidget.cpp

@@ -1221,50 +1221,73 @@ void EditorViewportWidget::SetViewportId(int id)
         AzFramework::ReloadCameraKeyBindings();
 
         auto controller = AZStd::make_shared<AtomToolsFramework::ModularViewportCameraController>();
-        controller->SetCameraListBuilderCallback([](AzFramework::Cameras& cameras)
-        {
-            auto firstPersonRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::CameraFreeLookButton);
-            auto firstPersonPanCamera =
-                AZStd::make_shared<AzFramework::PanCameraInput>(AzFramework::CameraFreePanButton, AzFramework::LookPan);
-            auto firstPersonTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(AzFramework::LookTranslation);
-            auto firstPersonWheelCamera = AZStd::make_shared<AzFramework::ScrollTranslationCameraInput>();
-
-            auto orbitCamera = AZStd::make_shared<AzFramework::OrbitCameraInput>();
-            orbitCamera->SetLookAtFn([]() -> AZStd::optional<AZ::Vector3> {
-                AZStd::optional<AZ::Transform> manipulatorTransform;
-                AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult(
-                    manipulatorTransform, AzToolsFramework::GetEntityContextId(),
-                    &AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform);
-
-                if (manipulatorTransform)
-                {
-                    return manipulatorTransform->GetTranslation();
-                }
-
-                return {};
+        controller->SetCameraListBuilderCallback(
+            [](AzFramework::Cameras& cameras)
+            {
+                auto firstPersonRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::CameraFreeLookButton);
+                auto firstPersonPanCamera =
+                    AZStd::make_shared<AzFramework::PanCameraInput>(AzFramework::CameraFreePanButton, AzFramework::LookPan);
+                auto firstPersonTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(AzFramework::LookTranslation);
+                auto firstPersonWheelCamera = AZStd::make_shared<AzFramework::ScrollTranslationCameraInput>();
+
+                auto orbitCamera = AZStd::make_shared<AzFramework::OrbitCameraInput>();
+                orbitCamera->SetLookAtFn(
+                    [](const AZ::Vector3& position, const AZ::Vector3& direction) -> AZStd::optional<AZ::Vector3>
+                    {
+                        AZStd::optional<AZ::Transform> manipulatorTransform;
+                        AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult(
+                            manipulatorTransform, AzToolsFramework::GetEntityContextId(),
+                            &AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform);
+
+                        // initially attempt to use manipulator transform if one exists (there is a selection)
+                        if (manipulatorTransform)
+                        {
+                            return manipulatorTransform->GetTranslation();
+                        }
+
+                        const float RayDistance = 1000.0f;
+                        AzFramework::RenderGeometry::RayRequest ray;
+                        ray.m_startWorldPosition = position;
+                        ray.m_endWorldPosition = position + direction * RayDistance;
+                        ray.m_onlyVisible = true;
+
+                        AzFramework::RenderGeometry::RayResult renderGeometryIntersectionResult;
+                        AzFramework::RenderGeometry::IntersectorBus::EventResult(
+                            renderGeometryIntersectionResult, AzToolsFramework::GetEntityContextId(),
+                            &AzFramework::RenderGeometry::IntersectorInterface::RayIntersect, ray);
+
+                        // attempt a ray intersection with any visible mesh and return the intersection position if successful
+                        if (renderGeometryIntersectionResult)
+                        {
+                            return renderGeometryIntersectionResult.m_worldPosition;
+                        }
+
+                        // if there is no selection or no intersection, fallback to default camera orbit behavior (ground plane
+                        // intersection)
+                        return {};
+                    });
+
+                auto orbitRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::CameraOrbitLookButton);
+                auto orbitTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(AzFramework::OrbitTranslation);
+                auto orbitDollyWheelCamera = AZStd::make_shared<AzFramework::OrbitDollyScrollCameraInput>();
+                auto orbitDollyMoveCamera =
+                    AZStd::make_shared<AzFramework::OrbitDollyCursorMoveCameraInput>(AzFramework::CameraOrbitDollyButton);
+                auto orbitPanCamera =
+                    AZStd::make_shared<AzFramework::PanCameraInput>(AzFramework::CameraOrbitPanButton, AzFramework::OrbitPan);
+
+                orbitCamera->m_orbitCameras.AddCamera(orbitRotateCamera);
+                orbitCamera->m_orbitCameras.AddCamera(orbitTranslateCamera);
+                orbitCamera->m_orbitCameras.AddCamera(orbitDollyWheelCamera);
+                orbitCamera->m_orbitCameras.AddCamera(orbitDollyMoveCamera);
+                orbitCamera->m_orbitCameras.AddCamera(orbitPanCamera);
+
+                cameras.AddCamera(firstPersonRotateCamera);
+                cameras.AddCamera(firstPersonPanCamera);
+                cameras.AddCamera(firstPersonTranslateCamera);
+                cameras.AddCamera(firstPersonWheelCamera);
+                cameras.AddCamera(orbitCamera);
             });
 
-            auto orbitRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::CameraOrbitLookButton);
-            auto orbitTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(AzFramework::OrbitTranslation);
-            auto orbitDollyWheelCamera = AZStd::make_shared<AzFramework::OrbitDollyScrollCameraInput>();
-            auto orbitDollyMoveCamera =
-                AZStd::make_shared<AzFramework::OrbitDollyCursorMoveCameraInput>(AzFramework::CameraOrbitDollyButton);
-            auto orbitPanCamera =
-                AZStd::make_shared<AzFramework::PanCameraInput>(AzFramework::CameraOrbitPanButton, AzFramework::OrbitPan);
-
-            orbitCamera->m_orbitCameras.AddCamera(orbitRotateCamera);
-            orbitCamera->m_orbitCameras.AddCamera(orbitTranslateCamera);
-            orbitCamera->m_orbitCameras.AddCamera(orbitDollyWheelCamera);
-            orbitCamera->m_orbitCameras.AddCamera(orbitDollyMoveCamera);
-            orbitCamera->m_orbitCameras.AddCamera(orbitPanCamera);
-
-            cameras.AddCamera(firstPersonRotateCamera);
-            cameras.AddCamera(firstPersonPanCamera);
-            cameras.AddCamera(firstPersonTranslateCamera);
-            cameras.AddCamera(firstPersonWheelCamera);
-            cameras.AddCamera(orbitCamera);
-        });
-
         m_renderViewport->GetControllerList()->Add(controller);
     }
     else

+ 1 - 2
Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.h

@@ -35,14 +35,13 @@ namespace AZ
             , private MeshComponentNotificationBus::Handler
         {
         public:
-
             using BaseClass = EditorRenderComponentAdapter<MeshComponentController, MeshComponent, MeshComponentConfig>;
             AZ_EDITOR_COMPONENT(AZ::Render::EditorMeshComponent, EditorMeshComponentTypeId, BaseClass);
 
             static void Reflect(AZ::ReflectContext* context);
 
             EditorMeshComponent() = default;
-            EditorMeshComponent(const MeshComponentConfig& config);
+            explicit EditorMeshComponent(const MeshComponentConfig& config);
 
             // AZ::Component overrides ...
             void Activate() override;

+ 1 - 2
Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponent.h

@@ -25,12 +25,11 @@ namespace AZ
             : public AzFramework::Components::ComponentAdapter<MeshComponentController, MeshComponentConfig>
         {
         public:
-
             using BaseClass = AzFramework::Components::ComponentAdapter<MeshComponentController, MeshComponentConfig>;
             AZ_COMPONENT(AZ::Render::MeshComponent, MeshComponentTypeId, BaseClass);
 
             MeshComponent() = default;
-            MeshComponent(const MeshComponentConfig& config);
+            explicit MeshComponent(const MeshComponentConfig& config);
 
             static void Reflect(AZ::ReflectContext* context);
         };

+ 56 - 17
Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp

@@ -177,28 +177,33 @@ namespace AZ
             FixUpModelAsset(m_configuration.m_modelAsset);
         }
 
-        void MeshComponentController::Activate(AZ::EntityId entityId)
+        void MeshComponentController::Activate(const AZ::EntityComponentIdPair& entityComponentIdPair)
         {
             FixUpModelAsset(m_configuration.m_modelAsset);
 
-            m_entityId = entityId;
+            const AZ::EntityId entityId = entityComponentIdPair.GetEntityId();
+            m_entityComponentIdPair = entityComponentIdPair;
 
-            m_transformInterface = TransformBus::FindFirstHandler(m_entityId);
+            m_transformInterface = TransformBus::FindFirstHandler(entityId);
             AZ_Warning("MeshComponentController", m_transformInterface, "Unable to attach to a TransformBus handler. This mesh will always be rendered at the origin.");
 
-            m_meshFeatureProcessor = RPI::Scene::GetFeatureProcessorForEntity<MeshFeatureProcessorInterface>(m_entityId);
+            m_meshFeatureProcessor = RPI::Scene::GetFeatureProcessorForEntity<MeshFeatureProcessorInterface>(entityId);
             AZ_Error("MeshComponentController", m_meshFeatureProcessor, "Unable to find a MeshFeatureProcessorInterface on the entityId.");
 
             m_cachedNonUniformScale = AZ::Vector3::CreateOne();
-            AZ::NonUniformScaleRequestBus::EventResult(m_cachedNonUniformScale, m_entityId, &AZ::NonUniformScaleRequests::GetScale);
-            AZ::NonUniformScaleRequestBus::Event(m_entityId, &AZ::NonUniformScaleRequests::RegisterScaleChangedEvent,
+            AZ::NonUniformScaleRequestBus::EventResult(m_cachedNonUniformScale, entityId, &AZ::NonUniformScaleRequests::GetScale);
+            AZ::NonUniformScaleRequestBus::Event(entityId, &AZ::NonUniformScaleRequests::RegisterScaleChangedEvent,
                 m_nonUniformScaleChangedHandler);
 
-            MeshComponentRequestBus::Handler::BusConnect(m_entityId);
-            TransformNotificationBus::Handler::BusConnect(m_entityId);
-            MaterialReceiverRequestBus::Handler::BusConnect(m_entityId);
-            MaterialComponentNotificationBus::Handler::BusConnect(m_entityId);
-            AzFramework::BoundsRequestBus::Handler::BusConnect(m_entityId);
+            MeshComponentRequestBus::Handler::BusConnect(entityId);
+            TransformNotificationBus::Handler::BusConnect(entityId);
+            MaterialReceiverRequestBus::Handler::BusConnect(entityId);
+            MaterialComponentNotificationBus::Handler::BusConnect(entityId);
+            AzFramework::BoundsRequestBus::Handler::BusConnect(entityId);
+            AzFramework::EntityContextId contextId;
+            AzFramework::EntityIdContextQueryBus::EventResult(
+                contextId, entityId, &AzFramework::EntityIdContextQueries::GetOwningContextId);
+            AzFramework::RenderGeometry::IntersectionRequestBus::Handler::BusConnect({entityId, contextId});
 
             //Buses must be connected before RegisterModel in case requests are made as a result of HandleModelChange
             RegisterModel();
@@ -209,6 +214,7 @@ namespace AZ
             // Buses must be disconnected after unregistering the model, otherwise they can't deliver the events during the process.
             UnregisterModel();
 
+            AzFramework::RenderGeometry::IntersectionRequestBus::Handler::BusDisconnect();
             AzFramework::BoundsRequestBus::Handler::BusDisconnect();
             MeshComponentRequestBus::Handler::BusDisconnect();
             TransformNotificationBus::Handler::BusDisconnect();
@@ -219,7 +225,7 @@ namespace AZ
 
             m_meshFeatureProcessor = nullptr;
             m_transformInterface = nullptr;
-            m_entityId = AZ::EntityId(AZ::EntityId::InvalidEntityId);
+            m_entityComponentIdPair = AZ::EntityComponentIdPair(AZ::EntityId(), AZ::InvalidComponentId);
             m_configuration.m_modelAsset.Release();
         }
 
@@ -293,10 +299,11 @@ namespace AZ
             Data::Asset<RPI::ModelAsset> modelAsset = m_meshFeatureProcessor->GetModelAsset(m_meshHandle);
             if (model && modelAsset)
             {
+                const AZ::EntityId entityId = m_entityComponentIdPair.GetEntityId();
                 m_configuration.m_modelAsset = modelAsset;
-                MeshComponentNotificationBus::Event(m_entityId, &MeshComponentNotificationBus::Events::OnModelReady, m_configuration.m_modelAsset, model);
-                MaterialReceiverNotificationBus::Event(m_entityId, &MaterialReceiverNotificationBus::Events::OnMaterialAssignmentsChanged);
-                AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->RefreshEntityLocalBoundsUnion(m_entityId);
+                MeshComponentNotificationBus::Event(entityId, &MeshComponentNotificationBus::Events::OnModelReady, m_configuration.m_modelAsset, model);
+                MaterialReceiverNotificationBus::Event(entityId, &MaterialReceiverNotificationBus::Events::OnMaterialAssignmentsChanged);
+                AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->RefreshEntityLocalBoundsUnion(entityId);
             }
         }
 
@@ -304,8 +311,10 @@ namespace AZ
         {
             if (m_meshFeatureProcessor && m_configuration.m_modelAsset.GetId().IsValid())
             {
+                const AZ::EntityId entityId = m_entityComponentIdPair.GetEntityId();
+
                 MaterialAssignmentMap materials;
-                MaterialComponentRequestBus::EventResult(materials, m_entityId, &MaterialComponentRequests::GetMaterialOverrides);
+                MaterialComponentRequestBus::EventResult(materials, entityId, &MaterialComponentRequests::GetMaterialOverrides);
 
                 m_meshFeatureProcessor->ReleaseMesh(m_meshHandle);
                 m_meshHandle = m_meshFeatureProcessor->AcquireMesh(m_configuration.m_modelAsset, materials,
@@ -330,7 +339,8 @@ namespace AZ
         {
             if (m_meshFeatureProcessor && m_meshHandle.IsValid())
             {
-                MeshComponentNotificationBus::Event(m_entityId, &MeshComponentNotificationBus::Events::OnModelPreDestroy);
+                MeshComponentNotificationBus::Event(
+                    m_entityComponentIdPair.GetEntityId(), &MeshComponentNotificationBus::Events::OnModelPreDestroy);
                 m_meshFeatureProcessor->ReleaseMesh(m_meshHandle);
             }
         }
@@ -462,5 +472,34 @@ namespace AZ
                 return Aabb::CreateNull();
             }
         }
+
+        AzFramework::RenderGeometry::RayResult MeshComponentController::RenderGeometryIntersect(
+            const AzFramework::RenderGeometry::RayRequest& ray)
+        {
+            AzFramework::RenderGeometry::RayResult result;
+            if (const Data::Instance<RPI::Model> model = GetModel())
+            {
+                float t;
+                AZ::Vector3 normal;
+                if (model->RayIntersection(
+                        m_transformInterface->GetWorldTM(), m_cachedNonUniformScale, ray.m_startWorldPosition,
+                        ray.m_endWorldPosition - ray.m_startWorldPosition, t, normal))
+                {
+                    // note: this is a temporary workaround to handle cases where model->RayIntersection
+                    // returns negative distances, follow-up ATOM-15673
+                    const auto absT = AZStd::abs(t);
+
+                    // fill in ray result structure after successful intersection
+                    const auto intersectionLine = (ray.m_endWorldPosition - ray.m_startWorldPosition);
+                    result.m_uv = AZ::Vector2::CreateZero();
+                    result.m_worldPosition = ray.m_startWorldPosition + intersectionLine * absT;
+                    result.m_worldNormal = normal;
+                    result.m_distance = intersectionLine.GetLength() * absT;
+                    result.m_entityAndComponent = m_entityComponentIdPair;
+                }
+            }
+
+            return result;
+        }
     } // namespace Render
 } // namespace AZ

+ 10 - 6
Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h

@@ -13,11 +13,13 @@
 #pragma once
 
 #include <AzCore/Component/Component.h>
+#include <AzCore/Component/ComponentBus.h>
 #include <AzCore/Component/TransformBus.h>
 #include <AzCore/Component/NonUniformScaleBus.h>
 
 #include <AtomCore/Instance/InstanceDatabase.h>
 
+#include <AzFramework/Render/GeometryIntersectionBus.h>
 #include <AzFramework/Visibility/BoundsBus.h>
 
 #include <Atom/RPI.Public/Model/Model.h>
@@ -32,9 +34,7 @@ namespace AZ
 {
     namespace Render
     {
-        /**
-         * A configuration structure for the MeshComponentController
-         */
+        //! A configuration structure for the MeshComponentController
         class MeshComponentConfig final
             : public AZ::ComponentConfig
         {
@@ -57,6 +57,7 @@ namespace AZ
         class MeshComponentController final
             : private MeshComponentRequestBus::Handler
             , public AzFramework::BoundsRequestBus::Handler
+            , public AzFramework::RenderGeometry::IntersectionRequestBus::Handler
             , private TransformNotificationBus::Handler
             , private MaterialReceiverRequestBus::Handler
             , private MaterialComponentNotificationBus::Handler
@@ -77,7 +78,7 @@ namespace AZ
             MeshComponentController() = default;
             MeshComponentController(const MeshComponentConfig& config);
 
-            void Activate(AZ::EntityId entityId);
+            void Activate(const AZ::EntityComponentIdPair& entityComponentIdPair);
             void Deactivate();
             void SetConfiguration(const MeshComponentConfig& config);
             const MeshComponentConfig& GetConfiguration() const;
@@ -103,10 +104,13 @@ namespace AZ
             void SetVisibility(bool visible) override;
             bool GetVisibility() const override;
 
-            // BoundsRequestBus and MeshComponentRequestBus ...
+            // BoundsRequestBus and MeshComponentRequestBus overrides ...
             AZ::Aabb GetWorldBounds() override;
             AZ::Aabb GetLocalBounds() override;
 
+            // IntersectionRequestBus overrides ...
+            AzFramework::RenderGeometry::RayResult RenderGeometryIntersect(const AzFramework::RenderGeometry::RayRequest& ray) override;
+
             // TransformNotificationBus::Handler overrides ...
             void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
 
@@ -134,7 +138,7 @@ namespace AZ
             Render::MeshFeatureProcessorInterface* m_meshFeatureProcessor = nullptr;
             Render::MeshFeatureProcessorInterface::MeshHandle m_meshHandle;
             TransformInterface* m_transformInterface = nullptr;
-            AZ::EntityId m_entityId;
+            AZ::EntityComponentIdPair m_entityComponentIdPair;
             bool m_isVisible = true;
             MeshComponentConfig m_configuration;
             AZ::Vector3 m_cachedNonUniformScale = AZ::Vector3::CreateOne();