// Copyright Epic Games, Inc. All Rights Reserved. #include "CoreMinimal.h" #include "Misc/App.h" #include "InputCoreTypes.h" #include "Engine/EngineTypes.h" #include "Components/StaticMeshComponent.h" #include "Engine/StaticMesh.h" #include "LandscapeToolInterface.h" #include "LandscapeProxy.h" #include "LandscapeEdMode.h" #include "LandscapeEditorObject.h" #include "LandscapeEdit.h" #include "LandscapeDataAccess.h" #include "LandscapeEdModeTools.h" #include "LandscapeSettings.h" #include "Landscape.h" #include "Logging/TokenizedMessage.h" #include "Logging/MessageLog.h" #include "Logging/LogMacros.h" #include "Misc/MapErrors.h" #include "EngineModule.h" #define LOCTEXT_NAMESPACE "LandscapeTools" DEFINE_LOG_CATEGORY(LogLandscapeTools); const int32 FNoiseParameter::Permutations[256] = { 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 }; // // FLandscapeToolPaintBase // template class FLandscapeToolPaintBase : public FLandscapeToolBase { using Super = FLandscapeToolBase; public: FLandscapeToolPaintBase(FEdModeLandscape* InEdMode) : Super(InEdMode) { } virtual ELandscapeToolTargetTypeMask::Type GetSupportedTargetTypes() override { return ELandscapeToolTargetTypeMask::FromType(TToolTarget::TargetType); } }; template class FLandscapeToolStrokePaintBase : public FLandscapeToolStrokeBase { using Super = FLandscapeToolStrokeBase; public: FLandscapeToolStrokePaintBase(FEdModeLandscape* InEdMode, FEditorViewportClient* InViewportClient, const FLandscapeToolTarget& InTarget) : Super(InEdMode, InViewportClient, InTarget) , Cache(InTarget) { const ULandscapeSettings* Settings = GetDefault(); PaintStrengthGamma = Settings->PaintStrengthGamma; bDisablePaintingStartupSlowdown = Settings->bDisablePaintingStartupSlowdown; } float GetStrength(const ULandscapeEditorObject* UISettings, bool bInIsHeightMap) const { return bInIsHeightMap ? UISettings->GetCurrentToolStrength() : FMath::Pow(UISettings->GetCurrentToolStrength(), PaintStrengthGamma); } protected: typename ToolTarget::CacheClass Cache; float PaintStrengthGamma = 1.0f; bool bDisablePaintingStartupSlowdown = false; }; // // FLandscapeToolPaint // class FLandscapeToolStrokePaint : public FLandscapeToolStrokePaintBase { using Super = FLandscapeToolStrokePaintBase; using ToolTarget = FWeightmapToolTarget; using ValueType = ToolTarget::CacheClass::DataType; TMap TotalInfluenceMap; // amount of time and weight the brush has spent on each vertex. bool bIsAllowListMode; bool bAddToAllowList; public: FLandscapeToolStrokePaint(FEdModeLandscape* InEdMode, FEditorViewportClient* InViewportClient, const FLandscapeToolTarget& InTarget) : Super(InEdMode, InViewportClient, InTarget) , bIsAllowListMode(EdMode->UISettings->PaintingRestriction == ELandscapeLayerPaintingRestriction::UseComponentAllowList && (InViewportClient->Viewport->KeyState(EKeys::Equals) || InViewportClient->Viewport->KeyState(EKeys::Hyphen))) , bAddToAllowList(bIsAllowListMode && InViewportClient->Viewport->KeyState(EKeys::Equals)) {} void Apply(FEditorViewportClient* ViewportClient, FLandscapeBrush* Brush, const ULandscapeEditorObject* UISettings, const TArray& InteractorPositions) { TRACE_CPUPROFILER_EVENT_SCOPE(FLandscapeToolStrokePaint_Apply); // Invert when holding Shift bool bInvert = InteractorPositions.Last().bModifierPressed; UE_LOG(LogLandscapeTools, VeryVerbose, TEXT("bInvert = %d"), bInvert); if (bIsAllowListMode) { // Get list of components to delete from brush // TODO - only retrieve bounds as we don't need the vert data FLandscapeBrushData BrushInfo = Brush->ApplyBrush(InteractorPositions); if (!BrushInfo) { return; } int32 X1, Y1, X2, Y2; BrushInfo.GetInclusiveBounds(X1, Y1, X2, Y2); // Shrink bounds by 1,1 to avoid GetComponentsInRegion picking up extra components on all sides due to the overlap between components TSet SelectedComponents; LandscapeInfo->GetComponentsInRegion(X1 + 1, Y1 + 1, X2 - 1, Y2 - 1, SelectedComponents); for (ULandscapeComponent* Component : SelectedComponents) { Component->Modify(); } if (bAddToAllowList) { for (ULandscapeComponent* Component : SelectedComponents) { Component->LayerAllowList.AddUnique(Target.LayerInfo.Get()); } } else { FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); for (ULandscapeComponent* Component : SelectedComponents) { Component->LayerAllowList.RemoveSingle(Target.LayerInfo.Get()); Component->DeleteLayer(Target.LayerInfo.Get(), LandscapeEdit); } } return; } // Get list of verts to update FLandscapeBrushData BrushInfo = Brush->ApplyBrush(InteractorPositions); 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; const bool bUseWeightTargetValue = UISettings->bUseWeightTargetValue; const bool bCacheOriginalData = !bUseWeightTargetValue; this->Cache.CacheData(X1, Y1, X2, Y2, bCacheOriginalData); // The data we'll be writing to TArray Data; this->Cache.GetCachedData(X1, Y1, X2, Y2, Data); // The source data we use for editing. TArray* SourceDataArrayPtr = &Data; TArray OriginalData; if (!bUseWeightTargetValue) { // When painting weights (and not using target value mode), we use a source value that tends more // to the current value as we paint over the same region multiple times. // TODO: Make this frame-rate independent this->Cache.GetOriginalData(X1, Y1, X2, Y2, OriginalData); SourceDataArrayPtr = &OriginalData; for (int32 Y = Y1; Y < Y2; Y++) { ValueType* DataScanline = Data.GetData() + (Y - Y1) * (X2 - X1 + 1) + (0 - X1); ValueType* OriginalDataScanline = OriginalData.GetData() + (Y - Y1) * (X2 - X1 + 1) + (0 - X1); for (int32 X = X1; X < X2; X++) { ValueType& SourceValue = OriginalDataScanline[X]; ValueType& CurrentValue = DataScanline[X]; if (bDisablePaintingStartupSlowdown) { SourceValue = CurrentValue; } else { float VertexInfluence = TotalInfluenceMap.FindRef(FIntPoint(X, Y)); SourceValue = FMath::Lerp(SourceValue, CurrentValue, FMath::Min(VertexInfluence * 0.05f, 1.0f)); } } } } // Adjust strength based on brush size and drawscale, so strength 1 = one hemisphere const float AdjustedStrength = ToolTarget::StrengthMultiplier(this->LandscapeInfo, UISettings->GetCurrentToolBrushRadius()); float PaintStrength = this->GetStrength(UISettings, /* bInIsHeightMap = */ false) * Pressure * AdjustedStrength; ValueType DestValue = FWeightmapToolTarget::CacheClass::ClampValue(static_cast(255.0f * UISettings->WeightTargetValue)); // TODO: make paint tool framerate independent like the sculpt tool // const float DeltaTime = FMath::Min(FApp::GetDeltaTime(), 0.1f); // Under 10 fps slow down paint speed // SculptStrength *= DeltaTime * 3.0f; // * 3.0f to partially compensate for impact of DeltaTime on slowing the tools down compared to the old framerate-dependent version if (PaintStrength <= 0.0f) { return; } if (!bUseWeightTargetValue) { PaintStrength = FMath::Max(PaintStrength, 1.0f); } // Apply the brush for (int32 Y = BrushInfo.GetBounds().Min.Y; Y < BrushInfo.GetBounds().Max.Y; Y++) { const float* BrushScanline = BrushInfo.GetDataPtr(FIntPoint(0, Y)); ValueType* DataScanline = Data.GetData() + (Y - Y1) * (X2 - X1 + 1) + (0 - X1); ValueType* SourceDataScanline = SourceDataArrayPtr->GetData() + (Y - Y1) * (X2 - X1 + 1) + (0 - X1); for (int32 X = BrushInfo.GetBounds().Min.X; X < BrushInfo.GetBounds().Max.X; X++) { const FIntPoint Key = FIntPoint(X, Y); const float BrushValue = BrushScanline[X]; // Update influence map float VertexInfluence = TotalInfluenceMap.FindRef(Key); TotalInfluenceMap.Add(Key, VertexInfluence + BrushValue); float PaintAmount = BrushValue * PaintStrength; ValueType& CurrentValue = DataScanline[X]; const ValueType& SourceValue = SourceDataScanline[X]; if (bUseWeightTargetValue) { CurrentValue = FMath::Lerp(CurrentValue, DestValue, PaintAmount / AdjustedStrength); } else { const int32 IntPaintAmount = FMath::RoundToInt(PaintAmount); if (bInvert) { CurrentValue = ToolTarget::CacheClass::ClampValue(FMath::Min(SourceValue - IntPaintAmount, CurrentValue)); } else { CurrentValue = ToolTarget::CacheClass::ClampValue(FMath::Max(SourceValue + IntPaintAmount, CurrentValue)); } } } } this->Cache.SetCachedData(X1, Y1, X2, Y2, Data, UISettings->PaintingRestriction); this->Cache.Flush(); // Dirty any runtime virtual textures that our landscape components write to. this->LandscapeInfo->DirtyRuntimeVirtualTextureForLandscapeArea(X1, Y1, X2, Y2); } }; class FLandscapeToolPaint : public FLandscapeToolPaintBase { using Super = FLandscapeToolPaintBase; public: FLandscapeToolPaint(FEdModeLandscape* InEdMode) : Super(InEdMode) { } virtual const TCHAR* GetToolName() const override { return TEXT("Paint"); } virtual FText GetDisplayName() const override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Paint", "Paint"); }; virtual FText GetDisplayMessage() const override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Paint_Message", "The Paint tool increases or decreases the weight of the Material layer being applied to the Landscape."); }; virtual void EnterTool() { if (EdMode->UISettings->PaintingRestriction == ELandscapeLayerPaintingRestriction::UseComponentAllowList) { EdMode->UISettings->UpdateComponentLayerAllowList(); } Super::EnterTool(); } }; class FLandscapeToolStrokeErase : public FLandscapeToolStrokePaintBase { using Super = FLandscapeToolStrokePaintBase; using ToolTarget = FHeightmapToolTarget; using ValueType = typename ToolTarget::CacheClass::DataType; const ValueType FlattenHeight; public: FLandscapeToolStrokeErase(FEdModeLandscape* InEdMode, FEditorViewportClient* InViewportClient, const FLandscapeToolTarget& InTarget) : Super(InEdMode, InViewportClient, InTarget) , FlattenHeight(LandscapeDataAccess::GetTexHeight(0.f)) {} void Apply(FEditorViewportClient* ViewportClient, FLandscapeBrush* Brush, const ULandscapeEditorObject* UISettings, const TArray& InteractorPositions) { TRACE_CPUPROFILER_EVENT_SCOPE(FLandscapeToolStrokeErase_Apply); if (!this->LandscapeInfo) return; // Get list of verts to update FLandscapeBrushData BrushInfo = Brush->ApplyBrush(InteractorPositions); 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; this->Cache.CacheData(X1, Y1, X2, Y2); TArray Data; this->Cache.GetCachedData(X1, Y1, X2, Y2, Data); // Apply the brush for (int32 Y = BrushInfo.GetBounds().Min.Y; Y < BrushInfo.GetBounds().Max.Y; Y++) { const float* BrushScanline = BrushInfo.GetDataPtr(FIntPoint(0, Y)); ValueType* DataScanline = Data.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 > 0.0f) { float Strength = FMath::Clamp(BrushValue * UISettings->GetCurrentToolStrength() * Pressure, 0.0f, 1.0f); int32 Delta = DataScanline[X] - FlattenHeight; if (Delta > 0) { DataScanline[X] = static_cast(FMath::FloorToInt(FMath::Lerp((float)DataScanline[X], (float)FlattenHeight, Strength))); } else { DataScanline[X] = static_cast(FMath::CeilToInt(FMath::Lerp((float)DataScanline[X], (float)FlattenHeight, Strength))); } } } } this->Cache.SetCachedData(X1, Y1, X2, Y2, Data, UISettings->PaintingRestriction); this->Cache.Flush(); // Dirty any runtime virtual textures that our landscape components write to. this->LandscapeInfo->DirtyRuntimeVirtualTextureForLandscapeArea(X1, Y1, X2, Y2); } }; // class FLandscapeToolStrokeSculpt : public FLandscapeToolStrokePaintBase { using Super = FLandscapeToolStrokePaintBase; using ToolTarget = FHeightmapToolTarget; using ValueType = ToolTarget::CacheClass::DataType; public: FLandscapeToolStrokeSculpt(FEdModeLandscape* InEdMode, FEditorViewportClient* InViewportClient, const FLandscapeToolTarget& InTarget) : Super(InEdMode, InViewportClient, InTarget) {} virtual void SetEditLayer(const FGuid& EditLayerGUID) override { this->Cache.DataAccess.SetEditLayer(EditLayerGUID); } void Apply(FEditorViewportClient* ViewportClient, FLandscapeBrush* Brush, const ULandscapeEditorObject* UISettings, const TArray& InteractorPositions) { TRACE_CPUPROFILER_EVENT_SCOPE(FLandscapeToolStrokeSculpt_Apply); // Invert when holding Shift bool bInvert = InteractorPositions.Last().bModifierPressed; UE_LOG(LogLandscapeTools, VeryVerbose, TEXT("bInvert = %d"), bInvert); // Get list of verts to update FLandscapeBrushData BrushInfo = Brush->ApplyBrush(InteractorPositions); 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; this->Cache.CacheData(X1, Y1, X2, Y2); bool bUseClayBrush = UISettings->bUseClayBrush; // The data we'll be writing to TArray Data; this->Cache.GetCachedData(X1, Y1, X2, Y2, Data); // The source data we use for editing. TArray* SourceDataArrayPtr = &Data; FMatrix ToWorld = ToolTarget::ToWorldMatrix(this->LandscapeInfo); FMatrix FromWorld = ToolTarget::FromWorldMatrix(this->LandscapeInfo); // Adjust strength based on brush size and drawscale, so strength 1 = one hemisphere const float AdjustedStrength = ToolTarget::StrengthMultiplier(this->LandscapeInfo, UISettings->GetCurrentToolBrushRadius()); float SculptStrength = UISettings->GetCurrentToolStrength() * Pressure * AdjustedStrength; const float DeltaTime = FMath::Min(static_cast(FApp::GetDeltaTime()), 0.1f); // Under 10 fps slow down paint speed SculptStrength *= DeltaTime * 3.0f; // * 3.0f to partially compensate for impact of DeltaTime on slowing the tools down compared to the old framerate-dependent version if (SculptStrength <= 0.0f) { return; } if (!bUseClayBrush) { SculptStrength = FMath::Max(SculptStrength, 1.0f); } FPlane BrushPlane(ForceInit); TArray Normals; if (bUseClayBrush) { TRACE_CPUPROFILER_EVENT_SCOPE(ComputeClayBrush); // Calculate normals for brush verts in data space Normals.Empty(SourceDataArrayPtr->Num()); Normals.AddZeroed(SourceDataArrayPtr->Num()); for (int32 Y = Y1; Y < Y2; Y++) { ValueType* SourceDataScanline_0 = SourceDataArrayPtr->GetData() + (Y - Y1) * (X2 - X1 + 1) + (0 - X1); ValueType* SourceDataScanline_1 = SourceDataArrayPtr->GetData() + (Y - Y1) * (X2 - X1 + 1) + (0 - X1); FVector* NormalsScanline_0 = Normals.GetData() + (Y - Y1) * (X2 - X1 + 1) + (0 - X1); FVector* NormalsScanline_1 = Normals.GetData() + (Y + 1 - Y1) * (X2 - X1 + 1) + (0 - X1); for (int32 X = X1; X < X2; X++) { FVector Vert00 = ToWorld.TransformPosition(FVector((float)X + 0.0f, (float)Y + 0.0f, SourceDataScanline_0[X + 0])); FVector Vert01 = ToWorld.TransformPosition(FVector((float)X + 0.0f, (float)Y + 1.0f, SourceDataScanline_1[X + 0])); FVector Vert10 = ToWorld.TransformPosition(FVector((float)X + 1.0f, (float)Y + 0.0f, SourceDataScanline_0[X + 1])); FVector Vert11 = ToWorld.TransformPosition(FVector((float)X + 1.0f, (float)Y + 1.0f, SourceDataScanline_1[X + 1])); FVector FaceNormal1 = ((Vert00 - Vert10) ^ (Vert10 - Vert11)).GetSafeNormal(); FVector FaceNormal2 = ((Vert11 - Vert01) ^ (Vert01 - Vert00)).GetSafeNormal(); // contribute to the vertex normals. NormalsScanline_0[X + 1] += FaceNormal1; NormalsScanline_1[X + 0] += FaceNormal2; NormalsScanline_0[X + 0] += FaceNormal1 + FaceNormal2; NormalsScanline_1[X + 1] += FaceNormal1 + FaceNormal2; } } for (int32 Y = Y1; Y <= Y2; Y++) { FVector* NormalsScanline = Normals.GetData() + (Y - Y1) * (X2 - X1 + 1) + (0 - X1); for (int32 X = X1; X <= X2; X++) { NormalsScanline[X] = NormalsScanline[X].GetSafeNormal(); } } // Find brush centroid location FVector AveragePoint(0.0f, 0.0f, 0.0f); FVector AverageNormal(0.0f, 0.0f, 0.0f); float TotalWeight = 0.0f; for (int32 Y = BrushInfo.GetBounds().Min.Y; Y < BrushInfo.GetBounds().Max.Y; Y++) { const float* BrushScanline = BrushInfo.GetDataPtr(FIntPoint(0, Y)); ValueType* SourceDataScanline = SourceDataArrayPtr->GetData() + (Y - Y1) * (X2 - X1 + 1) + (0 - X1); FVector* NormalsScanline = Normals.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 > 0.0f) { AveragePoint += FVector((float)X * BrushValue, (float)Y * BrushValue, (float)SourceDataScanline[X] * BrushValue); FVector SampleNormal = NormalsScanline[X]; AverageNormal += SampleNormal * BrushValue; TotalWeight += BrushValue; } } } if (TotalWeight > 0.0f) { AveragePoint /= TotalWeight; AverageNormal = AverageNormal.GetSafeNormal(); } // Convert to world space FVector AverageLocation = ToWorld.TransformPosition(AveragePoint); FVector StrengthVector = ToWorld.TransformVector(FVector(0, 0, SculptStrength)); // Brush pushes out in the normal direction FVector OffsetVector = AverageNormal * StrengthVector.Z; if (bInvert) { OffsetVector *= -1; } // World space brush plane BrushPlane = FPlane(AverageLocation + OffsetVector, AverageNormal); } // Apply the brush { TRACE_CPUPROFILER_EVENT_SCOPE(ApplyBrush); for (int32 Y = BrushInfo.GetBounds().Min.Y; Y < BrushInfo.GetBounds().Max.Y; Y++) { const float* BrushScanline = BrushInfo.GetDataPtr(FIntPoint(0, Y)); ValueType* DataScanline = Data.GetData() + (Y - Y1) * (X2 - X1 + 1) + (0 - X1); ValueType* SourceDataScanline = SourceDataArrayPtr->GetData() + (Y - Y1) * (X2 - X1 + 1) + (0 - X1); for (int32 X = BrushInfo.GetBounds().Min.X; X < BrushInfo.GetBounds().Max.X; X++) { const FIntPoint Key = FIntPoint(X, Y); const float BrushValue = BrushScanline[X]; float SculptAmount = BrushValue * SculptStrength; ValueType& CurrentValue = DataScanline[X]; const ValueType& SourceValue = SourceDataScanline[X]; if (bUseClayBrush) { // Brush application starts from original world location at start of stroke FVector WorldLoc = ToWorld.TransformPosition(FVector(X, Y, SourceValue)); // Calculate new location on the brush plane WorldLoc.Z = (BrushPlane.W - BrushPlane.X*WorldLoc.X - BrushPlane.Y*WorldLoc.Y) / BrushPlane.Z; // Painted amount lerps based on brush falloff. float PaintValue = FMath::Lerp(SourceValue, static_cast(FromWorld.TransformPosition(WorldLoc).Z), BrushValue); if (bInvert) { CurrentValue = ToolTarget::CacheClass::ClampValue(FMath::Min(FMath::RoundToInt(PaintValue), CurrentValue)); } else { CurrentValue = ToolTarget::CacheClass::ClampValue(FMath::Max(FMath::RoundToInt(PaintValue), CurrentValue)); } } else { if (bInvert) { CurrentValue = ToolTarget::CacheClass::ClampValue(FMath::Min(SourceValue - FMath::RoundToInt(SculptAmount), CurrentValue)); } else { CurrentValue = ToolTarget::CacheClass::ClampValue(FMath::Max(SourceValue + FMath::RoundToInt(SculptAmount), CurrentValue)); } } } } } this->Cache.SetCachedData(X1, Y1, X2, Y2, Data); this->Cache.Flush(); // Dirty any runtime virtual textures that our landscape components write to. this->LandscapeInfo->DirtyRuntimeVirtualTextureForLandscapeArea(X1, Y1, X2, Y2); } }; class FLandscapeToolSculpt : public FLandscapeToolPaintBase { using Super = FLandscapeToolPaintBase; public: FLandscapeToolSculpt(FEdModeLandscape* InEdMode) : Super(InEdMode) { } virtual const TCHAR* GetToolName() const override { return TEXT("Sculpt"); } virtual FText GetDisplayName() const override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Sculpt", "Sculpt"); }; virtual FText GetDisplayMessage() const override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Sculpt_Message", "Raise or lower the Landscape using the selected brush shape and falloff."); }; }; class FLandscapeToolErase : public FLandscapeToolPaintBase { using Super = FLandscapeToolPaintBase; public: FLandscapeToolErase(FEdModeLandscape* InEdMode) : Super(InEdMode) { } virtual const TCHAR* GetToolName() const override { return TEXT("Erase"); } virtual FText GetDisplayName() const override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Erase", "Erase"); }; virtual FText GetDisplayMessage() const override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Erase_Message", "Erase the Landscape using the selected brush shape and falloff."); }; }; // // FLandscapeToolSmooth // template class FLandscapeToolStrokeSmooth : public FLandscapeToolStrokePaintBase { using Super = FLandscapeToolStrokePaintBase; using ValueType = typename ToolTarget::CacheClass::DataType; bool bTargetIsHeightmap; FLandscapeLayerDataCache LayerDataCache; public: FLandscapeToolStrokeSmooth(FEdModeLandscape* InEdMode, FEditorViewportClient* InViewportClient, const FLandscapeToolTarget& InTarget) : Super(InEdMode, InViewportClient, InTarget) , bTargetIsHeightmap(InTarget.TargetType == ELandscapeToolTargetType::Heightmap) , LayerDataCache(InTarget, this->Cache) { } virtual void SetEditLayer(const FGuid& EditLayerGUID) override { LayerDataCache.SetCacheEditingLayer(EditLayerGUID); } void Apply(FEditorViewportClient* ViewportClient, FLandscapeBrush* Brush, const ULandscapeEditorObject* UISettings, const TArray& InteractorPositions) { TRACE_CPUPROFILER_EVENT_SCOPE(FLandscapeToolStrokeSmooth_Apply); if (!this->LandscapeInfo) return; ALandscape* Landscape = this->LandscapeInfo->LandscapeActor.Get(); const bool bCombinedLayerOperation = bTargetIsHeightmap && UISettings->bCombinedLayersOperation && Landscape && Landscape->HasLayersContent(); // Get list of verts to update FLandscapeBrushData BrushInfo = Brush->ApplyBrush(InteractorPositions); 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 CA_SUPPRESS(6326); if (ToolTarget::TargetType == ELandscapeToolTargetType::Heightmap) { X1 -= 1; Y1 -= 1; X2 += 1; Y2 += 1; } TArray Data; LayerDataCache.Initialize(this->LandscapeInfo, bCombinedLayerOperation); LayerDataCache.Read(X1, Y1, X2, Y2, Data); const TArray ReadData { Data }; const float ToolStrength = FMath::Clamp(this->GetStrength(UISettings, bTargetIsHeightmap) * Pressure, 0.0f, 1.0f); // Apply the brush if (UISettings->bDetailSmooth) { LowPassFilter(X1, Y1, X2, Y2, BrushInfo, Data, UISettings->DetailScale, ToolStrength); } else { const int32 FilterRadius = UISettings->SmoothFilterKernelSize; for (int32 Y = BrushInfo.GetBounds().Min.Y; Y < BrushInfo.GetBounds().Max.Y; Y++) { const float* BrushScanline = BrushInfo.GetDataPtr(FIntPoint(0, Y)); ValueType* DataScanline = Data.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] * ToolStrength; if (BrushValue > 0.0f) { // needs to be ~12 bits larger than ToolTarget::CacheClass::DataType (for max FilterRadius (31)) // the editor is 64-bit native so just go the whole hog :) int64 FilterValue = 0; int32 FilterSamplingNumber = 0; const int32 XRadius = FMath::Min3(FilterRadius, X - BrushInfo.GetBounds().Min.X, BrushInfo.GetBounds().Max.X - X - 1); const int32 YRadius = FMath::Min3(FilterRadius, Y - BrushInfo.GetBounds().Min.Y, BrushInfo.GetBounds().Max.Y - Y - 1); const int32 SampleX1 = X - XRadius; checkSlow(SampleX1 >= BrushInfo.GetBounds().Min.X); const int32 SampleY1 = Y - YRadius; checkSlow(SampleY1 >= BrushInfo.GetBounds().Min.Y); const int32 SampleX2 = X + XRadius; checkSlow(SampleX2 < BrushInfo.GetBounds().Max.X); const int32 SampleY2 = Y + YRadius; checkSlow(SampleY2 < BrushInfo.GetBounds().Max.Y); for (int32 SampleY = SampleY1; SampleY <= SampleY2; SampleY++) { const float* SampleBrushScanline = BrushInfo.GetDataPtr(FIntPoint(0, SampleY)); const float* SampleBrushScanline2 = BrushInfo.GetDataPtr(FIntPoint(0, Y + (Y - SampleY))); const ValueType* SampleDataScanline = ReadData.GetData() + (SampleY - Y1) * (X2 - X1 + 1) + (0 - X1); for (int32 SampleX = SampleX1; SampleX <= SampleX2; SampleX++) { // constrain sample to within the brush, symmetrically to prevent flattening bug const float SampleBrushValue = FMath::Min( FMath::Min(SampleBrushScanline[SampleX], SampleBrushScanline[X + (X - SampleX)]), FMath::Min(SampleBrushScanline2[SampleX], SampleBrushScanline2[X + (X - SampleX)]) ); if (SampleBrushValue > 0.0f) { FilterValue += SampleDataScanline[SampleX]; FilterSamplingNumber++; } } } FilterValue /= FilterSamplingNumber; DataScanline[X] = FMath::Lerp(DataScanline[X], static_cast(FilterValue), BrushValue); } } } } LayerDataCache.Write(X1, Y1, X2, Y2, Data, UISettings->PaintingRestriction); // Dirty any runtime virtual textures that our landscape components write to. this->LandscapeInfo->DirtyRuntimeVirtualTextureForLandscapeArea(X1, Y1, X2, Y2); } }; template class FLandscapeToolSmooth : public FLandscapeToolPaintBase> { using Super = FLandscapeToolPaintBase>; public: FLandscapeToolSmooth(FEdModeLandscape* InEdMode) : Super(InEdMode) { } virtual const TCHAR* GetToolName() const override { return TEXT("Smooth"); } virtual FText GetDisplayName() const override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Smooth", "Smooth"); }; virtual FText GetDisplayMessage() const override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Smooth_Message", "Smooth the Landscape within the brushes influence by averaging the Z position of the Landscape vertices."); }; }; // // FLandscapeToolFlatten // template class FLandscapeToolStrokeFlatten : public FLandscapeToolStrokePaintBase { using Super = FLandscapeToolStrokePaintBase; using ValueType = typename ToolTarget::CacheClass::DataType; ValueType FlattenValue; FVector FlattenNormal; float FlattenPlaneDist; bool bInitializedFlattenValue; bool bTargetIsHeightmap; FLandscapeLayerDataCache LayerDataCache; public: FLandscapeToolStrokeFlatten(FEdModeLandscape* InEdMode, FEditorViewportClient* InViewportClient, const FLandscapeToolTarget& InTarget) : FLandscapeToolStrokePaintBase(InEdMode, InViewportClient, InTarget) , bInitializedFlattenValue(false) , bTargetIsHeightmap(InTarget.TargetType == ELandscapeToolTargetType::Heightmap) , LayerDataCache(InTarget, this->Cache) { if (InEdMode->UISettings->bUseFlattenTarget && bTargetIsHeightmap) { FTransform LocalToWorld = InTarget.LandscapeInfo->GetLandscapeProxy()->ActorToWorld(); float Height = static_cast((InEdMode->UISettings->GetFlattenTarget(/*bInReturnPreviewValueIfActive = */false) - LocalToWorld.GetTranslation().Z) / LocalToWorld.GetScale3D().Z); FlattenValue = static_cast(LandscapeDataAccess::GetTexHeight(Height)); bInitializedFlattenValue = true; } } virtual void SetEditLayer(const FGuid& EditLayerGUID) override { LayerDataCache.SetCacheEditingLayer(EditLayerGUID); } void Apply(FEditorViewportClient* ViewportClient, FLandscapeBrush* Brush, const ULandscapeEditorObject* UISettings, const TArray& InteractorPositions) { if (!this->LandscapeInfo) return; ALandscape* Landscape = this->LandscapeInfo->LandscapeActor.Get(); const bool bCombinedLayerOperation = bTargetIsHeightmap && UISettings->bCombinedLayersOperation && Landscape && Landscape->HasLayersContent(); // Can't use slope if use Flatten Target because no normal is provided bool bUseSlopeFlatten = UISettings->bUseSlopeFlatten && !UISettings->bUseFlattenTarget; if (!bInitializedFlattenValue || (UISettings->bPickValuePerApply && bTargetIsHeightmap)) { bInitializedFlattenValue = false; float FlattenX = static_cast(InteractorPositions[0].Position.X); float FlattenY = static_cast(InteractorPositions[0].Position.Y); int32 FlattenHeightX = FMath::FloorToInt(FlattenX); int32 FlattenHeightY = FMath::FloorToInt(FlattenY); float InterpolatedValue = 0.f; float V00 = 0.f; float V10 = 0.f; float V01 = 0.f; float V11 = 0.f; if (bCombinedLayerOperation) { // create a new accessor and point it read from the final runtime data typename ToolTarget::CacheClass::AccessorClass RuntimeDataAccessor(this->Target); RuntimeDataAccessor.SetEditLayer(FGuid()); ValueType P00, P10, P01, P11; RuntimeDataAccessor.GetDataFast(FlattenHeightX, FlattenHeightY, FlattenHeightX, FlattenHeightY, &P00); RuntimeDataAccessor.GetDataFast(FlattenHeightX + 1, FlattenHeightY, FlattenHeightX + 1, FlattenHeightY, &P10); RuntimeDataAccessor.GetDataFast(FlattenHeightX, FlattenHeightY + 1, FlattenHeightX, FlattenHeightY + 1, &P01); RuntimeDataAccessor.GetDataFast(FlattenHeightX + 1, FlattenHeightY + 1, FlattenHeightX + 1, FlattenHeightY + 1, &P11); // Release Texture Mips that will be Locked by the next SynchronousUpdateComponentVisibilityForHeight (inside the LayerDataCache.Read call) RuntimeDataAccessor.Flush(); V00 = P00; V10 = P10; V01 = P01; V11 = P11; InterpolatedValue = FMath::Lerp(FMath::Lerp(V00, V10, FlattenX - FlattenHeightX), FMath::Lerp(V01, V11, FlattenX - FlattenHeightX), FlattenY - FlattenHeightY); } else { this->Cache.CacheData(FlattenHeightX, FlattenHeightY, FlattenHeightX + 1, FlattenHeightY + 1); InterpolatedValue = this->Cache.GetValue(FlattenX, FlattenY); } FlattenValue = static_cast(InterpolatedValue); if (bUseSlopeFlatten && bTargetIsHeightmap) { if (bCombinedLayerOperation) { // Can't rely on cache in this mode 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(); FlattenNormal = (FaceNormal1 + FaceNormal2).GetSafeNormal(); } else { FlattenNormal = this->Cache.GetNormal(FlattenHeightX, FlattenHeightY); } FlattenPlaneDist = static_cast(-(FlattenNormal | FVector(FlattenX, FlattenY, InterpolatedValue))); } bInitializedFlattenValue = true; } // Get list of verts to update FLandscapeBrushData BrushInfo = Brush->ApplyBrush(InteractorPositions); 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 CA_SUPPRESS(6326); if (ToolTarget::TargetType == ELandscapeToolTargetType::Heightmap) { X1 -= 1; Y1 -= 1; X2 += 1; Y2 += 1; } TArray Data; LayerDataCache.Initialize(this->LandscapeInfo, bCombinedLayerOperation); LayerDataCache.Read(X1, Y1, X2, Y2, Data); const float PaintStrength = this->GetStrength(UISettings, bTargetIsHeightmap); // Apply the brush for (int32 Y = BrushInfo.GetBounds().Min.Y; Y < BrushInfo.GetBounds().Max.Y; Y++) { const float* BrushScanline = BrushInfo.GetDataPtr(FIntPoint(0, Y)); ValueType* DataScanline = Data.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 > 0.0f) { float Strength = FMath::Clamp(BrushValue * PaintStrength * Pressure, 0.0f, 1.0f); if (!(bUseSlopeFlatten && bTargetIsHeightmap)) { int32 Delta = DataScanline[X] - FlattenValue; switch (UISettings->FlattenMode) { case ELandscapeToolFlattenMode::Terrace: if (bTargetIsHeightmap) { const FTransform& LocalToWorld = this->Target.LandscapeInfo->GetLandscapeProxy()->ActorToWorld(); float ScaleZ = static_cast(LocalToWorld.GetScale3D().Z); float TranslateZ = static_cast(LocalToWorld.GetTranslation().Z); float TerraceInterval = FMath::Clamp(UISettings->TerraceInterval, 1.0f, 32768.f); float Smoothness = UISettings->TerraceSmooth; float WorldHeight = LandscapeDataAccess::GetLocalHeight(DataScanline[X]); //move into world space WorldHeight = (WorldHeight * ScaleZ) + TranslateZ; float CurrentHeight = WorldHeight; //smoothing part float CurrentLevel = WorldHeight / TerraceInterval; Smoothness = 1.0f / FMath::Max(Smoothness, 0.0001f); float CurrentPhase = FMath::Frac(CurrentLevel); float Halfmask = FMath::Clamp(FMath::CeilToFloat(CurrentPhase - 0.5f), 0.0f, 1.0f); CurrentLevel = FMath::FloorToFloat(WorldHeight / TerraceInterval); float SCurve = FMath::Lerp(CurrentPhase, (1.0f - CurrentPhase), Halfmask) * 2.0f; SCurve = FMath::Pow(SCurve, Smoothness) * 0.5f; SCurve = FMath::Lerp(SCurve, 1.0f - SCurve, Halfmask) * TerraceInterval; WorldHeight = (CurrentLevel * TerraceInterval) + SCurve; //end of smoothing part float FinalHeight = FMath::Lerp(CurrentHeight, WorldHeight, Strength); FinalHeight = (FinalHeight - TranslateZ) / ScaleZ; DataScanline[X] = static_cast(LandscapeDataAccess::GetTexHeight(FinalHeight)); } break; case ELandscapeToolFlattenMode::Interval: if (bTargetIsHeightmap) { const FTransform& LocalToWorld = this->Target.LandscapeInfo->GetLandscapeProxy()->ActorToWorld(); float ScaleZ = static_cast(LocalToWorld.GetScale3D().Z); float TranslateZ = static_cast(LocalToWorld.GetTranslation().Z); float TerraceInterval = UISettings->TerraceInterval; float TargetHeight = LandscapeDataAccess::GetLocalHeight(FlattenValue); float CurrentHeight = LandscapeDataAccess::GetLocalHeight(DataScanline[X]); //move into world space TargetHeight = (TargetHeight * ScaleZ) + TranslateZ; CurrentHeight = (CurrentHeight * ScaleZ) + TranslateZ; TargetHeight = (FMath::RoundToFloat(TargetHeight / TerraceInterval)) * TerraceInterval; TargetHeight = FMath::Lerp(CurrentHeight, TargetHeight, BrushValue); //back to local space of landscape object TargetHeight = (TargetHeight - TranslateZ) / ScaleZ; DataScanline[X] = static_cast(LandscapeDataAccess::GetTexHeight(TargetHeight)); } break; case ELandscapeToolFlattenMode::Raise: if (Delta < 0) { DataScanline[X] = static_cast(FMath::CeilToInt(FMath::Lerp((float)DataScanline[X], (float)FlattenValue, Strength))); } break; case ELandscapeToolFlattenMode::Lower: if (Delta > 0) { DataScanline[X] = static_cast(FMath::FloorToInt(FMath::Lerp((float)DataScanline[X], (float)FlattenValue, Strength))); } break; default: case ELandscapeToolFlattenMode::Both: if (Delta > 0) { DataScanline[X] = static_cast(FMath::FloorToInt(FMath::Lerp((float)DataScanline[X], (float)FlattenValue, Strength))); } else { DataScanline[X] = static_cast(FMath::CeilToInt(FMath::Lerp((float)DataScanline[X], (float)FlattenValue, Strength))); } break; } } else { ValueType DestValue = static_cast(-(FlattenNormal.X * X + FlattenNormal.Y * Y + FlattenPlaneDist) / FlattenNormal.Z); //float PlaneDist = FlattenNormal | FVector(X, Y, HeightData(HeightDataIndex)) + FlattenPlaneDist; float PlaneDist = static_cast(DataScanline[X] - DestValue); DestValue = static_cast(DataScanline[X] - PlaneDist * Strength); switch (UISettings->FlattenMode) { case ELandscapeToolFlattenMode::Raise: if (PlaneDist < 0) { DataScanline[X] = static_cast(FMath::CeilToInt(FMath::Lerp((float)DataScanline[X], (float)DestValue, Strength))); } break; case ELandscapeToolFlattenMode::Lower: if (PlaneDist > 0) { DataScanline[X] = static_cast(FMath::FloorToInt(FMath::Lerp((float)DataScanline[X], (float)DestValue, Strength))); } break; default: case ELandscapeToolFlattenMode::Both: if (PlaneDist > 0) { DataScanline[X] = static_cast(FMath::FloorToInt(FMath::Lerp((float)DataScanline[X], (float)DestValue, Strength))); } else { DataScanline[X] = static_cast(FMath::CeilToInt(FMath::Lerp((float)DataScanline[X], (float)DestValue, Strength))); } break; } } } } } LayerDataCache.Write(X1, Y1, X2, Y2, Data, UISettings->PaintingRestriction); // Dirty any runtime virtual textures that our landscape components write to. this->LandscapeInfo->DirtyRuntimeVirtualTextureForLandscapeArea(X1, Y1, X2, Y2); } }; template class FLandscapeToolFlatten : public FLandscapeToolPaintBase> { using Super = FLandscapeToolPaintBase>; protected: TObjectPtr HeightmapFlattenPlaneMesh; TObjectPtr HeightmapFlattenPreviewComponent; bool bCanToolBeActivatedNextTick = false; bool bCanToolBeActivatedValue = false; public: FLandscapeToolFlatten(FEdModeLandscape* InEdMode) : Super(InEdMode) , HeightmapFlattenPlaneMesh(LoadObject(nullptr, TEXT("/Engine/EditorLandscapeResources/FlattenPlaneMesh.FlattenPlaneMesh"))) { check(HeightmapFlattenPlaneMesh); } virtual bool GetCursor(EMouseCursor::Type& OutCursor) const override { if (this->EdMode->UISettings->bFlattenEyeDropperModeActivated) { OutCursor = EMouseCursor::EyeDropper; return true; } return false; } virtual void SetCanToolBeActivated(bool Value) override { bCanToolBeActivatedNextTick = true; bCanToolBeActivatedValue = Value; } virtual void AddReferencedObjects(FReferenceCollector& Collector) override { Collector.AddReferencedObject(HeightmapFlattenPlaneMesh); Collector.AddReferencedObject(HeightmapFlattenPreviewComponent); } virtual const TCHAR* GetToolName() const override { return TEXT("Flatten"); } virtual FText GetDisplayName() const override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Flatten", "Flatten"); }; virtual FText GetDisplayMessage() const override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Flatten_Message", "Raise and lower the Landscape to be the same Z height as the location from which you started using the tool."); }; virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) override { if (bCanToolBeActivatedNextTick) { this->bCanToolBeActivated = bCanToolBeActivatedValue; bCanToolBeActivatedNextTick = false; } Super::Tick(ViewportClient, DeltaTime); const ALandscapeProxy* LandscapeProxy = this->EdMode->CurrentToolTarget.LandscapeInfo.IsValid() ? this->EdMode->CurrentToolTarget.LandscapeInfo->GetLandscapeProxy() : nullptr; if ((LandscapeProxy != nullptr) && (HeightmapFlattenPreviewComponent != nullptr)) { bool bShowGrid = this->EdMode->UISettings->bUseFlattenTarget && this->EdMode->UISettings->bShowFlattenTargetPreview; HeightmapFlattenPreviewComponent->SetVisibility(bShowGrid); // Always adjust the preview component's Z so that we react to the FlattenTarget changes in the details panel, which greatly helps eyeballing the adjustment : const FTransform LocalToWorld = LandscapeProxy->ActorToWorld(); FVector Origin = HeightmapFlattenPreviewComponent->GetRelativeLocation(); Origin.Z = ConvertFlattenTargetToLocalZ(LandscapeProxy, /*bInReturnPreviewValueIfActive = */true); HeightmapFlattenPreviewComponent->SetRelativeLocation(Origin, /*bSweep = */false); } } virtual bool MouseEnter(FEditorViewportClient* InViewportClient, FViewport* Viewport, int32 InMouseX, int32 InMouseY) override { if (InViewportClient->IsLevelEditorClient() && this->EdMode->UISettings->bFlattenEyeDropperModeActivated) { this->EdMode->UISettings->bFlattenEyeDropperModeMousingOverViewport = true; } return true; } virtual bool MouseLeave(FEditorViewportClient* InViewportClient, FViewport* Viewport) override { if (InViewportClient->IsLevelEditorClient() && this->EdMode->UISettings->bFlattenEyeDropperModeActivated) { this->EdMode->UISettings->bFlattenEyeDropperModeMousingOverViewport = false; } return true; } virtual bool MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) override { bool bResult = Super::MouseMove(ViewportClient, Viewport, x, y); if (ViewportClient->IsLevelEditorClient()) { const ALandscapeProxy* LandscapeProxy = this->EdMode->CurrentToolTarget.LandscapeInfo.IsValid() ? this->EdMode->CurrentToolTarget.LandscapeInfo->GetLandscapeProxy() : nullptr; if ((LandscapeProxy != nullptr) && (HeightmapFlattenPreviewComponent != nullptr)) { FVector MousePosition; if (this->EdMode->LandscapeMouseTrace((FEditorViewportClient*)ViewportClient, x, y, MousePosition)) { const FTransform LocalToWorld = this->EdMode->CurrentToolTarget.LandscapeInfo->GetLandscapeProxy()->ActorToWorld(); FVector Origin; Origin.X = FMath::RoundToDouble(MousePosition.X); Origin.Y = FMath::RoundToDouble(MousePosition.Y); Origin.Z = ConvertFlattenTargetToLocalZ(LandscapeProxy, /*bInReturnPreviewValueIfActive = */true); HeightmapFlattenPreviewComponent->SetRelativeLocation(Origin, false); // Clamp the value to the height map uint16 TexHeight = LandscapeDataAccess::GetTexHeight(static_cast(MousePosition.Z)); float Height = LandscapeDataAccess::GetLocalHeight(TexHeight); // Convert the height back to world space this->EdMode->UISettings->FlattenEyeDropperModeDesiredTarget = static_cast(Height * LocalToWorld.GetScale3D().Z + LocalToWorld.GetTranslation().Z); } } } return bResult; } virtual void EnterTool() override { Super::EnterTool(); if (!this->EdMode->CurrentToolTarget.LandscapeInfo.Get()) { return; } this->EdMode->UISettings->bFlattenEyeDropperModeMousingOverViewport = false; ALandscapeProxy* LandscapeProxy = this->EdMode->CurrentToolTarget.LandscapeInfo.IsValid() ? this->EdMode->CurrentToolTarget.LandscapeInfo->GetLandscapeProxy() : nullptr; if ((LandscapeProxy != nullptr) && (ToolTarget::TargetType == ELandscapeToolTargetType::Heightmap)) { HeightmapFlattenPreviewComponent = NewObject(LandscapeProxy, NAME_None, RF_Transient); HeightmapFlattenPreviewComponent->SetStaticMesh(HeightmapFlattenPlaneMesh); HeightmapFlattenPreviewComponent->SetCanEverAffectNavigation(false); HeightmapFlattenPreviewComponent->AttachToComponent(LandscapeProxy->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); HeightmapFlattenPreviewComponent->RegisterComponent(); bool bShowGrid = this->EdMode->UISettings->bUseFlattenTarget && this->EdMode->UISettings->bShowFlattenTargetPreview; HeightmapFlattenPreviewComponent->SetVisibility(bShowGrid); // Try to set a sane initial location for the preview grid const FTransform LocalToWorld = LandscapeProxy->GetRootComponent()->GetComponentToWorld(); FVector Origin = FVector::ZeroVector; Origin.Z = ConvertFlattenTargetToLocalZ(LandscapeProxy, /*bInReturnPreviewValueIfActive = */false); HeightmapFlattenPreviewComponent->SetRelativeLocation(Origin, false); } } virtual void ExitTool() override { Super::ExitTool(); if (HeightmapFlattenPreviewComponent != nullptr) { HeightmapFlattenPreviewComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); HeightmapFlattenPreviewComponent->DestroyComponent(); HeightmapFlattenPreviewComponent = nullptr; } // Make sure the eye dropper tool is reset upon leaving the flatten tool : this->EdMode->UISettings->bFlattenEyeDropperModeActivated = false; this->EdMode->UISettings->bFlattenEyeDropperModeMousingOverViewport = false; } private: double ConvertFlattenTargetToLocalZ(const ALandscapeProxy* InLandscapeProxy, bool bInReturnPreviewValueIfActive) const { const FTransform LocalToWorld = InLandscapeProxy->ActorToWorld(); return (FMath::RoundToDouble((this->EdMode->UISettings->GetFlattenTarget(bInReturnPreviewValueIfActive) - LocalToWorld.GetTranslation().Z) / LocalToWorld.GetScale3D().Z * LANDSCAPE_INV_ZSCALE) - 0.1f) * LANDSCAPE_ZSCALE; } }; // // FLandscapeToolNoise // template class FLandscapeToolStrokeNoise : public FLandscapeToolStrokePaintBase { using Super = FLandscapeToolStrokePaintBase; using ValueType = typename ToolTarget::CacheClass::DataType; public: FLandscapeToolStrokeNoise(FEdModeLandscape* InEdMode, FEditorViewportClient* InViewportClient, const FLandscapeToolTarget& InTarget) : Super(InEdMode, InViewportClient, InTarget) { } void Apply(FEditorViewportClient* ViewportClient, FLandscapeBrush* Brush, const ULandscapeEditorObject* UISettings, const TArray& InteractorPositions) { TRACE_CPUPROFILER_EVENT_SCOPE(FLandscapeToolStrokeNoise_Apply); if (!this->LandscapeInfo) return; // Get list of verts to update FLandscapeBrushData BrushInfo = Brush->ApplyBrush(InteractorPositions); 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 CA_SUPPRESS(6326); if (ToolTarget::TargetType == ELandscapeToolTargetType::Heightmap) { X1 -= 1; Y1 -= 1; X2 += 1; Y2 += 1; } this->Cache.CacheData(X1, Y1, X2, Y2); TArray Data; this->Cache.GetCachedData(X1, Y1, X2, Y2, Data); float BrushSizeAdjust = 1.0f; CA_SUPPRESS(6326); if (ToolTarget::TargetType != ELandscapeToolTargetType::Weightmap && UISettings->GetCurrentToolBrushRadius() < UISettings->MaximumValueRadius) { BrushSizeAdjust = UISettings->GetCurrentToolBrushRadius() / UISettings->MaximumValueRadius; } CA_SUPPRESS(6326); bool bUseWeightTargetValue = UISettings->bUseWeightTargetValue && ToolTarget::TargetType == ELandscapeToolTargetType::Weightmap; const float PaintStrength = this->GetStrength(UISettings, /* bInIsHeighthMap = */ false); // Apply the brush for (int32 Y = BrushInfo.GetBounds().Min.Y; Y < BrushInfo.GetBounds().Max.Y; Y++) { const float* BrushScanline = BrushInfo.GetDataPtr(FIntPoint(0, Y)); ValueType* DataScanline = Data.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 > 0.0f) { float OriginalValue = DataScanline[X]; if (bUseWeightTargetValue) { FNoiseParameter NoiseParam(0, UISettings->NoiseScale, 255.0f / 2.0f); float DestValue = NoiseModeConversion(ELandscapeToolNoiseMode::Add, NoiseParam.NoiseAmount, NoiseParam.Sample(X, Y)) * UISettings->WeightTargetValue; switch (UISettings->NoiseMode) { case ELandscapeToolNoiseMode::Add: if (OriginalValue >= DestValue) { continue; } break; case ELandscapeToolNoiseMode::Sub: DestValue += (1.0f - UISettings->WeightTargetValue) * NoiseParam.NoiseAmount; if (OriginalValue <= DestValue) { continue; } break; } DataScanline[X] = ToolTarget::CacheClass::ClampValue(FMath::RoundToInt(FMath::Lerp(OriginalValue, DestValue, BrushValue * UISettings->GetCurrentToolStrength() * Pressure))); } else { float TotalStrength = BrushValue * PaintStrength * Pressure * ToolTarget::StrengthMultiplier(this->LandscapeInfo, UISettings->GetCurrentToolBrushRadius()); FNoiseParameter NoiseParam(0, UISettings->NoiseScale, TotalStrength * BrushSizeAdjust); float PaintAmount = NoiseModeConversion(UISettings->NoiseMode, NoiseParam.NoiseAmount, NoiseParam.Sample(X, Y)); DataScanline[X] = static_cast(ToolTarget::CacheClass::ClampValue(static_cast(OriginalValue + PaintAmount))); } } } } this->Cache.SetCachedData(X1, Y1, X2, Y2, Data, UISettings->PaintingRestriction); this->Cache.Flush(); // Dirty any runtime virtual textures that our landscape components write to. this->LandscapeInfo->DirtyRuntimeVirtualTextureForLandscapeArea(X1, Y1, X2, Y2); } }; template class FLandscapeToolNoise : public FLandscapeToolPaintBase> { using Super = FLandscapeToolPaintBase>; public: FLandscapeToolNoise(FEdModeLandscape* InEdMode) : Super(InEdMode) { } virtual const TCHAR* GetToolName() const override { return TEXT("Noise"); } virtual FText GetDisplayName() const override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Noise", "Noise"); }; virtual FText GetDisplayMessage() const override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Noise_Message", "The Noise tool applies a noise filter to the heightmap or layer weight. The strength determines the amount of noise."); }; }; // // Toolset initialization // void FEdModeLandscape::InitializeTool_Paint() { TUniquePtr Tool_Sculpt = MakeUnique(this); Tool_Sculpt->ValidBrushes.Add("BrushSet_Circle"); Tool_Sculpt->ValidBrushes.Add("BrushSet_Alpha"); Tool_Sculpt->ValidBrushes.Add("BrushSet_Pattern"); Tool_Sculpt->ValidBrushes.Add("BrushSet_Component"); LandscapeTools.Add(MoveTemp(Tool_Sculpt)); TUniquePtr Tool_Erase = MakeUnique(this); Tool_Erase->ValidBrushes.Add("BrushSet_Circle"); LandscapeTools.Add(MoveTemp(Tool_Erase)); TUniquePtr Tool_Paint = MakeUnique(this); Tool_Paint->ValidBrushes.Add("BrushSet_Circle"); Tool_Paint->ValidBrushes.Add("BrushSet_Alpha"); Tool_Paint->ValidBrushes.Add("BrushSet_Pattern"); Tool_Paint->ValidBrushes.Add("BrushSet_Component"); LandscapeTools.Add(MoveTemp(Tool_Paint)); } void FEdModeLandscape::InitializeTool_Smooth() { TUniquePtr> Tool_Smooth_Heightmap = MakeUnique>(this); Tool_Smooth_Heightmap->ValidBrushes.Add("BrushSet_Circle"); Tool_Smooth_Heightmap->ValidBrushes.Add("BrushSet_Alpha"); Tool_Smooth_Heightmap->ValidBrushes.Add("BrushSet_Pattern"); LandscapeTools.Add(MoveTemp(Tool_Smooth_Heightmap)); TUniquePtr> Tool_Smooth_Weightmap = MakeUnique>(this); Tool_Smooth_Weightmap->ValidBrushes.Add("BrushSet_Circle"); Tool_Smooth_Weightmap->ValidBrushes.Add("BrushSet_Alpha"); Tool_Smooth_Weightmap->ValidBrushes.Add("BrushSet_Pattern"); LandscapeTools.Add(MoveTemp(Tool_Smooth_Weightmap)); } void FEdModeLandscape::InitializeTool_Flatten() { TUniquePtr> Tool_Flatten_Heightmap = MakeUnique>(this); Tool_Flatten_Heightmap->ValidBrushes.Add("BrushSet_Circle"); Tool_Flatten_Heightmap->ValidBrushes.Add("BrushSet_Alpha"); Tool_Flatten_Heightmap->ValidBrushes.Add("BrushSet_Pattern"); LandscapeTools.Add(MoveTemp(Tool_Flatten_Heightmap)); TUniquePtr> Tool_Flatten_Weightmap = MakeUnique>(this); Tool_Flatten_Weightmap->ValidBrushes.Add("BrushSet_Circle"); Tool_Flatten_Weightmap->ValidBrushes.Add("BrushSet_Alpha"); Tool_Flatten_Weightmap->ValidBrushes.Add("BrushSet_Pattern"); LandscapeTools.Add(MoveTemp(Tool_Flatten_Weightmap)); } void FEdModeLandscape::InitializeTool_Noise() { TUniquePtr> Tool_Noise_Heightmap = MakeUnique>(this); Tool_Noise_Heightmap->ValidBrushes.Add("BrushSet_Circle"); Tool_Noise_Heightmap->ValidBrushes.Add("BrushSet_Alpha"); Tool_Noise_Heightmap->ValidBrushes.Add("BrushSet_Pattern"); LandscapeTools.Add(MoveTemp(Tool_Noise_Heightmap)); TUniquePtr> Tool_Noise_Weightmap = MakeUnique>(this); Tool_Noise_Weightmap->ValidBrushes.Add("BrushSet_Circle"); Tool_Noise_Weightmap->ValidBrushes.Add("BrushSet_Alpha"); Tool_Noise_Weightmap->ValidBrushes.Add("BrushSet_Pattern"); LandscapeTools.Add(MoveTemp(Tool_Noise_Weightmap)); } #undef LOCTEXT_NAMESPACE