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