Update directive utilities plugin

This commit is contained in:
2026-08-15 21:06:43 +02:00
parent 2fe7f83a38
commit a06fed1cba
103 changed files with 18211 additions and 1071 deletions

View File

@@ -3,17 +3,47 @@
#include "Subsystems/DirectiveUtilEditorActorSubsystem.h"
#include "DirectiveUtilLogChannels.h"
#include "Components/StaticMeshComponent.h"
#include "Editor.h"
#include "Engine/StaticMeshActor.h"
#include "Engine/StaticMesh.h"
#include "Engine/Texture.h"
#include "Engine/Texture2D.h"
#include "Materials/Material.h"
#include "Materials/MaterialInterface.h"
#include "Materials/MaterialExpressionTextureObject.h"
#include "EditorViewportClient.h"
#include "LevelEditorViewport.h"
#include "Components/BoxComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/SphereComponent.h"
#include "Materials/MaterialExpressionTextureSample.h"
#include "Runtime/Launch/Resources/Version.h"
#include "ScopedTransaction.h"
#include "Engine/World.h"
namespace
{
bool IsFiniteVector(const FVector& Value)
{
return FMath::IsFinite(Value.X) && FMath::IsFinite(Value.Y) && FMath::IsFinite(Value.Z);
}
bool IsFiniteQuat(const FQuat& Value)
{
return FMath::IsFinite(Value.X)
&& FMath::IsFinite(Value.Y)
&& FMath::IsFinite(Value.Z)
&& FMath::IsFinite(Value.W);
}
bool MatchesTextureReference(const UTexture* Texture, const TSoftObjectPtr<UTexture2D>& TextureReference)
{
return TextureReference.IsNull()
? Texture == nullptr
: Texture && FSoftObjectPath(Texture) == TextureReference.ToSoftObjectPath();
}
// Adds an actor to OutActors when whether it matches the predicate (evaluated once across all of the
// actor's components) equals whether matches are being included. Centralizes the include/exclude
// aggregation so a multi-component actor is judged per-actor rather than per-component.
@@ -39,7 +69,7 @@ namespace
for (TSourceActor* Actor : *Source)
{
if (!Actor || Seen.Contains(Actor)) { continue; }
if (!IsValid(Actor) || Seen.Contains(Actor)) { continue; }
if (ActorMatches(Actor) == bIncludeMatches)
{
Seen.Add(Actor);
@@ -47,27 +77,163 @@ namespace
}
}
}
struct FActorLayoutData
{
AActor* Actor = nullptr;
FBox Bounds = FBox(ForceInit);
FVector Location = FVector::ZeroVector;
int32 AttachmentDepth = 0;
};
float GetAxisValue(const FVector& Vector, const EDirectiveUtilActorLayoutAxis Axis)
{
switch (Axis)
{
case EDirectiveUtilActorLayoutAxis::X:
return Vector.X;
case EDirectiveUtilActorLayoutAxis::Y:
return Vector.Y;
case EDirectiveUtilActorLayoutAxis::Z:
return Vector.Z;
}
return 0.0f;
}
void SetAxisValue(FVector& Vector, const EDirectiveUtilActorLayoutAxis Axis, const float Value)
{
switch (Axis)
{
case EDirectiveUtilActorLayoutAxis::X:
Vector.X = Value;
break;
case EDirectiveUtilActorLayoutAxis::Y:
Vector.Y = Value;
break;
case EDirectiveUtilActorLayoutAxis::Z:
Vector.Z = Value;
break;
}
}
int32 GetAttachmentDepth(const AActor* Actor)
{
int32 Depth = 0;
TSet<const AActor*> Visited;
for (const AActor* Parent = Actor ? Actor->GetAttachParentActor() : nullptr;
Parent && !Visited.Contains(Parent);
Parent = Parent->GetAttachParentActor())
{
Visited.Add(Parent);
++Depth;
}
return Depth;
}
bool IsLayoutActorValid(AActor* Actor)
{
return IsValid(Actor)
&& Actor->GetWorld()
&& !Actor->HasAnyFlags(RF_Transient)
&& !Actor->IsActorBeingDestroyed();
}
TArray<FActorLayoutData> GetLayoutActors(
const TArray<AActor*>& Actors,
FDirectiveUtilActorOperationResult& Result)
{
TArray<FActorLayoutData> LayoutActors;
TSet<AActor*> Seen;
for (AActor* Actor : Actors)
{
if (!Actor || Seen.Contains(Actor))
{
continue;
}
Seen.Add(Actor);
if (!IsLayoutActorValid(Actor))
{
Result.SkippedActors.Add(Actor);
continue;
}
FActorLayoutData Data;
Data.Actor = Actor;
Data.Location = Actor->GetActorLocation();
if (!IsFiniteVector(Data.Location)
|| !IsFiniteVector(Actor->GetActorScale3D())
|| !IsFiniteQuat(Actor->GetActorQuat()))
{
Result.SkippedActors.Add(Actor);
continue;
}
Data.Bounds = Actor->GetComponentsBoundingBox(true, true);
if (!Data.Bounds.IsValid)
{
Data.Bounds = FBox(Data.Location, Data.Location);
}
if (!IsFiniteVector(Data.Bounds.Min) || !IsFiniteVector(Data.Bounds.Max))
{
Result.SkippedActors.Add(Actor);
continue;
}
Data.AttachmentDepth = GetAttachmentDepth(Actor);
LayoutActors.Add(MoveTemp(Data));
}
return LayoutActors;
}
void ApplyLocations(
TArray<FActorLayoutData>& LayoutActors,
const TMap<AActor*, FVector>& Locations,
FDirectiveUtilActorOperationResult& Result)
{
LayoutActors.Sort([](const FActorLayoutData& Left, const FActorLayoutData& Right) {
return Left.AttachmentDepth < Right.AttachmentDepth;
});
for (const FActorLayoutData& Data : LayoutActors)
{
const FVector* Location = Locations.Find(Data.Actor);
if (!Location || !IsFiniteVector(*Location) || Data.Actor->GetActorLocation().Equals(*Location))
{
continue;
}
Data.Actor->Modify();
if (Data.Actor->SetActorLocation(*Location, false, nullptr, ETeleportType::TeleportPhysics))
{
Result.ChangedActors.Add(Data.Actor);
}
else
{
Result.SkippedActors.Add(Data.Actor);
}
}
}
}
void UDirectiveUtilEditorActorSubsystem::FocusActorsInViewport(const TArray<AActor*> Actors, const bool bInstant)
{
if (Actors.Num() == 0) { return; }
if (!GEditor) { return; }
FViewport* ActiveViewport = GEditor->GetActiveViewport();
if (!ActiveViewport) { return; }
FEditorViewportClient* ViewportClient = static_cast<FEditorViewportClient*>(ActiveViewport->GetClient());
if (!ViewportClient) { return; }
if (!GCurrentLevelEditingViewportClient) { return; }
FBox BoundingBox = FBox(ForceInit);
for (const auto Actor : Actors)
for (const AActor* Actor : Actors)
{
if (!Actor) { continue; }
BoundingBox += Actor->GetComponentsBoundingBox(true, true);
if (!IsValid(Actor)) { continue; }
const FBox ActorBounds = Actor->GetComponentsBoundingBox(true, true);
if (ActorBounds.IsValid)
{
BoundingBox += ActorBounds;
}
}
ViewportClient->FocusViewportOnBox(BoundingBox, bInstant);
if (BoundingBox.IsValid)
{
GCurrentLevelEditingViewportClient->FocusViewportOnBox(BoundingBox, bInstant);
}
}
TArray<UClass*> UDirectiveUtilEditorActorSubsystem::GetAllLevelClasses()
@@ -84,6 +250,236 @@ TArray<UClass*> UDirectiveUtilEditorActorSubsystem::GetAllLevelClasses()
return ActorClasses;
}
FDirectiveUtilActorOperationResult UDirectiveUtilEditorActorSubsystem::AlignActors(
const TArray<AActor*>& Actors,
const EDirectiveUtilActorLayoutAxis Axis,
const EDirectiveUtilActorAlignment Alignment)
{
FDirectiveUtilActorOperationResult Result;
TArray<FActorLayoutData> LayoutActors = GetLayoutActors(Actors, Result);
if (LayoutActors.Num() < 2)
{
return Result;
}
FBox CombinedBounds(ForceInit);
for (const FActorLayoutData& Data : LayoutActors)
{
CombinedBounds += Data.Bounds;
}
float Target = 0.0f;
switch (Alignment)
{
case EDirectiveUtilActorAlignment::Minimum:
Target = GetAxisValue(CombinedBounds.Min, Axis);
break;
case EDirectiveUtilActorAlignment::Center:
Target = GetAxisValue(CombinedBounds.GetCenter(), Axis);
break;
case EDirectiveUtilActorAlignment::Maximum:
Target = GetAxisValue(CombinedBounds.Max, Axis);
break;
}
TMap<AActor*, FVector> Locations;
for (const FActorLayoutData& Data : LayoutActors)
{
const float Current = Alignment == EDirectiveUtilActorAlignment::Minimum
? GetAxisValue(Data.Bounds.Min, Axis)
: Alignment == EDirectiveUtilActorAlignment::Maximum
? GetAxisValue(Data.Bounds.Max, Axis)
: GetAxisValue(Data.Bounds.GetCenter(), Axis);
FVector Location = Data.Location;
SetAxisValue(Location, Axis, GetAxisValue(Location, Axis) + Target - Current);
Locations.Add(Data.Actor, Location);
}
const FScopedTransaction Transaction(NSLOCTEXT("DirectiveUtilities", "AlignActors", "Align Actors"));
ApplyLocations(LayoutActors, Locations, Result);
return Result;
}
FDirectiveUtilActorOperationResult UDirectiveUtilEditorActorSubsystem::DistributeActors(
const TArray<AActor*>& Actors,
const EDirectiveUtilActorLayoutAxis Axis,
const EDirectiveUtilActorDistribution Distribution)
{
FDirectiveUtilActorOperationResult Result;
TArray<FActorLayoutData> LayoutActors = GetLayoutActors(Actors, Result);
if (LayoutActors.Num() < 3)
{
return Result;
}
LayoutActors.Sort([Axis](const FActorLayoutData& Left, const FActorLayoutData& Right) {
return GetAxisValue(Left.Bounds.GetCenter(), Axis) < GetAxisValue(Right.Bounds.GetCenter(), Axis);
});
TMap<AActor*, FVector> Locations;
if (Distribution == EDirectiveUtilActorDistribution::Centers)
{
const float Start = GetAxisValue(LayoutActors[0].Bounds.GetCenter(), Axis);
const float End = GetAxisValue(LayoutActors.Last().Bounds.GetCenter(), Axis);
const float Spacing = (End - Start) / (LayoutActors.Num() - 1);
for (int32 Index = 1; Index < LayoutActors.Num() - 1; ++Index)
{
const FActorLayoutData& Data = LayoutActors[Index];
FVector Location = Data.Location;
const float Delta = Start + Spacing * Index - GetAxisValue(Data.Bounds.GetCenter(), Axis);
SetAxisValue(Location, Axis, GetAxisValue(Location, Axis) + Delta);
Locations.Add(Data.Actor, Location);
}
}
else
{
float TotalSize = 0.0f;
for (const FActorLayoutData& Data : LayoutActors)
{
TotalSize += GetAxisValue(Data.Bounds.GetSize(), Axis);
}
const float Start = GetAxisValue(LayoutActors[0].Bounds.Min, Axis);
const float End = GetAxisValue(LayoutActors.Last().Bounds.Max, Axis);
const float Gap = (End - Start - TotalSize) / (LayoutActors.Num() - 1);
float NextMinimum = GetAxisValue(LayoutActors[0].Bounds.Max, Axis) + Gap;
for (int32 Index = 1; Index < LayoutActors.Num() - 1; ++Index)
{
const FActorLayoutData& Data = LayoutActors[Index];
FVector Location = Data.Location;
const float Delta = NextMinimum - GetAxisValue(Data.Bounds.Min, Axis);
SetAxisValue(Location, Axis, GetAxisValue(Location, Axis) + Delta);
Locations.Add(Data.Actor, Location);
NextMinimum += GetAxisValue(Data.Bounds.GetSize(), Axis) + Gap;
}
}
const FScopedTransaction Transaction(NSLOCTEXT("DirectiveUtilities", "DistributeActors", "Distribute Actors"));
ApplyLocations(LayoutActors, Locations, Result);
return Result;
}
FDirectiveUtilActorOperationResult UDirectiveUtilEditorActorSubsystem::SnapActorsToSurface(
const TArray<AActor*>& Actors,
const FVector TraceDirection,
const float MaximumDistance,
const TEnumAsByte<ECollisionChannel> TraceChannel,
const EDirectiveUtilSurfacePlacement Placement,
const bool bAlignToNormal)
{
FDirectiveUtilActorOperationResult Result;
TArray<FActorLayoutData> LayoutActors = GetLayoutActors(Actors, Result);
if (LayoutActors.IsEmpty()
|| !FMath::IsFinite(MaximumDistance)
|| MaximumDistance <= 0.0f
|| !IsFiniteVector(TraceDirection)
|| TraceDirection.IsNearlyZero()
|| TraceChannel.GetValue() >= ECC_MAX)
{
for (const FActorLayoutData& Data : LayoutActors)
{
Result.SkippedActors.Add(Data.Actor);
}
return Result;
}
const FVector Direction = TraceDirection.GetSafeNormal();
FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(DirectiveUtilitiesSnapActors), true);
for (const FActorLayoutData& Data : LayoutActors)
{
QueryParams.AddIgnoredActor(Data.Actor);
}
struct FSnapTarget
{
FVector Location;
FQuat Rotation;
};
TMap<AActor*, FSnapTarget> Targets;
for (const FActorLayoutData& Data : LayoutActors)
{
FHitResult Hit;
const FVector Start = Data.Location;
if (!Data.Actor->GetWorld()->LineTraceSingleByChannel(
Hit,
Start,
Start + Direction * MaximumDistance,
TraceChannel,
QueryParams))
{
Result.SkippedActors.Add(Data.Actor);
continue;
}
if (!IsFiniteVector(Hit.Location)
|| (bAlignToNormal && (!IsFiniteVector(Hit.ImpactNormal) || Hit.ImpactNormal.IsNearlyZero())))
{
Result.SkippedActors.Add(Data.Actor);
continue;
}
FVector Location = Hit.Location;
if (Placement == EDirectiveUtilSurfacePlacement::Bounds)
{
const FVector Extent = Data.Bounds.GetExtent();
const float Support = FMath::Abs(Direction.X) * Extent.X
+ FMath::Abs(Direction.Y) * Extent.Y
+ FMath::Abs(Direction.Z) * Extent.Z;
Location = Hit.Location - Direction * Support - (Data.Bounds.GetCenter() - Data.Location);
}
FQuat Rotation = Data.Actor->GetActorQuat();
if (bAlignToNormal)
{
Rotation = FQuat::FindBetweenNormals(Rotation.GetUpVector(), Hit.ImpactNormal) * Rotation;
Rotation.Normalize();
}
if (IsFiniteVector(Location) && IsFiniteQuat(Rotation))
{
Targets.Add(Data.Actor, {Location, Rotation});
}
else
{
Result.SkippedActors.Add(Data.Actor);
}
}
LayoutActors.Sort([](const FActorLayoutData& Left, const FActorLayoutData& Right) {
return Left.AttachmentDepth < Right.AttachmentDepth;
});
const FScopedTransaction Transaction(NSLOCTEXT("DirectiveUtilities", "SnapActorsToSurface", "Snap Actors To Surface"));
for (const FActorLayoutData& Data : LayoutActors)
{
const FSnapTarget* Target = Targets.Find(Data.Actor);
if (!Target)
{
continue;
}
const bool bLocationChanged = !Data.Actor->GetActorLocation().Equals(Target->Location);
const bool bRotationChanged = !Data.Actor->GetActorQuat().Equals(Target->Rotation);
if (!bLocationChanged && !bRotationChanged)
{
continue;
}
Data.Actor->Modify();
const bool bMoved = Data.Actor->SetActorLocationAndRotation(
Target->Location,
Target->Rotation,
false,
nullptr,
ETeleportType::TeleportPhysics);
if (bMoved)
{
Result.ChangedActors.Add(Data.Actor);
}
else
{
Result.SkippedActors.Add(Data.Actor);
}
}
return Result;
}
void UDirectiveUtilEditorActorSubsystem::FilterStaticMeshActors(
TArray<AStaticMeshActor*>& OutStaticMeshActors,
TArray<AActor*> ActorsToFilter) const
@@ -735,11 +1131,11 @@ void UDirectiveUtilEditorActorSubsystem::FilterActorsByTexture(
{
if (const UMaterialExpressionTextureSample* TextureSample = Cast<UMaterialExpressionTextureSample>(Expression))
{
if (TextureSample->Texture == TextureReference) { return true; }
if (MatchesTextureReference(TextureSample->Texture, TextureReference)) { return true; }
}
if (const UMaterialExpressionTextureObject* TextureObject = Cast<UMaterialExpressionTextureObject>(Expression))
{
if (TextureObject->Texture == TextureReference) { return true; }
if (MatchesTextureReference(TextureObject->Texture, TextureReference)) { return true; }
}
}
}
@@ -757,11 +1153,11 @@ void UDirectiveUtilEditorActorSubsystem::FilterActorsByTexture(
{
if (const UMaterialExpressionTextureSample* TextureSample = Cast<UMaterialExpressionTextureSample>(Expression))
{
if (TextureSample->Texture == TextureReference) { return true; }
if (MatchesTextureReference(TextureSample->Texture, TextureReference)) { return true; }
}
if (const UMaterialExpressionTextureObject* TextureObject = Cast<UMaterialExpressionTextureObject>(Expression))
{
if (TextureObject->Texture == TextureReference) { return true; }
if (MatchesTextureReference(TextureObject->Texture, TextureReference)) { return true; }
}
}
}
@@ -901,36 +1297,7 @@ void UDirectiveUtilEditorActorSubsystem::GetActorsByMaterialSoftReference(
const EDirectiveUtilInclusivity Inclusivity)
{
const TArray<AActor*> SourceActors = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
TArray<AStaticMeshActor*> StaticMeshActors;
FilterStaticMeshActors(StaticMeshActors, SourceActors);
FilterActorsByPredicate(StaticMeshActors, FoundActors, Inclusivity, [&](AStaticMeshActor* StaticMeshActor) -> bool
{
const UStaticMeshComponent* StaticMeshComp = StaticMeshActor->GetStaticMeshComponent();
if (!StaticMeshComp) { return false; }
const UStaticMesh* Mesh = StaticMeshComp->GetStaticMesh();
if (!Mesh) { return false; }
if (MaterialSource == BaseAndOverride || MaterialSource == OverrideOnly)
{
for (int32 i = 0; i < StaticMeshComp->GetNumMaterials(); i++)
{
if (StaticMeshComp->GetMaterial(i) == Material) { return true; }
}
}
if (MaterialSource == BaseAndOverride || MaterialSource == BaseOnly)
{
for (int32 i = 0; i < Mesh->GetStaticMaterials().Num(); i++)
{
if (Mesh->GetMaterial(i) == Material) { return true; }
}
}
return false;
});
UE_LOG(LogDirectiveUtilEditor, Display, TEXT("%i actors with the material reference were found."),
FoundActors.Num());
FilterActorsByMaterial(SourceActors, FoundActors, Material, MaterialSource, Inclusivity);
}
void UDirectiveUtilEditorActorSubsystem::GetActorsByMaterialName(
@@ -941,38 +1308,7 @@ void UDirectiveUtilEditorActorSubsystem::GetActorsByMaterialName(
const EDirectiveUtilInclusivity Inclusivity)
{
const TArray<AActor*> SourceActors = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
TArray<AStaticMeshActor*> StaticMeshActors;
FilterStaticMeshActors(StaticMeshActors, SourceActors);
FilterActorsByPredicate(StaticMeshActors, FoundActors, Inclusivity, [&](AStaticMeshActor* StaticMeshActor) -> bool
{
const UStaticMeshComponent* StaticMeshComp = StaticMeshActor->GetStaticMeshComponent();
if (!StaticMeshComp) { return false; }
const UStaticMesh* Mesh = StaticMeshComp->GetStaticMesh();
if (!Mesh) { return false; }
if (MaterialSource == BaseAndOverride || MaterialSource == OverrideOnly)
{
for (int32 i = 0; i < StaticMeshComp->GetNumMaterials(); i++)
{
const UMaterialInterface* Mat = StaticMeshComp->GetMaterial(i);
if (Mat && Mat->GetName().Contains(MaterialName)) { return true; }
}
}
if (MaterialSource == BaseAndOverride || MaterialSource == BaseOnly)
{
for (int32 i = 0; i < Mesh->GetStaticMaterials().Num(); i++)
{
const UMaterialInterface* Mat = Mesh->GetMaterial(i);
if (Mat && Mat->GetName().Contains(MaterialName)) { return true; }
}
}
return false;
});
UE_LOG(LogDirectiveUtilEditor, Display, TEXT("%i actors with material %s were found."), FoundActors.Num(),
*MaterialName);
FilterActorsByMaterialName(SourceActors, FoundActors, MaterialName, MaterialSource, Inclusivity);
}
void UDirectiveUtilEditorActorSubsystem::GetActorsByVertexCount(
@@ -1288,11 +1624,11 @@ void UDirectiveUtilEditorActorSubsystem::GetActorsByTextureSoftReference(
{
if (const UMaterialExpressionTextureSample* TextureSample = Cast<UMaterialExpressionTextureSample>(Expression))
{
if (TextureSample->Texture == Texture) { return true; }
if (MatchesTextureReference(TextureSample->Texture, Texture)) { return true; }
}
if (const UMaterialExpressionTextureObject* TextureObject = Cast<UMaterialExpressionTextureObject>(Expression))
{
if (TextureObject->Texture == Texture) { return true; }
if (MatchesTextureReference(TextureObject->Texture, Texture)) { return true; }
}
}
}
@@ -1341,8 +1677,7 @@ void UDirectiveUtilEditorActorSubsystem::GetActorsByTextureName(
void UDirectiveUtilEditorActorSubsystem::GetInvalidActors(TArray<AActor*>& FoundActors)
{
for (AActor* Actor : GetAllLevelActors()) { if (!IsValid(Actor)) { FoundActors.AddUnique(Actor); } }
UE_LOG(LogDirectiveUtilEditor, Display, TEXT("%i invalid actors were found."), FoundActors.Num());
FoundActors.Reset();
}
void UDirectiveUtilEditorActorSubsystem::PushOverrideMaterialsToSource(UStaticMeshComponent* StaticMeshComponent)
@@ -1361,11 +1696,14 @@ void UDirectiveUtilEditorActorSubsystem::PushOverrideMaterialsToSource(UStaticMe
}
const FScopedTransaction Transaction(NSLOCTEXT("DirectiveUtilities", "PushOverrideMaterialsToSource", "Push Override Materials To Source"));
for (int32 i = 0; i < StaticMeshComponent->GetNumMaterials(); i++)
const TArray<TObjectPtr<UMaterialInterface>>& OverrideMaterials = StaticMeshComponent->OverrideMaterials;
const int32 MaterialSlotCount = StaticMesh->GetStaticMaterials().Num();
for (int32 MaterialIndex = 0; MaterialIndex < FMath::Min(OverrideMaterials.Num(), MaterialSlotCount); ++MaterialIndex)
{
if (UMaterialInterface* Material = StaticMeshComponent->GetMaterial(i))
UMaterialInterface* OverrideMaterial = OverrideMaterials[MaterialIndex];
if (OverrideMaterial && StaticMesh->GetMaterial(MaterialIndex) != OverrideMaterial)
{
StaticMesh->SetMaterial(i, Material);
StaticMesh->SetMaterial(MaterialIndex, OverrideMaterial);
}
}
UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Materials were pushed to source for %s."), *StaticMeshComponent->GetName());