Landscape API – Landscape generation with Perlin noise

If you use ALandscape in your scene, you may want to program the heights on it using code instead of manually brushing it in. To access the ALandscape object and its functions inside of your code, you must compile and link in the Landscape and LandscapeEditor APIs.

Landscape API – Landscape generation with Perlin noise

Getting ready

Generating a landscape is not terribly challenging. You need to link in both the Landscape and LandscapeEditor APIs, and also have a programmatic way to set the height values across the map. In this recipe, we'll show how to use the Perlin noise for this.

Previously, you may have seen Perlin noise used for coloration, but that is not all it is good for. It is excellent for terrain heights as well. You can sum multiple Perlin noise values to get beautiful fractal noise. It is worth a brief study of Perlin noise to understand how to get good outputs.

How to do it…

  1. Retrieve the Perlin noise module from http://webstaff.itn.liu.se/~stegu/aqsis/aqsis-newnoise/. The two files you'll need are noise1234.h and noise1234.cpp (or you can select another pair of noise generation files from this repository if you wish). Link these files into your project and be sure to #include YourPrecompiledHeader.h into noise1234.cpp.
  2. Link in the Landscape and LandscapeEditor APIs in your Project.Build.cs file.
  3. Construct an interface using UMG that allows you to click a Generate button to call a C++ function that will ultimately populate the current Landscape with Perlin noise values. You can do this as follows:
    • Right-click on your Content Browser and select User Interface | Widget Blueprint.
    • Populate Widget Blueprint with a single button that kicks off a single Gen() function. The Gen() function can be attached to your Chapter12GameMode derived class object as that is easy to retrieve from the engine. The Gen() function must be BlueprintCallable UFUNCTION(). (See the Creating a UFUNCTION section in Chapter 2, Creating Classes, for details on how to do so.)
      How to do it…
    • Be sure to display your UI by creating it and adding it to the viewport in one of your booting Blueprints; for example, in your HUD's BeginPlay event.
    How to do it…
  4. Create a Landscape using the UE4 Editor. The landscape will be assumed to stay on screen. We will only modify its values using code.
  5. Inside your map generation routine, modify your ALandscape object using code that does the following:
    • Find the Landscape object in the level by searching through all objects in the Level. We do this using a C++ function that returns TArray of all Landscape instances in the level:
      TArray<ALandscape*> AChapter12GameMode::GetLandscapes()
      {
        TArray<ALandscape*> landscapes;
        ULevel *level = GetLevel();
        for( int i = 0; i < level->Actors.Num(); i++ )
        if( ALandscape* land = Cast<ALandscape>(level->Actors[i]) )
        landscapes.Push( land );
        return landscapes;
      }
    • Initialize the world's ULandscapeInfo objects for ALandscape editing using the very important line, which is as follows:
      ULandscapeInfo::RecreateLandscapeInfo( GetWorld(), 1 );

      Note

      The preceding line of code is extremely important. Without it, the ULandscapeInfo objects will not be initialized and your code will not work. Surprisingly, this is a static member function of the ULandscapeInfo class, and so it initializes all ULandscapeInfo objects within the level.

    • Get extents of your ALandscape object so that we can compute the number of height values we will need to generate.
    • Creates a set of height values to replace original values.
    • Calls LandscapeEditorUtils::SetHeightmapData( landscape, data ); to park new landscape height values into your ALandscape object.

      For example, use the following code:

      // a) REQUIRED STEP: Call static function
      // ULandscapeInfo::RecreateLandscapeInfo().
      // What this does is populate the Landscape object with
      // data values so you don't get nulls for your 
      // ULandscapeInfo objects on retrieval.
      ULandscapeInfo::RecreateLandscapeInfo( GetWorld(), 1 );
      
      // b) Assuming landscape is your landscape object pointer,
      // get extents of landscape, to compute # height values
      FIntRect landscapeBounds = landscape->GetBoundingRect();
      
      // c) Create height values.
      // LandscapeEditorUtils::SetHeightmapData() adds one to 
      // each dimension because the boundary edges may be used.
      int32 numHeights = (rect.Width()+1)*(rect.Height()+1);
      TArray<uint16> Data;
      Data.Init( 0, numHeights );
      for( int i = 0; i < Data.Num(); i++ ) {
        float nx = (i % cols) / cols; // normalized x value
        float ny = (i / cols) / rows; // normalized y value
        Data[i] = PerlinNoise2D( nx, ny, 16, 4, 4 );
      }
      
      // d) Set values in with call:
      LandscapeEditorUtils::SetHeightmapData( landscape, Data );

      Tip

      The initial values of heightmap will all be 32768 (SHRT_MAX (or USHRT_MAX/2+1)) when the map is completely flat. This is because the map uses unsigned shorts (uint16) for its values, making it incapable of taking on negative values. For the map to dip below z=0, the programmers made the default value half of the maximum value of heightmap.

How it works…

The Perlin noise function is used to generate a height value for (x, y) coordinate pairs. The 2D version of Perlin noise is used so that we can get a Perlin noise value based on 2-space spatial coordinates.

There's more…

You can play with the Perlin noise functions with the spatial coordinates of the map, and assign the heights of the maps to different combinations of the Perlin noise function. You will want to use a sum of multiple octaves of the Perlin noise function to get more detail into the landscape.

The PerlinNoise2D generation function looks as follows:

uint16 AChapter12GameMode::PerlinNoise2D( float x, float y,
  float amp, int32 octaves, int32 px, int32 py )
{
  float noise = 0.f;
  for( int octave = 1; octave < octaves; octave *= 2 )
  {
    // Add in fractions of faster varying noise at lower 
    // amplitudes for higher octaves. Assuming x is normalized, 
    // WHEN octave==px  you get full period. Higher frequencies 
    // will go out and also meet period.
    noise += Noise1234::pnoise( x*px*octave, y*py*octave, px, py ) / octave;
  }
  return USHRT_MAX/2.f + amp*noise;
}

The PerlinNoise2D function accounts for the fact that the mid-level value of the function (sea level or flat land) should have a value of SHRT_MAX (32768).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.205.183