diff --git a/Code/Framework/AzCore/AzCore/RTTI/ReflectContext.h b/Code/Framework/AzCore/AzCore/RTTI/ReflectContext.h index e03823888a..5b27cb9f88 100644 --- a/Code/Framework/AzCore/AzCore/RTTI/ReflectContext.h +++ b/Code/Framework/AzCore/AzCore/RTTI/ReflectContext.h @@ -274,7 +274,7 @@ namespace AZ { using Callable = AZStd::conditional_t::value, AZStd::function::function_type>, Invocable>; public: - AZ_RTTI((AttributeInvocable, "{60D5804F-9AF4-4EB1-8F5A-62AFB4883F9D}"), AZ::Attribute); + AZ_RTTI((AttributeInvocable, "{60D5804F-9AF4-4EB1-8F5A-62AFB4883F9D}", Invocable), AZ::Attribute); AZ_CLASS_ALLOCATOR(AttributeInvocable, SystemAllocator, 0); template explicit AttributeInvocable(CallableType&& invocable) diff --git a/Code/Framework/AzCore/Tests/Serialization.cpp b/Code/Framework/AzCore/Tests/Serialization.cpp index 036942f9dd..4dba972324 100644 --- a/Code/Framework/AzCore/Tests/Serialization.cpp +++ b/Code/Framework/AzCore/Tests/Serialization.cpp @@ -2010,6 +2010,28 @@ TEST_F(SerializeBasicTest, BasicTypeTest_Succeed) } + + /* + This test will dynamic cast (azrtti_cast) between incompatible types, which should always result in nullptr. + If this test fails, the RTTI declaration for the relevant type is incorrect. + */ + TEST_F(Serialization, AttributeRTTI) + { + { + AttributeInvocable> fn([](AZStd::string x) { return x + x; }); + Attribute* fnDownCast = &fn; + auto fnUpCast = azrtti_cast>*>(fnDownCast); + EXPECT_EQ(fnUpCast, nullptr); + } + + { + AttributeFunction fn([](AZStd::string x) { return x + x; }); + Attribute* fnDownCast = &fn; + auto fnUpCast = azrtti_cast*>(fnDownCast); + EXPECT_EQ(fnUpCast, nullptr); + } + } + /* * Deprecation */ diff --git a/Code/Sandbox/Editor/ViewportTitleDlg.cpp b/Code/Sandbox/Editor/ViewportTitleDlg.cpp index 48075597cd..2c4b29773d 100644 --- a/Code/Sandbox/Editor/ViewportTitleDlg.cpp +++ b/Code/Sandbox/Editor/ViewportTitleDlg.cpp @@ -181,7 +181,8 @@ void CViewportTitleDlg::SetupCameraDropdownMenu() auto comboBoxTextChanged = static_cast(&QComboBox::currentTextChanged); SetSpeedComboBox(cameraMoveSpeed); - m_cameraSpeed->setInsertPolicy(QComboBox::NoInsert); + m_cameraSpeed->setInsertPolicy(QComboBox::InsertAtBottom); + m_cameraSpeed->setDuplicatesEnabled(false); connect(m_cameraSpeed, comboBoxTextChanged, this, &CViewportTitleDlg::OnUpdateMoveSpeedText); connect(m_cameraSpeed->lineEdit(), &QLineEdit::returnPressed, this, &CViewportTitleDlg::OnSpeedComboBoxEnter); diff --git a/Code/Sandbox/Editor/ViewportTitleDlg.h b/Code/Sandbox/Editor/ViewportTitleDlg.h index 255354dcbb..7978277ce0 100644 --- a/Code/Sandbox/Editor/ViewportTitleDlg.h +++ b/Code/Sandbox/Editor/ViewportTitleDlg.h @@ -117,11 +117,11 @@ protected: // Speed combobox/lineEdit settings double m_minSpeed = 0.01; double m_maxSpeed = 100.0; - double m_speedStep = 0.01; + double m_speedStep = 0.001; int m_numDecimals = 3; // Speed presets - float m_speedPresetValues[3] = { 0.1f, 1.0f, 10.0f }; + float m_speedPresetValues[4] = { 0.01f, 0.1f, 1.0f, 10.0f }; double m_fieldWidthMultiplier = 1.8; diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvertJob.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvertJob.cpp index c6f0d3946a..fab8d47885 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvertJob.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvertJob.cpp @@ -109,14 +109,7 @@ namespace ImageProcessingAtom while (!m_process->IsFinished() && !IsJobCancelled()) { m_process->UpdateProcess(); - if (m_isPreview) - { - m_output->SetProgress(m_process->GetProgress() / static_cast(m_previewProcessStep)); - } - else - { - m_output->SetProgress(m_process->GetProgress()); - } + m_output->SetProgress(m_process->GetProgress() / static_cast(m_previewProcessStep)); } IImageObjectPtr outputImage = m_process->GetOutputImage(); @@ -125,7 +118,7 @@ namespace ImageProcessingAtom m_output->SetOutputImage(outputImage, ImageConvertOutput::Base); m_output->SetOutputImage(outputImageAlpha, ImageConvertOutput::Alpha); - if (m_isPreview && !IsJobCancelled()) + if (!IsJobCancelled()) { // For preview, combine image output with alpha if any m_output->SetProgress(1.0f / static_cast(m_previewProcessStep)); diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvertJob.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvertJob.h index a0cf3ad488..17e6191314 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvertJob.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvertJob.h @@ -72,7 +72,6 @@ namespace ImageProcessingAtom static const int m_previewProcessStep = 2; AZStd::unique_ptr m_process; - bool m_isPreview; AZStd::atomic_bool m_isCancelled; ImageConvertOutput* m_output; }; 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 0d61ef82d1..24c0658c61 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 @@ -95,6 +95,8 @@ namespace AZ TransformServiceFeatureProcessorInterface::ObjectId m_objectId; + Aabb m_aabb = Aabb::CreateNull(); + bool m_cullBoundsNeedsUpdate = false; bool m_cullableNeedsRebuild = false; bool m_objectSrgNeedsUpdate = true; @@ -160,6 +162,9 @@ namespace AZ Transform GetTransform(const MeshHandle& meshHandle) override; Vector3 GetNonUniformScale(const MeshHandle& meshHandle) override; + void SetLocalAabb(const MeshHandle& meshHandle, const AZ::Aabb& localAabb) override; + AZ::Aabb GetLocalAabb(const MeshHandle& meshHandle) const override; + void SetSortKey(const MeshHandle& meshHandle, RHI::DrawItemSortKey sortKey) override; RHI::DrawItemSortKey GetSortKey(const MeshHandle& meshHandle) 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 fb5bff5584..fdc6ea3cc8 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 @@ -86,6 +86,10 @@ namespace AZ virtual Transform GetTransform(const MeshHandle& meshHandle) = 0; //! Gets the non-uniform scale for a given mesh handle. virtual Vector3 GetNonUniformScale(const MeshHandle& meshHandle) = 0; + //! Sets the local space bbox for a given mesh handle. You don't need to call this for static models, only skinned/animated models + virtual void SetLocalAabb(const MeshHandle& meshHandle, const AZ::Aabb& localAabb) = 0; + //! Gets the local space bbox for a given mesh handle. Unless SetLocalAabb has been called before, this will be the bbox of the model asset + virtual AZ::Aabb GetLocalAabb(const MeshHandle& meshHandle) const = 0; //! 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. diff --git a/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h index 418ee0cfb8..e6635906e9 100644 --- a/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h @@ -33,6 +33,8 @@ namespace UnitTest MOCK_METHOD2(SetMaterialAssignmentMap, void(const MeshHandle&, const AZ::Render::MaterialAssignmentMap&)); MOCK_METHOD1(GetTransform, AZ::Transform(const MeshHandle&)); MOCK_METHOD1(GetNonUniformScale, AZ::Vector3(const MeshHandle&)); + 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)); diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index b087269089..6fe6cf1f88 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -304,6 +304,30 @@ namespace AZ } } + void MeshFeatureProcessor::SetLocalAabb(const MeshHandle& meshHandle, const AZ::Aabb& localAabb) + { + if (meshHandle.IsValid()) + { + MeshDataInstance& meshData = *meshHandle; + meshData.m_aabb = localAabb; + meshData.m_cullBoundsNeedsUpdate = true; + meshData.m_objectSrgNeedsUpdate = true; + } + }; + + AZ::Aabb MeshFeatureProcessor::GetLocalAabb(const MeshHandle& meshHandle) const + { + if (meshHandle.IsValid()) + { + return meshHandle->m_aabb; + } + else + { + AZ_Assert(false, "Invalid mesh handle"); + return Aabb::CreateNull(); + } + } + Transform MeshFeatureProcessor::GetTransform(const MeshHandle& meshHandle) { if (meshHandle.IsValid()) @@ -603,6 +627,8 @@ namespace AZ SetRayTracingData(); } + m_aabb = model->GetModelAsset()->GetAabb(); + m_cullableNeedsRebuild = true; m_cullBoundsNeedsUpdate = true; m_objectSrgNeedsUpdate = true; @@ -996,7 +1022,7 @@ namespace AZ RPI::Cullable::CullData& cullData = m_cullable.m_cullData; RPI::Cullable::LodData& lodData = m_cullable.m_lodData; - const Aabb& localAabb = m_model->GetAabb(); + const Aabb& localAabb = m_aabb; lodData.m_lodSelectionRadius = 0.5f*localAabb.GetExtents().GetMaxElement(); const size_t modelLodCount = m_model->GetLodCount(); @@ -1077,7 +1103,7 @@ namespace AZ Vector3 center; float radius; - Aabb localAabb = m_model->GetAabb(); + Aabb localAabb = m_aabb; localAabb.MultiplyByScale(nonUniformScale); localAabb.GetTransformedAabb(localToWorld).GetAsSphere(center, radius); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/Model.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/Model.h index 514e3e37a5..b403a86f00 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/Model.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/Model.h @@ -32,6 +32,7 @@ namespace AZ : public Data::InstanceData { friend class ModelSystem; + public: AZ_INSTANCE_DATA(Model, "{C30F5522-B381-4B38-BBAF-6E0B1885C8B9}"); AZ_CLASS_ALLOCATOR(Model, AZ::SystemAllocator, 0); @@ -53,8 +54,6 @@ namespace AZ //! Returns whether a buffer upload is pending. bool IsUploadPending() const; - const AZ::Aabb& GetAabb() const; - const Data::Asset& GetModelAsset() const; //! Checks a ray for intersection against this model. The ray must be in the same coordinate space as the model. @@ -105,8 +104,6 @@ namespace AZ // Tracks whether buffers have all been streamed up to the GPU. bool m_isUploadPending = false; - - AZ::Aabb m_aabb; }; } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp index 17ff2c64c8..8809225150 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp @@ -62,8 +62,6 @@ namespace AZ { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); - m_aabb = modelAsset.GetAabb(); - m_lods.resize(modelAsset.GetLodAssets().size()); for (size_t lodIndex = 0; lodIndex < m_lods.size(); ++lodIndex) @@ -127,11 +125,6 @@ namespace AZ return m_isUploadPending; } - const AZ::Aabb& Model::GetAabb() const - { - return m_aabb; - } - const Data::Asset& Model::GetModelAsset() const { return m_modelAsset; @@ -140,9 +133,16 @@ namespace AZ bool Model::LocalRayIntersection(const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); + + if (!GetModelAsset()) + { + AZ_Assert(false, "Invalid Model - not created from a ModelAsset?"); + return false; + } + float start; float end; - const int result = Intersect::IntersectRayAABB2(rayStart, rayDir.GetReciprocal(), m_aabb, start, end); + const int result = Intersect::IntersectRayAABB2(rayStart, rayDir.GetReciprocal(), GetModelAsset()->GetAabb(), start, end); if (Intersect::ISECT_RAY_AABB_NONE != result) { if (ModelAsset* modelAssetPtr = m_modelAsset.Get()) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLodUtils.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLodUtils.cpp index b213e84bca..a4e5d1ba10 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLodUtils.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLodUtils.cpp @@ -49,7 +49,7 @@ namespace AZ With that percentage we can determine which Lod we want to use. */ - Aabb modelAabb = model.GetAabb(); + Aabb modelAabb = model.GetModelAsset()->GetAabb(); modelAabb.Translate(position); Vector3 center; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp index e7eecd3c7f..90d3763067 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp @@ -460,10 +460,10 @@ namespace AZ Aabb MeshComponentController::GetLocalBounds() { - const Data::Instance model = GetModel(); - if (model) + if (m_meshHandle.IsValid() && m_meshFeatureProcessor) { - Aabb aabb = model->GetAabb(); + Aabb aabb = m_meshFeatureProcessor->GetLocalAabb(m_meshHandle); + aabb.MultiplyByScale(m_cachedNonUniformScale); return aabb; } diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp index 532c8720b5..ff4b82ce92 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp @@ -83,12 +83,20 @@ namespace AZ void AtomActorInstance::UpdateBounds() { // Update RenderActorInstance world bounding box - // The bounding box is moving with the actor instance. It is static in the way that it does not change shape. + // The bounding box is moving with the actor instance. // The entity and actor transforms are kept in sync already. m_worldAABB = AZ::Aabb::CreateFromMinMax(m_actorInstance->GetAABB().GetMin(), m_actorInstance->GetAABB().GetMax()); // Update RenderActorInstance local bounding box - m_localAABB = AZ::Aabb::CreateFromMinMax(m_actorInstance->GetStaticBasedAABB().GetMin(), m_actorInstance->GetStaticBasedAABB().GetMax()); + // NB: computing the local bbox from the world bbox makes the local bbox artifically larger than it should be + // instead EMFX should support getting the local bbox from the actor instance directly + m_localAABB = m_worldAABB.GetTransformedAabb(m_transformInterface->GetWorldTM().GetInverse()); + + // Update bbox on mesh instance if it exists + if (m_meshFeatureProcessor && m_meshHandle && m_meshHandle->IsValid() && m_skinnedMeshInstance) + { + m_meshFeatureProcessor->SetLocalAabb(*m_meshHandle, m_localAABB); + } AZ::Interface::Get()->RefreshEntityLocalBoundsUnion(m_entityId); } diff --git a/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp index b0065708fb..52977c9920 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp @@ -57,14 +57,68 @@ namespace EMotionFX } }; + ////////////////////////////////////////////////////////////////////////// + void ActorComponent::BoundingBoxConfiguration::Set(ActorInstance* actor) const + { + if (m_autoUpdateBounds) + { + actor->SetupAutoBoundsUpdate(m_updateTimeFrequency, m_boundsType, m_updateItemFrequency); + } + else + { + actor->SetBoundsUpdateType(m_boundsType); + actor->SetBoundsUpdateEnabled(false); + } + } + + void ActorComponent::BoundingBoxConfiguration::SetAndUpdate(ActorInstance* actor) const + { + Set(actor); + const AZ::u32 freq = actor->GetBoundsUpdateEnabled() ? actor->GetBoundsUpdateItemFrequency() : 1; + actor->UpdateBounds(0, actor->GetBoundsUpdateType(), freq); + } + + void ActorComponent::BoundingBoxConfiguration::Reflect(AZ::ReflectContext * context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(2, [](AZ::SerializeContext& sc, AZ::SerializeContext::DataElementNode& node) + { + if (node.GetVersion() < 2) + { + // m_boundsType used to be an enum class with `int' underlying type, is now `u8' + static const char* m_boundsType_name = "m_boundsType"; + static AZ::Crc32 m_boundsType_nameCrc(m_boundsType_name); + + int m_boundsType_as_int; + if (!node.GetChildData(m_boundsType_nameCrc, m_boundsType_as_int)) + { + return false; + } + if (!node.RemoveElementByName(m_boundsType_nameCrc)) return false; + if (node.AddElementWithData(sc, m_boundsType_name, (AZ::u8)m_boundsType_as_int) == -1) return false; + } + return true; + }) + ->Field("m_boundsType", &BoundingBoxConfiguration::m_boundsType) + ->Field("m_autoUpdateBounds", &BoundingBoxConfiguration::m_autoUpdateBounds) + ->Field("m_updateTimeFrequency", &BoundingBoxConfiguration::m_updateTimeFrequency) + ->Field("m_updateItemFrequency", &BoundingBoxConfiguration::m_updateItemFrequency) + ; + } + } + ////////////////////////////////////////////////////////////////////////// void ActorComponent::Configuration::Reflect(AZ::ReflectContext* context) { + BoundingBoxConfiguration::Reflect(context); + auto* serializeContext = azrtti_cast(context); if (serializeContext) { serializeContext->Class() - ->Version(3) + ->Version(4) ->Field("ActorAsset", &Configuration::m_actorAsset) ->Field("MaterialPerLOD", &Configuration::m_materialPerLOD) ->Field("RenderSkeleton", &Configuration::m_renderSkeleton) @@ -74,6 +128,7 @@ namespace EMotionFX ->Field("AttachmentTarget", &Configuration::m_attachmentTarget) ->Field("SkinningMethod", &Configuration::m_skinningMethod) ->Field("LODLevel", &Configuration::m_lodLevel) + ->Field("BoundingBoxConfig", &Configuration::m_bboxConfig) ->Field("ForceJointsUpdateOOV", &Configuration::m_forceUpdateJointsOOV) ; } @@ -349,6 +404,8 @@ namespace EMotionFX AZ::TransformNotificationBus::MultiHandler::BusConnect(GetEntityId()); m_actorInstance->UpdateWorldTransform(); + // Set bounds update mode and compute bbox first time + m_configuration.m_bboxConfig.SetAndUpdate(m_actorInstance.get()); m_actorInstance->UpdateBounds(0, ActorInstance::EBoundsType::BOUNDS_STATIC_BASED); // Creating the render actor AFTER both actor asset and mesh asset loaded. diff --git a/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.h b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.h index 4172ce63bc..b3e02f0cbd 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.h +++ b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.h @@ -49,6 +49,26 @@ namespace EMotionFX AZ_COMPONENT(ActorComponent, "{BDC97E7F-A054-448B-A26F-EA2B5D78E377}"); friend class EditorActorComponent; + struct BoundingBoxConfiguration + { + AZ_TYPE_INFO(BoundingBoxConfiguration, "{EBCFF975-00A5-4578-85C7-59909F52067C}"); + + BoundingBoxConfiguration() = default; + + EMotionFX::ActorInstance::EBoundsType m_boundsType = EMotionFX::ActorInstance::BOUNDS_STATIC_BASED; + bool m_autoUpdateBounds = true; + float m_updateTimeFrequency = 0.f; + AZ::u32 m_updateItemFrequency = 1; + + // Set the bounding box configuration of the given actor instance to the parameters given by `this'. The actor instance must not be null (this is not checked). + void Set(ActorInstance* inst) const; + + // Set the bounding box configuration, then update the bounds of the actor instance + void SetAndUpdate(ActorInstance* inst) const; + + static void Reflect(AZ::ReflectContext* context); + }; + /** * Configuration struct for procedural configuration of Actor Components. */ @@ -71,6 +91,7 @@ namespace EMotionFX // default, joints level update (beside the root joint) on // actor are disabled when the actor is out of view. bool m_forceUpdateJointsOOV = false; + BoundingBoxConfiguration m_bboxConfig; ///< Configuration for bounding box type and updates static void Reflect(AZ::ReflectContext* context); }; diff --git a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp index 5c085e96f7..157e8c0136 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp @@ -58,11 +58,49 @@ namespace EMotionFX ->Field("SkinningMethod", &EditorActorComponent::m_skinningMethod) ->Field("UpdateJointTransformsWhenOutOfView", &EditorActorComponent::m_forceUpdateJointsOOV) ->Field("LodLevel", &EditorActorComponent::m_lodLevel) + ->Field("BBoxConfig", &EditorActorComponent::m_bboxConfig) ; AZ::EditContext* editContext = serializeContext->GetEditContext(); if (editContext) { + editContext->Class("Actor Bounding Box Config", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + + ->DataElement(AZ::Edit::UIHandlers::ComboBox, &ActorComponent::BoundingBoxConfiguration::m_boundsType, + "Bounds type", + "The method used to compute the Actor bounding box. NOTE: ordered by least expensive to compute to most expensive to compute." + ) + ->EnumAttribute(ActorInstance::BOUNDS_STATIC_BASED, "Static bounds (source-asset bounds)") + ->EnumAttribute(ActorInstance::BOUNDS_NODE_BASED, "Bone position-based") + ->EnumAttribute(ActorInstance::BOUNDS_NODEOBB_BASED, "Bone local bounding box-based") + ->EnumAttribute(ActorInstance::BOUNDS_MESH_BASED, "Render mesh vertex position-based (VERY EXPENSIVE)") + + ->DataElement(0, &ActorComponent::BoundingBoxConfiguration::m_autoUpdateBounds, + "Automatically update bounds?", + "If true, bounds are automatically updated based on some frequency. Otherwise bounds are computed only at creation or when triggered manually" + ) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues) + + ->DataElement(0, &ActorComponent::BoundingBoxConfiguration::m_updateTimeFrequency, + "Update frequency", + "How often to update bounds automatically" + ) + ->Attribute(AZ::Edit::Attributes::Suffix, " Hz") + ->Attribute(AZ::Edit::Attributes::Min, 0.f) + ->Attribute(AZ::Edit::Attributes::Step, 0.001f) + ->Attribute(AZ::Edit::Attributes::Visibility, &ActorComponent::BoundingBoxConfiguration::m_autoUpdateBounds) + + ->DataElement(0, &ActorComponent::BoundingBoxConfiguration::m_updateItemFrequency, + "Update item skip factor", + "How many items (bones or vertices) to skip when automatically updating bounds." + "
i.e. =1 uses every single item, =2 uses every 2nd item, =3 uses every 3rd item... " + ) + ->Attribute(AZ::Edit::Attributes::Suffix, " items") + ->Attribute(AZ::Edit::Attributes::Min, (AZ::u32)1) + ->Attribute(AZ::Edit::Attributes::Visibility, &ActorComponent::BoundingBoxConfiguration::m_autoUpdateBounds) + ; + editContext->Class("Actor", "The Actor component manages an instance of an Actor") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "Animation") @@ -118,6 +156,9 @@ namespace EMotionFX ->DataElement(0, &EditorActorComponent::m_forceUpdateJointsOOV, "Force update joints", "Force update the joint transforms of actor, even when the character is out of the camera view.") + ->DataElement(0, &EditorActorComponent::m_bboxConfig, + "Bounding box configuration", "") + ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorActorComponent::OnBBoxConfigChanged) ; } } @@ -433,6 +474,14 @@ namespace EMotionFX return refreshLevel; } + void EditorActorComponent::OnBBoxConfigChanged() + { + if (m_actorInstance) + { + m_bboxConfig.SetAndUpdate(m_actorInstance.get()); + } + } + void EditorActorComponent::LaunchAnimationEditor(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType&) { // call to open must be done before LoadCharacter @@ -571,6 +620,7 @@ namespace EMotionFX cfg.m_attachmentJointIndex = m_attachmentJointIndex; cfg.m_lodLevel = m_lodLevel; cfg.m_skinningMethod = m_skinningMethod; + cfg.m_bboxConfig = m_bboxConfig; cfg.m_forceUpdateJointsOOV = m_forceUpdateJointsOOV; gameEntity->AddComponent(aznew ActorComponent(&cfg)); @@ -846,6 +896,7 @@ namespace EMotionFX // Force an update of node transforms so we can get an accurate bounding box. m_actorInstance->UpdateTransformations(0.0f, true, false); + OnBBoxConfigChanged(); // Apply BBox config // Creating the render actor AFTER both actor asset and mesh asset loaded. RenderBackend* renderBackend = AZ::Interface::Get()->GetRenderBackend(); @@ -898,4 +949,3 @@ namespace EMotionFX } } //namespace Integration } // namespace EMotionFX - diff --git a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.h b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.h index 5dabbe4ada..30bf0d405e 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.h +++ b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.h @@ -121,6 +121,7 @@ namespace EMotionFX AZ::Crc32 OnAttachmentTypeChanged(); AZ::Crc32 OnAttachmentTargetChanged(); AZ::Crc32 OnAttachmentTargetJointSelect(); + void OnBBoxConfigChanged(); bool AttachmentTargetVisibility(); bool AttachmentTargetJointVisibility(); AZStd::string AttachmentJointButtonText(); @@ -163,6 +164,7 @@ namespace EMotionFX AZStd::string m_attachmentJointName; ///< Joint name on target to which to attach (if ActorAttachment). AZ::u32 m_attachmentJointIndex; AZ::u32 m_lodLevel; + ActorComponent::BoundingBoxConfiguration m_bboxConfig; bool m_forceUpdateJointsOOV = false; // \todo attachmentTarget node nr diff --git a/Gems/Multiplayer/Code/CMakeLists.txt b/Gems/Multiplayer/Code/CMakeLists.txt index 430fe5ca4b..992d39c418 100644 --- a/Gems/Multiplayer/Code/CMakeLists.txt +++ b/Gems/Multiplayer/Code/CMakeLists.txt @@ -123,6 +123,8 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) BUILD_DEPENDENCIES PRIVATE Gem::Multiplayer.Builders.Static + RUNTIME_DEPENDENCIES + Gem::Multiplayer.Editor ) ly_add_target(