// 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(InComponent)) { if(GroomComponent->GetGroomSolver()) { if(UGroomSolverComponent* GroomSolver = Cast(GroomComponent->GetGroomSolver())) { return static_cast(GroomSolver->GetSimulationProxy()); } } } return nullptr; } bool FGroomCacheAdapter::ValidForPlayback(UPrimitiveComponent* InComponent, UChaosCache* InCache) const { const UGroomComponent* GroomComponent = CastChecked(InComponent); return GroomComponent != nullptr; } void FGroomCacheAdapter::InitializeForLoad(UPrimitiveComponent* InComponent, FObservedComponent& InObserved) { bIsLoading = true; if(UGroomComponent* GroomComponent = CastChecked(InComponent)) { if(UChaosCache* ChaosCache = InObserved.GetChaosCache()) { if(ChaosCache->GetCacheData() && !GroomComponent->GetGroomCache()) { GroomComponent->SetGroomCache(Cast(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(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(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(PackageCache, *NameCache, RF_Public | RF_Standalone | RF_Transactional); FHairStrandsCore::AssetHelper().RegisterAsset(CacheData); ChaosCache->SetCacheData(CacheData); } GroomCache.CacheAsset = Cast(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(InComponent)) { if(FGroomCacheData* GroomCache = GroomCaches.Find(InComponent)) { TSharedPtr PositionOutput = MakeShared(); 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& 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& 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 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 LocalData = GroupsData; FillGuidesPositions(*GroomCache.PositionsBuffer[FrameIndex], LocalData); GroomCache.CacheProcessor.AddGroomSample(MoveTemp(LocalData)); } } } } } } #endif void FGroomCacheAdapter::Finalize() { if(!bIsLoading) { for(TPair& GroomCache : GroomCaches) { if(UGroomComponent* GroomComponent = CastChecked(GroomCache.Key)) { #if WITH_EDITORONLY_DATA if(!GroomCache.Value.PositionsBuffer.IsEmpty()) { float MaxTime = -TNumericLimits::Max(); int32 MaxFrame = -TNumericLimits::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