Files
UnrealEngineUWP/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeErosionTools.cpp
Gareth Martin d9e53f7b12 Optimized landscape editing significantly by removing the TMap of brush points and replacing it with a 2D array of data (FLandscapeBrushData class)
Most code accessing brush/landscape data by calculating indexes (e.g. Data + (Y-Y1)*XSize + (X-X1)) replaced with scanline[X] style for speed
LandscapeInfo->SelectedRegion is now only queried if it contains anything, to avoid hashing the key unneccessarily (the hash function was showing on profiles :( )
Smoothing tool's SmoothFilterKernel*Scale* replaced by the more intuitive Size
Component brush fixed to be properly centered on the cursor at odd sizes and work correctly with the add component tool when cursor is over the landscape (but part of the brush is off the landscape) and size is >1
Copy tool fixed to copy the selected area and not a circle when using ctrl+c
Also a few style cleanups. for (auto it = xyz.CreateIterator...) replaced with ranged for, enums replaced with enum classes, 0.f -> 0.0f, NULL->nullptr, etc.

[CL 2392355 by Gareth Martin in Main branch]
2014-12-18 06:52:06 -05:00

523 lines
17 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "LandscapeEditorPrivatePCH.h"
#include "ObjectTools.h"
#include "LandscapeEdMode.h"
#include "ScopedTransaction.h"
#include "LandscapeEdit.h"
#include "LandscapeRender.h"
#include "LandscapeDataAccess.h"
#include "LandscapeSplineProxies.h"
#include "LandscapeEditorModule.h"
#include "Editor/PropertyEditor/Public/PropertyEditorModule.h"
#include "LandscapeEdModeTools.h"
#include "Landscape.h"
#include "LandscapeLayerInfoObject.h"
//
// FLandscapeToolErosionBase
//
class FLandscapeToolStrokeErosionBase : public FLandscapeToolStrokeBase
{
public:
FLandscapeToolStrokeErosionBase(FEdModeLandscape* InEdMode, const FLandscapeToolTarget& InTarget)
: LandscapeInfo(InTarget.LandscapeInfo.Get())
, HeightCache(InTarget)
, WeightCache(InTarget)
, bWeightApplied(InTarget.TargetType != ELandscapeToolTargetType::Heightmap)
{
}
protected:
ULandscapeInfo* LandscapeInfo;
FLandscapeHeightCache HeightCache;
FLandscapeFullWeightCache WeightCache;
bool bWeightApplied;
};
template<class TStrokeClass>
class FLandscapeToolErosionBase : public FLandscapeToolBase<TStrokeClass>
{
public:
FLandscapeToolErosionBase(FEdModeLandscape* InEdMode)
: FLandscapeToolBase<TStrokeClass>(InEdMode)
{
}
virtual ELandscapeToolTargetTypeMask::Type GetSupportedTargetTypes() override
{
return ELandscapeToolTargetTypeMask::Heightmap;
}
};
//
// FLandscapeToolErosion
//
class FLandscapeToolStrokeErosion : public FLandscapeToolStrokeErosionBase
{
public:
FLandscapeToolStrokeErosion(FEdModeLandscape* InEdMode, const FLandscapeToolTarget& InTarget)
: FLandscapeToolStrokeErosionBase(InEdMode, InTarget)
{
}
void Apply(FEditorViewportClient* ViewportClient, FLandscapeBrush* Brush, const ULandscapeEditorObject* UISettings, const TArray<FLandscapeToolMousePosition>& MousePositions)
{
if (!LandscapeInfo)
{
return;
}
// Get list of verts to update
FLandscapeBrushData BrushInfo = Brush->ApplyBrush(MousePositions);
if (!BrushInfo)
{
return;
}
int32 X1, Y1, X2, Y2;
BrushInfo.GetInclusiveBounds(X1, Y1, X2, Y2);
// Tablet pressure
float Pressure = ViewportClient->Viewport->IsPenActive() ? ViewportClient->Viewport->GetTabletPressure() : 1.0f;
// expand the area by one vertex in each direction to ensure normals are calculated correctly
X1 -= 1;
Y1 -= 1;
X2 += 1;
Y2 += 1;
const int32 NeighborNum = 4;
const int32 Iteration = UISettings->ErodeIterationNum;
const int32 Thickness = UISettings->ErodeSurfaceThickness;
const int32 LayerNum = LandscapeInfo->Layers.Num();
HeightCache.CacheData(X1, Y1, X2, Y2);
TArray<uint16> HeightData;
HeightCache.GetCachedData(X1, Y1, X2, Y2, HeightData);
TArray<uint8> WeightDatas; // Weight*Layers...
WeightCache.CacheData(X1, Y1, X2, Y2);
WeightCache.GetCachedData(X1, Y1, X2, Y2, WeightDatas, LayerNum);
// Apply the brush
uint16 Thresh = UISettings->ErodeThresh;
int32 WeightMoveThresh = FMath::Min<int32>(FMath::Max<int32>(Thickness >> 2, Thresh), Thickness >> 1);
TArray<float> CenterWeights;
CenterWeights.Empty(LayerNum);
CenterWeights.AddUninitialized(LayerNum);
TArray<float> NeighborWeight;
NeighborWeight.Empty(NeighborNum*LayerNum);
NeighborWeight.AddUninitialized(NeighborNum*LayerNum);
bool bHasChanged = false;
for (int32 i = 0; i < Iteration; i++)
{
bHasChanged = false;
for (int32 Y = BrushInfo.GetBounds().Min.Y; Y < BrushInfo.GetBounds().Max.Y; Y++)
{
const float* BrushScanline = BrushInfo.GetDataPtr(FIntPoint(0, Y));
for (int32 X = BrushInfo.GetBounds().Min.X; X < BrushInfo.GetBounds().Max.X; X++)
{
const float BrushValue = BrushScanline[X];
if (BrushValue > 0.0f)
{
int32 Center = (X - X1) + (Y - Y1)*(1 + X2 - X1);
int32 Neighbor[NeighborNum] = {
(X - 1 - X1) + (Y - Y1)*(1 + X2 - X1), // -X
(X + 1 - X1) + (Y - Y1)*(1 + X2 - X1), // +X
(X - X1) + (Y - 1 - Y1)*(1 + X2 - X1), // -Y
(X - X1) + (Y + 1 - Y1)*(1 + X2 - X1) }; // +Y
uint32 SlopeTotal = 0;
uint16 SlopeMax = Thresh;
for (int32 Idx = 0; Idx < NeighborNum; Idx++)
{
if (HeightData[Center] > HeightData[Neighbor[Idx]])
{
uint16 Slope = HeightData[Center] - HeightData[Neighbor[Idx]];
if (Slope * BrushValue > Thresh)
{
SlopeTotal += Slope;
if (SlopeMax < Slope)
{
SlopeMax = Slope;
}
}
}
}
if (SlopeTotal > 0)
{
float Softness = 1.0f;
{
for (int32 Idx = 0; Idx < LayerNum; Idx++)
{
ULandscapeLayerInfoObject* LayerInfo = LandscapeInfo->Layers[Idx].LayerInfoObj;
if (LayerInfo)
{
uint8 Weight = WeightDatas[Center*LayerNum + Idx];
Softness -= (float)(Weight) / 255.0f * LayerInfo->Hardness;
}
}
}
if (Softness > 0.0f)
{
//Softness = FMath::Clamp<float>(Softness, 0.0f, 1.0f);
float TotalHeightDiff = 0;
int32 WeightTransfer = FMath::Min<int32>(WeightMoveThresh, SlopeMax - Thresh);
for (int32 Idx = 0; Idx < NeighborNum; Idx++)
{
float TotalWeight = 0.0f;
if (HeightData[Center] > HeightData[Neighbor[Idx]])
{
uint16 Slope = HeightData[Center] - HeightData[Neighbor[Idx]];
if (Slope > Thresh)
{
float WeightDiff = Softness * UISettings->ToolStrength * Pressure * ((float)Slope / SlopeTotal) * BrushValue;
//uint16 HeightDiff = (uint16)((SlopeMax - Thresh) * WeightDiff);
float HeightDiff = (SlopeMax - Thresh) * WeightDiff;
HeightData[Neighbor[Idx]] += HeightDiff;
TotalHeightDiff += HeightDiff;
if (bWeightApplied)
{
for (int32 LayerIdx = 0; LayerIdx < LayerNum; LayerIdx++)
{
float CenterWeight = (float)(WeightDatas[Center*LayerNum + LayerIdx]) / 255.0f;
float Weight = (float)(WeightDatas[Neighbor[Idx] * LayerNum + LayerIdx]) / 255.0f;
NeighborWeight[Idx*LayerNum + LayerIdx] = Weight*(float)Thickness + CenterWeight*WeightDiff*WeightTransfer; // transferred + original...
TotalWeight += NeighborWeight[Idx*LayerNum + LayerIdx];
}
// Need to normalize weight...
for (int32 LayerIdx = 0; LayerIdx < LayerNum; LayerIdx++)
{
WeightDatas[Neighbor[Idx] * LayerNum + LayerIdx] = (uint8)(255.0f * NeighborWeight[Idx*LayerNum + LayerIdx] / TotalWeight);
}
}
}
}
}
HeightData[Center] -= TotalHeightDiff;
if (bWeightApplied)
{
float TotalWeight = 0.0f;
float WeightDiff = Softness * UISettings->ToolStrength * Pressure * BrushValue;
for (int32 LayerIdx = 0; LayerIdx < LayerNum; LayerIdx++)
{
float Weight = (float)(WeightDatas[Center*LayerNum + LayerIdx]) / 255.0f;
CenterWeights[LayerIdx] = Weight*Thickness - Weight*WeightDiff*WeightTransfer;
TotalWeight += CenterWeights[LayerIdx];
}
// Need to normalize weight...
for (int32 LayerIdx = 0; LayerIdx < LayerNum; LayerIdx++)
{
WeightDatas[Center*LayerNum + LayerIdx] = (uint8)(255.0f * CenterWeights[LayerIdx] / TotalWeight);
}
}
bHasChanged = true;
} // if Softness > 0.0f
} // if SlopeTotal > 0
}
}
}
if (!bHasChanged)
{
break;
}
}
float BrushSizeAdjust = 1.0f;
if (UISettings->BrushRadius < UISettings->MaximumValueRadius)
{
BrushSizeAdjust = UISettings->BrushRadius / UISettings->MaximumValueRadius;
}
// Make some noise...
for (int32 Y = BrushInfo.GetBounds().Min.Y; Y < BrushInfo.GetBounds().Max.Y; Y++)
{
const float* BrushScanline = BrushInfo.GetDataPtr(FIntPoint(0, Y));
for (int32 X = BrushInfo.GetBounds().Min.X; X < BrushInfo.GetBounds().Max.X; X++)
{
const float BrushValue = BrushScanline[X];
if (BrushValue > 0.0f)
{
FNoiseParameter NoiseParam(0, UISettings->ErosionNoiseScale, BrushValue * Thresh * UISettings->ToolStrength * BrushSizeAdjust);
float PaintAmount = ELandscapeToolNoiseMode::Conversion((ELandscapeToolNoiseMode::Type)UISettings->ErosionNoiseMode.GetValue(), NoiseParam.NoiseAmount, NoiseParam.Sample(X, Y));
HeightData[(X - X1) + (Y - Y1)*(1 + X2 - X1)] = FLandscapeHeightCache::ClampValue(HeightData[(X - X1) + (Y - Y1)*(1 + X2 - X1)] + PaintAmount);
}
}
}
HeightCache.SetCachedData(X1, Y1, X2, Y2, HeightData);
HeightCache.Flush();
if (bWeightApplied)
{
WeightCache.SetCachedData(X1, Y1, X2, Y2, WeightDatas, LayerNum, ELandscapeLayerPaintingRestriction::None);
}
WeightCache.Flush();
}
};
class FLandscapeToolErosion : public FLandscapeToolErosionBase<FLandscapeToolStrokeErosion>
{
public:
FLandscapeToolErosion(FEdModeLandscape* InEdMode)
: FLandscapeToolErosionBase(InEdMode)
{
}
virtual const TCHAR* GetToolName() override { return TEXT("Erosion"); }
virtual FText GetDisplayName() override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Erosion", "Erosion"); };
};
//
// FLandscapeToolHydraErosion
//
class FLandscapeToolStrokeHydraErosion : public FLandscapeToolStrokeErosionBase
{
public:
FLandscapeToolStrokeHydraErosion(FEdModeLandscape* InEdMode, const FLandscapeToolTarget& InTarget)
: FLandscapeToolStrokeErosionBase(InEdMode, InTarget)
{
}
void Apply(FEditorViewportClient* ViewportClient, FLandscapeBrush* Brush, const ULandscapeEditorObject* UISettings, const TArray<FLandscapeToolMousePosition>& MousePositions)
{
if (!LandscapeInfo)
{
return;
}
// Get list of verts to update
FLandscapeBrushData BrushInfo = Brush->ApplyBrush(MousePositions);
if (!BrushInfo)
{
return;
}
int32 X1, Y1, X2, Y2;
BrushInfo.GetInclusiveBounds(X1, Y1, X2, Y2);
// Tablet pressure
float Pressure = ViewportClient->Viewport->IsPenActive() ? ViewportClient->Viewport->GetTabletPressure() : 1.0f;
// expand the area by one vertex in each direction to ensure normals are calculated correctly
X1 -= 1;
Y1 -= 1;
X2 += 1;
Y2 += 1;
const int32 LayerNum = LandscapeInfo->Layers.Num();
const int32 Iteration = UISettings->HErodeIterationNum;
const uint16 RainAmount = UISettings->RainAmount;
const float DissolvingRatio = 0.07 * UISettings->ToolStrength * Pressure; //0.01;
const float EvaporateRatio = 0.5;
const float SedimentCapacity = 0.10 * UISettings->SedimentCapacity; //DissolvingRatio; //0.01;
HeightCache.CacheData(X1, Y1, X2, Y2);
TArray<uint16> HeightData;
HeightCache.GetCachedData(X1, Y1, X2, Y2, HeightData);
// Apply the brush
TArray<uint16> WaterData;
WaterData.Empty((1 + X2 - X1)*(1 + Y2 - Y1));
WaterData.AddZeroed((1 + X2 - X1)*(1 + Y2 - Y1));
TArray<uint16> SedimentData;
SedimentData.Empty((1 + X2 - X1)*(1 + Y2 - Y1));
SedimentData.AddZeroed((1 + X2 - X1)*(1 + Y2 - Y1));
// Only initial raining works better...
FNoiseParameter NoiseParam(0, UISettings->RainDistScale, RainAmount);
for (int32 Y = BrushInfo.GetBounds().Min.Y; Y < BrushInfo.GetBounds().Max.Y; Y++)
{
const float* BrushScanline = BrushInfo.GetDataPtr(FIntPoint(0, Y));
auto* WaterDataScanline = WaterData.GetData() + (Y - Y1) * (X2 - X1 + 1) + (0 - X1);
for (int32 X = BrushInfo.GetBounds().Min.X; X < BrushInfo.GetBounds().Max.X; X++)
{
const float BrushValue = BrushScanline[X];
if (BrushValue >= 1.0f)
{
float PaintAmount = ELandscapeToolNoiseMode::Conversion((ELandscapeToolNoiseMode::Type)UISettings->RainDistMode.GetValue(), NoiseParam.NoiseAmount, NoiseParam.Sample(X, Y));
if (PaintAmount > 0) // Raining only for positive region...
WaterDataScanline[X] += PaintAmount;
}
}
}
for (int32 i = 0; i < Iteration; i++)
{
bool bWaterExist = false;
for (int32 Y = BrushInfo.GetBounds().Min.Y; Y < BrushInfo.GetBounds().Max.Y; Y++)
{
const float* BrushScanline = BrushInfo.GetDataPtr(FIntPoint(0, Y));
for (int32 X = BrushInfo.GetBounds().Min.X; X < BrushInfo.GetBounds().Max.X; X++)
{
const float BrushValue = BrushScanline[X];
if (BrushValue > 0.0f)
{
int32 Center = (X - X1) + (Y - Y1)*(1 + X2 - X1);
const int32 NeighborNum = 8;
int32 Neighbor[NeighborNum] = {
(X - 1 - X1) + (Y - Y1)*(1 + X2 - X1), // -X
(X + 1 - X1) + (Y - Y1)*(1 + X2 - X1), // +X
(X - X1) + (Y - 1 - Y1)*(1 + X2 - X1), // -Y
(X - X1) + (Y + 1 - Y1)*(1 + X2 - X1), // +Y
(X - 1 - X1) + (Y - 1 - Y1)*(1 + X2 - X1), // -X -Y
(X + 1 - X1) + (Y + 1 - Y1)*(1 + X2 - X1), // +X +Y
(X + 1 - X1) + (Y - 1 - Y1)*(1 + X2 - X1), // +X -Y
(X - 1 - X1) + (Y + 1 - Y1)*(1 + X2 - X1) }; // -X +Y
// Dissolving...
float DissolvedAmount = DissolvingRatio * WaterData[Center] * BrushValue;
if (DissolvedAmount > 0 && HeightData[Center] >= DissolvedAmount)
{
HeightData[Center] -= DissolvedAmount;
SedimentData[Center] += DissolvedAmount;
}
uint32 TotalHeightDiff = 0;
uint32 TotalAltitudeDiff = 0;
uint32 AltitudeDiff[NeighborNum] = { 0 };
uint32 TotalWaterDiff = 0;
uint32 TotalSedimentDiff = 0;
uint32 Altitude = HeightData[Center] + WaterData[Center];
float AverageAltitude = 0;
uint32 LowerNeighbor = 0;
for (int32 Idx = 0; Idx < NeighborNum; Idx++)
{
uint32 NeighborAltitude = HeightData[Neighbor[Idx]] + WaterData[Neighbor[Idx]];
if (Altitude > NeighborAltitude)
{
AltitudeDiff[Idx] = Altitude - NeighborAltitude;
TotalAltitudeDiff += AltitudeDiff[Idx];
LowerNeighbor++;
AverageAltitude += NeighborAltitude;
if (HeightData[Center] > HeightData[Neighbor[Idx]])
{
TotalHeightDiff += HeightData[Center] - HeightData[Neighbor[Idx]];
}
}
else
{
AltitudeDiff[Idx] = 0;
}
}
// Water Transfer
if (LowerNeighbor > 0)
{
AverageAltitude /= (LowerNeighbor);
// This is not mathematically correct, but makes good result
if (TotalHeightDiff)
{
AverageAltitude *= (1.0f - 0.1 * UISettings->ToolStrength * Pressure);
//AverageAltitude -= 4000.0f * UISettings->ToolStrength;
}
uint32 WaterTransfer = FMath::Min<uint32>(WaterData[Center], Altitude - (uint32)AverageAltitude) * BrushValue;
for (int32 Idx = 0; Idx < NeighborNum; Idx++)
{
if (AltitudeDiff[Idx] > 0)
{
uint32 WaterDiff = (uint32)(WaterTransfer * (float)AltitudeDiff[Idx] / TotalAltitudeDiff);
WaterData[Neighbor[Idx]] += WaterDiff;
TotalWaterDiff += WaterDiff;
uint32 SedimentDiff = (uint32)(SedimentData[Center] * (float)WaterDiff / WaterData[Center]);
SedimentData[Neighbor[Idx]] += SedimentDiff;
TotalSedimentDiff += SedimentDiff;
}
}
WaterData[Center] -= TotalWaterDiff;
SedimentData[Center] -= TotalSedimentDiff;
}
// evaporation
if (WaterData[Center] > 0)
{
bWaterExist = true;
WaterData[Center] = (uint16)(WaterData[Center] * (1.0f - EvaporateRatio));
float SedimentCap = SedimentCapacity*WaterData[Center];
float SedimentDiff = SedimentData[Center] - SedimentCap;
if (SedimentDiff > 0)
{
SedimentData[Center] -= SedimentDiff;
HeightData[Center] = FMath::Clamp<uint16>(HeightData[Center] + SedimentDiff, 0, 65535);
}
}
}
}
}
if (!bWaterExist)
{
break;
}
}
if (UISettings->bHErosionDetailSmooth)
{
//LowPassFilter<uint16>(X1, Y1, X2, Y2, BrushInfo, HeightData, UISettings->HErosionDetailScale, UISettings->ToolStrength * Pressure);
LowPassFilter<uint16>(X1, Y1, X2, Y2, BrushInfo, HeightData, UISettings->HErosionDetailScale, 1.0f);
}
HeightCache.SetCachedData(X1, Y1, X2, Y2, HeightData);
HeightCache.Flush();
}
};
class FLandscapeToolHydraErosion : public FLandscapeToolErosionBase<FLandscapeToolStrokeHydraErosion>
{
public:
FLandscapeToolHydraErosion(FEdModeLandscape* InEdMode)
: FLandscapeToolErosionBase(InEdMode)
{
}
virtual const TCHAR* GetToolName() override { return TEXT("HydraErosion"); } // formerly HydraulicErosion
virtual FText GetDisplayName() override { return NSLOCTEXT("UnrealEd", "LandscapeMode_HydraErosion", "Hydraulic Erosion"); };
};
//
// Toolset initialization
//
void FEdModeLandscape::InitializeTool_Erosion()
{
auto Tool_Erosion = MakeUnique<FLandscapeToolErosion>(this);
Tool_Erosion->ValidBrushes.Add("BrushSet_Circle");
Tool_Erosion->ValidBrushes.Add("BrushSet_Alpha");
Tool_Erosion->ValidBrushes.Add("BrushSet_Pattern");
LandscapeTools.Add(MoveTemp(Tool_Erosion));
}
void FEdModeLandscape::InitializeTool_HydraErosion()
{
auto Tool_HydraErosion = MakeUnique<FLandscapeToolHydraErosion>(this);
Tool_HydraErosion->ValidBrushes.Add("BrushSet_Circle");
Tool_HydraErosion->ValidBrushes.Add("BrushSet_Alpha");
Tool_HydraErosion->ValidBrushes.Add("BrushSet_Pattern");
LandscapeTools.Add(MoveTemp(Tool_HydraErosion));
}