You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
This represents UE4/Main @18073326, Release-5.0 @18081140 and Dev-PerfTest @18045971 [CL 18081471 by aurel cordonnier in ue5-release-engine-test branch]
1545 lines
45 KiB
C++
1545 lines
45 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "UObject/GCObject.h"
|
|
#include "LandscapeProxy.h"
|
|
#include "LandscapeToolInterface.h"
|
|
#include "LandscapeEdMode.h"
|
|
#include "EditorViewportClient.h"
|
|
#include "LandscapeEdit.h"
|
|
#include "LandscapeComponent.h"
|
|
#include "LandscapeDataAccess.h"
|
|
#include "LandscapeHeightfieldCollisionComponent.h"
|
|
#include "InstancedFoliageActor.h"
|
|
#include "AI/NavigationSystemBase.h"
|
|
#include "Landscape.h"
|
|
#include "Logging/LogMacros.h"
|
|
|
|
DECLARE_LOG_CATEGORY_EXTERN(LogLandscapeTools, Log, All);
|
|
|
|
|
|
//
|
|
// FNoiseParameter - Perlin noise
|
|
//
|
|
struct FNoiseParameter
|
|
{
|
|
float Base,
|
|
NoiseScale,
|
|
NoiseAmount;
|
|
|
|
// Constructors.
|
|
|
|
FNoiseParameter()
|
|
{
|
|
}
|
|
FNoiseParameter(float InBase, float InScale, float InAmount) :
|
|
Base(InBase),
|
|
NoiseScale(InScale),
|
|
NoiseAmount(InAmount)
|
|
{
|
|
}
|
|
|
|
// Sample
|
|
float Sample(int32 X, int32 Y) const
|
|
{
|
|
float Noise = 0.0f;
|
|
X = FMath::Abs(X);
|
|
Y = FMath::Abs(Y);
|
|
|
|
if (NoiseScale > DELTA)
|
|
{
|
|
for (uint32 Octave = 0; Octave < 4; Octave++)
|
|
{
|
|
float OctaveShift = 1 << Octave;
|
|
float OctaveScale = OctaveShift / NoiseScale;
|
|
Noise += PerlinNoise2D(X * OctaveScale, Y * OctaveScale) / OctaveShift;
|
|
}
|
|
}
|
|
|
|
return Base + Noise * NoiseAmount;
|
|
}
|
|
|
|
// TestGreater - Returns 1 if TestValue is greater than the parameter.
|
|
bool TestGreater(int32 X, int32 Y, float TestValue) const
|
|
{
|
|
float ParameterValue = Base;
|
|
|
|
if (NoiseScale > DELTA)
|
|
{
|
|
for (uint32 Octave = 0; Octave < 4; Octave++)
|
|
{
|
|
float OctaveShift = 1 << Octave;
|
|
float OctaveAmplitude = NoiseAmount / OctaveShift;
|
|
|
|
// Attempt to avoid calculating noise if the test value is outside of the noise amplitude.
|
|
|
|
if (TestValue > ParameterValue + OctaveAmplitude)
|
|
return 1;
|
|
else if (TestValue < ParameterValue - OctaveAmplitude)
|
|
return 0;
|
|
else
|
|
{
|
|
float OctaveScale = OctaveShift / NoiseScale;
|
|
ParameterValue += PerlinNoise2D(X * OctaveScale, Y * OctaveScale) * OctaveAmplitude;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TestValue >= ParameterValue;
|
|
}
|
|
|
|
// TestLess
|
|
bool TestLess(int32 X, int32 Y, float TestValue) const { return !TestGreater(X, Y, TestValue); }
|
|
|
|
private:
|
|
static const int32 Permutations[256];
|
|
|
|
bool operator==(const FNoiseParameter& SrcNoise)
|
|
{
|
|
if ((Base == SrcNoise.Base) &&
|
|
(NoiseScale == SrcNoise.NoiseScale) &&
|
|
(NoiseAmount == SrcNoise.NoiseAmount))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void operator=(const FNoiseParameter& SrcNoise)
|
|
{
|
|
Base = SrcNoise.Base;
|
|
NoiseScale = SrcNoise.NoiseScale;
|
|
NoiseAmount = SrcNoise.NoiseAmount;
|
|
}
|
|
|
|
|
|
float Fade(float T) const
|
|
{
|
|
return T * T * T * (T * (T * 6 - 15) + 10);
|
|
}
|
|
|
|
|
|
float Grad(int32 Hash, float X, float Y) const
|
|
{
|
|
int32 H = Hash & 15;
|
|
float U = H < 8 || H == 12 || H == 13 ? X : Y,
|
|
V = H < 4 || H == 12 || H == 13 ? Y : 0;
|
|
return ((H & 1) == 0 ? U : -U) + ((H & 2) == 0 ? V : -V);
|
|
}
|
|
|
|
float PerlinNoise2D(float X, float Y) const
|
|
{
|
|
int32 TruncX = FMath::TruncToInt(X),
|
|
TruncY = FMath::TruncToInt(Y),
|
|
IntX = TruncX & 255,
|
|
IntY = TruncY & 255;
|
|
float FracX = X - TruncX,
|
|
FracY = Y - TruncY;
|
|
|
|
float U = Fade(FracX),
|
|
V = Fade(FracY);
|
|
|
|
int32 A = Permutations[IntX] + IntY,
|
|
AA = Permutations[A & 255],
|
|
AB = Permutations[(A + 1) & 255],
|
|
B = Permutations[(IntX + 1) & 255] + IntY,
|
|
BA = Permutations[B & 255],
|
|
BB = Permutations[(B + 1) & 255];
|
|
|
|
return FMath::Lerp(FMath::Lerp(Grad(Permutations[AA], FracX, FracY),
|
|
Grad(Permutations[BA], FracX - 1, FracY), U),
|
|
FMath::Lerp(Grad(Permutations[AB], FracX, FracY - 1),
|
|
Grad(Permutations[BB], FracX - 1, FracY - 1), U), V);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
#if WITH_KISSFFT
|
|
#include "tools/kiss_fftnd.h"
|
|
#endif
|
|
|
|
template<typename DataType>
|
|
inline void LowPassFilter(int32 X1, int32 Y1, int32 X2, int32 Y2, FLandscapeBrushData& BrushInfo, TArray<DataType>& Data, const float DetailScale, const float ApplyRatio = 1.0f)
|
|
{
|
|
#if WITH_KISSFFT
|
|
// Low-pass filter
|
|
int32 FFTWidth = X2 - X1 - 1;
|
|
int32 FFTHeight = Y2 - Y1 - 1;
|
|
|
|
if (FFTWidth <= 1 && FFTHeight <= 1)
|
|
{
|
|
// nothing to do
|
|
return;
|
|
}
|
|
|
|
const int32 NDims = 2;
|
|
const int32 Dims[NDims] = { FFTHeight, FFTWidth };
|
|
kiss_fftnd_cfg stf = kiss_fftnd_alloc(Dims, NDims, 0, NULL, NULL),
|
|
sti = kiss_fftnd_alloc(Dims, NDims, 1, NULL, NULL);
|
|
|
|
kiss_fft_cpx *buf = (kiss_fft_cpx *)KISS_FFT_MALLOC(sizeof(kiss_fft_cpx) * Dims[0] * Dims[1]);
|
|
kiss_fft_cpx *out = (kiss_fft_cpx *)KISS_FFT_MALLOC(sizeof(kiss_fft_cpx) * Dims[0] * Dims[1]);
|
|
|
|
for (int32 Y = Y1 + 1; Y <= Y2 - 1; Y++)
|
|
{
|
|
auto* DataScanline = Data.GetData() + (Y - Y1) * (X2 - X1 + 1) + (0 - X1);
|
|
auto* bufScanline = buf + (Y - (Y1 + 1)) * Dims[1] + (0 - (X1 + 1));
|
|
|
|
for (int32 X = X1 + 1; X <= X2 - 1; X++)
|
|
{
|
|
bufScanline[X].r = DataScanline[X];
|
|
bufScanline[X].i = 0;
|
|
}
|
|
}
|
|
|
|
// Forward FFT
|
|
kiss_fftnd(stf, buf, out);
|
|
|
|
int32 CenterPos[2] = { Dims[0] >> 1, Dims[1] >> 1 };
|
|
for (int32 Y = 0; Y < Dims[0]; Y++)
|
|
{
|
|
float DistFromCenter = 0.0f;
|
|
for (int32 X = 0; X < Dims[1]; X++)
|
|
{
|
|
if (Y < CenterPos[0])
|
|
{
|
|
if (X < CenterPos[1])
|
|
{
|
|
// 1
|
|
DistFromCenter = X*X + Y*Y;
|
|
}
|
|
else
|
|
{
|
|
// 2
|
|
DistFromCenter = (X - Dims[1])*(X - Dims[1]) + Y*Y;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (X < CenterPos[1])
|
|
{
|
|
// 3
|
|
DistFromCenter = X*X + (Y - Dims[0])*(Y - Dims[0]);
|
|
}
|
|
else
|
|
{
|
|
// 4
|
|
DistFromCenter = (X - Dims[1])*(X - Dims[1]) + (Y - Dims[0])*(Y - Dims[0]);
|
|
}
|
|
}
|
|
// High frequency removal
|
|
float Ratio = 1.0f - DetailScale;
|
|
float Dist = FMath::Min<float>((Dims[0] * Ratio)*(Dims[0] * Ratio), (Dims[1] * Ratio)*(Dims[1] * Ratio));
|
|
float Filter = 1.0 / (1.0 + DistFromCenter / Dist);
|
|
CA_SUPPRESS(6385);
|
|
out[X + Y*Dims[1]].r *= Filter;
|
|
out[X + Y*Dims[1]].i *= Filter;
|
|
}
|
|
}
|
|
|
|
// Inverse FFT
|
|
kiss_fftnd(sti, out, buf);
|
|
|
|
float Scale = Dims[0] * Dims[1];
|
|
const int32 BrushX1 = FMath::Max<int32>(BrushInfo.GetBounds().Min.X, X1 + 1);
|
|
const int32 BrushY1 = FMath::Max<int32>(BrushInfo.GetBounds().Min.Y, Y1 + 1);
|
|
const int32 BrushX2 = FMath::Min<int32>(BrushInfo.GetBounds().Max.X, X2);
|
|
const int32 BrushY2 = FMath::Min<int32>(BrushInfo.GetBounds().Max.Y, Y2);
|
|
for (int32 Y = BrushY1; Y < BrushY2; Y++)
|
|
{
|
|
const float* BrushScanline = BrushInfo.GetDataPtr(FIntPoint(0, Y));
|
|
auto* DataScanline = Data.GetData() + (Y - Y1) * (X2 - X1 + 1) + (0 - X1);
|
|
auto* bufScanline = buf + (Y - (Y1 + 1)) * Dims[1] + (0 - (X1 + 1));
|
|
|
|
for (int32 X = BrushX1; X < BrushX2; X++)
|
|
{
|
|
const float BrushValue = BrushScanline[X];
|
|
|
|
if (BrushValue > 0.0f)
|
|
{
|
|
DataScanline[X] = FMath::Lerp((float)DataScanline[X], bufScanline[X].r / Scale, BrushValue * ApplyRatio);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Free FFT allocation
|
|
KISS_FFT_FREE(stf);
|
|
KISS_FFT_FREE(sti);
|
|
KISS_FFT_FREE(buf);
|
|
KISS_FFT_FREE(out);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// TLandscapeEditCache
|
|
//
|
|
template<class Accessor, typename AccessorType>
|
|
struct TLandscapeEditCache
|
|
{
|
|
public:
|
|
typedef AccessorType DataType;
|
|
Accessor DataAccess;
|
|
|
|
TLandscapeEditCache(const FLandscapeToolTarget& InTarget)
|
|
: DataAccess(InTarget)
|
|
, Valid(false)
|
|
{
|
|
}
|
|
|
|
// X2/Y2 Coordinates are "inclusive" max values
|
|
void CacheData(int32 X1, int32 Y1, int32 X2, int32 Y2, bool bCacheOriginalData = false)
|
|
{
|
|
if (!Valid)
|
|
{
|
|
if (Accessor::bUseInterp)
|
|
{
|
|
ValidX1 = CachedX1 = X1;
|
|
ValidY1 = CachedY1 = Y1;
|
|
ValidX2 = CachedX2 = X2;
|
|
ValidY2 = CachedY2 = Y2;
|
|
|
|
DataAccess.GetData(ValidX1, ValidY1, ValidX2, ValidY2, CachedData);
|
|
if (!ensureMsgf(ValidX1 <= ValidX2 && ValidY1 <= ValidY2, TEXT("Invalid cache area: X(%d-%d), Y(%d-%d) from region X(%d-%d), Y(%d-%d)"), ValidX1, ValidX2, ValidY1, ValidY2, X1, X2, Y1, Y2))
|
|
{
|
|
Valid = false;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CachedX1 = X1;
|
|
CachedY1 = Y1;
|
|
CachedX2 = X2;
|
|
CachedY2 = Y2;
|
|
|
|
DataAccess.GetDataFast(CachedX1, CachedY1, CachedX2, CachedY2, CachedData);
|
|
}
|
|
|
|
if (bCacheOriginalData)
|
|
{
|
|
OriginalData = CachedData;
|
|
}
|
|
Valid = true;
|
|
}
|
|
else
|
|
{
|
|
// Extend the cache area if needed
|
|
if (X1 < CachedX1)
|
|
{
|
|
if (Accessor::bUseInterp)
|
|
{
|
|
int32 x1 = X1;
|
|
int32 x2 = ValidX1;
|
|
int32 y1 = FMath::Min<int32>(Y1, CachedY1);
|
|
int32 y2 = FMath::Max<int32>(Y2, CachedY2);
|
|
|
|
DataAccess.GetData(x1, y1, x2, y2, CachedData);
|
|
ValidX1 = FMath::Min<int32>(x1, ValidX1);
|
|
}
|
|
else
|
|
{
|
|
DataAccess.GetDataFast(X1, CachedY1, CachedX1 - 1, CachedY2, CachedData);
|
|
}
|
|
|
|
if (bCacheOriginalData)
|
|
{
|
|
CacheOriginalData(X1, CachedY1, CachedX1 - 1, CachedY2);
|
|
}
|
|
CachedX1 = X1;
|
|
}
|
|
|
|
if (X2 > CachedX2)
|
|
{
|
|
if (Accessor::bUseInterp)
|
|
{
|
|
int32 x1 = ValidX2;
|
|
int32 x2 = X2;
|
|
int32 y1 = FMath::Min<int32>(Y1, CachedY1);
|
|
int32 y2 = FMath::Max<int32>(Y2, CachedY2);
|
|
|
|
DataAccess.GetData(x1, y1, x2, y2, CachedData);
|
|
ValidX2 = FMath::Max<int32>(x2, ValidX2);
|
|
}
|
|
else
|
|
{
|
|
DataAccess.GetDataFast(CachedX2 + 1, CachedY1, X2, CachedY2, CachedData);
|
|
}
|
|
if (bCacheOriginalData)
|
|
{
|
|
CacheOriginalData(CachedX2 + 1, CachedY1, X2, CachedY2);
|
|
}
|
|
CachedX2 = X2;
|
|
}
|
|
|
|
if (Y1 < CachedY1)
|
|
{
|
|
if (Accessor::bUseInterp)
|
|
{
|
|
int32 x1 = CachedX1;
|
|
int32 x2 = CachedX2;
|
|
int32 y1 = Y1;
|
|
int32 y2 = ValidY1;
|
|
|
|
DataAccess.GetData(x1, y1, x2, y2, CachedData);
|
|
ValidY1 = FMath::Min<int32>(y1, ValidY1);
|
|
}
|
|
else
|
|
{
|
|
DataAccess.GetDataFast(CachedX1, Y1, CachedX2, CachedY1 - 1, CachedData);
|
|
}
|
|
if (bCacheOriginalData)
|
|
{
|
|
CacheOriginalData(CachedX1, Y1, CachedX2, CachedY1 - 1);
|
|
}
|
|
CachedY1 = Y1;
|
|
}
|
|
|
|
if (Y2 > CachedY2)
|
|
{
|
|
if (Accessor::bUseInterp)
|
|
{
|
|
int32 x1 = CachedX1;
|
|
int32 x2 = CachedX2;
|
|
int32 y1 = ValidY2;
|
|
int32 y2 = Y2;
|
|
|
|
DataAccess.GetData(x1, y1, x2, y2, CachedData);
|
|
ValidY2 = FMath::Max<int32>(y2, ValidY2);
|
|
}
|
|
else
|
|
{
|
|
DataAccess.GetDataFast(CachedX1, CachedY2 + 1, CachedX2, Y2, CachedData);
|
|
}
|
|
if (bCacheOriginalData)
|
|
{
|
|
CacheOriginalData(CachedX1, CachedY2 + 1, CachedX2, Y2);
|
|
}
|
|
CachedY2 = Y2;
|
|
}
|
|
}
|
|
}
|
|
|
|
AccessorType* GetValueRef(int32 LandscapeX, int32 LandscapeY)
|
|
{
|
|
return CachedData.Find(FIntPoint(LandscapeX, LandscapeY));
|
|
}
|
|
|
|
float GetValue(float LandscapeX, float LandscapeY)
|
|
{
|
|
int32 X = FMath::FloorToInt(LandscapeX);
|
|
int32 Y = FMath::FloorToInt(LandscapeY);
|
|
AccessorType* P00 = CachedData.Find(FIntPoint(X, Y));
|
|
AccessorType* P10 = CachedData.Find(FIntPoint(X + 1, Y));
|
|
AccessorType* P01 = CachedData.Find(FIntPoint(X, Y + 1));
|
|
AccessorType* P11 = CachedData.Find(FIntPoint(X + 1, Y + 1));
|
|
|
|
// Search for nearest value if missing data
|
|
float V00 = P00 ? *P00 : (P10 ? *P10 : (P01 ? *P01 : (P11 ? *P11 : 0.0f)));
|
|
float V10 = P10 ? *P10 : (P00 ? *P00 : (P11 ? *P11 : (P01 ? *P01 : 0.0f)));
|
|
float V01 = P01 ? *P01 : (P00 ? *P00 : (P11 ? *P11 : (P10 ? *P10 : 0.0f)));
|
|
float V11 = P11 ? *P11 : (P10 ? *P10 : (P01 ? *P01 : (P00 ? *P00 : 0.0f)));
|
|
|
|
return FMath::Lerp(
|
|
FMath::Lerp(V00, V10, LandscapeX - X),
|
|
FMath::Lerp(V01, V11, LandscapeX - X),
|
|
LandscapeY - Y);
|
|
}
|
|
|
|
FVector GetNormal(int32 X, int32 Y)
|
|
{
|
|
AccessorType* P00 = CachedData.Find(FIntPoint(X, Y));
|
|
AccessorType* P10 = CachedData.Find(FIntPoint(X + 1, Y));
|
|
AccessorType* P01 = CachedData.Find(FIntPoint(X, Y + 1));
|
|
AccessorType* P11 = CachedData.Find(FIntPoint(X + 1, Y + 1));
|
|
|
|
// Search for nearest value if missing data
|
|
float V00 = P00 ? *P00 : (P10 ? *P10 : (P01 ? *P01 : (P11 ? *P11 : 0.0f)));
|
|
float V10 = P10 ? *P10 : (P00 ? *P00 : (P11 ? *P11 : (P01 ? *P01 : 0.0f)));
|
|
float V01 = P01 ? *P01 : (P00 ? *P00 : (P11 ? *P11 : (P10 ? *P10 : 0.0f)));
|
|
float V11 = P11 ? *P11 : (P10 ? *P10 : (P01 ? *P01 : (P00 ? *P00 : 0.0f)));
|
|
|
|
FVector Vert00 = FVector(0.0f, 0.0f, V00);
|
|
FVector Vert01 = FVector(0.0f, 1.0f, V01);
|
|
FVector Vert10 = FVector(1.0f, 0.0f, V10);
|
|
FVector Vert11 = FVector(1.0f, 1.0f, V11);
|
|
|
|
FVector FaceNormal1 = ((Vert00 - Vert10) ^ (Vert10 - Vert11)).GetSafeNormal();
|
|
FVector FaceNormal2 = ((Vert11 - Vert01) ^ (Vert01 - Vert00)).GetSafeNormal();
|
|
return (FaceNormal1 + FaceNormal2).GetSafeNormal();
|
|
}
|
|
|
|
void SetValue(int32 LandscapeX, int32 LandscapeY, AccessorType Value)
|
|
{
|
|
CachedData.Add(FIntPoint(LandscapeX, LandscapeY), Forward<AccessorType>(Value));
|
|
}
|
|
|
|
bool IsZeroValue(const FVector& Value)
|
|
{
|
|
return (FMath::IsNearlyZero(Value.X) && FMath::IsNearlyZero(Value.Y));
|
|
}
|
|
|
|
bool IsZeroValue(const FVector2D& Value)
|
|
{
|
|
return (FMath::IsNearlyZero(Value.X) && FMath::IsNearlyZero(Value.Y));
|
|
}
|
|
|
|
bool IsZeroValue(const uint16& Value)
|
|
{
|
|
return Value == 0;
|
|
}
|
|
|
|
bool IsZeroValue(const uint8& Value)
|
|
{
|
|
return Value == 0;
|
|
}
|
|
|
|
bool HasCachedData(int32 X1, int32 Y1, int32 X2, int32 Y2) const
|
|
{
|
|
return (Valid && X1 >= CachedX1 && Y1 >= CachedY1 && X2 <= CachedX2 && Y2 <= CachedY2);
|
|
}
|
|
|
|
template<typename TGetCacheRegionFunction>
|
|
bool GetDataAndCache(int32 X1, int32 Y1, int32 X2, int32 Y2, TArray<AccessorType>& OutData, TGetCacheRegionFunction GetCacheRegion)
|
|
{
|
|
if (!HasCachedData(X1, Y1, X2, Y2))
|
|
{
|
|
FIntRect Bounds = GetCacheRegion();
|
|
check((Bounds.Min.X <= X1) && (Bounds.Min.Y <= Y1) && (Bounds.Max.X >= X2) && (Bounds.Max.Y >= Y2));
|
|
CacheData(Bounds.Min.X, Bounds.Min.Y, Bounds.Max.X, Bounds.Max.Y);
|
|
}
|
|
ensure(HasCachedData(X1, Y1, X2, Y2));
|
|
return GetCachedData(X1, Y1, X2, Y2, OutData);
|
|
}
|
|
|
|
// X2/Y2 Coordinates are "inclusive" max values
|
|
bool GetCachedData(int32 X1, int32 Y1, int32 X2, int32 Y2, TArray<AccessorType>& OutData)
|
|
{
|
|
const int32 XSize = (1 + X2 - X1);
|
|
const int32 YSize = (1 + Y2 - Y1);
|
|
const int32 NumSamples = XSize * YSize;
|
|
OutData.Empty(NumSamples);
|
|
OutData.AddUninitialized(NumSamples);
|
|
bool bHasNonZero = false;
|
|
|
|
for (int32 Y = Y1; Y <= Y2; Y++)
|
|
{
|
|
const int32 YOffset = (Y - Y1) * XSize;
|
|
for (int32 X = X1; X <= X2; X++)
|
|
{
|
|
const int32 XYOffset = YOffset + (X - X1);
|
|
AccessorType* Ptr = GetValueRef(X, Y);
|
|
if (Ptr)
|
|
{
|
|
OutData[XYOffset] = *Ptr;
|
|
if (!IsZeroValue(*Ptr))
|
|
{
|
|
bHasNonZero = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutData[XYOffset] = (AccessorType)0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bHasNonZero;
|
|
}
|
|
|
|
// X2/Y2 Coordinates are "inclusive" max values
|
|
void SetCachedData(int32 X1, int32 Y1, int32 X2, int32 Y2, TArray<AccessorType>& Data, ELandscapeLayerPaintingRestriction PaintingRestriction = ELandscapeLayerPaintingRestriction::None, bool bUpdateData = true)
|
|
{
|
|
checkSlow(Data.Num() == (1 + Y2 - Y1) * (1 + X2 - X1));
|
|
|
|
// Update cache
|
|
for (int32 Y = Y1; Y <= Y2; Y++)
|
|
{
|
|
for (int32 X = X1; X <= X2; X++)
|
|
{
|
|
SetValue(X, Y, Data[(X - X1) + (Y - Y1)*(1 + X2 - X1)]);
|
|
}
|
|
}
|
|
|
|
if (bUpdateData)
|
|
{
|
|
// Update real data
|
|
DataAccess.SetData(X1, Y1, X2, Y2, Data.GetData(), PaintingRestriction);
|
|
}
|
|
}
|
|
|
|
// Get the original data before we made any changes with the SetCachedData interface.
|
|
// X2/Y2 Coordinates are "inclusive" max values
|
|
void GetOriginalData(int32 X1, int32 Y1, int32 X2, int32 Y2, TArray<AccessorType>& OutOriginalData)
|
|
{
|
|
int32 NumSamples = (1 + X2 - X1)*(1 + Y2 - Y1);
|
|
OutOriginalData.Empty(NumSamples);
|
|
OutOriginalData.AddUninitialized(NumSamples);
|
|
|
|
for (int32 Y = Y1; Y <= Y2; Y++)
|
|
{
|
|
for (int32 X = X1; X <= X2; X++)
|
|
{
|
|
AccessorType* Ptr = OriginalData.Find(FIntPoint(X, Y));
|
|
if (Ptr)
|
|
{
|
|
OutOriginalData[(X - X1) + (Y - Y1)*(1 + X2 - X1)] = *Ptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Flush()
|
|
{
|
|
DataAccess.Flush();
|
|
}
|
|
|
|
private:
|
|
// X2/Y2 Coordinates are "inclusive" max values
|
|
void CacheOriginalData(int32 X1, int32 Y1, int32 X2, int32 Y2)
|
|
{
|
|
for (int32 Y = Y1; Y <= Y2; Y++)
|
|
{
|
|
for (int32 X = X1; X <= X2; X++)
|
|
{
|
|
FIntPoint Key = FIntPoint(X, Y);
|
|
AccessorType* Ptr = CachedData.Find(Key);
|
|
if (Ptr)
|
|
{
|
|
check(OriginalData.Find(Key) == NULL);
|
|
OriginalData.Add(Key, *Ptr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TMap<FIntPoint, AccessorType> CachedData;
|
|
TMap<FIntPoint, AccessorType> OriginalData;
|
|
|
|
bool Valid;
|
|
|
|
int32 CachedX1;
|
|
int32 CachedY1;
|
|
int32 CachedX2;
|
|
int32 CachedY2;
|
|
|
|
// To store valid region....
|
|
int32 ValidX1, ValidX2, ValidY1, ValidY2;
|
|
};
|
|
|
|
template<bool bInUseInterp>
|
|
struct FHeightmapAccessorTool : public FHeightmapAccessor<bInUseInterp>
|
|
{
|
|
FHeightmapAccessorTool(const FLandscapeToolTarget& InTarget)
|
|
: FHeightmapAccessor<bInUseInterp>(InTarget.LandscapeInfo.Get())
|
|
{
|
|
}
|
|
};
|
|
|
|
struct FLandscapeHeightCache : public TLandscapeEditCache<FHeightmapAccessorTool<true>, uint16>
|
|
{
|
|
static uint16 ClampValue(int32 Value) { return FMath::Clamp(Value, 0, LandscapeDataAccess::MaxValue); }
|
|
|
|
FLandscapeHeightCache(const FLandscapeToolTarget& InTarget)
|
|
: TLandscapeEditCache<FHeightmapAccessorTool<true>, uint16>(InTarget)
|
|
{
|
|
}
|
|
};
|
|
|
|
//
|
|
// FXYOffsetmapAccessor
|
|
//
|
|
template<bool bInUseInterp>
|
|
struct FXYOffsetmapAccessor
|
|
{
|
|
enum { bUseInterp = bInUseInterp };
|
|
FXYOffsetmapAccessor(ULandscapeInfo* InLandscapeInfo)
|
|
{
|
|
LandscapeInfo = InLandscapeInfo;
|
|
LandscapeEdit = new FLandscapeEditDataInterface(InLandscapeInfo);
|
|
}
|
|
|
|
FXYOffsetmapAccessor(const FLandscapeToolTarget& InTarget)
|
|
: FXYOffsetmapAccessor(InTarget.LandscapeInfo.Get())
|
|
{
|
|
}
|
|
|
|
// accessors
|
|
void GetData(int32& X1, int32& Y1, int32& X2, int32& Y2, TMap<FIntPoint, FVector>& Data)
|
|
{
|
|
LandscapeEdit->GetXYOffsetData(X1, Y1, X2, Y2, Data);
|
|
|
|
TMap<FIntPoint, uint16> NewHeights;
|
|
LandscapeEdit->GetHeightData(X1, Y1, X2, Y2, NewHeights);
|
|
for (int32 Y = Y1; Y <= Y2; ++Y)
|
|
{
|
|
for (int32 X = X1; X <= X2; ++X)
|
|
{
|
|
FVector* Value = Data.Find(FIntPoint(X, Y));
|
|
if (Value)
|
|
{
|
|
Value->Z = ((float)NewHeights.FindRef(FIntPoint(X, Y)) - 32768.0f) * LANDSCAPE_ZSCALE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GetDataFast(int32 X1, int32 Y1, int32 X2, int32 Y2, TMap<FIntPoint, FVector>& Data)
|
|
{
|
|
LandscapeEdit->GetXYOffsetDataFast(X1, Y1, X2, Y2, Data);
|
|
|
|
TMap<FIntPoint, uint16> NewHeights;
|
|
LandscapeEdit->GetHeightData(X1, Y1, X2, Y2, NewHeights);
|
|
for (int32 Y = Y1; Y <= Y2; ++Y)
|
|
{
|
|
for (int32 X = X1; X <= X2; ++X)
|
|
{
|
|
FVector* Value = Data.Find(FIntPoint(X, Y));
|
|
if (Value)
|
|
{
|
|
Value->Z = ((float)NewHeights.FindRef(FIntPoint(X, Y)) - 32768.0f) * LANDSCAPE_ZSCALE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetData(int32 X1, int32 Y1, int32 X2, int32 Y2, const FVector* Data, ELandscapeLayerPaintingRestriction PaintingRestriction = ELandscapeLayerPaintingRestriction::None)
|
|
{
|
|
TSet<ULandscapeComponent*> Components;
|
|
if (LandscapeInfo && LandscapeEdit->GetComponentsInRegion(X1, Y1, X2, Y2, &Components))
|
|
{
|
|
// Update data
|
|
ChangedComponents.Append(Components);
|
|
|
|
// Convert Height to uint16
|
|
TArray<uint16> NewHeights;
|
|
NewHeights.AddZeroed((Y2 - Y1 + 1) * (X2 - X1 + 1));
|
|
for (int32 Y = Y1; Y <= Y2; ++Y)
|
|
{
|
|
for (int32 X = X1; X <= X2; ++X)
|
|
{
|
|
NewHeights[X - X1 + (Y - Y1) * (X2 - X1 + 1)] = FMath::Clamp<uint16>(Data[(X - X1 + (Y - Y1) * (X2 - X1 + 1))].Z * LANDSCAPE_INV_ZSCALE + 32768.0f, 0, 65535);
|
|
}
|
|
}
|
|
|
|
// Notify foliage to move any attached instances
|
|
bool bUpdateFoliage = false;
|
|
bool bUpdateNormals = false;
|
|
|
|
if (!LandscapeEdit->HasLandscapeLayersContent())
|
|
{
|
|
ALandscapeProxy::InvalidateGeneratedComponentData(Components);
|
|
|
|
bUpdateNormals = true;
|
|
for (ULandscapeComponent* Component : Components)
|
|
{
|
|
ULandscapeHeightfieldCollisionComponent* CollisionComponent = Component->CollisionComponent.Get();
|
|
if (CollisionComponent && AInstancedFoliageActor::HasFoliageAttached(CollisionComponent))
|
|
{
|
|
bUpdateFoliage = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bUpdateFoliage)
|
|
{
|
|
// Calculate landscape local-space bounding box of old data, to look for foliage instances.
|
|
TArray<ULandscapeHeightfieldCollisionComponent*> CollisionComponents;
|
|
CollisionComponents.Empty(Components.Num());
|
|
TArray<FBox> PreUpdateLocalBoxes;
|
|
PreUpdateLocalBoxes.Empty(Components.Num());
|
|
|
|
for (ULandscapeComponent* Component : Components)
|
|
{
|
|
CollisionComponents.Add(Component->CollisionComponent.Get());
|
|
PreUpdateLocalBoxes.Add(FBox(FVector((float)X1, (float)Y1, Component->CachedLocalBox.Min.Z), FVector((float)X2, (float)Y2, Component->CachedLocalBox.Max.Z)));
|
|
}
|
|
|
|
// Update landscape.
|
|
LandscapeEdit->SetXYOffsetData(X1, Y1, X2, Y2, Data, 0); // XY Offset always need to be update before the height update
|
|
LandscapeEdit->SetHeightData(X1, Y1, X2, Y2, NewHeights.GetData(), 0, bUpdateNormals);
|
|
|
|
// Snap foliage for each component.
|
|
for (int32 Index = 0; Index < CollisionComponents.Num(); ++Index)
|
|
{
|
|
ULandscapeHeightfieldCollisionComponent* CollisionComponent = CollisionComponents[Index];
|
|
CollisionComponent->SnapFoliageInstances(PreUpdateLocalBoxes[Index].TransformBy(LandscapeInfo->GetLandscapeProxy()->LandscapeActorToWorld().ToMatrixWithScale()).ExpandBy(1.0f));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No foliage, just update landscape.
|
|
LandscapeEdit->SetXYOffsetData(X1, Y1, X2, Y2, Data, 0); // XY Offset always need to be update before the height update
|
|
LandscapeEdit->SetHeightData(X1, Y1, X2, Y2, NewHeights.GetData(), 0, bUpdateNormals);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Flush()
|
|
{
|
|
LandscapeEdit->Flush();
|
|
}
|
|
|
|
virtual ~FXYOffsetmapAccessor()
|
|
{
|
|
delete LandscapeEdit;
|
|
LandscapeEdit = NULL;
|
|
|
|
// Update the bounds for the components we edited
|
|
for (TSet<ULandscapeComponent*>::TConstIterator It(ChangedComponents); It; ++It)
|
|
{
|
|
(*It)->UpdateCachedBounds();
|
|
(*It)->UpdateComponentToWorld();
|
|
}
|
|
}
|
|
|
|
private:
|
|
ULandscapeInfo* LandscapeInfo;
|
|
FLandscapeEditDataInterface* LandscapeEdit;
|
|
TSet<ULandscapeComponent*> ChangedComponents;
|
|
};
|
|
|
|
template<bool bInUseInterp>
|
|
struct FLandscapeXYOffsetCache : public TLandscapeEditCache<FXYOffsetmapAccessor<bInUseInterp>, FVector>
|
|
{
|
|
FLandscapeXYOffsetCache(const FLandscapeToolTarget& InTarget)
|
|
: TLandscapeEditCache<FXYOffsetmapAccessor<bInUseInterp>, FVector>(InTarget)
|
|
{
|
|
}
|
|
};
|
|
|
|
template<bool bInUseInterp, bool bInUseTotalNormalize>
|
|
struct FAlphamapAccessorTool : public FAlphamapAccessor<bInUseInterp, bInUseTotalNormalize>
|
|
{
|
|
FAlphamapAccessorTool(ULandscapeInfo* InLandscapeInfo, ULandscapeLayerInfoObject* InLayerInfo)
|
|
: FAlphamapAccessor<bInUseInterp, bInUseTotalNormalize>(InLandscapeInfo, InLayerInfo)
|
|
{}
|
|
|
|
FAlphamapAccessorTool(const FLandscapeToolTarget& InTarget)
|
|
: FAlphamapAccessor<bInUseInterp, bInUseTotalNormalize>(InTarget.LandscapeInfo.Get(), InTarget.LayerInfo.Get())
|
|
{
|
|
}
|
|
};
|
|
|
|
struct FLandscapeAlphaCache : public TLandscapeEditCache<FAlphamapAccessorTool<true, false>, uint8>
|
|
{
|
|
static uint8 ClampValue(int32 Value) { return FMath::Clamp(Value, 0, 255); }
|
|
|
|
FLandscapeAlphaCache(const FLandscapeToolTarget& InTarget)
|
|
: TLandscapeEditCache<FAlphamapAccessorTool<true, false>, uint8>(InTarget)
|
|
{
|
|
}
|
|
};
|
|
|
|
struct FVisibilityAccessor : public FAlphamapAccessorTool<false, false>
|
|
{
|
|
FVisibilityAccessor(const FLandscapeToolTarget& InTarget)
|
|
: FAlphamapAccessorTool<false, false>(InTarget.LandscapeInfo.Get(), ALandscapeProxy::VisibilityLayer)
|
|
{
|
|
}
|
|
};
|
|
|
|
struct FLandscapeVisCache : public TLandscapeEditCache<FAlphamapAccessorTool<false, false>, uint8>
|
|
{
|
|
static uint8 ClampValue(int32 Value) { return FMath::Clamp(Value, 0, 255); }
|
|
|
|
FLandscapeVisCache(const FLandscapeToolTarget& InTarget)
|
|
: TLandscapeEditCache<FAlphamapAccessorTool<false, false>, uint8>(InTarget)
|
|
{
|
|
}
|
|
};
|
|
|
|
|
|
template<class ToolTarget>
|
|
class FLandscapeLayerDataCache
|
|
{
|
|
public:
|
|
FLandscapeLayerDataCache(const FLandscapeToolTarget& InTarget, typename ToolTarget::CacheClass& Cache)
|
|
: LandscapeInfo(nullptr)
|
|
, Landscape(nullptr)
|
|
, EditingLayerIndex(MAX_uint8)
|
|
, bIsInitialized(false)
|
|
, bCombinedLayerOperation(false)
|
|
, bVisibilityChanged(false)
|
|
, bTargetIsHeightmap(InTarget.TargetType == ELandscapeToolTargetType::Heightmap)
|
|
, CacheUpToEditingLayer(Cache)
|
|
, CacheBottomLayers(InTarget)
|
|
{
|
|
}
|
|
|
|
void Initialize(ULandscapeInfo* InLandscapeInfo, bool InCombinedLayerOperation)
|
|
{
|
|
if (!bIsInitialized)
|
|
{
|
|
LandscapeInfo = InLandscapeInfo;
|
|
Landscape = LandscapeInfo ? LandscapeInfo->LandscapeActor.Get() : nullptr;
|
|
bCombinedLayerOperation = Landscape && Landscape->HasLayersContent() && InCombinedLayerOperation && bTargetIsHeightmap;
|
|
if (bCombinedLayerOperation)
|
|
{
|
|
EditingLayerGuid = Landscape->GetEditingLayer();
|
|
for (int i = 0; i < Landscape->GetLayerCount(); ++i)
|
|
{
|
|
FLandscapeLayer* CurrentLayer = Landscape->GetLayer(i);
|
|
BackupLayerVisibility.Add(CurrentLayer->bVisible);
|
|
if (CurrentLayer->Guid == EditingLayerGuid)
|
|
{
|
|
EditingLayerIndex = i;
|
|
}
|
|
}
|
|
check(EditingLayerIndex < Landscape->GetLayerCount());
|
|
}
|
|
bIsInitialized = true;
|
|
}
|
|
}
|
|
|
|
void Read(int32 X1, int32 Y1, int32 X2, int32 Y2, TArray<typename ToolTarget::CacheClass::DataType>& Data)
|
|
{
|
|
check(bIsInitialized);
|
|
if (bCombinedLayerOperation)
|
|
{
|
|
TArray<bool> NewLayerVisibility;
|
|
for (int i = 0; i < Landscape->GetLayerCount(); ++i)
|
|
{
|
|
FLandscapeLayer* CurrentLayer = Landscape->GetLayer(i);
|
|
NewLayerVisibility.Add((i > EditingLayerIndex) ? false : CurrentLayer->bVisible);
|
|
}
|
|
|
|
FIntRect Bounds;
|
|
const float BoundStep = LandscapeInfo->ComponentSizeQuads / 4;
|
|
Bounds.Min.X = (FMath::FloorToInt((float)X1 / BoundStep) - 1) * BoundStep;
|
|
Bounds.Min.Y = (FMath::FloorToInt((float)Y1 / BoundStep) - 1) * BoundStep;
|
|
Bounds.Max.X = (FMath::CeilToInt((float)X2 / BoundStep) + 1) * BoundStep;
|
|
Bounds.Max.Y = (FMath::CeilToInt((float)Y2 / BoundStep) + 1) * BoundStep;
|
|
|
|
FScopedSetLandscapeEditingLayer Scope(Landscape, FGuid());
|
|
CacheUpToEditingLayer.GetDataAndCache(X1, Y1, X2, Y2, Data, [&]() -> FIntRect
|
|
{
|
|
TSet<ULandscapeComponent*> AffectedComponents;
|
|
LandscapeInfo->GetComponentsInRegion(Bounds.Min.X, Bounds.Min.Y, Bounds.Max.X, Bounds.Max.Y, AffectedComponents);
|
|
SynchronousUpdateComponentVisibilityForHeight(AffectedComponents, NewLayerVisibility);
|
|
if (bVisibilityChanged)
|
|
{
|
|
VisibilityChangedComponents.Append(AffectedComponents);
|
|
}
|
|
return Bounds;
|
|
});
|
|
// Release Texture Mips that will be Locked by the next SynchronousUpdateComponentVisibilityForHeight
|
|
CacheUpToEditingLayer.DataAccess.Flush();
|
|
|
|
CacheBottomLayers.GetDataAndCache(X1, Y1, X2, Y2, BottomLayersData, [&]() -> FIntRect
|
|
{
|
|
NewLayerVisibility[EditingLayerIndex] = false;
|
|
TSet<ULandscapeComponent*> AffectedComponents;
|
|
LandscapeInfo->GetComponentsInRegion(Bounds.Min.X, Bounds.Min.Y, Bounds.Max.X, Bounds.Max.Y, AffectedComponents);
|
|
SynchronousUpdateComponentVisibilityForHeight(AffectedComponents, NewLayerVisibility);
|
|
if (bVisibilityChanged)
|
|
{
|
|
VisibilityChangedComponents.Append(AffectedComponents);
|
|
}
|
|
return Bounds;
|
|
});
|
|
// Do the same here for consistency
|
|
CacheBottomLayers.DataAccess.Flush();
|
|
}
|
|
else
|
|
{
|
|
CacheUpToEditingLayer.CacheData(X1, Y1, X2, Y2);
|
|
CacheUpToEditingLayer.GetCachedData(X1, Y1, X2, Y2, Data);
|
|
}
|
|
}
|
|
|
|
void Write(int32 X1, int32 Y1, int32 X2, int32 Y2, TArray<typename ToolTarget::CacheClass::DataType>& Data, ELandscapeLayerPaintingRestriction PaintingRestriction = ELandscapeLayerPaintingRestriction::None)
|
|
{
|
|
check(bIsInitialized);
|
|
if (bCombinedLayerOperation)
|
|
{
|
|
const float Alpha = Landscape->GetLayerAlpha(EditingLayerIndex, bTargetIsHeightmap);
|
|
const float InverseAlpha = (Alpha != 0.f) ? 1.f / Alpha : 1.f;
|
|
TArray<typename ToolTarget::CacheClass::DataType> DataContribution;
|
|
DataContribution.Empty(Data.Num());
|
|
DataContribution.AddUninitialized(Data.Num());
|
|
check(Data.Num() == BottomLayersData.Num());
|
|
for (int i = 0; i < Data.Num(); ++i)
|
|
{
|
|
float Contribution = (LandscapeDataAccess::GetLocalHeight(Data[i]) - LandscapeDataAccess::GetLocalHeight(BottomLayersData[i])) * InverseAlpha;
|
|
DataContribution[i] = LandscapeDataAccess::GetTexHeight(Contribution);
|
|
}
|
|
checkf(EditingLayerGuid == Landscape->GetEditingLayer(), TEXT("EditingLayer has changed between Initialize and Write. Was: %s (%s). Is now: %s (%s)"),
|
|
Landscape->GetLayer(EditingLayerGuid) ? *(Landscape->GetLayer(EditingLayerGuid)->Name.ToString()) : TEXT("<unknown>"), *EditingLayerGuid.ToString(),
|
|
Landscape->GetLayer(Landscape->GetEditingLayer()) ? *(Landscape->GetLayer(Landscape->GetEditingLayer())->Name.ToString()) : TEXT("<unknown>"), *Landscape->GetEditingLayer().ToString());
|
|
|
|
// Restore layers visibility
|
|
SetLayersVisibility(BackupLayerVisibility);
|
|
// Only store data in cache
|
|
CacheUpToEditingLayer.SetCachedData(X1, Y1, X2, Y2, Data, PaintingRestriction, false);
|
|
// Effectively write the contribution
|
|
CacheUpToEditingLayer.DataAccess.SetData(X1, Y1, X2, Y2, DataContribution.GetData(), PaintingRestriction);
|
|
CacheUpToEditingLayer.DataAccess.Flush();
|
|
if (bVisibilityChanged)
|
|
{
|
|
const bool bUpdateCollision = true;
|
|
const bool bIntermediateRender = false;
|
|
SynchronousUpdateHeightmapForComponents(VisibilityChangedComponents, bUpdateCollision, bIntermediateRender);
|
|
VisibilityChangedComponents.Empty();
|
|
bVisibilityChanged = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CacheUpToEditingLayer.SetCachedData(X1, Y1, X2, Y2, Data, PaintingRestriction);
|
|
CacheUpToEditingLayer.Flush();
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
void SynchronousUpdateHeightmapForComponents(const TSet<ULandscapeComponent*>& InComponents, bool bUpdateCollision, bool bIntermediateRender)
|
|
{
|
|
for (ULandscapeComponent* Component : InComponents)
|
|
{
|
|
const bool bUpdateAll = false; // default value
|
|
Component->RequestHeightmapUpdate(bUpdateAll, bUpdateCollision);
|
|
}
|
|
Landscape->ForceUpdateLayersContent(bIntermediateRender);
|
|
};
|
|
|
|
void SetLayersVisibility(const TArray<bool>& InLayerVisibility)
|
|
{
|
|
check(InLayerVisibility.Num() == Landscape->GetLayerCount());
|
|
for (int i = 0; i < InLayerVisibility.Num(); ++i)
|
|
{
|
|
if (FLandscapeLayer* Layer = Landscape->GetLayer(i))
|
|
{
|
|
if (Layer->bVisible != InLayerVisibility[i])
|
|
{
|
|
Layer->bVisible = InLayerVisibility[i];
|
|
bVisibilityChanged = true;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void SynchronousUpdateComponentVisibilityForHeight(const TSet<ULandscapeComponent*>& InComponents, const TArray<bool>& InLayerVisibility)
|
|
{
|
|
SetLayersVisibility(InLayerVisibility);
|
|
// No need to update collision here as we are only doing a intermediate render to gather heightdata
|
|
const bool bUpdateCollision = false;
|
|
const bool bIntermediateRender = true;
|
|
SynchronousUpdateHeightmapForComponents(InComponents, bUpdateCollision, bIntermediateRender);
|
|
};
|
|
|
|
ULandscapeInfo* LandscapeInfo;
|
|
ALandscape* Landscape;
|
|
FGuid EditingLayerGuid;
|
|
uint8 EditingLayerIndex;
|
|
TArray<bool> BackupLayerVisibility;
|
|
TArray<typename ToolTarget::CacheClass::DataType> BottomLayersData;
|
|
bool bIsInitialized;
|
|
bool bCombinedLayerOperation;
|
|
bool bVisibilityChanged;
|
|
bool bTargetIsHeightmap;
|
|
TSet<ULandscapeComponent*> VisibilityChangedComponents;
|
|
|
|
typename ToolTarget::CacheClass& CacheUpToEditingLayer;
|
|
typename ToolTarget::CacheClass CacheBottomLayers;
|
|
};
|
|
|
|
//
|
|
// FFullWeightmapAccessor
|
|
//
|
|
template<bool bInUseInterp>
|
|
struct FFullWeightmapAccessor
|
|
{
|
|
enum { bUseInterp = bInUseInterp };
|
|
FFullWeightmapAccessor(ULandscapeInfo* InLandscapeInfo)
|
|
: LandscapeInfo(InLandscapeInfo)
|
|
, LandscapeEdit(InLandscapeInfo)
|
|
{
|
|
}
|
|
|
|
FFullWeightmapAccessor(const FLandscapeToolTarget& InTarget)
|
|
: FFullWeightmapAccessor(InTarget.LandscapeInfo.Get())
|
|
{
|
|
}
|
|
|
|
~FFullWeightmapAccessor()
|
|
{
|
|
if (!LandscapeEdit.HasLandscapeLayersContent())
|
|
{
|
|
// Recreate collision for modified components to update the physical materials
|
|
for (ULandscapeComponent* Component : ModifiedComponents)
|
|
{
|
|
ULandscapeHeightfieldCollisionComponent* CollisionComponent = Component->CollisionComponent.Get();
|
|
if (CollisionComponent)
|
|
{
|
|
CollisionComponent->RecreateCollision();
|
|
|
|
// We need to trigger navigation mesh build, in case user have painted holes on a landscape
|
|
if (LandscapeInfo->GetLayerInfoIndex(ALandscapeProxy::VisibilityLayer) != INDEX_NONE)
|
|
{
|
|
FNavigationSystem::UpdateComponentData(*CollisionComponent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GetData(int32& X1, int32& Y1, int32& X2, int32& Y2, TMap<FIntPoint, TArray<uint8>>& Data)
|
|
{
|
|
// Do not Support for interpolation....
|
|
check(false && TEXT("Do not support interpolation for FullWeightmapAccessor for now"));
|
|
}
|
|
|
|
void GetDataFast(int32 X1, int32 Y1, int32 X2, int32 Y2, TMap<FIntPoint, TArray<uint8>>& Data)
|
|
{
|
|
DirtyLayerInfos.Empty();
|
|
LandscapeEdit.GetWeightDataFast(NULL, X1, Y1, X2, Y2, Data);
|
|
}
|
|
|
|
void SetData(int32 X1, int32 Y1, int32 X2, int32 Y2, const uint8* Data, ELandscapeLayerPaintingRestriction PaintingRestriction)
|
|
{
|
|
TSet<ULandscapeComponent*> Components;
|
|
if (LandscapeEdit.GetComponentsInRegion(X1, Y1, X2, Y2, &Components))
|
|
{
|
|
if (!LandscapeEdit.HasLandscapeLayersContent())
|
|
{
|
|
ALandscapeProxy::InvalidateGeneratedComponentData(Components);
|
|
ModifiedComponents.Append(Components);
|
|
}
|
|
LandscapeEdit.SetAlphaData(DirtyLayerInfos, X1, Y1, X2, Y2, Data, 0, PaintingRestriction);
|
|
}
|
|
DirtyLayerInfos.Empty();
|
|
}
|
|
|
|
void Flush()
|
|
{
|
|
LandscapeEdit.Flush();
|
|
}
|
|
|
|
TSet<ULandscapeLayerInfoObject*> DirtyLayerInfos;
|
|
|
|
private:
|
|
ULandscapeInfo* LandscapeInfo;
|
|
FLandscapeEditDataInterface LandscapeEdit;
|
|
TSet<ULandscapeComponent*> ModifiedComponents;
|
|
};
|
|
|
|
struct FLandscapeFullWeightCache : public TLandscapeEditCache<FFullWeightmapAccessor<false>, TArray<uint8>>
|
|
{
|
|
FLandscapeFullWeightCache(const FLandscapeToolTarget& InTarget)
|
|
: TLandscapeEditCache<FFullWeightmapAccessor<false>, TArray<uint8>>(InTarget)
|
|
{
|
|
}
|
|
|
|
// Only for all weight case... the accessor type should be TArray<uint8>
|
|
void GetCachedData(int32 X1, int32 Y1, int32 X2, int32 Y2, TArray<uint8>& OutData, int32 ArraySize)
|
|
{
|
|
if (ArraySize == 0)
|
|
{
|
|
OutData.Empty();
|
|
return;
|
|
}
|
|
|
|
const int32 XSize = (1 + X2 - X1);
|
|
const int32 YSize = (1 + Y2 - Y1);
|
|
const int32 Stride = XSize * ArraySize;
|
|
int32 NumSamples = XSize * YSize * ArraySize;
|
|
OutData.Empty(NumSamples);
|
|
OutData.AddUninitialized(NumSamples);
|
|
|
|
for (int32 Y = Y1; Y <= Y2; Y++)
|
|
{
|
|
const int32 YOffset = (Y - Y1) * Stride;
|
|
for (int32 X = X1; X <= X2; X++)
|
|
{
|
|
const int32 XYOffset = YOffset + (X - X1) * ArraySize;
|
|
TArray<uint8>* Ptr = GetValueRef(X, Y);
|
|
if (Ptr)
|
|
{
|
|
for (int32 Z = 0; Z < ArraySize; Z++)
|
|
{
|
|
OutData[XYOffset + Z] = (*Ptr)[Z];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FMemory::Memzero((void*)&OutData[XYOffset], (SIZE_T)ArraySize);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only for all weight case... the accessor type should be TArray<uint8>
|
|
void SetCachedData(int32 X1, int32 Y1, int32 X2, int32 Y2, TArray<uint8>& Data, int32 ArraySize, ELandscapeLayerPaintingRestriction PaintingRestriction)
|
|
{
|
|
// Update cache
|
|
for (int32 Y = Y1; Y <= Y2; Y++)
|
|
{
|
|
for (int32 X = X1; X <= X2; X++)
|
|
{
|
|
TArray<uint8> Value;
|
|
Value.Empty(ArraySize);
|
|
Value.AddUninitialized(ArraySize);
|
|
for (int32 Z = 0; Z < ArraySize; Z++)
|
|
{
|
|
Value[Z] = Data[((X - X1) + (Y - Y1)*(1 + X2 - X1)) * ArraySize + Z];
|
|
}
|
|
SetValue(X, Y, MoveTemp(Value));
|
|
}
|
|
}
|
|
|
|
// Update real data
|
|
DataAccess.SetData(X1, Y1, X2, Y2, Data.GetData(), PaintingRestriction);
|
|
}
|
|
|
|
void AddDirtyLayer(ULandscapeLayerInfoObject* LayerInfo)
|
|
{
|
|
DataAccess.DirtyLayerInfos.Add(LayerInfo);
|
|
}
|
|
};
|
|
|
|
//
|
|
// FDatamapAccessor
|
|
//
|
|
template<bool bInUseInterp>
|
|
struct FDatamapAccessor
|
|
{
|
|
enum { bUseInterp = bInUseInterp };
|
|
FDatamapAccessor(ULandscapeInfo* InLandscapeInfo)
|
|
: LandscapeEdit(InLandscapeInfo)
|
|
{
|
|
}
|
|
|
|
FDatamapAccessor(const FLandscapeToolTarget& InTarget)
|
|
: FDatamapAccessor(InTarget.LandscapeInfo.Get())
|
|
{
|
|
}
|
|
|
|
void GetData(int32& X1, int32& Y1, int32& X2, int32& Y2, TMap<FIntPoint, uint8>& Data)
|
|
{
|
|
LandscapeEdit.GetSelectData(X1, Y1, X2, Y2, Data);
|
|
}
|
|
|
|
void GetDataFast(const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, TMap<FIntPoint, uint8>& Data)
|
|
{
|
|
LandscapeEdit.GetSelectData(X1, Y1, X2, Y2, Data);
|
|
}
|
|
|
|
void SetData(int32 X1, int32 Y1, int32 X2, int32 Y2, const uint8* Data, ELandscapeLayerPaintingRestriction PaintingRestriction = ELandscapeLayerPaintingRestriction::None)
|
|
{
|
|
if (LandscapeEdit.GetComponentsInRegion(X1, Y1, X2, Y2))
|
|
{
|
|
LandscapeEdit.SetSelectData(X1, Y1, X2, Y2, Data, 0);
|
|
}
|
|
}
|
|
|
|
void Flush()
|
|
{
|
|
LandscapeEdit.Flush();
|
|
}
|
|
|
|
private:
|
|
FLandscapeEditDataInterface LandscapeEdit;
|
|
};
|
|
|
|
struct FLandscapeDataCache : public TLandscapeEditCache<FDatamapAccessor<false>, uint8>
|
|
{
|
|
static uint8 ClampValue(int32 Value) { return FMath::Clamp(Value, 0, 255); }
|
|
|
|
FLandscapeDataCache(const FLandscapeToolTarget& InTarget)
|
|
: TLandscapeEditCache<FDatamapAccessor<false>, uint8>(InTarget)
|
|
{
|
|
}
|
|
};
|
|
|
|
|
|
//
|
|
// Tool targets
|
|
//
|
|
struct FHeightmapToolTarget
|
|
{
|
|
typedef FLandscapeHeightCache CacheClass;
|
|
static const ELandscapeToolTargetType::Type TargetType = ELandscapeToolTargetType::Heightmap;
|
|
|
|
static float StrengthMultiplier(ULandscapeInfo* LandscapeInfo, float BrushRadius)
|
|
{
|
|
if (LandscapeInfo)
|
|
{
|
|
// Adjust strength based on brush size and drawscale, so strength 1 = one hemisphere
|
|
return BrushRadius * LANDSCAPE_INV_ZSCALE / (LandscapeInfo->DrawScale.Z);
|
|
}
|
|
return 5.0f * LANDSCAPE_INV_ZSCALE;
|
|
}
|
|
|
|
static FMatrix ToWorldMatrix(ULandscapeInfo* LandscapeInfo)
|
|
{
|
|
FMatrix Result = FTranslationMatrix(FVector(0, 0, -32768.0f));
|
|
Result *= FScaleMatrix(FVector(1.0f, 1.0f, LANDSCAPE_ZSCALE) * LandscapeInfo->DrawScale);
|
|
return Result;
|
|
}
|
|
|
|
static FMatrix FromWorldMatrix(ULandscapeInfo* LandscapeInfo)
|
|
{
|
|
FMatrix Result = FScaleMatrix(FVector(1.0f, 1.0f, LANDSCAPE_INV_ZSCALE) / (LandscapeInfo->DrawScale));
|
|
Result *= FTranslationMatrix(FVector(0, 0, 32768.0f));
|
|
return Result;
|
|
}
|
|
};
|
|
|
|
|
|
struct FWeightmapToolTarget
|
|
{
|
|
typedef FLandscapeAlphaCache CacheClass;
|
|
static const ELandscapeToolTargetType::Type TargetType = ELandscapeToolTargetType::Weightmap;
|
|
|
|
static float StrengthMultiplier(ULandscapeInfo* LandscapeInfo, float BrushRadius)
|
|
{
|
|
return 255.0f;
|
|
}
|
|
|
|
static FMatrix ToWorldMatrix(ULandscapeInfo* LandscapeInfo) { return FMatrix::Identity; }
|
|
static FMatrix FromWorldMatrix(ULandscapeInfo* LandscapeInfo) { return FMatrix::Identity; }
|
|
};
|
|
|
|
/**
|
|
* FLandscapeToolStrokeBase - base class for tool strokes (used by FLandscapeToolBase)
|
|
*/
|
|
|
|
class FLandscapeToolStrokeBase : protected FGCObject
|
|
{
|
|
public:
|
|
// Whether to call Apply() every frame even if the mouse hasn't moved
|
|
enum { UseContinuousApply = false };
|
|
|
|
// This is also the expected signature of derived class constructor used by FLandscapeToolBase
|
|
FLandscapeToolStrokeBase(FEdModeLandscape* InEdMode, FEditorViewportClient* InViewportClient, const FLandscapeToolTarget& InTarget)
|
|
: EdMode(InEdMode)
|
|
, Target(InTarget)
|
|
, LandscapeInfo(InTarget.LandscapeInfo.Get())
|
|
{
|
|
}
|
|
|
|
// Signature of Apply() method for derived strokes
|
|
// void Apply(FEditorViewportClient* ViewportClient, FLandscapeBrush* Brush, const ULandscapeEditorObject* UISettings, const TArray<FLandscapeToolMousePosition>& MousePositions);
|
|
|
|
virtual void AddReferencedObjects(FReferenceCollector& Collector) override
|
|
{
|
|
Collector.AddReferencedObject(LandscapeInfo);
|
|
}
|
|
virtual FString GetReferencerName() const override
|
|
{
|
|
return TEXT("FLandscapeToolStrokeBase");
|
|
}
|
|
|
|
protected:
|
|
FEdModeLandscape* EdMode;
|
|
const FLandscapeToolTarget& Target;
|
|
ULandscapeInfo* LandscapeInfo;
|
|
};
|
|
|
|
|
|
/**
|
|
* FLandscapeToolBase - base class for painting tools
|
|
* ToolTarget - the target for the tool (weight or heightmap)
|
|
* StrokeClass - the class that implements the behavior for a mouse stroke applying the tool.
|
|
*/
|
|
template<class TStrokeClass>
|
|
class FLandscapeToolBase : public FLandscapeTool
|
|
{
|
|
public:
|
|
FLandscapeToolBase(FEdModeLandscape* InEdMode)
|
|
: LastInteractorPosition(FVector2D::ZeroVector)
|
|
, TimeSinceLastInteractorMove(0.0f)
|
|
, EdMode(InEdMode)
|
|
, bCanToolBeActivated(true)
|
|
, ToolStroke()
|
|
{
|
|
}
|
|
|
|
virtual bool ShouldUpdateEditingLayer() const { return EdMode->CanHaveLandscapeLayersContent(); }
|
|
|
|
virtual ELandscapeLayerUpdateMode GetBeginToolContentUpdateFlag() const
|
|
{
|
|
bool bUpdateHeightmap = this->EdMode->CurrentToolTarget.TargetType == ELandscapeToolTargetType::Type::Heightmap;
|
|
return bUpdateHeightmap ? ELandscapeLayerUpdateMode::Update_Heightmap_Editing : ELandscapeLayerUpdateMode::Update_Weightmap_Editing;
|
|
}
|
|
|
|
virtual ELandscapeLayerUpdateMode GetTickToolContentUpdateFlag() const
|
|
{
|
|
return GetBeginToolContentUpdateFlag();
|
|
}
|
|
|
|
virtual ELandscapeLayerUpdateMode GetEndToolContentUpdateFlag() const
|
|
{
|
|
bool bUpdateHeightmap = this->EdMode->CurrentToolTarget.TargetType == ELandscapeToolTargetType::Type::Heightmap;
|
|
return bUpdateHeightmap ? ELandscapeLayerUpdateMode::Update_Heightmap_All : ELandscapeLayerUpdateMode::Update_Weightmap_All;
|
|
}
|
|
|
|
virtual bool BeginTool(FEditorViewportClient* ViewportClient, const FLandscapeToolTarget& InTarget, const FVector& InHitLocation) override
|
|
{
|
|
if (ShouldUpdateEditingLayer())
|
|
{
|
|
ALandscape* Landscape = this->EdMode->GetLandscape();
|
|
if (Landscape)
|
|
{
|
|
Landscape->RequestLayersContentUpdate(GetBeginToolContentUpdateFlag());
|
|
Landscape->SetEditingLayer(this->EdMode->GetCurrentLayerGuid());
|
|
Landscape->SetGrassUpdateEnabled(false);
|
|
}
|
|
}
|
|
|
|
if (!ensure(InteractorPositions.Num() == 0))
|
|
{
|
|
InteractorPositions.Empty(1);
|
|
}
|
|
|
|
if( !IsToolActive() )
|
|
{
|
|
ToolStroke.Emplace( EdMode, ViewportClient, InTarget );
|
|
EdMode->CurrentBrush->BeginStroke( InHitLocation.X, InHitLocation.Y, this );
|
|
}
|
|
|
|
// Save the mouse position
|
|
LastInteractorPosition = FVector2D(InHitLocation);
|
|
InteractorPositions.Emplace(LastInteractorPosition, ViewportClient ? IsModifierPressed(ViewportClient) : false); // Copy tool sometimes activates without a specific viewport via ctrl+c hotkey
|
|
TimeSinceLastInteractorMove = 0.0f;
|
|
|
|
ToolStroke->Apply(ViewportClient, EdMode->CurrentBrush, EdMode->UISettings, InteractorPositions);
|
|
|
|
InteractorPositions.Empty(1);
|
|
return true;
|
|
}
|
|
|
|
virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) override
|
|
{
|
|
if (IsToolActive())
|
|
{
|
|
if (InteractorPositions.Num() > 0)
|
|
{
|
|
ToolStroke->Apply(ViewportClient, EdMode->CurrentBrush, EdMode->UISettings, InteractorPositions);
|
|
ViewportClient->Invalidate(false, false);
|
|
InteractorPositions.Empty(1);
|
|
}
|
|
else if (TStrokeClass::UseContinuousApply && TimeSinceLastInteractorMove >= 0.25f)
|
|
{
|
|
InteractorPositions.Emplace(LastInteractorPosition, IsModifierPressed(ViewportClient));
|
|
ToolStroke->Apply(ViewportClient, EdMode->CurrentBrush, EdMode->UISettings, InteractorPositions);
|
|
ViewportClient->Invalidate(false, false);
|
|
InteractorPositions.Empty(1);
|
|
}
|
|
TimeSinceLastInteractorMove += DeltaTime;
|
|
|
|
// Prevent landscape from baking textures while tool stroke is active
|
|
EdMode->CurrentToolTarget.LandscapeInfo->PostponeTextureBaking();
|
|
|
|
if (ShouldUpdateEditingLayer())
|
|
{
|
|
ALandscape* Landscape = this->EdMode->CurrentToolTarget.LandscapeInfo->LandscapeActor.Get();
|
|
if (Landscape != nullptr)
|
|
{
|
|
Landscape->RequestLayersContentUpdate(GetTickToolContentUpdateFlag());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual void EndTool(FEditorViewportClient* ViewportClient) override
|
|
{
|
|
if (IsToolActive() && InteractorPositions.Num())
|
|
{
|
|
ToolStroke->Apply(ViewportClient, EdMode->CurrentBrush, EdMode->UISettings, InteractorPositions);
|
|
InteractorPositions.Empty(1);
|
|
}
|
|
|
|
ToolStroke.Reset();
|
|
EdMode->CurrentBrush->EndStroke();
|
|
EdMode->UpdateLayerUsageInformation(&EdMode->CurrentToolTarget.LayerInfo);
|
|
|
|
if (ShouldUpdateEditingLayer())
|
|
{
|
|
ALandscape* Landscape = this->EdMode->GetLandscape();
|
|
if (Landscape)
|
|
{
|
|
Landscape->RequestLayersContentUpdate(GetEndToolContentUpdateFlag());
|
|
Landscape->SetEditingLayer();
|
|
Landscape->SetGrassUpdateEnabled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual bool MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) override
|
|
{
|
|
if (ViewportClient != nullptr && Viewport != nullptr)
|
|
{
|
|
FVector HitLocation;
|
|
if (EdMode->LandscapeMouseTrace(ViewportClient, x, y, HitLocation))
|
|
{
|
|
// If we are moving the mouse to adjust the brush size, don't move the brush
|
|
if (EdMode->CurrentBrush && !EdMode->IsAdjustingBrush(Viewport))
|
|
{
|
|
// Inform the brush of the current location, to update the cursor
|
|
EdMode->CurrentBrush->MouseMove(HitLocation.X, HitLocation.Y);
|
|
}
|
|
|
|
if (IsToolActive())
|
|
{
|
|
// Save the interactor position
|
|
if (InteractorPositions.Num() == 0 || LastInteractorPosition != FVector2D(HitLocation))
|
|
{
|
|
LastInteractorPosition = FVector2D(HitLocation);
|
|
InteractorPositions.Emplace(LastInteractorPosition, IsModifierPressed(ViewportClient));
|
|
}
|
|
TimeSinceLastInteractorMove = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const FVector2D NewPosition(x, y);
|
|
if (InteractorPositions.Num() == 0 || LastInteractorPosition != FVector2D(NewPosition))
|
|
{
|
|
LastInteractorPosition = FVector2D(NewPosition);
|
|
InteractorPositions.Emplace(LastInteractorPosition, IsModifierPressed());
|
|
}
|
|
TimeSinceLastInteractorMove = 0.0f;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual bool IsToolActive() const override { return ToolStroke.IsSet(); }
|
|
|
|
virtual void SetCanToolBeActivated(bool Value) { bCanToolBeActivated = Value; }
|
|
virtual bool CanToolBeActivated() const { return bCanToolBeActivated; }
|
|
|
|
protected:
|
|
TArray<FLandscapeToolInteractorPosition> InteractorPositions;
|
|
FVector2D LastInteractorPosition;
|
|
float TimeSinceLastInteractorMove;
|
|
FEdModeLandscape* EdMode;
|
|
bool bCanToolBeActivated;
|
|
TOptional<TStrokeClass> ToolStroke;
|
|
|
|
bool IsModifierPressed(const class FEditorViewportClient* ViewportClient = nullptr)
|
|
{
|
|
UE_LOG(LogLandscapeTools, VeryVerbose, TEXT("ViewportClient = %d, IsShiftDown = %d"), (ViewportClient != nullptr), (ViewportClient != nullptr && IsShiftDown(ViewportClient->Viewport)));
|
|
return ViewportClient != nullptr && IsShiftDown(ViewportClient->Viewport);
|
|
}
|
|
};
|
|
|
|
struct FToolFlattenCustomData
|
|
{
|
|
FToolFlattenCustomData()
|
|
: ActiveEyeDropperMode(false)
|
|
, EyeDropperModeHeight(0.0f)
|
|
{}
|
|
|
|
bool ActiveEyeDropperMode;
|
|
float EyeDropperModeHeight;
|
|
}; |