ScriptableDecalComponent.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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/ScriptableDecalComponent.h>
  9. #include <AzCore/RTTI/BehaviorContext.h>
  10. #include <AzCore/Serialization/EditContext.h>
  11. #include <AzCore/Serialization/SerializeContext.h>
  12. #include <AzCore/Time/ITime.h>
  13. #include <Atom/RPI.Public/Scene.h>
  14. #include <Atom/RPI.Public/Material/Material.h>
  15. namespace MultiplayerSample
  16. {
  17. void ScriptableDecalComponent::Reflect(AZ::ReflectContext* context)
  18. {
  19. SpawnDecalConfig::Reflect(context);
  20. if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
  21. {
  22. serialize->Class<MultiplayerSample::ScriptableDecalComponent, AZ::Component>()
  23. ->Version(0)
  24. ;
  25. AZ::EditContext* editContext = serialize->GetEditContext();
  26. if (editContext)
  27. {
  28. editContext->Class<MultiplayerSample::ScriptableDecalComponent>(
  29. "Scriptable Decals", "Allows spawning decals directly from script without prefabs.")
  30. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  31. ->Attribute(AZ::Edit::Attributes::Category, "Graphics")
  32. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector<AZ::Crc32>({ AZ_CRC_CE("Level") }))
  33. ;
  34. }
  35. }
  36. AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
  37. if (behaviorContext)
  38. {
  39. behaviorContext->EBus<DecalRequestBus>("RequestBus")
  40. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
  41. ->Attribute(AZ::Script::Attributes::Category, "Rendering")
  42. ->Attribute(AZ::Script::Attributes::Module, "rendering")
  43. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
  44. ->Event(
  45. "SpawnDecal",
  46. &DecalRequestBus::Events::SpawnDecal)
  47. ;
  48. }
  49. }
  50. void ScriptableDecalComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  51. {
  52. services.push_back(AZ_CRC_CE("ScriptableDecalService"));
  53. }
  54. void ScriptableDecalComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  55. {
  56. services.push_back(AZ_CRC_CE("ScriptableDecalService"));
  57. }
  58. void ScriptableDecalComponent::Activate()
  59. {
  60. m_decalFeatureProcessor = AZ::RPI::Scene::GetFeatureProcessorForEntity<AZ::Render::DecalFeatureProcessorInterface>(GetEntityId());
  61. if (m_decalFeatureProcessor)
  62. {
  63. AZ::RPI::Scene* scene = m_decalFeatureProcessor->GetParentScene();
  64. DecalRequestBus::Handler::BusConnect(scene->GetId());
  65. AZ::TickBus::Handler::BusConnect();
  66. }
  67. }
  68. void ScriptableDecalComponent::Deactivate()
  69. {
  70. AZ::TickBus::Handler::BusDisconnect();
  71. DecalRequestBus::Handler::BusDisconnect();
  72. auto removeDecalsFromList = [&](AZStd::vector<DecalInstance>& container)
  73. {
  74. for (const DecalInstance& instance : container)
  75. {
  76. m_decalFeatureProcessor->ReleaseDecal(instance.m_handle);
  77. }
  78. container.clear();
  79. };
  80. removeDecalsFromList(m_fadingInDecals);
  81. removeDecalsFromList(m_fadingOutDecals);
  82. removeDecalsFromList(m_decalHeap);
  83. m_decalFeatureProcessor = nullptr;
  84. }
  85. void ScriptableDecalComponent::SpawnDecal(const AZ::Transform& worldTm, const SpawnDecalConfig& config)
  86. {
  87. DecalHandle handle = m_decalFeatureProcessor->AcquireDecal();
  88. AZ::Vector3 scale = AZ::Vector3(config.m_scale, config.m_scale, config.m_scale * config.m_thickness);
  89. // Check for bad state.
  90. if (config.m_scale <= 0.0f || !config.m_materialAssetId.IsValid() || config.m_opacity <= 0.0f ||
  91. (config.m_fadeInTimeSec + config.m_lifeTimeSec + config.m_fadeOutTimeSec <= 0.0f))
  92. {
  93. return;
  94. }
  95. m_decalFeatureProcessor->SetDecalTransform(handle, worldTm, scale);
  96. m_decalFeatureProcessor->SetDecalMaterial(handle, config.m_materialAssetId);
  97. m_decalFeatureProcessor->SetDecalOpacity(handle, config.m_opacity);
  98. m_decalFeatureProcessor->SetDecalAttenuationAngle(handle, config.m_attenuationAngle);
  99. m_decalFeatureProcessor->SetDecalSortKey(handle, config.m_sortKey);
  100. uint32_t currentTimeMs = static_cast<uint32_t>(AZ::Interface<AZ::ITime>::Get()->GetElapsedTimeMs());
  101. if (config.m_fadeInTimeSec > 0.0f)
  102. {
  103. m_fadingInDecals.push_back({ config, handle, currentTimeMs });
  104. }
  105. else if (config.m_lifeTimeSec > 0.0f)
  106. {
  107. uint32_t lifetimeMs = static_cast<uint32_t>(config.m_lifeTimeSec * 1000.0f);
  108. uint32_t despawnTimeMs = currentTimeMs + lifetimeMs;
  109. // Store decals in a min heap sorted by when they need to start animating the fade out.
  110. // This makes it very cheap to check each frame if any decals need to change to the fade-out state.
  111. m_decalHeap.push_back({ config, handle, despawnTimeMs });
  112. AZStd::push_heap(m_decalHeap.begin(), m_decalHeap.end(), HeapCompare);
  113. }
  114. else
  115. {
  116. m_fadingOutDecals.push_back({ config, handle, currentTimeMs });
  117. }
  118. }
  119. void ScriptableDecalComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
  120. {
  121. uint32_t currentTimeMs = static_cast<uint32_t>(AZ::Interface<AZ::ITime>::Get()->GetElapsedTimeMs());
  122. for (size_t i = 0; i < m_fadingInDecals.size();)
  123. {
  124. DecalInstance& decalInstance = m_fadingInDecals.at(i);
  125. uint32_t currentFadeTimeMs = currentTimeMs - decalInstance.m_animationStartTimeMs;
  126. float fadeInTimeMsFloat = decalInstance.m_config.m_fadeInTimeSec * 1000.0f;
  127. float opacity = AZStd::GetMin(1.0f, currentFadeTimeMs / fadeInTimeMsFloat);
  128. opacity *= decalInstance.m_config.m_opacity;
  129. m_decalFeatureProcessor->SetDecalOpacity(decalInstance.m_handle, opacity);
  130. uint32_t fadeInTimeMs = static_cast<uint32_t>(fadeInTimeMsFloat);
  131. if (currentFadeTimeMs > fadeInTimeMs)
  132. {
  133. uint32_t lifetimeMs = static_cast<uint32_t>(decalInstance.m_config.m_lifeTimeSec * 1000.0f);
  134. // Fade out animation starts after the fade in time and life time have passed.
  135. uint32_t despawnTimeMs = fadeInTimeMs + lifetimeMs;
  136. decalInstance.m_animationStartTimeMs += despawnTimeMs;
  137. m_decalHeap.push_back(decalInstance);
  138. AZStd::push_heap(m_decalHeap.begin(), m_decalHeap.end(), HeapCompare);
  139. // Replace this instance with the one on the back
  140. decalInstance = m_fadingInDecals.back();
  141. m_fadingInDecals.pop_back();
  142. // Don't increment, next iteration needs to process the item just moved to this spot.
  143. }
  144. else
  145. {
  146. ++i;
  147. }
  148. }
  149. // Check to see if any decals need to despawn
  150. while (!m_decalHeap.empty() && m_decalHeap.front().m_animationStartTimeMs < currentTimeMs)
  151. {
  152. AZStd::pop_heap(m_decalHeap.begin(), m_decalHeap.end(), HeapCompare);
  153. m_fadingOutDecals.push_back(m_decalHeap.back());
  154. m_decalHeap.pop_back();
  155. }
  156. // Animate despawning decals, remove those that are expired.
  157. for (size_t i = 0; i < m_fadingOutDecals.size();)
  158. {
  159. DecalInstance& decalInstance = m_fadingOutDecals.at(i);
  160. // Clamp our minimum total fade time to 1 millisecond to avoid any potential divide-by-zero
  161. float totalFadeTimeMs = AZStd::max(decalInstance.m_config.m_fadeOutTimeSec * 1000.0f, 1.0f);
  162. float currentFadeTimeMs = static_cast<float>(currentTimeMs - decalInstance.m_animationStartTimeMs);
  163. if (currentFadeTimeMs > totalFadeTimeMs)
  164. {
  165. // Despawn the decal, it's done animating;
  166. m_decalFeatureProcessor->ReleaseDecal(decalInstance.m_handle);
  167. // Replace this instance with the one on the back
  168. decalInstance = m_fadingOutDecals.back();
  169. m_fadingOutDecals.pop_back();
  170. // Don't increment, next iteration needs to process the item just moved to this spot.
  171. }
  172. else
  173. {
  174. float opacity = 1.0f - (currentFadeTimeMs / totalFadeTimeMs);
  175. opacity *= decalInstance.m_config.m_opacity;
  176. m_decalFeatureProcessor->SetDecalOpacity(decalInstance.m_handle, opacity);
  177. ++i;
  178. }
  179. }
  180. }
  181. bool ScriptableDecalComponent::HeapCompare(const DecalInstance& value1, const DecalInstance& value2)
  182. {
  183. return value1.m_animationStartTimeMs > value2.m_animationStartTimeMs;
  184. }
  185. }