/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include "Vegetation_precompiled.h" #include "MeshBlockerComponent.h" #include #include #include #include #include #include #include #include #include #include #include namespace Vegetation { void MeshBlockerConfig::Reflect(AZ::ReflectContext* context) { AZ::SerializeContext* serialize = azrtti_cast(context); if (serialize) { serialize->Class() ->Version(2) ->Field("InheritBehavior", &MeshBlockerConfig::m_inheritBehavior) ->Field("MeshHeightPercentMin", &MeshBlockerConfig::m_meshHeightPercentMin) ->Field("MeshHeightPercentMax", &MeshBlockerConfig::m_meshHeightPercentMax) ->Field("BlockWhenInvisible", &MeshBlockerConfig::m_blockWhenInvisible) ; AZ::EditContext* edit = serialize->GetEditContext(); if (edit) { edit->Class( "Vegetation Layer Blocker (Mesh)", "") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement(0, &MeshBlockerConfig::m_inheritBehavior, "Inherit Behavior", "Allow shapes, modifiers, filters of a parent to affect this area.") ->DataElement(AZ::Edit::UIHandlers::Slider, &MeshBlockerConfig::m_meshHeightPercentMin, "Mesh Height Percent Min", "The percentage of the mesh height (from the bottom up) used as the lower bound for intersection tests") ->Attribute(AZ::Edit::Attributes::Min, 0.0f) ->Attribute(AZ::Edit::Attributes::Max, 1.0f) ->DataElement(AZ::Edit::UIHandlers::Slider, &MeshBlockerConfig::m_meshHeightPercentMax, "Mesh Height Percent Max", "The percentage of the mesh height (from the bottom up) used as the upper bound for intersection tests") ->Attribute(AZ::Edit::Attributes::Min, 0.0f) ->Attribute(AZ::Edit::Attributes::Max, 1.0f) ->DataElement(0, &MeshBlockerConfig::m_blockWhenInvisible, "Block When Invisible", "Continue to block vegetation even if the mesh is invisible.") ; } } if (auto behaviorContext = azrtti_cast(context)) { behaviorContext->Class() ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::Preview) ->Attribute(AZ::Script::Attributes::Category, "Vegetation") ->Constructor() ->Property("inheritBehavior", BehaviorValueProperty(&MeshBlockerConfig::m_inheritBehavior)) ->Property("meshHeightPercentMin", BehaviorValueProperty(&MeshBlockerConfig::m_meshHeightPercentMin)) ->Property("meshHeightPercentMax", BehaviorValueProperty(&MeshBlockerConfig::m_meshHeightPercentMax)) ->Property("blockWhenInvisible", BehaviorValueProperty(&MeshBlockerConfig::m_blockWhenInvisible)) ; } } void MeshBlockerComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services) { AreaComponentBase::GetProvidedServices(services); } void MeshBlockerComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services) { AreaComponentBase::GetIncompatibleServices(services); services.push_back(AZ_CRC("VegetationModifierService", 0xc551fca6)); } void MeshBlockerComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services) { AreaComponentBase::GetRequiredServices(services); services.push_back(AZ_CRC("MeshService", 0x71d8a455)); } void MeshBlockerComponent::Reflect(AZ::ReflectContext* context) { MeshBlockerConfig::Reflect(context); AZ::SerializeContext* serialize = azrtti_cast(context); if (serialize) { serialize->Class() ->Version(0) ->Field("Configuration", &MeshBlockerComponent::m_configuration) ; } if (auto behaviorContext = azrtti_cast(context)) { behaviorContext->Constant("MeshBlockerComponentTypeId", BehaviorConstant(MeshBlockerComponentTypeId)); behaviorContext->Class()->RequestBus("MeshBlockerRequestBus"); behaviorContext->EBus("MeshBlockerRequestBus") ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::Preview) ->Attribute(AZ::Script::Attributes::Category, "Vegetation") ->Event("GetAreaPriority", &MeshBlockerRequestBus::Events::GetAreaPriority) ->Event("SetAreaPriority", &MeshBlockerRequestBus::Events::SetAreaPriority) ->VirtualProperty("AreaPriority", "GetAreaPriority", "SetAreaPriority") ->Event("GetAreaLayer", &MeshBlockerRequestBus::Events::GetAreaLayer) ->Event("SetAreaLayer", &MeshBlockerRequestBus::Events::SetAreaLayer) ->VirtualProperty("AreaLayer", "GetAreaLayer", "SetAreaLayer") ->Event("GetAreaProductCount", &MeshBlockerRequestBus::Events::GetAreaProductCount) ->Event("GetInheritBehavior", &MeshBlockerRequestBus::Events::GetInheritBehavior) ->Event("SetInheritBehavior", &MeshBlockerRequestBus::Events::SetInheritBehavior) ->VirtualProperty("InheritBehavior", "GetInheritBehavior", "SetInheritBehavior") ->Event("GetMeshHeightPercentMin", &MeshBlockerRequestBus::Events::GetMeshHeightPercentMin) ->Event("SetMeshHeightPercentMin", &MeshBlockerRequestBus::Events::SetMeshHeightPercentMin) ->VirtualProperty("MeshHeightPercentMin", "GetMeshHeightPercentMin", "SetMeshHeightPercentMin") ->Event("GetMeshHeightPercentMax", &MeshBlockerRequestBus::Events::GetMeshHeightPercentMax) ->Event("SetMeshHeightPercentMax", &MeshBlockerRequestBus::Events::SetMeshHeightPercentMax) ->VirtualProperty("MeshHeightPercentMax", "GetMeshHeightPercentMax", "SetMeshHeightPercentMax") ->Event("GetBlockWhenInvisible", &MeshBlockerRequestBus::Events::GetBlockWhenInvisible) ->Event("SetBlockWhenInvisible", &MeshBlockerRequestBus::Events::SetBlockWhenInvisible) ->VirtualProperty("BlockWhenInvisible", "GetBlockWhenInvisible", "SetBlockWhenInvisible") ; } } MeshBlockerComponent::MeshBlockerComponent(const MeshBlockerConfig& configuration) : AreaComponentBase(configuration) , m_configuration(configuration) { } void MeshBlockerComponent::Activate() { LmbrCentral::MeshComponentNotificationBus::Handler::BusConnect(GetEntityId()); UpdateMeshData(); m_refresh = false; MeshBlockerRequestBus::Handler::BusConnect(GetEntityId()); SurfaceData::SurfaceDataSystemNotificationBus::Handler::BusConnect(); AreaComponentBase::Activate(); //must activate base last to connect AreaRequestBus once everything else is setup } void MeshBlockerComponent::Deactivate() { AreaComponentBase::Deactivate(); //must deactivate base first to ensure AreaRequestBus disconnect waits for other threads SurfaceData::SurfaceDataSystemNotificationBus::Handler::BusDisconnect(); m_refresh = false; AZ::TickBus::Handler::BusDisconnect(); LmbrCentral::MeshComponentNotificationBus::Handler::BusDisconnect(); MeshBlockerRequestBus::Handler::BusDisconnect(); } bool MeshBlockerComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig) { AreaComponentBase::ReadInConfig(baseConfig); if (auto config = azrtti_cast(baseConfig)) { m_configuration = *config; return true; } return false; } bool MeshBlockerComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const { AreaComponentBase::WriteOutConfig(outBaseConfig); if (auto config = azrtti_cast(outBaseConfig)) { *config = m_configuration; return true; } return false; } bool MeshBlockerComponent::PrepareToClaim(EntityIdStack& stackIds) { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity); AZStd::lock_guard cacheLock(m_cacheMutex); if (!m_meshAssetData.GetId().IsValid()) { return false; } LmbrCentral::MeshAsset* mesh = m_meshAssetData.GetAs(); if (!mesh) { return false; } return m_meshBoundsForIntersection.IsValid() && (m_meshVisible || m_configuration.m_blockWhenInvisible); } bool MeshBlockerComponent::ClaimPosition(EntityIdStack& processedIds, const ClaimPoint& point, InstanceData& instanceData) { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity); AZStd::lock_guard cacheLock(m_cacheMutex); // If we've previously looked up this point for collision, reuse the results. // Note that this performs lookups based on ClaimPoint handles, so the cache needs to be invalidated on anything // that can cause the handles to change. (See OnSurfaceChanged below) auto itCachedRayHit = m_cachedRayHits.find(point.m_handle); if (itCachedRayHit != m_cachedRayHits.end()) { return itCachedRayHit->second; } // test AABB as first pass to claim the point if (!m_meshBoundsForIntersection.Contains(point.m_position)) { return false; } LmbrCentral::MeshAsset* mesh = m_meshAssetData.GetAs(); if (!mesh) { return false; } // test shape bus as first pass to claim the point bool isInsideShape = true; for (const auto& id : processedIds) { LmbrCentral::ShapeComponentRequestsBus::EventResult(isInsideShape, id, &LmbrCentral::ShapeComponentRequestsBus::Events::IsPointInside, point.m_position); if (!isInsideShape) { m_cachedRayHits[point.m_handle] = false; return false; } } //determine if an instance can be created using the generated details for (const auto& id : processedIds) { bool accepted = true; FilterRequestBus::EnumerateHandlersId(id, [this, &instanceData, &accepted](FilterRequestBus::Events* handler) { accepted = handler->Evaluate(instanceData); return accepted; }); if (!accepted) { m_cachedRayHits[point.m_handle] = false; return false; } } AZ::Vector3 outPosition; AZ::Vector3 outNormal; const AZ::Vector3 rayOrigin(point.m_position.GetX(), point.m_position.GetY(), m_meshBoundsForIntersection.GetMax().GetZ()); const AZ::Vector3 rayDirection = -AZ::Vector3::CreateAxisZ(); const bool intersected = SurfaceData::GetMeshRayIntersection(*mesh, m_meshWorldTM, m_meshWorldTMInverse, rayOrigin, rayDirection, outPosition, outNormal) && m_meshBoundsForIntersection.Contains(outPosition); m_cachedRayHits[point.m_handle] = intersected; return intersected; } void MeshBlockerComponent::ClaimPositions(EntityIdStack& stackIds, ClaimContext& context) { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::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()); 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]; //generate details for a single vegetation instance instanceData.m_position = point.m_position; instanceData.m_normal = point.m_normal; instanceData.m_masks = point.m_masks; if (ClaimPosition(processedIds, point, instanceData)) { context.m_createdCallback(point, instanceData); //Swap an available point from the end of the list AZStd::swap(point, context.m_availablePoints.at(numAvailablePoints - 1)); --numAvailablePoints; continue; } ++pointIndex; } //resize to remove all used points context.m_availablePoints.resize(numAvailablePoints); } void MeshBlockerComponent::UnclaimPosition(const ClaimHandle handle) { } AZ::Aabb MeshBlockerComponent::GetEncompassingAabb() const { AZStd::lock_guard cacheLock(m_cacheMutex); return m_meshBounds; } AZ::u32 MeshBlockerComponent::GetProductCount() const { return 0; } void MeshBlockerComponent::OnCompositionChanged() { if (!m_refresh) { m_refresh = true; AZ::TickBus::Handler::BusConnect(); } } void MeshBlockerComponent::OnSurfaceChanged(const AZ::EntityId& /*entityId*/, const AZ::Aabb& /*oldBounds*/, const AZ::Aabb& /*newBounds*/) { // If our surfaces have changed, we will need to refresh our cache. // Our cache performs lookups based on ClaimPoint handles, but the list of handles can potentially change // from any type of surface change anywhere, so refresh even if the area doesn't overlap. OnCompositionChanged(); } void MeshBlockerComponent::OnMeshDestroyed() { LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged); } void MeshBlockerComponent::OnMeshCreated(const AZ::Data::Asset& asset) { (void)asset; LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged); } void MeshBlockerComponent::OnBoundsReset() { LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged); } void MeshBlockerComponent::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/) { if (m_refresh) { UpdateMeshData(); m_refresh = false; } AZ::TickBus::Handler::BusDisconnect(); } void MeshBlockerComponent::UpdateMeshData() { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity); AZStd::lock_guard cacheLock(m_cacheMutex); m_cachedRayHits.clear(); m_meshAssetData = {}; LmbrCentral::MeshComponentRequestBus::EventResult(m_meshAssetData, GetEntityId(), &LmbrCentral::MeshComponentRequests::GetMeshAsset); m_meshBounds = AZ::Aabb::CreateNull(); LmbrCentral::MeshComponentRequestBus::EventResult(m_meshBounds, GetEntityId(), &LmbrCentral::MeshComponentRequestBus::Events::GetWorldBounds); m_meshBoundsForIntersection = m_meshBounds; if (m_meshBoundsForIntersection.IsValid()) { const auto heights = AZStd::minmax( m_meshBoundsForIntersection.GetMin().GetZ() + m_meshBoundsForIntersection.GetExtents().GetZ() * m_configuration.m_meshHeightPercentMin, m_meshBoundsForIntersection.GetMin().GetZ() + m_meshBoundsForIntersection.GetExtents().GetZ() * m_configuration.m_meshHeightPercentMax); AZ::Vector3 cornerMin = m_meshBoundsForIntersection.GetMin(); cornerMin.SetZ(heights.first); AZ::Vector3 cornerMax = m_meshBoundsForIntersection.GetMax(); cornerMax.SetZ(heights.second); m_meshBoundsForIntersection.Set(cornerMin, cornerMax); } m_meshVisible = false; LmbrCentral::MeshComponentRequestBus::EventResult(m_meshVisible, GetEntityId(), &LmbrCentral::MeshComponentRequests::GetVisibility); m_meshWorldTM = AZ::Transform::CreateIdentity(); AZ::TransformBus::EventResult(m_meshWorldTM, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM); m_meshWorldTMInverse = m_meshWorldTM.GetInverseFull(); AreaComponentBase::OnCompositionChanged(); } AZ::u32 MeshBlockerComponent::GetAreaPriority() const { return m_configuration.m_priority; } void MeshBlockerComponent::SetAreaPriority(AZ::u32 priority) { m_configuration.m_priority = priority; LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged); } AZ::u32 MeshBlockerComponent::GetAreaLayer() const { return m_configuration.m_layer; } void MeshBlockerComponent::SetAreaLayer(AZ::u32 layer) { m_configuration.m_layer = layer; LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged); } AZ::u32 MeshBlockerComponent::GetAreaProductCount() const { return GetProductCount(); } bool MeshBlockerComponent::GetInheritBehavior() const { return m_configuration.m_inheritBehavior; } void MeshBlockerComponent::SetInheritBehavior(bool value) { m_configuration.m_inheritBehavior = value; LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged); } float MeshBlockerComponent::GetMeshHeightPercentMin() const { return m_configuration.m_meshHeightPercentMax; } void MeshBlockerComponent::SetMeshHeightPercentMin(float meshHeightPercentMin) { m_configuration.m_meshHeightPercentMax = meshHeightPercentMin; LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged); } float MeshBlockerComponent::GetMeshHeightPercentMax() const { return m_configuration.m_meshHeightPercentMax; } void MeshBlockerComponent::SetMeshHeightPercentMax(float meshHeightPercentMax) { m_configuration.m_meshHeightPercentMax = meshHeightPercentMax; LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged); } bool MeshBlockerComponent::GetBlockWhenInvisible() const { return m_configuration.m_blockWhenInvisible; } void MeshBlockerComponent::SetBlockWhenInvisible(bool value) { m_configuration.m_blockWhenInvisible = value; LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged); } }