Files
UnrealEngineUWP/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeErosionTools.cpp
Mikolaj Sieluzycki b2a114be70 Header cleanup: Engine module, Landscape headers.
[CL 2242422 by Mikolaj Sieluzycki in Main branch]
2014-08-04 10:14:05 -04:00

492 lines
16 KiB
C++

// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#include "LandscapeEditorPrivatePCH.h"
#include "ObjectTools.h"
#include "LandscapeEdMode.h"
#include "ScopedTransaction.h"
#include "Landscape/LandscapeEdit.h"
#include "Landscape/LandscapeRender.h"
#include "Landscape/LandscapeDataAccess.h"
#include "Landscape/LandscapeSplineProxies.h"
#include "LandscapeEditorModule.h"
#include "Editor/PropertyEditor/Public/PropertyEditorModule.h"
#include "LandscapeEdModeTools.h"
#include "Landscape/Landscape.h"
#include "Landscape/LandscapeLayerInfoObject.h"
//
// FLandscapeToolErosionBase
//
class FLandscapeToolStrokeErosionBase
{
public:
FLandscapeToolStrokeErosionBase(FEdModeLandscape* InEdMode, const FLandscapeToolTarget& InTarget)
: LandscapeInfo(InTarget.LandscapeInfo.Get())
, HeightCache(InTarget)
, WeightCache(InTarget)
, bWeightApplied(InTarget.TargetType != ELandscapeToolTargetType::Heightmap)
{}
virtual void Apply(FEditorViewportClient* ViewportClient, FLandscapeBrush* Brush, const ULandscapeEditorObject* UISettings, const TArray<FLandscapeToolMousePosition>& MousePositions) = 0;
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)
{}
virtual ~FLandscapeToolStrokeErosion() {}
virtual void Apply(FEditorViewportClient* ViewportClient, FLandscapeBrush* Brush, const ULandscapeEditorObject* UISettings, const TArray<FLandscapeToolMousePosition>& MousePositions) override
{
if (!LandscapeInfo)
{
return;
}
// Get list of verts to update
TMap<FIntPoint, float> BrushInfo;
int32 X1, Y1, X2, Y2;
if (!Brush->ApplyBrush(MousePositions, BrushInfo, X1, Y1, X2, Y2) || MousePositions.Num() == 0)
{
return;
}
// Tablet pressure
float Pressure = ViewportClient->Viewport->IsPenActive() ? ViewportClient->Viewport->GetTabletPressure() : 1.f;
// 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 (auto It = BrushInfo.CreateIterator(); It; ++It)
{
int32 X, Y;
ALandscape::UnpackKey(It.Key(), X, Y);
if (It.Value() > 0.f)
{
int32 Center = (X - X1) + (Y - Y1)*(1 + X2 - X1);
int32 Neighbor[NeighborNum] = { (X - 1 - X1) + (Y - Y1)*(1 + X2 - X1), (X + 1 - X1) + (Y - Y1)*(1 + X2 - X1), (X - X1) + (Y - 1 - Y1)*(1 + X2 - X1), (X - X1) + (Y + 1 - Y1)*(1 + X2 - X1) };
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*It.Value() > Thresh)
{
SlopeTotal += Slope;
if (SlopeMax < Slope)
{
SlopeMax = Slope;
}
}
}
}
if (SlopeTotal > 0)
{
float Softness = 1.f;
{
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.f * LayerInfo->Hardness;
}
}
}
if (Softness > 0.f)
{
//Softness = FMath::Clamp<float>(Softness, 0.f, 1.f);
float TotalHeightDiff = 0;
int32 WeightTransfer = FMath::Min<int32>(WeightMoveThresh, SlopeMax - Thresh);
for (int32 Idx = 0; Idx < NeighborNum; Idx++)
{
float TotalWeight = 0.f;
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) * It.Value();
//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.f;
float Weight = (float)(WeightDatas[Neighbor[Idx] * LayerNum + LayerIdx]) / 255.f;
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.f * NeighborWeight[Idx*LayerNum + LayerIdx] / TotalWeight);
}
}
}
}
}
HeightData[Center] -= TotalHeightDiff;
if (bWeightApplied)
{
float TotalWeight = 0.f;
float WeightDiff = Softness * UISettings->ToolStrength * Pressure * It.Value();
for (int32 LayerIdx = 0; LayerIdx < LayerNum; LayerIdx++)
{
float Weight = (float)(WeightDatas[Center*LayerNum + LayerIdx]) / 255.f;
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.f * CenterWeights[LayerIdx] / TotalWeight);
}
}
bHasChanged = true;
} // if Softness > 0.f
} // if SlopeTotal > 0
}
}
if (!bHasChanged)
{
break;
}
}
float BrushSizeAdjust = 1.0f;
if (UISettings->BrushRadius < UISettings->MaximumValueRadius)
{
BrushSizeAdjust = UISettings->BrushRadius / UISettings->MaximumValueRadius;
}
// Make some noise...
for (auto It = BrushInfo.CreateIterator(); It; ++It)
{
int32 X, Y;
ALandscape::UnpackKey(It.Key(), X, Y);
if (It.Value() > 0.f)
{
FNoiseParameter NoiseParam(0, UISettings->ErosionNoiseScale, It.Value() * 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)
{}
virtual ~FLandscapeToolStrokeHydraErosion() {}
virtual void Apply(FEditorViewportClient* ViewportClient, FLandscapeBrush* Brush, const ULandscapeEditorObject* UISettings, const TArray<FLandscapeToolMousePosition>& MousePositions) override
{
if (!LandscapeInfo)
{
return;
}
// Get list of verts to update
TMap<FIntPoint, float> BrushInfo;
int32 X1, Y1, X2, Y2;
if (!Brush->ApplyBrush(MousePositions, BrushInfo, X1, Y1, X2, Y2) || MousePositions.Num() == 0)
{
return;
}
// Tablet pressure
float Pressure = ViewportClient->Viewport->IsPenActive() ? ViewportClient->Viewport->GetTabletPressure() : 1.f;
// 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 = 8;
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 (auto It = BrushInfo.CreateIterator(); It; ++It)
{
int32 X, Y;
ALandscape::UnpackKey(It.Key(), X, Y);
if (It.Value() >= 1.f)
{
float PaintAmount = ELandscapeToolNoiseMode::Conversion((ELandscapeToolNoiseMode::Type)UISettings->RainDistMode.GetValue(), NoiseParam.NoiseAmount, NoiseParam.Sample(X, Y));
if (PaintAmount > 0) // Raining only for positive region...
WaterData[(X - X1) + (Y - Y1)*(1 + X2 - X1)] += PaintAmount;
}
}
for (int32 i = 0; i < Iteration; i++)
{
bool bWaterExist = false;
for (auto It = BrushInfo.CreateIterator(); It; ++It)
{
int32 X, Y;
ALandscape::UnpackKey(It.Key(), X, Y);
if (It.Value() > 0.f)
{
int32 Center = (X - X1) + (Y - Y1)*(1 + X2 - X1);
int32 Neighbor[NeighborNum] = {
(X - 1 - X1) + (Y - Y1)*(1 + X2 - X1), (X + 1 - X1) + (Y - Y1)*(1 + X2 - X1), (X - X1) + (Y - 1 - Y1)*(1 + X2 - X1), (X - X1) + (Y + 1 - Y1)*(1 + X2 - X1)
, (X - 1 - X1) + (Y - 1 - Y1)*(1 + X2 - X1), (X + 1 - X1) + (Y + 1 - Y1)*(1 + X2 - X1), (X + 1 - X1) + (Y - 1 - Y1)*(1 + X2 - X1), (X - 1 - X1) + (Y + 1 - Y1)*(1 + X2 - X1)
};
// Dissolving...
float DissolvedAmount = DissolvingRatio * WaterData[Center] * It.Value();
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.f - 0.1 * UISettings->ToolStrength * Pressure);
//AverageAltitude -= 4000.f * UISettings->ToolStrength;
}
uint32 WaterTransfer = FMath::Min<uint32>(WaterData[Center], Altitude - (uint32)AverageAltitude) * It.Value();
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.f - 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));
}