From b87e6896e6784e35220365f9441313c3c4304a8b Mon Sep 17 00:00:00 2001 From: Alex Peterson <26804013+AMZN-alexpete@users.noreply.github.com> Date: Fri, 14 Jan 2022 16:43:01 -0800 Subject: [PATCH] ScreenToWorld and WorldToScreen Camera functionality (#6903) Signed-off-by: Alex Peterson <26804013+AMZN-alexpete@users.noreply.github.com> --- .../AzFramework/Components/CameraBus.h | 26 ++++ .../Component/DebugCamera/CameraComponent.h | 4 + .../Code/Source/CameraComponent.cpp | 26 +++- Gems/Camera/Code/Source/CameraComponent.cpp | 4 + .../Code/Source/CameraComponentController.cpp | 63 ++++++++ .../Code/Source/CameraComponentController.h | 7 + .../EBus/Senders/CameraRequestBus.names | 134 +++++++++++++++++- ...hysXWorld_RayCastFromScreenWithGroup.names | 88 ++++++++++++ .../Code/Source/WorldNodes.h | 39 +++++ 9 files changed, 389 insertions(+), 2 deletions(-) create mode 100644 Gems/ScriptCanvas/Assets/TranslationAssets/Nodes/PhysXWorld_RayCastFromScreenWithGroup.names diff --git a/Code/Framework/AzFramework/AzFramework/Components/CameraBus.h b/Code/Framework/AzFramework/AzFramework/Components/CameraBus.h index e20578e939..964bf664a0 100644 --- a/Code/Framework/AzFramework/AzFramework/Components/CameraBus.h +++ b/Code/Framework/AzFramework/AzFramework/Components/CameraBus.h @@ -129,6 +129,32 @@ namespace Camera GetFrustumHeight() }; } + + //! Unprojects a position in screen space pixel coordinates to world space. + //! With a depth of zero, the position returned will be on the near clip plane of the camera + //! in world space. + //! @param screenPosition The absolute screen position + //! @param depth The depth offset into the world relative to the near clip plane of the camera + //! @return the position in world space + virtual AZ::Vector3 ScreenToWorld(const AZ::Vector2& screenPosition, float depth) = 0; + + //! Unprojects a position in screen space normalized device coordinates to world space. + //! With a depth of zero, the position returned will be on the near clip plane of the camera + //! in world space. + //! @param screenNdcPosition The normalized device coordinates in the range [0,1] + //! @param depth The depth offset into the world relative to the near clip plane of the camera + //! @return the position in world space + virtual AZ::Vector3 ScreenNdcToWorld(const AZ::Vector2& screenNdcPosition, float depth) = 0; + + //! Projects a position in world space to screen space for the given camera. + //! @param worldPosition The world position + //! @return The absolute screen position + virtual AZ::Vector2 WorldToScreen(const AZ::Vector3& worldPosition) = 0; + + //! Projects a position in world space to screen space normalized device coordinates. + //! @param worldPosition The world position + //! @return The normalized device coordinates in the range [0,1] + virtual AZ::Vector2 WorldToScreenNdc(const AZ::Vector3& worldPosition) = 0; }; using CameraRequestBus = AZ::EBus; diff --git a/Gems/Atom/Component/DebugCamera/Code/Include/Atom/Component/DebugCamera/CameraComponent.h b/Gems/Atom/Component/DebugCamera/Code/Include/Atom/Component/DebugCamera/CameraComponent.h index fb9c543bca..47254bcdc5 100644 --- a/Gems/Atom/Component/DebugCamera/Code/Include/Atom/Component/DebugCamera/CameraComponent.h +++ b/Gems/Atom/Component/DebugCamera/Code/Include/Atom/Component/DebugCamera/CameraComponent.h @@ -104,6 +104,10 @@ namespace AZ void SetOrthographicHalfWidth(float halfWidth) override; void MakeActiveView() override; bool IsActiveView() override; + AZ::Vector3 ScreenToWorld(const AZ::Vector2& screenPosition, float depth) override; + AZ::Vector3 ScreenNdcToWorld(const AZ::Vector2& screenPosition, float depth) override; + AZ::Vector2 WorldToScreen(const AZ::Vector3& worldPosition) override; + AZ::Vector2 WorldToScreenNdc(const AZ::Vector3& worldPosition) override; // RPI::WindowContextNotificationBus overrides... void OnViewportResized(uint32_t width, uint32_t height) override; diff --git a/Gems/Atom/Component/DebugCamera/Code/Source/CameraComponent.cpp b/Gems/Atom/Component/DebugCamera/Code/Source/CameraComponent.cpp index f3123e2b38..c4420dc417 100644 --- a/Gems/Atom/Component/DebugCamera/Code/Source/CameraComponent.cpp +++ b/Gems/Atom/Component/DebugCamera/Code/Source/CameraComponent.cpp @@ -245,7 +245,7 @@ namespace AZ AZ_Assert(false, "DebugCamera does not support orthographic projection"); } - void CameraComponent::MakeActiveView() + void CameraComponent::MakeActiveView() { // do nothing } @@ -255,6 +255,30 @@ namespace AZ return false; } + AZ::Vector3 CameraComponent::ScreenToWorld([[maybe_unused]] const AZ::Vector2& screenPosition, [[maybe_unused]] float depth) + { + // not implemented + return AZ::Vector3::CreateZero(); + } + + AZ::Vector3 CameraComponent::ScreenNdcToWorld([[maybe_unused]] const AZ::Vector2& screenPosition, [[maybe_unused]] float depth) + { + // not implemented + return AZ::Vector3::CreateZero(); + } + + AZ::Vector2 CameraComponent::WorldToScreen([[maybe_unused]] const AZ::Vector3& worldPosition) + { + // not implemented + return AZ::Vector2::CreateZero(); + } + + AZ::Vector2 CameraComponent::WorldToScreenNdc([[maybe_unused]] const AZ::Vector3& worldPosition) + { + // not implemented + return AZ::Vector2::CreateZero(); + } + void CameraComponent::OnViewportResized(uint32_t width, uint32_t height) { AZ_UNUSED(width); diff --git a/Gems/Camera/Code/Source/CameraComponent.cpp b/Gems/Camera/Code/Source/CameraComponent.cpp index 960f13a082..290691bbbe 100644 --- a/Gems/Camera/Code/Source/CameraComponent.cpp +++ b/Gems/Camera/Code/Source/CameraComponent.cpp @@ -106,6 +106,10 @@ namespace Camera ->Event("SetOrthographic", &CameraRequestBus::Events::SetOrthographic) ->Event("GetOrthographicHalfWidth", &CameraRequestBus::Events::GetOrthographicHalfWidth) ->Event("SetOrthographicHalfWidth", &CameraRequestBus::Events::SetOrthographicHalfWidth) + ->Event("ScreenToWorld", &CameraRequestBus::Events::ScreenToWorld) + ->Event("ScreenNdcToWorld", &CameraRequestBus::Events::ScreenNdcToWorld) + ->Event("WorldToScreen", &CameraRequestBus::Events::WorldToScreen) + ->Event("WorldToScreenNdc", &CameraRequestBus::Events::WorldToScreenNdc) ->VirtualProperty("FieldOfView","GetFovDegrees","SetFovDegrees") ->VirtualProperty("NearClipDistance", "GetNearClipDistance", "SetNearClipDistance") ->VirtualProperty("FarClipDistance", "GetFarClipDistance", "SetFarClipDistance") diff --git a/Gems/Camera/Code/Source/CameraComponentController.cpp b/Gems/Camera/Code/Source/CameraComponentController.cpp index 5b2e26b897..dfb2810fe2 100644 --- a/Gems/Camera/Code/Source/CameraComponentController.cpp +++ b/Gems/Camera/Code/Source/CameraComponentController.cpp @@ -10,12 +10,15 @@ #include "CameraViewRegistrationBus.h" #include +#include #include #include #include #include +#include + namespace Camera { void CameraComponentConfig::Reflect(AZ::ReflectContext* context) @@ -412,6 +415,66 @@ namespace Camera return m_isActiveView; } + namespace Util + { + AZ::Vector3 GetWorldPosition(const AZ::Vector3& origin, float depth, const AzFramework::CameraState& cameraState) + { + if (depth == 0.f) + { + return origin; + } + else + { + const AZ::Vector3 rayDirection = cameraState.m_orthographic ? cameraState.m_forward : (origin - cameraState.m_position); + return origin + (rayDirection.GetNormalized() * depth); + } + } + } + + AZ::Vector3 CameraComponentController::ScreenToWorld(const AZ::Vector2& screenPosition, float depth) + { + const AzFramework::ScreenPoint point{ static_cast(screenPosition.GetX()), static_cast(screenPosition.GetY()) }; + const AzFramework::CameraState& cameraState = GetCameraState(); + const AZ::Vector3 origin = AzFramework::ScreenToWorld(point, cameraState); + return Util::GetWorldPosition(origin, depth, cameraState); + } + + AZ::Vector3 CameraComponentController::ScreenNdcToWorld(const AZ::Vector2& screenNdcPosition, float depth) + { + const AzFramework::CameraState& cameraState = GetCameraState(); + const AZ::Vector3 origin = AzFramework::ScreenNdcToWorld(screenNdcPosition, AzFramework::InverseCameraView(cameraState), AzFramework::InverseCameraProjection(cameraState)); + return Util::GetWorldPosition(origin, depth, cameraState); + } + + AZ::Vector2 CameraComponentController::WorldToScreenNdc(const AZ::Vector3& worldPosition) + { + const AzFramework::CameraState& cameraState = GetCameraState(); + const AZ::Vector3 screenPosition = AzFramework::WorldToScreenNdc(worldPosition, AzFramework::CameraView(cameraState), AzFramework::CameraProjection(cameraState)); + return AZ::Vector3ToVector2(screenPosition); + } + + AZ::Vector2 CameraComponentController::WorldToScreen(const AZ::Vector3& worldPosition) + { + const AzFramework::ScreenPoint& point = AzFramework::WorldToScreen(worldPosition, GetCameraState()); + return AZ::Vector2(static_cast(point.m_x), static_cast(point.m_y)); + } + + AzFramework::CameraState CameraComponentController::GetCameraState() + { + auto viewportContext = GetViewportContext(); + if (!m_atomCamera || ! viewportContext) + { + return AzFramework::CameraState(); + } + + auto windowSize = viewportContext->GetViewportSize(); + auto viewportSize = AzFramework::Vector2FromScreenSize(AzFramework::ScreenSize(windowSize.m_width, windowSize.m_height)); + + AzFramework::CameraState cameraState = AzFramework::CreateDefaultCamera(m_atomCamera->GetCameraTransform(), viewportSize); + AzFramework::SetCameraClippingVolumeFromPerspectiveFovMatrixRH(cameraState, m_atomCamera->GetViewToClipMatrix()); + return cameraState; + } + void CameraComponentController::OnTransformChanged([[maybe_unused]] const AZ::Transform& local, const AZ::Transform& world) { if (m_updatingTransformFromEntity) diff --git a/Gems/Camera/Code/Source/CameraComponentController.h b/Gems/Camera/Code/Source/CameraComponentController.h index 09af7529dc..265ffaa0e9 100644 --- a/Gems/Camera/Code/Source/CameraComponentController.h +++ b/Gems/Camera/Code/Source/CameraComponentController.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -110,6 +111,11 @@ namespace Camera void MakeActiveView() override; bool IsActiveView() override; + AZ::Vector3 ScreenToWorld(const AZ::Vector2& screenPosition, float depth) override; + AZ::Vector3 ScreenNdcToWorld(const AZ::Vector2& screenNdcPosition, float depth) override; + AZ::Vector2 WorldToScreen(const AZ::Vector3& worldPosition) override; + AZ::Vector2 WorldToScreenNdc(const AZ::Vector3& worldPosition) override; + // AZ::TransformNotificationBus::Handler interface void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override; @@ -127,6 +133,7 @@ namespace Camera void DeactivateAtomView(); void UpdateCamera(); void SetupAtomAuxGeom(AZ::RPI::ViewportContextPtr viewportContext); + AzFramework::CameraState GetCameraState(); CameraComponentConfig m_config; AZ::EntityId m_entityId; diff --git a/Gems/ScriptCanvas/Assets/TranslationAssets/EBus/Senders/CameraRequestBus.names b/Gems/ScriptCanvas/Assets/TranslationAssets/EBus/Senders/CameraRequestBus.names index 6c0112bfc2..ad2127c2a1 100644 --- a/Gems/ScriptCanvas/Assets/TranslationAssets/EBus/Senders/CameraRequestBus.names +++ b/Gems/ScriptCanvas/Assets/TranslationAssets/EBus/Senders/CameraRequestBus.names @@ -31,6 +31,66 @@ } ] }, + { + "base": "WorldToScreen", + "entry": { + "name": "In", + "tooltip": "When signaled, this will invoke World To Screen" + }, + "exit": { + "name": "Out", + "tooltip": "Signaled after World To Screen is invoked" + }, + "details": { + "name": "World To Screen" + }, + "params": [ + { + "typeid": "{8379EB7D-01FA-4538-B64B-A6543B4BE73D}", + "details": { + "name": "World Position" + } + } + ], + "results": [ + { + "typeid": "{3D80F623-C85C-4741-90D0-E4E66164E6BF}", + "details": { + "name": "Screen Position" + } + } + ] + }, + { + "base": "WorldToScreenNdc", + "entry": { + "name": "In", + "tooltip": "When signaled, this will invoke World To Screen Ndc" + }, + "exit": { + "name": "Out", + "tooltip": "Signaled after World To Screen Ndc is invoked" + }, + "details": { + "name": "World To Screen NDC" + }, + "params": [ + { + "typeid": "{8379EB7D-01FA-4538-B64B-A6543B4BE73D}", + "details": { + "name": "World Position" + } + } + ], + "results": [ + { + "typeid": "{3D80F623-C85C-4741-90D0-E4E66164E6BF}", + "details": { + "name": "Screen NDC Position" + } + } + ] + }, { "base": "GetFov", "entry": { @@ -53,6 +113,78 @@ } ] }, + { + "base": "ScreenToWorld", + "entry": { + "name": "In", + "tooltip": "When signaled, this will invoke Screen To World" + }, + "exit": { + "name": "Out", + "tooltip": "Signaled after Screen To World is invoked" + }, + "details": { + "name": "Screen To World" + }, + "params": [ + { + "typeid": "{3D80F623-C85C-4741-90D0-E4E66164E6BF}", + "details": { + "name": "Screen Position" + } + }, + { + "typeid": "{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}", + "details": { + "name": "Depth" + } + } + ], + "results": [ + { + "typeid": "{8379EB7D-01FA-4538-B64B-A6543B4BE73D}", + "details": { + "name": "World Position" + } + } + ] + }, + { + "base": "ScreenNdcToWorld", + "entry": { + "name": "In", + "tooltip": "When signaled, this will invoke Screen Ndc To World" + }, + "exit": { + "name": "Out", + "tooltip": "Signaled after Screen Ndc To World is invoked" + }, + "details": { + "name": "Screen NDC To World" + }, + "params": [ + { + "typeid": "{3D80F623-C85C-4741-90D0-E4E66164E6BF}", + "details": { + "name": "Screen NDC Position" + } + }, + { + "typeid": "{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}", + "details": { + "name": "Depth" + } + } + ], + "results": [ + { + "typeid": "{8379EB7D-01FA-4538-B64B-A6543B4BE73D}", + "details": { + "name": "World Position" + } + } + ] + }, { "base": "SetFovRadians", "entry": { @@ -356,4 +488,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/Gems/ScriptCanvas/Assets/TranslationAssets/Nodes/PhysXWorld_RayCastFromScreenWithGroup.names b/Gems/ScriptCanvas/Assets/TranslationAssets/Nodes/PhysXWorld_RayCastFromScreenWithGroup.names new file mode 100644 index 0000000000..447866ae44 --- /dev/null +++ b/Gems/ScriptCanvas/Assets/TranslationAssets/Nodes/PhysXWorld_RayCastFromScreenWithGroup.names @@ -0,0 +1,88 @@ +{ + "entries": [ + { + "base": "{B164D87F-6620-5A1D-A2BC-CC09BA18C9B1}", + "context": "ScriptCanvas::Node", + "variant": "", + "details": { + "name": "Ray Cast From Screen With Group", + "category": "PhysX/World", + "tooltip": "Returns the first entity hit by a ray cast from the provided absolute 2D screen position." + }, + "slots": [ + { + "base": "Input_In_0", + "details": { + "name": "In" + } + }, + { + "base": "Output_Out_0", + "details": { + "name": "Out" + } + }, + { + "base": "DataInput_Screen Position_0", + "details": { + "name": "Screen Position" + } + }, + { + "base": "DataInput_Distance_1", + "details": { + "name": "Distance" + } + }, + { + "base": "DataInput_Collision group_2", + "details": { + "name": "Collision group" + } + }, + { + "base": "DataInput_Ignore_3", + "details": { + "name": "Ignore" + } + }, + { + "base": "DataOutput_Object hit_0", + "details": { + "name": "Object hit" + } + }, + { + "base": "DataOutput_Position_1", + "details": { + "name": "Position" + } + }, + { + "base": "DataOutput_Normal_2", + "details": { + "name": "Normal" + } + }, + { + "base": "DataOutput_Distance_3", + "details": { + "name": "Distance" + } + }, + { + "base": "DataOutput_EntityId_4", + "details": { + "name": "EntityId" + } + }, + { + "base": "DataOutput_Surface_5", + "details": { + "name": "Surface" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/Gems/ScriptCanvasPhysics/Code/Source/WorldNodes.h b/Gems/ScriptCanvasPhysics/Code/Source/WorldNodes.h index 96a15a9d29..1b03bd1851 100644 --- a/Gems/ScriptCanvasPhysics/Code/Source/WorldNodes.h +++ b/Gems/ScriptCanvasPhysics/Code/Source/WorldNodes.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -120,6 +121,43 @@ namespace ScriptCanvasPhysics "EntityId", "Surface"); + AZ_INLINE Result RayCastFromScreenWithGroup( + const AZ::Vector2& screenPosition, + float distance, + const AZStd::string& collisionGroup, + AZ::EntityId ignore) + { + AZ::EntityId camera; + Camera::CameraSystemRequestBus::BroadcastResult(camera, &Camera::CameraSystemRequestBus::Events::GetActiveCamera); + if (camera.IsValid()) + { + AZ::Vector3 origin = AZ::Vector3::CreateZero(); + Camera::CameraRequestBus::EventResult(origin, camera, &Camera::CameraRequestBus::Events::ScreenToWorld, screenPosition, 0.f); + AZ::Vector3 offset = AZ::Vector3::CreateZero(); + Camera::CameraRequestBus::EventResult(offset, camera, &Camera::CameraRequestBus::Events::ScreenToWorld, screenPosition, 1.f); + const AZ::Vector3 direction = (offset - origin).GetNormalized(); + return RayCastWorldSpaceWithGroup(origin, direction, distance, collisionGroup, ignore); + } + + // fallback in the rare case there is no active camera + return AZStd::make_tuple(false, AZ::Vector3::CreateZero(), AZ::Vector3::CreateZero(), 0.0f, AZ::EntityId(), AZ::Crc32()); + } + + SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(RayCastFromScreenWithGroup, + k_categoryName, + "{8F98A766-A93F-4DA7-B281-482C3DB20649}", + "Returns the first entity hit by a ray cast from the provided absolute 2D screen position.", + "Screen Position", + "Distance", + "Collision group", + "Ignore", + "Object hit", + "Position", + "Normal", + "Distance", + "EntityId", + "Surface"); + AZ_INLINE Result RayCastLocalSpaceWithGroup(const AZ::EntityId& fromEntityId, const AZ::Vector3& direction, float distance, @@ -389,6 +427,7 @@ namespace ScriptCanvasPhysics