Files
UnrealEngine/Engine/Source/Runtime/Landscape/Classes/LandscapeEdgeFixup.h
2025-05-18 13:04:45 +08:00

306 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Containers/ArrayView.h"
#include "Containers/StaticArray.h"
#include "Misc/EnumRange.h"
#include "Engine/AssetUserData.h"
#include "Streaming/TextureMipDataProvider.h"
#include "LandscapeEdgeFixup.generated.h"
struct FLandscapeGroup;
class ULandscapeComponent;
class ULandscapeInfo;
class UTexture2D;
namespace UE::Landscape
{
// Enumerates directions, for the edges or neighbors of a tile in the the landscape group grid.
// When specifically referencing an edge or a neighbor, use EEdgeIndex or ENeighborIndex
enum class EDirectionIndex : uint8
{
Bottom = 0,
BottomRight = 1,
Right = 2,
TopRight = 3,
Top = 4,
TopLeft = 5,
Left = 6,
BottomLeft = 7,
First = 0,
Last = 7,
Count = 8,
FirstEdge = 0,
LastEdge = 6,
EdgeCount = 4,
FirstCorner = 1,
LastCorner = 7,
CornerCount = 4,
};
// Specifies a set of edges or neighbors.
enum class EDirectionFlags : uint8
{
Bottom = 0x00000001,
BottomRight = 0x00000002,
Right = 0x00000004,
TopRight = 0x00000008,
Top = 0x00000010,
TopLeft = 0x00000020,
Left = 0x00000040,
BottomLeft = 0x00000080,
None = 0,
AllCorners = BottomRight | TopRight | TopLeft | BottomLeft,
AllEdges = Bottom | Right | Top | Left,
All = AllEdges | AllCorners
};
typedef EDirectionIndex EEdgeIndex; // EEdgeIndex specifies an edge or corner on the local landscape component.
typedef EDirectionFlags EEdgeFlags; // EEdgeFlags specifies a set of edges and corners on the local landscape component.
typedef EDirectionIndex ENeighborIndex; // ENeighborIndex specifies a neighbor landscape component, relative to a local landscape component.
typedef EDirectionFlags ENeighborFlags; // ENeighborFlags specifies a set of neighboring landscape components.
struct FHeightmapTexel
{
static_assert(PLATFORM_LITTLE_ENDIAN, "This code needs to be adapted to big endian");
union
{
uint32 Data32;
uint8 Data[4];
struct
{
uint8 NormalX;
uint8 HeightL; // low 8 bits
uint8 HeightH; // high 8 bits
uint8 NormalY;
};
};
uint32 GetHeight16() const
{
return (((uint32)HeightH) << 8) + HeightL;
}
void SetHeight16(uint16 NewHeight)
{
HeightL = NewHeight & 0xff;
HeightH = (NewHeight >> 8) & 0xff;
}
void SetNormal(const FVector& NewNormal)
{
const FVector Normal = NewNormal.GetSafeNormal();
// these seem to produce the nearest result to the GPU normal calculation
// (matches 'straight up' of 0x7f coming from the GPU)
NormalX = static_cast<uint8>(FMath::RoundToInt32(127.49999f + Normal.X * 127.5f));
NormalY = static_cast<uint8>(FMath::RoundToInt32(127.49999f + Normal.Y * 127.5f));
}
bool IsSameHeight(const FHeightmapTexel& Other) const
{
uint32 HeightBitsXOR = (Data32 ^ Other.Data32) & 0x00FFFF00;
return HeightBitsXOR == 0;
}
};
// states required to perform edge patching
struct FNeighborSnapshots
{
ENeighborFlags ExistingNeighbors;
EEdgeFlags EdgesWithAnyModifiedNeighbor;
FHeightmapTextureEdgeSnapshot* NeighborSnapshots[8];
FHeightmapTextureEdgeSnapshot* LocalSnapshot;
TStaticArray<uint32, 8> GPUEdgeHashes;
};
}
ENUM_RANGE_BY_FIRST_AND_LAST(UE::Landscape::EDirectionIndex, UE::Landscape::EDirectionIndex::First, UE::Landscape::EDirectionIndex::Last);
ENUM_CLASS_FLAGS(UE::Landscape::EDirectionFlags);
namespace UE::Landscape
{
// Returns the relative offset (in group tile coords) of a neighbor
FIntPoint GetNeighborRelativePosition(ENeighborIndex NeighborIndex);
// Returns the set of neighbors that blend with any local edge in EdgeFlags
ENeighborFlags EdgesToAffectedNeighbors(EEdgeFlags LocalEdgeFlags);
// Converts a neighbor index to a neighbor flag (or an edge index to an edge flag)
EDirectionFlags ToFlag(EDirectionIndex Index);
// Returns a debug string describing the neighbor (or edge) direction
const FString& GetDirectionString(EDirectionIndex Index);
}
/**
* The snapshots contain a copy of the heightmap edge texels (both height and normal info).
* It is filled out in editor or at cook time, to make available at runtime for dynamic edge fixup.
*/
USTRUCT()
struct FHeightmapTextureEdgeSnapshot
{
GENERATED_USTRUCT_BODY()
friend class ULandscapeHeightmapTextureEdgeFixup;
private:
int32 EdgeLength = 0; // edge length for recorded edge data here - when up to date, should match texture resolution (width AND height)
TArray<uint32> EdgeData; // height and normal data for each edge & mip (in heightmap texture format 32bpp). Use GetEdgeData() to access specific edges and mips.
TStaticArray<uint32, 8> SnapshotEdgeHashes; // hash of each edge / corner (at full resolution) in the EdgeData
TStaticArray<uint32, 8> InitialEdgeHashes; // hash of each edge / corner (at full resolution) in the GPU Texture Resource (at initial unpatched state)
#if WITH_EDITOR
FGuid TextureSourceID; // used to detect when this is out of date with texture sourcesource
#endif // WITH_EDITOR
public:
/** Return edge snapshot data for this component, for the specified neighbor direction and mip.
* Horizontal edges are stored left to right, and vertical edges bottom to top.
*/
TArrayView<UE::Landscape::FHeightmapTexel> GetEdgeData(UE::Landscape::EEdgeIndex InEdgeIndex, int32 InMipIndex);
UE::Landscape::FHeightmapTexel GetCornerData(UE::Landscape::EEdgeIndex InEdgeIndex);
#if WITH_EDITOR
// Create a snapshot from the heightmap source
static TSharedRef<FHeightmapTextureEdgeSnapshot> CreateEdgeSnapshotFromHeightmapSource(UTexture2D* InHeightmap, const FVector& LandscapeGridScale);
#endif // WITH_EDITOR
// Create a snapshot from an explicit byte array (in heightmap source standard layout)
static TSharedRef<FHeightmapTextureEdgeSnapshot> CreateEdgeSnapshotFromTextureData(const TArrayView64<UE::Landscape::FHeightmapTexel>& InHeightmapTextureData, int32 InEdgeLength, const FVector& LandscapeGridScale);
// Return the set of edges that are different (according to edge hashes) -- i.e. changes that could cause neighbors to patch
UE::Landscape::EEdgeFlags CompareEdges(const FHeightmapTextureEdgeSnapshot& OldSnapshot) const;
friend FArchive& operator<<(FArchive& Ar, FHeightmapTextureEdgeSnapshot& Data);
FString GetTextureSourceIDAsString()
{
#if WITH_EDITOR
return TextureSourceID.ToString();
#else
return FString("<NONE>");
#endif // WITH_EDITOR
}
private:
void ResizeForEdgeLength(const int32 InEdgeLength);
#if WITH_EDITOR
// These CaptureEdgeData* functions are NOT THREAD SAFE --
// they stomp existing data and should only be used on newly allocated FHeightmapTextureEdgeSnapshot
void CaptureEdgeDataFromHeightmapSource_Internal(UTexture2D* InHeightmap, const FVector& LandscapeGridScale);
#endif // WITH_EDITOR
void CaptureEdgeDataFromTextureData_Internal(const TArrayView64<UE::Landscape::FHeightmapTexel>& InHeightmapTextureData, int32 InEdgeLength, const FVector& LandscapeGridScale);
// must call ResizeForEdgeLength to set the texture size before calling CopyTextureEdgeDataAndRecomputeNormals
void CaptureSingleEdgeDataAndComputeNormalsAndHashes(const UE::Landscape::FHeightmapTexel* TextureData, const UE::Landscape::EEdgeIndex EdgeOrCorner, const FVector& LandscapeGridScale);
};
// This UAssetUserData is attached to landscape heightmap UTexture2D's and tracks the heightmap texture's edge fixup state
// This is used by mip providers to apply edge fixup on mip streaming/creation, and
// also used by runtime dynamic fixup when neighboring landscape components are pulled in
UCLASS(NotBlueprintable, MinimalAPI, Within = Texture2D)
class ULandscapeHeightmapTextureEdgeFixup : public UAssetUserData
{
GENERATED_UCLASS_BODY()
friend struct FLandscapeGroup;
friend class FLandscapeTextureMipEdgeOverrideProvider;
friend class FLandscapeTextureStorageMipProvider;
private:
// SERIALIZED snapshot of the heightmap edge data
// COPY-ON-WRITE so we can use it safely from other threads. do not modify an existing snapshot, create a new snapshot and replace this reference.
TSharedRef<FHeightmapTextureEdgeSnapshot, ESPMode::ThreadSafe> EdgeSnapshot;
// transient runtime tracking data
TObjectPtr<UTexture2D> HeightmapTexture; // heightmap texture (set to our parent heightmap, on first registration)
TObjectPtr<ULandscapeComponent> ActiveComponent; // the active component, that is patching HeightmapTexture
FLandscapeGroup* ActiveGroup = nullptr; // the active group, that is patching HeightmapTexture
TStaticArray<uint32, 8> GPUEdgeHashes; // hash for the current GPU edge state, initialized on first registration
UE::Landscape::EEdgeFlags GPUEdgeModifiedFlags = // edges that have been patched / modified from the initial state
UE::Landscape::EEdgeFlags::None;
#if WITH_EDITOR
bool bDoNotPatchUntilGPUEdgeHashesUpdated = false;
bool bUpdateGPUEdgeHashes = false;
#endif // WITH_EDITOR
// per-group settings (apply to the active group/component)
bool bMapped = false;
bool bForceUpdateSnapshot = true; // set to true initially so that we do a force update on the very first request
FIntPoint GroupCoord = FIntPoint::ZeroValue; // coordinate of this heightmap in the active group (when bMapped)
// components that also want to use this heightmap & edge fixup, but were disabled as we can only support one active component at a time.
// these must be weak object pointers, as they can be unregistered while still in this list, and can be garbage collected out from under us.
// Note that this array is generally empty except in scenarios where there are multiple active worlds sharing the same landscape textures (PIE)
// so the expense of accessing the TWeakObjectPtr is a PIE-only cost.
TArray<TWeakObjectPtr<ULandscapeComponent>> DisabledComponents;
public:
inline bool IsActive() { return ActiveGroup != nullptr; }
inline UTexture2D* GetHeightmapTexture() { return HeightmapTexture; }
inline bool IsComponentActive(ULandscapeComponent* Component) { return ActiveComponent == Component; }
inline const FIntPoint& GetGroupCoord() { return GroupCoord; }
#if WITH_EDITOR
inline bool IsTextureEdgePatchingPaused() { return bDoNotPatchUntilGPUEdgeHashesUpdated; }
inline void PauseTextureEdgePatchingUntilGPUEdgeHashesUpdated() { bDoNotPatchUntilGPUEdgeHashesUpdated = true; }
#endif // WITH_EDITOR
virtual void Serialize(FArchive& Ar) override;
static void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector);
// [main thread] -- Find or Create an edge fixup class for the given heightmap texture. Will only create in editor (the data used to create is only available in editor)
static ULandscapeHeightmapTextureEdgeFixup* FindOrCreateFor(UTexture2D* TargetTexture);
// set the heightmap texture (can only be called once)
void SetHeightmapTexture(UTexture2D* InHeightmapTexture);
// set the active landscape component, handling unmapping the old, and mapping the new.
// if bDisableCurrentActive, it will move the current active component, if any, to the disabled list.
void SetActiveComponent(ULandscapeComponent* InComponent, FLandscapeGroup* InGroup, const bool bDisableCurrentActive = true);
// Request edge texture patching on a set of neighbors.
void RequestEdgeTexturePatchingForNeighbors(UE::Landscape::ENeighborFlags NeighborsNeedingPatching);
#if WITH_EDITOR
// Request an update to the edge snapshot (capturing from the heightmap source).
// When bUpdateGPUEdgeHashes is true, it will also update the GPU Edge Hashes to match.
// This is set when we know the heightmap source exactly reflects the GPU texture state
// i.e. after reading back from GPU to CPU after a layer merge, or after UpdateResource().
void RequestEdgeSnapshotUpdateFromHeightmapSource(bool bUpdateGPUEdgeHashes = false);
// Update the edge snapshot from heightmap source. Returns the set of edges that changed since the previous snapshot.
UE::Landscape::EEdgeFlags UpdateEdgeSnapshotFromHeightmapSource(const FVector& LandscapeGridScale, bool bForceUpdate = false);
#endif // WITH_EDITOR
// Patch the GPU texture edges if needed, using the current snapshot and corresponding neighbor snapshots as source data.
// Returns the number of edges patched.
int32 CheckAndPatchTextureEdgesFromEdgeSnapshots();
// Get the set of neighbor snapshots (nullptr if they don't exist), and gather existence and modified flags
// this is the data necessary to perform patching on a component
void GetNeighborSnapshots(UE::Landscape::FNeighborSnapshots& OutSnapshots);
// Patch all of the edges for a single texture mip.
// Called during streaming operations to patch a newly streamed mip in flight.
// Returns the number of edges and corners patched
static int32 PatchTextureEdgesForSingleMip(int32 MipIndex, FTextureMipInfo& DestMipInfo, const UE::Landscape::FNeighborSnapshots& NeighborSnapshots);
static int32 PatchTextureEdgesForStreamingMips(int32 FirstMipIndexInclusive, int32 LastMipIndexExclusive, FTextureMipInfoArray& DestMipInfos, const UE::Landscape::FNeighborSnapshots& NeighborSnapshots);
private:
void PatchTextureEdge_Internal(UE::Landscape::EEdgeIndex EdgeIndex);
void PatchTextureCorner_Internal(UE::Landscape::EEdgeIndex CornerIndex, UE::Landscape::FHeightmapTexel Texel);
// Helper functions that generate blended edge or corner data from snapshots, to use in texture patching
static void BlendEdgeData(FHeightmapTextureEdgeSnapshot& EdgeSnapshot, UE::Landscape::EEdgeIndex EdgeIndex, int32 MipIndex, FHeightmapTextureEdgeSnapshot& NeighborEdgeSnapshot, TStridedView<UE::Landscape::FHeightmapTexel>& OutDestView);
static void BlendCornerData(UE::Landscape::FHeightmapTexel& OutTexel, UE::Landscape::EEdgeIndex CornerIndex, const UE::Landscape::FNeighborSnapshots& NeighborSnapshots);
};