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.
642 lines
26 KiB
C++
642 lines
26 KiB
C++
/*
|
|
* 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 <VegetationProfiler.h>
|
|
#include "SpawnerComponent.h"
|
|
#include <AzCore/Component/Entity.h>
|
|
#include <AzCore/Debug/Profiler.h>
|
|
#include <AzCore/RTTI/BehaviorContext.h>
|
|
#include <AzCore/Serialization/EditContext.h>
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include <LmbrCentral/Dependency/DependencyNotificationBus.h>
|
|
#include <Vegetation/Ebuses/AreaDebugBus.h>
|
|
#include <Vegetation/Ebuses/AreaInfoBus.h>
|
|
#include <Vegetation/Ebuses/DescriptorProviderRequestBus.h>
|
|
#include <Vegetation/InstanceData.h>
|
|
#include <GradientSignal/Util.h>
|
|
#include <SurfaceData/SurfaceDataTagEnumeratorRequestBus.h>
|
|
#include <AzCore/std/sort.h>
|
|
#include <SurfaceData/Utility/SurfaceDataUtility.h>
|
|
#include <Vegetation/Ebuses/DebugNotificationBus.h>
|
|
|
|
namespace Vegetation
|
|
{
|
|
namespace SpawnerUtil
|
|
{
|
|
static bool UpdateVersion([[maybe_unused]] AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
|
|
{
|
|
if (classElement.GetVersion() < 1)
|
|
{
|
|
classElement.RemoveElementByName(AZ_CRC("UseRelativeUVW", 0x97a6718e));
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void SpawnerConfig::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
|
|
if (serialize)
|
|
{
|
|
serialize->Class<SpawnerConfig, AreaConfig>()
|
|
->Version(1, &SpawnerUtil::UpdateVersion)
|
|
->Field("InheritBehavior", &SpawnerConfig::m_inheritBehavior)
|
|
->Field("AllowEmptyMeshes", &SpawnerConfig::m_allowEmptyMeshes)
|
|
->Field("FilterStage", &SpawnerConfig::m_filterStage)
|
|
;
|
|
|
|
AZ::EditContext* edit = serialize->GetEditContext();
|
|
if (edit)
|
|
{
|
|
edit->Class<SpawnerConfig>(
|
|
"Vegetation Layer Spawner", "Vegetation spawner")
|
|
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
|
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
|
|
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
|
|
->DataElement(0, &SpawnerConfig::m_inheritBehavior, "Inherit Behavior", "Allow shapes, modifiers, filters of a parent to affect this area.")
|
|
->DataElement(0, &SpawnerConfig::m_allowEmptyMeshes, "Allow Empty Assets", "Allow unspecified asset references in the Descriptors to claim space and block other vegetation.")
|
|
->DataElement(AZ::Edit::UIHandlers::ComboBox, &SpawnerConfig::m_filterStage, "Filter Stage", "Determines if filter is applied before (PreProcess) or after (PostProcess) modifiers.")
|
|
->EnumAttribute(FilterStage::PreProcess, "PreProcess")
|
|
->EnumAttribute(FilterStage::PostProcess, "PostProcess")
|
|
;
|
|
}
|
|
}
|
|
|
|
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
|
{
|
|
behaviorContext->Class<SpawnerConfig>("VegetationSpawnerConfig")
|
|
->Attribute(AZ::Script::Attributes::Category, "Vegetation")
|
|
->Constructor()
|
|
->Property("filterStage",
|
|
[](SpawnerConfig* config) { return (AZ::u8&)(config->m_filterStage); },
|
|
[](SpawnerConfig* config, const AZ::u8& i) { config->m_filterStage = (FilterStage)i; })
|
|
->Property("inheritBehavior", BehaviorValueProperty(&SpawnerConfig::m_inheritBehavior))
|
|
->Property("allowEmptyMeshes", BehaviorValueProperty(&SpawnerConfig::m_allowEmptyMeshes))
|
|
;
|
|
}
|
|
}
|
|
|
|
void SpawnerComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
|
|
{
|
|
AreaComponentBase::GetProvidedServices(services);
|
|
}
|
|
|
|
void SpawnerComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
|
|
{
|
|
AreaComponentBase::GetIncompatibleServices(services);
|
|
}
|
|
|
|
void SpawnerComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services)
|
|
{
|
|
AreaComponentBase::GetRequiredServices(services);
|
|
services.push_back(AZ_CRC("VegetationDescriptorProviderService", 0x62e51209));
|
|
services.push_back(AZ_CRC("ShapeService", 0xe86aa5fe));
|
|
}
|
|
|
|
void SpawnerComponent::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
SpawnerConfig::Reflect(context);
|
|
|
|
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
|
|
if (serialize)
|
|
{
|
|
serialize->Class<SpawnerComponent, AreaComponentBase>()
|
|
->Version(0)
|
|
->Field("Configuration", &SpawnerComponent::m_configuration)
|
|
;
|
|
}
|
|
|
|
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
|
{
|
|
behaviorContext->Constant("VegetationSpawnerComponentTypeId", BehaviorConstant(SpawnerComponentTypeId));
|
|
|
|
behaviorContext->Class<SpawnerComponent>("VegetationSpawner")->RequestBus("SpawnerRequestBus");
|
|
|
|
behaviorContext->EBus<SpawnerRequestBus>("VegetationSpawnerRequestBus")
|
|
->Attribute(AZ::Script::Attributes::Category, "Vegetation")
|
|
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
|
->Attribute(AZ::Script::Attributes::Module, "vegetation")
|
|
->Event("GetAreaPriority", &SpawnerRequestBus::Events::GetAreaPriority)
|
|
->Event("SetAreaPriority", &SpawnerRequestBus::Events::SetAreaPriority)
|
|
->VirtualProperty("AreaPriority", "GetAreaPriority", "SetAreaPriority")
|
|
->Event("GetAreaLayer", &SpawnerRequestBus::Events::GetAreaLayer)
|
|
->Event("SetAreaLayer", &SpawnerRequestBus::Events::SetAreaLayer)
|
|
->VirtualProperty("AreaLayer", "GetAreaLayer", "SetAreaLayer")
|
|
->Event("GetAreaProductCount", &SpawnerRequestBus::Events::GetAreaProductCount)
|
|
->Event("GetInheritBehavior", &SpawnerRequestBus::Events::GetInheritBehavior)
|
|
->Event("SetInheritBehavior", &SpawnerRequestBus::Events::SetInheritBehavior)
|
|
->VirtualProperty("InheritBehavior", "GetInheritBehavior", "SetInheritBehavior")
|
|
->Event("GetAllowEmptyMeshes", &SpawnerRequestBus::Events::GetAllowEmptyMeshes)
|
|
->Event("SetAllowEmptyMeshes", &SpawnerRequestBus::Events::SetAllowEmptyMeshes)
|
|
->VirtualProperty("AllowEmptyMeshes", "GetAllowEmptyMeshes", "SetAllowEmptyMeshes")
|
|
->Event("GetFilterStage", &SpawnerRequestBus::Events::GetFilterStage)
|
|
->Event("SetFilterStage", &SpawnerRequestBus::Events::SetFilterStage)
|
|
->VirtualProperty("FilterStage", "GetFilterStage", "SetFilterStage")
|
|
;
|
|
}
|
|
}
|
|
|
|
SpawnerComponent::SpawnerComponent(const SpawnerConfig& configuration)
|
|
: AreaComponentBase(configuration)
|
|
, m_configuration(configuration)
|
|
{
|
|
}
|
|
|
|
void SpawnerComponent::Activate()
|
|
{
|
|
ClearSelectableDescriptors();
|
|
|
|
SpawnerRequestBus::Handler::BusConnect(GetEntityId());
|
|
|
|
AreaComponentBase::Activate(); //must activate base last to connect AreaRequestBus once everything else is setup
|
|
}
|
|
|
|
void SpawnerComponent::Deactivate()
|
|
{
|
|
AreaComponentBase::Deactivate(); //must deactivate base first to ensure AreaRequestBus disconnect waits for other threads
|
|
|
|
SpawnerRequestBus::Handler::BusDisconnect();
|
|
|
|
OnUnregisterArea();
|
|
}
|
|
|
|
void SpawnerComponent::OnRegisterArea()
|
|
{
|
|
// Every time our area becomes valid and registered, make sure we clear our
|
|
// temporary descriptor caches. These should only contain valid data during
|
|
// PrepareToClaim / Claim, but it doesn't hurt to ensure they're cleared.
|
|
ClearSelectableDescriptors();
|
|
}
|
|
|
|
void SpawnerComponent::OnUnregisterArea()
|
|
{
|
|
// Every time our area becomes invalid and unregistered, make sure we destroy
|
|
// all instances that this component spawned. Also clear our temporary descriptor
|
|
// caches just to keep things tidy.
|
|
ClearSelectableDescriptors();
|
|
DestroyAllInstances();
|
|
}
|
|
|
|
bool SpawnerComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
|
|
{
|
|
AreaComponentBase::ReadInConfig(baseConfig);
|
|
if (auto config = azrtti_cast<const SpawnerConfig*>(baseConfig))
|
|
{
|
|
m_configuration = *config;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SpawnerComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
|
|
{
|
|
AreaComponentBase::WriteOutConfig(outBaseConfig);
|
|
if (auto config = azrtti_cast<SpawnerConfig*>(outBaseConfig))
|
|
{
|
|
*config = m_configuration;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SpawnerComponent::PrepareToClaim(EntityIdStack& stackIds)
|
|
{
|
|
AZ_PROFILE_FUNCTION(Entity);
|
|
|
|
//adding entity id to the stack of entity ids affecting vegetation
|
|
EntityIdStack emptyIds;
|
|
//when the inherit flag is disabled, as opposed to always inheriting, the stack must be cleared but preserved so redirecting to an empty stack to avoid copying
|
|
EntityIdStack& processedIds = m_configuration.m_inheritBehavior ? stackIds : emptyIds;
|
|
//adding current entity id to be processed uniformly
|
|
EntityIdStackPusher stackPusher(processedIds, GetEntityId());
|
|
|
|
#if defined(VEG_PROFILE_ENABLED)
|
|
CalcInstanceDebugColor(processedIds);
|
|
#endif
|
|
|
|
//gather tags from all sources so we can early out of processing this area
|
|
bool includeAll = false;
|
|
m_inclusiveTagsToConsider.clear();
|
|
m_exclusiveTagsToConsider.clear();
|
|
for (const auto& id : processedIds)
|
|
{
|
|
SurfaceData::SurfaceDataTagEnumeratorRequestBus::Event(id, &SurfaceData::SurfaceDataTagEnumeratorRequestBus::Events::GetInclusionSurfaceTags, m_inclusiveTagsToConsider, includeAll);
|
|
SurfaceData::SurfaceDataTagEnumeratorRequestBus::Event(id, &SurfaceData::SurfaceDataTagEnumeratorRequestBus::Events::GetExclusionSurfaceTags, m_exclusiveTagsToConsider);
|
|
}
|
|
|
|
// If anything is telling us to include all surfaces, clear out our list, as this means "check everything".
|
|
if (includeAll)
|
|
{
|
|
m_inclusiveTagsToConsider.clear();
|
|
}
|
|
|
|
AZStd::sort(m_inclusiveTagsToConsider.begin(), m_inclusiveTagsToConsider.end());
|
|
m_inclusiveTagsToConsider.erase(
|
|
std::unique(m_inclusiveTagsToConsider.begin(), m_inclusiveTagsToConsider.end()),
|
|
m_inclusiveTagsToConsider.end());
|
|
AZStd::sort(m_exclusiveTagsToConsider.begin(), m_exclusiveTagsToConsider.end());
|
|
m_exclusiveTagsToConsider.erase(
|
|
std::unique(m_exclusiveTagsToConsider.begin(), m_exclusiveTagsToConsider.end()),
|
|
m_exclusiveTagsToConsider.end());
|
|
|
|
//reset selectable descriptors
|
|
ClearSelectableDescriptors();
|
|
|
|
//gather all descriptors to be used for vegetation selection
|
|
AZStd::lock_guard<decltype(m_selectableDescriptorMutex)> selectableDescriptorLock(m_selectableDescriptorMutex);
|
|
for (const auto& id : processedIds)
|
|
{
|
|
DescriptorProviderRequestBus::Event(id, &DescriptorProviderRequestBus::Events::GetDescriptors, m_selectableDescriptorCache);
|
|
}
|
|
|
|
return !m_selectableDescriptorCache.empty();
|
|
}
|
|
|
|
bool SpawnerComponent::CreateInstance([[maybe_unused]] const ClaimPoint &point, InstanceData& instanceData)
|
|
{
|
|
AZ_PROFILE_FUNCTION(Entity);
|
|
|
|
instanceData.m_instanceId = InvalidInstanceId;
|
|
if (instanceData.m_descriptorPtr && instanceData.m_descriptorPtr->IsSpawnable())
|
|
{
|
|
InstanceSystemRequestBus::Broadcast(&InstanceSystemRequestBus::Events::CreateInstance, instanceData);
|
|
}
|
|
|
|
return instanceData.m_instanceId != InvalidInstanceId || m_configuration.m_allowEmptyMeshes;
|
|
}
|
|
|
|
void SpawnerComponent::ClearSelectableDescriptors()
|
|
{
|
|
AZStd::lock_guard<decltype(m_selectableDescriptorMutex)> selectableDescriptorLock(m_selectableDescriptorMutex);
|
|
m_selectedDescriptors.clear();
|
|
m_selectableDescriptorCache.clear();
|
|
}
|
|
|
|
bool SpawnerComponent::EvaluateFilters(EntityIdStack& processedIds, InstanceData& instanceData, const FilterStage intendedStage) const
|
|
{
|
|
AZ_PROFILE_FUNCTION(Entity);
|
|
|
|
bool accepted = true;
|
|
for (const auto& id : processedIds)
|
|
{
|
|
FilterRequestBus::EnumerateHandlersId(id, [this, &instanceData, &accepted, intendedStage](FilterRequestBus::Events* handler) {
|
|
const FilterStage stage = handler->GetFilterStage();
|
|
if (stage == intendedStage || (stage == FilterStage::Default && m_configuration.m_filterStage == intendedStage))
|
|
{
|
|
accepted = handler->Evaluate(instanceData);
|
|
}
|
|
return accepted;
|
|
});
|
|
if (!accepted)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return accepted;
|
|
}
|
|
|
|
bool SpawnerComponent::ProcessInstance(EntityIdStack& processedIds, const ClaimPoint& point, InstanceData& instanceData, DescriptorPtr descriptorPtr)
|
|
{
|
|
AZ_PROFILE_FUNCTION(Entity);
|
|
|
|
if (!descriptorPtr)
|
|
{
|
|
AZ_Error("vegetation", descriptorPtr, "DescriptorPtr should always be valid when spawning!");
|
|
return false;
|
|
}
|
|
|
|
// If this is an empty mesh asset (no valid id) AND we don't allow empty meshes, skip this descriptor
|
|
if (!m_configuration.m_allowEmptyMeshes && descriptorPtr->HasEmptyAssetReferences())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//generate details for a single vegetation instance using the current descriptor
|
|
static const AZ::Quaternion identityQuat = AZ::Quaternion::CreateIdentity();
|
|
instanceData.m_descriptorPtr = descriptorPtr;
|
|
instanceData.m_instanceId = InvalidInstanceId;
|
|
instanceData.m_position = point.m_position;
|
|
instanceData.m_normal = point.m_normal;
|
|
instanceData.m_masks = point.m_masks;
|
|
instanceData.m_rotation = identityQuat;
|
|
instanceData.m_alignment = identityQuat;
|
|
instanceData.m_scale = 1.0f;
|
|
|
|
// run pre-process filters on unmodified instance data
|
|
if (!EvaluateFilters(processedIds, instanceData, FilterStage::PreProcess))
|
|
{
|
|
// Once a filter rejects the instance, no point in running the rest.
|
|
return false;
|
|
}
|
|
|
|
// If all of the pre-process filters have allowed this instance to be placed, run the modifiers and post-process filters.
|
|
for (const auto& id : processedIds)
|
|
{
|
|
ModifierRequestBus::Event(id, &ModifierRequestBus::Events::Execute, instanceData);
|
|
}
|
|
|
|
// run post-process filters on modified instance data
|
|
if (!EvaluateFilters(processedIds, instanceData, FilterStage::PostProcess))
|
|
{
|
|
// Once a filter rejects the instance, no point in running the rest.
|
|
return false;
|
|
}
|
|
|
|
// If we made it through all the filters successfully, this descriptor can claim this instance.
|
|
return true;
|
|
}
|
|
|
|
bool SpawnerComponent::ClaimPosition(EntityIdStack& processedIds, const ClaimPoint& point, InstanceData& instanceData)
|
|
{
|
|
AZ_PROFILE_FUNCTION(Entity);
|
|
|
|
#if VEG_SPAWNER_ENABLE_CACHING
|
|
{
|
|
//return early if the point has already been rejected
|
|
AZStd::lock_guard<decltype(m_cacheMutex)> cacheLock(m_cacheMutex);
|
|
if (m_rejectedClaimCache.find(point.m_handle) != m_rejectedClaimCache.end())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//return early if an instance has already been generated and cached for this point
|
|
auto acceptedClaimCacheItr = m_acceptedClaimCache.find(point.m_handle);
|
|
if (acceptedClaimCacheItr != m_acceptedClaimCache.end())
|
|
{
|
|
instanceData = acceptedClaimCacheItr->second;
|
|
instanceData.m_instanceId = InvalidInstanceId;
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// test shape bus as first pass to claim the point
|
|
for (const auto& id : processedIds)
|
|
{
|
|
bool accepted = true;
|
|
LmbrCentral::ShapeComponentRequestsBus::EventResult(accepted, id, &LmbrCentral::ShapeComponentRequestsBus::Events::IsPointInside, point.m_position);
|
|
if (!accepted)
|
|
{
|
|
VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(&DebugNotificationBus::Events::FilterInstance, instanceData.m_id, AZStd::string_view("ShapeFilter")));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//generate uvw sample coordinates
|
|
DescriptorSelectorParams selectorParams;
|
|
selectorParams.m_position = point.m_position;
|
|
|
|
//copy the set of all selectable descriptors then remove any that don't pass the selection filter
|
|
AZStd::lock_guard<decltype(m_selectableDescriptorMutex)> selectableDescriptorLock(m_selectableDescriptorMutex);
|
|
m_selectedDescriptors = m_selectableDescriptorCache;
|
|
for (const auto& id : processedIds)
|
|
{
|
|
DescriptorSelectorRequestBus::Event(id, &DescriptorSelectorRequestBus::Events::SelectDescriptors, selectorParams, m_selectedDescriptors);
|
|
}
|
|
|
|
for (DescriptorPtr descriptorPtr : m_selectedDescriptors)
|
|
{
|
|
if (ProcessInstance(processedIds, point, instanceData, descriptorPtr))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// All the descriptors were filtered out, so don't claim the point.
|
|
return false;
|
|
}
|
|
|
|
void SpawnerComponent::ClaimPositions(EntityIdStack& stackIds, ClaimContext& context)
|
|
{
|
|
AZ_PROFILE_FUNCTION(Entity);
|
|
|
|
//reject entire spawner if there are inclusion tags to consider that don't exist in the context
|
|
if (context.m_masks.HasValidTags() &&
|
|
SurfaceData::HasValidTags(m_inclusiveTagsToConsider) &&
|
|
!context.m_masks.HasAnyMatchingTags(m_inclusiveTagsToConsider))
|
|
{
|
|
VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(&DebugNotificationBus::Events::MarkAreaRejectedByMask, GetEntityId()));
|
|
return;
|
|
}
|
|
|
|
//see comments in PrepareToClaim
|
|
EntityIdStack emptyIds;
|
|
EntityIdStack& processedIds = m_configuration.m_inheritBehavior ? stackIds : emptyIds;
|
|
EntityIdStackPusher stackPusher(processedIds, GetEntityId());
|
|
|
|
InstanceData instanceData;
|
|
instanceData.m_id = GetEntityId();
|
|
instanceData.m_changeIndex = GetChangeIndex();
|
|
|
|
size_t numAvailablePoints = context.m_availablePoints.size();
|
|
for (size_t pointIndex = 0; pointIndex < numAvailablePoints; )
|
|
{
|
|
ClaimPoint& point = context.m_availablePoints[pointIndex];
|
|
|
|
bool accepted = false;
|
|
if (ClaimPosition(processedIds, point, instanceData))
|
|
{
|
|
// Check if an identical instance already exists for reuse
|
|
if (context.m_existedCallback(point, instanceData))
|
|
{
|
|
accepted = true;
|
|
}
|
|
else
|
|
{
|
|
if (CreateInstance(point, instanceData))
|
|
{
|
|
accepted = true;
|
|
|
|
//notify the caller that this claim succeeded so it can do any cleanup or registration
|
|
context.m_createdCallback(point, instanceData);
|
|
|
|
//only store the instance id after all claim logic executes in case prior claim and instance gets released
|
|
AZStd::lock_guard<decltype(m_claimInstanceMappingMutex)> claimInstanceMappingMutexLock(m_claimInstanceMappingMutex);
|
|
m_claimInstanceMapping[point.m_handle] = instanceData.m_instanceId;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (accepted)
|
|
{
|
|
//Swap an available point from the end of the list
|
|
AZStd::swap(point, context.m_availablePoints.at(numAvailablePoints - 1));
|
|
--numAvailablePoints;
|
|
|
|
#if VEG_SPAWNER_ENABLE_CACHING
|
|
AZStd::lock_guard<decltype(m_cacheMutex)> cacheLock(m_cacheMutex);
|
|
m_acceptedClaimCache[point.m_handle] = instanceData;
|
|
m_rejectedClaimCache.erase(point.m_handle);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
UnclaimPosition(point.m_handle);
|
|
++pointIndex;
|
|
|
|
#if VEG_SPAWNER_ENABLE_CACHING
|
|
AZStd::lock_guard<decltype(m_cacheMutex)> cacheLock(m_cacheMutex);
|
|
m_acceptedClaimCache.erase(point.m_handle);
|
|
m_rejectedClaimCache.insert(point.m_handle);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
//resize to remove all used points
|
|
context.m_availablePoints.resize(numAvailablePoints);
|
|
|
|
//release residual descriptors and asset references used this claim attempt
|
|
AZStd::lock_guard<decltype(m_selectableDescriptorMutex)> selectableDescriptorLock(m_selectableDescriptorMutex);
|
|
m_selectedDescriptors.clear();
|
|
}
|
|
|
|
void SpawnerComponent::UnclaimPosition(const ClaimHandle handle)
|
|
{
|
|
AZ_PROFILE_FUNCTION(Entity);
|
|
|
|
InstanceId instanceId = InvalidInstanceId;
|
|
{
|
|
AZStd::lock_guard<decltype(m_claimInstanceMappingMutex)> claimInstanceMappingMutexLock(m_claimInstanceMappingMutex);
|
|
auto claimItr = m_claimInstanceMapping.find(handle);
|
|
if (claimItr != m_claimInstanceMapping.end())
|
|
{
|
|
instanceId = claimItr->second;
|
|
m_claimInstanceMapping.erase(claimItr);
|
|
}
|
|
}
|
|
|
|
if (instanceId != InvalidInstanceId)
|
|
{
|
|
InstanceSystemRequestBus::Broadcast(&InstanceSystemRequestBus::Events::DestroyInstance, instanceId);
|
|
}
|
|
}
|
|
|
|
AZ::Aabb SpawnerComponent::GetEncompassingAabb() const
|
|
{
|
|
AZ_PROFILE_FUNCTION(Entity);
|
|
|
|
AZ::Aabb bounds = AZ::Aabb::CreateNull();
|
|
LmbrCentral::ShapeComponentRequestsBus::EventResult(bounds, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
|
|
return bounds;
|
|
}
|
|
|
|
AZ::u32 SpawnerComponent::GetProductCount() const
|
|
{
|
|
AZStd::lock_guard<decltype(m_claimInstanceMappingMutex)> claimInstanceMappingMutexLock(m_claimInstanceMappingMutex);
|
|
return static_cast<AZ::u32>(m_claimInstanceMapping.size());
|
|
}
|
|
|
|
void SpawnerComponent::OnCompositionChanged()
|
|
{
|
|
AZ_PROFILE_FUNCTION(Entity);
|
|
AreaComponentBase::OnCompositionChanged();
|
|
|
|
#if VEG_SPAWNER_ENABLE_CACHING
|
|
//wipe the cache when content changes
|
|
AZStd::lock_guard<decltype(m_cacheMutex)> cacheLock(m_cacheMutex);
|
|
m_acceptedClaimCache.clear();
|
|
m_rejectedClaimCache.clear();
|
|
#endif
|
|
}
|
|
|
|
void SpawnerComponent::DestroyAllInstances()
|
|
{
|
|
AZ_PROFILE_FUNCTION(Entity);
|
|
|
|
ClaimInstanceMapping claimInstanceMapping;
|
|
{
|
|
AZStd::lock_guard<decltype(m_claimInstanceMappingMutex)> claimInstanceMappingMutexLock(m_claimInstanceMappingMutex);
|
|
AZStd::swap(claimInstanceMapping, m_claimInstanceMapping);
|
|
}
|
|
|
|
for (const auto& claim : claimInstanceMapping)
|
|
{
|
|
const auto instanceId = claim.second;
|
|
InstanceSystemRequestBus::Broadcast(&InstanceSystemRequestBus::Events::DestroyInstance, instanceId);
|
|
}
|
|
|
|
#if VEG_SPAWNER_ENABLE_CACHING
|
|
//wipe the cache
|
|
AZStd::lock_guard<decltype(m_cacheMutex)> cacheLock(m_cacheMutex);
|
|
m_acceptedClaimCache.clear();
|
|
m_rejectedClaimCache.clear();
|
|
#endif
|
|
}
|
|
|
|
void SpawnerComponent::CalcInstanceDebugColor(const EntityIdStack& processedIds)
|
|
{
|
|
AreaDebugBus::Event(GetEntityId(), &AreaDebugBus::Events::ResetBlendedDebugDisplayData);
|
|
for (const auto& id : processedIds)
|
|
{
|
|
AreaDebugDisplayData debugDisplayData;
|
|
AreaDebugBus::EventResult(debugDisplayData, id, &AreaDebugBus::Events::GetBaseDebugDisplayData);
|
|
AreaDebugBus::Event(GetEntityId(), &AreaDebugBus::Events::AddBlendedDebugDisplayData, debugDisplayData);
|
|
}
|
|
}
|
|
|
|
AZ::u32 SpawnerComponent::GetAreaPriority() const
|
|
{
|
|
return m_configuration.m_priority;
|
|
}
|
|
|
|
void SpawnerComponent::SetAreaPriority(AZ::u32 priority)
|
|
{
|
|
m_configuration.m_priority = priority;
|
|
LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
|
|
}
|
|
|
|
AZ::u32 SpawnerComponent::GetAreaLayer() const
|
|
{
|
|
return m_configuration.m_layer;
|
|
}
|
|
|
|
void SpawnerComponent::SetAreaLayer(AZ::u32 layer)
|
|
{
|
|
m_configuration.m_layer = layer;
|
|
LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
|
|
}
|
|
|
|
AZ::u32 SpawnerComponent::GetAreaProductCount() const
|
|
{
|
|
return GetProductCount();
|
|
}
|
|
|
|
bool SpawnerComponent::GetInheritBehavior() const
|
|
{
|
|
return m_configuration.m_inheritBehavior;
|
|
}
|
|
|
|
void SpawnerComponent::SetInheritBehavior(bool value)
|
|
{
|
|
m_configuration.m_inheritBehavior = value;
|
|
LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
|
|
}
|
|
|
|
bool SpawnerComponent::GetAllowEmptyMeshes() const
|
|
{
|
|
return m_configuration.m_allowEmptyMeshes;
|
|
}
|
|
|
|
void SpawnerComponent::SetAllowEmptyMeshes(bool value)
|
|
{
|
|
m_configuration.m_allowEmptyMeshes = value;
|
|
LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
|
|
}
|
|
|
|
FilterStage SpawnerComponent::GetFilterStage() const
|
|
{
|
|
return m_configuration.m_filterStage;
|
|
}
|
|
|
|
void SpawnerComponent::SetFilterStage(FilterStage filterStage)
|
|
{
|
|
m_configuration.m_filterStage = filterStage;
|
|
LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
|
|
}
|
|
}
|