ShapeAreaFalloffGradientComponent.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  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 <GradientSignal/Components/ShapeAreaFalloffGradientComponent.h>
  9. #include <AzCore/RTTI/BehaviorContext.h>
  10. #include <AzCore/Serialization/EditContext.h>
  11. #include <AzCore/Serialization/SerializeContext.h>
  12. #include <LmbrCentral/Shape/ShapeComponentBus.h>
  13. #include <AzCore/Debug/Profiler.h>
  14. namespace GradientSignal
  15. {
  16. void ShapeAreaFalloffGradientConfig::Reflect(AZ::ReflectContext* context)
  17. {
  18. AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
  19. if (serialize)
  20. {
  21. serialize->Class<ShapeAreaFalloffGradientConfig, AZ::ComponentConfig>()
  22. ->Version(0)
  23. ->Field("ShapeEntityId", &ShapeAreaFalloffGradientConfig::m_shapeEntityId)
  24. ->Field("FalloffWidth", &ShapeAreaFalloffGradientConfig::m_falloffWidth)
  25. ->Field("FalloffType", &ShapeAreaFalloffGradientConfig::m_falloffType)
  26. ->Field("Is3dFalloff", &ShapeAreaFalloffGradientConfig::m_is3dFalloff)
  27. ;
  28. AZ::EditContext* edit = serialize->GetEditContext();
  29. if (edit)
  30. {
  31. edit->Class<ShapeAreaFalloffGradientConfig>(
  32. "Shape Falloff Gradient", "")
  33. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  34. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
  35. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  36. ->DataElement(0, &ShapeAreaFalloffGradientConfig::m_shapeEntityId, "Shape Entity Id", "Entity with shape component to test distance against.")
  37. ->Attribute(AZ::Edit::Attributes::RequiredService, AZ_CRC_CE("ShapeService"))
  38. ->DataElement(AZ::Edit::UIHandlers::Slider, &ShapeAreaFalloffGradientConfig::m_falloffWidth, "Falloff Width", "Maximum distance used to calculate gradient in meters.")
  39. ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
  40. ->Attribute(AZ::Edit::Attributes::Max, 100.0f)
  41. ->DataElement(0, &ShapeAreaFalloffGradientConfig::m_falloffType, "Falloff Type", "Function for calculating falloff")
  42. // Inner falloff isn't supported yet.
  43. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Hide)
  44. //->EnumAttribute(ShapeAreaFalloffGradientConfig::FalloffType::Inner, "Inner")
  45. ->EnumAttribute(FalloffType::Outer, "Outer")
  46. //->EnumAttribute(ShapeAreaFalloffGradientConfig::FalloffType::InnerOuter, "InnerOuter")
  47. ->DataElement(
  48. AZ::Edit::UIHandlers::Default, &ShapeAreaFalloffGradientConfig::m_is3dFalloff, "3D Falloff",
  49. "Either calculate the falloff in the XY plane or in 3D space.")
  50. ;
  51. }
  52. }
  53. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  54. {
  55. behaviorContext->Class<ShapeAreaFalloffGradientConfig>()
  56. ->Attribute(AZ::Script::Attributes::Category, "Vegetation")
  57. ->Constructor()
  58. ->Property("shapeEntityId", BehaviorValueProperty(&ShapeAreaFalloffGradientConfig::m_shapeEntityId))
  59. ->Property("falloffWidth", BehaviorValueProperty(&ShapeAreaFalloffGradientConfig::m_falloffWidth))
  60. ->Property("falloffType",
  61. [](ShapeAreaFalloffGradientConfig* config) { return (AZ::u8&)(config->m_falloffType); },
  62. [](ShapeAreaFalloffGradientConfig* config, const AZ::u8& i) { config->m_falloffType = (FalloffType)i; })
  63. ->Property("is3dFalloff", BehaviorValueProperty(&ShapeAreaFalloffGradientConfig::m_is3dFalloff))
  64. ;
  65. }
  66. }
  67. void ShapeAreaFalloffGradientComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  68. {
  69. services.push_back(AZ_CRC_CE("GradientService"));
  70. }
  71. void ShapeAreaFalloffGradientComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  72. {
  73. services.push_back(AZ_CRC_CE("GradientService"));
  74. services.push_back(AZ_CRC_CE("GradientTransformService"));
  75. }
  76. void ShapeAreaFalloffGradientComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& services)
  77. {
  78. }
  79. void ShapeAreaFalloffGradientComponent::Reflect(AZ::ReflectContext* context)
  80. {
  81. ShapeAreaFalloffGradientConfig::Reflect(context);
  82. AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
  83. if (serialize)
  84. {
  85. serialize->Class<ShapeAreaFalloffGradientComponent, AZ::Component>()
  86. ->Version(0)
  87. ->Field("Configuration", &ShapeAreaFalloffGradientComponent::m_configuration)
  88. ;
  89. }
  90. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  91. {
  92. behaviorContext->Constant("ShapeAreaFalloffGradientComponentTypeId", BehaviorConstant(ShapeAreaFalloffGradientComponentTypeId));
  93. behaviorContext->Class<ShapeAreaFalloffGradientComponent>()->RequestBus("ShapeAreaFalloffGradientRequestBus");
  94. behaviorContext->EBus<ShapeAreaFalloffGradientRequestBus>("ShapeAreaFalloffGradientRequestBus")
  95. ->Attribute(AZ::Script::Attributes::Category, "Vegetation")
  96. ->Event("GetShapeEntityId", &ShapeAreaFalloffGradientRequestBus::Events::GetShapeEntityId)
  97. ->Event("SetShapeEntityId", &ShapeAreaFalloffGradientRequestBus::Events::SetShapeEntityId)
  98. ->VirtualProperty("ShapeEntityId", "GetShapeEntityId", "SetShapeEntityId")
  99. ->Event("GetFalloffWidth", &ShapeAreaFalloffGradientRequestBus::Events::GetFalloffWidth)
  100. ->Event("SetFalloffWidth", &ShapeAreaFalloffGradientRequestBus::Events::SetFalloffWidth)
  101. ->VirtualProperty("FalloffWidth", "GetFalloffWidth", "SetFalloffWidth")
  102. ->Event("GetFalloffType", &ShapeAreaFalloffGradientRequestBus::Events::GetFalloffType)
  103. ->Event("SetFalloffType", &ShapeAreaFalloffGradientRequestBus::Events::SetFalloffType)
  104. ->VirtualProperty("FalloffType", "GetFalloffType", "SetFalloffType")
  105. ->Event("Get3dFalloff", &ShapeAreaFalloffGradientRequestBus::Events::Get3dFalloff)
  106. ->Event("Set3dFalloff", &ShapeAreaFalloffGradientRequestBus::Events::Set3dFalloff)
  107. ->VirtualProperty("Is3dFalloff", "Get3dFalloff", "Set3dFalloff");
  108. }
  109. }
  110. ShapeAreaFalloffGradientComponent::ShapeAreaFalloffGradientComponent(const ShapeAreaFalloffGradientConfig& configuration)
  111. : m_configuration(configuration)
  112. {
  113. }
  114. void ShapeAreaFalloffGradientComponent::Activate()
  115. {
  116. ShapeAreaFalloffGradientRequestBus::Handler::BusConnect(GetEntityId());
  117. // Make sure we're notified whenever the shape changes, so that we can re-cache its center point.
  118. if (m_configuration.m_shapeEntityId.IsValid())
  119. {
  120. AZ::EntityBus::Handler::BusConnect(m_configuration.m_shapeEntityId);
  121. LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(m_configuration.m_shapeEntityId);
  122. }
  123. // Keep track of the center of the shape so that we can calculate falloff distance correctly.
  124. CacheShapeBounds();
  125. // Connect to GradientRequestBus last so that everything is initialized before listening for gradient queries.
  126. GradientRequestBus::Handler::BusConnect(GetEntityId());
  127. }
  128. void ShapeAreaFalloffGradientComponent::Deactivate()
  129. {
  130. // Disconnect from GradientRequestBus first to ensure no queries are in process when deactivating.
  131. GradientRequestBus::Handler::BusDisconnect();
  132. LmbrCentral::ShapeComponentNotificationsBus::Handler::BusDisconnect();
  133. ShapeAreaFalloffGradientRequestBus::Handler::BusDisconnect();
  134. AZ::EntityBus::Handler::BusDisconnect();
  135. }
  136. bool ShapeAreaFalloffGradientComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
  137. {
  138. if (auto config = azrtti_cast<const ShapeAreaFalloffGradientConfig*>(baseConfig))
  139. {
  140. m_configuration = *config;
  141. return true;
  142. }
  143. return false;
  144. }
  145. bool ShapeAreaFalloffGradientComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
  146. {
  147. if (auto config = azrtti_cast<ShapeAreaFalloffGradientConfig*>(outBaseConfig))
  148. {
  149. *config = m_configuration;
  150. return true;
  151. }
  152. return false;
  153. }
  154. float ShapeAreaFalloffGradientComponent::GetValue(const GradientSampleParams& sampleParams) const
  155. {
  156. AZStd::shared_lock lock(m_queryMutex);
  157. float distance = 0.0f;
  158. AZ::Vector3 queryPoint = sampleParams.m_position;
  159. if (!m_configuration.m_is3dFalloff)
  160. {
  161. // Calculate the shape falloff distance in the XY plane only by using the shape center as our Z location.
  162. queryPoint.SetZ(m_cachedShapeCenter.GetZ());
  163. }
  164. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  165. distance, m_configuration.m_shapeEntityId, &LmbrCentral::ShapeComponentRequestsBus::Events::DistanceFromPoint, queryPoint);
  166. // Since this is outer falloff, distance should give us values from 1.0 at the minimum distance to 0.0 at the maximum distance.
  167. // The statement is written specifically to handle the 0 falloff case as well. For 0 falloff, all points inside the shape
  168. // (0 distance) return 1.0, and all points outside the shape return 0. This works because division by 0 gives infinity, which gets
  169. // clamped by the GetMax() to 0. However, if distance == 0, it would give us NaN, so we have the separate conditional check to
  170. // handle that case and clamp to 1.0.
  171. return (distance <= 0.0f) ? 1.0f : AZ::GetMax(1.0f - (distance / m_configuration.m_falloffWidth), 0.0f);
  172. }
  173. void ShapeAreaFalloffGradientComponent::GetValues(AZStd::span<const AZ::Vector3> positions, AZStd::span<float> outValues) const
  174. {
  175. if (positions.size() != outValues.size())
  176. {
  177. AZ_Assert(false, "input and output lists are different sizes (%zu vs %zu).", positions.size(), outValues.size());
  178. return;
  179. }
  180. AZStd::shared_lock lock(m_queryMutex);
  181. bool shapeConnected = false;
  182. const float falloffWidth = m_configuration.m_falloffWidth;
  183. LmbrCentral::ShapeComponentRequestsBus::Event(
  184. m_configuration.m_shapeEntityId,
  185. [this, falloffWidth, positions, &outValues, &shapeConnected](LmbrCentral::ShapeComponentRequestsBus::Events* shapeRequests)
  186. {
  187. shapeConnected = true;
  188. for (size_t index = 0; index < positions.size(); index++)
  189. {
  190. AZ::Vector3 queryPoint = positions[index];
  191. if (!m_configuration.m_is3dFalloff)
  192. {
  193. // Calculate the shape falloff distance in the XY plane only by using the shape center as our Z location.
  194. queryPoint.SetZ(m_cachedShapeCenter.GetZ());
  195. }
  196. float distance = shapeRequests->DistanceFromPoint(queryPoint);
  197. // Since this is outer falloff, distance should give us values from 1.0 at the minimum distance to 0.0 at the maximum
  198. // distance. The statement is written specifically to handle the 0 falloff case as well. For 0 falloff, all points
  199. // inside the shape (0 distance) return 1.0, and all points outside the shape return 0. This works because division by 0
  200. // gives infinity, which gets clamped by the GetMax() to 0. However, if distance == 0, it would give us NaN, so we have
  201. // the separate conditional check to handle that case and clamp to 1.0.
  202. outValues[index] = (distance <= 0.0f) ? 1.0f : AZ::GetMax(1.0f - (distance / falloffWidth), 0.0f);
  203. }
  204. });
  205. // If there's no shape, there's no falloff.
  206. if (!shapeConnected)
  207. {
  208. for (auto& outValue : outValues)
  209. {
  210. outValue = 1.0f;
  211. }
  212. }
  213. }
  214. void ShapeAreaFalloffGradientComponent::NotifyRegionChanged(const AZ::Aabb& region)
  215. {
  216. if (region.IsValid())
  217. {
  218. LmbrCentral::DependencyNotificationBus::Event(
  219. GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionRegionChanged, region);
  220. }
  221. else
  222. {
  223. LmbrCentral::DependencyNotificationBus::Event(
  224. GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  225. }
  226. }
  227. AZ::EntityId ShapeAreaFalloffGradientComponent::GetShapeEntityId() const
  228. {
  229. return m_configuration.m_shapeEntityId;
  230. }
  231. void ShapeAreaFalloffGradientComponent::SetShapeEntityId(AZ::EntityId entityId)
  232. {
  233. // Only hold the lock while we're changing the data. Don't hold onto it during the OnCompositionChanged call, because that can
  234. // execute an arbitrary amount of logic, including calls back to this component.
  235. {
  236. AZStd::unique_lock lock(m_queryMutex);
  237. // If we're setting the entityId to the same one, don't do anything.
  238. if (entityId == m_configuration.m_shapeEntityId)
  239. {
  240. return;
  241. }
  242. m_configuration.m_shapeEntityId = entityId;
  243. AZ::EntityBus::Handler::BusDisconnect();
  244. LmbrCentral::ShapeComponentNotificationsBus::Handler::BusDisconnect();
  245. if (m_configuration.m_shapeEntityId.IsValid())
  246. {
  247. AZ::EntityBus::Handler::BusConnect(m_configuration.m_shapeEntityId);
  248. LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(m_configuration.m_shapeEntityId);
  249. }
  250. }
  251. CacheShapeBounds();
  252. }
  253. float ShapeAreaFalloffGradientComponent::GetFalloffWidth() const
  254. {
  255. return m_configuration.m_falloffWidth;
  256. }
  257. void ShapeAreaFalloffGradientComponent::SetFalloffWidth(float falloffWidth)
  258. {
  259. AZ::Aabb dirtyRegion = AZ::Aabb::CreateNull();
  260. // Only hold the lock while we're changing the data. Don't hold onto it during the OnCompositionChanged call, because that can
  261. // execute an arbitrary amount of logic, including calls back to this component.
  262. {
  263. AZStd::unique_lock lock(m_queryMutex);
  264. // We only support outer falloff, so our dirty region is our shape expanded by the larger falloff width.
  265. dirtyRegion = m_cachedShapeBounds.GetExpanded(AZ::Vector3(AZ::GetMax(m_configuration.m_falloffWidth, falloffWidth)));
  266. m_configuration.m_falloffWidth = falloffWidth;
  267. }
  268. NotifyRegionChanged(dirtyRegion);
  269. }
  270. FalloffType ShapeAreaFalloffGradientComponent::GetFalloffType() const
  271. {
  272. return m_configuration.m_falloffType;
  273. }
  274. void ShapeAreaFalloffGradientComponent::SetFalloffType(FalloffType type)
  275. {
  276. AZ::Aabb dirtyRegion = AZ::Aabb::CreateNull();
  277. // Only hold the lock while we're changing the data. Don't hold onto it during the OnCompositionChanged call, because that can
  278. // execute an arbitrary amount of logic, including calls back to this component.
  279. {
  280. AZStd::unique_lock lock(m_queryMutex);
  281. m_configuration.m_falloffType = type;
  282. // We only support outer falloff, so our dirty region is our shape expanded by the falloff width.
  283. dirtyRegion = m_cachedShapeBounds.GetExpanded(AZ::Vector3(m_configuration.m_falloffWidth));
  284. }
  285. NotifyRegionChanged(dirtyRegion);
  286. }
  287. bool ShapeAreaFalloffGradientComponent::Get3dFalloff() const
  288. {
  289. return m_configuration.m_is3dFalloff;
  290. }
  291. void ShapeAreaFalloffGradientComponent::Set3dFalloff(bool is3dFalloff)
  292. {
  293. AZ::Aabb dirtyRegion = AZ::Aabb::CreateNull();
  294. // Only hold the lock while we're changing the data. Don't hold onto it during the OnCompositionChanged call, because that can
  295. // execute an arbitrary amount of logic, including calls back to this component.
  296. {
  297. AZStd::unique_lock lock(m_queryMutex);
  298. m_configuration.m_is3dFalloff = is3dFalloff;
  299. // We only support outer falloff, so our dirty region is our shape expanded by the falloff width.
  300. dirtyRegion = m_cachedShapeBounds.GetExpanded(AZ::Vector3(m_configuration.m_falloffWidth));
  301. }
  302. NotifyRegionChanged(dirtyRegion);
  303. }
  304. void ShapeAreaFalloffGradientComponent::OnShapeChanged(
  305. [[maybe_unused]] LmbrCentral::ShapeComponentNotifications::ShapeChangeReasons reasons)
  306. {
  307. CacheShapeBounds();
  308. }
  309. void ShapeAreaFalloffGradientComponent::OnEntityActivated([[maybe_unused]] const AZ::EntityId& entityId)
  310. {
  311. CacheShapeBounds();
  312. }
  313. void ShapeAreaFalloffGradientComponent::OnEntityDeactivated([[maybe_unused]] const AZ::EntityId& entityId)
  314. {
  315. CacheShapeBounds();
  316. }
  317. void ShapeAreaFalloffGradientComponent::CacheShapeBounds()
  318. {
  319. AZ::Aabb dirtyRegion = AZ::Aabb::CreateNull();
  320. {
  321. AZStd::unique_lock lock(m_queryMutex);
  322. AZ::Aabb previousShapeBounds = m_cachedShapeBounds;
  323. m_cachedShapeBounds = AZ::Aabb::CreateNull();
  324. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  325. m_cachedShapeBounds, m_configuration.m_shapeEntityId, &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
  326. // Grab the center of the shape so that we can calculate falloff distance in 2D.
  327. if (m_cachedShapeBounds.IsValid())
  328. {
  329. m_cachedShapeCenter = m_cachedShapeBounds.GetCenter();
  330. }
  331. else
  332. {
  333. m_cachedShapeCenter = AZ::Vector3::CreateZero();
  334. }
  335. // Calculate the dirty region based on the previous and current shape bounds.
  336. // If either the previous or current shape bounds is invalid, then leave the dirtyRegion invalid.
  337. // This component returns 1.0 everywhere if there's no shape, because technically there's no falloff from max,
  338. // so changing to or from a valid shape will cause potential value changes across the entire world space.
  339. if (previousShapeBounds.IsValid() && m_cachedShapeBounds.IsValid())
  340. {
  341. dirtyRegion.AddAabb(previousShapeBounds.GetExpanded(AZ::Vector3(m_configuration.m_falloffWidth)));
  342. dirtyRegion.AddAabb(m_cachedShapeBounds.GetExpanded(AZ::Vector3(m_configuration.m_falloffWidth)));
  343. }
  344. }
  345. // Any time we're caching the shape bounds, it's presumably because the shape changed, so notify about the change.
  346. NotifyRegionChanged(dirtyRegion);
  347. }
  348. } // namespace GradientSignal