void FHierarchicalLODUtilities::DestroyCluster(ALODActor* InActor) { // Find if it has a parent ALODActor AActor* Actor = CastChecked<AActor>(InActor); UWorld* World = Actor->GetWorld(); ALODActor* ParentLOD = GetParentLODActor(InActor); const FScopedTransaction Transaction(LOCTEXT("UndoAction_DeleteCluster", "Deleting a (invalid) Cluster")); Actor->Modify(); World->Modify(); if (ParentLOD != nullptr) { ParentLOD->Modify(); ParentLOD->RemoveSubActor(Actor); } // Clean out sub actors and update their LODParent while (InActor->SubActors.Num()) { AActor* SubActor = InActor->SubActors[0]; SubActor->Modify(); InActor->RemoveSubActor(SubActor); } // Also destroy the cluster's data DestroyClusterData(InActor); World->DestroyActor(InActor); if (ParentLOD != nullptr && !ParentLOD->HasValidSubActors()) { DestroyCluster(ParentLOD); } }
FSlateColor HLODOutliner::FLODActorItem::GetTint() const { ALODActor* LODActorPtr = LODActor.Get(); if (LODActorPtr) { return LODActorPtr->IsDirty() ? FSlateColor::UseSubduedForeground() : FLinearColor(1.0f, 1.0f, 1.0f); } return FLinearColor(1.0f, 1.0f, 1.0f); }
void HLODOutliner::FLODActorDropTarget::MoveToCluster(AActor* InActor, ALODActor* NewParentActor) { const FScopedTransaction Transaction(LOCTEXT("UndoAction_MoveActorBetweenClusters", "Move Actor between Clusters")); InActor->Modify(); ALODActor* CurrentParentActor = HierarchicalLODUtils::GetParentLODActor(InActor); if (CurrentParentActor) { CurrentParentActor->Modify(); CurrentParentActor->RemoveSubActor(InActor); } NewParentActor->Modify(); NewParentActor->AddSubActor(InActor); GEngine->BroadcastHLODActorMoved(InActor, NewParentActor); }
void FHierarchicalLODUtilities::DestroyLODActor(ALODActor* InActor) { const FScopedTransaction Transaction(LOCTEXT("UndoAction_DeleteLODActor", "Delete LOD Actor")); UWorld* World = InActor->GetWorld(); World->Modify(); InActor->Modify(); ALODActor* ParentActor = FHierarchicalLODUtilities::GetParentLODActor(InActor); FHierarchicalLODUtilities::DestroyCluster(InActor); World->DestroyActor(InActor); if (ParentActor && !ParentActor->HasValidSubActors()) { ParentActor->Modify(); FHierarchicalLODUtilities::DestroyLODActor(ParentActor); } }
ALODActor* FHierarchicalLODUtilities::CreateNewClusterActor(UWorld* InWorld, const int32 InLODLevel, AWorldSettings* WorldSettings) { // Check incoming data check(InWorld != nullptr && WorldSettings != nullptr && InLODLevel >= 0); if (!WorldSettings->bEnableHierarchicalLODSystem || WorldSettings->HierarchicalLODSetup.Num() == 0 || WorldSettings->HierarchicalLODSetup.Num() < InLODLevel) { return nullptr; } // Spawn and setup actor ALODActor* NewActor = nullptr; NewActor = InWorld->SpawnActor<ALODActor>(ALODActor::StaticClass(), FTransform()); NewActor->LODLevel = InLODLevel + 1; NewActor->LODDrawDistance = 0.0f; NewActor->SetStaticMesh(nullptr); return NewActor; }
const bool FHierarchicalLODUtilities::RemoveActorFromCluster(AActor* InActor) { checkf(InActor != nullptr, TEXT("Invalid InActor")); bool bSucces = false; ALODActor* ParentActor = FHierarchicalLODUtilities::GetParentLODActor(InActor); if (ParentActor != nullptr) { const FScopedTransaction Transaction(LOCTEXT("UndoAction_RemoveActorFromCluster", "Remove Actor From Cluster")); ParentActor->Modify(); InActor->Modify(); bSucces = ParentActor->RemoveSubActor(InActor); if (!ParentActor->HasValidSubActors()) { DestroyCluster(ParentActor); } } return bSucces; }
ALODActor* FHierarchicalLODUtilities::CreateNewClusterFromActors(UWorld* InWorld, AWorldSettings* WorldSettings, const TArray<AActor*>& InActors, const int32 InLODLevel /*= 0*/) { checkf(InWorld != nullptr, TEXT("Invalid world")); checkf(InActors.Num() > 0, TEXT("Zero number of sub actors")); checkf(WorldSettings != nullptr, TEXT("Invalid world settings")); checkf(WorldSettings->bEnableHierarchicalLODSystem, TEXT("Hierarchical LOD system is disabled")); const FScopedTransaction Transaction(LOCTEXT("UndoAction_CreateNewCluster", "Create new Cluster")); InWorld->Modify(); // Create the cluster ALODActor* NewCluster = FHierarchicalLODUtilities::CreateNewClusterActor(InWorld, InLODLevel, WorldSettings); checkf(NewCluster != nullptr, TEXT("Failed to create a new cluster")); // Add InActors to new cluster for (AActor* Actor : InActors) { checkf(Actor != nullptr, TEXT("Invalid actor in InActors")); // Check if Actor is currently part of a different cluster ALODActor* ParentActor = FHierarchicalLODUtilities::GetParentLODActor(Actor); if (ParentActor != nullptr) { // If so remove it first ParentActor->Modify(); ParentActor->RemoveSubActor(Actor); // If the parent cluster is now empty (invalid) destroy it if (!ParentActor->HasValidSubActors()) { FHierarchicalLODUtilities::DestroyCluster(ParentActor); } } // Add actor to new cluster NewCluster->AddSubActor(Actor); } // Update sub actor LOD parents to populate NewCluster->UpdateSubActorLODParents(); return NewCluster; }
HLODOutliner::FDragValidationInfo HLODOutliner::FLODActorDropTarget::ValidateDrop(FDragDropPayload& DraggedObjects) const { if (DraggedObjects.StaticMeshActors.IsSet() && DraggedObjects.StaticMeshActors->Num() > 0) { if (DraggedObjects.StaticMeshActors->Num() > 0 && DraggedObjects.LODActors->Num() == 0) { if (DraggedObjects.bSceneOutliner == false) { bool bContaining = false; // Check if this StaticMesh Actor is not already inside this cluster for (int32 ActorIndex = 0; ActorIndex < DraggedObjects.StaticMeshActors->Num(); ++ActorIndex) { if (LODActor->SubActors.Contains(DraggedObjects.StaticMeshActors.GetValue()[ActorIndex])) { bContaining = true; break; } } if (!bContaining) { if (DraggedObjects.StaticMeshActors->Num() > 1) { return FDragValidationInfo(EHierarchicalLODActionType::MoveActorToCluster, FHLODOutlinerDragDropOp::ToolTip_MultipleSelection_Compatible, LOCTEXT("MoveMultipleToCluster", "Move Actors to Cluster")); } else { return FDragValidationInfo(EHierarchicalLODActionType::MoveActorToCluster, FHLODOutlinerDragDropOp::ToolTip_Compatible, LOCTEXT("MoveToCluster", "Move Actor to Cluster")); } } else { return FDragValidationInfo(EHierarchicalLODActionType::InvalidAction, FHLODOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("AlreadyInCluster", "Cannot Add to Existing cluster")); } } else { TArray<AActor*> Actors; for (auto StaticMeshActor : DraggedObjects.StaticMeshActors.GetValue()) { Actors.Add(StaticMeshActor.Get()); } Actors.Add(LODActor.Get()); const bool MultipleActors = DraggedObjects.StaticMeshActors->Num() > 1; if (FHierarchicalLODUtilities::AreActorsInSamePersistingLevel(Actors)) { return FDragValidationInfo(EHierarchicalLODActionType::AddActorToCluster, MultipleActors ? FHLODOutlinerDragDropOp::ToolTip_MultipleSelection_Compatible : FHLODOutlinerDragDropOp::ToolTip_Compatible, MultipleActors ? LOCTEXT("AddMultipleToCluster", "Add Actors to Cluster") : LOCTEXT("AddToCluster", "Add Actor to Cluster")); } else { return FDragValidationInfo(EHierarchicalLODActionType::InvalidAction, MultipleActors ? FHLODOutlinerDragDropOp::ToolTip_MultipleSelection_Incompatible : FHLODOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("NotInSameLODLevel", "Actors are not all in the same persisting level")); } } } if (DraggedObjects.bSceneOutliner) { return FDragValidationInfo(EHierarchicalLODActionType::InvalidAction, FHLODOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("AlreadyInHLOD", "Actor is already in one of the Hierarchical LOD clusters")); } } else if (DraggedObjects.LODActors.IsSet() && DraggedObjects.LODActors->Num() > 0) { // Only valid if dragging inside the HLOD outliner if (!DraggedObjects.bSceneOutliner) { bool bValidForMerge = true; bool bValidForChilding = true; int32 FirstLODLevel = -1; UObject* LevelOuter = nullptr; UObject* SubActorOuter = (LODActor->SubActors.Num()) ? LODActor->SubActors[0]->GetOuter() : nullptr; for (auto Actor : DraggedObjects.LODActors.GetValue()) { ALODActor* InLODActor = Cast<ALODActor>(Actor.Get()); if (InLODActor) { // If dragged onto self or already containing LODActor early out if (InLODActor == LODActor.Get() || LODActor->SubActors.Contains(InLODActor)) { bValidForMerge = false; bValidForChilding = false; break; } // Check in case of multiple selected LODActor items to make sure all of them come from the same LOD level if (FirstLODLevel == -1) { FirstLODLevel = InLODActor->LODLevel; } if (InLODActor->LODLevel != LODActor->LODLevel) { bValidForMerge = false; if (InLODActor->LODLevel != FirstLODLevel) { bValidForChilding = false; } } // Check if in same level asset if (LevelOuter == nullptr) { LevelOuter = InLODActor->GetOuter(); } else if (LevelOuter != InLODActor->GetOuter()) { bValidForMerge = false; bValidForChilding = false; } if (InLODActor->SubActors.Num() && SubActorOuter != InLODActor->SubActors[0]->GetOuter()) { bValidForChilding = false; bValidForMerge = false; } } } if (bValidForMerge) { return FDragValidationInfo(EHierarchicalLODActionType::MergeClusters, (DraggedObjects.LODActors->Num() == 1) ? FHLODOutlinerDragDropOp::ToolTip_Compatible : FHLODOutlinerDragDropOp::ToolTip_MultipleSelection_Compatible, LOCTEXT("MergeHLODClusters", "Merge Cluster(s)")); } else if (bValidForChilding) { return FDragValidationInfo(EHierarchicalLODActionType::ChildCluster, (DraggedObjects.LODActors->Num() == 1) ? FHLODOutlinerDragDropOp::ToolTip_Compatible : FHLODOutlinerDragDropOp::ToolTip_MultipleSelection_Compatible, LOCTEXT("ChildHLODClusters", "Add Child Cluster(s)")); } else { return FDragValidationInfo(EHierarchicalLODActionType::InvalidAction, FHLODOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("InvalidOperation", "Invalid Operation")); } } } return FDragValidationInfo(EHierarchicalLODActionType::InvalidAction, FHLODOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("NotImplemented", "Not implemented")); }
void FLODCluster::BuildActor(ULevel* InLevel, const int32 LODIdx, const bool bCreateMeshes) { FColor Colours[8] = { FColor::Cyan, FColor::Red, FColor::Green, FColor::Blue, FColor::Yellow, FColor::Magenta, FColor::White, FColor::Black }; // do big size if (InLevel && InLevel->GetWorld()) { // create asset using Actors const FHierarchicalSimplification& LODSetup = InLevel->GetWorld()->GetWorldSettings()->HierarchicalLODSetup[LODIdx]; // Retrieve draw distance for current and next LOD level const float DrawDistance = LODSetup.DrawDistance; const int32 LODCount = InLevel->GetWorld()->GetWorldSettings()->HierarchicalLODSetup.Num(); const float NextDrawDistance = (LODIdx < (LODCount - 1)) ? InLevel->GetWorld()->GetWorldSettings()->HierarchicalLODSetup[LODIdx + 1].DrawDistance : 0.0f; // Where generated assets will be stored UPackage* AssetsOuter = InLevel->GetOutermost(); // this asset is going to save with map, this means, I'll have to delete with it if (AssetsOuter) { TArray<UStaticMeshComponent*> AllComponents; for (auto& Actor: Actors) { TArray<UStaticMeshComponent*> Components; if (Actor->IsA<ALODActor>()) { HierarchicalLODUtils::ExtractStaticMeshComponentsFromLODActor(Actor, Components); } else { Actor->GetComponents<UStaticMeshComponent>(Components); } // TODO: support instanced static meshes Components.RemoveAll([](UStaticMeshComponent* Val){ return Val->IsA(UInstancedStaticMeshComponent::StaticClass()); }); AllComponents.Append(Components); } // it shouldn't even have come here if it didn't have any staticmesh if (ensure(AllComponents.Num() > 0)) { // In case we don't have outer generated assets should have same path as LOD level const FString AssetsPath = AssetsOuter->GetName() + TEXT("/"); AActor* FirstActor = Actors[0]; TArray<UObject*> OutAssets; FVector OutProxyLocation = FVector::ZeroVector; UStaticMesh* MainMesh = nullptr; if (bCreateMeshes) { // Generate proxy mesh and proxy material assets IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<IMeshUtilities>("MeshUtilities"); // should give unique name, so use level + actor name const FString PackageName = FString::Printf(TEXT("LOD_%s"), *FirstActor->GetName()); if (MeshUtilities.GetMeshMergingInterface() && LODSetup.bSimplifyMesh) { MeshUtilities.CreateProxyMesh(Actors, LODSetup.ProxySetting, AssetsOuter, PackageName, OutAssets, OutProxyLocation); } else { MeshUtilities.MergeStaticMeshComponents(AllComponents, FirstActor->GetWorld(), LODSetup.MergeSetting, AssetsOuter, PackageName, LODIdx, OutAssets, OutProxyLocation, LODSetup.DrawDistance, true); } // we make it private, so it can't be used by outside of map since it's useless, and then remove standalone for (auto& AssetIter : OutAssets) { AssetIter->ClearFlags(RF_Public | RF_Standalone); } // set staticmesh for (auto& Asset : OutAssets) { UStaticMesh* StaticMesh = Cast<UStaticMesh>(Asset); if (StaticMesh) { MainMesh = StaticMesh; } } } if (MainMesh || !bCreateMeshes) { UWorld* LevelWorld = Cast<UWorld>(InLevel->GetOuter()); check (LevelWorld); FTransform Transform; Transform.SetLocation(OutProxyLocation); // create LODActors using the current Actors ALODActor* NewActor = nullptr; NewActor = LevelWorld->SpawnActor<ALODActor>(ALODActor::StaticClass(), Transform); NewActor->SubObjects = OutAssets; NewActor->LODLevel = LODIdx+1; NewActor->LODDrawDistance = DrawDistance; NewActor->SetStaticMesh( MainMesh ); // now set as parent for(auto& Actor : Actors) { NewActor->AddSubActor(Actor); } // Mark dirty according to whether or not this is a preview build NewActor->SetIsDirty(!bCreateMeshes); } } } } }