Files
UnrealEngine/Engine/Source/Developer/HierarchicalLODUtilities/Private/HierarchicalLODProxyProcessor.cpp
2025-05-18 13:04:45 +08:00

203 lines
6.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "HierarchicalLODProxyProcessor.h"
#include "Misc/ScopeLock.h"
#include "Engine/StaticMesh.h"
#include "Engine/LODActor.h"
#include "IHierarchicalLODUtilities.h"
#include "HierarchicalLODUtilitiesModule.h"
#include "Editor.h"
#include "Modules/ModuleManager.h"
#include "Interfaces/IProjectManager.h"
#include "StaticMeshResources.h"
#include "Logging/TokenizedMessage.h"
#include "Logging/MessageLog.h"
#include "Misc/UObjectToken.h"
#include "Engine/HLODProxy.h"
#include "HierarchicalLOD.h"
#include "Algo/Transform.h"
#include "Modules/ModuleManager.h"
FHierarchicalLODProxyProcessor::FHierarchicalLODProxyProcessor()
{
#if WITH_EDITOR
FEditorDelegates::MapChange.AddRaw(this, &FHierarchicalLODProxyProcessor::OnMapChange);
FEditorDelegates::NewCurrentLevel.AddRaw(this, &FHierarchicalLODProxyProcessor::OnNewCurrentLevel);
#endif // WITH_EDITOR
}
FHierarchicalLODProxyProcessor::~FHierarchicalLODProxyProcessor()
{
#if WITH_EDITOR
FEditorDelegates::MapChange.RemoveAll(this);
FEditorDelegates::NewCurrentLevel.RemoveAll(this);
#endif // WITH_EDITOR
}
bool FHierarchicalLODProxyProcessor::Tick(float DeltaTime)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FHierarchicalLODProxyProcessor_Tick);
FScopeLock Lock(&StateLock);
while (ToProcessJobs.Num())
{
FProcessData* Data = ToProcessJobs.Pop();
UStaticMesh* MainMesh = nullptr;
for (TStrongObjectPtr<UObject>& AssetObject : Data->AssetObjects)
{
// Check if this is the generated proxy (static-)mesh
UStaticMesh* StaticMesh = Cast<UStaticMesh>(AssetObject.Get());
if (StaticMesh)
{
check(StaticMesh != nullptr);
MainMesh = StaticMesh;
}
}
check(MainMesh != nullptr);
// Force lightmap coordinate to 0 for proxy meshes
MainMesh->SetLightMapCoordinateIndex(0);
// Trigger post edit change, simulating we made a change in the Static mesh editor (could only call Build, but this is for possible future changes)
MainMesh->PostEditChange();
// Set new static mesh, location and sub-objects (UObjects)
bool bDirtyPackage = false;
UStaticMesh* PreviousStaticMesh = Data->LODActor->GetStaticMeshComponent()->GetStaticMesh();
bDirtyPackage |= (PreviousStaticMesh != MainMesh);
Data->LODActor->SetStaticMesh(MainMesh);
bDirtyPackage |= (Data->LODActor->GetActorLocation() != FVector::ZeroVector);
Data->LODActor->SetActorLocation(FVector::ZeroVector);
// Check resulting mesh and give a warning if it exceeds the vertex / triangle cap for certain platforms
FProjectStatus ProjectStatus;
if (IProjectManager::Get().QueryStatusForCurrentProject(ProjectStatus) && (ProjectStatus.IsTargetPlatformSupported(TEXT("Android")) || ProjectStatus.IsTargetPlatformSupported(TEXT("IOS"))))
{
if (MainMesh->GetRenderData() && MainMesh->GetRenderData()->LODResources.Num() && MainMesh->GetRenderData()->LODResources[0].IndexBuffer.Is32Bit())
{
FMessageLog("HLODResults").Warning()
->AddToken(FUObjectToken::Create(Data->LODActor))
->AddToken(FTextToken::Create(FText::FromString(TEXT(" Mesh has more that 65535 vertices, incompatible with mobile; forcing 16-bit (will probably cause rendering issues)."))));
FMessageLog("HLODResults").Open();
}
}
// Calculate correct drawing distance according to given screensize
// At the moment this assumes a fixed field of view of 90 degrees (horizontal and vertical axi)
static const float FOVRad = 90.0f * (float)PI / 360.0f;
static const FMatrix ProjectionMatrix = FPerspectiveMatrix(FOVRad, 1920, 1080, 0.01f);
FBoxSphereBounds Bounds = Data->LODActor->GetStaticMeshComponent()->CalcBounds(FTransform());
FHierarchicalLODUtilitiesModule& Module = FModuleManager::LoadModuleChecked<FHierarchicalLODUtilitiesModule>("HierarchicalLODUtilities");
IHierarchicalLODUtilities* Utilities = Module.GetUtilities();
float DrawDistance;
if (Data->LODSetup.bUseOverrideDrawDistance)
{
DrawDistance = Data->LODSetup.OverrideDrawDistance;
}
else
{
DrawDistance = Utilities->CalculateDrawDistanceFromScreenSize(Bounds.SphereRadius, Data->LODSetup.TransitionScreenSize, ProjectionMatrix);
}
bDirtyPackage |= (Data->LODActor->GetDrawDistance() != DrawDistance);
Data->LODActor->SetDrawDistance(DrawDistance);
Data->LODActor->DetermineShadowingFlags();
Data->LODActor->UpdateSubActorLODParents();
// Link proxy to actor
UHLODProxy* PreviousProxy = Data->LODActor->GetProxy();
Data->Proxy->AddMesh(Data->LODActor, MainMesh, UHLODProxy::GenerateKeyForActor(Data->LODActor));
bDirtyPackage |= (Data->LODActor->GetProxy() != PreviousProxy);
if(bDirtyPackage)
{
Data->LODActor->MarkPackageDirty();
}
// Clean out standalone meshes from the proxy package as we are about to GC, and mesh merging creates assets that are
// supposed to be standalone
Utilities->CleanStandaloneAssetsInPackage(Data->Proxy->GetOutermost());
// Collect garbage to clean up old unreferenced data in the HLOD package
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
delete Data;
}
return true;
}
FGuid FHierarchicalLODProxyProcessor::AddProxyJob(ALODActor* InLODActor, UHLODProxy* InProxy, const FHierarchicalSimplification& LODSetup)
{
FScopeLock Lock(&StateLock);
check(InLODActor);
// Create new unique Guid which will be used to identify this job
FGuid JobGuid = FGuid::NewGuid();
// Set up processing data
FProcessData* Data = new FProcessData();
Data->LODActor = InLODActor;
Data->Proxy = InProxy;
Data->LODSetup = LODSetup;
JobActorMap.Add(JobGuid, Data);
return JobGuid;
}
void FHierarchicalLODProxyProcessor::ProcessProxy(const FGuid InGuid, TArray<UObject*>& InAssetsToSync)
{
FScopeLock Lock(&StateLock);
// Check if there is data stored for the given Guid
FProcessData** DataPtr = JobActorMap.Find(InGuid);
if (DataPtr)
{
// If so push the job onto the processing queue
FProcessData* Data = *DataPtr;
JobActorMap.Remove(InGuid);
if (Data && Data->LODActor && InAssetsToSync.Num())
{
Algo::Transform(InAssetsToSync, Data->AssetObjects, [](UObject* InItem){ return TStrongObjectPtr<UObject>(InItem); });
ToProcessJobs.Push(Data);
}
}
}
FCreateProxyDelegate& FHierarchicalLODProxyProcessor::GetCallbackDelegate()
{
// Bind ProcessProxy to the delegate (if not bound yet)
if (!CallbackDelegate.IsBound())
{
CallbackDelegate.BindRaw(this, &FHierarchicalLODProxyProcessor::ProcessProxy);
}
return CallbackDelegate;
}
bool FHierarchicalLODProxyProcessor::IsProxyGenerationRunning() const
{
return JobActorMap.Num() > 0 || ToProcessJobs.Num() > 0;
}
void FHierarchicalLODProxyProcessor::OnMapChange(uint32 MapFlags)
{
ClearProcessingData();
}
void FHierarchicalLODProxyProcessor::OnNewCurrentLevel()
{
ClearProcessingData();
}
void FHierarchicalLODProxyProcessor::ClearProcessingData()
{
FScopeLock Lock(&StateLock);
JobActorMap.Empty();
ToProcessJobs.Empty();
}