Files
UnrealEngineUWP/Engine/Source/Editor/UnrealEd/Private/LODCluster.cpp
Marc Audy 4c1bb11c29 Merge UE5/Release-Engine-Staging to UE5/Main @ 14548662
This represents UE4/Main @ 14525125 + cherrypicked fixes
#skipundocheck

[CL 14551026 by Marc Audy in ue5-main branch]
2020-10-22 19:19:16 -04:00

290 lines
7.3 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