/* * 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 "SurfaceData_precompiled.h" #include "TerrainSurfaceDataSystemComponent.h" #include <AzCore/Debug/Profiler.h> #include <AzCore/Math/MathUtils.h> #include <AzCore/Serialization/EditContext.h> #include <AzCore/Serialization/SerializeContext.h> #include <AzFramework/Math/MathUtils.h> #include <AzFramework/Terrain/TerrainDataRequestBus.h> #include <MathConversion.h> #include <SurfaceData/SurfaceDataSystemRequestBus.h> #include <SurfaceData/SurfaceTag.h> #include <SurfaceData/Utility/SurfaceDataUtility.h> #include <ISystem.h> #include <I3DEngine.h> namespace SurfaceData { ////////////////////////////////////////////////////////////////////////// // TerrainSurfaceDataSystemConfig void TerrainSurfaceDataSystemConfig::Reflect(AZ::ReflectContext* context) { if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context)) { serializeContext->Class<TerrainSurfaceDataSystemConfig, AZ::ComponentConfig>() ->Version(0) ; if (AZ::EditContext* editContext = serializeContext->GetEditContext()) { editContext->Class<TerrainSurfaceDataSystemConfig>( "Terrain Surface Data System", "Configures management of surface data requests against legacy terrain") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ; } } } ////////////////////////////////////////////////////////////////////////// // TerrainSurfaceDataSystemComponent void TerrainSurfaceDataSystemComponent::Reflect(AZ::ReflectContext* context) { TerrainSurfaceDataSystemConfig::Reflect(context); AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context); if (serialize) { serialize->Class<TerrainSurfaceDataSystemComponent, AZ::Component>() ->Version(0) ->Field("Configuration", &TerrainSurfaceDataSystemComponent::m_configuration) ; if (AZ::EditContext* editContext = serialize->GetEditContext()) { editContext->Class<TerrainSurfaceDataSystemComponent>("Terrain Surface Data System", "Manages surface data requests against legacy terrain") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "Surface Data") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement(0, &TerrainSurfaceDataSystemComponent::m_configuration, "Configuration", "") ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ; } } } TerrainSurfaceDataSystemComponent::TerrainSurfaceDataSystemComponent(const TerrainSurfaceDataSystemConfig& configuration) : m_configuration(configuration) { } TerrainSurfaceDataSystemComponent::TerrainSurfaceDataSystemComponent() { } void TerrainSurfaceDataSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services) { services.push_back(AZ_CRC("SurfaceDataProviderService", 0xfe9fb95e)); services.push_back(AZ_CRC("TerrainSurfaceDataProviderService", 0xa1ac7717)); } void TerrainSurfaceDataSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services) { services.push_back(AZ_CRC("TerrainSurfaceDataProviderService", 0xa1ac7717)); } void TerrainSurfaceDataSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services) { services.push_back(AZ_CRC("SurfaceDataSystemService", 0x1d44d25f)); } void TerrainSurfaceDataSystemComponent::Activate() { m_providerHandle = InvalidSurfaceDataRegistryHandle; m_system = GetISystem(); CrySystemEventBus::Handler::BusConnect(); AZ::HeightmapUpdateNotificationBus::Handler::BusConnect(); UpdateTerrainData(AZ::Aabb::CreateNull()); } void TerrainSurfaceDataSystemComponent::Deactivate() { if (m_providerHandle != InvalidSurfaceDataRegistryHandle) { SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UnregisterSurfaceDataProvider, m_providerHandle); m_providerHandle = InvalidSurfaceDataRegistryHandle; } SurfaceDataProviderRequestBus::Handler::BusDisconnect(); AZ::HeightmapUpdateNotificationBus::Handler::BusDisconnect(); CrySystemEventBus::Handler::BusDisconnect(); m_system = nullptr; // Clear the cached terrain bounds data { m_terrainBounds = AZ::Aabb::CreateNull(); m_terrainBoundsIsValid = false; } } bool TerrainSurfaceDataSystemComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig) { if (const auto config = azrtti_cast<const TerrainSurfaceDataSystemConfig*>(baseConfig)) { m_configuration = *config; return true; } return false; } bool TerrainSurfaceDataSystemComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const { if (auto config = azrtti_cast<TerrainSurfaceDataSystemConfig*>(outBaseConfig)) { *config = m_configuration; return true; } return false; } void TerrainSurfaceDataSystemComponent::OnCrySystemInitialized(ISystem& system, const SSystemInitParams& systemInitParams) { m_system = &system; } void TerrainSurfaceDataSystemComponent::OnCrySystemShutdown(ISystem& system) { m_system = nullptr; } void TerrainSurfaceDataSystemComponent::GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const { if (m_terrainBoundsIsValid) { auto enumerationCallback = [&](AzFramework::Terrain::TerrainDataRequests* terrain) -> bool { if (terrain->GetTerrainAabb().Contains(inPosition)) { bool isTerrainValidAtPoint = false; const float terrainHeight = terrain->GetHeight(inPosition, AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR, &isTerrainValidAtPoint); const bool isHole = !isTerrainValidAtPoint; SurfacePoint point; point.m_entityId = GetEntityId(); point.m_position = AZ::Vector3(inPosition.GetX(), inPosition.GetY(), terrainHeight); point.m_normal = terrain->GetNormal(inPosition); const AZ::Crc32 terrainTag = isHole ? Constants::s_terrainHoleTagCrc : Constants::s_terrainTagCrc; AddMaxValueForMasks(point.m_masks, terrainTag, 1.0f); surfacePointList.push_back(point); } // Only one handler should exist. return false; }; AzFramework::Terrain::TerrainDataRequestBus::EnumerateHandlers(enumerationCallback); } } AZ::Aabb TerrainSurfaceDataSystemComponent::GetSurfaceAabb() const { auto terrain = AzFramework::Terrain::TerrainDataRequestBus::FindFirstHandler(); return terrain ? terrain->GetTerrainAabb() : AZ::Aabb::CreateNull(); } SurfaceTagVector TerrainSurfaceDataSystemComponent::GetSurfaceTags() const { SurfaceTagVector tags; tags.push_back(Constants::s_terrainHoleTagCrc); tags.push_back(Constants::s_terrainTagCrc); return tags; } void TerrainSurfaceDataSystemComponent::UpdateTerrainData(const AZ::Aabb& dirtyRegion) { bool terrainValidBeforeUpdate = m_terrainBoundsIsValid; bool terrainValidAfterUpdate = false; AZ::Aabb terrainBoundsBeforeUpdate = m_terrainBounds; SurfaceDataRegistryEntry registryEntry; registryEntry.m_entityId = GetEntityId(); registryEntry.m_bounds = GetSurfaceAabb(); registryEntry.m_tags = GetSurfaceTags(); m_terrainBounds = registryEntry.m_bounds; m_terrainBoundsIsValid = m_terrainBounds.IsValid(); terrainValidAfterUpdate = m_terrainBoundsIsValid; if (terrainValidBeforeUpdate && terrainValidAfterUpdate) { AZ_Assert((m_providerHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle"); // Our terrain was valid before and after, it just changed in some way. If we have a valid dirty region passed in // then it's possible that the heightmap has been modified in the Editor. Otherwise, just notify that the entire // terrain has changed in some way. if (dirtyRegion.IsValid()) { SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::RefreshSurfaceData, dirtyRegion); } else { SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UpdateSurfaceDataProvider, m_providerHandle, registryEntry); } } else if (!terrainValidBeforeUpdate && terrainValidAfterUpdate) { // Our terrain has become valid, so register as a provider and save off the registry handles AZ_Assert((m_providerHandle == InvalidSurfaceDataRegistryHandle), "Surface Provider data handle is initialized before our terrain became valid"); SurfaceDataSystemRequestBus::BroadcastResult(m_providerHandle, &SurfaceDataSystemRequestBus::Events::RegisterSurfaceDataProvider, registryEntry); // Start listening for surface data events AZ_Assert((m_providerHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle"); SurfaceDataProviderRequestBus::Handler::BusConnect(m_providerHandle); } else if (terrainValidBeforeUpdate && !terrainValidAfterUpdate) { // Our terrain has stopped being valid, so unregister and stop listening for surface data events AZ_Assert((m_providerHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle"); SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UnregisterSurfaceDataProvider, m_providerHandle); m_providerHandle = InvalidSurfaceDataRegistryHandle; SurfaceDataProviderRequestBus::Handler::BusDisconnect(); } else { // We didn't have a valid terrain before or after running this, so do nothing. } } void TerrainSurfaceDataSystemComponent::HeightmapModified(const AZ::Aabb& bounds) { UpdateTerrainData(bounds); } }