feat: carve Jajce river into terrain
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
extends SceneTree
|
||||
|
||||
const JajceWatercourse := preload("res://world/jajce/JajceWatercourse.gd")
|
||||
|
||||
var failures: Array[String] = []
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var camera := Camera3D.new()
|
||||
camera.current = true
|
||||
root.add_child(camera)
|
||||
var world: Node3D = load("res://world/jajce/JajceWorld.tscn").instantiate()
|
||||
root.add_child(world)
|
||||
await process_frame
|
||||
for _frame in 4:
|
||||
await physics_frame
|
||||
|
||||
var terrain := world.get_node("TerrainRoot/Terrain3D") as Terrain3D
|
||||
_check(terrain != null, "Jajce watercourse validation requires Terrain3D")
|
||||
if terrain == null:
|
||||
_finish()
|
||||
return
|
||||
|
||||
var shelf_z := -36.0
|
||||
var shelf_center_x := JajceWatercourse.upper_stream_center_x(shelf_z)
|
||||
var shelf_half_width := JajceWatercourse.upper_stream_half_width(shelf_z)
|
||||
var shelf_stream_height := _terrain_height(terrain, shelf_center_x, shelf_z)
|
||||
var shelf_bank_height := _terrain_height(
|
||||
terrain, shelf_center_x + shelf_half_width + 4.0, shelf_z
|
||||
)
|
||||
var plunge_height := _terrain_height(
|
||||
terrain, JajceWatercourse.PLUNGE_POOL_CENTER.x, JajceWatercourse.PLUNGE_POOL_CENTER.y
|
||||
)
|
||||
_check(shelf_stream_height >= 10.8, "Upper stream should remain on the raised waterfall shelf")
|
||||
_check(
|
||||
shelf_bank_height - shelf_stream_height >= 0.45,
|
||||
"Upper stream should be visibly carved below its shelf bank"
|
||||
)
|
||||
_check(
|
||||
shelf_stream_height - plunge_height >= 10.0,
|
||||
"Waterfall shelf should create a readable drop into the plunge basin"
|
||||
)
|
||||
|
||||
var downstream_samples := PackedFloat32Array([-10.0, 12.0, 34.0])
|
||||
for world_z in downstream_samples:
|
||||
var center_x := JajceWatercourse.downstream_center_x(world_z)
|
||||
var half_width := JajceWatercourse.downstream_half_width(world_z)
|
||||
var channel_height := _terrain_height(terrain, center_x, world_z)
|
||||
var left_bank_height := _terrain_height(terrain, center_x - half_width - 5.0, world_z)
|
||||
var right_bank_height := _terrain_height(terrain, center_x + half_width + 5.0, world_z)
|
||||
_check(
|
||||
channel_height <= JajceWatercourse.downstream_bed_height(world_z) + 0.08,
|
||||
"Downstream bed should follow its authored profile at z=%.1f" % world_z
|
||||
)
|
||||
_check(
|
||||
maxf(left_bank_height, right_bank_height) - channel_height >= 0.45,
|
||||
"Downstream channel should read below a natural bank at z=%.1f" % world_z
|
||||
)
|
||||
|
||||
var river_surface := world.get_node("WaterRoot/RiverSurface") as Node3D
|
||||
_check_ribbon_tracks_terrain(
|
||||
terrain, river_surface.get_node("RiverRibbon") as MeshInstance3D, "Downstream water"
|
||||
)
|
||||
_check_ribbon_tracks_terrain(
|
||||
terrain, river_surface.get_node("UpperRiverRibbon") as MeshInstance3D, "Upper-stream water"
|
||||
)
|
||||
|
||||
print(
|
||||
(
|
||||
"[TEST] Jajce watercourse samples | shelf=%.2f bank=%.2f pool=%.2f"
|
||||
% [shelf_stream_height, shelf_bank_height, plunge_height]
|
||||
)
|
||||
)
|
||||
_finish()
|
||||
|
||||
|
||||
func _terrain_height(terrain: Terrain3D, world_x: float, world_z: float) -> float:
|
||||
return terrain.data.get_height(Vector3(world_x, 0.0, world_z))
|
||||
|
||||
|
||||
func _check_ribbon_tracks_terrain(
|
||||
terrain: Terrain3D, ribbon: MeshInstance3D, label: String
|
||||
) -> 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]
|
||||
_check(not vertices.is_empty(), "%s ribbon should contain vertices" % label)
|
||||
for vertex_index in range(0, vertices.size(), maxi(1, vertices.size() / 12)):
|
||||
var world_vertex := ribbon.to_global(vertices[vertex_index])
|
||||
var terrain_height := terrain.data.get_height(world_vertex)
|
||||
if is_nan(terrain_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)
|
||||
return
|
||||
|
||||
|
||||
func _check(condition: bool, message: String) -> void:
|
||||
if not condition:
|
||||
failures.append(message)
|
||||
|
||||
|
||||
func _finish() -> void:
|
||||
if failures.is_empty():
|
||||
print("[TEST] Jajce watercourse terrain passed")
|
||||
quit(0)
|
||||
return
|
||||
for failure in failures:
|
||||
push_error("[TEST] " + failure)
|
||||
quit(1)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bs5xfsxii5j8w
|
||||
@@ -1,5 +1,7 @@
|
||||
extends SceneTree
|
||||
|
||||
const JajceWatercourse := preload("res://world/jajce/JajceWatercourse.gd")
|
||||
|
||||
var failures: Array[String] = []
|
||||
|
||||
|
||||
@@ -213,7 +215,7 @@ func _run() -> void:
|
||||
"Waterfall mist particles should have visible billboard geometry"
|
||||
)
|
||||
_check(
|
||||
waterfall_mist.position.distance_to(Vector3(25.0, 2.05, -24.0)) < 0.01,
|
||||
waterfall_mist.position.distance_to(Vector3(25.0, 0.65, -21.5)) < 0.01,
|
||||
"Waterfall mist should stay at the waterfall base instead of obscuring the village"
|
||||
)
|
||||
_check(world.has_node("WaterRoot/RiverSurface"), "WaterRoot should include a river surface")
|
||||
@@ -222,10 +224,34 @@ func _run() -> void:
|
||||
river_ribbon.mesh is ArrayMesh,
|
||||
"River should build a lightweight ribbon from the current Terrain3D surface"
|
||||
)
|
||||
var upper_river_ribbon := (
|
||||
world.get_node("WaterRoot/RiverSurface/UpperRiverRibbon") as MeshInstance3D
|
||||
)
|
||||
_check(
|
||||
upper_river_ribbon.mesh is ArrayMesh,
|
||||
"Waterfall shelf should expose a terrain-following upper stream"
|
||||
)
|
||||
_check(
|
||||
world.has_node("WaterRoot/RiverSurface/PlungePool"),
|
||||
"River should widen into a readable plunge pool beneath the waterfall"
|
||||
)
|
||||
var plunge_pool := world.get_node("WaterRoot/RiverSurface/PlungePool") as MeshInstance3D
|
||||
var expected_pool_height: float = terrain.data.get_height(
|
||||
Vector3(JajceWatercourse.PLUNGE_POOL_CENTER.x, 0.0, JajceWatercourse.PLUNGE_POOL_CENTER.y)
|
||||
)
|
||||
_check(
|
||||
(
|
||||
absf(
|
||||
(
|
||||
plunge_pool.global_position.y
|
||||
- expected_pool_height
|
||||
- JajceWatercourse.WATER_SURFACE_OFFSET
|
||||
)
|
||||
)
|
||||
< 0.03
|
||||
),
|
||||
"Plunge-pool water should follow the carved basin height"
|
||||
)
|
||||
var environment: Environment = (
|
||||
(world.get_node("WorldEnvironment") as WorldEnvironment).environment
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user