Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Private/LODCluster.cpp
2025-05-18 13:04:45 +08:00

295 lines
7.5 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 */
double CalculateOverlap(const FSphere& ASphere, const double AFillingFactor, const FSphere& BSphere, const double 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();
}
double Distance = (ASphere.Center - BSphere.Center).Size();
check(!FMath::IsNearlyZero(Distance));
double ARadius = ASphere.W;
double BRadius = BSphere.W;
double ACapHeight = (BRadius * BRadius - (ARadius - Distance) * (ARadius - Distance)) / (2 * Distance);
double 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;
}
double OverlapRadius1 = ((ARadius + BRadius) * (ARadius + BRadius) - Distance*Distance) * (Distance * Distance - (ARadius - BRadius) * (ARadius - BRadius));
double OverlapRadius2 = 2 * Distance;
double OverlapRadius = FMath::Sqrt(OverlapRadius1) / OverlapRadius2;
double OverlapRadiusSq = FMath::Square(OverlapRadius);
double ConstPI = UE_PI / 6.0f;
double AVolume = ConstPI * (3 * OverlapRadiusSq + ACapHeight * ACapHeight) * ACapHeight;
double BVolume = ConstPI * (3 * OverlapRadiusSq + BCapHeight * BCapHeight) * BCapHeight;
double TotalVolume = AFillingFactor * AVolume + BFillingFactor * BVolume;
return TotalVolume;
}
/** Utility function that calculates filling factor */
double CalculateFillingFactor(const FSphere& ASphere, const double AFillingFactor, const FSphere& BSphere, const double BFillingFactor)
{
const double 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(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;
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;
}
FLODCluster& FLODCluster::operator=(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.Num() == Other.Actors.Num() && Actors.Includes(Other.Actors);
}
double FLODCluster::GetMergedCost(const FLODCluster& Other) const
{
double MergedFillingFactor = CalculateFillingFactor(Bound, FillingFactor, Other.Bound, Other.FillingFactor);
FSphere MergedBound = Bound + Other.Bound;
double MergedClusterCost = (MergedBound.W * MergedBound.W * MergedBound.W) / MergedFillingFactor;
return MergedClusterCost;
}
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;
Actors.Append(Other.Actors);
if (Actors.Num() > 0)
{
bValid = true;
}
}
void FLODCluster::SubtractCluster(const FLODCluster& Other)
{
Actors = Actors.Difference(Other.Actors);
Invalidate();
// We need to recalculate parameters
if (Actors.Num() > 0)
{
bValid = true;
FillingFactor = 1.f;
Bound = FSphere(ForceInitToZero);
for (AActor* Actor : Actors)
{
FVector Origin, Extent;
Actor->GetActorBounds(false, Origin, Extent);
// scale 0.01 (change to meter from centimeter)
FSphere NewBound = FSphere(Origin * CM_TO_METER, Extent.Size() * CM_TO_METER);
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