295 lines
7.5 KiB
C++
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
|