feat(environment): add painterly skies and living meadows

This commit is contained in:
Rijad Zuzo
2026-09-05 20:27:09 +02:00
parent 7899d36f03
commit ec16e2f2ca
71 changed files with 2280 additions and 213 deletions
+85 -7
View File
@@ -63,10 +63,13 @@ func _run() -> void:
var river_surface := world.get_node("WaterRoot/RiverSurface") as Node3D
_check_ribbon_tracks_terrain(
terrain, river_surface.get_node("RiverRibbon") as MeshInstance3D, "Downstream water"
terrain, river_surface.get_node("RiverRibbon") as MeshInstance3D, "Downstream water", false
)
_check_ribbon_tracks_terrain(
terrain, river_surface.get_node("UpperRiverRibbon") as MeshInstance3D, "Upper-stream water"
terrain,
river_surface.get_node("UpperRiverRibbon") as MeshInstance3D,
"Upper-stream water",
true
)
print(
@@ -83,23 +86,98 @@ func _terrain_height(terrain: Terrain3D, world_x: float, world_z: float) -> floa
func _check_ribbon_tracks_terrain(
terrain: Terrain3D, ribbon: MeshInstance3D, label: String
terrain: Terrain3D, ribbon: MeshInstance3D, label: String, is_upper: bool
) -> void:
_check(ribbon != null and ribbon.mesh is ArrayMesh, "%s ribbon should be generated" % label)
if ribbon == null or not ribbon.mesh is ArrayMesh:
return
var arrays := ribbon.mesh.surface_get_arrays(0)
var vertices: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
var uvs: PackedVector2Array = arrays[Mesh.ARRAY_TEX_UV]
_check(not vertices.is_empty(), "%s ribbon should contain vertices" % label)
for vertex_index in range(0, vertices.size(), maxi(1, vertices.size() / 12)):
if vertices.is_empty() or uvs.size() != vertices.size():
failures.append("%s ribbon requires a complete coordinate profile" % label)
return
var start_z := (
JajceWatercourse.UPPER_STREAM_START_Z if is_upper else JajceWatercourse.DOWNSTREAM_START_Z
)
var end_z := (
JajceWatercourse.UPPER_STREAM_END_Z if is_upper else JajceWatercourse.DOWNSTREAM_END_Z
)
_check(
(
absf(ribbon.to_global(vertices[0]).z - start_z) < 0.03
and absf(ribbon.to_global(vertices[-1]).z - end_z) < 0.03
),
"%s ribbon should span its entire authored stream length" % label
)
var pool := ribbon.get_parent().get_node("PlungePool") as MeshInstance3D
var pool_mesh := pool.mesh as PlaneMesh
var pool_radii := pool_mesh.size * Vector2(pool.scale.x, pool.scale.z) * 0.5
var min_depth := INF
var max_depth := -INF
for vertex_index in vertices.size():
var world_vertex := ribbon.to_global(vertices[vertex_index])
var world_z := lerpf(start_z, end_z, uvs[vertex_index].y)
var center_x := (
JajceWatercourse.upper_stream_center_x(world_z)
if is_upper
else JajceWatercourse.downstream_center_x(world_z)
)
var half_width := (
JajceWatercourse.upper_stream_half_width(world_z)
if is_upper
else JajceWatercourse.downstream_half_width(world_z)
)
if not is_upper:
var pool_progress := (world_z - pool.global_position.z) / pool_radii.y
if absf(pool_progress) < 1.0:
half_width = maxf(
half_width, pool_radii.x * sqrt(1.0 - pool_progress * pool_progress)
)
var center_height := _terrain_height(terrain, center_x, world_z)
var terrain_height := terrain.data.get_height(world_vertex)
if is_nan(terrain_height):
if (
not world_vertex.is_finite()
or not is_finite(terrain_height)
or not is_finite(center_height)
):
failures.append("%s ribbon sampled outside Terrain3D" % label)
return
if absf(world_vertex.y - terrain_height - JajceWatercourse.WATER_SURFACE_OFFSET) > 0.03:
failures.append("%s ribbon should hug the carved terrain" % label)
if absf(world_vertex.y - center_height - JajceWatercourse.WATER_SURFACE_OFFSET) > 0.03:
failures.append("%s cross-section should follow its actual carved centerline" % label)
return
if (
absf(world_vertex.z - world_z) > 0.03
or absf(world_vertex.x - center_x) > half_width + 0.03
):
failures.append(
"%s ribbon should stay inside the authored stream/pool footprint" % label
)
return
var depth := world_vertex.y - terrain_height
min_depth = minf(min_depth, depth)
max_depth = maxf(max_depth, depth)
if depth < -0.03 or depth > JajceWatercourse.WATER_SURFACE_OFFSET + 0.03:
failures.append("%s surface should remain shallow and above the channel bed" % label)
return
var across := uvs[vertex_index].x
if is_zero_approx(across) or is_equal_approx(across, 1.0):
var edge_distance := absf(world_vertex.x - center_x)
if absf(edge_distance - half_width) > 0.03 and absf(depth) > 0.03:
failures.append(
"%s edges must reach the authored extent or the actual shoreline" % label
)
return
if (
(is_zero_approx(across) and world_vertex.x >= center_x)
or (is_equal_approx(across, 1.0) and world_vertex.x <= center_x)
):
failures.append(
"%s water must retain its authored centerline inside both banks" % label
)
return
print("[TEST] %s depth bounds | min=%.3f max=%.3f" % [label, min_depth, max_depth])
func _check(condition: bool, message: String) -> void: