Expose Actor bounding box configurations to component serialize and edit contexts. (#491)

* Expose Actor bounding box configurations to component serialize and edit contexts.

Allows one, for example, to have an animation which moves the character far away from the static bounds, and not have that character dissapear when the static bounds are outside of the camera frustum

* Apply suggestions from code review

* Bug fix: name the parameter, place comments in the intended place.
This commit is contained in:
yuriy0
2021-06-14 21:28:50 -04:00
committed by GitHub
parent 88765bdbb5
commit e22d21f4b6
4 changed files with 132 additions and 2 deletions
@@ -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<AZ::SerializeContext*>(context))
{
serializeContext->Class<BoundingBoxConfiguration>()
->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<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<Configuration>()
->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.
@@ -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);
};
@@ -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<ActorComponent::BoundingBoxConfiguration>("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."
" <br> 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<EditorActorComponent>("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<RenderBackendManager>::Get()->GetRenderBackend();
@@ -898,4 +949,3 @@ namespace EMotionFX
}
} //namespace Integration
} // namespace EMotionFX
@@ -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