You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/Vegetation/Code/Source/Components/AreaComponentBase.cpp

291 lines
12 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Vegetation/AreaComponentBase.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <Vegetation/Ebuses/AreaSystemRequestBus.h>
#include <AzCore/Debug/Profiler.h>
namespace Vegetation
{
namespace AreaUtil
{
static bool UpdateVersion(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
{
if (classElement.GetVersion() < 1)
{
AZ::u8 areaType = 0; //default to cluster
if (classElement.GetChildData(AZ_CRC("AreaType", 0x5365f66f), areaType))
{
classElement.RemoveElementByName(AZ_CRC("AreaType", 0x5365f66f));
switch (areaType)
{
default:
case 0: //cluster
classElement.AddElementWithData(context, "Layer", AreaConstants::s_foregroundLayer);
break;
case 1: //coverage
classElement.AddElementWithData(context, "Layer", AreaConstants::s_backgroundLayer);
break;
}
}
int priority = 1;
if (classElement.GetChildData(AZ_CRC("Priority", 0x62a6dc27), priority))
{
classElement.RemoveElementByName(AZ_CRC("Priority", 0x62a6dc27));
classElement.AddElementWithData(context, "Priority", (float)(priority - 1) / (float)std::numeric_limits<int>::max());
}
}
if (classElement.GetVersion() < 2)
{
float priority = 0.0f;
if (classElement.GetChildData(AZ_CRC("Priority", 0x62a6dc27), priority))
{
priority = AZ::GetClamp(priority, 0.0f, 1.0f);
const AZ::u32 convertedPriority = (AZ::u32)(priority * AreaConstants::s_prioritySoftMax); //using soft max accommodate slider range and int/float conversion
classElement.RemoveElementByName(AZ_CRC("Priority", 0x62a6dc27));
classElement.AddElementWithData(context, "Priority", convertedPriority);
}
}
return true;
}
}
void AreaConfig::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<AreaConfig, AZ::ComponentConfig>()
->Version(2, &AreaUtil::UpdateVersion)
->Field("Layer", &AreaConfig::m_layer)
->Field("Priority", &AreaConfig::m_priority)
;
AZ::EditContext* edit = serialize->GetEditContext();
if (edit)
{
edit->Class<AreaConfig>(
"Vegetation Area", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &AreaConfig::m_layer, "Layer Priority", "Defines a high level order vegetation areas are applied")
->Attribute(AZ::Edit::Attributes::EnumValues, &AreaConfig::GetSelectableLayers)
->DataElement(AZ::Edit::UIHandlers::Slider, &AreaConfig::m_priority, "Sub Priority", "Defines order vegetation areas are applied within a layer. Larger numbers = higher priority")
->Attribute(AZ::Edit::Attributes::Min, AreaConstants::s_priorityMin)
->Attribute(AZ::Edit::Attributes::Max, AreaConstants::s_priorityMax)
->Attribute(AZ::Edit::Attributes::SoftMin, AreaConstants::s_priorityMin)
->Attribute(AZ::Edit::Attributes::SoftMax, AreaConstants::s_prioritySoftMax)
;
}
}
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<AreaConfig>()
->Attribute(AZ::Script::Attributes::Category, "Vegetation")
->Constructor()
->Property("areaPriority", BehaviorValueProperty(&AreaConfig::m_priority))
->Property("areaLayer",
[](AreaConfig* config) { return config->m_layer; },
[](AreaConfig* config, const AZ::u32& i) { config->m_layer = i; })
;
}
}
AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> AreaConfig::GetSelectableLayers() const
{
AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> selectableLayers;
selectableLayers.push_back({ AreaConstants::s_backgroundLayer, AZStd::string("Background") });
selectableLayers.push_back({ AreaConstants::s_foregroundLayer, AZStd::string("Foreground") });
return selectableLayers;
}
void AreaComponentBase::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("VegetationAreaService", 0x6a859504));
}
void AreaComponentBase::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("VegetationAreaService", 0x6a859504));
services.push_back(AZ_CRC("GradientService", 0x21c18d23));
services.push_back(AZ_CRC("GradientTransformService", 0x8c8c5ecc));
}
void AreaComponentBase::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& services)
{
}
void AreaComponentBase::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<AreaComponentBase, AZ::Component>()
->Version(0)
->Field("Configuration", &AreaComponentBase::m_configuration)
;
}
}
AreaComponentBase::AreaComponentBase(const AreaConfig& configuration)
: m_configuration(configuration)
{
}
void AreaComponentBase::Activate()
{
m_areaRegistered = false;
LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(GetEntityId());
AZ::TransformNotificationBus::Handler::BusConnect(GetEntityId());
AreaNotificationBus::Handler::BusConnect(GetEntityId());
AreaInfoBus::Handler::BusConnect(GetEntityId());
LmbrCentral::DependencyNotificationBus::Handler::BusConnect(GetEntityId());
UpdateRegistration();
}
void AreaComponentBase::Deactivate()
{
// Disconnect from the busses *before* unregistering to ensure that unregistration can't trigger any
// messages back into this component while it is deactivating.
// Specifically, unregistering the area first previously caused a bug in the SpawnerComponent in which OnUnregisterArea
// cleared out Descriptor pointers, and if any of them went to a refcount of 0, they could trigger an
// OnCompositionChanged event which ended up looping back into this component.
AreaNotificationBus::Handler::BusDisconnect();
AreaInfoBus::Handler::BusDisconnect();
AreaRequestBus::Handler::BusDisconnect();
LmbrCentral::DependencyNotificationBus::Handler::BusDisconnect();
LmbrCentral::ShapeComponentNotificationsBus::Handler::BusDisconnect();
AZ::TransformNotificationBus::Handler::BusDisconnect();
if (m_areaRegistered)
{
m_areaRegistered = false;
AreaSystemRequestBus::Broadcast(&AreaSystemRequestBus::Events::UnregisterArea, GetEntityId());
// Let area subclasses know that we've just unregistered the area
OnUnregisterArea();
}
}
bool AreaComponentBase::ReadInConfig(const AZ::ComponentConfig* baseConfig)
{
if (auto config = azrtti_cast<const AreaConfig*>(baseConfig))
{
m_configuration = *config;
return true;
}
return false;
}
bool AreaComponentBase::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
{
if (auto config = azrtti_cast<AreaConfig*>(outBaseConfig))
{
*config = m_configuration;
return true;
}
return false;
}
AZ::u32 AreaComponentBase::GetLayer() const
{
return m_configuration.m_layer;
}
AZ::u32 AreaComponentBase::GetPriority() const
{
return m_configuration.m_priority;
}
AZ::u32 AreaComponentBase::GetChangeIndex() const
{
return m_changeIndex;
}
void AreaComponentBase::UpdateRegistration()
{
// Area "valid" lifetimes can be shorter than the time in which the area components are active.
// This can occur due to the chain of entity dependencies, or dependencies on asset loading, etc.
// This method ensures that we update our registration status so that the area is only registered with
// the vegetation system once the area is completely valid, and the area is unregistered the moment
// it becomes invalid. Right now, we're defining "completely valid" as "has a well-defined valid AABB",
// since that's the minimum requirement for a vegetation area.
AZ::u32 layer = GetLayer();
AZ::u32 priority = GetPriority();
AZ::Aabb bounds = GetEncompassingAabb();
bool areaIsValid = bounds.IsValid();
if (m_areaRegistered && areaIsValid)
{
// Area is already registered, we're just updating information, so Refresh the area
AreaSystemRequestBus::Broadcast(&AreaSystemRequestBus::Events::RefreshArea, GetEntityId(), layer, priority, bounds);
}
else if (!m_areaRegistered && areaIsValid)
{
// We've gone from an invalid to valid state, so Register the area
m_areaRegistered = true;
AreaSystemRequestBus::Broadcast(&AreaSystemRequestBus::Events::RegisterArea, GetEntityId(), layer, priority, bounds);
// Let area subclasses know that we've just registered the area
OnRegisterArea();
}
else if (m_areaRegistered && !areaIsValid)
{
// We've gone from a valid to invalid state, so Unregister the area
m_areaRegistered = false;
AreaSystemRequestBus::Broadcast(&AreaSystemRequestBus::Events::UnregisterArea, GetEntityId());
// Let area subclasses know that we've just unregistered the area
OnUnregisterArea();
}
else
{
// Our state before and after were both invalid, so do nothing.
}
}
void AreaComponentBase::OnCompositionChanged()
{
UpdateRegistration();
++m_changeIndex;
}
void AreaComponentBase::OnAreaConnect()
{
AreaRequestBus::Handler::BusConnect(GetEntityId());
}
void AreaComponentBase::OnAreaDisconnect()
{
AreaRequestBus::Handler::BusDisconnect();
}
void AreaComponentBase::OnAreaRefreshed()
{
}
void AreaComponentBase::OnTransformChanged(const AZ::Transform& /*local*/, const AZ::Transform& /*world*/)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
OnCompositionChanged();
}
void AreaComponentBase::OnShapeChanged([[maybe_unused]] ShapeComponentNotifications::ShapeChangeReasons reasons)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
OnCompositionChanged();
}
}