Improve generated river terrain

This commit is contained in:
Alexander Sellite 2026-08-24 20:21:49 -04:00
parent 8456093f8c
commit e6f46dcad9
21 changed files with 586 additions and 18 deletions

View file

@ -247,6 +247,7 @@ func _validate_generated_region(
assert(_placement_count(placements, "chunk_0034") == 1)
assert(_placement_count(placements, "chunk_0035") == 1)
assert(_placement_count(placements, "chunk_0036") == 1)
assert(_placement_count(placements, "chunk_0038") == 2)
assert(
_placement_count(placements, "chunk_0014")
== (2 if has_secondary_elevation else 0)
@ -285,10 +286,31 @@ func _validate_generated_region(
var river_edge_count := (
_placement_count(placements, "chunk_0024")
+ _placement_count(placements, "chunk_0025")
+ _placement_count(placements, "chunk_0038")
)
assert(river_coordinate_count >= 12)
assert(generator._river_feature_placements.size() == river_coordinate_count)
assert(river_edge_count == river_coordinate_count - 8)
var stone_bridge_specs: Array[Dictionary] = []
for spec: Dictionary in generator._river_feature_placements:
if spec.get("id", &"") == &"chunk_0038":
stone_bridge_specs.append(spec)
assert(stone_bridge_specs.size() == 2)
var first_bridge_coordinate := stone_bridge_specs[0].get(
"coordinate",
Vector2i(-1, -1),
) as Vector2i
var second_bridge_coordinate := stone_bridge_specs[1].get(
"coordinate",
Vector2i(-1, -1),
) as Vector2i
var bridge_delta := second_bridge_coordinate - first_bridge_coordinate
assert(absi(bridge_delta.x) + absi(bridge_delta.y) == 1)
assert(posmod(
int(stone_bridge_specs[1].get("turns", -1))
- int(stone_bridge_specs[0].get("turns", -1)),
4,
) == 2)
var river_source_coordinates := generator._river_source_coordinates(
generator._river_feature_origin,
generator._river_feature_footprint_size,
@ -446,6 +468,20 @@ func _validate_generated_region(
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 generated_bed := fresh.get_node_or_null(
"GeneratedBed",
) as MeshInstance3D
assert(generated_bed != null and generated_bed.mesh != null)
assert(
is_equal_approx(
generated_bed.global_position.y,
GeneratedWorldRegion.GENERATED_FRESH_WATER_BED_HEIGHT,
)
)
assert(
generated_bed.material_override != null
and generated_bed.material_override.resource_name == "dirt"
)
var fresh_recovery := (
fresh.get_node("RecoveryRegion") as PlayerWaterTrigger
)
@ -486,6 +522,16 @@ func _validate_generated_region(
)
var decorations := region.get_node("Decorations") as Node3D
var grass_prop_surface_triangles := region.get_spawn_surface_triangles(
[&"grass_lite"],
-INF,
0.35,
)
var sand_prop_surface_triangles := region.get_spawn_surface_triangles(
[&"sand"],
-INF,
0.35,
)
var anchors := region.get_node(
"GatherableAnchors/ReachableTreeTrunks"
) as GatherableAnchorSet3D
@ -540,6 +586,8 @@ func _validate_generated_region(
region,
child as Node3D,
definition,
grass_prop_surface_triangles,
sand_prop_surface_triangles,
)
if prop_id == &"prop_palm":
var cluster_id := int(
@ -1063,6 +1111,8 @@ func _validate_decoration_transform(
region: GeneratedWorldRegion,
prop: Node3D,
definition: TerrainPropDefinition,
grass_surface_triangles: Array[PackedVector3Array],
sand_surface_triangles: Array[PackedVector3Array],
) -> void:
assert(prop != null)
assert(prop.scale.is_equal_approx(Vector3.ONE))
@ -1142,6 +1192,43 @@ func _validate_decoration_transform(
],
],
)
var required_surface_triangles: Array[PackedVector3Array] = (
sand_surface_triangles
if definition.procedural_group == &"sand_tree"
else grass_surface_triangles
)
var required_surface_height := _triangle_surface_height_at(
prop.global_position,
required_surface_triangles,
)
assert(
required_surface_height > -INF
and absf(required_surface_height - prop.global_position.y) <= 0.01,
"%s (%s) is not rooted on its required terrain material at %s."
% [prop.name, definition.stable_id, prop.global_position],
)
func _triangle_surface_height_at(
position: Vector3,
triangles: Array[PackedVector3Array],
) -> float:
var highest := -INF
var segment_start := position + Vector3.UP * 100.0
var segment_end := position + Vector3.DOWN * 100.0
for triangle: PackedVector3Array in triangles:
if triangle.size() != 3:
continue
var hit: Variant = Geometry3D.segment_intersects_triangle(
segment_start,
segment_end,
triangle[0],
triangle[1],
triangle[2],
)
if hit is Vector3:
highest = maxf(highest, (hit as Vector3).y)
return highest
func _validate_tree_gatherable_anchors(

View file

@ -42,6 +42,7 @@ const EXPECTED_IDS: Array[String] = [
"chunk_0035",
"chunk_0036",
"chunk_0037",
"chunk_0038",
"chunk_9999",
"chunk_9998",
"chunk_9997",
@ -98,6 +99,7 @@ const EXPECTED_VARIANT_COUNTS: Dictionary[StringName, int] = {
&"chunk_0035": 4,
&"chunk_0036": 4,
&"chunk_0037": 4,
&"chunk_0038": 4,
&"chunk_9999": 4,
&"chunk_9998": 4,
&"chunk_9997": 4,
@ -163,6 +165,7 @@ func _validate_catalog() -> void:
var lake_narrow_to_regular := CATALOG.definition_for_id(&"chunk_0023")
var river_edge := CATALOG.definition_for_id(&"chunk_0024")
var river_edge_variant := CATALOG.definition_for_id(&"chunk_0025")
var river_stone_bridge := CATALOG.definition_for_id(&"chunk_0038")
var river_source_right := CATALOG.definition_for_id(&"chunk_0029")
var river_source_left := CATALOG.definition_for_id(&"chunk_0030")
var river_outlet_right := CATALOG.definition_for_id(&"chunk_0031")
@ -208,6 +211,7 @@ func _validate_catalog() -> void:
and lake_narrow_to_regular != null
and river_edge != null
and river_edge_variant != null
and river_stone_bridge != null
and river_source_right != null
and river_source_left != null
and river_outlet_right != null
@ -255,6 +259,7 @@ func _validate_catalog() -> void:
and lake_narrow_to_regular != null
and river_edge != null
and river_edge_variant != null
and river_stone_bridge != null
and river_source_right != null
and river_source_left != null
and river_outlet_right != null
@ -509,6 +514,8 @@ func _validate_catalog() -> void:
(
not river_edge.participates_in_base_solver
and not river_edge_variant.participates_in_base_solver
and not river_stone_bridge.participates_in_base_solver
and river_stone_bridge.maximum_placements == 2
and not river_source_right.participates_in_base_solver
and not river_source_left.participates_in_base_solver
and not river_outlet_right.participates_in_base_solver
@ -522,6 +529,8 @@ func _validate_catalog() -> void:
and river_edge.water_outlet_edges == 14
and river_edge_variant.water_inlet_edges == 14
and river_edge_variant.water_outlet_edges == 14
and river_stone_bridge.water_inlet_edges == 14
and river_stone_bridge.water_outlet_edges == 14
and river_source_right.water_inlet_edges == 6
and river_source_right.water_outlet_edges == 6
and river_source_left.water_inlet_edges == 3
@ -544,6 +553,7 @@ func _validate_catalog() -> void:
and river_cap.water_outlet_edges == 6
and river_edge.water_surface_polygon.size() == 13
and river_edge_variant.water_surface_polygon.size() == 13
and river_stone_bridge.water_surface_polygon.size() == 13
and river_source_right.water_surface_polygon.size() == 13
and river_source_left.water_surface_polygon.size() == 13
and river_outlet_right.water_surface_polygon.size() == 19
@ -554,6 +564,8 @@ func _validate_catalog() -> void:
and river_cap.primary_mesh_name == &"chunk_0020"
and "river" in river_edge.tags
and "river" in river_edge_variant.tags
and "river_bridge" in river_stone_bridge.tags
and "stepping_stones" in river_stone_bridge.tags
and "river_source_right" in river_source_right.tags
and "river_source_left" in river_source_left.tags
and "river_outlet_right" in river_outlet_right.tags
@ -1461,6 +1473,10 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
var river_edge_variant_bottom := _find_variant(generator, &"chunk_0025", 2)
var river_edge_vertical_left := _find_variant(generator, &"chunk_0024", 1)
var river_edge_vertical_right := _find_variant(generator, &"chunk_0025", 3)
var river_stones_top := _find_variant(generator, &"chunk_0038", 0)
var river_stones_bottom := _find_variant(generator, &"chunk_0038", 2)
var river_stones_left := _find_variant(generator, &"chunk_0038", 1)
var river_stones_right := _find_variant(generator, &"chunk_0038", 3)
var river_source_right := _find_variant(generator, &"chunk_0029", 0)
var river_source_left := _find_variant(generator, &"chunk_0030", 0)
var river_outlet_right := _find_variant(generator, &"chunk_0031", 0)
@ -1500,6 +1516,10 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
and river_edge_variant_bottom != null
and river_edge_vertical_left != null
and river_edge_vertical_right != null
and river_stones_top != null
and river_stones_bottom != null
and river_stones_left != null
and river_stones_right != null
and river_source_right != null
and river_source_left != null
and river_outlet_right != null
@ -1525,6 +1545,10 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
and river_edge_variant_bottom != null
and river_edge_vertical_left != null
and river_edge_vertical_right != null
and river_stones_top != null
and river_stones_bottom != null
and river_stones_left != null
and river_stones_right != null
and river_source_right != null
and river_source_left != null
and river_outlet_right != null
@ -1535,6 +1559,30 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
and river_bend_d != null
and beach_east != null
):
_check(
generator._edges_are_compatible(
river_edge_top,
TerrainChunkTopology.Edge.EAST,
river_stones_top,
TerrainChunkTopology.Edge.WEST,
)
and generator._edges_are_compatible(
river_stones_top,
TerrainChunkTopology.Edge.SOUTH,
river_stones_bottom,
TerrainChunkTopology.Edge.NORTH,
)
and generator._edges_are_compatible(
river_stones_left,
TerrainChunkTopology.Edge.EAST,
river_stones_right,
TerrainChunkTopology.Edge.WEST,
),
(
"The stepping-stone banks must replace one paired straight river "
+ "station in either orientation."
),
)
_check(
generator._edges_are_compatible(
river_source_right,
@ -1750,8 +1798,8 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
func _validate_generation_summary(generator: TerrainChunkGenerator) -> void:
var summary := generator._build_summary()
_check(
int(summary.get("variant_count", 0)) == 170,
"The current catalog must expose all 170 authored rotations.",
int(summary.get("variant_count", 0)) == 174,
"The current catalog must expose all 174 authored rotations.",
)
_check(
int(summary.get("solver_variant_count", 0))

View file

@ -1,6 +1,11 @@
extends SceneTree
const POLICY := preload("res://main/texture_sampling_policy.gd")
const PROJECTED_TERRAIN_TEXTURE_IMPORTS: Array[String] = [
"res://world/generation/chunks/assets/chunk_0000_grass_lite.png.import",
"res://world/generation/chunks/assets/chunk_0001_sand.png.import",
"res://world/generation/chunks/assets/chunk_0031_dirt.png.import",
]
var failures: Array[String] = []
@ -83,9 +88,14 @@ func _validate_repository_files(path: String) -> void:
func _validate_import_file(path: String) -> void:
var source := FileAccess.get_file_as_string(path)
var expects_mipmaps := path in PROJECTED_TERRAIN_TEXTURE_IMPORTS
_check(
"mipmaps/generate=true" not in source,
"Texture import generates mipmaps: %s" % path,
("mipmaps/generate=true" in source) == expects_mipmaps,
(
"Projected terrain mipmap policy mismatch: %s"
if expects_mipmaps
else "Texture import generates unapproved mipmaps: %s"
) % path,
)