EM_Task/UnrealEd/Private/LODCluster.cpp
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

278 lines
8.0 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LODCluster.h"
#include "Modules/ModuleManager.h"
#include "Engine/World.h"
#include "Components/StaticMeshComponent.h"
#include "GameFramework/Volume.h"
#include "Components/InstancedStaticMeshComponent.h"
#include "Engine/HLODProxy.h"
#if WITH_EDITOR
#include "Engine/LODActor.h"
#include "GameFramework/WorldSettings.h"
#include "IHierarchicalLODUtilities.h"
#include "HierarchicalLODUtilitiesModule.h"
#endif // WITH_EDITOR
#define LOCTEXT_NAMESPACE "LODCluster"
#define CM_TO_METER 0.01f
#define METER_TO_CM 100.0f
/** Utility function to calculate overlap of two spheres */
const float CalculateOverlap(const FSphere& ASphere, const float AFillingFactor, const FSphere& BSphere, const float BFillingFactor)
{
// if it doesn't intersect, return zero
if (!ASphere.Intersects(BSphere))
{
return 0.f;
}
if (ASphere.IsInside(BSphere))
{
return ASphere.GetVolume();
}
if (BSphere.IsInside(ASphere))
{
return BSphere.GetVolume();
}
if (ASphere.Equals(BSphere))
{
return ASphere.GetVolume();
}
float Distance = (ASphere.Center - BSphere.Center).Size();
check(!FMath::IsNearlyZero(Distance));
float ARadius = ASphere.W;
float BRadius = BSphere.W;
float ACapHeight = (BRadius * BRadius - (ARadius - Distance) * (ARadius - Distance)) / (2 * Distance);
float BCapHeight = (ARadius * ARadius - (BRadius - Distance) * (BRadius - Distance)) / (2 * Distance);
if (ACapHeight <= 0.f || BCapHeight <= 0.f)
{
// it's possible to get cap height to be less than 0
// since when we do check intersect, we do have regular tolerance
return 0.f;
}
float OverlapRadius1 = ((ARadius + BRadius) * (ARadius + BRadius) - Distance * Distance) * (Distance * Distance - (ARadius - BRadius) * (ARadius - BRadius));
float OverlapRadius2 = 2 * Distance;
float OverlapRadius = FMath::Sqrt(OverlapRadius1) / OverlapRadius2;
float OverlapRadiusSq = FMath::Square(OverlapRadius);
float ConstPI = PI / 6.0f;
float AVolume = ConstPI * (3 * OverlapRadiusSq + ACapHeight * ACapHeight) * ACapHeight;
float BVolume = ConstPI * (3 * OverlapRadiusSq + BCapHeight * BCapHeight) * BCapHeight;
float TotalVolume = AFillingFactor * AVolume + BFillingFactor * BVolume;
return TotalVolume;
}
/** Utility function that calculates filling factor */
const float CalculateFillingFactor(const FSphere& ASphere, const float AFillingFactor, const FSphere& BSphere, const float BFillingFactor)
{
const float OverlapVolume = CalculateOverlap(ASphere, AFillingFactor, BSphere, BFillingFactor);
FSphere UnionSphere = ASphere + BSphere;
// it shouldn't be zero or it should be checked outside
ensure(UnionSphere.W != 0.f);
// http://deim.urv.cat/~rivi/pub/3d/icra04b.pdf
// cost is calculated based on r^3 / filling factor
// since it subtract by AFillingFactor * 1/2 overlap volume + BfillingFactor * 1/2 overlap volume
return FMath::Max(0.0f, (AFillingFactor * ASphere.GetVolume() + BFillingFactor * BSphere.GetVolume() - OverlapVolume) / UnionSphere.GetVolume());
}
FLODCluster::FLODCluster(const FLODCluster& Other)
: Actors(Other.Actors), Bound(Other.Bound), FillingFactor(Other.FillingFactor), ClusterCost(Other.ClusterCost), bValid(Other.bValid)
{
}
FLODCluster::FLODCluster(AActor* Actor1)
: Bound(ForceInit), bValid(true)
{
AddActor(Actor1);
// calculate new filling factor
FillingFactor = 1.f;
ClusterCost = (Bound.W * Bound.W * Bound.W);
}
FLODCluster::FLODCluster(AActor* Actor1, AActor* Actor2)
: Bound(ForceInit), bValid(true)
{
FSphere Actor1Bound = AddActor(Actor1);
FSphere Actor2Bound = AddActor(Actor2);
// calculate new filling factor
FillingFactor = CalculateFillingFactor(Actor1Bound, 1.f, Actor2Bound, 1.f);
ClusterCost = (Bound.W * Bound.W * Bound.W) / FillingFactor;
}
FLODCluster::FLODCluster()
: Bound(ForceInit), bValid(false)
{
FillingFactor = 1.0f;
ClusterCost = (Bound.W * Bound.W * Bound.W);
}
FSphere FLODCluster::AddActor(AActor* NewActor)
{
bValid = true;
ensure(Actors.Contains(NewActor) == false);
Actors.Add(NewActor);
FVector Origin, Extent;
NewActor->GetActorBounds(false, Origin, Extent);
// scale 0.01 (change to meter from centimeter)
FSphere NewBound = FSphere(Origin * CM_TO_METER, Extent.Size() * CM_TO_METER);
Bound += NewBound;
return NewBound;
}
FLODCluster FLODCluster::operator+(const FLODCluster& Other) const
{
FLODCluster UnionCluster(*this);
UnionCluster.MergeClusters(Other);
return UnionCluster;
}
FLODCluster& FLODCluster::operator+=(const FLODCluster& Other)
{
MergeClusters(Other);
return *this;
}
FLODCluster FLODCluster::operator-(const FLODCluster& Other) const
{
FLODCluster Cluster(*this);
Cluster.SubtractCluster(Other);
return Cluster;
}
FLODCluster& FLODCluster::operator-=(const FLODCluster& Other)
{
SubtractCluster(Other);
return *this;
}
FLODCluster& FLODCluster::operator=(const FLODCluster& Other)
{
this->bValid = Other.bValid;
this->Actors = Other.Actors;
this->Bound = Other.Bound;
this->FillingFactor = Other.FillingFactor;
this->ClusterCost = Other.ClusterCost;
return *this;
}
bool FLODCluster::operator==(const FLODCluster& Other) const
{
return Actors == Other.Actors;
}
void FLODCluster::MergeClusters(const FLODCluster& Other)
{
// please note that when merge, we merge two boxes from each cluster, not exactly all actors' bound
// have to recalculate filling factor and bound based on cluster data
FillingFactor = CalculateFillingFactor(Bound, FillingFactor, Other.Bound, Other.FillingFactor);
Bound += Other.Bound;
ClusterCost = (Bound.W * Bound.W * Bound.W) / FillingFactor;
for (auto& Actor: Other.Actors)
{
Actors.AddUnique(Actor);
}
if (Actors.Num() > 0)
{
bValid = true;
}
}
void FLODCluster::SubtractCluster(const FLODCluster& Other)
{
for (int32 ActorId = 0; ActorId < Actors.Num(); ++ActorId)
{
if (Other.Actors.Contains(Actors[ActorId]))
{
Actors.RemoveAt(ActorId);
--ActorId;
}
}
TArray<AActor*> NewActors = Actors;
Actors.Empty();
// need to recalculate parameter
if (NewActors.Num() == 0)
{
Invalidate();
}
else if (NewActors.Num() == 1)
{
Bound = FSphere(ForceInitToZero);
AddActor(NewActors[0]);
FillingFactor = 1.f;
ClusterCost = (Bound.W * Bound.W * Bound.W) / FillingFactor;
}
else if (NewActors.Num() >= 2)
{
Bound = FSphere(ForceInit);
FSphere Actor1Bound = AddActor(NewActors[0]);
FSphere Actor2Bound = AddActor(NewActors[1]);
// calculate new filling factor
FillingFactor = CalculateFillingFactor(Actor1Bound, 1.f, Actor2Bound, 1.f);
// if more actors, we add them manually
for (int32 ActorId = 2; ActorId < NewActors.Num(); ++ActorId)
{
// if not contained, it shouldn't be
check(!Actors.Contains(NewActors[ActorId]));
FSphere NewBound = AddActor(NewActors[ActorId]);
FillingFactor = CalculateFillingFactor(NewBound, 1.f, Bound, FillingFactor);
Bound += NewBound;
}
ClusterCost = (Bound.W * Bound.W * Bound.W) / FillingFactor;
}
}
bool FLODCluster::Contains(FLODCluster& Other) const
{
if (IsValid() && Other.IsValid())
{
for (auto& Actor: Other.Actors)
{
if (Actors.Contains(Actor))
{
return true;
}
}
}
return false;
}
FString FLODCluster::ToString() const
{
FString ActorList;
for (auto& Actor: Actors)
{
ActorList += Actor->GetActorLabel();
ActorList += ", ";
}
return FString::Printf(TEXT("ActorNum(%d), Actor List (%s)"), Actors.Num(), *ActorList);
}
#undef LOCTEXT_NAMESPACE