| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- /*
- * 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 <AzCore/PlatformDef.h>
- #include <AzCore/Component/ComponentApplicationBus.h>
- #include <AzCore/Component/Entity.h>
- #include <AzCore/Serialization/SerializeContext.h>
- #include <AzCore/Serialization/EditContext.h>
- #include <AzCore/RTTI/BehaviorContext.h>
- #include <Integration/Components/SimpleLODComponent.h>
- #include <MCore/Source/AttributeString.h>
- #include <EMotionFX/Source/ActorInstance.h>
- #include <MathConversion.h>
- #include <IRenderAuxGeom.h>
- #include <Atom/RPI.Public/ViewportContext.h>
- #include <Atom/RPI.Public/ViewportContextBus.h>
- namespace EMotionFX
- {
- namespace Integration
- {
- void SimpleLODComponent::Configuration::Reflect(AZ::ReflectContext *context)
- {
- AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
- if (serializeContext)
- {
- serializeContext->Class<Configuration>()
- ->Version(2)
- ->Field("LODDistances", &Configuration::m_lodDistances)
- ->Field("EnableLODSampling", &Configuration::m_enableLodSampling)
- ->Field("LODSampleRates", &Configuration::m_lodSampleRates)
- ;
- AZ::EditContext* editContext = serializeContext->GetEditContext();
- if (editContext)
- {
- editContext->Class<SimpleLODComponent::Configuration>("Configuration", "The LOD Configuration.")
- ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
- ->Attribute(AZ::Edit::Attributes::AutoExpand, "")
- ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
- ->DataElement(0, &SimpleLODComponent::Configuration::m_lodDistances,
- "LOD distance (Max)", "The maximum camera distance of this LOD.")
- ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false)
- ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
- ->ElementAttribute(AZ::Edit::Attributes::Step, 0.01f)
- ->ElementAttribute(AZ::Edit::Attributes::Suffix, " m")
- ->ElementAttribute(AZ::Edit::Attributes::Min, 0.00f)
- ->DataElement(0, &SimpleLODComponent::Configuration::m_enableLodSampling,
- "Enable LOD anim graph sampling", "AnimGraph sample rate will adjust based on LOD level.")
- ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
- ->DataElement(0, &SimpleLODComponent::Configuration::m_lodSampleRates,
- "Anim graph sample rates", "The sample rate of anim graph based on LOD. Setting it to O means the maximum sample rate.")
- ->Attribute(AZ::Edit::Attributes::Visibility, &SimpleLODComponent::Configuration::GetEnableLodSampling)
- ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false)
- ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
- ->ElementAttribute(AZ::Edit::Attributes::Step, 1.0f)
- ->ElementAttribute(AZ::Edit::Attributes::Min, 0.0f);
- }
- }
- }
- void SimpleLODComponent::Configuration::Reset()
- {
- m_lodDistances.clear();
- }
- void SimpleLODComponent::Configuration::GenerateDefaultValue(size_t numLODs)
- {
- if (numLODs != m_lodDistances.size())
- {
- // Generate the default LOD (max) distance to 10, 20, 30....
- m_lodDistances.resize(numLODs);
- for (size_t i = 0; i < numLODs; ++i)
- {
- m_lodDistances[i] = i * 10.0f + 10.0f;
- }
- }
- if (numLODs != m_lodSampleRates.size())
- {
- // Generate the default LOD Sample Rate to 140, 60, 45, 25, 15, 10, 10, 10, ...
- constexpr AZStd::array defaultSampleRate {140.0f, 60.0f, 45.0f, 25.0f, 15.0f, 10.0f};
- m_lodSampleRates.resize(numLODs, 10.0f);
- // Do not copy more than what fits in defaultSampleRates or numLODs.
- size_t copyCount = std::min(defaultSampleRate.size(), numLODs);
- AZStd::copy(begin(defaultSampleRate), begin(defaultSampleRate) + copyCount, begin(m_lodSampleRates));
- }
- }
- bool SimpleLODComponent::Configuration::GetEnableLodSampling()
- {
- return m_enableLodSampling;
- }
- void SimpleLODComponent::Reflect(AZ::ReflectContext* context)
- {
- Configuration::Reflect(context);
- AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
- if (serializeContext)
- {
- serializeContext->Class<SimpleLODComponent, AZ::Component>()
- ->Version(1)
- ->Field("Configuration", &SimpleLODComponent::m_configuration)
- ;
- AZ::EditContext* editContext = serializeContext->GetEditContext();
- if (editContext)
- {
- editContext->Class<SimpleLODComponent>(
- "Simple LOD distance", "The Simple LOD distance component alters the actor LOD level based on distance to camera")
- ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
- ;
- }
- }
- }
- SimpleLODComponent::SimpleLODComponent(const Configuration* config)
- : m_actorInstance(nullptr)
- {
- if (config)
- {
- m_configuration = *config;
- }
- }
- SimpleLODComponent::~SimpleLODComponent()
- {
- }
- void SimpleLODComponent::Init()
- {
- }
- void SimpleLODComponent::Activate()
- {
- AZ::ApplicationTypeQuery appType;
- AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationBus::Events::QueryApplicationType, appType);
- if (appType.IsHeadless())
- {
- return;
- }
- ActorComponentNotificationBus::Handler::BusConnect(GetEntityId());
- AZ::TickBus::Handler::BusConnect();
- // Remember the lod type and level so that we can set it back to the previous one on deactivation of the component.
- AZ::Render::MeshComponentRequestBus::EventResult(m_previousLodType,
- GetEntityId(),
- &AZ::Render::MeshComponentRequestBus::Events::GetLodType);
- if (m_actorInstance)
- {
- m_previousLodLevel = m_actorInstance->GetLODLevel();
- }
- }
- void SimpleLODComponent::Deactivate()
- {
- AZ::ApplicationTypeQuery appType;
- AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationBus::Events::QueryApplicationType, appType);
- if (appType.IsHeadless())
- {
- return;
- }
- AZ::TickBus::Handler::BusDisconnect();
- ActorComponentNotificationBus::Handler::BusDisconnect();
- AZ::Render::MeshComponentRequestBus::Event(GetEntityId(),
- &AZ::Render::MeshComponentRequestBus::Events::SetLodType,
- m_previousLodType);
- if (m_actorInstance)
- {
- m_actorInstance->SetLODLevel(m_previousLodLevel);
- }
- }
- void SimpleLODComponent::OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance)
- {
- m_actorInstance = actorInstance;
- }
- void SimpleLODComponent::OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance)
- {
- AZ_UNUSED(actorInstance);
- m_actorInstance = nullptr;
- }
- void SimpleLODComponent::OnTick(float deltaTime, AZ::ScriptTimePoint time)
- {
- AZ_UNUSED(deltaTime);
- AZ_UNUSED(time);
- UpdateLodLevelByDistance(m_actorInstance, m_configuration, GetEntityId());
- }
- size_t SimpleLODComponent::GetLodByDistance(const AZStd::vector<float>& distances, float distance)
- {
- const size_t max = distances.size();
- for (size_t i = 0; i < max; ++i)
- {
- const float rDistance = distances[i];
- if (distance < rDistance)
- {
- return i;
- }
- }
- return max - 1;
- }
- void SimpleLODComponent::UpdateLodLevelByDistance(EMotionFX::ActorInstance* actorInstance, const Configuration& configuration, AZ::EntityId entityId)
- {
- if (actorInstance)
- {
- // Compute the distance between the camera and the entity
- AZ::Transform worldTransform;
- AZ::TransformBus::EventResult(worldTransform, entityId, &AZ::TransformBus::Events::GetWorldTM);
- const AZ::Vector3& worldPos = worldTransform.GetTranslation();
- auto viewportContextManager = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
- if (!viewportContextManager)
- {
- return;
- }
- AZ::RPI::ViewportContextPtr defaultViewportContext =
- viewportContextManager->GetViewportContextByName(viewportContextManager->GetDefaultViewportContextName());
- if (!defaultViewportContext)
- {
- return;
- }
- const float distance = worldPos.GetDistance(defaultViewportContext->GetCameraTransform().GetTranslation());
- const size_t requestedLod = GetLodByDistance(configuration.m_lodDistances, distance);
- actorInstance->SetLODLevel(requestedLod);
- if (configuration.m_enableLodSampling)
- {
- const float animGraphSampleRate = configuration.m_lodSampleRates[requestedLod];
- const float updateRateInSeconds = animGraphSampleRate > 0.0f ? 1.0f / animGraphSampleRate : 0.0f;
- actorInstance->SetMotionSamplingRate(updateRateInSeconds);
- }
- else if (actorInstance->GetMotionSamplingRate() != 0)
- {
- actorInstance->SetMotionSamplingRate(0);
- }
- // Disable the automatic mesh LOD level adjustment based on screen space in case a simple LOD component is present.
- // The simple LOD component overrides the mesh LOD level and syncs the skeleton with the mesh LOD level.
- AZ::Render::MeshComponentRequestBus::Event(entityId,
- &AZ::Render::MeshComponentRequestBus::Events::SetLodType,
- AZ::RPI::Cullable::LodType::SpecificLod);
- // When setting the actor instance LOD level, a change is just requested and with the next update it will get applied.
- // This means that the current LOD level might differ from the requested one. We need to sync the Atom LOD level with the
- // current LOD level of the actor instance to avoid skinning artifacts. The requested LOD level will be present and applied
- // the following frame.
- const size_t currentLod = actorInstance->GetLODLevel();
- AZ::Render::MeshComponentRequestBus::Event(entityId,
- &AZ::Render::MeshComponentRequestBus::Events::SetLodOverride,
- static_cast<AZ::RPI::Cullable::LodOverride>(currentLod));
- }
- }
- } // namespace integration
- } // namespace EMotionFX
|