Expand generated terrain and coastal elevations

This commit is contained in:
Alexander Sellite 2026-08-21 21:33:30 -04:00
parent 6d0e996583
commit 339812763d
28 changed files with 906 additions and 171 deletions

View file

@ -13,7 +13,7 @@ const GeneratedRiverPool: FishPool = preload(
"res://fish/pools/generated_river_pool.tres"
)
const FIRST_SEED := 13001
const SECOND_SEED := 13004
const SECOND_SEED := 13002
const EXPECTED_PROP_IDS: Array[StringName] = [
&"prop_bridge",
&"prop_dock",
@ -110,23 +110,47 @@ func _validate_generated_region(
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_0002") >= 1)
assert(_placement_count(placements, "chunk_0003") >= 1)
assert(_placement_count(placements, "chunk_0016") >= 1)
assert(_placement_count(placements, "chunk_0004") >= 1)
assert(_placement_count(placements, "chunk_0005") >= 1)
assert(_placement_count(placements, "chunk_0006") >= 1)
assert(_placement_count(placements, "chunk_0007") >= 1)
assert(_placement_count(placements, "chunk_0008") >= 1)
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 > 0) != (grass_coast_count > 0))
if beach_count > 0:
assert(beach_count == 48 and grass_coast_count == 0)
assert(beach_corner_count == 4 and grass_corner_count == 0)
assert(_placement_count(placements, "chunk_0009") == 9)
assert(_placement_count(placements, "chunk_0010") == 4)
assert(
_placement_count(placements, "chunk_0011")
+ _placement_count(placements, "chunk_0012")
== 12
)
assert(_placement_count(placements, "chunk_0012") == 1)
for stable_id: String in [
"chunk_0014",
"chunk_0015",
"chunk_0017",
"chunk_0018",
]:
assert(_placement_count(placements, stable_id) == 0)
else:
assert(grass_coast_count == 44 and beach_count == 0)
assert(grass_corner_count == 3 and beach_corner_count == 0)
assert(_placement_count(placements, "chunk_0009") == 10)
assert(_placement_count(placements, "chunk_0010") == 5)
assert(
_placement_count(placements, "chunk_0011")
+ _placement_count(placements, "chunk_0012")
== 14
)
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") == 1)
assert(_placement_count(placements, "chunk_0018") == 1)
assert(_placement_count(placements, "chunk_0013") >= 1)
assert(_placement_count(placements, "chunk_0009") == 1)
assert(_placement_count(placements, "chunk_0010") == 4)
assert(
_placement_count(placements, "chunk_0011")
+ _placement_count(placements, "chunk_0012")
== 4
)
assert(_placement_count(placements, "chunk_0012") <= 1)
assert(
generator.get_generated_chunks_root().get_child_count()
== expected_chunk_count
@ -164,6 +188,7 @@ func _validate_generated_region(
_validate_ocean_facing_record(record, generator.grid_size)
if "fresh_water" in tags:
fresh_placement_count += 1
_validate_complete_coastline(generator)
assert(fresh_root.get_child_count() == fresh_placement_count)
for child: Node in fresh_root.get_children():
var fresh := child as WaterBodyAuthoring
@ -191,6 +216,7 @@ func _validate_generated_region(
assert(decorations != null and decorations.get_child_count() > 0)
assert(anchors != null)
assert(anchors.get_spawn_positions().size() > 0)
_validate_tree_gatherable_anchors(region, decorations, anchors)
var palm_clusters: Dictionary[int, Array] = {}
var tree_scales: Array[float] = []
var tree_yaws: Dictionary[float, bool] = {}
@ -295,7 +321,7 @@ func _validate_generated_region(
)
assert(not palm_clusters.is_empty())
for members: Array in palm_clusters.values():
assert(members.size() >= 2 and members.size() <= 3)
assert(members.size() >= 1 and members.size() <= 3)
for first_index: int in members.size():
for second_index: int in range(first_index + 1, members.size()):
var first := members[first_index] as Node3D
@ -314,14 +340,14 @@ func _validate_generated_region(
region,
&"biome_forest",
&"grass_tree",
) >= 2
) >= 4
)
assert(
_decoration_biome_group_count(
region,
&"biome_pine_forest",
&"grass_tree",
) >= 2
) >= 4
)
@ -329,7 +355,15 @@ func _validate_elevated_cliff_feature(
generator: TerrainChunkGenerator,
) -> void:
var elevated_coordinates: Dictionary[Vector2i, StringName] = {}
var top_coordinate := Vector2i(-1, -1)
var top_coordinates: Array[Vector2i] = []
var ramp_records: Array[Dictionary] = []
var coastal_ids: Array[StringName] = [
&"chunk_0014",
&"chunk_0015",
&"chunk_0017",
&"chunk_0018",
]
var has_coastal_feature := false
for record: Dictionary in generator.placement_records():
var stable_id: StringName = record.get("stable_id", &"")
if stable_id not in [
@ -337,23 +371,100 @@ func _validate_elevated_cliff_feature(
&"chunk_0010",
&"chunk_0011",
&"chunk_0012",
&"chunk_0014",
&"chunk_0015",
&"chunk_0017",
&"chunk_0018",
]:
continue
var coordinate: Vector2i = record.get("coordinate", Vector2i.ZERO)
assert(coordinate.x > 0 and coordinate.x < generator.grid_size.x - 1)
assert(coordinate.y > 0 and coordinate.y < generator.grid_size.y - 1)
if stable_id in coastal_ids:
has_coastal_feature = true
assert(
coordinate.x in [0, generator.grid_size.x - 1]
or coordinate.y in [0, generator.grid_size.y - 1]
)
else:
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":
top_coordinate = coordinate
assert(elevated_coordinates.size() == 9)
assert(top_coordinate != Vector2i(-1, -1))
for row_offset: int in range(-1, 2):
for column_offset: int in range(-1, 2):
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)
for candidate: Vector2i in top_coordinates:
var neighboring_top_count := 0
for row_offset: int in range(-1, 2):
for column_offset: int in range(-1, 2):
neighboring_top_count += int(
elevated_coordinates.get(
candidate + Vector2i(column_offset, row_offset),
&"",
) == &"chunk_0009"
)
if neighboring_top_count == 9:
feature_center = candidate
break
assert(feature_center != Vector2i(-1, -1))
for row_offset: int in range(-2, 3):
for column_offset: int in range(-2, 3):
assert(
elevated_coordinates.has(
top_coordinate + Vector2i(column_offset, row_offset)
feature_center + Vector2i(column_offset, row_offset)
)
)
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
assert(secondary_top != Vector2i(-1, -1))
for row_offset: int in range(-1, 2):
for column_offset: int in range(-1, 2):
assert(
elevated_coordinates.has(
secondary_top + Vector2i(column_offset, row_offset)
)
)
var found_deep_top_landing := false
for ramp_record: Dictionary in ramp_records:
var ramp_coordinate: Vector2i = ramp_record.get(
"coordinate", Vector2i(-1, -1)
)
var ramp_turns := int(ramp_record.get("rotation_quarters", 0))
var high_edge := TerrainChunkTopology.rotated_edge(
TerrainChunkTopology.Edge.WEST,
ramp_turns,
)
var high_offset := TerrainChunkTopology.grid_offset(high_edge)
assert(
elevated_coordinates.get(ramp_coordinate + high_offset, &"")
== &"chunk_0009"
)
found_deep_top_landing = (
found_deep_top_landing
or elevated_coordinates.get(
ramp_coordinate + high_offset * 2,
&"",
) == &"chunk_0009"
)
var low_coordinate := ramp_coordinate - high_offset
var low_index := (
low_coordinate.y * generator.grid_size.x + low_coordinate.x
)
assert(
generator.placement_keys()[low_index].begins_with("chunk_0000@")
or generator.placement_keys()[low_index].begins_with("chunk_spawn@")
)
assert(found_deep_top_landing)
var generated := generator.get_generated_chunks_root()
assert(generated != null)
@ -367,25 +478,33 @@ func _validate_elevated_cliff_feature(
&"chunk_0010",
&"chunk_0011",
&"chunk_0012",
&"chunk_0014",
&"chunk_0015",
&"chunk_0017",
&"chunk_0018",
]:
continue
if stable_id in [&"chunk_0014", &"chunk_0015"]:
assert(chunk_root.get_node_or_null("TerrainBaseLayer") == null)
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 is_transition := stable_id in [&"chunk_0017", &"chunk_0018"]
var base_mesh := TerrainChunkAnalyzer.find_primary_mesh(
base_layer,
&"chunk_0000",
&"chunk_0005" if is_transition else &"chunk_0000",
)
var overlay_mesh := TerrainChunkAnalyzer.find_primary_mesh(
overlay,
stable_id,
&"chunk_0015" if is_transition else stable_id,
)
assert(base_mesh != null and base_mesh.mesh != null)
assert(overlay_mesh != null and overlay_mesh.mesh != null)
assert(base_mesh.has_node("TerrainBaseLayerCollision"))
assert(overlay_mesh.has_node("TerrainCollision"))
assert(layered_count == 9)
assert(layered_count == (31 if has_coastal_feature else 25))
var stacked_keys := generator.stacked_elevated_placement_keys()
assert(stacked_keys.size() in [0, 4])
if stacked_keys.is_empty():
@ -441,6 +560,28 @@ func _validate_ocean_facing_record(
)
func _validate_complete_coastline(generator: TerrainChunkGenerator) -> void:
for record: Dictionary in generator.placement_records():
var coordinate: Vector2i = record.get("coordinate", Vector2i.ZERO)
var ocean_edges := int(record.get("ocean_facing_edges", 0))
for edge_value: int in TerrainChunkTopology.Edge.values():
var neighbor := (
coordinate
+ TerrainChunkTopology.grid_offset(
edge_value as TerrainChunkTopology.Edge
)
)
var outside := (
neighbor.x < 0
or neighbor.y < 0
or neighbor.x >= generator.grid_size.x
or neighbor.y >= generator.grid_size.y
)
assert(
((ocean_edges & (1 << edge_value)) != 0) == outside
)
func _ocean_direction(ocean_edges: int) -> Vector3:
var result := Vector3.ZERO
for edge_value: int in TerrainChunkTopology.Edge.values():
@ -522,13 +663,19 @@ func _validate_prop_catalog(region: GeneratedWorldRegion) -> void:
+ catalog.definition_for_id(&"prop_tree_3").selection_weight
)
assert(forest_rule.allows_prop(large_tree))
assert(large_tree.selection_weight > small_tree_weight)
assert(large_tree.selection_weight <= small_tree_weight)
var pine_forest := region.get_biome_catalog().definition_for_id(
&"biome_pine_forest"
)
var pine_rule := pine_forest.prop_rule_for_group(&"grass_tree")
assert(pine_rule.allows_prop(large_pine))
assert(large_pine.selection_weight > pine.selection_weight)
assert(
is_equal_approx(large_pine.selection_weight, pine.selection_weight)
)
assert(forest_rule.minimum_placements == 4)
assert(forest_rule.placement_attempts_per_chunk == 4)
assert(pine_rule.minimum_placements == 4)
assert(pine_rule.placement_attempts_per_chunk == 4)
func _validate_biome_catalog(
@ -623,6 +770,57 @@ func _validate_decoration_transform(
assert(absf(ground_position.y - prop.global_position.y) <= 0.01)
func _validate_tree_gatherable_anchors(
region: GeneratedWorldRegion,
decorations: Node3D,
anchors: GatherableAnchorSet3D,
) -> void:
var eligible_props: Array[Node3D] = []
for child: Node in decorations.get_children():
var prop := child as Node3D
if prop == null:
continue
var prop_id := StringName(prop.get_meta(&"terrain_prop_id", &""))
var definition := region.get_prop_catalog().definition_for_id(prop_id)
if definition != null and definition.gatherable_anchor_height > 0.0:
eligible_props.append(prop)
var positions := anchors.get_spawn_positions()
assert(positions.size() == eligible_props.size())
for prop: Node3D in eligible_props:
var prop_id := StringName(prop.get_meta(&"terrain_prop_id", &""))
var definition := region.get_prop_catalog().definition_for_id(prop_id)
var visual_scale := float(
prop.get_meta(&"terrain_prop_visual_scale", 1.0)
)
var nearest_anchor := Vector3(INF, INF, INF)
var nearest_distance_squared := INF
for position: Vector3 in positions:
var distance_squared := position.distance_squared_to(
prop.global_position
)
if distance_squared < nearest_distance_squared:
nearest_distance_squared = distance_squared
nearest_anchor = position
assert(nearest_anchor.is_finite())
var horizontal_distance := Vector2(
nearest_anchor.x - prop.global_position.x,
nearest_anchor.z - prop.global_position.z,
).length()
assert(
horizontal_distance
>= definition.gatherable_anchor_surface_radius() * visual_scale
)
assert(
absf(
nearest_anchor.y
- (
prop.global_position.y
+ definition.gatherable_anchor_height * visual_scale
)
) <= 0.001
)
func _find_mesh_instance(root_node: Node) -> MeshInstance3D:
if root_node is MeshInstance3D:
return root_node as MeshInstance3D
@ -662,13 +860,15 @@ func _validate_authored_chunk_surfaces(
var generated := generator.get_generated_chunks_root()
var found_beach := false
var found_stream := false
var found_alternate_stream := false
var found_pond := false
var found_grass_ocean_edge := false
var found_grass_beach_transition := false
var found_grass_ocean_corner := false
var found_beach_ocean_corner := false
var found_grass_sand_diagonal := false
var beach_family := (
_placement_count(generator.placement_keys(), "chunk_0002") > 0
)
for chunk_root: Node in generated.get_children():
if chunk_root.name.begins_with("chunk_0002r"):
var beach := chunk_root.find_child(
@ -695,9 +895,7 @@ func _validate_authored_chunk_surfaces(
assert("dirt" in stream_materials)
assert("grass_lite" in stream_materials)
assert("dirt_wall" in stream_materials)
if stream_id == &"chunk_0016":
found_alternate_stream = true
else:
if stream_id == &"chunk_0003":
found_stream = true
elif chunk_root.name.begins_with("chunk_0004r"):
var pond := chunk_root.find_child(
@ -761,15 +959,20 @@ func _validate_authored_chunk_surfaces(
assert("grass_lite" in diagonal_materials)
assert("sand" in diagonal_materials)
found_grass_sand_diagonal = true
assert(found_beach)
assert(found_stream)
assert(found_alternate_stream)
assert(found_pond)
assert(found_grass_ocean_edge)
assert(found_grass_beach_transition)
assert(found_grass_ocean_corner)
assert(found_beach_ocean_corner)
assert(found_grass_sand_diagonal)
if beach_family:
assert(found_beach)
assert(found_beach_ocean_corner)
assert(not found_grass_ocean_edge)
assert(not found_grass_ocean_corner)
else:
assert(not found_beach)
assert(not found_beach_ocean_corner)
assert(found_grass_ocean_edge)
assert(found_grass_ocean_corner)
assert(not found_grass_beach_transition)
func _material_names(mesh_instance: MeshInstance3D) -> PackedStringArray: