123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- /*
- * 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/RTTI/BehaviorContext.h>
- #include <Atom/RPI.Public/Scene.h>
- #include <AzFramework/Components/TransformComponent.h>
- #include <Integration/Components/ActorComponent.h>
- #include <EMotionFX/Source/TransformData.h>
- #include <EMotionFX/Source/ActorInstance.h>
- #include <EMotionFX/Source/Node.h>
- // Hair Specific
- #include <TressFX/TressFXAsset.h>
- #include <TressFX/TressFXSettings.h>
- #include <Rendering/HairFeatureProcessor.h>
- #include <Components/HairComponentController.h>
- namespace AZ
- {
- namespace Render
- {
- namespace Hair
- {
- HairComponentController::~HairComponentController()
- {
- RemoveHairObject();
- }
- void HairComponentController::Reflect(ReflectContext* context)
- {
- HairComponentConfig::Reflect(context);
- if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
- {
- serializeContext->Class<HairComponentController>()
- ->Version(2)
- ->Field("Configuration", &HairComponentController::m_configuration)
- ;
- }
- if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
- {
- behaviorContext->EBus<HairRequestsBus>("HairRequestsBus")
- ->Attribute(AZ::Script::Attributes::Module, "render")
- ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
- // Insert auto-gen behavior context here...
- ;
- }
- }
- void HairComponentController::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
- {
- provided.push_back(AZ_CRC_CE("HairService"));
- }
- void HairComponentController::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
- {
- incompatible.push_back(AZ_CRC_CE("HairService"));
- }
- void HairComponentController::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
- {
- // Dependency in the Actor due to the need to get the bone / joint matrices
- required.push_back(AZ_CRC_CE("EMotionFXActorService"));
- }
- HairComponentController::HairComponentController(const HairComponentConfig& config)
- : m_configuration(config)
- {
- }
- void HairComponentController::Activate(EntityId entityId)
- {
- m_entityId = entityId;
- m_featureProcessor = RPI::Scene::GetFeatureProcessorForEntity<Hair::HairFeatureProcessor>(m_entityId);
- if (m_featureProcessor)
- {
- m_featureProcessor->SetHairGlobalSettings(m_configuration.m_hairGlobalSettings);
- if (!m_renderObject)
- {
- // Call this function if object doesn't exist to trigger the load of the existing asset
- OnHairAssetChanged();
- }
- }
- EMotionFX::Integration::ActorComponentNotificationBus::Handler::BusConnect(m_entityId);
- HairRequestsBus::Handler::BusConnect(m_entityId);
- TickBus::Handler::BusConnect();
- HairGlobalSettingsNotificationBus::Handler::BusConnect();
- }
- void HairComponentController::Deactivate()
- {
- HairRequestsBus::Handler::BusDisconnect(m_entityId);
- EMotionFX::Integration::ActorComponentNotificationBus::Handler::BusDisconnect(m_entityId);
- Data::AssetBus::MultiHandler::BusDisconnect();
- TickBus::Handler::BusDisconnect();
- HairGlobalSettingsNotificationBus::Handler::BusDisconnect();
- RemoveHairObject();
- m_entityId.SetInvalid();
- }
- void HairComponentController::SetConfiguration(const HairComponentConfig& config)
- {
- m_configuration = config;
- OnHairConfigChanged();
- }
- const HairComponentConfig& HairComponentController::GetConfiguration() const
- {
- return m_configuration;
- }
- void HairComponentController::OnHairAssetChanged()
- {
- Data::AssetBus::MultiHandler::BusDisconnect();
- if (m_configuration.m_hairAsset.GetId().IsValid())
- {
- Data::AssetBus::MultiHandler::BusConnect(m_configuration.m_hairAsset.GetId());
- m_configuration.m_hairAsset.QueueLoad();
- }
- else
- {
- RemoveHairObject();
- }
- }
- void HairComponentController::OnHairGlobalSettingsChanged(const HairGlobalSettings& hairGlobalSettings)
- {
- m_configuration.m_hairGlobalSettings = hairGlobalSettings;
- }
- void HairComponentController::RemoveHairObject()
- {
- if (m_featureProcessor)
- {
- m_featureProcessor->RemoveHairRenderObject(m_renderObject);
- }
- m_renderObject.reset();
- }
- void HairComponentController::OnHairConfigChanged()
- {
- // The actual config change to render object happens in the onTick function. We do this to make sure it
- // always happens pre-rendering. There is no need to do it before render object created, because the object
- // will always be created with the updated configuration.
- if (m_renderObject)
- {
- m_configChanged = true;
- }
- }
- void HairComponentController::OnAssetReady(Data::Asset<Data::AssetData> asset)
- {
- if (asset.GetId() == m_configuration.m_hairAsset.GetId())
- {
- m_configuration.m_hairAsset = asset;
- CreateHairObject();
- }
- }
- void HairComponentController::OnAssetReloaded(Data::Asset<Data::AssetData> asset)
- {
- OnAssetReady(asset);
- }
- void HairComponentController::OnActorInstanceCreated([[maybe_unused]]EMotionFX::ActorInstance* actorInstance)
- {
- CreateHairObject();
- }
- void HairComponentController::OnActorInstanceDestroyed([[maybe_unused]]EMotionFX::ActorInstance* actorInstance)
- {
- RemoveHairObject();
- }
- void HairComponentController::OnTick([[maybe_unused]]float deltaTime, [[maybe_unused]]AZ::ScriptTimePoint time)
- {
- if (!m_renderObject)
- {
- return;
- }
- // Config change to renderObject happens on the OnTick, so we know the it occurs before render update.
- if (m_configChanged)
- {
- const float MAX_SIMULATION_TIME_STEP = 0.033f; // Assuming minimal of 30 fps
- float currentDeltaTime = AZStd::min(deltaTime, MAX_SIMULATION_TIME_STEP);
- m_renderObject->UpdateSimulationParameters(&m_configuration.m_simulationSettings, currentDeltaTime);
- // [To Do] Hair - Allow update of the following settings to control dynamic LOD
- const float distanceFromCamera = 1.0f;
- const float updateShadows = false;
- m_renderObject->UpdateRenderingParameters(
- &m_configuration.m_renderingSettings, RESERVED_PIXELS_FOR_OIT, distanceFromCamera, updateShadows);
- m_configChanged = false;
- // Only load the image asset when the dirty flag has been set on the settings.
- if (m_configuration.m_renderingSettings.m_imgDirty)
- {
- m_renderObject->LoadImageAsset(&m_configuration.m_renderingSettings);
- m_configuration.m_renderingSettings.m_imgDirty = false;
- }
- }
- // Update the enable flag for hair render object
- // The enable flag depends on the visibility of render actor instance and the flag of hair configuration.
- bool actorVisible = false;
- EMotionFX::Integration::ActorComponentRequestBus::EventResult(
- actorVisible, m_entityId, &EMotionFX::Integration::ActorComponentRequestBus::Events::GetRenderActorVisible);
- m_renderObject->SetEnabled(actorVisible);
- UpdateActorMatrices();
- }
- int HairComponentController::GetTickOrder()
- {
- return AZ::TICK_PRE_RENDER;
- }
- bool HairComponentController::UpdateActorMatrices()
- {
- if (!m_renderObject->IsEnabled())
- {
- return false;
- }
- EMotionFX::ActorInstance* actorInstance = nullptr;
- EMotionFX::Integration::ActorComponentRequestBus::EventResult(
- actorInstance, m_entityId, &EMotionFX::Integration::ActorComponentRequestBus::Events::GetActorInstance);
- if (!actorInstance)
- {
- return false;
- }
- const EMotionFX::TransformData* transformData = actorInstance->GetTransformData();
- if (!transformData)
- {
- AZ_WarningOnce("Hair Gem", false, "Error getting the transformData from the actorInstance.");
- return false;
- }
- // In EMotionFX the skinning matrices is stored as a 3x4. The conversion to 4x4 matrices happens at the update bone matrices function.
- // In here we use the boneIndexMap to find the correct EMotionFX bone index (also as the global bone index), and passing the
- // matrices of those bones to the hair render object. We do this for both hair and collision bone matrices.
- const AZ::Matrix3x4* matrices = transformData->GetSkinningMatrices();
- for (AZ::u32 tressFXBoneIndex = 0; tressFXBoneIndex < m_cachedHairBoneMatrices.size(); ++tressFXBoneIndex)
- {
- const AZ::u32 emfxBoneIndex = m_hairBoneIndexLookup[tressFXBoneIndex];
- m_cachedHairBoneMatrices[tressFXBoneIndex] = matrices[emfxBoneIndex];
- }
- for (AZ::u32 tressFXBoneIndex = 0; tressFXBoneIndex < m_cachedCollisionBoneMatrices.size(); ++tressFXBoneIndex)
- {
- const AZ::u32 emfxBoneIndex = m_collisionBoneIndexLookup[tressFXBoneIndex];
- m_cachedCollisionBoneMatrices[tressFXBoneIndex] = matrices[emfxBoneIndex];
- }
- m_entityWorldMatrix = Matrix3x4::CreateFromTransform(actorInstance->GetWorldSpaceTransform().ToAZTransform());
- m_renderObject->UpdateBoneMatrices(m_entityWorldMatrix, m_cachedHairBoneMatrices);
- return true;
- }
- bool HairComponentController::GenerateLocalToGlobalBoneIndex(
- EMotionFX::ActorInstance* actorInstance, AMD::TressFXAsset* hairAsset)
- {
- // Generate local TressFX to global EMFX bone index lookup.
- AMD::BoneNameToIndexMap globalNameToIndexMap;
- const EMotionFX::Skeleton* skeleton = actorInstance->GetActor()->GetSkeleton();
- if (!skeleton)
- {
- AZ_Error("Hair Gem", false, "Actor could not retrieve his skeleton.");
- return false;
- }
- const uint32_t numBones = uint32_t(skeleton->GetNumNodes());
- globalNameToIndexMap.reserve(size_t(numBones));
- for (uint32_t i = 0; i < numBones; ++i)
- {
- const char* boneName = skeleton->GetNode(i)->GetName();
- globalNameToIndexMap[boneName] = i;
- }
- if (!hairAsset->GenerateLocaltoGlobalHairBoneIndexLookup(globalNameToIndexMap, m_hairBoneIndexLookup) ||
- !hairAsset->GenerateLocaltoGlobalCollisionBoneIndexLookup(globalNameToIndexMap, m_collisionBoneIndexLookup))
- {
- AZ_Error("Hair Gem", false, "Cannot convert local bone index to global bone index. The hair asset may not be compatible with the actor.");
- return false;
- }
- return true;
- }
- // The hair object will only be created if both conditions are met:
- // 1. The hair asset is loaded
- // 2. The actor instance is created
- bool HairComponentController::CreateHairObject()
- {
- // Do not create a hairRenderObject when actor instance hasn't been created.
- EMotionFX::ActorInstance* actorInstance = nullptr;
- EMotionFX::Integration::ActorComponentRequestBus::EventResult(
- actorInstance, m_entityId, &EMotionFX::Integration::ActorComponentRequestBus::Events::GetActorInstance);
- if (!actorInstance)
- {
- return false;
- }
- if (!m_featureProcessor)
- {
- AZ_Error("Hair Gem", false, "Required feature processor does not exist yet");
- return false;
- }
- if (!m_configuration.m_hairAsset.GetId().IsValid() || !m_configuration.m_hairAsset.IsReady())
- {
- AZ_Warning("Hair Gem", false, "Hair Asset was not ready - second attempt will be made when ready");
- return false;
- }
- AMD::TressFXAsset* hairAsset = m_configuration.m_hairAsset.Get()->m_tressFXAsset.get();
- if (!hairAsset)
- {
- AZ_Error("Hair Gem", false, "Hair asset could not be loaded");
- return false;
- }
- if (!GenerateLocalToGlobalBoneIndex(actorInstance, hairAsset))
- {
- return false;
- }
-
- // First remove the existing hair object - this can happen if the configuration
- // or the hair asset selected changes.
- RemoveHairObject();
- // create a new instance - will remove the old one.
- m_renderObject.reset(new HairRenderObject());
- AZStd::string hairName;
- AzFramework::StringFunc::Path::GetFileName(m_configuration.m_hairAsset.GetHint().c_str(), hairName);
- if (!m_renderObject->Init( m_featureProcessor, hairName.c_str(), hairAsset,
- &m_configuration.m_simulationSettings, &m_configuration.m_renderingSettings))
- {
- AZ_Warning("Hair Gem", false, "Hair object was not initialize succesfully");
- m_renderObject.reset(); // no instancing yet - remove manually
- return false;
- }
- // Resize the bone matrices array. The size should equal to the number of bones in the tressFXAsset.
- m_cachedHairBoneMatrices.resize(m_hairBoneIndexLookup.size());
- m_cachedCollisionBoneMatrices.resize(m_collisionBoneIndexLookup.size());
- // Feature processor registration that will hold an instance.
- // Remark: DO NOT remove the TressFX asset - it's data might be required for
- // more instance hair objects.
- m_featureProcessor->AddHairRenderObject(m_renderObject);
- return true;
- }
- } // namespace Hair
- } // namespace Render
- } // namespace AZ
|