extends SceneTree const MAP_SIZE := 512 const HALF_SIZE := MAP_SIZE / 2 const HEIGHT_SCALE := 40.0 const DATA_DIRECTORY := "res://terrain/jajce/data" const TEXTURE_DIRECTORY := "res://terrain/jajce/textures" const Watercourse := preload("res://world/jajce/JajceWatercourse.gd") func _initialize() -> void: call_deferred("_generate") func _generate() -> void: DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(TEXTURE_DIRECTORY)) _generate_texture("grass_albedo.png", Color("#516f36"), Color("#79924c"), 17) _generate_texture("soil_albedo.png", Color("#65513b"), Color("#92734d"), 29) _generate_texture("rock_albedo.png", Color("#55534f"), Color("#89847b"), 43) _generate_neutral_normal() var terrain := Terrain3D.new() terrain.region_size = 256 root.add_child(terrain) await process_frame var height_map := Image.create_empty(MAP_SIZE, MAP_SIZE, false, Image.FORMAT_RF) var noise := FastNoiseLite.new() noise.seed = 1943 noise.frequency = 0.018 noise.fractal_octaves = 4 noise.fractal_gain = 0.45 for image_z in MAP_SIZE: for image_x in MAP_SIZE: var world_x := float(image_x - HALF_SIZE) var world_z := float(image_z - HALF_SIZE) var distance := Vector2(world_x, world_z).length() var outside_play_area := smoothstep(20.0, 52.0, distance) var ridge := 28.0 * _elliptical_peak(world_x, world_z, -72.0, 18.0, 72.0, 48.0) var eastern_hill := 15.0 * _elliptical_peak(world_x, world_z, 105.0, 65.0, 110.0, 90.0) var southern_hill := ( 11.0 * _elliptical_peak(world_x, world_z, -35.0, -125.0, 130.0, 85.0) ) var broad_slope := maxf(0.0, (-world_x - 30.0) * 0.035) var detail := noise.get_noise_2d(world_x, world_z) * 2.4 var height := ( (ridge + eastern_hill + southern_hill + broad_slope + detail) * outside_play_area ) height = _shape_waterfall_shelf(height, world_x, world_z, detail) height = _shape_upper_stream(height, world_x, world_z) height = _shape_plunge_pool(height, world_x, world_z) height = _shape_downstream_channel(height, world_x, world_z) height_map.set_pixel(image_x, image_z, Color(height / HEIGHT_SCALE, 0.0, 0.0, 1.0)) terrain.data.import_images( [height_map, null, null], Vector3(-HALF_SIZE, 0.0, -HALF_SIZE), 0.0, HEIGHT_SCALE ) terrain.data.save_directory(DATA_DIRECTORY) print("[TerrainGenerator] Generated bounded Jajce terrain and textures") quit(0) func _elliptical_peak( x: float, z: float, center_x: float, center_z: float, width: float, depth: float ) -> float: return exp(-(pow((x - center_x) / width, 2.0) + pow((z - center_z) / depth, 2.0))) func _shape_waterfall_shelf(height: float, x: float, z: float, detail: float) -> float: var horizontal_mask := 1.0 - smoothstep(10.0, 28.0, absf(x - Watercourse.WATERFALL_CENTER.x)) var upstream_mask := smoothstep(-76.0, -54.0, z) var drop_mask := 1.0 - smoothstep(-25.0, -21.0, z) var shelf_mask := horizontal_mask * upstream_mask * drop_mask var shelf_height := Watercourse.SHELF_TOP_HEIGHT + detail * 0.08 return lerpf(height, maxf(height, shelf_height), shelf_mask) func _shape_upper_stream(height: float, x: float, z: float) -> float: if z < Watercourse.UPPER_STREAM_START_Z or z > Watercourse.UPPER_STREAM_END_Z: return height var center_x := Watercourse.upper_stream_center_x(z) var half_width := Watercourse.upper_stream_half_width(z) var channel_mask := 1.0 - smoothstep(half_width, half_width + 3.2, absf(x - center_x)) var target_height := minf(height, Watercourse.upper_stream_bed_height(z)) return lerpf(height, target_height, channel_mask) func _shape_plunge_pool(height: float, x: float, z: float) -> float: var offset := Vector2(x, z) - Watercourse.PLUNGE_POOL_CENTER var elliptical_distance := Vector2(offset.x / 10.5, offset.y / 7.8).length() var basin_mask := 1.0 - smoothstep(0.68, 1.15, elliptical_distance) var cliff_front_mask := smoothstep(-25.0, -20.5, z) var target_height := minf(height, Watercourse.PLUNGE_POOL_BED_HEIGHT) return lerpf(height, target_height, basin_mask * cliff_front_mask) func _shape_downstream_channel(height: float, x: float, z: float) -> float: if z < Watercourse.DOWNSTREAM_START_Z or z > Watercourse.DOWNSTREAM_END_Z: return height var center_x := Watercourse.downstream_center_x(z) var half_width := Watercourse.downstream_half_width(z) var bank_mask := 1.0 - smoothstep(half_width, half_width + 4.5, absf(x - center_x)) var target_height := minf(height, Watercourse.downstream_bed_height(z)) return lerpf(height, target_height, bank_mask) func _generate_texture( file_name: String, dark_color: Color, light_color: Color, seed_value: int ) -> void: var noise := FastNoiseLite.new() noise.seed = seed_value noise.frequency = 0.035 noise.fractal_octaves = 3 var image := Image.create_empty(256, 256, false, Image.FORMAT_RGBA8) for y in image.get_height(): for x in image.get_width(): var value := noise.get_noise_2d(x, y) * 0.5 + 0.5 var color := dark_color.lerp(light_color, value) color.a = value image.set_pixel(x, y, color) image.generate_mipmaps() var path := TEXTURE_DIRECTORY.path_join(file_name) var error := image.save_png(path) if error != OK: push_error("Could not write terrain texture: " + path) func _generate_neutral_normal() -> void: var image := Image.create_empty(256, 256, false, Image.FORMAT_RGBA8) image.fill(Color(0.5, 0.5, 1.0, 0.86)) image.generate_mipmaps() var path := TEXTURE_DIRECTORY.path_join("neutral_normal.png") var error := image.save_png(path) if error != OK: push_error("Could not write terrain normal texture: " + path)