MixedGradientComponent.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  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/MixedGradientComponent.h>
  9. #include <AzCore/Debug/Profiler.h>
  10. #include <AzCore/RTTI/BehaviorContext.h>
  11. #include <AzCore/Serialization/EditContext.h>
  12. #include <AzCore/Serialization/SerializeContext.h>
  13. namespace GradientSignal
  14. {
  15. void MixedGradientLayer::Reflect(AZ::ReflectContext* context)
  16. {
  17. AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
  18. if (serialize)
  19. {
  20. serialize->Class<MixedGradientLayer>()
  21. ->Version(0)
  22. ->Field("Enabled", &MixedGradientLayer::m_enabled)
  23. ->Field("Operation", &MixedGradientLayer::m_operation)
  24. ->Field("Gradient", &MixedGradientLayer::m_gradientSampler)
  25. ;
  26. AZ::EditContext* edit = serialize->GetEditContext();
  27. if (edit)
  28. {
  29. edit->Class<MixedGradientLayer>(
  30. "Mixed Gradient Layer", "")
  31. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  32. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  33. ->DataElement(0, &MixedGradientLayer::m_enabled, "Enabled", "Toggle the influence of this gradient layer.")
  34. ->DataElement(AZ::Edit::UIHandlers::ComboBox, &MixedGradientLayer::m_operation, "Operation", "Function used to mix the current gradient with the previous result.")
  35. ->EnumAttribute(MixedGradientLayer::MixingOperation::Initialize, "Initialize")
  36. ->EnumAttribute(MixedGradientLayer::MixingOperation::Multiply, "Multiply")
  37. ->EnumAttribute(MixedGradientLayer::MixingOperation::Screen, "Screen")
  38. ->EnumAttribute(MixedGradientLayer::MixingOperation::Add, "Linear Dodge (Add)")
  39. ->EnumAttribute(MixedGradientLayer::MixingOperation::Subtract, "Subtract")
  40. ->EnumAttribute(MixedGradientLayer::MixingOperation::Min, "Darken (Min)")
  41. ->EnumAttribute(MixedGradientLayer::MixingOperation::Max, "Lighten (Max)")
  42. ->EnumAttribute(MixedGradientLayer::MixingOperation::Average, "Average")
  43. ->EnumAttribute(MixedGradientLayer::MixingOperation::Normal, "Normal")
  44. ->EnumAttribute(MixedGradientLayer::MixingOperation::Overlay, "Overlay")
  45. ->DataElement(0, &MixedGradientLayer::m_gradientSampler, "Gradient", "Gradient that will contribute to result of gradient mixing.")
  46. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  47. ;
  48. }
  49. }
  50. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  51. {
  52. behaviorContext->Class<MixedGradientLayer>()
  53. ->Constructor()
  54. ->Attribute(AZ::Script::Attributes::Category, "Vegetation")
  55. ->Property("enabled", BehaviorValueProperty(&MixedGradientLayer::m_enabled))
  56. ->Property("mixingOperation",
  57. [](MixedGradientLayer* config) { return (AZ::u8&)(config->m_operation); },
  58. [](MixedGradientLayer* config, const AZ::u8& i) { config->m_operation = (MixedGradientLayer::MixingOperation)i; })
  59. ->Property("gradientSampler", BehaviorValueProperty(&MixedGradientLayer::m_gradientSampler))
  60. ;
  61. }
  62. }
  63. const char* MixedGradientLayer::GetLayerEntityName() const
  64. {
  65. // This needs to be static since the return value is used by the RPE and needs to exist long enough to set the UI data
  66. static AZStd::string entityName;
  67. static constexpr auto emptyName = "<empty>";
  68. AZ::EntityId layerEntityId = m_gradientSampler.m_gradientId;
  69. if (layerEntityId.IsValid())
  70. {
  71. AZ::ComponentApplicationBus::BroadcastResult(entityName, &AZ::ComponentApplicationRequests::GetEntityName, layerEntityId);
  72. }
  73. if (entityName.empty())
  74. {
  75. return emptyName;
  76. }
  77. else
  78. {
  79. return entityName.c_str();
  80. }
  81. }
  82. size_t MixedGradientConfig::GetNumLayers() const
  83. {
  84. return m_layers.size();
  85. }
  86. void MixedGradientConfig::AddLayer()
  87. {
  88. m_layers[GetNumLayers()] = MixedGradientLayer();
  89. OnLayerAdded();
  90. }
  91. void MixedGradientConfig::OnLayerAdded()
  92. {
  93. // The first layer should always default to "Initialize".
  94. if (GetNumLayers() == 1)
  95. {
  96. m_layers[0].m_operation = MixedGradientLayer::MixingOperation::Initialize;
  97. }
  98. }
  99. void MixedGradientConfig::RemoveLayer(int layerIndex)
  100. {
  101. if (layerIndex < m_layers.size() && layerIndex >= 0)
  102. {
  103. m_layers.erase(m_layers.begin() + layerIndex);
  104. }
  105. }
  106. MixedGradientLayer* MixedGradientConfig::GetLayer(int layerIndex)
  107. {
  108. if (layerIndex < m_layers.size() && layerIndex >= 0)
  109. {
  110. return &(m_layers[layerIndex]);
  111. }
  112. return nullptr;
  113. }
  114. void MixedGradientConfig::Reflect(AZ::ReflectContext* context)
  115. {
  116. MixedGradientLayer::Reflect(context);
  117. AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
  118. if (serialize)
  119. {
  120. serialize->Class<MixedGradientConfig, AZ::ComponentConfig>()
  121. ->Version(0)
  122. ->Field("Layers", &MixedGradientConfig::m_layers)
  123. ;
  124. AZ::EditContext* edit = serialize->GetEditContext();
  125. if (edit)
  126. {
  127. edit->Class<MixedGradientConfig>(
  128. "Mixed Gradient", "")
  129. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  130. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
  131. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  132. ->DataElement(0, &MixedGradientConfig::m_layers, "Layers", "List of gradient mixing layers.")
  133. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  134. ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, true)
  135. ->Attribute(AZ::Edit::Attributes::AddNotify, &MixedGradientConfig::OnLayerAdded)
  136. ->ElementAttribute(AZ::Edit::Attributes::NameLabelOverride, &MixedGradientLayer::GetLayerEntityName)
  137. ;
  138. }
  139. }
  140. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  141. {
  142. behaviorContext->Class<MixedGradientConfig>()
  143. ->Attribute(AZ::Script::Attributes::Category, "Vegetation")
  144. ->Constructor()
  145. ->Method("GetNumLayers", &MixedGradientConfig::GetNumLayers)
  146. ->Method("AddLayer", &MixedGradientConfig::AddLayer)
  147. ->Method("RemoveLayer", &MixedGradientConfig::RemoveLayer)
  148. ->Method("GetLayer", &MixedGradientConfig::GetLayer)
  149. ;
  150. }
  151. }
  152. void MixedGradientComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  153. {
  154. services.push_back(AZ_CRC_CE("GradientService"));
  155. }
  156. void MixedGradientComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  157. {
  158. services.push_back(AZ_CRC_CE("GradientService"));
  159. services.push_back(AZ_CRC_CE("GradientTransformService"));
  160. }
  161. void MixedGradientComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& services)
  162. {
  163. }
  164. void MixedGradientComponent::Reflect(AZ::ReflectContext* context)
  165. {
  166. MixedGradientConfig::Reflect(context);
  167. AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
  168. if (serialize)
  169. {
  170. serialize->Class<MixedGradientComponent, AZ::Component>()
  171. ->Version(0)
  172. ->Field("Configuration", &MixedGradientComponent::m_configuration)
  173. ;
  174. }
  175. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  176. {
  177. behaviorContext->Constant("MixedGradientComponentTypeId", BehaviorConstant(MixedGradientComponentTypeId));
  178. behaviorContext->Class<MixedGradientComponent>()->RequestBus("MixedGradientRequestBus");
  179. behaviorContext->EBus<MixedGradientRequestBus>("MixedGradientRequestBus")
  180. ->Attribute(AZ::Script::Attributes::Category, "Vegetation")
  181. ->Event("GetNumLayers", &MixedGradientRequestBus::Events::GetNumLayers)
  182. ->Event("AddLayer", &MixedGradientRequestBus::Events::AddLayer)
  183. ->Event("RemoveLayer", &MixedGradientRequestBus::Events::RemoveLayer)
  184. ->Event("GetLayer", &MixedGradientRequestBus::Events::GetLayer)
  185. ;
  186. }
  187. }
  188. MixedGradientComponent::MixedGradientComponent(const MixedGradientConfig& configuration)
  189. : m_configuration(configuration)
  190. {
  191. }
  192. void MixedGradientComponent::Activate()
  193. {
  194. m_dependencyMonitor.Reset();
  195. m_dependencyMonitor.SetEntityNotificationFunction(
  196. [this](const AZ::EntityId& ownerId, const AZ::EntityId& dependentId, const AZ::Aabb& dirtyRegion)
  197. {
  198. for (const auto& layer : m_configuration.m_layers)
  199. {
  200. if (layer.m_enabled &&
  201. (layer.m_gradientSampler.m_gradientId == dependentId) &&
  202. layer.m_gradientSampler.m_opacity != 0.0f)
  203. {
  204. if (dirtyRegion.IsValid())
  205. {
  206. AZ::Aabb transformedRegion = layer.m_gradientSampler.TransformDirtyRegion(dirtyRegion);
  207. LmbrCentral::DependencyNotificationBus::Event(
  208. ownerId, &LmbrCentral::DependencyNotificationBus::Events::OnCompositionRegionChanged, transformedRegion);
  209. }
  210. else
  211. {
  212. LmbrCentral::DependencyNotificationBus::Event(
  213. ownerId, &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  214. }
  215. }
  216. }
  217. });
  218. m_dependencyMonitor.ConnectOwner(GetEntityId());
  219. for (const auto& layer : m_configuration.m_layers)
  220. {
  221. m_dependencyMonitor.ConnectDependency(layer.m_gradientSampler.m_gradientId);
  222. }
  223. if (!m_configuration.m_layers.empty())
  224. {
  225. // Force the first layer to always be 'Initialize'
  226. m_configuration.m_layers.front().m_operation = MixedGradientLayer::MixingOperation::Initialize;
  227. }
  228. MixedGradientRequestBus::Handler::BusConnect(GetEntityId());
  229. // Connect to GradientRequestBus last so that everything is initialized before listening for gradient queries.
  230. GradientRequestBus::Handler::BusConnect(GetEntityId());
  231. }
  232. void MixedGradientComponent::Deactivate()
  233. {
  234. // Disconnect from GradientRequestBus first to ensure no queries are in process when deactivating.
  235. GradientRequestBus::Handler::BusDisconnect();
  236. m_dependencyMonitor.Reset();
  237. MixedGradientRequestBus::Handler::BusDisconnect();
  238. }
  239. bool MixedGradientComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
  240. {
  241. if (auto config = azrtti_cast<const MixedGradientConfig*>(baseConfig))
  242. {
  243. m_configuration = *config;
  244. return true;
  245. }
  246. return false;
  247. }
  248. bool MixedGradientComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
  249. {
  250. if (auto config = azrtti_cast<MixedGradientConfig*>(outBaseConfig))
  251. {
  252. *config = m_configuration;
  253. return true;
  254. }
  255. return false;
  256. }
  257. float MixedGradientComponent::GetValue(const GradientSampleParams& sampleParams) const
  258. {
  259. AZStd::shared_lock lock(m_queryMutex);
  260. // accumulate the mixed/combined result of all layers and operations
  261. float result = 0.0f;
  262. for (const auto& layer : m_configuration.m_layers)
  263. {
  264. // added check to prevent opacity of 0.0, which will bust when we unpremultiply the alpha out
  265. if (layer.m_enabled && layer.m_gradientSampler.m_opacity != 0.0f)
  266. {
  267. // Precalculate the inverse opacity that we'll use for blending the current accumulated value with.
  268. // In the one case of "Initialize" blending, force this value to 0 so that we erase any accumulated values.
  269. const float inverseOpacity = (layer.m_operation == MixedGradientLayer::MixingOperation::Initialize)
  270. ? 0.0f
  271. : (1.0f - layer.m_gradientSampler.m_opacity);
  272. // this includes leveling and opacity result, we need unpremultiplied opacity to combine properly
  273. float current = layer.m_gradientSampler.GetValue(sampleParams);
  274. // unpremultiplied alpha (we clamp the end result)
  275. const float currentUnpremultiplied = current / layer.m_gradientSampler.m_opacity;
  276. const float operationResult = PerformMixingOperation(layer.m_operation, result, currentUnpremultiplied);
  277. // blend layers (re-applying opacity, which is why we needed to use unpremultiplied)
  278. result = (result * inverseOpacity) + (operationResult * layer.m_gradientSampler.m_opacity);
  279. }
  280. }
  281. return AZ::GetClamp(result, 0.0f, 1.0f);
  282. }
  283. void MixedGradientComponent::GetValues(AZStd::span<const AZ::Vector3> positions, AZStd::span<float> outValues) const
  284. {
  285. if (positions.size() != outValues.size())
  286. {
  287. AZ_Assert(false, "input and output lists are different sizes (%zu vs %zu).", positions.size(), outValues.size());
  288. return;
  289. }
  290. AZStd::shared_lock lock(m_queryMutex);
  291. // Initialize all of our output data to 0.0f. Layer blends will combine with this, so we need it to have an initial value.
  292. AZStd::fill(outValues.begin(), outValues.end(), 0.0f);
  293. AZStd::vector<float> layerValues(positions.size());
  294. // accumulate the mixed/combined result of all layers and operations
  295. for (const auto& layer : m_configuration.m_layers)
  296. {
  297. // added check to prevent opacity of 0.0, which will bust when we unpremultiply the alpha out
  298. if (layer.m_enabled && layer.m_gradientSampler.m_opacity != 0.0f)
  299. {
  300. // Precalculate the inverse opacity that we'll use for blending the current accumulated value with.
  301. // In the one case of "Initialize" blending, force this value to 0 so that we erase any accumulated values.
  302. const float inverseOpacity = (layer.m_operation == MixedGradientLayer::MixingOperation::Initialize)
  303. ? 0.0f
  304. : (1.0f - layer.m_gradientSampler.m_opacity);
  305. // this includes leveling and opacity result, we need unpremultiplied opacity to combine properly
  306. layer.m_gradientSampler.GetValues(positions, layerValues);
  307. for (size_t index = 0; index < outValues.size(); index++)
  308. {
  309. // unpremultiplied alpha (we clamp the end result)
  310. const float currentUnpremultiplied = layerValues[index] / layer.m_gradientSampler.m_opacity;
  311. const float operationResult = PerformMixingOperation(layer.m_operation, outValues[index], currentUnpremultiplied);
  312. // blend layers (re-applying opacity, which is why we needed to use unpremultiplied)
  313. outValues[index] = (outValues[index] * inverseOpacity) + (operationResult * layer.m_gradientSampler.m_opacity);
  314. }
  315. }
  316. }
  317. for (auto& outValue : outValues)
  318. {
  319. outValue = AZ::GetClamp(outValue, 0.0f, 1.0f);
  320. }
  321. }
  322. bool MixedGradientComponent::IsEntityInHierarchy(const AZ::EntityId& entityId) const
  323. {
  324. for (const auto& layer : m_configuration.m_layers)
  325. {
  326. if (layer.m_gradientSampler.IsEntityInHierarchy(entityId))
  327. {
  328. return true;
  329. }
  330. }
  331. return false;
  332. }
  333. size_t MixedGradientComponent::GetNumLayers() const
  334. {
  335. return m_configuration.GetNumLayers();
  336. }
  337. void MixedGradientComponent::AddLayer()
  338. {
  339. // Only hold the lock while we're changing the data. Don't hold onto it during the OnCompositionChanged call, because that can
  340. // execute an arbitrary amount of logic, including calls back to this component.
  341. {
  342. AZStd::unique_lock lock(m_queryMutex);
  343. m_configuration.AddLayer();
  344. }
  345. LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  346. }
  347. void MixedGradientComponent::RemoveLayer(int layerIndex)
  348. {
  349. // Only hold the lock while we're changing the data. Don't hold onto it during the OnCompositionChanged call, because that can
  350. // execute an arbitrary amount of logic, including calls back to this component.
  351. {
  352. AZStd::unique_lock lock(m_queryMutex);
  353. m_configuration.RemoveLayer(layerIndex);
  354. }
  355. LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  356. }
  357. MixedGradientLayer* MixedGradientComponent::GetLayer(int layerIndex)
  358. {
  359. return m_configuration.GetLayer(layerIndex);
  360. }
  361. }