diff --git a/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp b/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp index 6fcf3faffd..28bc6fe3b3 100644 --- a/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp +++ b/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp @@ -19,6 +19,66 @@ namespace UnitTest using AzToolsFramework::ViewportInteraction::MouseInteractionEvent; + class ViewportMouseCursorRequestImpl : public AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Handler + { + public: + void Connect(const AzFramework::ViewportId viewportId, AzToolsFramework::QtEventToAzInputMapper* inputChannelMapper) + { + AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Handler::BusConnect(viewportId); + m_inputChannelMapper = inputChannelMapper; + } + + void Disconnect() + { + AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Handler::BusDisconnect(); + } + + // ViewportMouseCursorRequestBus overrides ... + void BeginCursorCapture() override; + void EndCursorCapture() override; + bool IsMouseOver() const override; + + private: + AzToolsFramework::QtEventToAzInputMapper* m_inputChannelMapper = nullptr; + }; + + void ViewportMouseCursorRequestImpl::BeginCursorCapture() + { + m_inputChannelMapper->SetCursorCaptureEnabled(true); + } + + void ViewportMouseCursorRequestImpl::EndCursorCapture() + { + m_inputChannelMapper->SetCursorCaptureEnabled(false); + } + + bool ViewportMouseCursorRequestImpl::IsMouseOver() const + { + return true; + } + + class TestModularCameraViewportContextImpl : public AtomToolsFramework::ModularCameraViewportContext + { + public: + AZ::Transform GetCameraTransform() const override + { + return m_cameraTransform; + } + + void SetCameraTransform(const AZ::Transform& transform) override + { + m_cameraTransform = transform; + } + + void ConnectViewMatrixChangedHandler(AZ::RPI::ViewportContext::MatrixChangedEvent::Handler&) override + { + // noop + } + + private: + AZ::Transform m_cameraTransform = AZ::Transform::CreateIdentity(); + }; + class ModularViewportCameraControllerFixture : public AllocatorsTestFixture { public: @@ -48,123 +108,159 @@ namespace UnitTest AllocatorsTestFixture::TearDown(); } + void PrepareCollaborators() + { + AzFramework::NativeWindowHandle nativeWindowHandle = nullptr; + + // listen for events signaled from QtEventToAzInputMapper and forward to the controller list + QObject::connect( + m_inputChannelMapper.get(), &AzToolsFramework::QtEventToAzInputMapper::InputChannelUpdated, m_rootWidget.get(), + [this, nativeWindowHandle](const AzFramework::InputChannel* inputChannel, [[maybe_unused]] QEvent* event) + { + m_controllerList->HandleInputChannelEvent( + AzFramework::ViewportControllerInputEvent{ TestViewportId, nativeWindowHandle, *inputChannel }); + }); + + m_mockWindowRequests.Connect(nativeWindowHandle); + + using ::testing::Return; + // note: WindowRequests is used internally by ModularViewportCameraController, this ensures it returns the viewport size we want + ON_CALL(m_mockWindowRequests, GetClientAreaSize()) + .WillByDefault(Return(AzFramework::WindowSize(WidgetSize.width(), WidgetSize.height()))); + + // respond to begin/end cursor capture events + m_viewportMouseCursorRequests.Connect(TestViewportId, m_inputChannelMapper.get()); + + // create editor modular camera + auto controller = CreateModularViewportCameraController(TestViewportId); + + // set some overrides for the test + controller->SetCameraViewportContextBuilderCallback( + [this](AZStd::unique_ptr& cameraViewportContext) + { + cameraViewportContext = AZStd::make_unique(); + m_cameraViewportContextView = cameraViewportContext.get(); + }); + + // disable smoothing in the test + controller->SetCameraPropsBuilderCallback( + [](AzFramework::CameraProps& cameraProps) + { + cameraProps.m_rotateSmoothingEnabledFn = [] + { + return false; + }; + + cameraProps.m_translateSmoothingEnabledFn = [] + { + return false; + }; + }); + + m_controllerList->Add(controller); + } + + void HaltCollaborators() + { + m_mockWindowRequests.Disconnect(); + m_viewportMouseCursorRequests.Disconnect(); + m_cameraViewportContextView = nullptr; + } + + void RepeatDiagonalMouseMovements(const AZStd::function& deltaTimeFn) + { + // move to the center of the screen + auto start = QPoint(WidgetSize.width() / 2, WidgetSize.height() / 2); + MouseMove(m_rootWidget.get(), start, QPoint(0, 0)); + m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTimeFn()), AZ::ScriptTimePoint() }); + + // move mouse diagonally to top right, then to bottom left and back repeatedly + auto current = start; + auto halfDelta = QPoint(200, -200); + const int iterationsPerDiagonal = 50; + for (int diagonals = 0; diagonals < 80; ++diagonals) + { + for (int i = 0; i < iterationsPerDiagonal; ++i) + { + MousePressAndMove(m_rootWidget.get(), current, halfDelta / iterationsPerDiagonal, Qt::MouseButton::RightButton); + m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTimeFn()), AZ::ScriptTimePoint() }); + current += halfDelta / iterationsPerDiagonal; + } + + if (diagonals % 2 == 0) + { + halfDelta.setX(halfDelta.x() * -1); + halfDelta.setY(halfDelta.y() * -1); + } + } + + QTest::mouseRelease(m_rootWidget.get(), Qt::MouseButton::RightButton, Qt::KeyboardModifier::NoModifier, current); + m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTimeFn()), AZ::ScriptTimePoint() }); + } + AZStd::unique_ptr m_rootWidget; AzFramework::ViewportControllerListPtr m_controllerList; AZStd::unique_ptr m_inputChannelMapper; + ::testing::NiceMock m_mockWindowRequests; + ViewportMouseCursorRequestImpl m_viewportMouseCursorRequests; + AtomToolsFramework::ModularCameraViewportContext* m_cameraViewportContextView = nullptr; }; const AzFramework::ViewportId ModularViewportCameraControllerFixture::TestViewportId = AzFramework::ViewportId(0); - class TestModularCameraViewportContextImpl : public AtomToolsFramework::ModularCameraViewportContext + TEST_F(ModularViewportCameraControllerFixture, MouseMovementDoesNotAccumulateExcessiveDriftInModularViewportCameraWithVaryingDeltaTime) { - public: - AZ::Transform GetCameraTransform() const override - { - return m_cameraTransform; - } - - void SetCameraTransform(const AZ::Transform& transform) override - { - m_cameraTransform = transform; - } - - void ConnectViewMatrixChangedHandler(AZ::RPI::ViewportContext::MatrixChangedEvent::Handler&) override - { - // noop - } - - private: - AZ::Transform m_cameraTransform = AZ::Transform::CreateIdentity(); - }; - - TEST_F(ModularViewportCameraControllerFixture, Mouse_movement_does_not_accumulate_excessive_drift_in_modular_viewport_camera) - { - AzFramework::NativeWindowHandle nativeWindowHandle = nullptr; - - const float deltaTime = 1.0f / 60.0f; // mimic 60fps - // Given - // listen for events signaled from QtEventToAzInputMapper and forward to the controller list - QObject::connect( - m_inputChannelMapper.get(), &AzToolsFramework::QtEventToAzInputMapper::InputChannelUpdated, m_rootWidget.get(), - [this, nativeWindowHandle](const AzFramework::InputChannel* inputChannel, [[maybe_unused]] QEvent* event) - { - m_controllerList->HandleInputChannelEvent( - AzFramework::ViewportControllerInputEvent{ TestViewportId, nativeWindowHandle, *inputChannel }); - }); - - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock mockWindowRequests; - mockWindowRequests.Connect(nativeWindowHandle); - - // note: WindowRequests is used internally by ModularViewportCameraController, this ensures it returns the viewport size we want - ON_CALL(mockWindowRequests, GetClientAreaSize()) - .WillByDefault(Return(AzFramework::WindowSize(WidgetSize.width(), WidgetSize.height()))); - - // create editor modular camera - auto controller = CreateModularViewportCameraController(TestViewportId); - - // set some overrides for the test - AtomToolsFramework::ModularCameraViewportContext* cameraViewportContextView = nullptr; - controller->SetCameraViewportContextBuilderCallback( - [&cameraViewportContextView](AZStd::unique_ptr& cameraViewportContext) - { - cameraViewportContext = AZStd::make_unique(); - cameraViewportContextView = cameraViewportContext.get(); - }); - - controller->SetCameraPropsBuilderCallback( - [](AzFramework::CameraProps& cameraProps) - { - cameraProps.m_rotateSmoothingEnabledFn = [] - { - return false; - }; - - cameraProps.m_translateSmoothingEnabledFn = [] - { - return false; - }; - }); - - m_controllerList->Add(controller); - - // move to the center of the screen - auto start = QPoint(WidgetSize.width() / 2, WidgetSize.height() / 2); - MouseMove(m_rootWidget.get(), start, QPoint(0, 0)); - m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() }); + PrepareCollaborators(); // When - // move mouse diagonally to top right, then to bottom left and back repeatedly - auto current = start; - auto halfDelta = QPoint(200, -200); - const int iterationsPerDiagonal = 50; - for (int diagonals = 0; diagonals < 80; ++diagonals) - { - for (int i = 0; i < iterationsPerDiagonal; ++i) + RepeatDiagonalMouseMovements( + [t = 0.0f]() mutable { - MousePressAndMove(m_rootWidget.get(), current, halfDelta / iterationsPerDiagonal, Qt::MouseButton::RightButton); - m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() }); - current += halfDelta / iterationsPerDiagonal; - } - - if (diagonals % 2 == 0) - { - halfDelta.setX(halfDelta.x() * -1); - halfDelta.setY(halfDelta.y() * -1); - } - } - - QTest::mouseRelease(m_rootWidget.get(), Qt::MouseButton::RightButton, Qt::KeyboardModifier::NoModifier, current); - m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() }); + // vary between 30 and 50 fps (40 +/- 10) + const float fps = 40.0f + (10.0f * AZStd::sin(t)); + t += AZ::DegToRad(5.0f); + return 1.0f / fps; + }); // Then // ensure the camera rotation is the identity (no significant drift has occurred as we moved the mouse) - const AZ::Transform cameraRotation = cameraViewportContextView->GetCameraTransform(); + const AZ::Transform cameraRotation = m_cameraViewportContextView->GetCameraTransform(); EXPECT_THAT(cameraRotation.GetRotation(), IsClose(AZ::Quaternion::CreateIdentity())); - mockWindowRequests.Disconnect(); + // Clean-up + HaltCollaborators(); } + + class ModularViewportCameraControllerDeltaTimeParamFixture + : public ModularViewportCameraControllerFixture + , public ::testing::WithParamInterface // delta time + { + }; + + TEST_P( + ModularViewportCameraControllerDeltaTimeParamFixture, + MouseMovementDoesNotAccumulateExcessiveDriftInModularViewportCameraWithFixedDeltaTime) + { + // Given + PrepareCollaborators(); + + // When + RepeatDiagonalMouseMovements( + [this] + { + return GetParam(); + }); + + // Then + // ensure the camera rotation is the identity (no significant drift has occurred as we moved the mouse) + const AZ::Transform cameraRotation = m_cameraViewportContextView->GetCameraTransform(); + EXPECT_THAT(cameraRotation.GetRotation(), IsClose(AZ::Quaternion::CreateIdentity())); + + // Clean-up + HaltCollaborators(); + } + + INSTANTIATE_TEST_CASE_P( + All, ModularViewportCameraControllerDeltaTimeParamFixture, testing::Values(1.0f / 60.0f, 1.0f / 50.0f, 1.0f / 30.0f)); } // namespace UnitTest diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp index 74adcd9543..72984f8111 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp @@ -802,7 +802,6 @@ namespace AzFramework { return VerticalMotionEvent{ aznumeric_cast(inputChannel.GetValue()) }; } - else if (inputChannelId == InputDeviceMouse::Movement::Z) { return ScrollEvent{ inputChannel.GetValue() }; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h index 543d5fb3a5..f6ba87a0ec 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h @@ -275,9 +275,7 @@ namespace AzToolsFramework virtual void BeginCursorCapture() = 0; //! Restores the cursor and ends locking it in place, allowing it to be moved freely. virtual void EndCursorCapture() = 0; - //! Gets the most recent recorded cursor position in the viewport in screen space coordinates. - virtual AzFramework::ScreenPoint ViewportCursorScreenPosition() = 0; - //! Is mouse over viewport. + //! Is the mouse over the viewport. virtual bool IsMouseOver() const = 0; protected: diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h index a2d9517ec4..389c5902f9 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h @@ -62,9 +62,9 @@ namespace AZ void BuildDrawPacketList(size_t modelLodIndex); void SetRayTracingData(); void SetSortKey(RHI::DrawItemSortKey sortKey); - RHI::DrawItemSortKey GetSortKey(); - void SetLodOverride(RPI::Cullable::LodOverride lodOverride); - RPI::Cullable::LodOverride GetLodOverride(); + RHI::DrawItemSortKey GetSortKey() const; + void SetMeshLodConfiguration(RPI::Cullable::LodConfiguration meshLodConfig); + RPI::Cullable::LodConfiguration GetMeshLodConfiguration() const; void UpdateDrawPackets(bool forceUpdate = false); void BuildCullable(); void UpdateCullBounds(const TransformServiceFeatureProcessor* transformService); @@ -153,10 +153,10 @@ namespace AZ AZ::Aabb GetLocalAabb(const MeshHandle& meshHandle) const override; void SetSortKey(const MeshHandle& meshHandle, RHI::DrawItemSortKey sortKey) override; - RHI::DrawItemSortKey GetSortKey(const MeshHandle& meshHandle) override; + RHI::DrawItemSortKey GetSortKey(const MeshHandle& meshHandle) const override; - void SetLodOverride(const MeshHandle& meshHandle, RPI::Cullable::LodOverride lodOverride) override; - RPI::Cullable::LodOverride GetLodOverride(const MeshHandle& meshHandle) override; + void SetMeshLodConfiguration(const MeshHandle& meshHandle, const RPI::Cullable::LodConfiguration& meshLodConfig) override; + RPI::Cullable::LodConfiguration GetMeshLodConfiguration(const MeshHandle& meshHandle) const override; void SetExcludeFromReflectionCubeMaps(const MeshHandle& meshHandle, bool excludeFromReflectionCubeMaps) override; void SetRayTracingEnabled(const MeshHandle& meshHandle, bool rayTracingEnabled) override; diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h index 4e74302276..cffbe5c3c5 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h @@ -93,11 +93,11 @@ namespace AZ //! Sets the sort key for a given mesh handle. virtual void SetSortKey(const MeshHandle& meshHandle, RHI::DrawItemSortKey sortKey) = 0; //! Gets the sort key for a given mesh handle. - virtual RHI::DrawItemSortKey GetSortKey(const MeshHandle& meshHandle) = 0; - //! Sets an LOD override for a given mesh handle. This LOD will always be rendered instead being automatically determined. - virtual void SetLodOverride(const MeshHandle& meshHandle, RPI::Cullable::LodOverride lodOverride) = 0; - //! Gets the LOD override for a given mesh handle. - virtual RPI::Cullable::LodOverride GetLodOverride(const MeshHandle& meshHandle) = 0; + virtual RHI::DrawItemSortKey GetSortKey(const MeshHandle& meshHandle) const = 0; + //! Sets LOD mesh configurations to be used in the Mesh Feature Processor + virtual void SetMeshLodConfiguration(const MeshHandle& meshHandle, const RPI::Cullable::LodConfiguration& meshLodConfig) = 0; + //! Gets the LOD mesh configurations being used in the Mesh Feature Processor + virtual RPI::Cullable::LodConfiguration GetMeshLodConfiguration(const MeshHandle& meshHandle) const = 0; //! Sets the option to exclude this mesh from baked reflection probe cubemaps virtual void SetExcludeFromReflectionCubeMaps(const MeshHandle& meshHandle, bool excludeFromReflectionCubeMaps) = 0; //! Sets the option to exclude this mesh from raytracing diff --git a/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h index 72e547fc1b..35e399997f 100644 --- a/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h @@ -32,9 +32,9 @@ namespace UnitTest MOCK_METHOD2(SetLocalAabb, void(const MeshHandle&, const AZ::Aabb&)); MOCK_CONST_METHOD1(GetLocalAabb, AZ::Aabb(const MeshHandle&)); MOCK_METHOD2(SetSortKey, void (const MeshHandle&, AZ::RHI::DrawItemSortKey)); - MOCK_METHOD1(GetSortKey, AZ::RHI::DrawItemSortKey(const MeshHandle&)); - MOCK_METHOD2(SetLodOverride, void(const MeshHandle&, AZ::RPI::Cullable::LodOverride)); - MOCK_METHOD1(GetLodOverride, AZ::RPI::Cullable::LodOverride(const MeshHandle&)); + MOCK_CONST_METHOD1(GetSortKey, AZ::RHI::DrawItemSortKey(const MeshHandle&)); + MOCK_METHOD2(SetMeshLodConfiguration, void(const MeshHandle&, const AZ::RPI::Cullable::LodConfiguration&)); + MOCK_CONST_METHOD1(GetMeshLodConfiguration, AZ::RPI::Cullable::LodConfiguration(const MeshHandle&)); MOCK_METHOD2(AcquireMesh, MeshHandle (const AZ::Render::MeshHandleDescriptor&, const AZ::Render::MaterialAssignmentMap&)); MOCK_METHOD2(AcquireMesh, MeshHandle (const AZ::Render::MeshHandleDescriptor&, const AZ::Data::Instance&)); MOCK_METHOD2(SetRayTracingEnabled, void (const MeshHandle&, bool)); diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index 6ed37ce972..f52d9fe37c 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -346,7 +346,7 @@ namespace AZ } } - RHI::DrawItemSortKey MeshFeatureProcessor::GetSortKey(const MeshHandle& meshHandle) + RHI::DrawItemSortKey MeshFeatureProcessor::GetSortKey(const MeshHandle& meshHandle) const { if (meshHandle.IsValid()) { @@ -359,24 +359,24 @@ namespace AZ } } - void MeshFeatureProcessor::SetLodOverride(const MeshHandle& meshHandle, RPI::Cullable::LodOverride lodOverride) + void MeshFeatureProcessor::SetMeshLodConfiguration(const MeshHandle& meshHandle, const RPI::Cullable::LodConfiguration& meshLodConfig) { if (meshHandle.IsValid()) { - meshHandle->SetLodOverride(lodOverride); + meshHandle->SetMeshLodConfiguration(meshLodConfig); } } - RPI::Cullable::LodOverride MeshFeatureProcessor::GetLodOverride(const MeshHandle& meshHandle) + RPI::Cullable::LodConfiguration MeshFeatureProcessor::GetMeshLodConfiguration(const MeshHandle& meshHandle) const { if (meshHandle.IsValid()) { - return meshHandle->GetLodOverride(); + return meshHandle->GetMeshLodConfiguration(); } else { AZ_Assert(false, "Invalid mesh handle"); - return 0; + return {RPI::Cullable::LodType::Default, 0, 0.0f, 0.0f }; } } @@ -968,19 +968,19 @@ namespace AZ } } - RHI::DrawItemSortKey MeshDataInstance::GetSortKey() + RHI::DrawItemSortKey MeshDataInstance::GetSortKey() const { return m_sortKey; } - void MeshDataInstance::SetLodOverride(RPI::Cullable::LodOverride lodOverride) + void MeshDataInstance::SetMeshLodConfiguration(RPI::Cullable::LodConfiguration meshLodConfig) { - m_cullable.m_lodData.m_lodOverride = lodOverride; + m_cullable.m_lodData.m_lodConfiguration = meshLodConfig; } - RPI::Cullable::LodOverride MeshDataInstance::GetLodOverride() + RPI::Cullable::LodConfiguration MeshDataInstance::GetMeshLodConfiguration() const { - return m_cullable.m_lodData.m_lodOverride; + return m_cullable.m_lodData.m_lodConfiguration; } void MeshDataInstance::UpdateDrawPackets(bool forceUpdate /*= false*/) @@ -1022,9 +1022,6 @@ namespace AZ { //initialize the lod RPI::Cullable::LodData::Lod& lod = lodData.m_lods[lodIndex]; - //[GFX TODO][ATOM-5562] - Level of detail: override lod distances and add global lod multiplier(s) - static const float MinimumScreenCoverage = 1.0f/1080.0f; //mesh should cover at least a screen pixel at 1080p to be drawn - static const float ReductionFactor = 0.5f; if (lodIndex == 0) { //first lod @@ -1033,17 +1030,18 @@ namespace AZ else { //every other lod: use the previous lod's min - lod.m_screenCoverageMax = AZStd::GetMax(lodData.m_lods[lodIndex-1].m_screenCoverageMin, MinimumScreenCoverage); + lod.m_screenCoverageMax = AZStd::GetMax(lodData.m_lods[lodIndex - 1].m_screenCoverageMin, lodData.m_lodConfiguration.m_minimumScreenCoverage); } + if (lodIndex < lodAssets.size() - 1) { //first and middle lods: compute a stepdown value for the min - lod.m_screenCoverageMin = AZStd::GetMax(ReductionFactor * lod.m_screenCoverageMax, MinimumScreenCoverage); + lod.m_screenCoverageMin = AZStd::GetMax(lodData.m_lodConfiguration.m_qualityDecayRate * lod.m_screenCoverageMax, lodData.m_lodConfiguration.m_minimumScreenCoverage); } else { //last lod: use MinimumScreenCoverage for the min - lod.m_screenCoverageMin = MinimumScreenCoverage; + lod.m_screenCoverageMin = lodData.m_lodConfiguration.m_minimumScreenCoverage; } lod.m_drawPackets.clear(); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Culling.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Culling.h index bcc4dfb89f..82e9c733c8 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Culling.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Culling.h @@ -70,8 +70,23 @@ namespace AZ }; CullData m_cullData; + enum LodType : uint8_t + { + Default = 0, + ScreenCoverage, + SpecificLod, + }; using LodOverride = uint8_t; - static constexpr uint8_t NoLodOverride = AZStd::numeric_limits::max(); + + struct LodConfiguration + { + LodType m_lodType = LodType::Default; + LodOverride m_lodOverride = 0; + // the minimum possibe area a sphere enclosing a mesh projected onto the screen should have before it is culled. + float m_minimumScreenCoverage = 1.0f / 1080.0f; // For default, mesh should cover at least a screen pixel at 1080p to be drawn; + // The screen area decay between 0 and 1, i.e. closer to 1 -> lose quality immediately, closer to 0 -> never lose quality + float m_qualityDecayRate = 0.5f; + }; struct LodData { @@ -88,7 +103,7 @@ namespace AZ //! Suggest setting to: 0.5f*localAabb.GetExtents().GetMaxElement() float m_lodSelectionRadius = 1.0f; - LodOverride m_lodOverride = NoLodOverride; + LodConfiguration m_lodConfiguration; }; LodData m_lodData; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp index f07262f2ca..54163f830b 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp @@ -672,20 +672,25 @@ namespace AZ } }; - if (lodData.m_lodOverride == Cullable::NoLodOverride) + switch (lodData.m_lodConfiguration.m_lodType) { - for (const Cullable::LodData::Lod& lod : lodData.m_lods) - { - //Note that this supports overlapping lod ranges (to suport cross-fading lods, for example) - if (approxScreenPercentage >= lod.m_screenCoverageMin && approxScreenPercentage <= lod.m_screenCoverageMax) + case Cullable::LodType::SpecificLod: + if (lodData.m_lodConfiguration.m_lodOverride < lodData.m_lods.size()) { - addLodToDrawPacket(lod); + addLodToDrawPacket(lodData.m_lods.at(lodData.m_lodConfiguration.m_lodOverride)); } - } - } - else if(lodData.m_lodOverride < lodData.m_lods.size()) - { - addLodToDrawPacket(lodData.m_lods.at(lodData.m_lodOverride)); + break; + case Cullable::LodType::ScreenCoverage: + default: + for (const Cullable::LodData::Lod& lod : lodData.m_lods) + { + // Note that this supports overlapping lod ranges (to suport cross-fading lods, for example) + if (approxScreenPercentage >= lod.m_screenCoverageMin && approxScreenPercentage <= lod.m_screenCoverageMax) + { + addLodToDrawPacket(lod); + } + } + break; } return numVisibleDrawPackets; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocumentMainWindow.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocumentMainWindow.h new file mode 100644 index 0000000000..826d2427b1 --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocumentMainWindow.h @@ -0,0 +1,90 @@ +/* + * 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 + +#if !defined(Q_MOC_RUN) +#include +#include +#include +#include +#endif + +namespace AtomToolsFramework +{ + //! AtomToolsDocumentMainWindow + class AtomToolsDocumentMainWindow + : public AtomToolsMainWindow + , private AtomToolsDocumentNotificationBus::Handler + { + Q_OBJECT + public: + AZ_CLASS_ALLOCATOR(AtomToolsDocumentMainWindow, AZ::SystemAllocator, 0); + + using Base = AtomToolsMainWindow; + + AtomToolsDocumentMainWindow(QWidget* parent = 0); + ~AtomToolsDocumentMainWindow(); + + protected: + void AddDocumentMenus(); + void AddDocumentTabBar(); + + QString GetDocumentPath(const AZ::Uuid& documentId) const; + + AZ::Uuid GetDocumentTabId(const int tabIndex) const; + + void AddDocumentTab( + const AZ::Uuid& documentId, + const AZStd::string& label, + const AZStd::string& toolTip); + + void RemoveDocumentTab(const AZ::Uuid& documentId); + + void UpdateDocumentTab( + const AZ::Uuid& documentId, const AZStd::string& label, const AZStd::string& toolTip, bool isModified); + + void SelectPrevDocumentTab(); + void SelectNextDocumentTab(); + + virtual QWidget* CreateDocumentTabView(const AZ::Uuid& documentId); + virtual void OpenDocumentTabContextMenu(); + virtual bool GetCreateDocumentParams(AZStd::string& openPath, AZStd::string& savePath); + virtual bool GetOpenDocumentParams(AZStd::string& openPath); + + // AtomToolsDocumentNotificationBus::Handler overrides... + void OnDocumentOpened(const AZ::Uuid& documentId) override; + void OnDocumentClosed(const AZ::Uuid& documentId) override; + void OnDocumentModified(const AZ::Uuid& documentId) override; + void OnDocumentUndoStateChanged(const AZ::Uuid& documentId) override; + void OnDocumentSaved(const AZ::Uuid& documentId) override; + + void closeEvent(QCloseEvent* closeEvent) override; + + template + QAction* CreateAction(const QString& text, Functor functor, const QKeySequence& shortcut = 0); + + QAction* m_actionNew = {}; + QAction* m_actionOpen = {}; + QAction* m_actionClose = {}; + QAction* m_actionCloseAll = {}; + QAction* m_actionCloseOthers = {}; + QAction* m_actionSave = {}; + QAction* m_actionSaveAsCopy = {}; + QAction* m_actionSaveAsChild = {}; + QAction* m_actionSaveAll = {}; + + QAction* m_actionUndo = {}; + QAction* m_actionRedo = {}; + + QAction* m_actionNextTab = {}; + QAction* m_actionPreviousTab = {}; + + AzQtComponents::TabWidget* m_tabWidget = {}; + }; +} // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h index bb26e116af..b6db6b78ae 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h @@ -109,7 +109,6 @@ namespace AtomToolsFramework // AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Handler ... void BeginCursorCapture() override; void EndCursorCapture() override; - AzFramework::ScreenPoint ViewportCursorScreenPosition() override; bool IsMouseOver() const override; // AzFramework::WindowRequestBus::Handler ... diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Window/AtomToolsMainWindow.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Window/AtomToolsMainWindow.h index 2a304c9a8c..fab6e2fb01 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Window/AtomToolsMainWindow.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Window/AtomToolsMainWindow.h @@ -7,18 +7,14 @@ */ #pragma once + #include - #include - #include #include #include -#include #include -#include -#include namespace AtomToolsFramework { @@ -38,28 +34,26 @@ namespace AtomToolsFramework bool IsDockWidgetVisible(const AZStd::string& name) const override; AZStd::vector GetDockWidgetNames() const override; - virtual void CreateMenu(); - virtual void CreateTabBar(); - - virtual void AddTabForDocumentId( - const AZ::Uuid& documentId, const AZStd::string& label, const AZStd::string& toolTip, AZStd::function widgetCreator); - virtual void RemoveTabForDocumentId(const AZ::Uuid& documentId); - virtual void UpdateTabForDocumentId( - const AZ::Uuid& documentId, const AZStd::string& label, const AZStd::string& toolTip, bool isModified); - virtual AZ::Uuid GetDocumentIdFromTab(const int tabIndex) const; - - virtual void OpenTabContextMenu(); - virtual void SelectPreviousTab(); - virtual void SelectNextTab(); - void SetStatusMessage(const QString& message); void SetStatusWarning(const QString& message); void SetStatusError(const QString& message); - AzQtComponents::FancyDocking* m_advancedDockManager = nullptr; - AzQtComponents::TabWidget* m_tabWidget = nullptr; - QLabel* m_statusMessage = nullptr; + void AddCommonMenus(); + + virtual void OpenSettings(); + virtual void OpenHelp(); + virtual void OpenAbout(); + + AzQtComponents::FancyDocking* m_advancedDockManager = {}; + + QLabel* m_statusMessage = {}; + + QMenu* m_menuFile = {}; + QMenu* m_menuEdit = {}; + QMenu* m_menuView = {}; + QMenu* m_menuHelp = {}; AZStd::unordered_map m_dockWidgets; + AZStd::unordered_map m_dockActions; }; } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentMainWindow.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentMainWindow.cpp new file mode 100644 index 0000000000..c39bfc8327 --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentMainWindow.cpp @@ -0,0 +1,493 @@ +/* + * 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 +#include +#include + +AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT +#include +#include +#include +#include +#include +#include +#include +AZ_POP_DISABLE_WARNING + +namespace AtomToolsFramework +{ + AtomToolsDocumentMainWindow::AtomToolsDocumentMainWindow(QWidget* parent /* = 0 */) + : AtomToolsMainWindow(parent) + { + setObjectName("AtomToolsDocumentMainWindow"); + AddDocumentMenus(); + AddDocumentTabBar(); + AtomToolsDocumentNotificationBus::Handler::BusConnect(); + } + + AtomToolsDocumentMainWindow::~AtomToolsDocumentMainWindow() + { + AtomToolsDocumentNotificationBus::Handler::BusDisconnect(); + } + + void AtomToolsDocumentMainWindow::AddDocumentMenus() + { + QAction* insertPostion = !m_menuFile->actions().empty() ? m_menuFile->actions().front() : nullptr; + + // Generating the main menu manually because it's easier and we will have some dynamic or data driven entries + m_actionNew = CreateAction("&New...", [this]() { + AZStd::string openPath; + AZStd::string savePath; + if (GetCreateDocumentParams(openPath, savePath)) + { + AtomToolsDocumentSystemRequestBus::Broadcast( + &AtomToolsDocumentSystemRequestBus::Events::CreateDocumentFromFile, openPath, savePath); + } + }, QKeySequence::New); + m_menuFile->insertAction(insertPostion, m_actionNew); + + m_actionOpen = CreateAction("&Open...", [this]() { + AZStd::string openPath; + if (GetOpenDocumentParams(openPath)) + { + AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsDocumentSystemRequestBus::Events::OpenDocument, openPath); + } + }, QKeySequence::Open); + m_menuFile->insertAction(insertPostion, m_actionOpen); + m_menuFile->insertSeparator(insertPostion); + + m_actionSave = CreateAction("&Save", [this]() { + const AZ::Uuid documentId = GetDocumentTabId(m_tabWidget->currentIndex()); + bool result = false; + AtomToolsDocumentSystemRequestBus::BroadcastResult(result, &AtomToolsDocumentSystemRequestBus::Events::SaveDocument, documentId); + if (!result) + { + SetStatusError(tr("Document save failed: %1").arg(GetDocumentPath(documentId))); + } + }, QKeySequence::Save); + m_menuFile->insertAction(insertPostion, m_actionSave); + + m_actionSaveAsCopy = CreateAction("Save &As...", [this]() { + const AZ::Uuid documentId = GetDocumentTabId(m_tabWidget->currentIndex()); + const QString documentPath = GetDocumentPath(documentId); + + bool result = false; + AtomToolsDocumentSystemRequestBus::BroadcastResult(result, &AtomToolsDocumentSystemRequestBus::Events::SaveDocumentAsCopy, + documentId, GetSaveFileInfo(documentPath).absoluteFilePath().toUtf8().constData()); + if (!result) + { + SetStatusError(tr("Document save failed: %1").arg(GetDocumentPath(documentId))); + } + }, QKeySequence::SaveAs); + m_menuFile->insertAction(insertPostion, m_actionSaveAsCopy); + + m_actionSaveAsChild = CreateAction("Save As &Child...", [this]() { + const AZ::Uuid documentId = GetDocumentTabId(m_tabWidget->currentIndex()); + const QString documentPath = GetDocumentPath(documentId); + + bool result = false; + AtomToolsDocumentSystemRequestBus::BroadcastResult(result, &AtomToolsDocumentSystemRequestBus::Events::SaveDocumentAsChild, + documentId, GetSaveFileInfo(documentPath).absoluteFilePath().toUtf8().constData()); + if (!result) + { + SetStatusError(tr("Document save failed: %1").arg(GetDocumentPath(documentId))); + } + }); + m_menuFile->insertAction(insertPostion, m_actionSaveAsChild); + + m_actionSaveAll = CreateAction("Save A&ll", [this]() { + bool result = false; + AtomToolsDocumentSystemRequestBus::BroadcastResult(result, &AtomToolsDocumentSystemRequestBus::Events::SaveAllDocuments); + if (!result) + { + SetStatusError(tr("Document save all failed")); + } + }); + m_menuFile->insertAction(insertPostion, m_actionSaveAll); + m_menuFile->insertSeparator(insertPostion); + + m_actionClose = CreateAction("&Close", [this]() { + const AZ::Uuid documentId = GetDocumentTabId(m_tabWidget->currentIndex()); + AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsDocumentSystemRequestBus::Events::CloseDocument, documentId); + }, QKeySequence::Close); + m_menuFile->insertAction(insertPostion, m_actionClose); + + m_actionCloseAll = CreateAction("Close All", [this]() { + AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsDocumentSystemRequestBus::Events::CloseAllDocuments); + }); + m_menuFile->insertAction(insertPostion, m_actionCloseAll); + + m_actionCloseOthers = CreateAction("Close Others", [this]() { + const AZ::Uuid documentId = GetDocumentTabId(m_tabWidget->currentIndex()); + AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsDocumentSystemRequestBus::Events::CloseAllDocumentsExcept, documentId); + }); + m_menuFile->insertAction(insertPostion, m_actionCloseOthers); + m_menuFile->insertSeparator(insertPostion); + + insertPostion = !m_menuEdit->actions().empty() ? m_menuEdit->actions().front() : nullptr; + + m_actionUndo = CreateAction("&Undo", [this]() { + const AZ::Uuid documentId = GetDocumentTabId(m_tabWidget->currentIndex()); + bool result = false; + AtomToolsDocumentRequestBus::EventResult(result, documentId, &AtomToolsDocumentRequestBus::Events::Undo); + if (!result) + { + SetStatusError(tr("Document undo failed: %1").arg(GetDocumentPath(documentId))); + } + }, QKeySequence::Undo); + m_menuEdit->insertAction(insertPostion, m_actionUndo); + + m_actionRedo = CreateAction("&Redo", [this]() { + const AZ::Uuid documentId = GetDocumentTabId(m_tabWidget->currentIndex()); + bool result = false; + AtomToolsDocumentRequestBus::EventResult(result, documentId, &AtomToolsDocumentRequestBus::Events::Redo); + if (!result) + { + SetStatusError(tr("Document redo failed: %1").arg(GetDocumentPath(documentId))); + } + }, QKeySequence::Redo); + m_menuEdit->insertAction(insertPostion, m_actionRedo); + m_menuEdit->insertSeparator(insertPostion); + + insertPostion = !m_menuView->actions().empty() ? m_menuView->actions().front() : nullptr; + + m_actionPreviousTab = CreateAction( + "&Previous Tab", + [this]() + { + SelectPrevDocumentTab(); + }, Qt::CTRL | Qt::SHIFT | Qt::Key_Tab); //QKeySequence::PreviousChild is mapped incorrectly in Qt + m_menuView->insertAction(insertPostion, m_actionPreviousTab); + + m_actionNextTab = CreateAction("&Next Tab", [this]() { + SelectNextDocumentTab(); + }, Qt::CTRL | Qt::Key_Tab); //QKeySequence::NextChild works as expected but mirroring Previous + m_menuView->insertAction(insertPostion, m_actionNextTab); + m_menuView->insertSeparator(insertPostion); + } + + void AtomToolsDocumentMainWindow::AddDocumentTabBar() + { + m_tabWidget = new AzQtComponents::TabWidget(centralWidget()); + m_tabWidget->setObjectName("TabWidget"); + m_tabWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); + m_tabWidget->setContentsMargins(0, 0, 0, 0); + + // The tab bar should only be visible if it has active documents + m_tabWidget->setVisible(false); + m_tabWidget->setTabBarAutoHide(false); + m_tabWidget->setMovable(true); + m_tabWidget->setTabsClosable(true); + m_tabWidget->setUsesScrollButtons(true); + + // This signal will be triggered whenever a tab is added, removed, selected, clicked, dragged + // When the last tab is removed tabIndex will be -1 and the document ID will be null + // This should automatically clear the active document + connect(m_tabWidget, &QTabWidget::currentChanged, this, [this](int tabIndex) { + const AZ::Uuid documentId = GetDocumentTabId(tabIndex); + AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, documentId); + }); + + connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, [this](int tabIndex) { + const AZ::Uuid documentId = GetDocumentTabId(tabIndex); + AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsDocumentSystemRequestBus::Events::CloseDocument, documentId); + }); + + // Add context menu for right-clicking on tabs + m_tabWidget->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); + connect(m_tabWidget, &QWidget::customContextMenuRequested, this, [this]() { + OpenDocumentTabContextMenu(); + }); + + centralWidget()->layout()->addWidget(m_tabWidget); + } + + QString AtomToolsDocumentMainWindow::GetDocumentPath(const AZ::Uuid& documentId) const + { + AZStd::string absolutePath; + AtomToolsDocumentRequestBus::EventResult(absolutePath, documentId, &AtomToolsDocumentRequestBus::Handler::GetAbsolutePath); + return absolutePath.c_str(); + } + + AZ::Uuid AtomToolsDocumentMainWindow::GetDocumentTabId(const int tabIndex) const + { + const QVariant tabData = m_tabWidget->tabBar()->tabData(tabIndex); + if (!tabData.isNull()) + { + // We need to be able to convert between a UUID and a string to store and retrieve a document ID from the tab bar + const QString documentIdString = tabData.toString(); + const QByteArray documentIdBytes = documentIdString.toUtf8(); + const AZ::Uuid documentId(documentIdBytes.data(), documentIdBytes.size()); + return documentId; + } + return AZ::Uuid::CreateNull(); + } + + void AtomToolsDocumentMainWindow::AddDocumentTab( + const AZ::Uuid& documentId, const AZStd::string& label, const AZStd::string& toolTip) + { + // Blocking signals from the tab bar so the currentChanged signal is not sent while a document is already being opened. + // This prevents the OnDocumentOpened notification from being sent recursively. + const QSignalBlocker blocker(m_tabWidget); + + // If a tab for this document already exists then select it instead of creating a new one + for (int tabIndex = 0; tabIndex < m_tabWidget->count(); ++tabIndex) + { + if (documentId == GetDocumentTabId(tabIndex)) + { + m_tabWidget->setCurrentIndex(tabIndex); + m_tabWidget->repaint(); + return; + } + } + + const int tabIndex = m_tabWidget->addTab(CreateDocumentTabView(documentId), label.c_str()); + + // The user can manually reorder tabs which will invalidate any association by index. + // We need to store the document ID with the tab using the tab instead of a separate mapping. + m_tabWidget->tabBar()->setTabData(tabIndex, QVariant(documentId.ToString())); + m_tabWidget->setTabToolTip(tabIndex, toolTip.c_str()); + m_tabWidget->setCurrentIndex(tabIndex); + m_tabWidget->setVisible(true); + m_tabWidget->repaint(); + } + + void AtomToolsDocumentMainWindow::RemoveDocumentTab(const AZ::Uuid& documentId) + { + // We are not blocking signals here because we want closing tabs to close the associated document + // and automatically select the next document. + for (int tabIndex = 0; tabIndex < m_tabWidget->count(); ++tabIndex) + { + if (documentId == GetDocumentTabId(tabIndex)) + { + m_tabWidget->removeTab(tabIndex); + m_tabWidget->setVisible(m_tabWidget->count() > 0); + m_tabWidget->repaint(); + break; + } + } + } + + void AtomToolsDocumentMainWindow::UpdateDocumentTab( + const AZ::Uuid& documentId, const AZStd::string& label, const AZStd::string& toolTip, bool isModified) + { + // Whenever a document is opened, saved, or modified we need to update the tab label + if (!documentId.IsNull()) + { + // Because tab order and indexes can change from user interactions, we cannot store a map + // between a tab index and document ID. + // We must iterate over all of the tabs to find the one associated with this document. + for (int tabIndex = 0; tabIndex < m_tabWidget->count(); ++tabIndex) + { + if (documentId == GetDocumentTabId(tabIndex)) + { + // We use an asterisk prepended to the file name to denote modified document + // Appending is standard and preferred but the tabs elide from the + // end (instead of middle) and cut it off + const AZStd::string modifiedLabel = isModified ? "* " + label : label; + m_tabWidget->setTabText(tabIndex, modifiedLabel.c_str()); + m_tabWidget->setTabToolTip(tabIndex, toolTip.c_str()); + m_tabWidget->repaint(); + break; + } + } + } + } + + void AtomToolsDocumentMainWindow::SelectPrevDocumentTab() + { + if (m_tabWidget->count() > 1) + { + // Adding count to wrap around when index <= 0 + m_tabWidget->setCurrentIndex((m_tabWidget->currentIndex() + m_tabWidget->count() - 1) % m_tabWidget->count()); + } + } + + void AtomToolsDocumentMainWindow::SelectNextDocumentTab() + { + if (m_tabWidget->count() > 1) + { + m_tabWidget->setCurrentIndex((m_tabWidget->currentIndex() + 1) % m_tabWidget->count()); + } + } + + QWidget* AtomToolsDocumentMainWindow::CreateDocumentTabView(const AZ::Uuid& documentId) + { + AZ_UNUSED(documentId); + auto contentWidget = new QWidget(centralWidget()); + contentWidget->setContentsMargins(0, 0, 0, 0); + contentWidget->setFixedSize(0, 0); + return contentWidget; + } + + void AtomToolsDocumentMainWindow::OpenDocumentTabContextMenu() + { + const QTabBar* tabBar = m_tabWidget->tabBar(); + const QPoint position = tabBar->mapFromGlobal(QCursor::pos()); + const int clickedTabIndex = tabBar->tabAt(position); + const int currentTabIndex = tabBar->currentIndex(); + if (clickedTabIndex >= 0) + { + QMenu tabMenu; + const QString selectActionName = (currentTabIndex == clickedTabIndex) ? "Select in Browser" : "Select"; + tabMenu.addAction(selectActionName, [this, clickedTabIndex]() { + const AZ::Uuid documentId = GetDocumentTabId(clickedTabIndex); + AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, documentId); + }); + tabMenu.addAction("Close", [this, clickedTabIndex]() { + const AZ::Uuid documentId = GetDocumentTabId(clickedTabIndex); + AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsDocumentSystemRequestBus::Events::CloseDocument, documentId); + }); + auto closeOthersAction = tabMenu.addAction("Close Others", [this, clickedTabIndex]() { + const AZ::Uuid documentId = GetDocumentTabId(clickedTabIndex); + AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsDocumentSystemRequestBus::Events::CloseAllDocumentsExcept, documentId); + }); + closeOthersAction->setEnabled(tabBar->count() > 1); + tabMenu.exec(QCursor::pos()); + } + } + + bool AtomToolsDocumentMainWindow::GetCreateDocumentParams(AZStd::string& openPath, AZStd::string& savePath) + { + AZ_UNUSED(openPath); + AZ_UNUSED(savePath); + return false; + } + + bool AtomToolsDocumentMainWindow::GetOpenDocumentParams(AZStd::string& openPath) + { + AZ_UNUSED(openPath); + return false; + } + + void AtomToolsDocumentMainWindow::OnDocumentOpened(const AZ::Uuid& documentId) + { + bool isOpen = false; + AtomToolsDocumentRequestBus::EventResult(isOpen, documentId, &AtomToolsDocumentRequestBus::Events::IsOpen); + bool isSavable = false; + AtomToolsDocumentRequestBus::EventResult(isSavable, documentId, &AtomToolsDocumentRequestBus::Events::IsSavable); + bool isModified = false; + AtomToolsDocumentRequestBus::EventResult(isModified, documentId, &AtomToolsDocumentRequestBus::Events::IsModified); + bool canUndo = false; + AtomToolsDocumentRequestBus::EventResult(canUndo, documentId, &AtomToolsDocumentRequestBus::Events::CanUndo); + bool canRedo = false; + AtomToolsDocumentRequestBus::EventResult(canRedo, documentId, &AtomToolsDocumentRequestBus::Events::CanRedo); + AZStd::string absolutePath; + AtomToolsDocumentRequestBus::EventResult(absolutePath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath); + AZStd::string filename; + AzFramework::StringFunc::Path::GetFullFileName(absolutePath.c_str(), filename); + + // Update UI to display the new document + if (!documentId.IsNull() && isOpen) + { + // Create a new tab for the document ID and assign it's label to the file name of the document. + AddDocumentTab(documentId, filename, absolutePath); + UpdateDocumentTab(documentId, filename, absolutePath, isModified); + } + + const bool hasTabs = m_tabWidget->count() > 0; + + // Update menu options + m_actionNew->setEnabled(true); + m_actionOpen->setEnabled(true); + m_actionClose->setEnabled(hasTabs); + m_actionCloseAll->setEnabled(hasTabs); + m_actionCloseOthers->setEnabled(hasTabs); + + m_actionSave->setEnabled(isOpen && isSavable); + m_actionSaveAsCopy->setEnabled(isOpen && isSavable); + m_actionSaveAsChild->setEnabled(isOpen); + m_actionSaveAll->setEnabled(hasTabs); + + m_actionUndo->setEnabled(canUndo); + m_actionRedo->setEnabled(canRedo); + + m_actionPreviousTab->setEnabled(m_tabWidget->count() > 1); + m_actionNextTab->setEnabled(m_tabWidget->count() > 1); + + activateWindow(); + raise(); + + const QString documentPath = GetDocumentPath(documentId); + if (!documentPath.isEmpty()) + { + SetStatusMessage(tr("Document opened: %1").arg(documentPath)); + } + } + + void AtomToolsDocumentMainWindow::OnDocumentClosed(const AZ::Uuid& documentId) + { + RemoveDocumentTab(documentId); + SetStatusMessage(tr("Document closed: %1").arg(GetDocumentPath(documentId))); + } + + void AtomToolsDocumentMainWindow::OnDocumentModified(const AZ::Uuid& documentId) + { + bool isModified = false; + AtomToolsDocumentRequestBus::EventResult(isModified, documentId, &AtomToolsDocumentRequestBus::Events::IsModified); + AZStd::string absolutePath; + AtomToolsDocumentRequestBus::EventResult(absolutePath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath); + AZStd::string filename; + AzFramework::StringFunc::Path::GetFullFileName(absolutePath.c_str(), filename); + UpdateDocumentTab(documentId, filename, absolutePath, isModified); + } + + void AtomToolsDocumentMainWindow::OnDocumentUndoStateChanged(const AZ::Uuid& documentId) + { + if (documentId == GetDocumentTabId(m_tabWidget->currentIndex())) + { + bool canUndo = false; + AtomToolsDocumentRequestBus::EventResult(canUndo, documentId, &AtomToolsDocumentRequestBus::Events::CanUndo); + bool canRedo = false; + AtomToolsDocumentRequestBus::EventResult(canRedo, documentId, &AtomToolsDocumentRequestBus::Events::CanRedo); + m_actionUndo->setEnabled(canUndo); + m_actionRedo->setEnabled(canRedo); + } + } + + void AtomToolsDocumentMainWindow::OnDocumentSaved(const AZ::Uuid& documentId) + { + bool isModified = false; + AtomToolsDocumentRequestBus::EventResult(isModified, documentId, &AtomToolsDocumentRequestBus::Events::IsModified); + AZStd::string absolutePath; + AtomToolsDocumentRequestBus::EventResult(absolutePath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath); + AZStd::string filename; + AzFramework::StringFunc::Path::GetFullFileName(absolutePath.c_str(), filename); + UpdateDocumentTab(documentId, filename, absolutePath, isModified); + SetStatusMessage(tr("Document saved: %1").arg(GetDocumentPath(documentId))); + } + + + void AtomToolsDocumentMainWindow::closeEvent(QCloseEvent* closeEvent) + { + bool didClose = true; + AtomToolsDocumentSystemRequestBus::BroadcastResult(didClose, &AtomToolsDocumentSystemRequestBus::Events::CloseAllDocuments); + if (!didClose) + { + closeEvent->ignore(); + return; + } + + AtomToolsMainWindowNotificationBus::Broadcast(&AtomToolsMainWindowNotifications::OnMainWindowClosing); + } + + template + QAction* AtomToolsDocumentMainWindow::CreateAction(const QString& text, Functor functor, const QKeySequence& shortcut) + { + QAction* action = new QAction(text, this); + action->setShortcut(shortcut); + connect(action, &QAction::triggered, this, functor); + return action; + } +} // namespace AtomToolsFramework + +//#include diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp index bf356d2b99..cbb30ba6da 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp @@ -403,11 +403,6 @@ namespace AtomToolsFramework return aznumeric_cast(devicePixelRatioF()); } - AzFramework::ScreenPoint RenderViewportWidget::ViewportCursorScreenPosition() - { - return AzToolsFramework::ViewportInteraction::ScreenPointFromQPoint(m_mousePosition.toPoint()); - } - bool RenderViewportWidget::IsMouseOver() const { return m_mouseOver; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Window/AtomToolsMainWindow.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Window/AtomToolsMainWindow.cpp index cd7d49d8d3..aeca51230a 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Window/AtomToolsMainWindow.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Window/AtomToolsMainWindow.cpp @@ -7,6 +7,11 @@ */ #include +#include + +#include +#include +#include #include #include @@ -23,6 +28,8 @@ namespace AtomToolsFramework setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); + AddCommonMenus(); + m_statusMessage = new QLabel(statusBar()); statusBar()->addPermanentWidget(m_statusMessage, 1); @@ -65,6 +72,10 @@ namespace AtomToolsFramework addDockWidget(aznumeric_cast(area), dockWidget); resizeDocks({ dockWidget }, { 400 }, aznumeric_cast(orientation)); m_dockWidgets[name] = dockWidget; + + m_dockActions[name] = m_menuView->addAction(name.c_str(), [this, name](){ + SetDockWidgetVisible(name, !IsDockWidgetVisible(name)); + }); return true; } @@ -76,6 +87,12 @@ namespace AtomToolsFramework delete dockWidgetItr->second; m_dockWidgets.erase(dockWidgetItr); } + auto dockActionItr = m_dockActions.find(name); + if (dockActionItr != m_dockActions.end()) + { + delete dockActionItr->second; + m_dockActions.erase(dockActionItr); + } } void AtomToolsMainWindow::SetDockWidgetVisible(const AZStd::string& name, bool visible) @@ -108,145 +125,6 @@ namespace AtomToolsFramework return names; } - void AtomToolsMainWindow::CreateMenu() - { - auto menuBar = new QMenuBar(this); - menuBar->setObjectName("MenuBar"); - setMenuBar(menuBar); - } - - void AtomToolsMainWindow::CreateTabBar() - { - m_tabWidget = new AzQtComponents::TabWidget(centralWidget()); - m_tabWidget->setObjectName("TabWidget"); - m_tabWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); - m_tabWidget->setContentsMargins(0, 0, 0, 0); - - // The tab bar should only be visible if it has active documents - m_tabWidget->setVisible(false); - m_tabWidget->setTabBarAutoHide(false); - m_tabWidget->setMovable(true); - m_tabWidget->setTabsClosable(true); - m_tabWidget->setUsesScrollButtons(true); - - // Add context menu for right-clicking on tabs - m_tabWidget->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); - connect( - m_tabWidget, &QWidget::customContextMenuRequested, this, - [this]() - { - OpenTabContextMenu(); - }); - - centralWidget()->layout()->addWidget(m_tabWidget); - } - - void AtomToolsMainWindow::AddTabForDocumentId( - const AZ::Uuid& documentId, const AZStd::string& label, const AZStd::string& toolTip, AZStd::function widgetCreator) - { - // Blocking signals from the tab bar so the currentChanged signal is not sent while a document is already being opened. - // This prevents the OnDocumentOpened notification from being sent recursively. - const QSignalBlocker blocker(m_tabWidget); - - // If a tab for this document already exists then select it instead of creating a new one - for (int tabIndex = 0; tabIndex < m_tabWidget->count(); ++tabIndex) - { - if (documentId == GetDocumentIdFromTab(tabIndex)) - { - m_tabWidget->setCurrentIndex(tabIndex); - m_tabWidget->repaint(); - return; - } - } - - const int tabIndex = m_tabWidget->addTab(widgetCreator(), label.c_str()); - - // The user can manually reorder tabs which will invalidate any association by index. - // We need to store the document ID with the tab using the tab instead of a separate mapping. - m_tabWidget->tabBar()->setTabData(tabIndex, QVariant(documentId.ToString())); - m_tabWidget->setTabToolTip(tabIndex, toolTip.c_str()); - m_tabWidget->setCurrentIndex(tabIndex); - m_tabWidget->setVisible(true); - m_tabWidget->repaint(); - } - - void AtomToolsMainWindow::RemoveTabForDocumentId(const AZ::Uuid& documentId) - { - // We are not blocking signals here because we want closing tabs to close the associated document - // and automatically select the next document. - for (int tabIndex = 0; tabIndex < m_tabWidget->count(); ++tabIndex) - { - if (documentId == GetDocumentIdFromTab(tabIndex)) - { - m_tabWidget->removeTab(tabIndex); - m_tabWidget->setVisible(m_tabWidget->count() > 0); - m_tabWidget->repaint(); - break; - } - } - } - - void AtomToolsMainWindow::UpdateTabForDocumentId( - const AZ::Uuid& documentId, const AZStd::string& label, const AZStd::string& toolTip, bool isModified) - { - // Whenever a document is opened, saved, or modified we need to update the tab label - if (!documentId.IsNull()) - { - // Because tab order and indexes can change from user interactions, we cannot store a map - // between a tab index and document ID. - // We must iterate over all of the tabs to find the one associated with this document. - for (int tabIndex = 0; tabIndex < m_tabWidget->count(); ++tabIndex) - { - if (documentId == GetDocumentIdFromTab(tabIndex)) - { - // We use an asterisk prepended to the file name to denote modified document - // Appending is standard and preferred but the tabs elide from the - // end (instead of middle) and cut it off - const AZStd::string modifiedLabel = isModified ? "* " + label : label; - m_tabWidget->setTabText(tabIndex, modifiedLabel.c_str()); - m_tabWidget->setTabToolTip(tabIndex, toolTip.c_str()); - m_tabWidget->repaint(); - break; - } - } - } - } - - AZ::Uuid AtomToolsMainWindow::GetDocumentIdFromTab(const int tabIndex) const - { - const QVariant tabData = m_tabWidget->tabBar()->tabData(tabIndex); - if (!tabData.isNull()) - { - // We need to be able to convert between a UUID and a string to store and retrieve a document ID from the tab bar - const QString documentIdString = tabData.toString(); - const QByteArray documentIdBytes = documentIdString.toUtf8(); - const AZ::Uuid documentId(documentIdBytes.data(), documentIdBytes.size()); - return documentId; - } - return AZ::Uuid::CreateNull(); - } - - void AtomToolsMainWindow::OpenTabContextMenu() - { - } - - void AtomToolsMainWindow::SelectPreviousTab() - { - if (m_tabWidget->count() > 1) - { - // Adding count to wrap around when index <= 0 - m_tabWidget->setCurrentIndex((m_tabWidget->currentIndex() + m_tabWidget->count() - 1) % m_tabWidget->count()); - } - } - - void AtomToolsMainWindow::SelectNextTab() - { - if (m_tabWidget->count() > 1) - { - m_tabWidget->setCurrentIndex((m_tabWidget->currentIndex() + 1) % m_tabWidget->count()); - } - } - void AtomToolsMainWindow::SetStatusMessage(const QString& message) { m_statusMessage->setText(QString("%1").arg(message)); @@ -261,4 +139,50 @@ namespace AtomToolsFramework { m_statusMessage->setText(QString("%1").arg(message)); } + + void AtomToolsMainWindow::AddCommonMenus() + { + m_menuFile = menuBar()->addMenu("&File"); + m_menuEdit = menuBar()->addMenu("&Edit"); + m_menuView = menuBar()->addMenu("&View"); + m_menuHelp = menuBar()->addMenu("&Help"); + + m_menuFile->addAction("Run &Python...", [this]() { + const QString script = QFileDialog::getOpenFileName(this, "Run Script", QString(), QString("*.py")); + if (!script.isEmpty()) + { + AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilename, script.toUtf8().constData()); + } + }); + + m_menuFile->addSeparator(); + + m_menuFile->addAction("E&xit", [this]() { + close(); + }, QKeySequence::Quit); + + m_menuEdit->addAction("&Settings...", [this]() { + OpenSettings(); + }, QKeySequence::Preferences); + + m_menuHelp->addAction("&Help...", [this]() { + OpenHelp(); + }); + + m_menuHelp->addAction("&About...", [this]() { + OpenAbout(); + }); + } + + void AtomToolsMainWindow::OpenSettings() + { + } + + void AtomToolsMainWindow::OpenHelp() + { + } + + void AtomToolsMainWindow::OpenAbout() + { + } } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_files.cmake b/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_files.cmake index cd056f5fcf..3d4bb82eec 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_files.cmake +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_files.cmake @@ -12,6 +12,7 @@ set(FILES Include/AtomToolsFramework/Communication/LocalSocket.h Include/AtomToolsFramework/Debug/TraceRecorder.h Include/AtomToolsFramework/Document/AtomToolsDocument.h + Include/AtomToolsFramework/Document/AtomToolsDocumentMainWindow.h Include/AtomToolsFramework/Document/AtomToolsDocumentSystemSettings.h Include/AtomToolsFramework/Document/AtomToolsDocumentSystemRequestBus.h Include/AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h @@ -38,6 +39,7 @@ set(FILES Source/Communication/LocalSocket.cpp Source/Debug/TraceRecorder.cpp Source/Document/AtomToolsDocument.cpp + Source/Document/AtomToolsDocumentMainWindow.cpp Source/Document/AtomToolsDocumentSystemSettings.cpp Source/Document/AtomToolsDocumentSystemComponent.cpp Source/Document/AtomToolsDocumentSystemComponent.h diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialEditorWindow.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialEditorWindow.cpp index 8922591580..0be6925701 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialEditorWindow.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialEditorWindow.cpp @@ -9,15 +9,9 @@ #include #include #include -#include -#include #include -#include -#include #include #include -#include -#include #include #include #include @@ -33,7 +27,6 @@ AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnin #include #include #include -#include #include #include AZ_POP_DISABLE_WARNING @@ -41,7 +34,7 @@ AZ_POP_DISABLE_WARNING namespace MaterialEditor { MaterialEditorWindow::MaterialEditorWindow(QWidget* parent /* = 0 */) - : AtomToolsFramework::AtomToolsMainWindow(parent) + : AtomToolsFramework::AtomToolsDocumentMainWindow(parent) { resize(1280, 1024); @@ -74,9 +67,6 @@ namespace MaterialEditor m_toolBar->setObjectName("ToolBar"); addToolBar(m_toolBar); - CreateMenu(); - CreateTabBar(); - m_materialViewport = new MaterialViewportWidget(centralWidget()); m_materialViewport->setObjectName("Viewport"); m_materialViewport->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); @@ -105,16 +95,9 @@ namespace MaterialEditor m_advancedDockManager->restoreState(windowState); } - AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler::BusConnect(); OnDocumentOpened(AZ::Uuid::CreateNull()); } - MaterialEditorWindow::~MaterialEditorWindow() - { - AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler::BusDisconnect(); - } - - void MaterialEditorWindow::ResizeViewportRenderTarget(uint32_t width, uint32_t height) { QSize requestedViewportSize = QSize(width, height) / devicePixelRatioF(); @@ -146,16 +129,43 @@ namespace MaterialEditor m_materialViewport->UnlockRenderTargetSize(); } + bool MaterialEditorWindow::GetCreateDocumentParams(AZStd::string& openPath, AZStd::string& savePath) + { + CreateMaterialDialog createDialog(this); + createDialog.adjustSize(); + + if (createDialog.exec() == QDialog::Accepted && + !createDialog.m_materialFileInfo.absoluteFilePath().isEmpty() && + !createDialog.m_materialTypeFileInfo.absoluteFilePath().isEmpty()) + { + savePath = createDialog.m_materialFileInfo.absoluteFilePath().toUtf8().constData(); + openPath = createDialog.m_materialTypeFileInfo.absoluteFilePath().toUtf8().constData(); + return true; + } + return false; + } + + bool MaterialEditorWindow::GetOpenDocumentParams(AZStd::string& openPath) + { + const AZStd::vector assetTypes = { azrtti_typeid() }; + openPath = AtomToolsFramework::GetOpenFileInfo(assetTypes).absoluteFilePath().toUtf8().constData(); + return !openPath.empty(); + } + + void MaterialEditorWindow::OpenSettings() + { + SettingsDialog dialog(this); + dialog.exec(); + } + + void MaterialEditorWindow::OpenHelp() + { + HelpDialog dialog(this); + dialog.exec(); + } + void MaterialEditorWindow::closeEvent(QCloseEvent* closeEvent) { - bool didClose = true; - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::BroadcastResult(didClose, &AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CloseAllDocuments); - if (!didClose) - { - closeEvent->ignore(); - return; - } - // Capture docking state before shutdown auto windowSettings = AZ::UserSettings::CreateFind( AZ::Crc32("MaterialEditorWindowSettings"), AZ::UserSettings::CT_GLOBAL); @@ -163,368 +173,7 @@ namespace MaterialEditor QByteArray windowState = m_advancedDockManager->saveState(); windowSettings->m_mainWindowState.assign(windowState.begin(), windowState.end()); - AtomToolsFramework::AtomToolsMainWindowNotificationBus::Broadcast( - &AtomToolsFramework::AtomToolsMainWindowNotifications::OnMainWindowClosing); - } - - void MaterialEditorWindow::OnDocumentOpened(const AZ::Uuid& documentId) - { - bool isOpen = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(isOpen, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsOpen); - bool isSavable = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(isSavable, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsSavable); - bool isModified = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(isModified, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsModified); - bool canUndo = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(canUndo, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::CanUndo); - bool canRedo = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(canRedo, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::CanRedo); - AZStd::string absolutePath; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(absolutePath, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetAbsolutePath); - AZStd::string filename; - AzFramework::StringFunc::Path::GetFullFileName(absolutePath.c_str(), filename); - - // Update UI to display the new document - if (!documentId.IsNull() && isOpen) - { - // Create a new tab for the document ID and assign it's label to the file name of the document. - AddTabForDocumentId(documentId, filename, absolutePath, [this]{ - // The tab widget requires a dummy page per tab - auto contentWidget = new QWidget(centralWidget()); - contentWidget->setContentsMargins(0, 0, 0, 0); - contentWidget->setFixedSize(0, 0); - return contentWidget; - }); - } - - UpdateTabForDocumentId(documentId, filename, absolutePath, isModified); - - const bool hasTabs = m_tabWidget->count() > 0; - - // Update menu options - m_actionNew->setEnabled(true); - m_actionOpen->setEnabled(true); - m_actionOpenRecent->setEnabled(false); - m_actionClose->setEnabled(hasTabs); - m_actionCloseAll->setEnabled(hasTabs); - m_actionCloseOthers->setEnabled(hasTabs); - - m_actionSave->setEnabled(isOpen && isSavable); - m_actionSaveAsCopy->setEnabled(isOpen && isSavable); - m_actionSaveAsChild->setEnabled(isOpen); - m_actionSaveAll->setEnabled(hasTabs); - - m_actionExit->setEnabled(true); - - m_actionUndo->setEnabled(canUndo); - m_actionRedo->setEnabled(canRedo); - m_actionSettings->setEnabled(true); - - m_actionAssetBrowser->setEnabled(true); - m_actionInspector->setEnabled(true); - m_actionConsole->setEnabled(false); - m_actionPythonTerminal->setEnabled(true); - m_actionPerfMonitor->setEnabled(true); - m_actionViewportSettings->setEnabled(true); - m_actionPreviousTab->setEnabled(m_tabWidget->count() > 1); - m_actionNextTab->setEnabled(m_tabWidget->count() > 1); - - m_actionAbout->setEnabled(false); - - activateWindow(); - raise(); - - const QString documentPath = GetDocumentPath(documentId); - if (!documentPath.isEmpty()) - { - SetStatusMessage(tr("Document opened: %1").arg(documentPath)); - } - } - - void MaterialEditorWindow::OnDocumentClosed(const AZ::Uuid& documentId) - { - RemoveTabForDocumentId(documentId); - SetStatusMessage(tr("Document closed: %1").arg(GetDocumentPath(documentId))); - } - - void MaterialEditorWindow::OnDocumentModified(const AZ::Uuid& documentId) - { - bool isModified = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(isModified, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsModified); - AZStd::string absolutePath; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(absolutePath, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetAbsolutePath); - AZStd::string filename; - AzFramework::StringFunc::Path::GetFullFileName(absolutePath.c_str(), filename); - UpdateTabForDocumentId(documentId, filename, absolutePath, isModified); - } - - void MaterialEditorWindow::OnDocumentUndoStateChanged(const AZ::Uuid& documentId) - { - if (documentId == GetDocumentIdFromTab(m_tabWidget->currentIndex())) - { - bool canUndo = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(canUndo, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::CanUndo); - bool canRedo = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(canRedo, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::CanRedo); - m_actionUndo->setEnabled(canUndo); - m_actionRedo->setEnabled(canRedo); - } - } - - void MaterialEditorWindow::OnDocumentSaved(const AZ::Uuid& documentId) - { - bool isModified = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(isModified, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsModified); - AZStd::string absolutePath; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(absolutePath, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetAbsolutePath); - AZStd::string filename; - AzFramework::StringFunc::Path::GetFullFileName(absolutePath.c_str(), filename); - UpdateTabForDocumentId(documentId, filename, absolutePath, isModified); - SetStatusMessage(tr("Document saved: %1").arg(GetDocumentPath(documentId))); - } - - void MaterialEditorWindow::CreateMenu() - { - Base::CreateMenu(); - - // Generating the main menu manually because it's easier and we will have some dynamic or data driven entries - m_menuFile = menuBar()->addMenu("&File"); - - m_actionNew = m_menuFile->addAction("&New...", [this]() { - CreateMaterialDialog createDialog(this); - createDialog.adjustSize(); - - if (createDialog.exec() == QDialog::Accepted && - !createDialog.m_materialFileInfo.absoluteFilePath().isEmpty() && - !createDialog.m_materialTypeFileInfo.absoluteFilePath().isEmpty()) - { - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CreateDocumentFromFile, - createDialog.m_materialTypeFileInfo.absoluteFilePath().toUtf8().constData(), - createDialog.m_materialFileInfo.absoluteFilePath().toUtf8().constData()); - } - }, QKeySequence::New); - - m_actionOpen = m_menuFile->addAction("&Open...", [this]() { - const AZStd::vector assetTypes = { azrtti_typeid() }; - const AZStd::string filePath = AtomToolsFramework::GetOpenFileInfo(assetTypes).absoluteFilePath().toUtf8().constData(); - if (!filePath.empty()) - { - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::OpenDocument, filePath); - } - }, QKeySequence::Open); - - m_actionOpenRecent = m_menuFile->addAction("Open &Recent"); - - m_menuFile->addSeparator(); - - m_actionSave = m_menuFile->addAction("&Save", [this]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(m_tabWidget->currentIndex()); - bool result = false; - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::BroadcastResult(result, &AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::SaveDocument, documentId); - if (!result) - { - SetStatusError(tr("Document save failed: %1").arg(GetDocumentPath(documentId))); - } - }, QKeySequence::Save); - - m_actionSaveAsCopy = m_menuFile->addAction("Save &As...", [this]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(m_tabWidget->currentIndex()); - const QString documentPath = GetDocumentPath(documentId); - - bool result = false; - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::BroadcastResult(result, &AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::SaveDocumentAsCopy, - documentId, AtomToolsFramework::GetSaveFileInfo(documentPath).absoluteFilePath().toUtf8().constData()); - if (!result) - { - SetStatusError(tr("Document save failed: %1").arg(GetDocumentPath(documentId))); - } - }, QKeySequence::SaveAs); - - m_actionSaveAsChild = m_menuFile->addAction("Save As &Child...", [this]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(m_tabWidget->currentIndex()); - const QString documentPath = GetDocumentPath(documentId); - - bool result = false; - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::BroadcastResult(result, &AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::SaveDocumentAsChild, - documentId, AtomToolsFramework::GetSaveFileInfo(documentPath).absoluteFilePath().toUtf8().constData()); - if (!result) - { - SetStatusError(tr("Document save failed: %1").arg(GetDocumentPath(documentId))); - } - }); - - m_actionSaveAll = m_menuFile->addAction("Save A&ll", [this]() { - bool result = false; - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::BroadcastResult(result, &AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::SaveAllDocuments); - if (!result) - { - SetStatusError(tr("Document save all failed")); - } - }); - - m_menuFile->addSeparator(); - - m_actionClose = m_menuFile->addAction("&Close", [this]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(m_tabWidget->currentIndex()); - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CloseDocument, documentId); - }, QKeySequence::Close); - - m_actionCloseAll = m_menuFile->addAction("Close All", [this]() { - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CloseAllDocuments); - }); - - m_actionCloseOthers = m_menuFile->addAction("Close Others", [this]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(m_tabWidget->currentIndex()); - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CloseAllDocumentsExcept, documentId); - }); - - m_menuFile->addSeparator(); - - m_menuFile->addAction("Run &Python...", [this]() { - const QString script = QFileDialog::getOpenFileName(this, "Run Script", QString(), QString("*.py")); - if (!script.isEmpty()) - { - AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilename, script.toUtf8().constData()); - } - }); - - m_menuFile->addSeparator(); - - m_actionExit = m_menuFile->addAction("E&xit", [this]() { - close(); - }, QKeySequence::Quit); - - m_menuEdit = menuBar()->addMenu("&Edit"); - - m_actionUndo = m_menuEdit->addAction("&Undo", [this]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(m_tabWidget->currentIndex()); - bool result = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(result, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::Undo); - if (!result) - { - SetStatusError(tr("Document undo failed: %1").arg(GetDocumentPath(documentId))); - } - }, QKeySequence::Undo); - - m_actionRedo = m_menuEdit->addAction("&Redo", [this]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(m_tabWidget->currentIndex()); - bool result = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(result, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::Redo); - if (!result) - { - SetStatusError(tr("Document redo failed: %1").arg(GetDocumentPath(documentId))); - } - }, QKeySequence::Redo); - - m_menuEdit->addSeparator(); - - m_actionSettings = m_menuEdit->addAction("&Settings...", [this]() { - SettingsDialog dialog(this); - dialog.exec(); - }, QKeySequence::Preferences); - m_actionSettings->setEnabled(true); - - m_menuView = menuBar()->addMenu("&View"); - - m_actionAssetBrowser = m_menuView->addAction("&Asset Browser", [this]() { - const AZStd::string label = "Asset Browser"; - SetDockWidgetVisible(label, !IsDockWidgetVisible(label)); - }); - - m_actionInspector = m_menuView->addAction("&Inspector", [this]() { - const AZStd::string label = "Inspector"; - SetDockWidgetVisible(label, !IsDockWidgetVisible(label)); - }); - - m_actionConsole = m_menuView->addAction("&Console", [this]() { - }); - - m_actionPythonTerminal = m_menuView->addAction("Python &Terminal", [this]() { - const AZStd::string label = "Python Terminal"; - SetDockWidgetVisible(label, !IsDockWidgetVisible(label)); - }); - - m_actionPerfMonitor = m_menuView->addAction("Performance &Monitor", [this]() { - const AZStd::string label = "Performance Monitor"; - SetDockWidgetVisible(label, !IsDockWidgetVisible(label)); - }); - - m_actionViewportSettings = m_menuView->addAction("Viewport Settings", [this]() { - const AZStd::string label = "Viewport Settings"; - SetDockWidgetVisible(label, !IsDockWidgetVisible(label)); - }); - - m_menuView->addSeparator(); - - m_actionPreviousTab = m_menuView->addAction("&Previous Tab", [this]() { - SelectPreviousTab(); - }, Qt::CTRL | Qt::SHIFT | Qt::Key_Tab); //QKeySequence::PreviousChild is mapped incorrectly in Qt - - m_actionNextTab = m_menuView->addAction("&Next Tab", [this]() { - SelectNextTab(); - }, Qt::CTRL | Qt::Key_Tab); //QKeySequence::NextChild works as expected but mirroring Previous - - m_menuHelp = menuBar()->addMenu("&Help"); - - m_actionHelp = m_menuHelp->addAction("&Help...", [this]() { - HelpDialog dialog(this); - dialog.exec(); - }); - - m_actionAbout = m_menuHelp->addAction("&About...", [this]() { - }); - } - - void MaterialEditorWindow::CreateTabBar() - { - Base::CreateTabBar(); - - // This signal will be triggered whenever a tab is added, removed, selected, clicked, dragged - // When the last tab is removed tabIndex will be -1 and the document ID will be null - // This should automatically clear the active document - connect(m_tabWidget, &QTabWidget::currentChanged, this, [this](int tabIndex) { - const AZ::Uuid documentId = GetDocumentIdFromTab(tabIndex); - AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, documentId); - }); - - connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, [this](int tabIndex) { - const AZ::Uuid documentId = GetDocumentIdFromTab(tabIndex); - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CloseDocument, documentId); - }); - } - - QString MaterialEditorWindow::GetDocumentPath(const AZ::Uuid& documentId) const - { - AZStd::string absolutePath; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(absolutePath, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Handler::GetAbsolutePath); - return absolutePath.c_str(); - } - - void MaterialEditorWindow::OpenTabContextMenu() - { - const QTabBar* tabBar = m_tabWidget->tabBar(); - const QPoint position = tabBar->mapFromGlobal(QCursor::pos()); - const int clickedTabIndex = tabBar->tabAt(position); - const int currentTabIndex = tabBar->currentIndex(); - if (clickedTabIndex >= 0) - { - QMenu tabMenu; - const QString selectActionName = (currentTabIndex == clickedTabIndex) ? "Select in Browser" : "Select"; - tabMenu.addAction(selectActionName, [this, clickedTabIndex]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(clickedTabIndex); - AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, documentId); - }); - tabMenu.addAction("Close", [this, clickedTabIndex]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(clickedTabIndex); - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CloseDocument, documentId); - }); - auto closeOthersAction = tabMenu.addAction("Close Others", [this, clickedTabIndex]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(clickedTabIndex); - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CloseAllDocumentsExcept, documentId); - }); - closeOthersAction->setEnabled(tabBar->count() > 1); - tabMenu.exec(QCursor::pos()); - } + Base::closeEvent(closeEvent); } } // namespace MaterialEditor diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialEditorWindow.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialEditorWindow.h index b7d0cbf8da..bed2aa34e4 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialEditorWindow.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialEditorWindow.h @@ -9,9 +9,7 @@ #pragma once #if !defined(Q_MOC_RUN) -#include -#include -#include +#include AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT #include @@ -21,80 +19,36 @@ AZ_POP_DISABLE_WARNING namespace MaterialEditor { - /** - * MaterialEditorWindow is the main class. Its responsibility is limited to initializing and connecting - * its panels, managing selection of assets, and performing high-level actions like saving. It contains... - * 1) MaterialBrowser - The user browses for Material (.material) assets. - * 2) MaterialViewport - The user can see the selected Material applied to a model. - * 3) MaterialPropertyInspector - The user edits the properties of the selected Material. - */ + //! MaterialEditorWindow is the main class. Its responsibility is limited to initializing and connecting + //! its panels, managing selection of assets, and performing high-level actions like saving. It contains... + //! 1) MaterialBrowser - The user browses for Material (.material) assets. + //! 2) MaterialViewport - The user can see the selected Material applied to a model. + //! 3) MaterialPropertyInspector - The user edits the properties of the selected Material. class MaterialEditorWindow - : public AtomToolsFramework::AtomToolsMainWindow - , private AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler + : public AtomToolsFramework::AtomToolsDocumentMainWindow { Q_OBJECT public: AZ_CLASS_ALLOCATOR(MaterialEditorWindow, AZ::SystemAllocator, 0); - using Base = AtomToolsFramework::AtomToolsMainWindow; + using Base = AtomToolsFramework::AtomToolsDocumentMainWindow; MaterialEditorWindow(QWidget* parent = 0); - ~MaterialEditorWindow(); + ~MaterialEditorWindow() = default; - private: + protected: void ResizeViewportRenderTarget(uint32_t width, uint32_t height) override; void LockViewportRenderTargetSize(uint32_t width, uint32_t height) override; void UnlockViewportRenderTargetSize() override; - // AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler overrides... - void OnDocumentOpened(const AZ::Uuid& documentId) override; - void OnDocumentClosed(const AZ::Uuid& documentId) override; - void OnDocumentModified(const AZ::Uuid& documentId) override; - void OnDocumentUndoStateChanged(const AZ::Uuid& documentId) override; - void OnDocumentSaved(const AZ::Uuid& documentId) override; - - void CreateMenu() override; - void CreateTabBar() override; - - QString GetDocumentPath(const AZ::Uuid& documentId) const; - - void OpenTabContextMenu() override; + bool GetCreateDocumentParams(AZStd::string& openPath, AZStd::string& savePath) override; + bool GetOpenDocumentParams(AZStd::string& openPath) override; + void OpenSettings() override; + void OpenHelp() override; void closeEvent(QCloseEvent* closeEvent) override; MaterialViewportWidget* m_materialViewport = nullptr; MaterialEditorToolBar* m_toolBar = nullptr; - - QMenu* m_menuFile = {}; - QAction* m_actionNew = {}; - QAction* m_actionOpen = {}; - QAction* m_actionOpenRecent = {}; - QAction* m_actionClose = {}; - QAction* m_actionCloseAll = {}; - QAction* m_actionCloseOthers = {}; - QAction* m_actionSave = {}; - QAction* m_actionSaveAsCopy = {}; - QAction* m_actionSaveAsChild = {}; - QAction* m_actionSaveAll = {}; - QAction* m_actionExit = {}; - - QMenu* m_menuEdit = {}; - QAction* m_actionUndo = {}; - QAction* m_actionRedo = {}; - QAction* m_actionSettings = {}; - - QMenu* m_menuView = {}; - QAction* m_actionAssetBrowser = {}; - QAction* m_actionInspector = {}; - QAction* m_actionConsole = {}; - QAction* m_actionPythonTerminal = {}; - QAction* m_actionPerfMonitor = {}; - QAction* m_actionViewportSettings = {}; - QAction* m_actionNextTab = {}; - QAction* m_actionPreviousTab = {}; - - QMenu* m_menuHelp = {}; - QAction* m_actionHelp = {}; - QAction* m_actionAbout = {}; }; } // namespace MaterialEditor diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Window/ShaderManagementConsoleWindow.cpp b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Window/ShaderManagementConsoleWindow.cpp index 6354f8beba..29dafe99fe 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Window/ShaderManagementConsoleWindow.cpp +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Window/ShaderManagementConsoleWindow.cpp @@ -5,24 +5,15 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include + #include -#include -#include +#include +#include +#include +#include #include AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT -#include -#include #include #include #include @@ -32,7 +23,7 @@ AZ_POP_DISABLE_WARNING namespace ShaderManagementConsole { ShaderManagementConsoleWindow::ShaderManagementConsoleWindow(QWidget* parent /* = 0 */) - : AtomToolsFramework::AtomToolsMainWindow(parent) + : AtomToolsFramework::AtomToolsDocumentMainWindow(parent) { resize(1280, 1024); @@ -50,9 +41,6 @@ namespace ShaderManagementConsole m_toolBar->setObjectName("ToolBar"); addToolBar(m_toolBar); - CreateMenu(); - CreateTabBar(); - AddDockWidget("Asset Browser", new ShaderManagementConsoleBrowserWidget, Qt::BottomDockWidgetArea, Qt::Vertical); AddDockWidget("Python Terminal", new AzToolsFramework::CScriptTermDialog, Qt::BottomDockWidgetArea, Qt::Horizontal); @@ -61,356 +49,36 @@ namespace ShaderManagementConsole // Restore geometry and show the window mainWindowWrapper->showFromSettings(); - AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler::BusConnect(); + // Disable unused actions + m_actionNew->setVisible(false); + m_actionNew->setEnabled(false); + m_actionSaveAsChild->setVisible(false); + m_actionSaveAsChild->setEnabled(false); + OnDocumentOpened(AZ::Uuid::CreateNull()); } - ShaderManagementConsoleWindow::~ShaderManagementConsoleWindow() - { - AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler::BusDisconnect(); - } - - void ShaderManagementConsoleWindow::closeEvent(QCloseEvent* closeEvent) - { - bool didClose = true; - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::BroadcastResult(didClose, &AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CloseAllDocuments); - if (!didClose) - { - closeEvent->ignore(); - return; - } - - AtomToolsFramework::AtomToolsMainWindowNotificationBus::Broadcast( - &AtomToolsFramework::AtomToolsMainWindowNotifications::OnMainWindowClosing); - } - - void ShaderManagementConsoleWindow::OnDocumentOpened(const AZ::Uuid& documentId) - { - bool isOpen = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(isOpen, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsOpen); - bool isSavable = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(isSavable, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsSavable); - bool isModified = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(isModified, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsModified); - bool canUndo = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(canUndo, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::CanUndo); - bool canRedo = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(canRedo, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::CanRedo); - AZStd::string absolutePath; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(absolutePath, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetAbsolutePath); - AZStd::string filename; - AzFramework::StringFunc::Path::GetFullFileName(absolutePath.c_str(), filename); - - // Update UI to display the new document - if (!documentId.IsNull() && isOpen) - { - // Create a new tab for the document ID and assign it's label to the file name of the document. - AddTabForDocumentId(documentId, filename, absolutePath, [this, documentId]{ - // The document tab contains a table view. - auto contentWidget = new QTableView(centralWidget()); - contentWidget->setSelectionBehavior(QAbstractItemView::SelectRows); - contentWidget->setModel(CreateDocumentContent(documentId)); - return contentWidget; - }); - } - - UpdateTabForDocumentId(documentId, filename, absolutePath, isModified); - - const bool hasTabs = m_tabWidget->count() > 0; - - // Update menu options - m_actionOpen->setEnabled(true); - m_actionOpenRecent->setEnabled(false); - m_actionClose->setEnabled(hasTabs); - m_actionCloseAll->setEnabled(hasTabs); - m_actionCloseOthers->setEnabled(hasTabs); - - m_actionSave->setEnabled(isOpen && isSavable); - m_actionSaveAsCopy->setEnabled(isOpen && isSavable); - m_actionSaveAll->setEnabled(hasTabs); - - m_actionExit->setEnabled(true); - - m_actionUndo->setEnabled(canUndo); - m_actionRedo->setEnabled(canRedo); - m_actionSettings->setEnabled(false); - - m_actionAssetBrowser->setEnabled(true); - m_actionPythonTerminal->setEnabled(true); - m_actionPreviousTab->setEnabled(m_tabWidget->count() > 1); - m_actionNextTab->setEnabled(m_tabWidget->count() > 1); - - m_actionHelp->setEnabled(false); - m_actionAbout->setEnabled(false); - - activateWindow(); - raise(); - - const QString documentPath = GetDocumentPath(documentId); - if (!documentPath.isEmpty()) - { - SetStatusMessage(tr("Document opened: %1").arg(documentPath)); - } - } - - void ShaderManagementConsoleWindow::OnDocumentClosed(const AZ::Uuid& documentId) - { - RemoveTabForDocumentId(documentId); - SetStatusMessage(tr("Document closed: %1").arg(GetDocumentPath(documentId))); - } - - void ShaderManagementConsoleWindow::OnDocumentModified(const AZ::Uuid& documentId) - { - bool isModified = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(isModified, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsModified); - AZStd::string absolutePath; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(absolutePath, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetAbsolutePath); - AZStd::string filename; - AzFramework::StringFunc::Path::GetFullFileName(absolutePath.c_str(), filename); - UpdateTabForDocumentId(documentId, filename, absolutePath, isModified); - } - - void ShaderManagementConsoleWindow::OnDocumentUndoStateChanged(const AZ::Uuid& documentId) - { - if (documentId == GetDocumentIdFromTab(m_tabWidget->currentIndex())) - { - bool canUndo = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(canUndo, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::CanUndo); - bool canRedo = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(canRedo, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::CanRedo); - m_actionUndo->setEnabled(canUndo); - m_actionRedo->setEnabled(canRedo); - } - } - - void ShaderManagementConsoleWindow::OnDocumentSaved(const AZ::Uuid& documentId) - { - bool isModified = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(isModified, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsModified); - AZStd::string absolutePath; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(absolutePath, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetAbsolutePath); - AZStd::string filename; - AzFramework::StringFunc::Path::GetFullFileName(absolutePath.c_str(), filename); - UpdateTabForDocumentId(documentId, filename, absolutePath, isModified); - SetStatusMessage(tr("Document saved: %1").arg(GetDocumentPath(documentId))); - } - - void ShaderManagementConsoleWindow::CreateMenu() - { - Base::CreateMenu(); - - // Generating the main menu manually because it's easier and we will have some dynamic or data driven entries - m_menuFile = menuBar()->addMenu("&File"); - - m_actionOpen = m_menuFile->addAction("&Open...", [this]() { - const AZStd::vector assetTypes = { - }; - - const AZStd::string filePath = AtomToolsFramework::GetOpenFileInfo(assetTypes).absoluteFilePath().toUtf8().constData(); - if (!filePath.empty()) - { - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::OpenDocument, filePath); - } - }, QKeySequence::Open); - - m_actionOpenRecent = m_menuFile->addAction("Open &Recent"); - - m_menuFile->addSeparator(); - - m_actionSave = m_menuFile->addAction("&Save", [this]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(m_tabWidget->currentIndex()); - bool result = false; - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::BroadcastResult(result, &AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::SaveDocument, documentId); - if (!result) - { - SetStatusError(tr("Document save failed: %1").arg(GetDocumentPath(documentId))); - } - }, QKeySequence::Save); - - m_actionSaveAsCopy = m_menuFile->addAction("Save &As...", [this]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(m_tabWidget->currentIndex()); - const QString documentPath = GetDocumentPath(documentId); - - bool result = false; - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::BroadcastResult(result, &AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::SaveDocumentAsCopy, - documentId, AtomToolsFramework::GetSaveFileInfo(documentPath).absoluteFilePath().toUtf8().constData()); - if (!result) - { - SetStatusError(tr("Document save failed: %1").arg(GetDocumentPath(documentId))); - } - }, QKeySequence::SaveAs); - - m_actionSaveAll = m_menuFile->addAction("Save A&ll", [this]() { - bool result = false; - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::BroadcastResult(result, &AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::SaveAllDocuments); - if (!result) - { - SetStatusError(tr("Document save all failed")); - } - }); - - m_menuFile->addSeparator(); - - m_actionClose = m_menuFile->addAction("&Close", [this]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(m_tabWidget->currentIndex()); - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CloseDocument, documentId); - }, QKeySequence::Close); - - m_actionCloseAll = m_menuFile->addAction("Close All", [this]() { - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CloseAllDocuments); - }); - - m_actionCloseOthers = m_menuFile->addAction("Close Others", [this]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(m_tabWidget->currentIndex()); - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CloseAllDocumentsExcept, documentId); - }); - - m_menuFile->addSeparator(); - - m_menuFile->addAction("Run &Python...", [this]() { - const QString script = QFileDialog::getOpenFileName(this, "Run Script", QString(), QString("*.py")); - if (!script.isEmpty()) - { - AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilename, script.toUtf8().constData()); - } - }); - - m_menuFile->addSeparator(); - - m_actionExit = m_menuFile->addAction("E&xit", [this]() { - close(); - }, QKeySequence::Quit); - - m_menuEdit = menuBar()->addMenu("&Edit"); - - m_actionUndo = m_menuEdit->addAction("&Undo", [this]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(m_tabWidget->currentIndex()); - bool result = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(result, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::Undo); - if (!result) - { - SetStatusError(tr("Document undo failed: %1").arg(GetDocumentPath(documentId))); - } - }, QKeySequence::Undo); - - m_actionRedo = m_menuEdit->addAction("&Redo", [this]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(m_tabWidget->currentIndex()); - bool result = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(result, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::Redo); - if (!result) - { - SetStatusError(tr("Document redo failed: %1").arg(GetDocumentPath(documentId))); - } - }, QKeySequence::Redo); - - m_menuEdit->addSeparator(); - - m_actionSettings = m_menuEdit->addAction("&Settings...", [this]() { - }, QKeySequence::Preferences); - m_actionSettings->setEnabled(false); - - m_menuView = menuBar()->addMenu("&View"); - - m_actionAssetBrowser = m_menuView->addAction("&Asset Browser", [this]() { - const AZStd::string label = "Asset Browser"; - SetDockWidgetVisible(label, !IsDockWidgetVisible(label)); - }); - - m_actionPythonTerminal = m_menuView->addAction("Python &Terminal", [this]() { - const AZStd::string label = "Python Terminal"; - SetDockWidgetVisible(label, !IsDockWidgetVisible(label)); - }); - - - m_menuView->addSeparator(); - - m_actionPreviousTab = m_menuView->addAction("&Previous Tab", [this]() { - SelectPreviousTab(); - }, Qt::CTRL | Qt::SHIFT | Qt::Key_Tab); //QKeySequence::PreviousChild is mapped incorrectly in Qt - - m_actionNextTab = m_menuView->addAction("&Next Tab", [this]() { - SelectNextTab(); - }, Qt::CTRL | Qt::Key_Tab); //QKeySequence::NextChild works as expected but mirroring Previous - - m_menuHelp = menuBar()->addMenu("&Help"); - - m_actionHelp = m_menuHelp->addAction("&Help...", [this]() { - }); - - m_actionAbout = m_menuHelp->addAction("&About...", [this]() { - }); - } - - void ShaderManagementConsoleWindow::CreateTabBar() - { - Base::CreateTabBar(); - - // This signal will be triggered whenever a tab is added, removed, selected, clicked, dragged - // When the last tab is removed tabIndex will be -1 and the document ID will be null - // This should automatically clear the active document - connect(m_tabWidget, &QTabWidget::currentChanged, this, [this](int tabIndex) { - const AZ::Uuid documentId = GetDocumentIdFromTab(tabIndex); - AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, documentId); - }); - - connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, [this](int tabIndex) { - const AZ::Uuid documentId = GetDocumentIdFromTab(tabIndex); - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CloseDocument, documentId); - }); - } - - QString ShaderManagementConsoleWindow::GetDocumentPath(const AZ::Uuid& documentId) const - { - AZStd::string absolutePath; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(absolutePath, documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Handler::GetAbsolutePath); - return absolutePath.c_str(); - } - - void ShaderManagementConsoleWindow::OpenTabContextMenu() - { - const QTabBar* tabBar = m_tabWidget->tabBar(); - const QPoint position = tabBar->mapFromGlobal(QCursor::pos()); - const int clickedTabIndex = tabBar->tabAt(position); - const int currentTabIndex = tabBar->currentIndex(); - if (clickedTabIndex >= 0) - { - QMenu tabMenu; - const QString selectActionName = (currentTabIndex == clickedTabIndex) ? "Select in Browser" : "Select"; - tabMenu.addAction(selectActionName, [this, clickedTabIndex]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(clickedTabIndex); - AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, documentId); - }); - tabMenu.addAction("Close", [this, clickedTabIndex]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(clickedTabIndex); - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CloseDocument, documentId); - }); - auto closeOthersAction = tabMenu.addAction("Close Others", [this, clickedTabIndex]() { - const AZ::Uuid documentId = GetDocumentIdFromTab(clickedTabIndex); - AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CloseAllDocumentsExcept, documentId); - }); - closeOthersAction->setEnabled(tabBar->count() > 1); - tabMenu.exec(QCursor::pos()); - } - } - - QStandardItemModel* ShaderManagementConsoleWindow::CreateDocumentContent(const AZ::Uuid& documentId) + QWidget* ShaderManagementConsoleWindow::CreateDocumentTabView(const AZ::Uuid& documentId) { AZStd::unordered_set optionNames; size_t shaderOptionCount = 0; - ShaderManagementConsoleDocumentRequestBus::EventResult(shaderOptionCount, documentId, &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderOptionCount); + ShaderManagementConsoleDocumentRequestBus::EventResult( + shaderOptionCount, documentId, &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderOptionCount); for (size_t optionIndex = 0; optionIndex < shaderOptionCount; ++optionIndex) { AZ::RPI::ShaderOptionDescriptor shaderOptionDesc; - ShaderManagementConsoleDocumentRequestBus::EventResult(shaderOptionDesc, documentId, &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderOptionDescriptor, optionIndex); + ShaderManagementConsoleDocumentRequestBus::EventResult( + shaderOptionDesc, documentId, &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderOptionDescriptor, optionIndex); const char* optionName = shaderOptionDesc.GetName().GetCStr(); optionNames.insert(optionName); } size_t shaderVariantCount = 0; - ShaderManagementConsoleDocumentRequestBus::EventResult(shaderVariantCount, documentId, &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderVariantCount); + ShaderManagementConsoleDocumentRequestBus::EventResult( + shaderVariantCount, documentId, &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderVariantCount); auto model = new QStandardItemModel(); model->setRowCount(static_cast(shaderVariantCount)); @@ -425,7 +93,8 @@ namespace ShaderManagementConsole for (int variantIndex = 0; variantIndex < shaderVariantCount; ++variantIndex) { AZ::RPI::ShaderVariantListSourceData::VariantInfo shaderVariantInfo; - ShaderManagementConsoleDocumentRequestBus::EventResult(shaderVariantInfo, documentId, &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderVariantInfo, variantIndex); + ShaderManagementConsoleDocumentRequestBus::EventResult( + shaderVariantInfo, documentId, &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderVariantInfo, variantIndex); model->setHeaderData(variantIndex, Qt::Vertical, QString::number(variantIndex)); @@ -442,7 +111,11 @@ namespace ShaderManagementConsole } } - return model; + // The document tab contains a table view. + auto contentWidget = new QTableView(centralWidget()); + contentWidget->setSelectionBehavior(QAbstractItemView::SelectRows); + contentWidget->setModel(model); + return contentWidget; } } // namespace ShaderManagementConsole diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Window/ShaderManagementConsoleWindow.h b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Window/ShaderManagementConsoleWindow.h index 2b682f6c09..3ba122674a 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Window/ShaderManagementConsoleWindow.h +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Window/ShaderManagementConsoleWindow.h @@ -11,9 +11,7 @@ #if !defined(Q_MOC_RUN) #include #include -#include -#include -#include +#include AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT #include @@ -25,69 +23,23 @@ AZ_POP_DISABLE_WARNING namespace ShaderManagementConsole { - /** - * ShaderManagementConsoleWindow is the main class. Its responsibility is limited to initializing and connecting - * its panels, managing selection of assets, and performing high-level actions like saving. It contains... - */ + //! ShaderManagementConsoleWindow is the main class. Its responsibility is limited to initializing and connecting + //! its panels, managing selection of assets, and performing high-level actions like saving. It contains... class ShaderManagementConsoleWindow - : public AtomToolsFramework::AtomToolsMainWindow - , private AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler + : public AtomToolsFramework::AtomToolsDocumentMainWindow { Q_OBJECT public: AZ_CLASS_ALLOCATOR(ShaderManagementConsoleWindow, AZ::SystemAllocator, 0); - using Base = AtomToolsFramework::AtomToolsMainWindow; + using Base = AtomToolsFramework::AtomToolsDocumentMainWindow; ShaderManagementConsoleWindow(QWidget* parent = 0); - ~ShaderManagementConsoleWindow(); + ~ShaderManagementConsoleWindow() = default; - private: - // AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler overrides... - void OnDocumentOpened(const AZ::Uuid& documentId) override; - void OnDocumentClosed(const AZ::Uuid& documentId) override; - void OnDocumentModified(const AZ::Uuid& documentId) override; - void OnDocumentUndoStateChanged(const AZ::Uuid& documentId) override; - void OnDocumentSaved(const AZ::Uuid& documentId) override; - - void CreateMenu() override; - void CreateTabBar() override; - - QString GetDocumentPath(const AZ::Uuid& documentId) const; - - void OpenTabContextMenu() override; - - void closeEvent(QCloseEvent* closeEvent) override; - - QStandardItemModel* CreateDocumentContent(const AZ::Uuid& documentId); + protected: + QWidget* CreateDocumentTabView(const AZ::Uuid& documentId) override; ShaderManagementConsoleToolBar* m_toolBar = nullptr; - - QMenu* m_menuFile = {}; - QMenu* m_menuNew = {}; - QAction* m_actionOpen = {}; - QAction* m_actionOpenRecent = {}; - QAction* m_actionClose = {}; - QAction* m_actionCloseAll = {}; - QAction* m_actionCloseOthers = {}; - QAction* m_actionSave = {}; - QAction* m_actionSaveAsCopy = {}; - QAction* m_actionSaveAll = {}; - QAction* m_actionExit = {}; - - QMenu* m_menuEdit = {}; - QAction* m_actionUndo = {}; - QAction* m_actionRedo = {}; - QAction* m_actionSettings = {}; - - QMenu* m_menuView = {}; - QAction* m_actionAssetBrowser = {}; - QAction* m_actionPythonTerminal = {}; - QAction* m_actionNextTab = {}; - QAction* m_actionPreviousTab = {}; - - QMenu* m_menuHelp = {}; - QAction* m_actionHelp = {}; - QAction* m_actionAbout = {}; }; } // namespace ShaderManagementConsole diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Mesh/MeshComponentBus.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Mesh/MeshComponentBus.h index ff75f7f45a..cc9c78d356 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Mesh/MeshComponentBus.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Mesh/MeshComponentBus.h @@ -36,9 +36,18 @@ namespace AZ virtual void SetSortKey(RHI::DrawItemSortKey sortKey) = 0; virtual RHI::DrawItemSortKey GetSortKey() const = 0; + virtual void SetLodType(RPI::Cullable::LodType lodType) = 0; + virtual RPI::Cullable::LodType GetLodType() const = 0; + virtual void SetLodOverride(RPI::Cullable::LodOverride lodOverride) = 0; virtual RPI::Cullable::LodOverride GetLodOverride() const = 0; + virtual void SetMinimumScreenCoverage(float minimumScreenCoverage) = 0; + virtual float GetMinimumScreenCoverage() const = 0; + + virtual void SetQualityDecayRate(float qualityDecayRate) = 0; + virtual float GetQualityDecayRate() const = 0; + virtual void SetVisibility(bool visible) = 0; virtual bool GetVisibility() const = 0; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp index 4b3322c442..3324bdfa8d 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp @@ -71,16 +71,36 @@ namespace AZ "MeshComponentConfig", "") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->DataElement(AZ::Edit::UIHandlers::Default, &MeshComponentConfig::m_modelAsset, "Mesh Asset", "Mesh asset reference") - ->DataElement(AZ::Edit::UIHandlers::Default, &MeshComponentConfig::m_sortKey, "Sort Key", "Transparent meshes are drawn by sort key then depth. Used this to force certain transparent meshes to draw before or after others.") - ->DataElement(AZ::Edit::UIHandlers::ComboBox, &MeshComponentConfig::m_lodOverride, "Lod Override", "Allows the rendered LOD to be overridden instead of being calculated automatically.") - ->Attribute(AZ::Edit::Attributes::EnumValues, &MeshComponentConfig::GetLodOverrideValues) - ->Attribute(AZ::Edit::Attributes::Visibility, &MeshComponentConfig::IsAssetSet) - ->DataElement(AZ::Edit::UIHandlers::CheckBox, &MeshComponentConfig::m_excludeFromReflectionCubeMaps, "Exclude from reflection cubemaps", "Mesh will not be visible in baked reflection probe cubemaps") - ->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) - ->DataElement(AZ::Edit::UIHandlers::CheckBox, &MeshComponentConfig::m_useForwardPassIblSpecular, "Use Forward Pass IBL Specular", - "Renders IBL specular reflections in the forward pass, using only the most influential probe (based on the position of the entity) and the global IBL cubemap. Can reduce rendering costs, but only recommended for static objects that are affected by at most one reflection probe.") - ->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + ->DataElement(AZ::Edit::UIHandlers::Default, &MeshComponentConfig::m_modelAsset, "Mesh Asset", "Mesh asset reference") + ->DataElement(AZ::Edit::UIHandlers::Default, &MeshComponentConfig::m_sortKey, "Sort Key", "Transparent meshes are drawn by sort key then depth. Used this to force certain transparent meshes to draw before or after others.") + ->Attribute(AZ::Edit::Attributes::Visibility, &MeshComponentConfig::IsAssetSet) + ->DataElement(AZ::Edit::UIHandlers::CheckBox, &MeshComponentConfig::m_excludeFromReflectionCubeMaps, "Exclude from reflection cubemaps", "Mesh will not be visible in baked reflection probe cubemaps") + ->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + ->DataElement(AZ::Edit::UIHandlers::CheckBox, &MeshComponentConfig::m_useForwardPassIblSpecular, "Use Forward Pass IBL Specular", + "Renders IBL specular reflections in the forward pass, using only the most influential probe (based on the position of the entity) and the global IBL cubemap. Can reduce rendering costs, but only recommended for static objects that are affected by at most one reflection probe.") + ->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + ->DataElement(AZ::Edit::UIHandlers::ComboBox, &MeshComponentConfig::m_lodType, "Lod Type", "Lod Method.") + ->EnumAttribute(RPI::Cullable::LodType::Default, "Default") + ->EnumAttribute(RPI::Cullable::LodType::ScreenCoverage, "Screen Coverage") + ->EnumAttribute(RPI::Cullable::LodType::SpecificLod, "Specific Lod") + ->Attribute(AZ::Edit::Attributes::Visibility, &MeshComponentConfig::IsAssetSet) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::EntireTree) + ->ClassElement(AZ::Edit::ClassElements::Group, "Lod Configuration") + ->Attribute(AZ::Edit::Attributes::AutoExpand, false) + ->Attribute(AZ::Edit::Attributes::Visibility, &MeshComponentConfig::ShowLodConfig) + ->DataElement(AZ::Edit::UIHandlers::ComboBox, &MeshComponentConfig::m_lodOverride, "Lod Override", "Allows the rendered LOD to be overridden instead of being calculated automatically.") + ->Attribute(AZ::Edit::Attributes::EnumValues, &MeshComponentConfig::GetLodOverrideValues) + ->Attribute(AZ::Edit::Attributes::Visibility, &MeshComponentConfig::LodTypeIsSpecificLOD) + ->DataElement(AZ::Edit::UIHandlers::Slider, &MeshComponentConfig::m_minimumScreenCoverage, "Minimum Screen Coverage", "Minimum proportion of screen area an entitiy takes up, after that the entitiy is culled.") + ->Attribute(AZ::Edit::Attributes::Min, 0.f) + ->Attribute(AZ::Edit::Attributes::Max, 1.f) + ->Attribute(AZ::Edit::Attributes::Suffix, " percent") + ->Attribute(AZ::Edit::Attributes::Visibility, &MeshComponentConfig::LodTypeIsScreenCoverage) + ->DataElement(AZ::Edit::UIHandlers::Slider, &MeshComponentConfig::m_qualityDecayRate, "Quality Decay Rate", + "Rate at which mesh quality decays (0 -> always stay highest quality, 1 -> quality falls off to lowest quality immediately).") + ->Attribute(AZ::Edit::Attributes::Min, 0.f) + ->Attribute(AZ::Edit::Attributes::Max, 1.f) + ->Attribute(AZ::Edit::Attributes::Visibility, &MeshComponentConfig::LodTypeIsScreenCoverage) ; } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp index bb38f932fb..ade4beaa33 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp @@ -31,18 +31,40 @@ namespace AZ { namespace Render { + + namespace MeshComponentControllerVersionUtility + { + bool VersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement) + { + if (classElement.GetVersion() < 2) + { + RPI::Cullable::LodOverride lodOverride = aznumeric_cast(classElement.FindElement(AZ_CRC("LodOverride"))); + static constexpr uint8_t old_NoLodOverride = AZStd::numeric_limits ::max(); + if (lodOverride == old_NoLodOverride) + { + classElement.AddElementWithData(context, "LodType", RPI::Cullable::LodType::SpecificLod); + } + } + return true; + } + } // namespace MeshComponentControllerVersionUtility + void MeshComponentConfig::Reflect(ReflectContext* context) { if (auto* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(1) + ->Version(2, &MeshComponentControllerVersionUtility::VersionConverter) ->Field("ModelAsset", &MeshComponentConfig::m_modelAsset) ->Field("SortKey", &MeshComponentConfig::m_sortKey) - ->Field("LodOverride", &MeshComponentConfig::m_lodOverride) ->Field("ExcludeFromReflectionCubeMaps", &MeshComponentConfig::m_excludeFromReflectionCubeMaps) - ->Field("UseForwardPassIBLSpecular", &MeshComponentConfig::m_useForwardPassIblSpecular); + ->Field("UseForwardPassIBLSpecular", &MeshComponentConfig::m_useForwardPassIblSpecular) + ->Field("LodType", &MeshComponentConfig::m_lodType) + ->Field("LodOverride", &MeshComponentConfig::m_lodOverride) + ->Field("MinimumScreenCoverage", &MeshComponentConfig::m_minimumScreenCoverage) + ->Field("QualityDecayRate", &MeshComponentConfig::m_qualityDecayRate); } + } bool MeshComponentConfig::IsAssetSet() @@ -50,6 +72,21 @@ namespace AZ return m_modelAsset.GetId().IsValid(); } + bool MeshComponentConfig::LodTypeIsScreenCoverage() + { + return m_lodType == RPI::Cullable::LodType::ScreenCoverage; + } + + bool MeshComponentConfig::LodTypeIsSpecificLOD() + { + return m_lodType == RPI::Cullable::LodType::SpecificLod; + } + + bool MeshComponentConfig::ShowLodConfig() + { + return LodTypeIsScreenCoverage() && LodTypeIsSpecificLOD(); + } + AZStd::vector> MeshComponentConfig::GetLodOverrideValues() { AZStd::vector> values; @@ -72,9 +109,9 @@ namespace AZ } values.reserve(lodCount + 1); - values.push_back({ RPI::Cullable::NoLodOverride, "Not Set" }); + values.push_back({ aznumeric_cast(0), "Default (Highest)" }); - for (uint32_t i = 0; i < lodCount; ++i) + for (uint32_t i = 1; i < lodCount; ++i) { AZStd::string enumDescription = AZStd::string::format("Lod %i", i); values.push_back({ aznumeric_cast(i), enumDescription.c_str() }); @@ -102,7 +139,12 @@ namespace AZ if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) { - behaviorContext->ConstantProperty("NoLodOverride", BehaviorConstant(RPI::Cullable::NoLodOverride)) + behaviorContext->ConstantProperty("DefaultLodOverride", BehaviorConstant(0)) + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Category, "render") + ->Attribute(AZ::Script::Attributes::Module, "render"); + + behaviorContext->ConstantProperty("DefaultLodType", BehaviorConstant(RPI::Cullable::LodType::Default)) ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) ->Attribute(AZ::Script::Attributes::Category, "render") ->Attribute(AZ::Script::Attributes::Module, "render"); @@ -114,12 +156,21 @@ namespace AZ ->Event("SetModelAssetPath", &MeshComponentRequestBus::Events::SetModelAssetPath) ->Event("SetSortKey", &MeshComponentRequestBus::Events::SetSortKey) ->Event("GetSortKey", &MeshComponentRequestBus::Events::GetSortKey) + ->Event("SetLodType", &MeshComponentRequestBus::Events::SetLodType) + ->Event("GetLodType", &MeshComponentRequestBus::Events::GetLodType) ->Event("SetLodOverride", &MeshComponentRequestBus::Events::SetLodOverride) ->Event("GetLodOverride", &MeshComponentRequestBus::Events::GetLodOverride) + ->Event("SetMinimumScreenCoverage", &MeshComponentRequestBus::Events::SetMinimumScreenCoverage) + ->Event("GetMinimumScreenCoverage", &MeshComponentRequestBus::Events::GetMinimumScreenCoverage) + ->Event("SetQualityDecayRate", &MeshComponentRequestBus::Events::SetQualityDecayRate) + ->Event("GetQualityDecayRate", &MeshComponentRequestBus::Events::GetQualityDecayRate) ->VirtualProperty("ModelAssetId", "GetModelAssetId", "SetModelAssetId") ->VirtualProperty("ModelAssetPath", "GetModelAssetPath", "SetModelAssetPath") ->VirtualProperty("SortKey", "GetSortKey", "SetSortKey") + ->VirtualProperty("LodType", "GetLodType", "SetLodType") ->VirtualProperty("LodOverride", "GetLodOverride", "SetLodOverride") + ->VirtualProperty("MinimumScreenCoverage", "GetMinimumScreenCoverage", "SetMinimumScreenCoverage") + ->VirtualProperty("QualityDecayRate", "GetQualityDecayRate", "SetQualityDecayRate") ; } } @@ -343,7 +394,7 @@ namespace AZ m_meshFeatureProcessor->SetTransform(m_meshHandle, transform, m_cachedNonUniformScale); m_meshFeatureProcessor->SetSortKey(m_meshHandle, m_configuration.m_sortKey); - m_meshFeatureProcessor->SetLodOverride(m_meshHandle, m_configuration.m_lodOverride); + m_meshFeatureProcessor->SetMeshLodConfiguration(m_meshHandle, GetMeshLodConfiguration()); m_meshFeatureProcessor->SetExcludeFromReflectionCubeMaps(m_meshHandle, m_configuration.m_excludeFromReflectionCubeMaps); m_meshFeatureProcessor->SetVisible(m_meshHandle, m_isVisible); @@ -434,15 +485,66 @@ namespace AZ return m_meshFeatureProcessor->GetSortKey(m_meshHandle); } + RPI::Cullable::LodConfiguration MeshComponentController::GetMeshLodConfiguration() const + { + return { + m_configuration.m_lodType, + m_configuration.m_lodOverride, + m_configuration.m_minimumScreenCoverage, + m_configuration.m_qualityDecayRate + }; + } + // ----------------------- + void MeshComponentController::SetLodType(RPI::Cullable::LodType lodType) + { + RPI::Cullable::LodConfiguration lodConfig = GetMeshLodConfiguration(); + lodConfig.m_lodType = lodType; + m_meshFeatureProcessor->SetMeshLodConfiguration(m_meshHandle, lodConfig); + } + + RPI::Cullable::LodType MeshComponentController::GetLodType() const + { + RPI::Cullable::LodConfiguration lodConfig = m_meshFeatureProcessor->GetMeshLodConfiguration(m_meshHandle); + return lodConfig.m_lodType; + } + void MeshComponentController::SetLodOverride(RPI::Cullable::LodOverride lodOverride) { - m_configuration.m_lodOverride = lodOverride; // Save for serialization - m_meshFeatureProcessor->SetLodOverride(m_meshHandle, lodOverride); + RPI::Cullable::LodConfiguration lodConfig = GetMeshLodConfiguration(); + lodConfig.m_lodOverride = lodOverride; + m_meshFeatureProcessor->SetMeshLodConfiguration(m_meshHandle, lodConfig); } RPI::Cullable::LodOverride MeshComponentController::GetLodOverride() const { - return static_cast(m_meshFeatureProcessor->GetSortKey(m_meshHandle)); + RPI::Cullable::LodConfiguration lodConfig = m_meshFeatureProcessor->GetMeshLodConfiguration(m_meshHandle); + return lodConfig.m_lodOverride; + } + + void MeshComponentController::SetMinimumScreenCoverage(float minimumScreenCoverage) + { + RPI::Cullable::LodConfiguration lodConfig = GetMeshLodConfiguration(); + lodConfig.m_minimumScreenCoverage = minimumScreenCoverage; + m_meshFeatureProcessor->SetMeshLodConfiguration(m_meshHandle, lodConfig); + } + + float MeshComponentController::GetMinimumScreenCoverage() const + { + RPI::Cullable::LodConfiguration lodConfig = m_meshFeatureProcessor->GetMeshLodConfiguration(m_meshHandle); + return lodConfig.m_minimumScreenCoverage; + } + + void MeshComponentController::SetQualityDecayRate(float qualityDecayRate) + { + RPI::Cullable::LodConfiguration lodConfig = GetMeshLodConfiguration(); + lodConfig.m_qualityDecayRate = qualityDecayRate; + m_meshFeatureProcessor->SetMeshLodConfiguration(m_meshHandle, lodConfig); + } + + float MeshComponentController::GetQualityDecayRate() const + { + RPI::Cullable::LodConfiguration lodConfig = m_meshFeatureProcessor->GetMeshLodConfiguration(m_meshHandle); + return lodConfig.m_qualityDecayRate; } void MeshComponentController::SetVisibility(bool visible) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h index 63dba83fef..2c7cc78979 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h @@ -30,6 +30,9 @@ namespace AZ { namespace Render { + + + //! A configuration structure for the MeshComponentController class MeshComponentConfig final : public AZ::ComponentConfig @@ -41,13 +44,20 @@ namespace AZ // Editor helper functions bool IsAssetSet(); + bool LodTypeIsScreenCoverage(); + bool LodTypeIsSpecificLOD(); + bool ShowLodConfig(); AZStd::vector> GetLodOverrideValues(); Data::Asset m_modelAsset = { AZ::Data::AssetLoadBehavior::QueueLoad }; RHI::DrawItemSortKey m_sortKey = 0; - RPI::Cullable::LodOverride m_lodOverride = RPI::Cullable::NoLodOverride; bool m_excludeFromReflectionCubeMaps = false; bool m_useForwardPassIblSpecular = false; + + RPI::Cullable::LodType m_lodType = RPI::Cullable::LodType::Default; + RPI::Cullable::LodOverride m_lodOverride = aznumeric_cast(0); + float m_minimumScreenCoverage = 1.0f / 1080.0f; + float m_qualityDecayRate = 0.5f; }; class MeshComponentController final @@ -94,8 +104,17 @@ namespace AZ void SetSortKey(RHI::DrawItemSortKey sortKey) override; RHI::DrawItemSortKey GetSortKey() const override; - void SetLodOverride(RPI::Cullable::LodOverride lodOverride) override; - RPI::Cullable::LodOverride GetLodOverride() const override; + void SetLodType(RPI::Cullable::LodType lodType) override; + RPI::Cullable::LodType GetLodType() const override; + + virtual void SetLodOverride(RPI::Cullable::LodOverride lodOverride); + virtual RPI::Cullable::LodOverride GetLodOverride() const; + + virtual void SetMinimumScreenCoverage(float minimumScreenCoverage); + virtual float GetMinimumScreenCoverage() const; + + virtual void SetQualityDecayRate(float qualityDecayRate); + virtual float GetQualityDecayRate() const; void SetVisibility(bool visible) override; bool GetVisibility() const override; @@ -132,6 +151,8 @@ namespace AZ void UnregisterModel(); void RefreshModelRegistration(); + RPI::Cullable::LodConfiguration GetMeshLodConfiguration() const; + void HandleNonUniformScaleChange(const AZ::Vector3& nonUniformScale); Render::MeshFeatureProcessorInterface* m_meshFeatureProcessor = nullptr; diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp index e85c2b92e7..16243235a2 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp @@ -426,15 +426,53 @@ namespace AZ { return m_meshFeatureProcessor->GetSortKey(*m_meshHandle); } + + void AtomActorInstance::SetLodType(RPI::Cullable::LodType lodType) + { + RPI::Cullable::LodConfiguration config = m_meshFeatureProcessor->GetMeshLodConfiguration(*m_meshHandle); + config.m_lodType = lodType; + m_meshFeatureProcessor->SetMeshLodConfiguration(*m_meshHandle, config); + } + + RPI::Cullable::LodType AtomActorInstance::GetLodType() const + { + return m_meshFeatureProcessor->GetMeshLodConfiguration(*m_meshHandle).m_lodType; + } void AtomActorInstance::SetLodOverride(RPI::Cullable::LodOverride lodOverride) { - m_meshFeatureProcessor->SetLodOverride(*m_meshHandle, lodOverride); + RPI::Cullable::LodConfiguration config = m_meshFeatureProcessor->GetMeshLodConfiguration(*m_meshHandle); + config.m_lodOverride = lodOverride; + m_meshFeatureProcessor->SetMeshLodConfiguration(*m_meshHandle, config); } RPI::Cullable::LodOverride AtomActorInstance::GetLodOverride() const { - return m_meshFeatureProcessor->GetLodOverride(*m_meshHandle); + return m_meshFeatureProcessor->GetMeshLodConfiguration(*m_meshHandle).m_lodOverride; + } + + void AtomActorInstance::SetMinimumScreenCoverage(float minimumScreenCoverage) + { + RPI::Cullable::LodConfiguration config = m_meshFeatureProcessor->GetMeshLodConfiguration(*m_meshHandle); + config.m_minimumScreenCoverage = minimumScreenCoverage; + m_meshFeatureProcessor->SetMeshLodConfiguration(*m_meshHandle, config); + } + + float AtomActorInstance::GetMinimumScreenCoverage() const + { + return m_meshFeatureProcessor->GetMeshLodConfiguration(*m_meshHandle).m_minimumScreenCoverage; + } + + void AtomActorInstance::SetQualityDecayRate(float qualityDecayRate) + { + RPI::Cullable::LodConfiguration config = m_meshFeatureProcessor->GetMeshLodConfiguration(*m_meshHandle); + config.m_qualityDecayRate = qualityDecayRate; + m_meshFeatureProcessor->SetMeshLodConfiguration(*m_meshHandle, config); + } + + float AtomActorInstance::GetQualityDecayRate() const + { + return m_meshFeatureProcessor->GetMeshLodConfiguration(*m_meshHandle).m_qualityDecayRate; } void AtomActorInstance::SetVisibility(bool visible) diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h index b3e1a8a989..a74cc46e65 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h @@ -141,8 +141,14 @@ namespace AZ AZ::Data::Instance GetModel() const override; void SetSortKey(RHI::DrawItemSortKey sortKey) override; RHI::DrawItemSortKey GetSortKey() const override; + void SetLodType(RPI::Cullable::LodType lodType) override; + RPI::Cullable::LodType GetLodType() const override; void SetLodOverride(RPI::Cullable::LodOverride lodOverride) override; RPI::Cullable::LodOverride GetLodOverride() const override; + void SetMinimumScreenCoverage(float minimumScreenCoverage) override; + float GetMinimumScreenCoverage() const override; + void SetQualityDecayRate(float qualityDecayRate) override; + float GetQualityDecayRate() const override; void SetVisibility(bool visible) override; bool GetVisibility() const override; // GetWorldBounds/GetLocalBounds already overridden by BoundsRequestBus::Handler diff --git a/Gems/LmbrCentral/Code/Source/Audio/AudioProxyComponent.cpp b/Gems/LmbrCentral/Code/Source/Audio/AudioProxyComponent.cpp index 8be8aa042e..659fff55d9 100644 --- a/Gems/LmbrCentral/Code/Source/Audio/AudioProxyComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Audio/AudioProxyComponent.cpp @@ -39,7 +39,7 @@ namespace LmbrCentral ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/AudioProxy.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AddableByUser, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/audio-proxy/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/audio/proxy/") ; } } diff --git a/Gems/LmbrCentral/Code/Source/Audio/EditorAudioAreaEnvironmentComponent.cpp b/Gems/LmbrCentral/Code/Source/Audio/EditorAudioAreaEnvironmentComponent.cpp index 2b4741331f..7861244ea4 100644 --- a/Gems/LmbrCentral/Code/Source/Audio/EditorAudioAreaEnvironmentComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Audio/EditorAudioAreaEnvironmentComponent.cpp @@ -40,7 +40,7 @@ namespace LmbrCentral ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/AudioAreaEnvironment.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/audio-area-environment/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/audio/area-environment/") ->DataElement(AZ::Edit::UIHandlers::Default, &EditorAudioAreaEnvironmentComponent::m_broadPhaseTriggerArea, "Broad-phase trigger area", "The entity that contains a Trigger Area component for broad-phase checks") ->Attribute(AZ::Edit::Attributes::RequiredService, AZ_CRC("ProximityTriggerService", 0x561f262c)) diff --git a/Gems/LmbrCentral/Code/Source/Audio/EditorAudioEnvironmentComponent.cpp b/Gems/LmbrCentral/Code/Source/Audio/EditorAudioEnvironmentComponent.cpp index b4dd5c4d74..b2633a9cb0 100644 --- a/Gems/LmbrCentral/Code/Source/Audio/EditorAudioEnvironmentComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Audio/EditorAudioEnvironmentComponent.cpp @@ -33,7 +33,7 @@ namespace LmbrCentral ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/AudioEnvironment.png") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/audio-environment/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/audio/environment/") ->DataElement("AudioControl", &EditorAudioEnvironmentComponent::m_defaultEnvironment, "Default Environment", "Name of the default ATL Environment control to use") ; } diff --git a/Gems/LmbrCentral/Code/Source/Audio/EditorAudioListenerComponent.cpp b/Gems/LmbrCentral/Code/Source/Audio/EditorAudioListenerComponent.cpp index 205267db73..c9f80ed398 100644 --- a/Gems/LmbrCentral/Code/Source/Audio/EditorAudioListenerComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Audio/EditorAudioListenerComponent.cpp @@ -36,7 +36,7 @@ namespace LmbrCentral ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/AudioListener.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/audio-listener/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/audio/listener/") ->DataElement(AZ::Edit::UIHandlers::Default, &EditorAudioListenerComponent::m_rotationEntity, "Rotation Entity", "The Entity whose rotation the audio listener will adopt. If none set, will assume 'this' Entity") ->DataElement(AZ::Edit::UIHandlers::Default, &EditorAudioListenerComponent::m_positionEntity, diff --git a/Gems/LmbrCentral/Code/Source/Audio/EditorAudioMultiPositionComponent.cpp b/Gems/LmbrCentral/Code/Source/Audio/EditorAudioMultiPositionComponent.cpp index 987fa6e0e4..51386b1c02 100644 --- a/Gems/LmbrCentral/Code/Source/Audio/EditorAudioMultiPositionComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Audio/EditorAudioMultiPositionComponent.cpp @@ -38,7 +38,7 @@ namespace LmbrCentral ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/AudioMultiPosition.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - // Followup: Need Help URL + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/audio/multi-position/") ->DataElement(AZ::Edit::UIHandlers::Default, &EditorAudioMultiPositionComponent::m_entityRefs, "Entity References", "The entities from which positions will be obtained for multi-position audio") ->DataElement(AZ::Edit::UIHandlers::ComboBox, &EditorAudioMultiPositionComponent::m_behaviorType, "Behavior Type", "Determines how multi-postion sounds are treated, Separate or Blended") ; diff --git a/Gems/LmbrCentral/Code/Source/Audio/EditorAudioPreloadComponent.cpp b/Gems/LmbrCentral/Code/Source/Audio/EditorAudioPreloadComponent.cpp index a5d137c1b2..a24acac292 100644 --- a/Gems/LmbrCentral/Code/Source/Audio/EditorAudioPreloadComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Audio/EditorAudioPreloadComponent.cpp @@ -43,7 +43,7 @@ namespace LmbrCentral // Icon todo: //->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/AudioPreload.png") - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/audio-preload/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/audio/preload/") ->DataElement("AudioControl", &EditorAudioPreloadComponent::m_defaultPreload, "Preload Name", "The default ATL Preload control to use") ->DataElement(AZ::Edit::UIHandlers::ComboBox, &EditorAudioPreloadComponent::m_loadType, "Load Type", "Automatically when the component activates/deactivates, or Manually at user's request") diff --git a/Gems/LmbrCentral/Code/Source/Audio/EditorAudioRtpcComponent.cpp b/Gems/LmbrCentral/Code/Source/Audio/EditorAudioRtpcComponent.cpp index ab98692a50..4426c21ff6 100644 --- a/Gems/LmbrCentral/Code/Source/Audio/EditorAudioRtpcComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Audio/EditorAudioRtpcComponent.cpp @@ -34,7 +34,7 @@ namespace LmbrCentral ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/AudioRtpc.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/audio-rtpc/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/audio/rtpc/") ->DataElement("AudioControl", &EditorAudioRtpcComponent::m_defaultRtpc, "Default Rtpc", "The default ATL Rtpc control to use") ; } diff --git a/Gems/LmbrCentral/Code/Source/Audio/EditorAudioSwitchComponent.cpp b/Gems/LmbrCentral/Code/Source/Audio/EditorAudioSwitchComponent.cpp index 6ea89ee31e..1aafbd5c21 100644 --- a/Gems/LmbrCentral/Code/Source/Audio/EditorAudioSwitchComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Audio/EditorAudioSwitchComponent.cpp @@ -34,7 +34,7 @@ namespace LmbrCentral ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/AudioSwitch.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/audio-switch/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/audio/switch/") ->DataElement("AudioControl", &EditorAudioSwitchComponent::m_defaultSwitch, "Default Switch", "The default ATL Switch to use when Activated") ->DataElement("AudioControl", &EditorAudioSwitchComponent::m_defaultState, "Default State", "The default ATL State to set on the default Switch when Activated") ; diff --git a/Gems/LmbrCentral/Code/Source/Audio/EditorAudioTriggerComponent.cpp b/Gems/LmbrCentral/Code/Source/Audio/EditorAudioTriggerComponent.cpp index e38caf303b..adecf3c4d0 100644 --- a/Gems/LmbrCentral/Code/Source/Audio/EditorAudioTriggerComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Audio/EditorAudioTriggerComponent.cpp @@ -43,7 +43,7 @@ namespace LmbrCentral ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/AudioTrigger.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/audio-trigger/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/audio/trigger/") ->DataElement("AudioControl", &EditorAudioTriggerComponent::m_defaultPlayTrigger, "Default 'play' Trigger", "The default ATL Trigger control used by 'Play'") ->DataElement("AudioControl", &EditorAudioTriggerComponent::m_defaultStopTrigger, "Default 'stop' Trigger", "The default ATL Trigger control used by 'Stop'") ->DataElement(AZ::Edit::UIHandlers::ComboBox, &EditorAudioTriggerComponent::m_obstructionType, "Obstruction Type", "Ray-casts used in calculation of obstruction and occlusion") diff --git a/Gems/PhysX/Code/Source/EditorColliderComponent.cpp b/Gems/PhysX/Code/Source/EditorColliderComponent.cpp index 2445aba9bd..c337ab0a85 100644 --- a/Gems/PhysX/Code/Source/EditorColliderComponent.cpp +++ b/Gems/PhysX/Code/Source/EditorColliderComponent.cpp @@ -122,7 +122,10 @@ namespace PhysX if (m_shapeType != Physics::ShapeType::PhysicsAsset && m_lastShapeType == Physics::ShapeType::PhysicsAsset) { + //clean up any reference to a physics assets, and re-initialize to an empty Pipeline::MeshAsset asset. m_physicsAsset.m_pxAsset.Reset(); + m_physicsAsset.m_pxAsset = AZ::Data::Asset(AZ::Data::AssetLoadBehavior::QueueLoad); + m_physicsAsset.m_configuration = Physics::PhysicsAssetShapeConfiguration(); } m_lastShapeType = m_shapeType; diff --git a/Gems/Vegetation/Code/Tests/VegetationMocks.h b/Gems/Vegetation/Code/Tests/VegetationMocks.h index 37bacef21a..ebc51d1b3b 100644 --- a/Gems/Vegetation/Code/Tests/VegetationMocks.h +++ b/Gems/Vegetation/Code/Tests/VegetationMocks.h @@ -554,6 +554,16 @@ namespace UnitTest return m_drawItemSortKeyOutput; } + AZ::RPI::Cullable::LodType m_lodTypeOutput; + void SetLodType(AZ::RPI::Cullable::LodType lodType) override + { + m_lodTypeOutput = lodType; + } + AZ::RPI::Cullable::LodType GetLodType() const override + { + return m_lodTypeOutput; + } + AZ::RPI::Cullable::LodOverride m_lodOverrideOutput; void SetLodOverride(AZ::RPI::Cullable::LodOverride lodOverride) override { @@ -563,6 +573,26 @@ namespace UnitTest { return m_lodOverrideOutput; } + + float m_minimumScreenCoverageOutput; + void SetMinimumScreenCoverage(float minimumScreenCoverage) override + { + m_minimumScreenCoverageOutput = minimumScreenCoverage; + } + float GetMinimumScreenCoverage() const override + { + return m_minimumScreenCoverageOutput; + } + + float m_qualityDecayRateOutput; + void SetQualityDecayRate(float qualityDecayRate) override + { + m_qualityDecayRateOutput = qualityDecayRate; + } + float GetQualityDecayRate() const override + { + return m_qualityDecayRateOutput; + } }; struct MockTransformBus