diff --git a/Gems/Terrain/Assets/Shaders/Terrain/Terrain.azsl b/Gems/Terrain/Assets/Shaders/Terrain/Terrain.azsl new file mode 100644 index 0000000000..57d0909ae4 --- /dev/null +++ b/Gems/Terrain/Assets/Shaders/Terrain/Terrain.azsl @@ -0,0 +1,125 @@ +/* + * 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 +#include + + +struct VertexInput +{ + float2 Position : POSITION; + float2 UV : UV; +}; + +struct VertexOutput +{ + float4 Position : SV_Position; + float3 Normal : NORMAL; + float2 UV : UV; +}; + +ShaderResourceGroup ObjectSrg : SRG_PerObject +{ + Texture2D HeightmapImage; + + Sampler LinearSampler + { + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; + }; + + row_major float3x4 m_modelToWorld; + float m_heightScale; + float2 m_uvMin; + float2 m_uvMax; + float2 m_uvStep; +} + +float4x4 GetObject_WorldMatrix() +{ + float4x4 modelToWorld = float4x4( + float4(1, 0, 0, 0), + float4(0, 1, 0, 0), + float4(0, 0, 1, 0), + float4(0, 0, 0, 1)); + + modelToWorld[0] = ObjectSrg::m_modelToWorld[0]; + modelToWorld[1] = ObjectSrg::m_modelToWorld[1]; + modelToWorld[2] = ObjectSrg::m_modelToWorld[2]; + return modelToWorld; +} + +float GetHeight(float2 origUv) +{ + float2 uv = clamp(origUv, 0.0f, 1.0f); + return ObjectSrg::m_heightScale * (ObjectSrg::HeightmapImage.SampleLevel(ObjectSrg::LinearSampler, uv, 0).r - 0.5f); +} + +VertexOutput MainVS(in VertexInput input) +{ + VertexOutput output; + + // Clamp the UVs *after* lerping to ensure that everything aligns properly right to the edge. + // We use out-of-bounds UV values to denote vertices that need to be removed. + float2 origUv = lerp(ObjectSrg::m_uvMin, ObjectSrg::m_uvMax, input.UV); + float2 uv = clamp(origUv, 0.0f, 1.0f); + + // Loop up the height and calculate our final position. + float height = GetHeight(uv); + float3 worldPosition = mul(GetObject_WorldMatrix(), float4(input.Position, height, 1.0f)).xyz; + output.Position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0f)); + + // Remove all vertices outside our bounds by turning them into NaN positions. + output.Position = output.Position / ((origUv.x >= 0.0f && origUv.x < 1.0f && origUv.y >= 0.0f && origUv.y < 1.0f) ? 1.0f : 0.0f); + + // Calculate normal + float2 gridSize = {1.0f, 1.0f}; + float up = GetHeight(uv + ObjectSrg::m_uvStep * float2(-1.0f, 0.0f)); + float right = GetHeight(uv + ObjectSrg::m_uvStep * float2( 0.0f, 1.0f)); + float down = GetHeight(uv + ObjectSrg::m_uvStep * float2( 1.0f, 0.0f)); + float left = GetHeight(uv + ObjectSrg::m_uvStep * float2( 0.0f, -1.0f)); + + float dydx = (right - left) * gridSize[0]; + float dydz = (down - up) * gridSize[1]; + + output.Normal = normalize(float3(dydx, 2.0f, dydz)); + + output.UV = uv; + return output; +} + +struct PixelOutput +{ + float4 m_color : SV_Target0; +}; + +PixelOutput MainPS(in VertexOutput input) +{ + PixelOutput output; + + // Hard-coded fake light direction + float3 lightDirection = normalize(float3(1.0, -1.0, 1.0)); + + // Fake light intensity ranges from 1.0 for normals directly facing the light to zero for those + // directly facing away. + float lightDot = dot(normalize(input.Normal), lightDirection); + float lightIntensity = lightDot * 0.5 + 0.5; + + // add a small amount of ambient and reduce direct light to keep in 0-1 range + lightIntensity = saturate(0.1 + lightIntensity * 0.9); + + // The lightIntensity should not affect alpha so only apply it to rgb. + //output.m_color.rgb = ((input.Normal + float3(1.0, 1.0, 1.0)) / 2.0); + output.m_color.rgb = float3(1.0, 1.0, 1.0) * lightIntensity; + output.m_color.a = 1.0f; + + return output; +} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/Terrain.shader b/Gems/Terrain/Assets/Shaders/Terrain/Terrain.shader new file mode 100644 index 0000000000..f20f4201ae --- /dev/null +++ b/Gems/Terrain/Assets/Shaders/Terrain/Terrain.shader @@ -0,0 +1,37 @@ +{ + + "Source" : "Terrain", + + + "RasterState" : { "CullMode" : "None" }, + + "DepthStencilState" : { + "Depth" : { "Enable" : true, "CompareFunc" : "GreaterEqual" } + }, + + "BlendState" : { + "Enable" : true, + "BlendSource" : "One", + "BlendAlphaSource" : "One", + "BlendDest" : "AlphaSourceInverse", + "BlendAlphaDest" : "AlphaSourceInverse", + "BlendAlphaOp" : "Add" + }, + + "DrawList" : "forward", + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "MainVS", + "type": "Vertex" + }, + { + "name": "MainPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp new file mode 100644 index 0000000000..d64f660d90 --- /dev/null +++ b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp @@ -0,0 +1,231 @@ +/* + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +namespace Terrain +{ + void TerrainHeightGradientListConfig::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serialize = azrtti_cast(context); + if (serialize) + { + serialize->Class() + ->Version(1) + ->Field("GradientEntities", &TerrainHeightGradientListConfig::m_gradientEntities) + ; + + AZ::EditContext* edit = serialize->GetEditContext(); + if (edit) + { + edit->Class( + "Terrain Height Gradient List Component", "Provide height data for a region of the world") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + + ->DataElement(0, &TerrainHeightGradientListConfig::m_gradientEntities, "Gradient Entities", "Ordered list of gradients to use as height providers.") + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, true) + ->Attribute(AZ::Edit::Attributes::RequiredService, AZ_CRC_CE("GradientService")) + ; + } + } + } + + void TerrainHeightGradientListComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services) + { + services.push_back(AZ_CRC_CE("TerrainHeightProviderService")); + } + + void TerrainHeightGradientListComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services) + { + services.push_back(AZ_CRC_CE("TerrainHeightProviderService")); + services.push_back(AZ_CRC_CE("GradientService")); + } + + void TerrainHeightGradientListComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services) + { + services.push_back(AZ_CRC_CE("TerrainAreaService")); + services.push_back(AZ_CRC_CE("BoxShapeService")); + } + + void TerrainHeightGradientListComponent::Reflect(AZ::ReflectContext* context) + { + TerrainHeightGradientListConfig::Reflect(context); + + AZ::SerializeContext* serialize = azrtti_cast(context); + if (serialize) + { + serialize->Class() + ->Version(0) + ->Field("Configuration", &TerrainHeightGradientListComponent::m_configuration) + ; + } + } + + TerrainHeightGradientListComponent::TerrainHeightGradientListComponent(const TerrainHeightGradientListConfig& configuration) + : m_configuration(configuration) + { + } + + void TerrainHeightGradientListComponent::Activate() + { + LmbrCentral::DependencyNotificationBus::Handler::BusConnect(GetEntityId()); + Terrain::TerrainAreaHeightRequestBus::Handler::BusConnect(GetEntityId()); + AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect(); + + // Make sure we get update notifications whenever this entity or any dependent gradient entity changes in any way. + // We'll use that to notify the terrain system that the height information needs to be refreshed. + m_dependencyMonitor.Reset(); + m_dependencyMonitor.ConnectOwner(GetEntityId()); + m_dependencyMonitor.ConnectDependency(GetEntityId()); + + for (auto& entityId : m_configuration.m_gradientEntities) + { + if (entityId != GetEntityId()) + { + m_dependencyMonitor.ConnectDependency(entityId); + } + } + + // Cache any height data needed and notify that the area has changed. + OnCompositionChanged(); + } + + void TerrainHeightGradientListComponent::Deactivate() + { + m_dependencyMonitor.Reset(); + AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusDisconnect(); + Terrain::TerrainAreaHeightRequestBus::Handler::BusDisconnect(); + LmbrCentral::DependencyNotificationBus::Handler::BusDisconnect(); + + // Since this height data will no longer exist, notify the terrain system to refresh the area. + TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId()); + } + + bool TerrainHeightGradientListComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig) + { + if (auto config = azrtti_cast(baseConfig)) + { + m_configuration = *config; + return true; + } + return false; + } + + bool TerrainHeightGradientListComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const + { + if (auto config = azrtti_cast(outBaseConfig)) + { + *config = m_configuration; + return true; + } + return false; + } + + float TerrainHeightGradientListComponent::GetHeight(float x, float y) + { + float maxSample = 0.0f; + + GradientSignal::GradientSampleParams params(AZ::Vector3(x, y, 0.0f)); + + // Right now, when the list contains multiple entries, we will use the highest point from each gradient. + // This is needed in part because gradients don't really have world bounds, so they exist everywhere but generally have a value + // of 0 outside their data bounds if they're using bounded data. We should examine the possibility of extending the gradient API + // to provide actual bounds so that it's possible to detect if the gradient even 'exists' in an area, at which point we could just + // make this list a prioritized list from top to bottom for any points that overlap. + for (auto& gradientId : m_configuration.m_gradientEntities) + { + float sample = 0.0f; + GradientSignal::GradientRequestBus::EventResult(sample, gradientId, &GradientSignal::GradientRequestBus::Events::GetValue, params); + maxSample = AZ::GetMax(maxSample, sample); + } + + const float height = AZ::Lerp(m_cachedShapeBounds.GetMin().GetZ(), m_cachedShapeBounds.GetMax().GetZ(), maxSample); + + return AZ::GetClamp(height, m_cachedMinWorldHeight, m_cachedMaxWorldHeight); + } + + void TerrainHeightGradientListComponent::GetHeight( + const AZ::Vector3& inPosition, AZ::Vector3& outPosition, [[maybe_unused]] Sampler sampleFilter = Sampler::DEFAULT) + { + const float height = GetHeight(inPosition.GetX(), inPosition.GetY()); + outPosition.SetZ(height); + } + + void TerrainHeightGradientListComponent::GetNormal( + const AZ::Vector3& inPosition, AZ::Vector3& outNormal, [[maybe_unused]] Sampler sampleFilter = Sampler::DEFAULT) + { + const float x = inPosition.GetX(); + const float y = inPosition.GetY(); + + if ((x >= m_cachedShapeBounds.GetMin().GetX()) && (x <= m_cachedShapeBounds.GetMax().GetX()) && + (y >= m_cachedShapeBounds.GetMin().GetY()) && (y <= m_cachedShapeBounds.GetMax().GetY())) + { + AZ::Vector2 fRange = (m_cachedHeightQueryResolution / 2.0f) + AZ::Vector2(0.05f); + + AZ::Vector3 v1(x - fRange.GetX(), y - fRange.GetY(), GetHeight(x - fRange.GetX(), y - fRange.GetY())); + AZ::Vector3 v2(x - fRange.GetX(), y + fRange.GetY(), GetHeight(x - fRange.GetX(), y + fRange.GetY())); + AZ::Vector3 v3(x + fRange.GetX(), y - fRange.GetY(), GetHeight(x + fRange.GetX(), y - fRange.GetY())); + AZ::Vector3 v4(x + fRange.GetX(), y + fRange.GetY(), GetHeight(x + fRange.GetX(), y + fRange.GetY())); + outNormal = (v3 - v2).Cross(v4 - v1).GetNormalized(); + } + } + + + void TerrainHeightGradientListComponent::OnCompositionChanged() + { + RefreshMinMaxHeights(); + TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId()); + } + + void TerrainHeightGradientListComponent::RefreshMinMaxHeights() + { + // Get the height range of our height provider based on the shape component. + LmbrCentral::ShapeComponentRequestsBus::EventResult(m_cachedShapeBounds, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb); + + // Get the height range of the entire world + m_cachedHeightQueryResolution = AZ::Vector2(1.0f); + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + m_cachedHeightQueryResolution, &AzFramework::Terrain::TerrainDataRequestBus::Events::GetTerrainGridResolution); + + AZ::Aabb worldBounds = AZ::Aabb::CreateNull(); + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + worldBounds, &AzFramework::Terrain::TerrainDataRequestBus::Events::GetTerrainAabb); + + // Save off the min/max heights so that we don't have to re-query them on every single height query. + m_cachedMinWorldHeight = worldBounds.GetMin().GetZ(); + m_cachedMaxWorldHeight = worldBounds.GetMax().GetZ(); + } + + void TerrainHeightGradientListComponent::OnTerrainDataChanged( + [[maybe_unused]] const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask) + { + if (dataChangedMask & TerrainDataChangedMask::Settings) + { + // If the terrain system settings changed, it's possible that the world bounds have changed, which can affect our height data. + // Refresh the min/max heights and notify that the height data for this area needs to be refreshed. + OnCompositionChanged(); + } + } + +} diff --git a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.h b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.h new file mode 100644 index 0000000000..7635680815 --- /dev/null +++ b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.h @@ -0,0 +1,101 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + + +namespace LmbrCentral +{ + template + class EditorWrappedComponentBase; +} + +namespace Terrain +{ + class TerrainHeightGradientListConfig + : public AZ::ComponentConfig + { + public: + AZ_CLASS_ALLOCATOR(TerrainHeightGradientListConfig, AZ::SystemAllocator, 0); + AZ_RTTI(TerrainHeightGradientListConfig, "{C5FD71A9-0722-4D4C-B605-EBEBF90C628F}", AZ::ComponentConfig); + static void Reflect(AZ::ReflectContext* context); + + AZStd::vector m_gradientEntities; + }; + + + class TerrainHeightGradientListComponent + : public AZ::Component + , private Terrain::TerrainAreaHeightRequestBus::Handler + , private LmbrCentral::DependencyNotificationBus::Handler + , private AzFramework::Terrain::TerrainDataNotificationBus::Handler + { + public: + template + friend class LmbrCentral::EditorWrappedComponentBase; + AZ_COMPONENT(TerrainHeightGradientListComponent, "{1BB3BA6C-6D4A-4636-B542-F23ECBA8F2AB}"); + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void Reflect(AZ::ReflectContext* context); + + TerrainHeightGradientListComponent(const TerrainHeightGradientListConfig& configuration); + TerrainHeightGradientListComponent() = default; + ~TerrainHeightGradientListComponent() = default; + + void GetHeight(const AZ::Vector3& inPosition, AZ::Vector3& outPosition, Sampler sampleFilter) override; + void GetNormal(const AZ::Vector3& inPosition, AZ::Vector3& outNormal, Sampler sampleFilter) override; + + ////////////////////////////////////////////////////////////////////////// + // AZ::Component interface implementation + void Activate() override; + void Deactivate() override; + bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override; + bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override; + + ////////////////////////////////////////////////////////////////////////// + // LmbrCentral::DependencyNotificationBus + void OnCompositionChanged() override; + + ////////////////////////////////////////////////////////////////////////// + // AzFramework::Terrain::TerrainDataNotificationBus + void OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask) override; + + private: + TerrainHeightGradientListConfig m_configuration; + + /////////////////////////////////////////// + void GetNormalSynchronous(float x, float y, AZ::Vector3& normal); + + void RefreshMinMaxHeights(); + float GetHeight(float x, float y); + + float m_cachedMinWorldHeight{ 0.0f }; + float m_cachedMaxWorldHeight{ 0.0f }; + AZ::Vector2 m_cachedHeightQueryResolution{ 1.0f, 1.0f }; + AZ::Aabb m_cachedShapeBounds; + + LmbrCentral::DependencyMonitor m_dependencyMonitor; + }; +} diff --git a/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp new file mode 100644 index 0000000000..06372a3490 --- /dev/null +++ b/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp @@ -0,0 +1,160 @@ +/* + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Terrain +{ + void TerrainLayerSpawnerConfig::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serialize = azrtti_cast(context); + if (serialize) + { + serialize->Class() + ->Version(1) + ->Field("Layer", &TerrainLayerSpawnerConfig::m_layer) + ->Field("Priority", &TerrainLayerSpawnerConfig::m_priority) + ->Field("UseGroundPlane", &TerrainLayerSpawnerConfig::m_useGroundPlane) + ; + + AZ::EditContext* edit = serialize->GetEditContext(); + if (edit) + { + edit->Class( + "Terrain Layer Spawner Component", "Provide terrain data for a region of the world") + ->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, &TerrainLayerSpawnerConfig::m_layer, "Layer Priority", "Defines a high level order that terrain spawners are applied") + ->Attribute(AZ::Edit::Attributes::EnumValues, &TerrainLayerSpawnerConfig::GetSelectableLayers) + ->DataElement(AZ::Edit::UIHandlers::Slider, &TerrainLayerSpawnerConfig::m_priority, "Sub Priority", "Defines order terrain spawners 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) + ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainLayerSpawnerConfig::m_useGroundPlane, "Use Ground Plane", "Determines whether or not to provide a default ground plane") + ; + } + } + } + + AZStd::vector> TerrainLayerSpawnerConfig::GetSelectableLayers() const + { + AZStd::vector> selectableLayers; + selectableLayers.push_back({ AreaConstants::s_backgroundLayer, AZStd::string("Background") }); + selectableLayers.push_back({ AreaConstants::s_foregroundLayer, AZStd::string("Foreground") }); + return selectableLayers; + } + + + void TerrainLayerSpawnerComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services) + { + services.push_back(AZ_CRC("TerrainAreaService")); + } + + void TerrainLayerSpawnerComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services) + { + services.push_back(AZ_CRC("TerrainAreaService")); + } + + void TerrainLayerSpawnerComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services) + { + services.push_back(AZ_CRC("BoxShapeService")); + } + + void TerrainLayerSpawnerComponent::Reflect(AZ::ReflectContext* context) + { + TerrainLayerSpawnerConfig::Reflect(context); + + AZ::SerializeContext* serialize = azrtti_cast(context); + if (serialize) + { + serialize->Class() + ->Version(0) + ->Field("Configuration", &TerrainLayerSpawnerComponent::m_configuration) + ; + } + } + + TerrainLayerSpawnerComponent::TerrainLayerSpawnerComponent(const TerrainLayerSpawnerConfig& configuration) + : m_configuration(configuration) + { + } + + void TerrainLayerSpawnerComponent::Activate() + { + AZ::TransformNotificationBus::Handler::BusConnect(GetEntityId()); + LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(GetEntityId()); + TerrainAreaRequestBus::Handler::BusConnect(GetEntityId()); + + TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RegisterArea, GetEntityId()); + } + + void TerrainLayerSpawnerComponent::Deactivate() + { + TerrainAreaRequestBus::Handler::BusDisconnect(); + TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::UnregisterArea, GetEntityId()); + + AZ::TransformNotificationBus::Handler::BusDisconnect(); + LmbrCentral::ShapeComponentNotificationsBus::Handler::BusDisconnect(); + } + + bool TerrainLayerSpawnerComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig) + { + if (auto config = azrtti_cast(baseConfig)) + { + m_configuration = *config; + return true; + } + return false; + } + + bool TerrainLayerSpawnerComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const + { + if (auto config = azrtti_cast(outBaseConfig)) + { + *config = m_configuration; + return true; + } + return false; + } + + void TerrainLayerSpawnerComponent::OnTransformChanged([[maybe_unused]] const AZ::Transform& local, [[maybe_unused]] const AZ::Transform& world) + { + RefreshArea(); + } + + void TerrainLayerSpawnerComponent::OnShapeChanged([[maybe_unused]] ShapeChangeReasons changeReason) + { + RefreshArea(); + } + + void TerrainLayerSpawnerComponent::RegisterArea() + { + TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RegisterArea, GetEntityId()); + } + + void TerrainLayerSpawnerComponent::RefreshArea() + { + TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId()); + } +} diff --git a/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.h b/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.h new file mode 100644 index 0000000000..1f1ae90227 --- /dev/null +++ b/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.h @@ -0,0 +1,97 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +namespace LmbrCentral +{ + template + class EditorWrappedComponentBase; +} + +namespace Terrain +{ + namespace AreaConstants + { + static const AZ::u32 s_backgroundLayer = 0; + static const AZ::u32 s_foregroundLayer = 1; + static const AZ::u32 s_priorityMin = 0; + static const AZ::u32 s_priorityMax = 10000; //arbitrary number because std::numeric_limits::max() always dislays -1 in RPE + static const AZ::u32 s_prioritySoftMax = 100; //design specified slider range + } + + class TerrainLayerSpawnerConfig + : public AZ::ComponentConfig + { + public: + AZ_CLASS_ALLOCATOR(TerrainLayerSpawnerConfig, AZ::SystemAllocator, 0); + AZ_RTTI(TerrainLayerSpawnerConfig, "{8E0695DE-E843-4858-BAEA-70953E74C810}", AZ::ComponentConfig); + static void Reflect(AZ::ReflectContext* context); + + AZStd::vector> GetSelectableLayers() const; + AZ::u32 m_layer = AreaConstants::s_foregroundLayer; + AZ::u32 m_priority = AreaConstants::s_priorityMin; + bool m_useGroundPlane = true; + }; + + + class TerrainLayerSpawnerComponent + : public AZ::Component + , private AZ::TransformNotificationBus::Handler + , private LmbrCentral::ShapeComponentNotificationsBus::Handler + , private Terrain::TerrainAreaRequestBus::Handler + { + public: + template + friend class LmbrCentral::EditorWrappedComponentBase; + AZ_COMPONENT(TerrainLayerSpawnerComponent, "{3848605F-A4EA-478C-B710-84AB8DCA9EC5}"); + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void Reflect(AZ::ReflectContext* context); + + TerrainLayerSpawnerComponent(const TerrainLayerSpawnerConfig& configuration); + TerrainLayerSpawnerComponent() = default; + ~TerrainLayerSpawnerComponent() = default; + + ////////////////////////////////////////////////////////////////////////// + // AZ::Component interface implementation + void Activate() override; + void Deactivate() override; + bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override; + bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override; + + + ////////////////////////////////////////////////////////////////////////// + // AZ::TransformNotificationBus::Handler + void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override; + + // ShapeComponentNotificationsBus + void OnShapeChanged(ShapeChangeReasons changeReason) override; + + void RegisterArea() override; + void RefreshArea() override; + + private: + TerrainLayerSpawnerConfig m_configuration; + }; +} diff --git a/Gems/Terrain/Code/Source/Components/TerrainSystemComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainSystemComponent.cpp index dea7babbbc..afaf0470b4 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainSystemComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainSystemComponent.cpp @@ -14,6 +14,8 @@ #include #include +#include +#include namespace Terrain { @@ -32,6 +34,8 @@ namespace Terrain ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ; } + + Terrain::TerrainFeatureProcessor::Reflect(context); } } @@ -60,9 +64,18 @@ namespace Terrain void TerrainSystemComponent::Activate() { + // Currently, the Terrain System Component owns the Terrain System instance because the Terrain World component gets recreated + // every time an entity is added or removed to a level. If this ever changes, the Terrain System ownership could move into + // the level component. + m_terrainSystem = new TerrainSystem(); + AZ::RPI::FeatureProcessorFactory::Get()->RegisterFeatureProcessor(); } void TerrainSystemComponent::Deactivate() { + delete m_terrainSystem; + m_terrainSystem = nullptr; + + AZ::RPI::FeatureProcessorFactory::Get()->UnregisterFeatureProcessor(); } } diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp new file mode 100644 index 0000000000..e2af58e3eb --- /dev/null +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp @@ -0,0 +1,123 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +namespace Terrain +{ + void TerrainWorldConfig::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serialize = azrtti_cast(context); + if (serialize) + { + serialize->Class() + ->Version(1) + ->Field("WorldMin", &TerrainWorldConfig::m_worldMin) + ->Field("WorldMax", &TerrainWorldConfig::m_worldMax) + ->Field("HeightQueryResolution", &TerrainWorldConfig::m_heightQueryResolution) + ; + + AZ::EditContext* edit = serialize->GetEditContext(); + if (edit) + { + edit->Class( + "Terrain World Component", "Data required for the terrain system to run") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector({ AZ_CRC_CE("Level") })) + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + + ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_worldMin, "World Bounds (Min)", "") + ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_worldMax, "World Bounds (Max)", "") + ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_heightQueryResolution, "Height Query Resolution (m)", "") + ; + } + } + } + + void TerrainWorldComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services) + { + services.push_back(AZ_CRC_CE("TerrainService")); + } + + void TerrainWorldComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services) + { + services.push_back(AZ_CRC_CE("TerrainService")); + } + + void TerrainWorldComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& services) + { + } + + void TerrainWorldComponent::Reflect(AZ::ReflectContext* context) + { + TerrainWorldConfig::Reflect(context); + + AZ::SerializeContext* serialize = azrtti_cast(context); + if (serialize) + { + serialize->Class() + ->Version(0) + ->Field("Configuration", &TerrainWorldComponent::m_configuration) + ; + } + } + + TerrainWorldComponent::TerrainWorldComponent(const TerrainWorldConfig& configuration) + : m_configuration(configuration) + { + } + + TerrainWorldComponent::~TerrainWorldComponent() + { + } + + void TerrainWorldComponent::Activate() + { + TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::SetWorldMin, m_configuration.m_worldMin); + TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::SetWorldMax, m_configuration.m_worldMax); + TerrainSystemServiceRequestBus::Broadcast( + &TerrainSystemServiceRequestBus::Events::SetHeightQueryResolution, m_configuration.m_heightQueryResolution); + + // Currently, the Terrain System Component owns the Terrain System instance because the Terrain World component gets recreated + // every time an entity is added or removed to a level. If this ever changes, the Terrain System ownership could move into + // the level component. + TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::Activate); + } + + void TerrainWorldComponent::Deactivate() + { + TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::Deactivate); + } + + bool TerrainWorldComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig) + { + if (auto config = azrtti_cast(baseConfig)) + { + m_configuration = *config; + return true; + } + return false; + } + + bool TerrainWorldComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const + { + if (auto config = azrtti_cast(outBaseConfig)) + { + *config = m_configuration; + return true; + } + return false; + } +} diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.h b/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.h new file mode 100644 index 0000000000..2dfe1135c8 --- /dev/null +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.h @@ -0,0 +1,64 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace LmbrCentral +{ + template + class EditorWrappedComponentBase; +} + +namespace Terrain +{ + class TerrainWorldConfig + : public AZ::ComponentConfig + { + public: + AZ_CLASS_ALLOCATOR(TerrainWorldConfig, AZ::SystemAllocator, 0); + AZ_RTTI(TerrainWorldConfig, "{295844DB-20DD-45B2-94DB-4245D5AE9AFF}", AZ::ComponentConfig); + static void Reflect(AZ::ReflectContext* context); + + AZ::Vector3 m_worldMin{ 0.0f, 0.0f, 0.0f }; + AZ::Vector3 m_worldMax{ 1024.0f, 1024.0f, 1024.0f }; + AZ::Vector2 m_heightQueryResolution{ 1.0f, 1.0f }; + }; + + + class TerrainWorldComponent + : public AZ::Component + { + public: + template + friend class LmbrCentral::EditorWrappedComponentBase; + AZ_COMPONENT(TerrainWorldComponent, "{4734EFDC-135D-4BF5-BE57-4F9AD03ADF78}"); + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void Reflect(AZ::ReflectContext* context); + + TerrainWorldComponent(const TerrainWorldConfig& configuration); + TerrainWorldComponent() = default; + ~TerrainWorldComponent() override; + + ////////////////////////////////////////////////////////////////////////// + // AZ::Component interface implementation + void Activate() override; + void Deactivate() override; + bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override; + bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override; + + private: + TerrainWorldConfig m_configuration; + }; +} diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp new file mode 100644 index 0000000000..1fea51ded4 --- /dev/null +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp @@ -0,0 +1,322 @@ +/* + * 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 + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace Terrain +{ + void TerrainWorldDebuggerConfig::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serialize = azrtti_cast(context); + if (serialize) + { + serialize->Class() + ->Version(1) + ->Field("DebugWireframe", &TerrainWorldDebuggerConfig::m_drawWireframe) + ->Field("DebugWorldBounds", &TerrainWorldDebuggerConfig::m_drawWorldBounds) + ; + + AZ::EditContext* edit = serialize->GetEditContext(); + if (edit) + { + edit->Class( + "Terrain World Debugger Component", "Optional component for enabling terrain debugging features.") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector({ AZ_CRC_CE("Level") })) + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + + ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldDebuggerConfig::m_drawWireframe, "Show Wireframe", "") + ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldDebuggerConfig::m_drawWorldBounds, "Show World Bounds", ""); + } + } + } + + void TerrainWorldDebuggerComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services) + { + services.push_back(AZ_CRC_CE("TerrainDebugService")); + } + + void TerrainWorldDebuggerComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services) + { + services.push_back(AZ_CRC_CE("TerrainDebugService")); + } + + void TerrainWorldDebuggerComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services) + { + services.push_back(AZ_CRC_CE("TerrainService")); + } + + void TerrainWorldDebuggerComponent::Reflect(AZ::ReflectContext* context) + { + TerrainWorldDebuggerConfig::Reflect(context); + + AZ::SerializeContext* serialize = azrtti_cast(context); + if (serialize) + { + serialize->Class() + ->Version(0) + ->Field("Configuration", &TerrainWorldDebuggerComponent::m_configuration) + ; + } + } + + TerrainWorldDebuggerComponent::TerrainWorldDebuggerComponent(const TerrainWorldDebuggerConfig& configuration) + : m_configuration(configuration) + { + } + + TerrainWorldDebuggerComponent::~TerrainWorldDebuggerComponent() + { + } + + void TerrainWorldDebuggerComponent::Activate() + { + m_wireframeBounds = AZ::Aabb::CreateNull(); + + TerrainSystemServiceRequestBus::Broadcast( + &TerrainSystemServiceRequestBus::Events::SetDebugWireframe, m_configuration.m_drawWireframe); + + AzFramework::EntityDebugDisplayEventBus::Handler::BusConnect(GetEntityId()); + AzFramework::BoundsRequestBus::Handler::BusConnect(GetEntityId()); + AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect(); + + } + + void TerrainWorldDebuggerComponent::Deactivate() + { + TerrainSystemServiceRequestBus::Broadcast( + &TerrainSystemServiceRequestBus::Events::SetDebugWireframe, false); + + AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusDisconnect(); + AzFramework::BoundsRequestBus::Handler::BusDisconnect(); + AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect(); + + m_wireframeBounds = AZ::Aabb::CreateNull(); + m_wireframeSectors.clear(); + } + + bool TerrainWorldDebuggerComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig) + { + if (auto config = azrtti_cast(baseConfig)) + { + m_configuration = *config; + return true; + } + return false; + } + + bool TerrainWorldDebuggerComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const + { + if (auto config = azrtti_cast(outBaseConfig)) + { + *config = m_configuration; + return true; + } + return false; + } + + AZ::Aabb TerrainWorldDebuggerComponent::GetWorldBounds() + { + AZ::Aabb terrainAabb = AZ::Aabb::CreateFromPoint(AZ::Vector3::CreateZero()); + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + terrainAabb, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb); + + return terrainAabb; + } + + AZ::Aabb TerrainWorldDebuggerComponent::GetLocalBounds() + { + // This is a level component, so the local bounds will always be the same as the world bounds. + return GetWorldBounds(); + } + + void TerrainWorldDebuggerComponent::DisplayEntityViewport( + const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) + { + // Draw a wireframe box around the entire terrain world bounds + if (m_configuration.m_drawWorldBounds) + { + AZ::Color outlineColor(1.0f, 0.0f, 0.0f, 1.0f); + AZ::Aabb aabb = GetWorldBounds(); + + debugDisplay.SetColor(outlineColor); + debugDisplay.DrawWireBox(aabb.GetMin(), aabb.GetMax()); + } + + // Draw a wireframe representation of the terrain surface + if (m_configuration.m_drawWireframe && !m_wireframeSectors.empty()) + { + // Start by assuming we'll draw the entire world. + AZ::Aabb drawingAabb = GetWorldBounds(); + + // Assuming we can get the camera, reduce the drawing bounds to a fixed distance around the camera. + if (auto viewportContextRequests = AZ::RPI::ViewportContextRequests::Get(); viewportContextRequests) + { + // Get the current camera position. + AZ::RPI::ViewportContextPtr viewportContext = viewportContextRequests->GetViewportContextById(viewportInfo.m_viewportId); + AZ::Vector3 cameraPos = viewportContext->GetCameraTransform().GetTranslation(); + + // Determine how far to draw in each direction in world space based on our MaxSectorsToDraw + AZ::Vector2 queryResolution = AZ::Vector2(1.0f); + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + queryResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainGridResolution); + AZ::Vector3 viewDistance( + queryResolution.GetX() * SectorSizeInGridPoints * sqrtf(MaxSectorsToDraw), + queryResolution.GetY() * SectorSizeInGridPoints * sqrtf(MaxSectorsToDraw), + 0.0f); + + // Create an AABB around the camera based on how far we want to be able to draw in each direction and clamp the + // drawing AABB to it. + AZ::Aabb cameraAabb = AZ::Aabb::CreateFromMinMax( + AZ::Vector3( + cameraPos.GetX() - viewDistance.GetX(), cameraPos.GetY() - viewDistance.GetY(), drawingAabb.GetMin().GetZ()), + AZ::Vector3( + cameraPos.GetX() + viewDistance.GetX(), cameraPos.GetY() + viewDistance.GetY(), drawingAabb.GetMin().GetZ())); + drawingAabb.Clamp(cameraAabb); + } + + // For each sector, if it appears within our view distance, draw it. + for (auto& sector : m_wireframeSectors) + { + if (drawingAabb.Overlaps(sector.m_aabb)) + { + if (!sector.m_lineVertices.empty()) + { + const AZ::Color primaryColor = AZ::Color(0.25f, 0.25f, 0.25f, 1.0f); + debugDisplay.DrawLines(sector.m_lineVertices, primaryColor); + } + else + { + AZ_Warning("Debug", false, "empty sector!"); + } + } + } + } + } + + void TerrainWorldDebuggerComponent::RefreshCachedWireframeGrid(const AZ::Aabb& dirtyRegion) + { + // Get the terrain world bounds and grid resolution. + + AZ::Aabb worldBounds = GetWorldBounds(); + + AZ::Vector2 queryResolution = AZ::Vector2(1.0f); + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + queryResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainGridResolution); + + // Calculate the world size of each sector. Note that this size actually ends at the last point, not the last square. + // So for example, the sector size for 3 points will go from (*--*--*) even though it will be used to draw (*--*--*--). + const float xSectorSize = (queryResolution.GetX() * SectorSizeInGridPoints); + const float ySectorSize = (queryResolution.GetY() * SectorSizeInGridPoints); + + // Calculate the total number of sectors to cache. The world bounds might not be evenly divisible by sector bounds, so we add + // an extra sector's worth of size in each direction so that clamping down to an integer still accounts for that fractional sector. + const int32_t numSectorsX = aznumeric_cast((worldBounds.GetXExtent() + xSectorSize) / xSectorSize); + const int32_t numSectorsY = aznumeric_cast((worldBounds.GetYExtent() + ySectorSize) / ySectorSize); + + // If we haven't cached anything before, or if the world bounds has changed, clear our cache structure and repopulate it + // with WireframeSector entries with the proper AABB sizes. + if (!m_wireframeBounds.IsValid() || !dirtyRegion.IsValid() || !m_wireframeBounds.IsClose(worldBounds)) + { + m_wireframeBounds = worldBounds; + + m_wireframeSectors.clear(); + m_wireframeSectors.reserve(numSectorsX * numSectorsY); + + for (int32_t ySector = 0; ySector < numSectorsY; ySector++) + { + for (int32_t xSector = 0; xSector < numSectorsX; xSector++) + { + // For each sector, set up the AABB for the sector and reserve memory for the line vertices. + WireframeSector sector; + sector.m_lineVertices.reserve(VerticesPerSector); + sector.m_aabb = AZ::Aabb::CreateFromMinMax( + AZ::Vector3( + worldBounds.GetMin().GetX() + (xSector * xSectorSize), worldBounds.GetMin().GetY() + (ySector * ySectorSize), + worldBounds.GetMin().GetZ()), + AZ::Vector3( + worldBounds.GetMin().GetX() + ((xSector + 1) * xSectorSize), + worldBounds.GetMin().GetY() + ((ySector + 1) * ySectorSize), worldBounds.GetMax().GetZ())); + + sector.m_aabb.Clamp(worldBounds); + + m_wireframeSectors.push_back(AZStd::move(sector)); + } + } + + // Notify the visibility system that our bounds have changed. + AzFramework::IEntityBoundsUnionRequestBus::Broadcast( + &AzFramework::IEntityBoundsUnionRequestBus::Events::RefreshEntityLocalBoundsUnion, GetEntityId()); + } + + // For each sector, if it overlaps with the dirty region, clear it out and recache the wireframe line data. + for (auto& sector : m_wireframeSectors) + { + if (dirtyRegion.IsValid() && !dirtyRegion.Overlaps(sector.m_aabb)) + { + continue; + } + + sector.m_lineVertices.clear(); + + for (float y = sector.m_aabb.GetMin().GetY(); y < sector.m_aabb.GetMax().GetY(); y += queryResolution.GetY()) + { + for (float x = sector.m_aabb.GetMin().GetX(); x < sector.m_aabb.GetMax().GetX(); x += queryResolution.GetX()) + { + float x1 = x + queryResolution.GetX(); + float y1 = y + queryResolution.GetY(); + + float z00 = 0.0f; + float z01 = 0.0f; + float z10 = 0.0f; + bool terrainExists; + + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + z00, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, x, y, + AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT, &terrainExists); + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + z01, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, x, y1, + AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT, &terrainExists); + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + z10, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, x1, y, + AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT, &terrainExists); + + sector.m_lineVertices.push_back(AZ::Vector3(x, y, z00)); + sector.m_lineVertices.push_back(AZ::Vector3(x1, y, z10)); + + sector.m_lineVertices.push_back(AZ::Vector3(x, y, z00)); + sector.m_lineVertices.push_back(AZ::Vector3(x, y1, z01)); + } + } + } + } + + void TerrainWorldDebuggerComponent::OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask) + { + if (dataChangedMask & (TerrainDataChangedMask::Settings | TerrainDataChangedMask::HeightData)) + { + RefreshCachedWireframeGrid(dirtyRegion); + } + } + + +} // namespace Terrain diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.h b/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.h new file mode 100644 index 0000000000..5ec831c038 --- /dev/null +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.h @@ -0,0 +1,114 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace LmbrCentral +{ + template + class EditorWrappedComponentBase; +} + +namespace Terrain +{ + class TerrainWorldDebuggerConfig + : public AZ::ComponentConfig + { + public: + AZ_CLASS_ALLOCATOR(TerrainWorldDebuggerConfig, AZ::SystemAllocator, 0); + AZ_RTTI(TerrainWorldDebuggerConfig, "{92686FA9-2C0B-47F1-8E2D-F2F302CDE5AA}", AZ::ComponentConfig); + static void Reflect(AZ::ReflectContext* context); + + bool m_drawWireframe{ true }; + bool m_drawWorldBounds{ true }; + }; + + + class TerrainWorldDebuggerComponent + : public AZ::Component + , private AzFramework::EntityDebugDisplayEventBus::Handler + , private AzFramework::BoundsRequestBus::Handler + , private AzFramework::Terrain::TerrainDataNotificationBus::Handler + { + public: + template + friend class LmbrCentral::EditorWrappedComponentBase; + AZ_COMPONENT(TerrainWorldDebuggerComponent, "{ECA1F4CB-5395-41FD-B6ED-FFD2C80096E2}"); + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void Reflect(AZ::ReflectContext* context); + + TerrainWorldDebuggerComponent(const TerrainWorldDebuggerConfig& configuration); + TerrainWorldDebuggerComponent() = default; + ~TerrainWorldDebuggerComponent() override; + + ////////////////////////////////////////////////////////////////////////// + // AZ::Component interface implementation + void Activate() override; + void Deactivate() override; + bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override; + bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override; + + ////////////////////////////////////////////////////////////////////////// + // EntityDebugDisplayEventBus + + // Ideally this would use ViewportDebugDisplayEventBus::DisplayViewport, but that doesn't currently work in game mode, + // so instead we use this plus the BoundsRequestBus with a large AABB to get ourselves rendered. + void DisplayEntityViewport( + const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) override; + + ////////////////////////////////////////////////////////////////////////// + // BoundsRequestBus + AZ::Aabb GetWorldBounds() override; + AZ::Aabb GetLocalBounds() override; + + ////////////////////////////////////////////////////////////////////////// + // AzFramework::Terrain::TerrainDataNotificationBus + void OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask) override; + + private: + + // Cache our debug wireframe representation in "sectors" of data so that we can easily control how far out we draw + // the wireframe representation in each direction. + struct WireframeSector + { + AZ::Aabb m_aabb; + AZStd::vector m_lineVertices; + }; + + // Each sector contains an N x N grid of squares that it will draw. Since this is a count of the number of terrain grid points + // in each direction, the actual world size will depend on the terrain grid resolution in each direction. + static constexpr int32_t SectorSizeInGridPoints = 10; + + // For each grid point we will draw half a square (left-right, top-down), so we need 4 vertices for the two lines. + static constexpr int32_t VerticesPerGridPoint = 4; + + // Pre-calculate the total number of vertices per sector. + static constexpr int32_t VerticesPerSector = + (SectorSizeInGridPoints * VerticesPerGridPoint) * (SectorSizeInGridPoints * VerticesPerGridPoint); + + // AuxGeom has limits to the number of lines it can draw in a frame, so we'll cap how many total sectors to draw. + static constexpr int32_t MaxVerticesToDraw = 500000; + static constexpr int32_t MaxSectorsToDraw = MaxVerticesToDraw / VerticesPerSector; + + void RefreshCachedWireframeGrid(const AZ::Aabb& dirtyRegion); + + TerrainWorldDebuggerConfig m_configuration; + AZStd::vector m_wireframeSectors; + AZ::Aabb m_wireframeBounds; + }; +} diff --git a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainHeightGradientListComponent.cpp b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainHeightGradientListComponent.cpp new file mode 100644 index 0000000000..8a36445446 --- /dev/null +++ b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainHeightGradientListComponent.cpp @@ -0,0 +1,22 @@ +/* + * 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 +#include +#include + +namespace Terrain +{ + void EditorTerrainHeightGradientListComponent::Reflect(AZ::ReflectContext* context) + { + BaseClassType::ReflectSubClass(context, 1, + &LmbrCentral::EditorWrappedComponentBaseVersionConverter + ); + } +} diff --git a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainHeightGradientListComponent.h b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainHeightGradientListComponent.h new file mode 100644 index 0000000000..2433eda009 --- /dev/null +++ b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainHeightGradientListComponent.h @@ -0,0 +1,32 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include + +namespace Terrain +{ + class EditorTerrainHeightGradientListComponent + : public LmbrCentral::EditorWrappedComponentBase + { + public: + using BaseClassType = LmbrCentral::EditorWrappedComponentBase; + AZ_EDITOR_COMPONENT(EditorTerrainHeightGradientListComponent, "{2D945B90-ADAB-4F9A-A113-39E714708068}", BaseClassType); + static void Reflect(AZ::ReflectContext* context); + + static constexpr const char* const s_categoryName = "Terrain"; + static constexpr const char* const s_componentName = "Terrain Height Gradient List"; + static constexpr const char* const s_componentDescription = "Provides height data for a region to the terrain system"; + static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainHeight.svg"; + static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainHeight.svg"; + static constexpr const char* const s_helpUrl = ""; + }; +} diff --git a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainLayerSpawnerComponent.cpp b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainLayerSpawnerComponent.cpp new file mode 100644 index 0000000000..3a0208aece --- /dev/null +++ b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainLayerSpawnerComponent.cpp @@ -0,0 +1,22 @@ +/* + * 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 +#include +#include + +namespace Terrain +{ + void EditorTerrainLayerSpawnerComponent::Reflect(AZ::ReflectContext* context) + { + BaseClassType::ReflectSubClass(context, 1, + &LmbrCentral::EditorWrappedComponentBaseVersionConverter + ); + } +} diff --git a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainLayerSpawnerComponent.h b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainLayerSpawnerComponent.h new file mode 100644 index 0000000000..1eb3413d52 --- /dev/null +++ b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainLayerSpawnerComponent.h @@ -0,0 +1,32 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include + +namespace Terrain +{ + class EditorTerrainLayerSpawnerComponent + : public LmbrCentral::EditorWrappedComponentBase + { + public: + using BaseClassType = LmbrCentral::EditorWrappedComponentBase; + AZ_EDITOR_COMPONENT(EditorTerrainLayerSpawnerComponent, "{9403FC94-FA38-4387-BEFD-A728C7D850C1}", BaseClassType); + static void Reflect(AZ::ReflectContext* context); + + static constexpr const char* const s_categoryName = "Terrain"; + static constexpr const char* const s_componentName = "Terrain Layer Spawner"; + static constexpr const char* const s_componentDescription = "Defines a terrain region for use by the terrain system"; + static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainLayerSpawner.svg"; + static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainLayerSpawner.svg"; + static constexpr const char* const s_helpUrl = ""; + }; +} diff --git a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldComponent.cpp b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldComponent.cpp new file mode 100644 index 0000000000..a50a44745f --- /dev/null +++ b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldComponent.cpp @@ -0,0 +1,56 @@ +/* + * 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 +#include +#include + +namespace Terrain +{ + void EditorTerrainWorldComponent::Reflect(AZ::ReflectContext* context) + { + BaseClassType::Reflect(context); + + AZ::SerializeContext* serializeContext = azrtti_cast(context); + + if (serializeContext) + { + serializeContext->Class() + ->Version(0) + ; + + if (auto editContext = serializeContext->GetEditContext()) + { + editContext->Class( + "Terrain World", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Category, "Terrain") + ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/TerrainWorld.svg") + ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/TerrainWorld.svg") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector({ AZ_CRC_CE("Level") })) + ; + } + } + } + + + void EditorTerrainWorldComponent::Init() + { + BaseClassType::Init(); + } + + void EditorTerrainWorldComponent::Activate() + { + BaseClassType::Activate(); + } + + AZ::u32 EditorTerrainWorldComponent::ConfigurationChanged() + { + return BaseClassType::ConfigurationChanged(); + } +} diff --git a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldComponent.h b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldComponent.h new file mode 100644 index 0000000000..6f3ab73361 --- /dev/null +++ b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldComponent.h @@ -0,0 +1,38 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include + +namespace Terrain +{ + class EditorTerrainWorldComponent + : public LmbrCentral::EditorWrappedComponentBase + { + public: + using BaseClassType = LmbrCentral::EditorWrappedComponentBase; + AZ_EDITOR_COMPONENT(EditorTerrainWorldComponent, "{43D02ADC-111F-4584-B590-FF6DC9FC912C}", BaseClassType); + static void Reflect(AZ::ReflectContext* context); + + ////////////////////////////////////////////////////////////////////////// + // AZ::Component interface implementation + void Init() override; + void Activate() override; + AZ::u32 ConfigurationChanged() override; + + protected: + using BaseClassType::m_configuration; + using BaseClassType::m_component; + using BaseClassType::m_visible; + + private: + }; +} diff --git a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldDebuggerComponent.cpp b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldDebuggerComponent.cpp new file mode 100644 index 0000000000..7e8fbf58e4 --- /dev/null +++ b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldDebuggerComponent.cpp @@ -0,0 +1,56 @@ +/* + * 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 +#include +#include + +namespace Terrain +{ + void EditorTerrainWorldDebuggerComponent::Reflect(AZ::ReflectContext* context) + { + BaseClassType::Reflect(context); + + AZ::SerializeContext* serializeContext = azrtti_cast(context); + + if (serializeContext) + { + serializeContext->Class() + ->Version(0) + ; + + if (auto editContext = serializeContext->GetEditContext()) + { + editContext->Class( + "Terrain World Debugger", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Category, "Terrain") + ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/TerrainWorldDebugger.svg") + ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/TerrainWorldDebugger.svg") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector({ AZ_CRC_CE("Level") })) + ; + } + } + } + + + void EditorTerrainWorldDebuggerComponent::Init() + { + BaseClassType::Init(); + } + + void EditorTerrainWorldDebuggerComponent::Activate() + { + BaseClassType::Activate(); + } + + AZ::u32 EditorTerrainWorldDebuggerComponent::ConfigurationChanged() + { + return BaseClassType::ConfigurationChanged(); + } +} diff --git a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldDebuggerComponent.h b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldDebuggerComponent.h new file mode 100644 index 0000000000..ddcfe3c76a --- /dev/null +++ b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldDebuggerComponent.h @@ -0,0 +1,38 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include + +namespace Terrain +{ + class EditorTerrainWorldDebuggerComponent + : public LmbrCentral::EditorWrappedComponentBase + { + public: + using BaseClassType = LmbrCentral::EditorWrappedComponentBase; + AZ_EDITOR_COMPONENT(EditorTerrainWorldDebuggerComponent, "{D09BA0B9-FB51-446B-BD7B-3C40743D2E39}", BaseClassType); + static void Reflect(AZ::ReflectContext* context); + + ////////////////////////////////////////////////////////////////////////// + // AZ::Component interface implementation + void Init() override; + void Activate() override; + AZ::u32 ConfigurationChanged() override; + + protected: + using BaseClassType::m_configuration; + using BaseClassType::m_component; + using BaseClassType::m_visible; + + private: + }; +} diff --git a/Gems/Terrain/Code/Source/EditorTerrainModule.cpp b/Gems/Terrain/Code/Source/EditorTerrainModule.cpp index bd4c94f4bf..0ba416cbb7 100644 --- a/Gems/Terrain/Code/Source/EditorTerrainModule.cpp +++ b/Gems/Terrain/Code/Source/EditorTerrainModule.cpp @@ -7,7 +7,11 @@ */ #include +#include +#include #include +#include +#include namespace Terrain { @@ -16,7 +20,12 @@ namespace Terrain m_descriptors.insert( m_descriptors.end(), { + Terrain::EditorTerrainHeightGradientListComponent::CreateDescriptor(), + Terrain::EditorTerrainLayerSpawnerComponent::CreateDescriptor(), Terrain::EditorTerrainSystemComponent::CreateDescriptor(), + Terrain::EditorTerrainWorldComponent::CreateDescriptor(), + Terrain::EditorTerrainWorldDebuggerComponent::CreateDescriptor(), + }); } diff --git a/Gems/Terrain/Code/Source/TerrainModule.cpp b/Gems/Terrain/Code/Source/TerrainModule.cpp index ec9325eb5c..29b2542472 100644 --- a/Gems/Terrain/Code/Source/TerrainModule.cpp +++ b/Gems/Terrain/Code/Source/TerrainModule.cpp @@ -11,6 +11,10 @@ #include #include +#include +#include +#include +#include #include namespace Terrain @@ -20,6 +24,10 @@ namespace Terrain { m_descriptors.insert(m_descriptors.end(), { TerrainSystemComponent::CreateDescriptor(), + TerrainWorldComponent::CreateDescriptor(), + TerrainWorldDebuggerComponent::CreateDescriptor(), + TerrainHeightGradientListComponent::CreateDescriptor(), + TerrainLayerSpawnerComponent::CreateDescriptor(), TerrainSurfaceDataSystemComponent::CreateDescriptor(), }); } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp new file mode 100644 index 0000000000..5af3fd58b9 --- /dev/null +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp @@ -0,0 +1,490 @@ +/* + * 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 + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Terrain +{ + namespace + { + const uint32_t DEFAULT_UploadBufferSize = 512 * 1024; // 512k + } + + namespace ShaderInputs + { + static const char* const HeightmapImage("HeightmapImage"); + static const char* const ModelToWorld("m_modelToWorld"); + static const char* const HeightScale("m_heightScale"); + static const char* const UvMin("m_uvMin"); + static const char* const UvMax("m_uvMax"); + static const char* const UvStep("m_uvStep"); + } + + + void TerrainFeatureProcessor::Reflect(AZ::ReflectContext* context) + { + if (AZ::SerializeContext* serialize = azrtti_cast(context)) + { + serialize->Class() + ->Version(0) + ; + } + } + + void TerrainFeatureProcessor::Activate() + { + m_areaData.clear(); + + InitializeAtomStuff(); + EnableSceneNotification(); + } + + void TerrainFeatureProcessor::InitializeAtomStuff() + { + m_rhiSystem = AZ::RHI::RHISystemInterface::Get(); + + m_rhiSystem->GetDrawListTagRegistry()->AcquireTag(AZ::Name("Terrain")); + + { + // Load the shader + + const char* terrainShaderFilePath = "Shaders/Terrain/Terrain.azshader"; + + AZ::Data::AssetId shaderAssetId; + AZ::Data::AssetCatalogRequestBus::BroadcastResult( + shaderAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, + terrainShaderFilePath, azrtti_typeid(), false); + if (!shaderAssetId.IsValid()) + { + AZ_Error("Terrain", false, "Failed to get shader asset id with path %s", terrainShaderFilePath); + return; + } + + auto shaderAsset = AZ::Data::AssetManager::Instance().GetAsset(shaderAssetId, AZ::Data::AssetLoadBehavior::PreLoad); + shaderAsset.BlockUntilLoadComplete(); + + if (!shaderAsset.IsReady()) + { + AZ_Error("Terrain", false, "Failed to get shader asset with path %s", terrainShaderFilePath); + return; + } + + m_shader = AZ::RPI::Shader::FindOrCreate(shaderAsset); + if (!m_shader) + { + AZ_Error("Terrain", false, "Failed to find or create a shader instance from shader asset '%s'", terrainShaderFilePath); + return; + } + + // Create the data layout + + m_pipelineStateDescriptor = AZ::RHI::PipelineStateDescriptorForDraw{}; + + { + AZ::RHI::InputStreamLayoutBuilder layoutBuilder; + + layoutBuilder.AddBuffer() + ->Channel("POSITION", AZ::RHI::Format::R32G32_FLOAT) + ->Channel("UV", AZ::RHI::Format::R32G32_FLOAT) + ; + m_pipelineStateDescriptor.m_inputStreamLayout = layoutBuilder.End(); + } + + auto shaderVariant = m_shader->GetVariant(AZ::RPI::ShaderAsset::RootShaderVariantStableId); + shaderVariant.ConfigurePipelineState(m_pipelineStateDescriptor); + + m_drawListTag = m_shader->GetDrawListTag(); + + m_perObjectSrgAsset = m_shader->FindShaderResourceGroupLayout(AZ::Name{"ObjectSrg"}); + if (!m_perObjectSrgAsset) + { + AZ_Error("Terrain", false, "Failed to get shader resource group asset"); + return; + } + else if (!m_perObjectSrgAsset->IsFinalized()) + { + AZ_Error("Terrain", false, "Shader resource group asset is not loaded"); + return; + } + + const AZ::RHI::ShaderResourceGroupLayout* shaderResourceGroupLayout = &(*m_perObjectSrgAsset); + + m_heightmapImageIndex = shaderResourceGroupLayout->FindShaderInputImageIndex(AZ::Name(ShaderInputs::HeightmapImage)); + AZ_Error("Terrain", m_heightmapImageIndex.IsValid(), "Failed to find shader input image %s.", ShaderInputs::HeightmapImage); + + m_modelToWorldIndex = shaderResourceGroupLayout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::ModelToWorld)); + AZ_Error("Terrain", m_modelToWorldIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::ModelToWorld); + + m_heightScaleIndex = shaderResourceGroupLayout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::HeightScale)); + AZ_Error("Terrain", m_heightScaleIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::HeightScale); + + m_uvMinIndex = shaderResourceGroupLayout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::UvMin)); + AZ_Error("Terrain", m_uvMinIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::UvMin); + + m_uvMaxIndex = shaderResourceGroupLayout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::UvMax)); + AZ_Error("Terrain", m_uvMaxIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::UvMax); + + m_uvStepIndex = shaderResourceGroupLayout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::UvStep)); + AZ_Error("Terrain", m_uvStepIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::UvStep); + + // If this fails to run now, it's ok, we'll initialize it in OnRenderPipelineAdded later. + bool success = GetParentScene()->ConfigurePipelineState(m_shader->GetDrawListTag(), m_pipelineStateDescriptor); + if (success) + { + m_pipelineState = m_shader->AcquirePipelineState(m_pipelineStateDescriptor); + AZ_Assert(m_pipelineState, "Failed to acquire default pipeline state for shader '%s'", terrainShaderFilePath); + } + } + + AZ::RHI::BufferPoolDescriptor dmaPoolDescriptor; + dmaPoolDescriptor.m_heapMemoryLevel = AZ::RHI::HeapMemoryLevel::Host; + dmaPoolDescriptor.m_bindFlags = AZ::RHI::BufferBindFlags::InputAssembly; + + m_hostPool = AZ::RHI::Factory::Get().CreateBufferPool(); + m_hostPool->SetName(AZ::Name("TerrainVertexPool")); + AZ::RHI::ResultCode resultCode = m_hostPool->Init(*m_rhiSystem->GetDevice(), dmaPoolDescriptor); + + if (resultCode != AZ::RHI::ResultCode::Success) + { + AZ_Error("Terrain", false, "Failed to create host buffer pool from RPI"); + return; + } + + InitializeTerrainPatch(); + + if (!InitializeRenderBuffers()) + { + AZ_Error("Terrain", false, "Failed to create Terrain render buffers!"); + return; + } + } + + void TerrainFeatureProcessor::OnRenderPipelineAdded([[maybe_unused]] AZ::RPI::RenderPipelinePtr pipeline) + { + bool success = GetParentScene()->ConfigurePipelineState(m_drawListTag, m_pipelineStateDescriptor); + AZ_Assert(success, "Couldn't configure the pipeline state."); + if (success) + { + m_pipelineState = m_shader->AcquirePipelineState(m_pipelineStateDescriptor); + AZ_Assert(m_pipelineState, "Failed to acquire default pipeline state."); + } + } + + void TerrainFeatureProcessor::OnRenderPipelineRemoved([[maybe_unused]] AZ::RPI::RenderPipeline* pipeline) + { + } + + void TerrainFeatureProcessor::OnRenderPipelinePassesChanged([[maybe_unused]] AZ::RPI::RenderPipeline* renderPipeline) + { + } + + + void TerrainFeatureProcessor::Deactivate() + { + DisableSceneNotification(); + + DestroyRenderBuffers(); + m_areaData.clear(); + + if (m_hostPool) + { + m_hostPool.reset(); + } + + m_rhiSystem = nullptr; + } + + void TerrainFeatureProcessor::Render(const AZ::RPI::FeatureProcessor::RenderPacket& packet) + { + ProcessSurfaces(packet); + } + + void TerrainFeatureProcessor::UpdateTerrainData( + AZ::EntityId areaId, + const AZ::Transform& transform, + const AZ::Aabb& worldBounds, + [[maybe_unused]] float sampleSpacing, + uint32_t width, uint32_t height, const AZStd::vector& heightData) + { + if (!worldBounds.IsValid()) + { + m_areaData.erase(areaId); + return; + } + + TerrainAreaData areaData; + + areaData.m_transform = transform; + areaData.m_heightScale = worldBounds.GetZExtent(); + areaData.m_terrainBounds = worldBounds; + areaData.m_heightmapImageHeight = height; + areaData.m_heightmapImageWidth = width; + + // Create heightmap image data + { + areaData.m_propertiesDirty = true; + + AZ::RHI::Size imageSize; + imageSize.m_width = width; + imageSize.m_height = height; + + AZ::Data::Instance streamingImagePool = AZ::RPI::ImageSystemInterface::Get()->GetSystemStreamingPool(); + areaData.m_heightmapImage = AZ::RPI::StreamingImage::CreateFromCpuData(*streamingImagePool, + AZ::RHI::ImageDimension::Image2D, + imageSize, + AZ::RHI::Format::R32_FLOAT, + (uint8_t*)heightData.data(), + heightData.size() * sizeof(float)); + AZ_Error("Terrain", areaData.m_heightmapImage, "Failed to initialize the heightmap image!"); + } + + m_areaData.insert_or_assign(areaId, areaData); + } + + void TerrainFeatureProcessor::ProcessSurfaces(const FeatureProcessor::RenderPacket& process) + { + AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzRender); + + if (m_drawListTag.IsNull()) + { + return; + } + + if (m_areaData.empty()) + { + return; + } + + m_drawPackets.clear(); + m_processSrgs.clear(); + + AZ::RHI::DrawPacketBuilder drawPacketBuilder; + + uint32_t numIndices = static_cast(m_gridIndices.size()); + + AZ::RHI::DrawIndexed drawIndexed; + drawIndexed.m_indexCount = numIndices; + drawIndexed.m_indexOffset = 0; + drawIndexed.m_vertexOffset = 0; + + for (auto& [areaId, areaData] : m_areaData) + { + float xFirstPatchStart = + areaData.m_terrainBounds.GetMin().GetX() - fmod(areaData.m_terrainBounds.GetMin().GetX(), m_gridMeters); + float xLastPatchStart = areaData.m_terrainBounds.GetMax().GetX() - fmod(areaData.m_terrainBounds.GetMax().GetX(), m_gridMeters); + float yFirstPatchStart = + areaData.m_terrainBounds.GetMin().GetY() - fmod(areaData.m_terrainBounds.GetMin().GetY(), m_gridMeters); + float yLastPatchStart = areaData.m_terrainBounds.GetMax().GetY() - fmod(areaData.m_terrainBounds.GetMax().GetY(), m_gridMeters); + + for (float yPatch = yFirstPatchStart; yPatch <= yLastPatchStart; yPatch += m_gridMeters) + { + for (float xPatch = xFirstPatchStart; xPatch <= xLastPatchStart; xPatch += m_gridMeters) + { + drawPacketBuilder.Begin(nullptr); + drawPacketBuilder.SetDrawArguments(drawIndexed); + drawPacketBuilder.SetIndexBufferView(m_indexBufferView); + + auto m_resourceGroup = AZ::RPI::ShaderResourceGroup::Create(m_shader->GetAsset(), m_shader->GetSupervariantIndex(), AZ::Name("ObjectSrg")); + //auto m_resourceGroup = AZ::RPI::ShaderResourceGroup::Create(m_shader->GetAsset(), AZ::Name("ObjectSrg")); + if (!m_resourceGroup) + { + AZ_Error("Terrain", false, "Failed to create shader resource group"); + return; + } + + float uvMin[2] = { 0.0f, 0.0f }; + float uvMax[2] = { 1.0f, 1.0f }; + + uvMin[0] = (float)((xPatch - areaData.m_terrainBounds.GetMin().GetX()) / areaData.m_terrainBounds.GetXExtent()); + uvMin[1] = (float)((yPatch - areaData.m_terrainBounds.GetMin().GetY()) / areaData.m_terrainBounds.GetYExtent()); + + uvMax[0] = + (float)(((xPatch + m_gridMeters) - areaData.m_terrainBounds.GetMin().GetX()) / areaData.m_terrainBounds.GetXExtent()); + uvMax[1] = + (float)(((yPatch + m_gridMeters) - areaData.m_terrainBounds.GetMin().GetY()) / areaData.m_terrainBounds.GetYExtent()); + + float uvStep[2] = + { + 1.0f / areaData.m_heightmapImageWidth, 1.0f / areaData.m_heightmapImageHeight, + }; + + AZ::Transform transform = areaData.m_transform; + transform.SetTranslation(xPatch, yPatch, areaData.m_transform.GetTranslation().GetZ()); + + AZ::Matrix3x4 matrix3x4 = AZ::Matrix3x4::CreateFromTransform(transform); + + m_resourceGroup->SetImage(m_heightmapImageIndex, areaData.m_heightmapImage); + m_resourceGroup->SetConstant(m_modelToWorldIndex, matrix3x4); + m_resourceGroup->SetConstant(m_heightScaleIndex, areaData.m_heightScale); + m_resourceGroup->SetConstant(m_uvMinIndex, uvMin); + m_resourceGroup->SetConstant(m_uvMaxIndex, uvMax); + m_resourceGroup->SetConstant(m_uvStepIndex, uvStep); + m_resourceGroup->Compile(); + m_processSrgs.push_back(m_resourceGroup); + + if (m_resourceGroup != nullptr) + { + drawPacketBuilder.AddShaderResourceGroup(m_resourceGroup->GetRHIShaderResourceGroup()); + } + + AZ::RHI::DrawPacketBuilder::DrawRequest drawRequest; + drawRequest.m_listTag = m_drawListTag; + drawRequest.m_pipelineState = m_pipelineState.get(); + drawRequest.m_streamBufferViews = m_vertexBufferViews; + drawPacketBuilder.AddDrawItem(drawRequest); + + const AZ::RHI::DrawPacket* drawPacket = drawPacketBuilder.End(); + m_drawPackets.emplace_back(drawPacket); + + for (auto& view : process.m_views) + { + view->AddDrawPacket(drawPacket); + } + } + } + } + } + + void TerrainFeatureProcessor::InitializeTerrainPatch() + { + m_gridVertices.clear(); + m_gridIndices.clear(); + + for (float y = 0.0f; y < m_gridMeters; y += m_gridSpacing) + { + for (float x = 0.0f; x < m_gridMeters; x += m_gridSpacing) + { + float x0 = x; + float x1 = x + m_gridSpacing; + float y0 = y; + float y1 = y + m_gridSpacing; + + uint16_t startIndex = (uint16_t)(m_gridVertices.size()); + + m_gridVertices.emplace_back(x0, y0, x0 / m_gridMeters, y0 / m_gridMeters); + m_gridVertices.emplace_back(x0, y1, x0 / m_gridMeters, y1 / m_gridMeters); + m_gridVertices.emplace_back(x1, y0, x1 / m_gridMeters, y0 / m_gridMeters); + m_gridVertices.emplace_back(x1, y1, x1 / m_gridMeters, y1 / m_gridMeters); + + m_gridIndices.emplace_back(startIndex); + m_gridIndices.emplace_back(aznumeric_cast(startIndex + 1)); + m_gridIndices.emplace_back(aznumeric_cast(startIndex + 2)); + m_gridIndices.emplace_back(aznumeric_cast(startIndex + 1)); + m_gridIndices.emplace_back(aznumeric_cast(startIndex + 2)); + m_gridIndices.emplace_back(aznumeric_cast(startIndex + 3)); + } + } + } + + bool TerrainFeatureProcessor::InitializeRenderBuffers() + { + AZ::RHI::ResultCode result = AZ::RHI::ResultCode::Fail; + + // Create geometry buffers + m_indexBuffer = AZ::RHI::Factory::Get().CreateBuffer(); + m_vertexBuffer = AZ::RHI::Factory::Get().CreateBuffer(); + + m_indexBuffer->SetName(AZ::Name("TerrainIndexBuffer")); + m_vertexBuffer->SetName(AZ::Name("TerrainVertexBuffer")); + + // We only need one vertex buffer view. + m_vertexBufferViews.resize(1); + + AZStd::vector> buffers = { m_indexBuffer , m_vertexBuffer }; + + // Fill our buffers with the vertex/index data + for (size_t bufferIndex = 0; bufferIndex < buffers.size(); ++bufferIndex) + { + AZ::RHI::Ptr buffer = buffers[bufferIndex]; + + // Initialize the buffer + + AZ::RHI::BufferInitRequest bufferRequest; + bufferRequest.m_descriptor = AZ::RHI::BufferDescriptor{ AZ::RHI::BufferBindFlags::InputAssembly, DEFAULT_UploadBufferSize }; + bufferRequest.m_buffer = buffer.get(); + + result = m_hostPool->InitBuffer(bufferRequest); + + if (result != AZ::RHI::ResultCode::Success) + { + AZ_Error("Terrain", false, "Failed to create GPU buffers for Terrain"); + return false; + } + + // Grab a pointer to the buffer's data + + m_hostPool->OrphanBuffer(*buffer); + + AZ::RHI::BufferMapResponse mapResponse; + m_hostPool->MapBuffer(AZ::RHI::BufferMapRequest(*buffer, 0, DEFAULT_UploadBufferSize), mapResponse); + + auto* mappedData = reinterpret_cast(mapResponse.m_data); + + //0th index should always be the index buffer + if (bufferIndex == 0) + { + // Fill the index buffer with our terrain patch indices + const uint64_t idxSize = m_gridIndices.size() * sizeof(uint16_t); + memcpy(mappedData, m_gridIndices.data(), idxSize); + + m_indexBufferView = AZ::RHI::IndexBufferView( + *buffer, 0, static_cast(idxSize), AZ::RHI::IndexFormat::Uint16); + } + else + { + // Fill the vertex buffer with our terrain patch vertices + const uint64_t elementSize = m_gridVertices.size() * sizeof(Vertex); + memcpy(mappedData, m_gridVertices.data(), elementSize); + + m_vertexBufferViews[bufferIndex - 1] = AZ::RHI::StreamBufferView( + *buffer, 0, static_cast(elementSize), static_cast(sizeof(Vertex))); + + AZ::RHI::ValidateStreamBufferViews(m_pipelineStateDescriptor.m_inputStreamLayout, m_vertexBufferViews); + } + + m_hostPool->UnmapBuffer(*buffer); + } + + return true; + } + + void TerrainFeatureProcessor::DestroyRenderBuffers() + { + m_indexBuffer.reset(); + m_vertexBuffer.reset(); + + m_vertexBufferViews.clear(); + + m_processSrgs.clear(); + + m_pipelineStateDescriptor = AZ::RHI::PipelineStateDescriptorForDraw{}; + m_pipelineState = nullptr; + } + +} diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h new file mode 100644 index 0000000000..2a1d5b7514 --- /dev/null +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h @@ -0,0 +1,145 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Terrain +{ + class TerrainFeatureProcessor final + : public AZ::RPI::FeatureProcessor + { + public: + AZ_RTTI(TerrainFeatureProcessor, "{D7DAC1F9-4A9F-4D3C-80AE-99579BF8AB1C}", AZ::RPI::FeatureProcessor); + AZ_DISABLE_COPY_MOVE(TerrainFeatureProcessor); + AZ_FEATURE_PROCESSOR(TerrainFeatureProcessor); + + static void Reflect(AZ::ReflectContext* context); + + TerrainFeatureProcessor() = default; + ~TerrainFeatureProcessor() = default; + + ////////////////////////////////////////////////////////////////////////// + // AZ::Component interface implementation + void Activate() override; + void Deactivate() override; + void Render(const AZ::RPI::FeatureProcessor::RenderPacket& packet) override; + + void UpdateTerrainData(AZ::EntityId areaId, const AZ::Transform& transform, const AZ::Aabb& worldBounds, float sampleSpacing, + uint32_t width, uint32_t height, const AZStd::vector& heightData); + + void RemoveTerrainData(AZ::EntityId areaId) + { + m_areaData.erase(areaId); + } + void RemoveTerrainData() + { + m_areaData.clear(); + } + + private: + // RPI::SceneNotificationBus overrides ... + void OnRenderPipelineAdded(AZ::RPI::RenderPipelinePtr pipeline) override; + void OnRenderPipelineRemoved(AZ::RPI::RenderPipeline* pipeline) override; + void OnRenderPipelinePassesChanged(AZ::RPI::RenderPipeline* renderPipeline) override; + + void InitializeAtomStuff(); + + void InitializeTerrainPatch(); + + bool InitializeRenderBuffers(); + void DestroyRenderBuffers(); + + void ProcessSurfaces(const FeatureProcessor::RenderPacket& process); + + // System-level parameters + const float m_gridSpacing{ 1.0f }; + const float m_gridMeters{ 32.0f }; + + // System-level cached reference to the Atom RHI + AZ::RHI::RHISystemInterface* m_rhiSystem = nullptr; + + // System-level references to the shader, pipeline, and shader-related information + AZ::Data::Instance m_shader{}; + AZ::RHI::PipelineStateDescriptorForDraw m_pipelineStateDescriptor; + AZ::RHI::ConstPtr m_pipelineState = nullptr; + AZ::RHI::DrawListTag m_drawListTag; + AZ::RHI::Ptr m_perObjectSrgAsset; + + AZ::RHI::ShaderInputImageIndex m_heightmapImageIndex; + AZ::RHI::ShaderInputConstantIndex m_modelToWorldIndex; + AZ::RHI::ShaderInputConstantIndex m_heightScaleIndex; + AZ::RHI::ShaderInputConstantIndex m_uvMinIndex; + AZ::RHI::ShaderInputConstantIndex m_uvMaxIndex; + AZ::RHI::ShaderInputConstantIndex m_uvStepIndex; + + + // Pos_float_2 + UV_float_2 + struct Vertex + { + float m_posx; + float m_posy; + float m_u; + float m_v; + + Vertex(float posx, float posy, float u, float v) + : m_posx(posx) + , m_posy(posy) + , m_u(u) + , m_v(v) + { + } + }; + + // System-level definition of a grid patch. (ex: 32m x 32m) + AZStd::vector m_gridVertices; + AZStd::vector m_gridIndices; + + // System-level data related to the grid patch + AZ::RHI::Ptr m_hostPool = nullptr; + AZ::RHI::Ptr m_indexBuffer; + AZ::RHI::Ptr m_vertexBuffer; + AZ::RHI::IndexBufferView m_indexBufferView; + AZStd::fixed_vector m_vertexBufferViews; + + // Per-area data + struct TerrainAreaData + { + AZ::Transform m_transform; + AZ::Aabb m_terrainBounds; + float m_heightScale; + AZ::Data::Instance m_heightmapImage; + uint32_t m_heightmapImageWidth; + uint32_t m_heightmapImageHeight; + bool m_propertiesDirty{ true }; + }; + + AZStd::unordered_map m_areaData; + + // These could either be per-area or system-level + AZStd::vector> m_drawPackets; + AZStd::vector> m_processSrgs; + }; +} diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp new file mode 100644 index 0000000000..872b37a02e --- /dev/null +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp @@ -0,0 +1,489 @@ +/* + * 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 +#include +#include +#include + +#include +#include +#include + +using namespace Terrain; + +TerrainSystem::TerrainSystem() +{ + Terrain::TerrainSystemServiceRequestBus::Handler::BusConnect(); + AZ::TickBus::Handler::BusConnect(); + + m_currentSettings.m_systemActive = false; + m_currentSettings.m_worldBounds = AZ::Aabb::CreateNull(); + + m_requestedSettings = m_currentSettings; + m_requestedSettings.m_worldBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(4096.0f, 4096.0f, 2048.0f)); +} + +TerrainSystem::~TerrainSystem() +{ + AZ::TickBus::Handler::BusDisconnect(); + Terrain::TerrainSystemServiceRequestBus::Handler::BusDisconnect(); + + Deactivate(); +} + +void TerrainSystem::Activate() +{ + m_requestedSettings.m_systemActive = true; + m_terrainSettingsDirty = true; +} + +void TerrainSystem::Deactivate() +{ + m_requestedSettings.m_systemActive = false; + m_terrainSettingsDirty = true; +} + +void TerrainSystem::SetWorldMin(AZ::Vector3 worldOrigin) +{ + m_requestedSettings.m_worldBounds.SetMin(worldOrigin); + m_terrainSettingsDirty = true; +} + +void TerrainSystem::SetWorldMax(AZ::Vector3 worldBounds) +{ + m_requestedSettings.m_worldBounds.SetMax(worldBounds); + m_terrainSettingsDirty = true; +} + +void TerrainSystem::SetHeightQueryResolution(AZ::Vector2 queryResolution) +{ + m_requestedSettings.m_heightQueryResolution = queryResolution; + m_terrainSettingsDirty = true; +} + +void TerrainSystem::SetDebugWireframe(bool wireframeEnabled) +{ + m_requestedSettings.m_debugWireframeEnabled = wireframeEnabled; + m_terrainSettingsDirty = true; +} + + +AZ::Aabb TerrainSystem::GetTerrainAabb() const +{ + return m_currentSettings.m_worldBounds; +} + +AZ::Vector2 TerrainSystem::GetTerrainGridResolution() const +{ + return m_currentSettings.m_heightQueryResolution; +} + +float TerrainSystem::GetHeightSynchronous(float x, float y) const +{ + AZ::Vector3 inPosition((float)x, (float)y, m_currentSettings.m_worldBounds.GetMin().GetZ()); + AZ::Vector3 outPosition((float)x, (float)y, m_currentSettings.m_worldBounds.GetMin().GetZ()); + + AZStd::shared_lock lock(m_areaMutex); + + if (!m_registeredAreas.empty()) + { + for (auto& [areaId, areaBounds] : m_registeredAreas) + { + inPosition.SetZ(areaBounds.GetMin().GetZ()); + if (areaBounds.Contains(inPosition)) + { + Terrain::TerrainAreaHeightRequestBus::Event( + areaId, &Terrain::TerrainAreaHeightRequestBus::Events::GetHeight, inPosition, outPosition, + Terrain::TerrainAreaHeightRequestBus::Events::Sampler::DEFAULT); + } + } + } + + return AZ::GetClamp( + outPosition.GetZ(), m_currentSettings.m_worldBounds.GetMin().GetZ(), m_currentSettings.m_worldBounds.GetMax().GetZ()); +} + +float TerrainSystem::GetHeight(AZ::Vector3 position, [[maybe_unused]] Sampler sampler, [[maybe_unused]] bool* terrainExistsPtr) const +{ + if (terrainExistsPtr) + { + *terrainExistsPtr = true; + } + + return GetHeightSynchronous(position.GetX(), position.GetY()); +} + +float TerrainSystem::GetHeightFromFloats( + float x, float y, [[maybe_unused]] Sampler sampler, [[maybe_unused]] bool* terrainExistsPtr) const +{ + if (terrainExistsPtr) + { + *terrainExistsPtr = true; + } + + return GetHeightSynchronous(x, y); +} + +bool TerrainSystem::GetIsHoleFromFloats( + [[maybe_unused]] float x, [[maybe_unused]] float y, [[maybe_unused]] Sampler sampleFilter) const +{ + return false; +} + +AZ::Vector3 TerrainSystem::GetNormalSynchronous([[maybe_unused]] float x, [[maybe_unused]] float y) const +{ + return AZ::Vector3::CreateAxisZ(); +} + +AZ::Vector3 TerrainSystem::GetNormal( + AZ::Vector3 position, [[maybe_unused]] Sampler sampleFilter, [[maybe_unused]] bool* terrainExistsPtr) const +{ + if (terrainExistsPtr) + { + *terrainExistsPtr = true; + } + + return GetNormalSynchronous(position.GetX(), position.GetY()); +} + +AZ::Vector3 TerrainSystem::GetNormalFromFloats( + float x, float y, [[maybe_unused]] Sampler sampleFilter, [[maybe_unused]] bool* terrainExistsPtr) const +{ + if (terrainExistsPtr) + { + *terrainExistsPtr = true; + } + + return GetNormalSynchronous(x, y); +} + + +AzFramework::SurfaceData::SurfaceTagWeight TerrainSystem::GetMaxSurfaceWeight( + [[maybe_unused]] AZ::Vector3 position, [[maybe_unused]] Sampler sampleFilter, [[maybe_unused]] bool* terrainExistsPtr) const +{ + if (terrainExistsPtr) + { + *terrainExistsPtr = true; + } + + return AzFramework::SurfaceData::SurfaceTagWeight(); +} + +AzFramework::SurfaceData::SurfaceTagWeight TerrainSystem::GetMaxSurfaceWeightFromFloats( + [[maybe_unused]] float x, + [[maybe_unused]] float y, + [[maybe_unused]] Sampler sampleFilter, + [[maybe_unused]] bool* terrainExistsPtr) const +{ + if (terrainExistsPtr) + { + *terrainExistsPtr = true; + } + + return AzFramework::SurfaceData::SurfaceTagWeight(); +} + +const char* TerrainSystem::GetMaxSurfaceName( + [[maybe_unused]] AZ::Vector3 position, [[maybe_unused]] Sampler sampleFilter, [[maybe_unused]] bool* terrainExistsPtr) const +{ + if (terrainExistsPtr) + { + *terrainExistsPtr = true; + } + + return ""; +} + +/* +void TerrainSystem::GetSurfaceWeights( + [[maybe_unused]] const AZ::Vector3& inPosition, + [[maybe_unused]] Sampler sampleFilter, + [[maybe_unused]] SurfaceData::SurfaceTagWeightMap& outSurfaceWeights) +{ + // TODO: implement +} + +void TerrainSystem::GetSurfacePoint( + const AZ::Vector3& inPosition, [[maybe_unused]] Sampler sampleFilter, SurfaceData::SurfacePoint& outSurfacePoint) +{ + // TODO: Handle sampleFilter + + float sampleX = inPosition.GetX(); + float sampleY = inPosition.GetY(); + + GetHeight(inPosition, sampleFilter, outSurfacePoint.m_position); + //outSurfacePoint.m_position = AZ::Vector3(sampleX, sampleY, GetHeightSynchronous(sampleX, sampleY)); + outSurfacePoint.m_normal = GetNormalSynchronous(sampleX, sampleY); +} + + + + +void TerrainSystem::ProcessHeightsFromRegion(const AZ::Aabb& inRegion, const AZ::Vector2 stepSize, Sampler sampleFilter, SurfacePointRegionFillCallback perPositionCallback, TerrainDataReadyCallback onComplete) +{ + // Don't bother processing if we don't have a callback + if (!perPositionCallback) + { + return; + } + + uint32_t numSamplesX = static_cast((inRegion.GetMax().GetX() - inRegion.GetMin().GetX()) / stepSize.GetX()); + uint32_t numSamplesY = static_cast((inRegion.GetMax().GetY() - inRegion.GetMin().GetY()) / stepSize.GetY()); + + for (uint32_t y = 0; y < numSamplesY; y++) + { + for (uint32_t x = 0; x < numSamplesX; x++) + { + float fx = (float)(inRegion.GetMin().GetX() + (x * stepSize.GetX())); + float fy = (float)(inRegion.GetMin().GetY() + (y * stepSize.GetY())); + + SurfaceData::SurfacePoint surfacePoint; + GetHeight(AZ::Vector3(fx, fy, 0.0f), sampleFilter, surfacePoint.m_position); + perPositionCallback(surfacePoint, x, y); + } + } + + if (onComplete) + { + onComplete(); + } +} + + +void TerrainSystem::ProcessSurfacePointsFromRegion(const AZ::Aabb& inRegion, const AZ::Vector2 stepSize, Sampler sampleFilter, SurfacePointRegionFillCallback perPositionCallback, TerrainDataReadyCallback onComplete) +{ + // Don't bother processing if we don't have a callback + if (!perPositionCallback) + { + return; + } + + uint32_t numSamplesX = static_cast((inRegion.GetMax().GetX() - inRegion.GetMin().GetX()) / stepSize.GetX()); + uint32_t numSamplesY = static_cast((inRegion.GetMax().GetY() - inRegion.GetMin().GetY()) / stepSize.GetY()); + + for (uint32_t y = 0; y < numSamplesY; y++) + { + for (uint32_t x = 0; x < numSamplesX; x++) + { + float fx = (float)(inRegion.GetMin().GetX() + (x * stepSize.GetX())); + float fy = (float)(inRegion.GetMin().GetY() + (y * stepSize.GetY())); + + SurfaceData::SurfacePoint surfacePoint; + GetSurfacePoint(AZ::Vector3(fx, fy, inRegion.GetMin().GetZ()), sampleFilter, surfacePoint); + perPositionCallback(surfacePoint, x, y); + } + } + + if (onComplete) + { + onComplete(); + } +} +*/ + +void TerrainSystem::SystemActivate() +{ + { + AZStd::shared_lock lock(m_areaMutex); + m_registeredAreas.clear(); + } + + AzFramework::Terrain::TerrainDataRequestBus::Handler::BusConnect(); + + TerrainAreaRequestBus::Broadcast(&TerrainAreaRequestBus::Events::RegisterArea); +} + +void TerrainSystem::SystemDeactivate() +{ + AzFramework::Terrain::TerrainDataRequestBus::Handler::BusDisconnect(); + + { + AZStd::shared_lock lock(m_areaMutex); + m_registeredAreas.clear(); + } + + const AZ::RPI::Scene* scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene().get(); + auto terrainFeatureProcessor = scene->GetFeatureProcessor(); + if (terrainFeatureProcessor) + { + terrainFeatureProcessor->RemoveTerrainData(); + } +} + +void TerrainSystem::RegisterArea(AZ::EntityId areaId) +{ + { + AZStd::unique_lock lock(m_areaMutex); + AZ::Aabb aabb = AZ::Aabb::CreateNull(); + LmbrCentral::ShapeComponentRequestsBus::EventResult(aabb, areaId, &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb); + m_registeredAreas[areaId] = aabb; + } + + RefreshArea(areaId); +} + +void TerrainSystem::UnregisterArea(AZ::EntityId areaId) +{ + { + AZStd::unique_lock lock(m_areaMutex); + AZ::Aabb aabb = AZ::Aabb::CreateNull(); + LmbrCentral::ShapeComponentRequestsBus::EventResult(aabb, areaId, &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb); + m_registeredAreas.erase(areaId); + } + + RefreshArea(areaId); +} + +void TerrainSystem::RefreshArea(AZ::EntityId areaId) +{ + AZStd::unique_lock lock(m_areaMutex); + + auto areaAabb = m_registeredAreas.find(areaId); + + AZ::Aabb oldAabb = (areaAabb != m_registeredAreas.end()) ? areaAabb->second : AZ::Aabb::CreateNull(); + AZ::Aabb newAabb = AZ::Aabb::CreateNull(); + LmbrCentral::ShapeComponentRequestsBus::EventResult(newAabb, areaId, &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb); + + m_registeredAreas[areaId] = newAabb; + + AZ::Aabb expandedAabb = oldAabb; + expandedAabb.AddAabb(newAabb); + + m_dirtyRegion.AddAabb(expandedAabb); + m_terrainHeightDirty = true; +} + +void TerrainSystem::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/) +{ + bool terrainSettingsChanged = false; + + if (m_terrainSettingsDirty) + { + m_terrainSettingsDirty = false; + + // This needs to happen before the "system active" check below, because activating the system will cause the various + // terrain layer areas to request the current world bounds. + if (m_requestedSettings.m_worldBounds != m_currentSettings.m_worldBounds) + { + m_dirtyRegion = m_currentSettings.m_worldBounds; + m_dirtyRegion.AddAabb(m_requestedSettings.m_worldBounds); + m_terrainHeightDirty = true; + m_currentSettings.m_worldBounds = m_requestedSettings.m_worldBounds; + terrainSettingsChanged = true; + } + + if (m_requestedSettings.m_debugWireframeEnabled != m_currentSettings.m_debugWireframeEnabled) + { + m_dirtyRegion = AZ::Aabb::CreateNull(); + m_terrainHeightDirty = true; + terrainSettingsChanged = true; + } + + if (m_requestedSettings.m_heightQueryResolution != m_currentSettings.m_heightQueryResolution) + { + m_dirtyRegion = AZ::Aabb::CreateNull(); + m_terrainHeightDirty = true; + terrainSettingsChanged = true; + } + + if (m_requestedSettings.m_systemActive != m_currentSettings.m_systemActive) + { + m_requestedSettings.m_systemActive ? SystemActivate() : SystemDeactivate(); + + // Null dirty region will be interpreted as updating everything + m_dirtyRegion = AZ::Aabb::CreateNull(); + m_terrainHeightDirty = true; + terrainSettingsChanged = true; + } + + m_currentSettings = m_requestedSettings; + } + + if (m_currentSettings.m_systemActive && m_terrainHeightDirty) + { + AZStd::shared_lock lock(m_areaMutex); + + AZ::EntityId entityId(0); + AZ::Transform transform = AZ::Transform::CreateTranslation(m_currentSettings.m_worldBounds.GetCenter()); + + uint32_t width = aznumeric_cast( + (float)m_currentSettings.m_worldBounds.GetXExtent() / m_currentSettings.m_heightQueryResolution.GetX()); + uint32_t height = aznumeric_cast( + (float)m_currentSettings.m_worldBounds.GetYExtent() / m_currentSettings.m_heightQueryResolution.GetY()); + AZStd::vector pixels; + pixels.resize(width * height); + const uint32_t pixelDataSize = width * height * sizeof(float); + memset(pixels.data(), 0, pixelDataSize); + + for (auto& [areaId, areaBounds] : m_registeredAreas) + { + for (uint32_t y = 0; y < height; y++) + { + for (uint32_t x = 0; x < width; x++) + { + AZ::Vector3 inPosition( + (x * m_currentSettings.m_heightQueryResolution.GetX()) + m_currentSettings.m_worldBounds.GetMin().GetX(), + (y * m_currentSettings.m_heightQueryResolution.GetY()) + m_currentSettings.m_worldBounds.GetMin().GetY(), + areaBounds.GetMin().GetZ()); + if (areaBounds.Contains(inPosition)) + { + AZ::Vector3 outPosition; + const Terrain::TerrainAreaHeightRequests::Sampler sampleFilter = + Terrain::TerrainAreaHeightRequests::Sampler::DEFAULT; + + Terrain::TerrainAreaHeightRequestBus::Event( + areaId, &Terrain::TerrainAreaHeightRequestBus::Events::GetHeight, inPosition, outPosition, sampleFilter); + + pixels[(y * width) + x] = (outPosition.GetZ() - m_currentSettings.m_worldBounds.GetMin().GetZ()) / + m_currentSettings.m_worldBounds.GetExtents().GetZ(); + } + } + } + } + + const AZ::RPI::Scene* scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene().get(); + auto terrainFeatureProcessor = scene->GetFeatureProcessor(); + + AZ_Assert(terrainFeatureProcessor, "Unable to find a TerrainFeatureProcessor."); + if (terrainFeatureProcessor) + { + terrainFeatureProcessor->UpdateTerrainData( + entityId, transform, m_currentSettings.m_worldBounds, m_currentSettings.m_heightQueryResolution.GetX(), width, height, + pixels); + } + } + + if (terrainSettingsChanged || m_terrainHeightDirty) + { + AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask = + AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask::None; + + if (terrainSettingsChanged) + { + changeMask = static_cast( + changeMask | AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask::Settings); + } + if (m_terrainHeightDirty) + { + changeMask = static_cast( + changeMask | AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask::HeightData); + } + + // Make sure to set these *before* calling OnTerrainDataChanged, since it's possible that subsystems reacting to that call will + // cause the data to become dirty again. + AZ::Aabb dirtyRegion = m_dirtyRegion; + m_terrainHeightDirty = false; + m_dirtyRegion = AZ::Aabb::CreateNull(); + + AzFramework::Terrain::TerrainDataNotificationBus::Broadcast( + &AzFramework::Terrain::TerrainDataNotificationBus::Events::OnTerrainDataChanged, dirtyRegion, + changeMask); + } + +} diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h new file mode 100644 index 0000000000..56ed182047 --- /dev/null +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h @@ -0,0 +1,120 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace Terrain +{ + class TerrainSystem + : public AzFramework::Terrain::TerrainDataRequestBus::Handler + , private Terrain::TerrainSystemServiceRequestBus::Handler + , private AZ::TickBus::Handler + { + public: + TerrainSystem(); + ~TerrainSystem(); + + /////////////////////////////////////////// + // TerrainSystemServiceRequestBus::Handler Impl + + void SetWorldMin(AZ::Vector3 worldOrigin) override; + void SetWorldMax(AZ::Vector3 worldBounds) override; + void SetHeightQueryResolution(AZ::Vector2 queryResolution) override; + void SetDebugWireframe(bool wireframeEnabled) override; + + void Activate() override; + void Deactivate() override; + + void RegisterArea(AZ::EntityId areaId) override; + void UnregisterArea(AZ::EntityId areaId) override; + void RefreshArea(AZ::EntityId areaId) override; + + /////////////////////////////////////////// + // TerrainDataRequestBus::Handler Impl + AZ::Vector2 GetTerrainGridResolution() const override; + AZ::Aabb GetTerrainAabb() const override; + + //! Returns terrains height in meters at location x,y. + //! @terrainExistsPtr: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside a terrain + //! HOLE then *terrainExistsPtr will become false, + //! otherwise *terrainExistsPtr will become true. + float GetHeight(AZ::Vector3 position, Sampler sampler = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const override; + float GetHeightFromFloats(float x, float y, Sampler sampler = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const override; + + //! Given an XY coordinate, return the max surface type and weight. + //! @terrainExists: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside a terrain + //! HOLE then *terrainExistsPtr will be set to false, + //! otherwise *terrainExistsPtr will be set to true. + AzFramework::SurfaceData::SurfaceTagWeight GetMaxSurfaceWeight( + AZ::Vector3 position, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const override; + AzFramework::SurfaceData::SurfaceTagWeight GetMaxSurfaceWeightFromFloats( + float x, float y, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const override; + + //! Convenience function for low level systems that can't do a reverse lookup from Crc to string. Everyone else should use + //! GetMaxSurfaceWeight or GetMaxSurfaceWeightFromFloats. Not available in the behavior context. Returns nullptr if the position is + //! inside a hole or outside of the terrain boundaries. + const char* GetMaxSurfaceName( + AZ::Vector3 position, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const override; + + //! Returns true if there's a hole at location x,y. + //! Also returns true if there's no terrain data at location x,y. + bool GetIsHoleFromFloats(float x, float y, Sampler sampleFilter = Sampler::BILINEAR) const override; + + // Given an XY coordinate, return the surface normal. + //! @terrainExists: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside a terrain + //! HOLE then *terrainExistsPtr will be set to false, + //! otherwise *terrainExistsPtr will be set to true. + AZ::Vector3 GetNormal( + AZ::Vector3 position, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const override; + AZ::Vector3 GetNormalFromFloats( + float x, float y, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const override; + + private: + float GetHeightSynchronous(float x, float y) const; + AZ::Vector3 GetNormalSynchronous(float x, float y) const; + + // AZ::TickBus::Handler overrides ... + void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; + + void SystemActivate(); + void SystemDeactivate(); + + struct TerrainSystemSettings + { + AZ::Aabb m_worldBounds; + AZ::Vector2 m_heightQueryResolution{ 1.0f }; + bool m_debugWireframeEnabled{ false }; + bool m_systemActive{ false }; + }; + + TerrainSystemSettings m_currentSettings; + TerrainSystemSettings m_requestedSettings; + + bool m_terrainSettingsDirty = true; + bool m_terrainHeightDirty = false; + AZ::Aabb m_dirtyRegion; + + mutable AZStd::shared_mutex m_areaMutex; + AZStd::unordered_map m_registeredAreas; + }; +} // namespace Terrain diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h new file mode 100644 index 0000000000..08521fd780 --- /dev/null +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h @@ -0,0 +1,120 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace Terrain +{ + /** + * A bus to signal the life times of terrain areas + * Note: all the API are meant to be queued events + */ + class TerrainSystemServiceRequests + : public AZ::EBusTraits + { + public: + //////////////////////////////////////////////////////////////////////// + // EBusTraits + // singleton pattern + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + using MutexType = AZStd::recursive_mutex; + //////////////////////////////////////////////////////////////////////// + + virtual ~TerrainSystemServiceRequests() = default; + + virtual void Activate() = 0; + virtual void Deactivate() = 0; + + virtual void SetWorldMin(AZ::Vector3 worldOrigin) = 0; + virtual void SetWorldMax(AZ::Vector3 worldBounds) = 0; + virtual void SetHeightQueryResolution(AZ::Vector2 queryResolution) = 0; + virtual void SetDebugWireframe(bool wireframeEnabled) = 0; + + // register an area to override terrain + virtual void RegisterArea(AZ::EntityId areaId) = 0; + virtual void UnregisterArea(AZ::EntityId areaId) = 0; + virtual void RefreshArea(AZ::EntityId areaId) = 0; + }; + + using TerrainSystemServiceRequestBus = AZ::EBus; + + /** + * A bus to signal the life times of terrain areas + * Note: all the API are meant to be queued events + */ + class TerrainAreaRequests + : public AZ::ComponentBus + { + public: + //////////////////////////////////////////////////////////////////////// + // EBusTraits + using MutexType = AZStd::recursive_mutex; + //////////////////////////////////////////////////////////////////////// + + virtual ~TerrainAreaRequests() = default; + + virtual void RegisterArea() = 0; + virtual void RefreshArea() = 0; + + }; + + using TerrainAreaRequestBus = AZ::EBus; + + /** + * A bus to signal the life times of terrain areas + * Note: all the API are meant to be queued events + */ + class TerrainAreaHeightRequests + : public AZ::ComponentBus + { + public: + //////////////////////////////////////////////////////////////////////// + // EBusTraits + using MutexType = AZStd::recursive_mutex; + //////////////////////////////////////////////////////////////////////// + + virtual ~TerrainAreaHeightRequests() = default; + + enum class Sampler + { + BILINEAR, // Get the value at the requested location, using terrain sample grid to bilinear filter between sample grid points + CLAMP, // Clamp the input point to the terrain sample grid, then get the exact value + EXACT, // Directly get the value at the location, regardless of terrain sample grid density + + DEFAULT = BILINEAR + }; + + enum SurfacePointDataMask + { + POSITION = 0x01, + NORMAL = 0x02, + SURFACE_WEIGHTS = 0x04, + + DEFAULT = POSITION | NORMAL | SURFACE_WEIGHTS + }; + + // Synchronous single input location. The Vector3 input position versions are defined to ignore the input Z value. + + virtual void GetHeight(const AZ::Vector3& inPosition, AZ::Vector3& outPosition, Sampler sampleFilter = Sampler::DEFAULT) = 0; + virtual void GetNormal(const AZ::Vector3& inPosition, AZ::Vector3& outNormal, Sampler sampleFilter = Sampler::DEFAULT) = 0; + //virtual void GetSurfaceWeights(const AZ::Vector3& inPosition, SurfaceTagWeightMap& outSurfaceWeights, Sampler sampleFilter = DEFAULT) = 0; + //virtual void GetSurfacePoint(const AZ::Vector3& inPosition, SurfacePoint& outSurfacePoint, SurfacePointDataMask dataMask = DEFAULT, Sampler sampleFilter = DEFAULT) = 0; + }; + + using TerrainAreaHeightRequestBus = AZ::EBus; + +} diff --git a/Gems/Terrain/Code/terrain_editor_shared_files.cmake b/Gems/Terrain/Code/terrain_editor_shared_files.cmake index 68ec9aeb54..9868f7d310 100644 --- a/Gems/Terrain/Code/terrain_editor_shared_files.cmake +++ b/Gems/Terrain/Code/terrain_editor_shared_files.cmake @@ -7,6 +7,14 @@ # set(FILES + Source/EditorComponents/EditorTerrainHeightGradientListComponent.cpp + Source/EditorComponents/EditorTerrainHeightGradientListComponent.h + Source/EditorComponents/EditorTerrainLayerSpawnerComponent.cpp + Source/EditorComponents/EditorTerrainLayerSpawnerComponent.h + Source/EditorComponents/EditorTerrainWorldComponent.cpp + Source/EditorComponents/EditorTerrainWorldComponent.h + Source/EditorComponents/EditorTerrainWorldDebuggerComponent.cpp + Source/EditorComponents/EditorTerrainWorldDebuggerComponent.h Source/EditorComponents/EditorTerrainSystemComponent.cpp Source/EditorComponents/EditorTerrainSystemComponent.h Source/EditorTerrainModule.cpp diff --git a/Gems/Terrain/Code/terrain_files.cmake b/Gems/Terrain/Code/terrain_files.cmake index c67d0c63b4..6a76dd7141 100644 --- a/Gems/Terrain/Code/terrain_files.cmake +++ b/Gems/Terrain/Code/terrain_files.cmake @@ -7,8 +7,21 @@ # set(FILES + Source/Components/TerrainHeightGradientListComponent.cpp + Source/Components/TerrainHeightGradientListComponent.h + Source/Components/TerrainLayerSpawnerComponent.cpp + Source/Components/TerrainLayerSpawnerComponent.h Source/Components/TerrainSurfaceDataSystemComponent.cpp Source/Components/TerrainSurfaceDataSystemComponent.h Source/Components/TerrainSystemComponent.cpp Source/Components/TerrainSystemComponent.h + Source/Components/TerrainWorldComponent.cpp + Source/Components/TerrainWorldComponent.h + Source/Components/TerrainWorldDebuggerComponent.cpp + Source/Components/TerrainWorldDebuggerComponent.h + Source/TerrainRenderer/TerrainFeatureProcessor.cpp + Source/TerrainRenderer/TerrainFeatureProcessor.h + Source/TerrainSystem/TerrainSystem.cpp + Source/TerrainSystem/TerrainSystem.h + Source/TerrainSystem/TerrainSystemBus.h )