Procedural Landscapes - editing the heightmap in C++?

Hi there,

I’m loving the new Landscape system. I’ve combed over the documentation but I’m not certain if there’s a way to achieve what I want - at runtime, would I be able to change the heightmap of a terrain? For example, I plan to use LibNoise as a plugin to generate my landscapes on the fly. Is this possible?

Thanks!

It’s possible with some dances around, but you have to check the Landscapes code. It’s not a “documented” thing.

Any pointers? I’ve had a browse but it seems quite non-obvious.

I’m trying to do add a kind of geommip mapping and I need to modify the heightmap too.

Here is the code I’m working on.




//my input is a landscape object
void UUpdateLandscape::UpdateTile(ALandscape *landscape)
{

UTexture2D *heightMap = landscape->LandscapeComponents[0]->HeightmapTexture;
EPixelFormat formatPixel = heightMap->GetPixelFormat();

//texture resolution
int sizeX = heightMap->GetSizeX();
int sizeY = heightMap->GetSizeY();

int pixelSize = sizeof(formatPixel)*8;
int pitch = pixelSize * sizeX;

FUpdateTextureRegion2D region;
region.DestX = 0;
region.DestY = 0;
region.SrcX = 0;
region.SrcY = 0;
region.Width = sizeX;
region.Height = sizeY;



                                //foreach pixel, I will change the height. Today, it's done by the CPU but I will move it later to the GPU
				for (int x = 0; x < sizeX; x++)
				{
					for (int y = 0; y < sizeY; y++)
					{
						
					}
				}

heightMap->UpdateTextureRegions(0, 1, &region, pitch, pixelSize, NULL, false);
}



Today, the new data pointer is set to NULL but I’ve a problem when I compile the file. The linking operation failed because visual studio doesn’t find "external reference. I’m searching why and I let you know later if this code is working (or not…).

1 Like

Any update on this? I’ve ran into the same problem, and I’d love to know a solution

Nevermind, I figured this out

Would love it if you shared your solution.

What did you found.

wisdom_of_the_ancients.png

WHAT DID YOU SEE, MatrixCompSci!!!

1 Like

I’m inputing wild numbers from 0 to 255 into that piece of code set there but nothing gets shown in the landscape when the level starts… Is there any missing call to make the landscape dynamically update after the texture changes?

https://docs.unrealengine.com/en-US/…ers/index.html

https://docs.unrealengine.com/en-US/…int/index.html

https://dq8iqaixvew1d.cloudfront.net…ent/index.html

The code for the layer brushes lives in the “Runtime” part of Landmass, so I think you should just be able to turn this on, create displacements, and go wild!
Don’t use static/pre-computed lighting, though …

If that doesn’t work, you can use world offset in landscape materials, if you enable that checkbox on the landscape actor. Turn on world offset, sample a dynamic texture, material moves!

I recently managed to write custom heightmap data to the Landscape using the helper class FHeightmapAccessor. I haven’t tried it in runtime yet though, but I think it might be possible.

  1. As a starting point, have a look at the following function, found in /Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdMode.cpp
bool LandscapeEditorUtils::SetHeightmapData(ALandscapeProxy* Landscape, const TArray<uint16>& Data)
{
	FIntRect ComponentsRect = Landscape->GetBoundingRect() + Landscape->LandscapeSectionOffset;

	if (Data.Num() == (1 + ComponentsRect.Width())*(1 + ComponentsRect.Height()))
	{
		FHeightmapAccessor<false> HeightmapAccessor(Landscape->GetLandscapeInfo());
		HeightmapAccessor.SetData(ComponentsRect.Min.X, ComponentsRect.Min.Y, ComponentsRect.Max.X, ComponentsRect.Max.Y, Data.GetData());
		return true;
	}

	return false;
}

…and just replicate that code. You don’t want to use that function directly since it is in an Editor module. The FHeightmapAccessor will update the internal textures, includig mips, and recalculate the normals for you.

ComponentsRect corresponds to the number of quads, meaning that for example Width() + 1 is the number of vertices along the X-axis.

If you want to manually set normals etc, just look inside the implementation of SetData() and replicate what it does but pass also normal data etc to LandscapeEdit->SetHeightData(...).

  1. Though, you probably first need to convert your floating point heights to uint16 packed for the heightmaps. You can do it like this, per height in your array:
#include "LandscapeDataAccess.h"

...

float HeightCm = /* height in centimeter */
float HeightLocal = HeightCm / Landscape->GetTransform().GetScale3D().Z;
uint16 HeightTex = LandscapeDataAccess::GetTexHeight(HeightLocal);
2 Likes

My data pointer is null. How to fix this?

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.