Files
UnrealEngine/Engine/Plugins/Experimental/Landmass/Source/Editor/Private/LandmassManagerBase.cpp
2025-05-18 13:04:45 +08:00

585 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LandmassManagerBase.h"
#include "Modules/ModuleManager.h"
#include "LevelEditor.h"
#include "Landscape.h"
#include "LandscapeEditTypes.h"
#include "LandscapeModule.h"
#include "Kismet/KismetMathLibrary.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(LandmassManagerBase)
DEFINE_LOG_CATEGORY(LandmassManager);
ALandmassManagerBase::ALandmassManagerBase()
{
BrushTreeDepth = 3;
if (!HasAnyFlags(EObjectFlags::RF_ClassDefaultObject))
{
FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
OnActorSelectionChangedHandle = LevelEditor.OnActorSelectionChanged().AddUObject(this, &ALandmassManagerBase::HandleActorSelectionChanged);
}
// Explicitly set flag so this can be placed as a BP Brush from the landscape editor
SetCanAffectHeightmap(true);
}
void ALandmassManagerBase::SetTargetLandscape(ALandscape* InTargetLandscape)
{
#if WITH_EDITOR
if (OwningLandscape != InTargetLandscape)
{
if (OwningLandscape)
{
OwningLandscape->RemoveBrush(this);
}
if (!InTargetLandscape)
{
if (OwningLandscape != nullptr)
{
// This can occur if the RemoveBrush call above did not do anything because the manager
// was removed from the landscape in some other way (probably in landscape mode panel)
SetOwningLandscape(nullptr);
}
return;
}
if (!InTargetLandscape->CanHaveLayersContent())
{
UE_LOG(LandmassManager, Warning, TEXT("Landscape target for Landmass Brush manager did not have edit layers enabled. Unable to attach manager."));
SetOwningLandscape(nullptr);
}
}
#endif
}
//Copied from KismetMathlibrary because the function was note exported via ENGINE_API
int32 ALandmassManagerBase::Convert2DTo1D(const FIntPoint& Index2D, int32 XSize)
{
const bool bInvalidBounds =
Index2D.X < 0 ||
Index2D.X >= XSize ||
Index2D.Y < 0
;
if (bInvalidBounds)
{
return 0;
}
if (XSize <= 0)
{
return 0;
}
return Index2D.X + (Index2D.Y * XSize);
}
void ALandmassManagerBase::PopulateNodeTree()
{
BrushNodeData.Empty();
int32 NodeIndexOffset = 0;
int32 ParentIndexOffset = 0;
BrushTreeDepth = FMath::Clamp(BrushTreeDepth, 0, 4);
FVector2D WorldSize = { LandscapeInformation.LandscapeTransform.GetScale3D().X, LandscapeInformation.LandscapeTransform.GetScale3D().Y };
WorldSize *= LandscapeInformation.LandscapeQuads;
//For each level
for (int i = 0; i <= BrushTreeDepth; i++)
{
int32 SizeOfLevel = FMath::TruncToInt(FMath::Pow(2.0, i));
int32 NodesInLevel = SizeOfLevel * SizeOfLevel;
int32 SizeOfNextLevel = FMath::TruncToInt(FMath::Pow(2.0, i + 1));
NodeIndexOffset += NodesInLevel;
//Create empty nodes
for (int j = 0; j <= NodesInLevel - 1; j++)
{
FVector2D NodeWorldSize = WorldSize / SizeOfLevel;
FIntPoint NodeIndex2D = FIntPoint( j % SizeOfLevel, j / SizeOfLevel );
FVector LandscapePosition = LandscapeInformation.LandscapeTransform.GetLocation();
FVector2D NodeMinExtent = FVector2D(NodeIndex2D.X, NodeIndex2D.Y) * NodeWorldSize + FVector2D(LandscapePosition.X, LandscapePosition.Y);
FVector4 CurrentNodeExtents = FVector4(NodeMinExtent.X, NodeMinExtent.Y, NodeMinExtent.X + NodeWorldSize.X, NodeMinExtent.Y + NodeWorldSize.Y);
FBrushDataTree NewNode;
NewNode.CurrentLevel = i;
//Calculate the index of this node's parent by going up one level of subdivision
NewNode.ParentIndex = Convert2DTo1D((NodeIndex2D / FIntPoint(2, 2)), SizeOfLevel / 2) + ParentIndexOffset;
NewNode.Index_x0y0 = -1;
NewNode.Index_x1y0 = -1;
NewNode.Index_x0y1 = -1;
NewNode.Index_x1y1 = -1;
NewNode.ChildDataCount = 0;
NewNode.NodeExtents = CurrentNodeExtents;
if (i < BrushTreeDepth)
{
//Nodes contain the index of child nodes, calculate the index based on index from subdivision of level
NewNode.Index_x0y0 = Convert2DTo1D( (NodeIndex2D * FIntPoint(2,2)) + FIntPoint(0,0), SizeOfNextLevel) + NodeIndexOffset;
NewNode.Index_x1y0 = Convert2DTo1D( (NodeIndex2D * FIntPoint(2,2)) + FIntPoint(1,0), SizeOfNextLevel) + NodeIndexOffset;
NewNode.Index_x0y1 = Convert2DTo1D( (NodeIndex2D * FIntPoint(2,2)) + FIntPoint(0,1), SizeOfNextLevel) + NodeIndexOffset;
NewNode.Index_x1y1 = Convert2DTo1D( (NodeIndex2D * FIntPoint(2,2)) + FIntPoint(1,1), SizeOfNextLevel) + NodeIndexOffset;
}
BrushNodeData.Add(NewNode);
}
int32 NodesInPreviousLevel = FMath::TruncToInt(FMath::Pow(2.0, i - 1)) * FMath::TruncToInt(FMath::Pow(2.0, i - 1));
ParentIndexOffset += NodesInPreviousLevel;
}
}
TArray<ALandmassActor*> ALandmassManagerBase::GetActorsWithinModifiedNodes(TArray<int32>& InModifiedNodes)
{
TArray<ALandmassActor*> ActorsInNodes;
TArray<int32> NodesToCheck = InModifiedNodes;
TArray<int32> ParentsToCheck = InModifiedNodes;
TArray<int32> NodesToCheckNext;
TArray<int32> ParentsToCheckNext;
//Check each level of tree
for (int i = 0; i <= BrushTreeDepth; i++)
{
NodesToCheckNext.Empty();
//Check all nodes in the current level
for (int32 CurrentNodeIndex : NodesToCheck)
{
if (!BrushNodeData[CurrentNodeIndex].BrushActors.IsEmpty())
{
//Get any brushes from those nodes
for (ALandmassActor* CurrentActor : BrushNodeData[CurrentNodeIndex].BrushActors)
{
ActorsInNodes.AddUnique(CurrentActor);
}
}
//If any of the node's children have data, add them to list to check at the next level
if (i < BrushTreeDepth && BrushNodeData[CurrentNodeIndex].ChildDataCount > 0)
{
NodesToCheckNext.Add(BrushNodeData[CurrentNodeIndex].Index_x0y0);
NodesToCheckNext.Add(BrushNodeData[CurrentNodeIndex].Index_x1y0);
NodesToCheckNext.Add(BrushNodeData[CurrentNodeIndex].Index_x0y1);
NodesToCheckNext.Add(BrushNodeData[CurrentNodeIndex].Index_x1y1);
}
}
//Set the next level iteration to check those nodes that were found with data
NodesToCheck = NodesToCheckNext;
ParentsToCheckNext.Empty();
//Also check the node's parent for data, since it is possible the search began with leaf nodes
for (int32 CurrentNodeIndex : ParentsToCheck)
{
FBrushDataTree CheckNode = BrushNodeData[CurrentNodeIndex];
int32 CurrentParentIndex = CheckNode.ParentIndex;
if (CurrentNodeIndex > 0 && CheckNode.CurrentLevel > 0)
{
if (!BrushNodeData[CurrentParentIndex].BrushActors.IsEmpty())
{
//Get all brushes from the parent node
for (ALandmassActor* CurrentActor : BrushNodeData[CurrentParentIndex].BrushActors)
{
ActorsInNodes.AddUnique(CurrentActor);
}
}
ParentsToCheckNext.AddUnique(CurrentParentIndex);
}
}
//Set the next level's iteration to check the parents of checked nodes
ParentsToCheck = ParentsToCheckNext;
}
return ActorsInNodes;
}
//Go through nodes and update nodes to include the number of brushes in all their children under ChildDataCount
void ALandmassManagerBase::UpdateChildDataCounts()
{
int32 CurrentActorCount;
for (FBrushDataTree& CurrentNode : BrushNodeData)
{
CurrentNode.ChildDataCount = 0;
CurrentActorCount = CurrentNode.BrushActors.Num();
if (CurrentActorCount > 0 && CurrentNode.CurrentLevel > 0)
{
int32 CurrentLevel = CurrentNode.CurrentLevel;
int32 ParentToUpdate = CurrentNode.ParentIndex;
for (int i = 0; i <= CurrentLevel - 1; i++)
{
FBrushDataTree& CurrentParentData = BrushNodeData[ParentToUpdate];
BrushNodeData[ParentToUpdate].ChildDataCount += CurrentActorCount;
ParentToUpdate = CurrentParentData.ParentIndex;
}
}
}
}
//When a list of nodes includes all the child nodes of a parent, replace the children with the parent
void ALandmassManagerBase::ConsolidateNodes(TArray<int32>& NodesToConsolidate)
{
TArray<int32> FullyOccupiedParentNodes;
//Check each tree level
for (int i = 0; i <= BrushTreeDepth - 1; i++)
{
FullyOccupiedParentNodes.Empty();
for (int32 CurrentCheckIndex : NodesToConsolidate)
{
//Check if Node list contains all the children of a given node
int32 CurrentParentIndex = BrushNodeData[CurrentCheckIndex].ParentIndex;
FBrushDataTree CurrentParentData = BrushNodeData[CurrentParentIndex];
int32 Child_x0y0 = NodesToConsolidate.Find(CurrentParentData.Index_x0y0);
int32 Child_x1y0 = NodesToConsolidate.Find(CurrentParentData.Index_x1y0);
int32 Child_x0y1 = NodesToConsolidate.Find(CurrentParentData.Index_x0y1);
int32 Child_x1y1 = NodesToConsolidate.Find(CurrentParentData.Index_x1y1);
//If it does, mark it as a fully occupied parent node
if (Child_x0y0 >= 0 && Child_x1y0 >= 0 && Child_x0y1 >= 0 && Child_x1y1 >= 0)
{
FullyOccupiedParentNodes.AddUnique(CurrentParentIndex);
}
}
//Since we added the parent node to the list and all its children were also occupied, remove the children
for (int32 FullyOccupiedNode : FullyOccupiedParentNodes)
{
FBrushDataTree CurrentNodeData = BrushNodeData[FullyOccupiedNode];
NodesToConsolidate.Remove(CurrentNodeData.Index_x0y0);
NodesToConsolidate.Remove(CurrentNodeData.Index_x1y0);
NodesToConsolidate.Remove(CurrentNodeData.Index_x0y1);
NodesToConsolidate.Remove(CurrentNodeData.Index_x1y1);
NodesToConsolidate.Insert(FullyOccupiedNode, 0);
}
}
}
TArray<int32> ALandmassManagerBase::GetNodesWithinExtents(FVector4& InExtents)
{
TArray<int32> OverlappedNodes = { 0 };
TArray<int32> NodesToCheckNext;
FVector2D CheckExtentMin = { InExtents.X, InExtents.Y };
FVector2D CheckExtentMax = { InExtents.Z, InExtents.W };
//Check each level of tree
for (int i = 0; i <= BrushTreeDepth - 1; i++)
{
NodesToCheckNext.Empty();
//Check which nodes the given extents overlap, down to the smallest level
for (int32 CurrentCheckIndex : OverlappedNodes)
{
FVector4 CurrentNodeExtents = BrushNodeData[CurrentCheckIndex].NodeExtents;
FVector2D CurrentNodeCenter = (FVector2D(CurrentNodeExtents.X, CurrentNodeExtents.Y) + FVector2D(CurrentNodeExtents.Z, CurrentNodeExtents.W)) / 2;
if ((CheckExtentMin.X < CurrentNodeCenter.X) && (CheckExtentMin.Y < CurrentNodeCenter.Y))
{
NodesToCheckNext.Add(BrushNodeData[CurrentCheckIndex].Index_x0y0);
}
if ((CheckExtentMax.X > CurrentNodeCenter.X) && (CheckExtentMin.Y < CurrentNodeCenter.Y))
{
NodesToCheckNext.Add(BrushNodeData[CurrentCheckIndex].Index_x1y0);
}
if ((CheckExtentMin.X < CurrentNodeCenter.X) && (CheckExtentMax.Y > CurrentNodeCenter.Y))
{
NodesToCheckNext.Add(BrushNodeData[CurrentCheckIndex].Index_x0y1);
}
if ((CheckExtentMax.X > CurrentNodeCenter.X) && (CheckExtentMax.Y > CurrentNodeCenter.Y))
{
NodesToCheckNext.Add(BrushNodeData[CurrentCheckIndex].Index_x1y1);
}
}
OverlappedNodes = NodesToCheckNext;
}
//Consolidate the nodes back up to the lowest level possible. ie, if all nodes are returned, just use the root node
ConsolidateNodes(OverlappedNodes);
return OverlappedNodes;
}
//Go through and remove the Brush from the entire tree
TArray<int32> ALandmassManagerBase::RemoveBrushFromTree(ALandmassActor* BrushToRemove)
{
TArray<int32> RemovedIndices;
TArray<int32> NodesToCheck = {0};
TArray<int32> NodesToCheckNext;
for (int i = 0; i <= BrushTreeDepth; i++)
{
NodesToCheckNext.Empty();
for (int32 CurrentCheckIndex : NodesToCheck)
{
if (!BrushNodeData[CurrentCheckIndex].BrushActors.IsEmpty())
{
int32 RemovalIndex = BrushNodeData[CurrentCheckIndex].BrushActors.Find(BrushToRemove);
if (RemovalIndex >= 0)
{
BrushNodeData[CurrentCheckIndex].BrushActors.Remove(BrushToRemove);
RemovedIndices.Add(CurrentCheckIndex);
}
//Remove all null entries while at it
BrushNodeData[CurrentCheckIndex].BrushActors.Remove(nullptr);
}
if (BrushNodeData[CurrentCheckIndex].ChildDataCount > 0)
{
NodesToCheckNext.Add(BrushNodeData[CurrentCheckIndex].Index_x0y0);
NodesToCheckNext.Add(BrushNodeData[CurrentCheckIndex].Index_x1y0);
NodesToCheckNext.Add(BrushNodeData[CurrentCheckIndex].Index_x0y1);
NodesToCheckNext.Add(BrushNodeData[CurrentCheckIndex].Index_x1y1);
}
}
NodesToCheck = NodesToCheckNext;
}
return RemovedIndices;
}
//Sort one array to match another. Used to ensure the list of modified brushes pulled from the tree matches the order from the complete brush list to maintain draw order
TArray<ALandmassActor*> ALandmassManagerBase::SortBrushes(TArray<ALandmassActor*> BrushArrayToMatch, TArray<ALandmassActor*> ActorsToSort)
{
TMap<ALandmassActor*, int32> MapToSort;
for (ALandmassActor* CurrentBrush : ActorsToSort)
{
int32 IndexInSourceArray = BrushArrayToMatch.Find(CurrentBrush);
if(IndexInSourceArray >= 0)
{
MapToSort.Add(CurrentBrush, IndexInSourceArray);
}
}
//Algo::SortBy(SortedKeys, [&SortedKeys](const AActor* A) { return SortedKeys[A]; }, TGreater<>{});
MapToSort.ValueSort([](int32 A, int32 B) { return A < B; });
TArray<ALandmassActor*> SortedActors;
MapToSort.GenerateKeyArray(SortedActors);
return SortedActors;
}
void ALandmassManagerBase::AddBrushToTree(ALandmassActor* BrushToAdd, FVector4 InExtents, bool InMapToWholeLandscape, FVector4& ModifiedExtents, TArray<ALandmassActor*>& InvalidatedBrushes, TArray<int32>& ModifiedNodes)
{
if (BrushNodeData.IsEmpty())
{
UE_LOG(LandmassManager, Warning, TEXT("Landmass Manager Brush Tree was Empty but a Brush attempted to add itself to the tree."));
return;
}
TArray<int32> RemovedFromNodes = RemoveBrushFromTree(BrushToAdd);
ModifiedNodes.Empty();
if (!InMapToWholeLandscape)
{
//Get the Nodes within the modified Brush Extents
ModifiedNodes = GetNodesWithinExtents(InExtents);
}
else
{
//If Brush affects Whole Landscape, modify the Root Node
ModifiedNodes = { 0 };
}
for (int32 ModifiedNode : ModifiedNodes)
{
BrushNodeData[ModifiedNode].BrushActors.AddUnique(BrushToAdd);
}
//Update the child data counts each node contains for its children
UpdateChildDataCounts();
//Invalidate the currently modified Nodes and Nodes Brush was previously in
for (int32 RemovedNodeIndex : RemovedFromNodes)
{
ModifiedNodes.AddUnique(RemovedNodeIndex);
}
ModifiedExtents = FVector4(TNumericLimits<double>::Max(), TNumericLimits<double>::Max(), TNumericLimits<double>::Min(), TNumericLimits<double>::Min());
for (int32 ModifiedNodeIndex : ModifiedNodes)
{
FVector4 CurrentExtents = BrushNodeData[ModifiedNodeIndex].NodeExtents;
//Min of Extent Mins
ModifiedExtents.X = FMath::Min(CurrentExtents.X, ModifiedExtents.X);
ModifiedExtents.Y = FMath::Min(CurrentExtents.Y, ModifiedExtents.Y);
//Max of Extents Maxs
ModifiedExtents.Z = FMath::Max(CurrentExtents.Z, ModifiedExtents.Z);
ModifiedExtents.W = FMath::Max(CurrentExtents.W, ModifiedExtents.W);
}
//Now re-gather ModfiedNodes using the combined Modified Extents including nodes Actor removed from
ModifiedNodes = GetNodesWithinExtents(ModifiedExtents);
InvalidatedBrushes = GetActorsWithinModifiedNodes(ModifiedNodes);
}
void ALandmassManagerBase::AddBrushToArray(ALandmassActor* BrushToAdd)
{
LandmassBrushes.AddUnique(BrushToAdd);
int32 LastBrushIndex = LandmassBrushes.Num() - 1;
for (int i = 0; i <= LastBrushIndex; i++)
{
//Remove null blueprints caused by deleting brushes
if (LandmassBrushes[LastBrushIndex - i] == nullptr)
{
LandmassBrushes.RemoveAt(LastBrushIndex - i);
}
//Remove REINST blueprints caused by recompiling blueprint
else
{
FString BrushName = LandmassBrushes[LastBrushIndex-i]->GetName();
if (BrushName.StartsWith("REINST"))
{
LandmassBrushes.RemoveAt(LastBrushIndex - i);
}
}
}
}
void ALandmassManagerBase::RequestUpdateFromBrush_Implementation(ALandmassActor* BrushRequestingUpdate)
{
}
void ALandmassManagerBase::DrawBrushMaterial_Implementation(ALandmassActor* Brush, UMaterialInterface* BrushMaterial)
{
}
void ALandmassManagerBase::LaunchLandmassEditor_Implementation(ALandmassActor* BrushRequestingEditor)
{
}
void ALandmassManagerBase::TogglePreviewMode_Implementation(bool bEnablePreviewMode)
{
}
void ALandmassManagerBase::SetCaptureBoundaryNormals(bool)
{
}
void ALandmassManagerBase::MoveBrushUp(ALandmassActor* BrushToMove)
{
int32 BrushIndex = LandmassBrushes.Find(BrushToMove);
if (BrushIndex >= 0 && BrushIndex < (LandmassBrushes.Num() - 1))
{
LandmassBrushes.Swap(BrushIndex, BrushIndex + 1);
}
RequestUpdateFromBrush(BrushToMove);
}
void ALandmassManagerBase::MoveBrushDown(ALandmassActor* BrushToMove)
{
int32 BrushIndex = LandmassBrushes.Find(BrushToMove);
if (BrushIndex > 0)
{
LandmassBrushes.Swap(BrushIndex, BrushIndex - 1);
}
RequestUpdateFromBrush(BrushToMove);
}
void ALandmassManagerBase::MoveBrushToTop(ALandmassActor* BrushToMove)
{
int32 BrushIndex = LandmassBrushes.Find(BrushToMove);
if (BrushIndex >= 0 && BrushIndex < (LandmassBrushes.Num() - 1))
{
LandmassBrushes.Remove(BrushToMove);
LandmassBrushes.Add(BrushToMove);
}
RequestUpdateFromBrush(BrushToMove);
}
void ALandmassManagerBase::MoveBrushToBottom(ALandmassActor* BrushToMove)
{
int32 BrushIndex = LandmassBrushes.Find(BrushToMove);
if (BrushIndex > 0)
{
LandmassBrushes.Remove(BrushToMove);
LandmassBrushes.Insert(BrushToMove, 0);
}
RequestUpdateFromBrush(BrushToMove);
}
ALandscape* ALandmassManagerBase::GetLandscape()
{
return DetailPanelLandscape.Get();
}
void ALandmassManagerBase::SetOwningLandscape(ALandscape* InOwningLandscape)
{
Super::SetOwningLandscape(InOwningLandscape);
DetailPanelLandscape = OwningLandscape;
}
// We override PostEditChange to allow the users to change the owning landscape via a property displayed in the detail panel.
void ALandmassManagerBase::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
// Do a bunch of checks to make sure that we don't try to do anything when the editing is happening inside the blueprint editor.
UWorld* World = GetWorld();
if (IsTemplate() || !IsValidChecked(this) || !IsValid(World) || World->WorldType != EWorldType::Editor)
{
return;
}
if (PropertyChangedEvent.Property
&& (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(ALandmassManagerBase, DetailPanelLandscape)))
{
SetTargetLandscape(DetailPanelLandscape.Get());
}
}
void ALandmassManagerBase::HandleActorSelectionChanged(const TArray<UObject*>& NewSelection, bool bForceRefresh)
{
if (!IsTemplate())
{
bool bUpdateActor = false;
if (bWasSelected && !NewSelection.Contains(this))
{
bWasSelected = false;
bUpdateActor = true;
}
if (!bWasSelected && NewSelection.Contains(this))
{
bWasSelected = true;
bUpdateActor = true;
}
if (bUpdateActor)
{
ActorSelectionChanged(bWasSelected);
}
}
}
void ALandmassManagerBase::ActorSelectionChanged_Implementation(bool bSelected)
{
}