Files
UnrealEngine/Engine/Plugins/Runtime/HairStrands/Source/HairStrandsSolver/Private/GroomCacheAdapter.cpp
2025-05-18 13:04:45 +08:00

294 lines
9.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "GroomCacheAdapter.h"
#include "GroomBuilder.h"
#include "GroomCache.h"
#include "Chaos/ChaosCache.h"
#include "Chaos/CacheManagerActor.h"
#include "GroomComponent.h"
#include "GroomSolverComponent.h"
#include "UObject/Package.h"
#include "UObject/UObjectGlobals.h"
#include "HairStrandsCore.h"
DEFINE_LOG_CATEGORY(LogGroomCache)
namespace UE::Groom
{
FGroomCacheAdapter::~FGroomCacheAdapter()
{}
Chaos::FComponentCacheAdapter::SupportType FGroomCacheAdapter::SupportsComponentClass(UClass* InComponentClass) const
{
const UClass* Desired = GetDesiredClass();
if(InComponentClass == Desired)
{
return Chaos::FComponentCacheAdapter::SupportType::Direct;
}
else if(InComponentClass->IsChildOf(Desired))
{
return Chaos::FComponentCacheAdapter::SupportType::Derived;
}
return Chaos::FComponentCacheAdapter::SupportType::None;
}
UClass* FGroomCacheAdapter::GetDesiredClass() const
{
return UGroomComponent::StaticClass();
}
uint8 FGroomCacheAdapter::GetPriority() const
{
return EngineAdapterPriorityBegin;
}
FGuid FGroomCacheAdapter::GetGuid() const
{
FGuid NewGuid;
checkSlow(FGuid::Parse(TEXT("FC61D2A13092410CBCF2F767C8490986"), NewGuid));
return NewGuid;
}
Chaos::FPhysicsSolver* FGroomCacheAdapter::GetComponentSolver(UPrimitiveComponent* InComponent) const
{
return nullptr;
}
Chaos::FPhysicsSolverEvents* FGroomCacheAdapter::BuildEventsSolver(UPrimitiveComponent* InComponent) const
{
if(UGroomComponent* GroomComponent = CastChecked<UGroomComponent>(InComponent))
{
if(GroomComponent->GetGroomSolver())
{
if(UGroomSolverComponent* GroomSolver = Cast<UGroomSolverComponent>(GroomComponent->GetGroomSolver()))
{
return static_cast<FDataflowGroomSolverProxy*>(GroomSolver->GetSimulationProxy());
}
}
}
return nullptr;
}
bool FGroomCacheAdapter::ValidForPlayback(UPrimitiveComponent* InComponent, UChaosCache* InCache) const
{
const UGroomComponent* GroomComponent = CastChecked<UGroomComponent>(InComponent);
return GroomComponent != nullptr;
}
void FGroomCacheAdapter::InitializeForLoad(UPrimitiveComponent* InComponent, FObservedComponent& InObserved)
{
bIsLoading = true;
if(UGroomComponent* GroomComponent = CastChecked<UGroomComponent>(InComponent))
{
if(UChaosCache* ChaosCache = InObserved.GetChaosCache())
{
if(ChaosCache->GetCacheData() && !GroomComponent->GetGroomCache())
{
GroomComponent->SetGroomCache(Cast<UGroomCache>(ChaosCache->GetCacheData()));
GroomComponent->SetManualTick(true);
if(GroomComponent->GroomAsset && !GroomComponent->GroomAsset->GetEnableSimulationCache())
{
GroomComponent->GroomAsset->ValidateSimulationCache();
}
}
}
}
}
void FGroomCacheAdapter::SetRestState(UPrimitiveComponent* InComponent, UChaosCache* InCache, const FTransform& InRootTransform, Chaos::FReal InTime) const
{
if(bIsLoading)
{
if(UGroomComponent* GroomComponent = CastChecked<UGroomComponent>(InComponent))
{
if(GroomComponent->GetGroomCache())
{
if(InTime < GroomComponent->GetGroomCache()->GetDuration())
{
GroomComponent->TickAtThisTime(InTime, true, false, true);
}
}
}
}
}
bool FGroomCacheAdapter::InitializeForRecord(UPrimitiveComponent* InComponent, FObservedComponent& InObserved)
{
bIsLoading = false;
if(UGroomComponent* GroomComponent = CastChecked<UGroomComponent>(InComponent))
{
FGroomCacheData& GroomCache = GroomCaches.FindOrAdd(InComponent);
GroomCache.AnimInfo.Attributes = EGroomCacheAttributes::Position;
GroomCache.AnimInfo.StartFrame = 0;
GroomCache.AnimInfo.StartTime = 0.0f;
GroomCache.AnimInfo.EndFrame = 0;
GroomCache.AnimInfo.EndTime = 0.0f;
GroomCache.PositionsBuffer.Reset();
GroomCache.CacheTimes.Reset();
GroomCache.CacheName = InObserved.CacheName.ToString();
GroomCache.CacheProcessor = FGroomCacheProcessor(EGroomCacheType::Guides, GroomCache.AnimInfo.Attributes);
GroomComponent->SetGroomCache(nullptr);
GroomComponent->SetManualTick(false);
if(UChaosCache* ChaosCache = InObserved.GetChaosCache())
{
#if WITH_EDITOR
if(!ChaosCache->GetCacheData())
{
FString PackageNameCache, NameCache;
FHairStrandsCore::AssetHelper().CreateFilename(GroomComponent->GroomAsset->GetOutermost()->GetName(), TEXT("_GroomCache"), PackageNameCache, NameCache);
UPackage* PackageCache = CreatePackage(*PackageNameCache);
UGroomCache* CacheData = NewObject<UGroomCache>(PackageCache, *NameCache, RF_Public | RF_Standalone | RF_Transactional);
FHairStrandsCore::AssetHelper().RegisterAsset(CacheData);
ChaosCache->SetCacheData(CacheData);
}
GroomCache.CacheAsset = Cast<UGroomCache>(ChaosCache->GetCacheData());
#endif
}
}
return true;
}
void FGroomCacheAdapter::Record_PostSolve(UPrimitiveComponent* InComponent, const FTransform& InRootTransform, FPendingFrameWrite& OutFrame, Chaos::FReal InTime) const
{
if(!bIsLoading)
{
if(const UGroomComponent* GroomComponent = CastChecked<UGroomComponent>(InComponent))
{
if(FGroomCacheData* GroomCache = GroomCaches.Find(InComponent))
{
TSharedPtr<FStrandsPositionOutput> PositionOutput = MakeShared<FStrandsPositionOutput>();
GroomCache->PositionsBuffer.Add(PositionOutput);
GroomCache->CacheTimes.Add(InTime);
#if WITH_EDITOR
ReadbackTasks.Add(FFunctionGraphTask::CreateAndDispatchWhenReady([GroomComponent, PositionOutput](
ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
RequestStrandsPosition(GroomComponent, PositionOutput, true);
},TStatId(), nullptr, ENamedThreads::Type::RHIThread ));
#endif
GroomCache->AnimInfo.EndFrame++;
GroomCache->AnimInfo.EndTime = InTime;
}
}
}
}
bool FGroomCacheAdapter::InitializeForPlayback(UPrimitiveComponent* InComponent, FObservedComponent& InObserved, float InTime)
{
bIsLoading = false;
return false;
}
FORCEINLINE void FillGuidesPositions(const FStrandsPositionOutput& PositionsBuffer, TArray<FGroomCacheInputData>& GroupsData)
{
if(PositionsBuffer.IsValid() && PositionsBuffer.Groups.Num() == GroupsData.Num())
{
for(uint32 GroupIndex = 0, NumGroups = GroupsData.Num(); GroupIndex < NumGroups; ++GroupIndex)
{
const FStrandsPositionOutput::FGroup& PositionsGroup = PositionsBuffer.Groups[GroupIndex];
TArray<FVector3f>& GuidesPositions = GroupsData[GroupIndex].Guides.StrandsPoints.PointsPosition;
for(uint32 GuideIndex = 0, PointIndex = 0, NumGuides = PositionsGroup.Num(); GuideIndex < NumGuides; ++GuideIndex)
{
for(uint32 VertexIndex = 0, NumVertices = PositionsGroup[GuideIndex].Num()-1; VertexIndex < NumVertices; ++VertexIndex, ++PointIndex)
{
GuidesPositions[PointIndex] = PositionsGroup[GuideIndex][VertexIndex];
}
}
}
}
}
void FGroomCacheAdapter::WaitForSolverTasks(UPrimitiveComponent* InComponent) const
{
if (ReadbackTasks.Num())
{
FTaskGraphInterface::Get().WaitUntilTasksComplete(ReadbackTasks, ENamedThreads::GameThread);
}
}
#if WITH_EDITORONLY_DATA
void FGroomCacheAdapter::FillCacheProcessor(const UGroomComponent* GroomComponent, FGroomCacheData& GroomCache, const int32 NumFrames, float& MaxTime, int32& MaxFrame)
{
if(UGroomAsset* GroomAsset = GroomComponent->GroomAsset)
{
TArray<FGroomCacheInputData> GroupsData;
if(UE::Groom::BuildGroupsData(GroomAsset->GetHairDescription(), GroomAsset->GetHairGroupsPlatformData(), GroomAsset->GetHairGroupsInfo(), GroomAsset->GetHairGroupsInterpolation(), GroupsData))
{
if(GroomCache.PositionsBuffer.Num() == NumFrames)
{
for(int32 FrameIndex = 0; FrameIndex < NumFrames; ++FrameIndex)
{
if(GroomCache.PositionsBuffer[FrameIndex]->IsValid())
{
MaxTime = FMath::Max(MaxTime, GroomCache.CacheTimes[FrameIndex]);
MaxFrame = FMath::Max(MaxFrame, FrameIndex+1);
TArray<FGroomCacheInputData> LocalData = GroupsData;
FillGuidesPositions(*GroomCache.PositionsBuffer[FrameIndex], LocalData);
GroomCache.CacheProcessor.AddGroomSample(MoveTemp(LocalData));
}
}
}
}
}
}
#endif
void FGroomCacheAdapter::Finalize()
{
if(!bIsLoading)
{
for(TPair<UPrimitiveComponent*,FGroomCacheData>& GroomCache : GroomCaches)
{
if(UGroomComponent* GroomComponent = CastChecked<UGroomComponent>(GroomCache.Key))
{
#if WITH_EDITORONLY_DATA
if(!GroomCache.Value.PositionsBuffer.IsEmpty())
{
float MaxTime = -TNumericLimits<float>::Max();
int32 MaxFrame = -TNumericLimits<int32>::Max();
FillCacheProcessor(GroomComponent, GroomCache.Value,
GroomCache.Value.AnimInfo.EndFrame - GroomCache.Value.AnimInfo.StartFrame, MaxTime, MaxFrame);
GroomCache.Value.AnimInfo.EndTime = MaxTime;
GroomCache.Value.AnimInfo.EndFrame = MaxFrame;
GroomCache.Value.AnimInfo.NumFrames = GroomCache.Value.AnimInfo.EndFrame - GroomCache.Value.AnimInfo.StartFrame;
GroomCache.Value.AnimInfo.Duration = GroomCache.Value.AnimInfo.EndTime - GroomCache.Value.AnimInfo.StartTime;
GroomCache.Value.AnimInfo.SecondsPerFrame = GroomCache.Value.AnimInfo.Duration / GroomCache.Value.AnimInfo.NumFrames;
GroomCache.Value.PositionsBuffer.Reset();
GroomCache.Value.CacheTimes.Reset();
if(GroomCache.Value.CacheAsset)
{
GroomCache.Value.CacheAsset->Initialize(EGroomCacheType::Guides);
UE::Groom::BuildGroomCache(GroomCache.Value.CacheProcessor, GroomCache.Value.AnimInfo, GroomCache.Value.CacheAsset);
if(GroomCache.Value.CacheAsset->MarkPackageDirty())
{
FHairStrandsCore::SaveAsset(GroomCache.Value.CacheAsset);
}
}
}
#endif
GroomComponent->SetGroomCache(nullptr);
GroomComponent->SetManualTick(false);
}
}
}
GroomCaches.Reset();
ReadbackTasks.Reset();
}
} // namespace UE::Groom