Generate complete freshwater and elevated terrain features

This commit is contained in:
Alexander Sellite 2026-08-22 18:38:17 -04:00
parent 219c462181
commit f2b96f2c1f
7 changed files with 2105 additions and 141 deletions

View file

@ -14,6 +14,7 @@ const GeneratedRiverPool: FishPool = preload(
)
const FIRST_SEED := 13001
const SECOND_SEED := 13002
const RIVER_FALLBACK_SEED := 13012
const EXPECTED_PROP_IDS: Array[StringName] = [
&"prop_bridge",
&"prop_dock",
@ -62,6 +63,9 @@ func _run() -> void:
) as TerrainChunkGenerator
assert(configured_generator != null)
configured_generator.elevated_cliff_third_level_chance = 1.0
configured_generator.elevated_cliff_third_tier_base_chance = 0.0
configured_generator.elevated_cliff_third_tier_stack_chance = 1.0
configured_generator.elevated_cliff_double_third_tier_chance = 0.0
root.add_child(region)
await process_frame
await physics_frame
@ -74,13 +78,19 @@ func _run() -> void:
_validate_generated_region(region, generator)
var first_layout := generator.placement_keys()
var first_stacked_layout := generator.stacked_elevated_placement_keys()
var first_biomes := _biome_signature(region, generator)
var first_decorations := _decoration_signature(region)
assert(region.generate_world(FIRST_SEED))
assert(generator.placement_keys() == first_layout)
assert(_biome_signature(region, generator) == first_biomes)
assert(_decoration_signature(region) == first_decorations)
generator._elevated_feature_center = Vector2i(-1, -1)
assert(generator.generate_from_placement_keys(first_layout))
assert(generator.stacked_elevated_placement_keys() == first_stacked_layout)
configured_generator.elevated_cliff_third_tier_base_chance = 1.0
configured_generator.elevated_cliff_third_tier_stack_chance = 0.0
assert(region.generate_world(SECOND_SEED))
await process_frame
await physics_frame
@ -88,6 +98,17 @@ func _run() -> void:
_validate_generated_region(region, generator)
assert(generator.placement_keys() != first_layout)
assert(_biome_signature(region, generator) != first_biomes)
var second_layout := generator.placement_keys()
configured_generator.elevated_cliff_double_third_tier_chance = 1.0
assert(region.generate_world(RIVER_FALLBACK_SEED))
await process_frame
await physics_frame
await physics_frame
_validate_generated_region(region, generator)
assert(generator.placement_keys() != first_layout)
assert(generator.placement_keys() != second_layout)
assert(generator._river_feature_candidate_index >= 0)
region.queue_free()
await process_frame
@ -106,32 +127,192 @@ func _validate_generated_region(
generator.grid_size.y / 2,
)
var center_index := center.y * generator.grid_size.x + center.x
assert(generator.grid_size == Vector2i(20, 20))
assert(generator._lake_feature_footprint_size.x >= 7)
assert(
generator._lake_feature_footprint_size.x
== generator._lake_feature_footprint_size.y
)
assert(maxi(
generator._river_feature_footprint_size.x,
generator._river_feature_footprint_size.y,
) >= 7)
assert(mini(
generator._river_feature_footprint_size.x,
generator._river_feature_footprint_size.y,
) == 2)
assert(generator._river_feature_flow_direction in [
Vector2i.RIGHT,
Vector2i.DOWN,
Vector2i.LEFT,
Vector2i.UP,
])
assert(generator.elevated_cliff_coastal_feature_required)
assert(placements.size() == expected_chunk_count)
assert(placements[center_index].begins_with("chunk_spawn@"))
assert(_placement_count(placements, "chunk_spawn") == 1)
assert(_placement_count(placements, "chunk_0001") >= 1)
assert(_placement_count(placements, "chunk_0003") >= 1)
assert(_placement_count(placements, "chunk_0004") >= 1)
var stream_count := (
_placement_count(placements, "chunk_0003")
+ _placement_count(placements, "chunk_0016")
)
assert(stream_count >= 1 and stream_count <= 3)
assert(_placement_count(placements, "chunk_0004") == 2)
var beach_count := _placement_count(placements, "chunk_0002")
var grass_coast_count := _placement_count(placements, "chunk_0005")
var grass_corner_count := _placement_count(placements, "chunk_0007")
var beach_corner_count := _placement_count(placements, "chunk_0008")
assert(beach_count == 44 and grass_coast_count == 0)
assert(beach_corner_count == 3 and grass_corner_count == 0)
assert(_placement_count(placements, "chunk_0009") == 10)
assert(_placement_count(placements, "chunk_0010") == 5)
var has_secondary_elevation := (
generator._secondary_elevated_feature_center.x >= 0
)
var has_third_tier_base := (
_placement_count(placements, "chunk_0028") > 0
)
var coastline_edge_count := (
2 * (generator.grid_size.x - 2)
+ 2 * (generator.grid_size.y - 2)
)
assert(
beach_count
== coastline_edge_count - (4 if has_secondary_elevation else 0) - 2
and grass_coast_count == 0
)
assert(
beach_corner_count == (3 if has_secondary_elevation else 4)
and grass_corner_count == 0
)
assert(
_placement_count(placements, "chunk_0009")
== (
(1 if has_secondary_elevation else 0)
+ (0 if has_third_tier_base else 9)
)
)
assert(
_placement_count(placements, "chunk_0010")
== (
(1 if has_secondary_elevation else 0)
+ (0 if has_third_tier_base else 4)
)
)
assert(
_placement_count(placements, "chunk_0011")
+ _placement_count(placements, "chunk_0012")
== 14
== (
(2 if has_secondary_elevation else 0)
+ (0 if has_third_tier_base else 12)
)
)
assert(
_placement_count(placements, "chunk_0012")
== (
(1 if has_secondary_elevation else 0)
+ (0 if has_third_tier_base else 1)
)
)
assert(
_placement_count(placements, "chunk_0028")
== (9 if has_third_tier_base else 0)
)
assert(
_placement_count(placements, "chunk_0027")
== (4 if has_third_tier_base else 0)
)
assert(
_placement_count(placements, "chunk_0026")
== (10 if has_third_tier_base else 0)
)
assert(_placement_count(placements, "chunk_0029") == 1)
assert(_placement_count(placements, "chunk_0030") == 1)
assert(_placement_count(placements, "chunk_0031") == 1)
assert(_placement_count(placements, "chunk_0032") == 1)
assert(
_placement_count(placements, "chunk_0014")
== (2 if has_secondary_elevation else 0)
)
assert(
_placement_count(placements, "chunk_0015")
== (1 if has_secondary_elevation else 0)
)
assert(_placement_count(placements, "chunk_0012") == 2)
assert(_placement_count(placements, "chunk_0014") == 2)
assert(_placement_count(placements, "chunk_0015") == 1)
assert(_placement_count(placements, "chunk_0017") == 0)
assert(_placement_count(placements, "chunk_0018") == 0)
assert(_placement_count(placements, "chunk_0019") == 1)
assert(_placement_count(placements, "chunk_0020") == 1)
assert(_placement_count(placements, "chunk_0020") == 4)
assert(_placement_count(placements, "chunk_0022") == 4)
assert(_placement_count(placements, "chunk_0023") == 4)
var lake_edge_count := _placement_count(placements, "chunk_0017")
var lake_fill_count := _placement_count(placements, "chunk_0019")
var lake_narrow_count := _placement_count(placements, "chunk_0021")
var lake_size := generator._lake_feature_footprint_size.x
assert(lake_size in [7, 8])
assert(lake_fill_count == (lake_size - 2) * (lake_size - 2))
assert(lake_edge_count == (lake_size - 6) * 4)
assert(lake_narrow_count == 8)
assert(_placement_count(placements, "chunk_9999") == 0)
assert(_placement_count(placements, "chunk_9998") == 0)
assert(
_placement_count(placements, "chunk_9997")
== (1 if has_secondary_elevation else 0)
)
assert(
_placement_count(placements, "chunk_9996")
== (1 if has_secondary_elevation else 0)
)
assert(_placement_count(placements, "chunk_9995") == 0)
var river_length := maxi(
generator._river_feature_footprint_size.x,
generator._river_feature_footprint_size.y,
)
assert(river_length in [7, 8])
assert(_placement_count(placements, "chunk_0024") == river_length - 2)
assert(_placement_count(placements, "chunk_0025") == river_length - 2)
var river_source_coordinates := generator._river_source_coordinates(
generator._river_feature_origin,
generator._river_feature_footprint_size,
generator._river_feature_flow_direction,
)
assert(river_source_coordinates.size() == 2)
for source_coordinate: Vector2i in river_source_coordinates:
var source_index := (
source_coordinate.y * generator.grid_size.x + source_coordinate.x
)
assert(
placements[source_index].begins_with("chunk_0029@")
or placements[source_index].begins_with("chunk_0030@")
)
var source_offset := source_coordinate - generator._elevated_feature_center
assert(
maxi(absi(source_offset.x), absi(source_offset.y))
== TerrainChunkGenerator.ELEVATED_FEATURE_RADIUS
)
var outlet_coordinates: Array[Vector2i] = []
for index: int in placements.size():
if (
placements[index].begins_with("chunk_0031@")
or placements[index].begins_with("chunk_0032@")
):
var outlet_coordinate := Vector2i(
index % generator.grid_size.x,
index / generator.grid_size.x,
)
outlet_coordinates.append(outlet_coordinate)
assert(
not generator._coordinate_is_inside_grid(
outlet_coordinate
+ generator._river_feature_flow_direction
)
)
assert(outlet_coordinates.size() == 2)
var outlet_delta := outlet_coordinates[1] - outlet_coordinates[0]
assert(absi(outlet_delta.x) + absi(outlet_delta.y) == 1)
for beach_coordinate: Vector2i in [
outlet_coordinates[0] - outlet_delta,
outlet_coordinates[1] + outlet_delta,
]:
assert(generator._coordinate_is_inside_grid(beach_coordinate))
assert(generator._coordinate_is_on_boundary(beach_coordinate))
var beach_index := (
beach_coordinate.y * generator.grid_size.x + beach_coordinate.x
)
assert(placements[beach_index].begins_with("chunk_0002@"))
for index: int in placements.size():
var coordinate := Vector2i(
index % generator.grid_size.x,
@ -159,8 +340,11 @@ func _validate_generated_region(
var storage := region.get_node("Interactables/PlayerStorageBox") as Node3D
assert(shop != null and shop.has_node("Shopkeeper"))
assert(storage != null and storage.has_node("InteractionArea"))
assert(absf(shop.position.x) < 5.0 and absf(shop.position.z) < 5.0)
assert(absf(storage.position.x) < 5.0 and absf(storage.position.z) < 5.0)
var spawn_chunk_center := generator.chunk_position(center)
var shop_offset := shop.position - spawn_chunk_center
var storage_offset := storage.position - spawn_chunk_center
assert(absf(shop_offset.x) < 5.0 and absf(shop_offset.z) < 5.0)
assert(absf(storage_offset.x) < 5.0 and absf(storage_offset.z) < 5.0)
assert(region.get_fishing_shop() != null)
assert(region.get_player_storage() != null)
assert(region.get_player_spawn_transform().origin.y > 0.0)
@ -173,30 +357,68 @@ func _validate_generated_region(
assert(ocean.water_type == WaterType.Type.SALT_WATER)
assert(is_equal_approx(ocean.position.y, GeneratedWorldRegion.WATER_HEIGHT))
var fresh_placement_count := 0
var river_placement_count := 0
var polygon_sizes_by_coordinate: Dictionary[Vector2i, int] = {}
for record: Dictionary in generator.placement_records():
var tags: PackedStringArray = record.get("tags", PackedStringArray())
if "coast" in tags:
_validate_ocean_facing_record(record, generator.grid_size)
if "fresh_water" in tags:
fresh_placement_count += 1
if "river" in tags:
river_placement_count += 1
assert(not "pond" in tags)
assert(not "lake" in tags)
var surface_polygon: PackedVector2Array = record.get(
"water_surface_polygon",
PackedVector2Array(),
)
if surface_polygon.size() >= 3:
polygon_sizes_by_coordinate[
record.get("coordinate", Vector2i.ZERO)
] = surface_polygon.size()
_validate_complete_coastline(generator)
assert(fresh_root.get_child_count() == fresh_placement_count)
assert(river_placement_count == river_length * 2)
assert(
polygon_sizes_by_coordinate.size()
== 20 + river_placement_count
)
var river_body_count := 0
for child: Node in fresh_root.get_children():
var fresh := child as WaterBodyAuthoring
assert(fresh != null)
assert(fresh.water_type == WaterType.Type.FRESH_WATER)
assert(is_equal_approx(fresh.position.y, GeneratedWorldRegion.WATER_HEIGHT))
assert(fresh.visible)
assert(fresh.surface_size.x < 10.0 or fresh.surface_size.y < 10.0)
assert(fresh.surface_size.x <= 10.0 and fresh.surface_size.y <= 10.0)
assert(not fresh.visual_surface_enabled)
assert(not (fresh.get_node("VisualWater") as MeshInstance3D).visible)
var coordinate_tokens := child.name.trim_prefix("FreshWater_").split("_")
assert(coordinate_tokens.size() == 2)
var coordinate := Vector2i(
int(coordinate_tokens[0]),
int(coordinate_tokens[1]),
)
if polygon_sizes_by_coordinate.has(coordinate):
assert(
fresh.surface_polygon.size()
== polygon_sizes_by_coordinate[coordinate]
)
assert(
fresh.get_node("FishingRegion").get_child_count() > 1
)
else:
assert(fresh.surface_polygon.is_empty())
if &"river" in fresh.location_tags:
river_body_count += 1
assert(fresh.fish_pool == GeneratedRiverPool)
elif &"lake" in fresh.location_tags:
assert(fresh.fish_pool == GeneratedLakePool)
else:
assert(&"pond" in fresh.location_tags)
assert(fresh.fish_pool == GeneratedPondPool)
assert(river_body_count == river_placement_count)
_validate_authored_chunk_surfaces(region, generator)
@ -351,10 +573,10 @@ func _validate_elevated_cliff_feature(
var coastal_ids: Array[StringName] = [
&"chunk_0014",
&"chunk_0015",
&"chunk_0017",
&"chunk_0018",
&"chunk_0019",
&"chunk_0020",
&"chunk_9999",
&"chunk_9998",
&"chunk_9997",
&"chunk_9996",
]
var has_coastal_feature := false
for record: Dictionary in generator.placement_records():
@ -364,12 +586,17 @@ func _validate_elevated_cliff_feature(
&"chunk_0010",
&"chunk_0011",
&"chunk_0012",
&"chunk_0026",
&"chunk_0027",
&"chunk_0028",
&"chunk_0029",
&"chunk_0030",
&"chunk_0014",
&"chunk_0015",
&"chunk_0017",
&"chunk_0018",
&"chunk_0019",
&"chunk_0020",
&"chunk_9999",
&"chunk_9998",
&"chunk_9997",
&"chunk_9996",
]:
continue
var coordinate: Vector2i = record.get("coordinate", Vector2i.ZERO)
@ -383,14 +610,32 @@ func _validate_elevated_cliff_feature(
assert(coordinate.x > 0 and coordinate.x < generator.grid_size.x - 1)
assert(coordinate.y > 0 and coordinate.y < generator.grid_size.y - 1)
elevated_coordinates[coordinate] = stable_id
if stable_id == &"chunk_0009":
if stable_id in [&"chunk_0009", &"chunk_0028"]:
top_coordinates.append(coordinate)
elif stable_id == &"chunk_0012":
ramp_records.append(record)
assert(elevated_coordinates.size() == (34 if has_coastal_feature else 25))
assert(top_coordinates.size() == (10 if has_coastal_feature else 9))
assert(ramp_records.size() == (2 if has_coastal_feature else 1))
var feature_center := Vector2i(-1, -1)
var feature_center := generator._elevated_feature_center
assert(feature_center != Vector2i(-1, -1))
var center_index := feature_center.y * generator.grid_size.x + feature_center.x
assert(center_index >= 0 and center_index < generator.placement_keys().size())
var primary_top_id := StringName(
generator.placement_keys()[center_index].get_slice("@", 0)
)
assert(primary_top_id in [&"chunk_0009", &"chunk_0028"])
var primary_is_third_tier := primary_top_id == &"chunk_0028"
assert(
top_coordinates.size()
== (9 + (1 if has_coastal_feature else 0))
)
assert(
ramp_records.size()
== (
(0 if primary_is_third_tier else 1)
+ (1 if has_coastal_feature else 0)
)
)
var detected_feature_center := Vector2i(-1, -1)
for candidate: Vector2i in top_coordinates:
var neighboring_top_count := 0
for row_offset: int in range(-1, 2):
@ -399,12 +644,12 @@ func _validate_elevated_cliff_feature(
elevated_coordinates.get(
candidate + Vector2i(column_offset, row_offset),
&"",
) == &"chunk_0009"
) == primary_top_id
)
if neighboring_top_count == 9:
feature_center = candidate
detected_feature_center = candidate
break
assert(feature_center != Vector2i(-1, -1))
assert(detected_feature_center == feature_center)
for row_offset: int in range(-2, 3):
for column_offset: int in range(-2, 3):
assert(
@ -413,14 +658,7 @@ func _validate_elevated_cliff_feature(
)
)
if has_coastal_feature:
var secondary_top := Vector2i(-1, -1)
for coordinate: Vector2i in top_coordinates:
if (
absi(coordinate.x - feature_center.x) > 1
or absi(coordinate.y - feature_center.y) > 1
):
secondary_top = coordinate
break
var secondary_top := generator._secondary_elevated_feature_center
assert(secondary_top != Vector2i(-1, -1))
for row_offset: int in range(-1, 2):
for column_offset: int in range(-1, 2):
@ -429,7 +667,7 @@ func _validate_elevated_cliff_feature(
secondary_top + Vector2i(column_offset, row_offset)
)
)
var found_deep_top_landing := false
var found_primary_ramp := false
for ramp_record: Dictionary in ramp_records:
var ramp_coordinate: Vector2i = ramp_record.get(
"coordinate", Vector2i(-1, -1)
@ -444,8 +682,8 @@ func _validate_elevated_cliff_feature(
elevated_coordinates.get(ramp_coordinate + high_offset, &"")
== &"chunk_0009"
)
found_deep_top_landing = (
found_deep_top_landing
found_primary_ramp = (
found_primary_ramp
or elevated_coordinates.get(
ramp_coordinate + high_offset * 2,
&"",
@ -459,7 +697,7 @@ func _validate_elevated_cliff_feature(
generator.placement_keys()[low_index].begins_with("chunk_0000@")
or generator.placement_keys()[low_index].begins_with("chunk_spawn@")
)
assert(found_deep_top_landing)
assert(found_primary_ramp == not primary_is_third_tier)
var generated := generator.get_generated_chunks_root()
assert(generated != null)
@ -473,25 +711,39 @@ func _validate_elevated_cliff_feature(
&"chunk_0010",
&"chunk_0011",
&"chunk_0012",
&"chunk_0026",
&"chunk_0027",
&"chunk_0028",
&"chunk_0029",
&"chunk_0030",
&"chunk_0014",
&"chunk_0015",
&"chunk_0017",
&"chunk_0018",
&"chunk_0019",
&"chunk_0020",
&"chunk_9999",
&"chunk_9998",
&"chunk_9997",
&"chunk_9996",
]:
continue
if stable_id in [&"chunk_0014", &"chunk_0015"]:
assert(chunk_root.get_node_or_null("TerrainBaseLayer") == null)
continue
if stable_id in [&"chunk_0029", &"chunk_0030"]:
assert(chunk_root.get_node_or_null("TerrainBaseLayer") == null)
var source_mesh := TerrainChunkAnalyzer.find_primary_mesh(
chunk_root,
stable_id,
)
assert(source_mesh != null and source_mesh.mesh != null)
assert(source_mesh.has_node("TerrainCollision"))
continue
layered_count += 1
var base_layer := chunk_root.get_node_or_null("TerrainBaseLayer")
var overlay := chunk_root.get_node_or_null("TerrainOverlay")
assert(base_layer != null and overlay != null)
var transition_base_mesh := &"chunk_0000"
if stable_id in [&"chunk_0017", &"chunk_0018"]:
if stable_id in [&"chunk_9999", &"chunk_9998"]:
transition_base_mesh = &"chunk_0007"
elif stable_id in [&"chunk_0019", &"chunk_0020"]:
elif stable_id in [&"chunk_9997", &"chunk_9996"]:
transition_base_mesh = &"chunk_0008"
var base_mesh := TerrainChunkAnalyzer.find_primary_mesh(
base_layer,
@ -502,10 +754,10 @@ func _validate_elevated_cliff_feature(
(
&"chunk_0015"
if stable_id in [
&"chunk_0017",
&"chunk_0018",
&"chunk_0019",
&"chunk_0020",
&"chunk_9999",
&"chunk_9998",
&"chunk_9997",
&"chunk_9996",
]
else stable_id
),
@ -514,32 +766,40 @@ func _validate_elevated_cliff_feature(
assert(overlay_mesh != null and overlay_mesh.mesh != null)
assert(base_mesh.has_node("TerrainBaseLayerCollision"))
assert(overlay_mesh.has_node("TerrainCollision"))
assert(layered_count == (31 if has_coastal_feature else 25))
assert(layered_count == (29 if has_coastal_feature else 23))
var stacked_keys := generator.stacked_elevated_placement_keys()
assert(stacked_keys.size() in [0, 4])
if stacked_keys.is_empty():
return
for key: String in stacked_keys:
assert(key.begins_with("chunk_0010@"))
assert(
key.begins_with("chunk_0010@")
or key.begins_with("chunk_0027@")
)
var stacked_count := 0
for chunk_root: Node in generated.get_children():
for child: Node in chunk_root.get_children():
if not bool(child.get_meta(&"terrain_stacked_elevation", false)):
continue
stacked_count += 1
assert(
StringName(child.get_meta(&"terrain_chunk_id", &""))
== &"chunk_0010"
var stacked_id := StringName(
child.get_meta(&"terrain_chunk_id", &"")
)
assert(stacked_id in [&"chunk_0010", &"chunk_0027"])
var expected_height := (
generator.elevated_cliff_level_height * 2.0
if primary_is_third_tier
else generator.elevated_cliff_level_height
)
assert(
is_equal_approx(
(child as Node3D).position.y,
generator.elevated_cliff_level_height,
expected_height,
)
)
var stacked_mesh := TerrainChunkAnalyzer.find_primary_mesh(
child,
&"chunk_0010",
stacked_id,
)
assert(stacked_mesh != null and stacked_mesh.mesh != null)
assert(stacked_mesh.has_node("TerrainCollision"))