TerrainHeightGradientListComponent.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <Components/TerrainHeightGradientListComponent.h>
  9. #include <AzCore/Asset/AssetManagerBus.h>
  10. #include <AzCore/Component/Entity.h>
  11. #include <AzCore/Asset/AssetManager.h>
  12. #include <AzCore/RTTI/BehaviorContext.h>
  13. #include <AzCore/Serialization/EditContext.h>
  14. #include <AzCore/Serialization/SerializeContext.h>
  15. #include <AzCore/Math/Aabb.h>
  16. #include <AzCore/std/smart_ptr/make_shared.h>
  17. #include <GradientSignal/Ebuses/GradientRequestBus.h>
  18. #include <SurfaceData/SurfaceDataProviderRequestBus.h>
  19. #include <TerrainProfiler.h>
  20. namespace Terrain
  21. {
  22. void TerrainHeightGradientListConfig::Reflect(AZ::ReflectContext* context)
  23. {
  24. AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
  25. if (serialize)
  26. {
  27. serialize->Class<TerrainHeightGradientListConfig, AZ::ComponentConfig>()
  28. ->Version(1)
  29. ->Field("GradientEntities", &TerrainHeightGradientListConfig::m_gradientEntities)
  30. ;
  31. AZ::EditContext* edit = serialize->GetEditContext();
  32. if (edit)
  33. {
  34. edit->Class<TerrainHeightGradientListConfig>(
  35. "Terrain Height Gradient List Component", "Provide height data for a region of the world")
  36. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  37. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
  38. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  39. ->DataElement(0, &TerrainHeightGradientListConfig::m_gradientEntities, "Gradient Entities", "Ordered list of gradients to use as height providers.")
  40. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  41. ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, true)
  42. ->Attribute(AZ::Edit::Attributes::RequiredService, AZ_CRC_CE("GradientService"))
  43. ;
  44. }
  45. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  46. {
  47. behaviorContext->Class<TerrainHeightGradientListConfig>()
  48. ->Attribute(AZ::Script::Attributes::Category, "Terrain")
  49. ->Constructor()
  50. ->Property("gradientEntities", BehaviorValueProperty(&TerrainHeightGradientListConfig::m_gradientEntities))
  51. ;
  52. }
  53. }
  54. }
  55. void TerrainHeightGradientListComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  56. {
  57. services.push_back(AZ_CRC_CE("TerrainHeightProviderService"));
  58. }
  59. void TerrainHeightGradientListComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  60. {
  61. services.push_back(AZ_CRC_CE("TerrainHeightProviderService"));
  62. services.push_back(AZ_CRC_CE("GradientService"));
  63. }
  64. void TerrainHeightGradientListComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  65. {
  66. services.push_back(AZ_CRC_CE("TerrainAreaService"));
  67. services.push_back(AZ_CRC_CE("AxisAlignedBoxShapeService"));
  68. }
  69. void TerrainHeightGradientListComponent::Reflect(AZ::ReflectContext* context)
  70. {
  71. TerrainHeightGradientListConfig::Reflect(context);
  72. AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
  73. if (serialize)
  74. {
  75. serialize->Class<TerrainHeightGradientListComponent, AZ::Component>()
  76. ->Version(0)
  77. ->Field("Configuration", &TerrainHeightGradientListComponent::m_configuration)
  78. ;
  79. }
  80. }
  81. TerrainHeightGradientListComponent::TerrainHeightGradientListComponent(const TerrainHeightGradientListConfig& configuration)
  82. : m_configuration(configuration)
  83. {
  84. }
  85. void TerrainHeightGradientListComponent::Activate()
  86. {
  87. LmbrCentral::DependencyNotificationBus::Handler::BusConnect(GetEntityId());
  88. AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect();
  89. // Make sure we get update notifications whenever this entity or any dependent gradient entity changes in any way.
  90. // We'll use that to notify the terrain system that the height information needs to be refreshed.
  91. m_dependencyMonitor.Reset();
  92. m_dependencyMonitor.SetRegionChangedEntityNotificationFunction();
  93. m_dependencyMonitor.ConnectOwner(GetEntityId());
  94. m_dependencyMonitor.ConnectDependency(GetEntityId());
  95. for (auto& entityId : m_configuration.m_gradientEntities)
  96. {
  97. if (entityId != GetEntityId())
  98. {
  99. m_dependencyMonitor.ConnectDependency(entityId);
  100. }
  101. }
  102. Terrain::TerrainAreaHeightRequestBus::Handler::BusConnect(GetEntityId());
  103. // Cache any height data needed and notify that the area has changed.
  104. OnCompositionChanged();
  105. }
  106. void TerrainHeightGradientListComponent::Deactivate()
  107. {
  108. // Disconnect before doing any other teardown. This will guarantee that any active queries have finished before we proceed.
  109. Terrain::TerrainAreaHeightRequestBus::Handler::BusDisconnect();
  110. m_dependencyMonitor.Reset();
  111. AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusDisconnect();
  112. LmbrCentral::DependencyNotificationBus::Handler::BusDisconnect();
  113. // Since this height data will no longer exist, notify the terrain system to refresh the area.
  114. TerrainSystemServiceRequestBus::Broadcast(
  115. &TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId(),
  116. AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask::HeightData);
  117. }
  118. bool TerrainHeightGradientListComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
  119. {
  120. if (auto config = azrtti_cast<const TerrainHeightGradientListConfig*>(baseConfig))
  121. {
  122. m_configuration = *config;
  123. return true;
  124. }
  125. return false;
  126. }
  127. bool TerrainHeightGradientListComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
  128. {
  129. if (auto config = azrtti_cast<TerrainHeightGradientListConfig*>(outBaseConfig))
  130. {
  131. *config = m_configuration;
  132. return true;
  133. }
  134. return false;
  135. }
  136. void TerrainHeightGradientListComponent::GetHeight(
  137. const AZ::Vector3& inPosition,
  138. AZ::Vector3& outPosition,
  139. bool& terrainExists)
  140. {
  141. // Make sure we don't run queries simultaneously with changing any of the cached data.
  142. AZStd::shared_lock lock(m_queryMutex);
  143. float maxSample = 0.0f;
  144. terrainExists = false;
  145. AZ_ErrorOnce(
  146. "Terrain", !Terrain::TerrainAreaHeightRequestBus::HasReentrantEBusUseThisThread(),
  147. "Detected cyclic dependencies with terrain height entity references on entity '%s' (%s)", GetEntity()->GetName().c_str(),
  148. GetEntityId().ToString().c_str());
  149. if (!Terrain::TerrainAreaHeightRequestBus::HasReentrantEBusUseThisThread())
  150. {
  151. GradientSignal::GradientSampleParams params(inPosition);
  152. // Right now, when the list contains multiple entries, we will use the highest point from each gradient.
  153. // This is needed in part because gradients don't really have world bounds, so they exist everywhere but generally have a value
  154. // of 0 outside their data bounds if they're using bounded data. We should examine the possibility of extending the gradient
  155. // API to provide actual bounds so that it's possible to detect if the gradient even 'exists' in an area, at which point we
  156. // could just make this list a prioritized list from top to bottom for any points that overlap.
  157. for (auto& gradientId : m_configuration.m_gradientEntities)
  158. {
  159. if (gradientId.IsValid())
  160. {
  161. // If gradients ever provide bounds, or if we add a value threshold in this component, it would be possible for terrain
  162. // to *not* exist at a specific point.
  163. terrainExists = true;
  164. float sample = 0.0f;
  165. GradientSignal::GradientRequestBus::EventResult(
  166. sample, gradientId, &GradientSignal::GradientRequestBus::Events::GetValue, params);
  167. maxSample = AZ::GetMax(maxSample, sample);
  168. }
  169. }
  170. }
  171. const float height = AZ::Lerp(m_cachedShapeBounds.GetMin().GetZ(), m_cachedShapeBounds.GetMax().GetZ(), maxSample);
  172. outPosition.Set(inPosition.GetX(), inPosition.GetY(), AZ::GetClamp(height, m_cachedHeightBounds.m_min, m_cachedHeightBounds.m_max));
  173. }
  174. void TerrainHeightGradientListComponent::GetHeights(
  175. AZStd::span<AZ::Vector3> inOutPositionList, AZStd::span<bool> terrainExistsList)
  176. {
  177. TERRAIN_PROFILE_FUNCTION_VERBOSE
  178. // Make sure we don't run queries simultaneously with changing any of the cached data.
  179. AZStd::shared_lock lock(m_queryMutex);
  180. AZ_Assert(
  181. inOutPositionList.size() == terrainExistsList.size(), "The position list size doesn't match the terrainExists list size.");
  182. AZ_ErrorOnce(
  183. "Terrain", !Terrain::TerrainAreaHeightRequestBus::HasReentrantEBusUseThisThread(),
  184. "Detected cyclic dependencies with terrain height entity references on entity '%s' (%s)", GetEntity()->GetName().c_str(),
  185. GetEntityId().ToString().c_str());
  186. if (!Terrain::TerrainAreaHeightRequestBus::HasReentrantEBusUseThisThread())
  187. {
  188. // Start by initializing all our terrainExists flags to false.
  189. AZStd::fill(terrainExistsList.begin(), terrainExistsList.end(), false);
  190. // Create a temporary buffer for storing all the gradient values for the currently-queried gradient.
  191. AZStd::vector<float> curGradientSamples(inOutPositionList.size());
  192. // Create a temporary buffer for storing all the max gradient values.
  193. AZStd::vector<float> maxValueSamples(inOutPositionList.size());
  194. // Right now, when the list contains multiple entries, we will use the highest point from each gradient.
  195. // This is needed in part because gradients don't really have world bounds, so they exist everywhere but generally have a
  196. // value of 0 outside their data bounds if they're using bounded data. We should examine the possibility of extending the
  197. // gradient API to provide actual bounds so that it's possible to detect if the gradient even 'exists' in an area, at which
  198. // point we could just make this list a prioritized list from top to bottom for any points that overlap.
  199. for (auto& gradientId : m_configuration.m_gradientEntities)
  200. {
  201. if (gradientId.IsValid())
  202. {
  203. GradientSignal::GradientRequestBus::Event(
  204. gradientId, &GradientSignal::GradientRequestBus::Events::GetValues, inOutPositionList, curGradientSamples);
  205. for (size_t index = 0; index < maxValueSamples.size(); index++)
  206. {
  207. maxValueSamples[index] = AZ::GetMax(maxValueSamples[index], curGradientSamples[index]);
  208. // If gradients ever provide bounds, or if we add a value threshold in this component, it would be possible for
  209. // terrain to *not* exist at a specific point.
  210. terrainExistsList[index] = true;
  211. }
  212. }
  213. }
  214. for (size_t index = 0; index < inOutPositionList.size(); index++)
  215. {
  216. if (terrainExistsList[index])
  217. {
  218. const float height =
  219. AZ::Lerp(m_cachedShapeBounds.GetMin().GetZ(), m_cachedShapeBounds.GetMax().GetZ(), maxValueSamples[index]);
  220. inOutPositionList[index].SetZ(AZ::GetClamp(height, m_cachedHeightBounds.m_min, m_cachedHeightBounds.m_max));
  221. }
  222. }
  223. }
  224. }
  225. void TerrainHeightGradientListComponent::OnCompositionChanged()
  226. {
  227. OnCompositionRegionChanged(AZ::Aabb::CreateNull());
  228. }
  229. void TerrainHeightGradientListComponent::OnCompositionRegionChanged(const AZ::Aabb& dirtyRegion)
  230. {
  231. // We query the shape and world bounds prior to locking the queryMutex to help reduce the chances of deadlocks between
  232. // threads due to the EBus call mutexes.
  233. // Get the height range of our height provider based on the shape component.
  234. AZ::Aabb shapeBounds = AZ::Aabb::CreateNull();
  235. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  236. shapeBounds, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
  237. // Get the height range of the entire world
  238. AzFramework::Terrain::FloatRange heightBounds = AzFramework::Terrain::FloatRange::CreateNull();
  239. AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
  240. heightBounds, &AzFramework::Terrain::TerrainDataRequestBus::Events::GetTerrainHeightBounds);
  241. // Ensure that we only change our cached data and terrain registration status when no queries are actively running.
  242. {
  243. AZStd::unique_lock lock(m_queryMutex);
  244. m_cachedShapeBounds = shapeBounds;
  245. // Save off the min/max heights so that we don't have to re-query them on every single height query.
  246. m_cachedHeightBounds = heightBounds;
  247. }
  248. // We specifically refresh this outside of the queryMutex lock to avoid lock inversion deadlocks. These can occur if one thread
  249. // is calling TerrainHeightGradientListComponent::OnCompositionChanged -> TerrainSystem::RefreshArea, and another thread
  250. // is running a query like TerrainSystem::GetHeights -> TerrainHeightGradientListComponent::GetHeights.
  251. // It's ok if a query is able to run in-between the cache change and the RefreshArea call, because the RefreshArea should cause
  252. // the querying system to refresh and achieve eventual consistency.
  253. if (dirtyRegion.IsValid())
  254. {
  255. // Only send a terrain update if the dirty region overlaps the bounds of the terrain spawner
  256. if (dirtyRegion.Overlaps(shapeBounds))
  257. {
  258. AZ::Aabb clampedDirtyRegion = dirtyRegion.GetClamped(shapeBounds);
  259. TerrainSystemServiceRequestBus::Broadcast(
  260. &TerrainSystemServiceRequestBus::Events::RefreshRegion,
  261. clampedDirtyRegion,
  262. AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask::HeightData);
  263. }
  264. }
  265. else
  266. {
  267. TerrainSystemServiceRequestBus::Broadcast(
  268. &TerrainSystemServiceRequestBus::Events::RefreshArea,
  269. GetEntityId(),
  270. AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask::HeightData);
  271. }
  272. }
  273. void TerrainHeightGradientListComponent::OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask)
  274. {
  275. if ((dataChangedMask & TerrainDataChangedMask::Settings) == TerrainDataChangedMask::Settings)
  276. {
  277. // If the terrain system settings changed, it's possible that the world bounds have changed, which can affect our height data.
  278. // Refresh the min/max heights and notify that the height data for this area needs to be refreshed.
  279. OnCompositionRegionChanged(dirtyRegion);
  280. }
  281. }
  282. }