// 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 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; ActorIdGetActorLabel(); ActorList += ", "; } return FString::Printf(TEXT("ActorNum(%d), Actor List (%s)"), Actors.Num(), *ActorList); } #undef LOCTEXT_NAMESPACE