Terrain Determination in Perlin Noise Texture in Godot 4.2.1

Godot Version



For my Grand Strategy game, I am looking to use maps generated by Godot’s FastNoiseLite implementation of Perlin noise. While generating the texture for the map was straightforward, I’ve already spent weeks to figure out a way to determine the overall terrain type of Voronoi cells drawn on the map by checking the noise values for a set of “checkpoints” and applying a set of rules . For this, I implemented a “Star Check” to spread points on the borders of each cell and from those border points to the respective site points, like so:

func star_check(cell_center: Vector2, n_steps: int=10) -> Array:
    var terrain_types: Array
    var polygon: Array = _polygon
    var n_points = polygon.size()
    var checkpoints: Array
    var lines: Array

    # read in lines as arrays of [start, end point, distance]
    for i in range(n_points):
        var last_index: int = n_points - 1
        var line: Array
        match i:
                line = [polygon[i],polygon[0],polygon[i].distance_to(polygon[0])]
                line = [polygon[i],polygon[i + 1],polygon[i].distance_to(polygon[i + 1])]


    var border_checkpoints: Array
    # compute ceckpoints along edges of points
    for line in lines:
        var line_points: Array = []
        var step_size: int = floor(line[2] / n_steps)
        var from: Vector2 = line[0]
        var to: Vector2 = line[1]

        for k in range(n_steps):
            var point: Vector2 = from + k * step_size * from.direction_to(to)


    var center_lines: Array = border_checkpoints.map(func(p):

        return [p, cell_center, p.distance_to(cell_center)]

    # compute center checkpoints from border points
    var center_checkpoints: Array
    for line in center_lines:
        var line_points: Array = []
        var step_size: int = floor(line[2] / n_steps)
        var from: Vector2 = Vector2(line[0])
        var to: Vector2 = Vector2(line[1])

        for k in range(n_steps):
            var point: Vector2 = from + k * step_size * from.direction_to(to)



    checkpoints = border_checkpoints.filter(func(p): return Geometry2D.is_point_in_polygon(p, _polygon)) #only use points in voronoi site
    check_points = checkpoints #internal class_variable

    terrain_types = checkpoints.map(func(p): return _evaluate_point_type(p))
    check_point_values = terrain_types #internal class_variables

    return terrain_types

Determining the terrain type of a point is done in _evaluate_point_type using enums to represent their position in the (non-normalized and centered) NoiseTexture’s color ramp:


func _evaluate_point_type(point: Vector2) -> int:
    var point_value = ((_map_noise.get_noise_2dv(point) + 1) * 0.5) #translate to [0,1] for offset matching
    var type: int = -1
    var offsets = _terrain_gradient.offsets
    var last_index: int = len(offsets) - 1
    for i in range(len(offsets)): #find corresponding offset
        match i:
                if point_value < offsets[Terrains.SHALLOW_OCEAN]: #everything below first non-zero offset
                    type = Terrains.DEEP_OCEAN
                if point_value > offsets[Terrains.MOUNTAIN_TOP]: # everything above highest offset
                    type = Terrains.MOUNTAIN_TOP
                if point_value >= offsets[i] and point_value < offsets[i + 1]:
                    type = i

    return type

The noise gradient is loaded from a saved Gradient resource that corresponds to the color ramp used by the noise texture:

[gd_resource type="Gradient" format=3 uid="uid://wrob2ye40vuu"]

interpolation_mode = 1
offsets = PackedFloat32Array(0, 0.481236, 0.505519, 0.516556, 0.626932, 0.660044, 0.679912)
colors = PackedColorArray(0, 0, 1, 1, 0.299985, 0.470862, 1, 1, 0.657183, 0.508635, 0.167871, 1, 0.280679, 0.399257, 0.108318, 1, 0.333719, 0.196772, 0.0853799, 1, 0.396025, 0.358924, 0.223805, 1, 1, 1, 1, 1)

and the noise texture’s underlying parameters are loaded from this resource:

[gd_resource type="FastNoiseLite" format=3 uid="uid://davh6t1gndhdm"]

noise_type = 3
frequency = 0.002
fractal_lacunarity = 2.575
domain_warp_type = 1
domain_warp_amplitude = 29.055

Now, instead of a representation of the point type, I get results that don’t correspond to what I see on the texture. When plotting these points using the color in the gradient corresponding to their values, I often find blue ocean points on green grassland portions or coastline points showing up in ocean parts. I’d be grateful for any pointers to what I might be doing wrong here.

Example of point on “mountain” portion of gradient being evaluated as an ocean point (circle is larger than point for visibility)