Thank you for pointing me in the right direction. After a week of effort, I successfully created a function to deform or raise the landscape at a specific world location. This function can also be customized to support various landscape modification use cases.
#include "Landscape.h"
#include "LandscapeComponent.h"
#include "LandscapeEdit.h"
void AdjustLandscapeAtWorldLocation(const FVector& WorldLocation, ALandscape* Landscape, float AdjustRadius, float EdgeBlendFactor)
{
if (!Landscape) return;
EdgeBlendFactor = FMath::Clamp(EdgeBlendFactor, 0.0f, 1.0f);
if (Landscape->bCanHaveLayersContent == true)
{
Landscape->ToggleCanHaveLayersContent();
}
ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo();
if (!LandscapeInfo) return;
FIntRect LandscapeExtent;
if (!LandscapeInfo->GetLandscapeExtent(LandscapeExtent)) return;
const FVector LandscapeOrigin = Landscape->GetActorLocation();
const FVector LandscapeScale = Landscape->GetActorScale3D();
// Convert world location to landscape-space coordinates
FVector LocalLocation = (WorldLocation - LandscapeOrigin) / LandscapeScale;
int32 CenterX = FMath::RoundToInt(LocalLocation.X);
int32 CenterY = FMath::RoundToInt(LocalLocation.Y);
int32 AdjustRadiusInGridUnits = FMath::CeilToInt(AdjustRadius / LandscapeScale.X);
// Calculate the bounds of the affected area
int32 MinX = FMath::Clamp(CenterX - AdjustRadiusInGridUnits, LandscapeExtent.Min.X, LandscapeExtent.Max.X);
int32 MinY = FMath::Clamp(CenterY - AdjustRadiusInGridUnits, LandscapeExtent.Min.Y, LandscapeExtent.Max.Y);
int32 MaxX = FMath::Clamp(CenterX + AdjustRadiusInGridUnits, LandscapeExtent.Min.X, LandscapeExtent.Max.X);
int32 MaxY = FMath::Clamp(CenterY + AdjustRadiusInGridUnits, LandscapeExtent.Min.Y, LandscapeExtent.Max.Y);
int32 Width = MaxX - MinX + 1;
int32 Height = MaxY - MinY + 1;
//Create an instance of FLandscapeEditDataInterface
FHeightmapAccessor<false> HeightmapAccessor(LandscapeInfo);
TArray<uint16> HeightData;
HeightData.SetNum(Width * Height);
// Get the current heightmap data
HeightmapAccessor.GetDataFast(MinX, MinY, MaxX, MaxY, HeightData.GetData());
// Calculate the target height based on WorldLocation.Z
float BaseHeightInWorld = WorldLocation.Z - LandscapeOrigin.Z;
const uint16 FlatHeightValue = 32768;
uint16 TargetHeight = FMath::Clamp<uint16>(FlatHeightValue + BaseHeightInWorld * 128.0f / LandscapeScale.Z, 0, UINT16_MAX);
for (int32 Y = 0; Y < Height; ++Y)
{
for (int32 X = 0; X < Width; ++X)
{
// Adjust the heightmap with a smooth falloff
float Distance = FVector2D::Distance(FVector2D(X + MinX, Y + MinY), FVector2D(CenterX, CenterY));
if (Distance > AdjustRadiusInGridUnits) continue;
float EffectiveRadius = AdjustRadiusInGridUnits * EdgeBlendFactor;
float NormalizedDistance = FMath::Clamp((Distance - (AdjustRadiusInGridUnits - EffectiveRadius)) / EffectiveRadius, 0.0f, 1.0f);
float Falloff = (EdgeBlendFactor > 0.0f) ? FMath::SmoothStep(0.0f, 1.0f, 1.0f - NormalizedDistance) : 1.0f;
uint16 CurrentHeight = HeightData[X + Y * Width];
HeightData[X + Y * Width] = FMath::Lerp(CurrentHeight, TargetHeight, Falloff);
}
}
// Set the new heightmap data
HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, HeightData.GetData());
// Flush any changes to the heightmap
HeightmapAccessor.Flush();
}
The world location defines the exact point in 3D space where the landscape modification occurs. The EdgeBlendFactor
is a parameter ranging from 0 to 1, which controls the smoothness or blending at the edges of the modified area.
For those curious, the flat heightmap value is 32768. Since a uint16
ranges from 0 to 65535, 32768 represents the midpoint. Additionally, the default landscape scale in the engine is 128.0f
.