The noise utility library

In this lesson, we look at Utils.gd, a library script with a few utility functions to get the most out of our noise generators.

In the previous part, we processed and baked the height_map using the domain_warp() function. The other noise textures, heat_map and moisture_map, aren’t normalized, however. While we could process them in GDScript, using CPU computing, I want to show you how to do it on the GPU instead. We need to calculate two values, heat_map_minmax and moisture_map_minmax, and pass them to the shader. This is where our Utils library class comes in.

Create a new Utils.gd script, remove all code from it, and define the class’s name:

class_name Utils

Doing so registers the Utils type globally, allowing us to access to it from anywhere in our project. This class’s purpose is to provide some utility functions. Thanks to this, you can easily reuse the library across projects.

Let’s add our first pair of functions:

# Normalizes a value from a noise generator based on the noise's minimum and maximum range.
static func normalize_noise(value: float, minmax := Vector2(-1, 1)) -> float:
    return range_lerp(value, minmax.x, minmax.y, 0, 1)


# Normalizes the components of a 2D vector individually and returns them as a new Vector2.
# Intended to use with the minmax range of a noise generator.
static func normalize_noise_vector2(value: Vector2) -> Vector2:
    return Vector2(normalize_noise(value.x), normalize_noise(value.y))

Notice we gave the default value of Vector2(-1, 1) to the minmax parameter of normalize_noise() function. OpenSimplexNoise.get_noise_*() functions return values in the [-1, 1] interval while the textures in the shader work with [0, 1]. That’s the reason we need to normalize the noise values. Thus we need these two functions.

Let’s add our last function to Utils:

# Returns the minimum and maximum value of a noise texture as a Vector2.
static func get_minmax_noise(texture: NoiseTexture) -> Vector2:
    var out := Vector2(INF, -INF)
    for x in texture.width:
        for y in texture.height:
            var value := texture.noise.get_noise_2d(y, x)
            out.x = min(out.x, value)
            out.y = max(out.y, value)
    return out

We will use get_minmax_noise() on heat_map and moisture_map, combined with normalize_noise_vector2(), to get the interval, that is to say, the minimum and maximum values of the noise values. We use these values to normalize the textures on the GPU side.

It might seem convoluted to do it this way. Still, we wanted to highlight one caveat with the OpenSimplexNoise implementation in Godot.

If you check the for loop in get_minmax_noise() you’ll see that we call texture.noise.get_noise_2d(y, x). We pass the y coordinate as the first argument instead of using the more intuitive order (x, y). That’s how OpenSimplexNoise implements these functions in Godot 3.2.2 compared to OpenSimplexNoise.get_image() and OpenSimplexNoise.get_seamless_image(). The axes are swaped.

This issue has been fixed in the Godot repository with PR #30424, but the fix isn’t in Godot 3.2.2, the version we used for this series.

This concludes the noise utility library lesson. Next, we’ll cover how to generate the rivers. Then, we’ll put everything together to get our world map result with rivers and the full range of the normalized noise values.

References

This listing shows the entire Utils.gd code.

class_name Utils


# Normalizes a value from a noise generator based on the noise's minimum and maximum range.
static func normalize_noise(value: float, minmax := Vector2(-1, 1)) -> float:
    return range_lerp(value, minmax.x, minmax.y, 0, 1)


# Normalizes the components of a 2D vector individually and returns them as a new Vector2.
# Intended to use with the minmax range of a noise generator.
static func normalize_noise_vector2(value: Vector2) -> Vector2:
    return Vector2(normalize_noise(value.x), normalize_noise(value.y))


# Returns the minimum and maximum value of a noise texture as a Vector2.
static func get_minmax_noise(texture: NoiseTexture) -> Vector2:
    var out := Vector2(INF, -INF)
    for x in texture.width:
        for y in texture.height:
            var value := texture.noise.get_noise_2d(y, x)
            out.x = min(out.x, value)
            out.y = max(out.y, value)
    return out