Files
UnrealEngine/Engine/Source/Editor/VirtualTexturingEditor/Private/RuntimeVirtualTextureSetBounds.cpp
2025-05-18 13:04:45 +08:00

148 lines
7.0 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "RuntimeVirtualTextureSetBounds.h"
#include "Components/PrimitiveComponent.h"
#include "Components/RuntimeVirtualTextureComponent.h"
#include "GameFramework/Actor.h"
#include "Landscape.h"
#include "LandscapeInfo.h"
#include "UObject/UObjectIterator.h"
#include "VT/RuntimeVirtualTexture.h"
namespace RuntimeVirtualTexture
{
void SetBounds(URuntimeVirtualTextureComponent* InComponent)
{
URuntimeVirtualTexture const* VirtualTexture = InComponent->GetVirtualTexture();
check(VirtualTexture != nullptr);
// Calculate bounds in our desired local space.
AActor* Owner = InComponent->GetOwner();
const FVector TargetPosition = Owner->ActorToWorld().GetTranslation();
// Local space will take rotation from a BoundsAlignActor if set.
TSoftObjectPtr<AActor>& BoundsAlignActor = InComponent->GetBoundsAlignActor();
const FQuat TargetRotation = BoundsAlignActor.IsValid() ? BoundsAlignActor->GetTransform().GetRotation() : Owner->ActorToWorld().GetRotation();
FTransform LocalTransform;
LocalTransform.SetComponents(TargetRotation, TargetPosition, FVector::OneVector);
FTransform WorldToLocal = LocalTransform.Inverse();
// Expand bounds for the BoundsAlignActor and all primitive components that write to this virtual texture.
FBox Bounds(ForceInit);
// Special case where if the bounds align actor is a landscape: we want to automatically include all associated landscape components,
// including those that are not currently loaded. Luckily there's a function for that:
if (BoundsAlignActor.IsValid())
{
if (ALandscape const* Landscape = Cast<ALandscape>(BoundsAlignActor.Get()))
{
FBox WorldBounds = Landscape->GetCompleteBounds();
Bounds = WorldBounds.TransformBy(WorldToLocal);
}
}
for (TObjectIterator<UPrimitiveComponent> It(RF_ClassDefaultObject, true, EInternalObjectFlags::Garbage); It; ++It)
{
bool bUseBounds = BoundsAlignActor.IsValid() && It->GetOwner() == BoundsAlignActor.Get();
TArray<URuntimeVirtualTexture*> const& VirtualTextures = It->GetRuntimeVirtualTextures();
for (int32 Index = 0; !bUseBounds && Index < VirtualTextures.Num(); ++Index)
{
if (VirtualTextures[Index] == InComponent->GetVirtualTexture())
{
bUseBounds = true;
}
}
if (bUseBounds)
{
FBoxSphereBounds LocalSpaceBounds = It->CalcBounds(It->GetComponentTransform() * WorldToLocal);
Bounds += LocalSpaceBounds.GetBox();
}
}
if (Bounds.IsValid)
{
const FVector BoundsSize = Bounds.GetSize();
// If XY bounds are valid but Z is 0, let's just expand by a little value to get something to render still (e.g. flat landscape)
if ((BoundsSize.X > UE_KINDA_SMALL_NUMBER) && (BoundsSize.Y > UE_KINDA_SMALL_NUMBER) && (BoundsSize.Z <= UE_KINDA_SMALL_NUMBER))
{
Bounds = Bounds.ExpandBy(FVector(0.0, 0.0, 0.5));
}
// Expand bounds if requested
const float ExpandBounds = InComponent->GetExpandBounds();
if (ExpandBounds > 0.0f)
{
Bounds = Bounds.ExpandBy(ExpandBounds);
}
}
// Calculate the transform to fit the bounds.
FTransform Transform;
const FVector LocalPosition = Bounds.Min;
const FVector WorldPosition = LocalTransform.TransformPosition(LocalPosition);
const FVector WorldSize = Bounds.GetSize();
Transform.SetComponents(TargetRotation, WorldPosition, WorldSize);
// Adjust and snap to landscape if requested.
// This places the texels on the landscape vertex positions which is desirable for virtual textures that hold height or position information.
// Warning: This shifts the virtual texture volume so that it might be larger then the landscape (or smaller if insufficient resolution has been set).
if (InComponent->GetSnapBoundsToLandscape() && BoundsAlignActor.IsValid())
{
ALandscape const* Landscape = Cast<ALandscape>(BoundsAlignActor.Get());
if (Landscape != nullptr)
{
const FTransform LandscapeTransform = Landscape->GetTransform();
const FVector LandscapePosition = LandscapeTransform.GetTranslation();
const FVector LandscapeScale = LandscapeTransform.GetScale3D();
// We want to set the virtual texture scale so that the landscape quad size is some power of two multiple of the final virtual texture texel size.
ULandscapeInfo const* LandscapeInfo = Landscape->GetLandscapeInfo();
FIntRect LandscapeRect = LandscapeInfo->GetCompleteLandscapeExtent();
const FIntPoint LandscapeSize = LandscapeRect.Size();
const int32 LandscapeSizeLog2 = FMath::Max(FMath::CeilLogTwo(LandscapeSize.X), FMath::CeilLogTwo(LandscapeSize.Y));
const int32 VirtualTextureSize = VirtualTexture->GetSize();
const int32 VirtualTextureSizeLog2 = FMath::FloorLog2(VirtualTextureSize);
const int32 VirtualTexelsPerLandscapeVertexLog2 = FMath::Max(VirtualTextureSizeLog2 - LandscapeSizeLog2, 0);
const int32 VirtualTexelsPerLandscapeVertex = 1 << VirtualTexelsPerLandscapeVertexLog2;
FVector VirtualTexelWorldSize = LandscapeScale / (float)VirtualTexelsPerLandscapeVertex;
FVector VirtualTextureScale = VirtualTexelWorldSize * (float)VirtualTextureSize;
// We may need to adjust for the case where the calculated VirtualTextureScale wasn't big enough to cover the previously calculated bounds.
const int32 AdditionalScaleX = (int32)FMath::CeilToInt(WorldSize.X / VirtualTextureScale.X);
const int32 AdditionalScaleY = (int32)FMath::CeilToInt(WorldSize.Y / VirtualTextureScale.Y);
const int32 AdditionalScaleAligned = FMath::RoundUpToPowerOfTwo(FMath::Max(AdditionalScaleX, AdditionalScaleY));
// Need to clamp scale so that virtual texture texel is no bigger than a landscape quad.
// This may prevent us from covering the required bounds. In that case the user fix is to increase the resolution of the runtime virtual texture.
const float AdditionalScale = (float)FMath::Min(VirtualTexelsPerLandscapeVertex, AdditionalScaleAligned);
VirtualTexelWorldSize.X *= AdditionalScale;
VirtualTexelWorldSize.Y *= AdditionalScale;
VirtualTextureScale.X *= AdditionalScale;
VirtualTextureScale.Y *= AdditionalScale;
Transform.SetScale3D(FVector(VirtualTextureScale.X, VirtualTextureScale.Y, Transform.GetScale3D().Z));
// Adjust position to snap at a half texel offset from landscape.
const FVector BaseVirtualTexturePosition = Transform.GetTranslation();
const FVector LandscapeSnapPosition = LandscapePosition - 0.5f * VirtualTexelWorldSize;
const double SnapOffsetX = FMath::Frac((BaseVirtualTexturePosition.X - LandscapeSnapPosition.X) / VirtualTexelWorldSize.X) * VirtualTexelWorldSize.X;
const double SnapOffsetY = FMath::Frac((BaseVirtualTexturePosition.Y - LandscapeSnapPosition.Y) / VirtualTexelWorldSize.Y) * VirtualTexelWorldSize.Y;
const FVector VirtualTexturePosition = BaseVirtualTexturePosition - FVector(SnapOffsetX, SnapOffsetY, 0);
Transform.SetTranslation(FVector(BaseVirtualTexturePosition.X - SnapOffsetX, BaseVirtualTexturePosition.Y - SnapOffsetY, BaseVirtualTexturePosition.Z));
}
}
// Apply final result and notify the parent actor
Owner->Modify();
Owner->SetActorTransform(Transform);
Owner->PostEditMove(true);
}
}