Initial Terrain System (#3401)
This represents the very beginnings of the Terrain System presented in Sig-Content RFC 4 ( https://github.com/o3de/sig-content/blob/main/rfcs/rfc-4-terrain-system.md ). There is some basic working functionality in this PR, but the system as a whole should not be considered working yet. The gem is disabled by default in all projects. All of the code below is contained in the Terrain Gem, which is disabled by default. The following components exist and can be experimented with, but should not be expected to be functionally complete yet: Terrain World - level component for enabling terrain Terrain World Debugger - level component for enabling terrain debugging features Terrain Layer Spawner - component for defining a region of terrain Terrain Height Gradient List - component for defining a list of gradients to use as terrain heights Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>
This commit is contained in:
@@ -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 <Atom/Features/SrgSemantics.azsli>
|
||||
#include <viewsrg.srgi>
|
||||
|
||||
|
||||
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<float4> 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;
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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 <Components/TerrainHeightGradientListComponent.h>
|
||||
|
||||
#include <AzCore/Asset/AssetManagerBus.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Math/Aabb.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
|
||||
#include <GradientSignal/Ebuses/GradientRequestBus.h>
|
||||
#include <SurfaceData/SurfaceDataProviderRequestBus.h>
|
||||
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
void TerrainHeightGradientListConfig::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
if (serialize)
|
||||
{
|
||||
serialize->Class<TerrainHeightGradientListConfig, AZ::ComponentConfig>()
|
||||
->Version(1)
|
||||
->Field("GradientEntities", &TerrainHeightGradientListConfig::m_gradientEntities)
|
||||
;
|
||||
|
||||
AZ::EditContext* edit = serialize->GetEditContext();
|
||||
if (edit)
|
||||
{
|
||||
edit->Class<TerrainHeightGradientListConfig>(
|
||||
"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<AZ::SerializeContext*>(context);
|
||||
if (serialize)
|
||||
{
|
||||
serialize->Class<TerrainHeightGradientListComponent, AZ::Component>()
|
||||
->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<const TerrainHeightGradientListConfig*>(baseConfig))
|
||||
{
|
||||
m_configuration = *config;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TerrainHeightGradientListComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
|
||||
{
|
||||
if (auto config = azrtti_cast<TerrainHeightGradientListConfig*>(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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/Component/EntityBus.h>
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzCore/Jobs/JobManagerBus.h>
|
||||
#include <AzCore/Jobs/JobFunction.h>
|
||||
#include <AzCore/Math/Vector3.h>
|
||||
#include <AzCore/Math/Aabb.h>
|
||||
|
||||
#include <LmbrCentral/Dependency/DependencyMonitor.h>
|
||||
#include <LmbrCentral/Dependency/DependencyNotificationBus.h>
|
||||
#include <LmbrCentral/Shape/ShapeComponentBus.h>
|
||||
|
||||
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
|
||||
#include <TerrainSystem/TerrainSystemBus.h>
|
||||
|
||||
|
||||
namespace LmbrCentral
|
||||
{
|
||||
template<typename, typename>
|
||||
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<AZ::EntityId> m_gradientEntities;
|
||||
};
|
||||
|
||||
|
||||
class TerrainHeightGradientListComponent
|
||||
: public AZ::Component
|
||||
, private Terrain::TerrainAreaHeightRequestBus::Handler
|
||||
, private LmbrCentral::DependencyNotificationBus::Handler
|
||||
, private AzFramework::Terrain::TerrainDataNotificationBus::Handler
|
||||
{
|
||||
public:
|
||||
template<typename, typename>
|
||||
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;
|
||||
};
|
||||
}
|
||||
@@ -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 <Components/TerrainLayerSpawnerComponent.h>
|
||||
|
||||
#include <AzCore/Asset/AssetManagerBus.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Math/Aabb.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
|
||||
#include <GradientSignal/Ebuses/GradientRequestBus.h>
|
||||
#include <SurfaceData/SurfaceDataProviderRequestBus.h>
|
||||
#include <TerrainSystem/TerrainSystemBus.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
void TerrainLayerSpawnerConfig::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
if (serialize)
|
||||
{
|
||||
serialize->Class<TerrainLayerSpawnerConfig, AZ::ComponentConfig>()
|
||||
->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<TerrainLayerSpawnerConfig>(
|
||||
"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<AZStd::pair<AZ::u32, AZStd::string>> TerrainLayerSpawnerConfig::GetSelectableLayers() const
|
||||
{
|
||||
AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> selectableLayers;
|
||||
selectableLayers.push_back({ AreaConstants::s_backgroundLayer, AZStd::string("Background") });
|
||||
selectableLayers.push_back({ AreaConstants::s_foregroundLayer, AZStd::string("Foreground") });
|
||||
return selectableLayers;
|
||||
}
|
||||
|
||||
|
||||
void 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<AZ::SerializeContext*>(context);
|
||||
if (serialize)
|
||||
{
|
||||
serialize->Class<TerrainLayerSpawnerComponent, AZ::Component>()
|
||||
->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<const TerrainLayerSpawnerConfig*>(baseConfig))
|
||||
{
|
||||
m_configuration = *config;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TerrainLayerSpawnerComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
|
||||
{
|
||||
if (auto config = azrtti_cast<TerrainLayerSpawnerConfig*>(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());
|
||||
}
|
||||
}
|
||||
@@ -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 <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/Math/Vector3.h>
|
||||
|
||||
#include <AzCore/Jobs/JobManagerBus.h>
|
||||
#include <AzCore/Jobs/JobFunction.h>
|
||||
|
||||
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
|
||||
#include <TerrainSystem/TerrainSystemBus.h>
|
||||
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <LmbrCentral/Shape/ShapeComponentBus.h>
|
||||
#include <AzCore/Math/Aabb.h>
|
||||
|
||||
namespace LmbrCentral
|
||||
{
|
||||
template<typename, typename>
|
||||
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<AZ::u32>::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<AZStd::pair<AZ::u32, AZStd::string>> 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<typename, typename>
|
||||
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;
|
||||
};
|
||||
}
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <AzCore/Serialization/EditContextConstants.inl>
|
||||
|
||||
#include <Atom/RPI.Public/FeatureProcessorFactory.h>
|
||||
#include <TerrainRenderer/TerrainFeatureProcessor.h>
|
||||
#include <TerrainSystem/TerrainSystem.h>
|
||||
|
||||
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<Terrain::TerrainFeatureProcessor>();
|
||||
}
|
||||
|
||||
void TerrainSystemComponent::Deactivate()
|
||||
{
|
||||
delete m_terrainSystem;
|
||||
m_terrainSystem = nullptr;
|
||||
|
||||
AZ::RPI::FeatureProcessorFactory::Get()->UnregisterFeatureProcessor<Terrain::TerrainFeatureProcessor>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <Components/TerrainWorldComponent.h>
|
||||
#include <AzCore/Asset/AssetManagerBus.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
void TerrainWorldConfig::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
if (serialize)
|
||||
{
|
||||
serialize->Class<TerrainWorldConfig, AZ::ComponentConfig>()
|
||||
->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<TerrainWorldConfig>(
|
||||
"Terrain World Component", "Data required for the terrain system to run")
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector<AZ::Crc32>({ 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<AZ::SerializeContext*>(context);
|
||||
if (serialize)
|
||||
{
|
||||
serialize->Class<TerrainWorldComponent, AZ::Component>()
|
||||
->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<const TerrainWorldConfig*>(baseConfig))
|
||||
{
|
||||
m_configuration = *config;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TerrainWorldComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
|
||||
{
|
||||
if (auto config = azrtti_cast<TerrainWorldConfig*>(outBaseConfig))
|
||||
{
|
||||
*config = m_configuration;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -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 <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/Math/Vector3.h>
|
||||
#include <TerrainSystem/TerrainSystem.h>
|
||||
|
||||
namespace LmbrCentral
|
||||
{
|
||||
template<typename, typename>
|
||||
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<typename, typename>
|
||||
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;
|
||||
};
|
||||
}
|
||||
@@ -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 <Components/TerrainWorldDebuggerComponent.h>
|
||||
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
#include <AzCore/Asset/AssetManagerBus.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
|
||||
#include <AzFramework/Visibility/EntityBoundsUnionBus.h>
|
||||
|
||||
#include <Atom/RPI.Public/View.h>
|
||||
#include <Atom/RPI.Public/ViewportContext.h>
|
||||
#include <Atom/RPI.Public/ViewportContextBus.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
void TerrainWorldDebuggerConfig::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
if (serialize)
|
||||
{
|
||||
serialize->Class<TerrainWorldDebuggerConfig, AZ::ComponentConfig>()
|
||||
->Version(1)
|
||||
->Field("DebugWireframe", &TerrainWorldDebuggerConfig::m_drawWireframe)
|
||||
->Field("DebugWorldBounds", &TerrainWorldDebuggerConfig::m_drawWorldBounds)
|
||||
;
|
||||
|
||||
AZ::EditContext* edit = serialize->GetEditContext();
|
||||
if (edit)
|
||||
{
|
||||
edit->Class<TerrainWorldDebuggerConfig>(
|
||||
"Terrain World Debugger Component", "Optional component for enabling terrain debugging features.")
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector<AZ::Crc32>({ 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<AZ::SerializeContext*>(context);
|
||||
if (serialize)
|
||||
{
|
||||
serialize->Class<TerrainWorldDebuggerComponent, AZ::Component>()
|
||||
->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<const TerrainWorldDebuggerConfig*>(baseConfig))
|
||||
{
|
||||
m_configuration = *config;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TerrainWorldDebuggerComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
|
||||
{
|
||||
if (auto config = azrtti_cast<TerrainWorldDebuggerConfig*>(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<int32_t>((worldBounds.GetXExtent() + xSectorSize) / xSectorSize);
|
||||
const int32_t numSectorsY = aznumeric_cast<int32_t>((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
|
||||
@@ -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 <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/Math/Vector3.h>
|
||||
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
|
||||
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
|
||||
#include <AzFramework/Visibility/BoundsBus.h>
|
||||
#include <TerrainSystem/TerrainSystem.h>
|
||||
|
||||
namespace LmbrCentral
|
||||
{
|
||||
template<typename, typename>
|
||||
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<typename, typename>
|
||||
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<AZ::Vector3> 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<WireframeSector> m_wireframeSectors;
|
||||
AZ::Aabb m_wireframeBounds;
|
||||
};
|
||||
}
|
||||
+22
@@ -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 <EditorComponents/EditorTerrainHeightGradientListComponent.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
void EditorTerrainHeightGradientListComponent::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
BaseClassType::ReflectSubClass<EditorTerrainHeightGradientListComponent, BaseClassType>(context, 1,
|
||||
&LmbrCentral::EditorWrappedComponentBaseVersionConverter<typename BaseClassType::WrappedComponentType,
|
||||
typename BaseClassType::WrappedConfigType, 1>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 <Components/TerrainHeightGradientListComponent.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
|
||||
#include <LmbrCentral/Component/EditorWrappedComponentBase.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
class EditorTerrainHeightGradientListComponent
|
||||
: public LmbrCentral::EditorWrappedComponentBase<TerrainHeightGradientListComponent, TerrainHeightGradientListConfig>
|
||||
{
|
||||
public:
|
||||
using BaseClassType = LmbrCentral::EditorWrappedComponentBase<TerrainHeightGradientListComponent, TerrainHeightGradientListConfig>;
|
||||
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 = "";
|
||||
};
|
||||
}
|
||||
@@ -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 <EditorComponents/EditorTerrainLayerSpawnerComponent.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
void EditorTerrainLayerSpawnerComponent::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
BaseClassType::ReflectSubClass<EditorTerrainLayerSpawnerComponent, BaseClassType>(context, 1,
|
||||
&LmbrCentral::EditorWrappedComponentBaseVersionConverter<typename BaseClassType::WrappedComponentType,
|
||||
typename BaseClassType::WrappedConfigType, 1>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 <Components/TerrainLayerSpawnerComponent.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
|
||||
#include <LmbrCentral/Component/EditorWrappedComponentBase.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
class EditorTerrainLayerSpawnerComponent
|
||||
: public LmbrCentral::EditorWrappedComponentBase<TerrainLayerSpawnerComponent, TerrainLayerSpawnerConfig>
|
||||
{
|
||||
public:
|
||||
using BaseClassType = LmbrCentral::EditorWrappedComponentBase<TerrainLayerSpawnerComponent, TerrainLayerSpawnerConfig>;
|
||||
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 = "";
|
||||
};
|
||||
}
|
||||
@@ -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 <EditorComponents/EditorTerrainWorldComponent.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
void EditorTerrainWorldComponent::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
BaseClassType::Reflect(context);
|
||||
|
||||
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->Class<EditorTerrainWorldComponent, BaseClassType>()
|
||||
->Version(0)
|
||||
;
|
||||
|
||||
if (auto editContext = serializeContext->GetEditContext())
|
||||
{
|
||||
editContext->Class<EditorTerrainWorldComponent>(
|
||||
"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::Crc32>({ AZ_CRC_CE("Level") }))
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void EditorTerrainWorldComponent::Init()
|
||||
{
|
||||
BaseClassType::Init();
|
||||
}
|
||||
|
||||
void EditorTerrainWorldComponent::Activate()
|
||||
{
|
||||
BaseClassType::Activate();
|
||||
}
|
||||
|
||||
AZ::u32 EditorTerrainWorldComponent::ConfigurationChanged()
|
||||
{
|
||||
return BaseClassType::ConfigurationChanged();
|
||||
}
|
||||
}
|
||||
@@ -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 <Components/TerrainWorldComponent.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
|
||||
#include <LmbrCentral/Component/EditorWrappedComponentBase.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
class EditorTerrainWorldComponent
|
||||
: public LmbrCentral::EditorWrappedComponentBase<TerrainWorldComponent, TerrainWorldConfig>
|
||||
{
|
||||
public:
|
||||
using BaseClassType = LmbrCentral::EditorWrappedComponentBase<TerrainWorldComponent, TerrainWorldConfig>;
|
||||
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:
|
||||
};
|
||||
}
|
||||
@@ -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 <EditorComponents/EditorTerrainWorldDebuggerComponent.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
void EditorTerrainWorldDebuggerComponent::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
BaseClassType::Reflect(context);
|
||||
|
||||
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->Class<EditorTerrainWorldDebuggerComponent, BaseClassType>()
|
||||
->Version(0)
|
||||
;
|
||||
|
||||
if (auto editContext = serializeContext->GetEditContext())
|
||||
{
|
||||
editContext->Class<EditorTerrainWorldDebuggerComponent>(
|
||||
"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::Crc32>({ AZ_CRC_CE("Level") }))
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void EditorTerrainWorldDebuggerComponent::Init()
|
||||
{
|
||||
BaseClassType::Init();
|
||||
}
|
||||
|
||||
void EditorTerrainWorldDebuggerComponent::Activate()
|
||||
{
|
||||
BaseClassType::Activate();
|
||||
}
|
||||
|
||||
AZ::u32 EditorTerrainWorldDebuggerComponent::ConfigurationChanged()
|
||||
{
|
||||
return BaseClassType::ConfigurationChanged();
|
||||
}
|
||||
}
|
||||
@@ -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 <Components/TerrainWorldDebuggerComponent.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
|
||||
#include <LmbrCentral/Component/EditorWrappedComponentBase.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
class EditorTerrainWorldDebuggerComponent
|
||||
: public LmbrCentral::EditorWrappedComponentBase<TerrainWorldDebuggerComponent, TerrainWorldDebuggerConfig>
|
||||
{
|
||||
public:
|
||||
using BaseClassType = LmbrCentral::EditorWrappedComponentBase<TerrainWorldDebuggerComponent, TerrainWorldDebuggerConfig>;
|
||||
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:
|
||||
};
|
||||
}
|
||||
@@ -7,7 +7,11 @@
|
||||
*/
|
||||
|
||||
#include <EditorTerrainModule.h>
|
||||
#include <EditorComponents/EditorTerrainHeightGradientListComponent.h>
|
||||
#include <EditorComponents/EditorTerrainLayerSpawnerComponent.h>
|
||||
#include <EditorComponents/EditorTerrainSystemComponent.h>
|
||||
#include <EditorComponents/EditorTerrainWorldComponent.h>
|
||||
#include <EditorComponents/EditorTerrainWorldDebuggerComponent.h>
|
||||
|
||||
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(),
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
|
||||
#include <TerrainModule.h>
|
||||
#include <Components/TerrainSystemComponent.h>
|
||||
#include <Components/TerrainWorldComponent.h>
|
||||
#include <Components/TerrainWorldDebuggerComponent.h>
|
||||
#include <Components/TerrainHeightGradientListComponent.h>
|
||||
#include <Components/TerrainLayerSpawnerComponent.h>
|
||||
#include <Components/TerrainSurfaceDataSystemComponent.h>
|
||||
|
||||
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(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 <TerrainRenderer/TerrainFeatureProcessor.h>
|
||||
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
|
||||
#include <Atom/Utils/Utils.h>
|
||||
|
||||
#include <Atom/RHI/DrawPacketBuilder.h>
|
||||
#include <Atom/RHI/Factory.h>
|
||||
#include <Atom/RPI.Public/Shader/Shader.h>
|
||||
#include <Atom/RPI.Public/Scene.h>
|
||||
#include <Atom/RPI.Public/View.h>
|
||||
#include <Atom/RPI.Public/AuxGeom/AuxGeomFeatureProcessorInterface.h>
|
||||
#include <Atom/RPI.Public/AuxGeom/AuxGeomDraw.h>
|
||||
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
|
||||
#include <Atom/RPI.Public/Image/StreamingImagePool.h>
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
|
||||
#include <Atom/RPI.Reflect/Image/ImageMipChainAssetCreator.h>
|
||||
#include <Atom/RPI.Reflect/Image/StreamingImageAssetCreator.h>
|
||||
#include <Atom/RHI.Reflect/InputStreamLayout.h>
|
||||
#include <Atom/RHI.Reflect/InputStreamLayoutBuilder.h>
|
||||
|
||||
|
||||
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<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serialize->Class<TerrainFeatureProcessor, AZ::RPI::FeatureProcessor>()
|
||||
->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<AZ::RPI::ShaderAsset>(), 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<AZ::RPI::ShaderAsset>(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<float>& 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<AZ::RPI::StreamingImagePool> 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<uint32_t>(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<uint16_t>(startIndex + 1));
|
||||
m_gridIndices.emplace_back(aznumeric_cast<uint16_t>(startIndex + 2));
|
||||
m_gridIndices.emplace_back(aznumeric_cast<uint16_t>(startIndex + 1));
|
||||
m_gridIndices.emplace_back(aznumeric_cast<uint16_t>(startIndex + 2));
|
||||
m_gridIndices.emplace_back(aznumeric_cast<uint16_t>(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<AZ::RHI::Ptr<AZ::RHI::Buffer>> 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<AZ::RHI::Buffer> 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<uint8_t*>(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<uint32_t>(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<uint32_t>(elementSize), static_cast<uint32_t>(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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <AzCore/Component/Component.h>
|
||||
#include <AzCore/Component/TickBus.h>
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <LmbrCentral/Shape/ShapeComponentBus.h>
|
||||
|
||||
#include <Atom/RPI.Public/FeatureProcessor.h>
|
||||
#include <Atom/RPI.Public/Shader/Shader.h>
|
||||
|
||||
#include <Atom/RPI.Public/Image/StreamingImage.h>
|
||||
#include <Atom/RHI/ShaderResourceGroup.h>
|
||||
#include <Atom/RHI/BufferPool.h>
|
||||
#include <Atom/RHI/DrawPacket.h>
|
||||
#include <Atom/RHI/IndexBufferView.h>
|
||||
#include <Atom/RHI/PipelineState.h>
|
||||
#include <Atom/RHI/StreamBufferView.h>
|
||||
#include <Atom/RHI/RHISystemInterface.h>
|
||||
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
|
||||
|
||||
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<float>& 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<AZ::RPI::Shader> m_shader{};
|
||||
AZ::RHI::PipelineStateDescriptorForDraw m_pipelineStateDescriptor;
|
||||
AZ::RHI::ConstPtr<AZ::RHI::PipelineState> m_pipelineState = nullptr;
|
||||
AZ::RHI::DrawListTag m_drawListTag;
|
||||
AZ::RHI::Ptr<AZ::RHI::ShaderResourceGroupLayout> 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<Vertex> m_gridVertices;
|
||||
AZStd::vector<uint16_t> m_gridIndices;
|
||||
|
||||
// System-level data related to the grid patch
|
||||
AZ::RHI::Ptr<AZ::RHI::BufferPool> m_hostPool = nullptr;
|
||||
AZ::RHI::Ptr<AZ::RHI::Buffer> m_indexBuffer;
|
||||
AZ::RHI::Ptr<AZ::RHI::Buffer> m_vertexBuffer;
|
||||
AZ::RHI::IndexBufferView m_indexBufferView;
|
||||
AZStd::fixed_vector<AZ::RHI::StreamBufferView, AZ::RHI::Limits::Pipeline::StreamCountMax> m_vertexBufferViews;
|
||||
|
||||
// Per-area data
|
||||
struct TerrainAreaData
|
||||
{
|
||||
AZ::Transform m_transform;
|
||||
AZ::Aabb m_terrainBounds;
|
||||
float m_heightScale;
|
||||
AZ::Data::Instance<AZ::RPI::StreamingImage> m_heightmapImage;
|
||||
uint32_t m_heightmapImageWidth;
|
||||
uint32_t m_heightmapImageHeight;
|
||||
bool m_propertiesDirty{ true };
|
||||
};
|
||||
|
||||
AZStd::unordered_map<AZ::EntityId, TerrainAreaData> m_areaData;
|
||||
|
||||
// These could either be per-area or system-level
|
||||
AZStd::vector<AZStd::unique_ptr<const AZ::RHI::DrawPacket>> m_drawPackets;
|
||||
AZStd::vector<AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>> m_processSrgs;
|
||||
};
|
||||
}
|
||||
@@ -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 <TerrainSystem/TerrainSystem.h>
|
||||
#include <AzCore/std/parallel/shared_mutex.h>
|
||||
#include <SurfaceData/SurfaceDataTypes.h>
|
||||
#include <LmbrCentral/Shape/ShapeComponentBus.h>
|
||||
|
||||
#include <Atom/RPI.Public/Scene.h>
|
||||
#include <Atom/RPI.Public/FeatureProcessorFactory.h>
|
||||
#include <TerrainRenderer/TerrainFeatureProcessor.h>
|
||||
|
||||
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<AZStd::shared_mutex> 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<uint32_t>((inRegion.GetMax().GetX() - inRegion.GetMin().GetX()) / stepSize.GetX());
|
||||
uint32_t numSamplesY = static_cast<uint32_t>((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<uint32_t>((inRegion.GetMax().GetX() - inRegion.GetMin().GetX()) / stepSize.GetX());
|
||||
uint32_t numSamplesY = static_cast<uint32_t>((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<AZStd::shared_mutex> 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<AZStd::shared_mutex> lock(m_areaMutex);
|
||||
m_registeredAreas.clear();
|
||||
}
|
||||
|
||||
const AZ::RPI::Scene* scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene().get();
|
||||
auto terrainFeatureProcessor = scene->GetFeatureProcessor<TerrainFeatureProcessor>();
|
||||
if (terrainFeatureProcessor)
|
||||
{
|
||||
terrainFeatureProcessor->RemoveTerrainData();
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainSystem::RegisterArea(AZ::EntityId areaId)
|
||||
{
|
||||
{
|
||||
AZStd::unique_lock<AZStd::shared_mutex> 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<AZStd::shared_mutex> 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<AZStd::shared_mutex> 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<AZStd::shared_mutex> lock(m_areaMutex);
|
||||
|
||||
AZ::EntityId entityId(0);
|
||||
AZ::Transform transform = AZ::Transform::CreateTranslation(m_currentSettings.m_worldBounds.GetCenter());
|
||||
|
||||
uint32_t width = aznumeric_cast<uint32_t>(
|
||||
(float)m_currentSettings.m_worldBounds.GetXExtent() / m_currentSettings.m_heightQueryResolution.GetX());
|
||||
uint32_t height = aznumeric_cast<uint32_t>(
|
||||
(float)m_currentSettings.m_worldBounds.GetYExtent() / m_currentSettings.m_heightQueryResolution.GetY());
|
||||
AZStd::vector<float> 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<TerrainFeatureProcessor>();
|
||||
|
||||
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<AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask>(
|
||||
changeMask | AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask::Settings);
|
||||
}
|
||||
if (m_terrainHeightDirty)
|
||||
{
|
||||
changeMask = static_cast<AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask>(
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <AzCore/std/containers/fixed_vector.h>
|
||||
#include <AzCore/Math/Vector3.h>
|
||||
#include <AzCore/std/algorithm.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzCore/std/parallel/shared_mutex.h>
|
||||
#include <AzCore/Math/Color.h>
|
||||
#include <AzCore/Math/Aabb.h>
|
||||
|
||||
#include <AzCore/Component/TickBus.h>
|
||||
#include <AzCore/Jobs/JobManagerBus.h>
|
||||
#include <AzCore/Jobs/JobFunction.h>
|
||||
|
||||
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
|
||||
#include <TerrainSystem/TerrainSystemBus.h>
|
||||
|
||||
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<AZ::EntityId, AZ::Aabb> m_registeredAreas;
|
||||
};
|
||||
} // namespace Terrain
|
||||
@@ -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 <AzCore/Math/Vector2.h>
|
||||
#include <AzCore/Math/Aabb.h>
|
||||
#include <AzCore/std/functional.h>
|
||||
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
||||
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzCore/Component/ComponentBus.h>
|
||||
|
||||
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<TerrainSystemServiceRequests>;
|
||||
|
||||
/**
|
||||
* 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<TerrainAreaRequests>;
|
||||
|
||||
/**
|
||||
* 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<TerrainAreaHeightRequests>;
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user