rnd(M03): add rendered ground query facade
This commit is contained in:
@@ -17,6 +17,7 @@ const GODOT_WORLD_POSITION_SCRIPT := preload("res://src/domain/coordinates/godot
|
||||
const COORDINATE_MAPPER_SCRIPT := preload("res://src/domain/coordinates/coordinate_mapper.gd")
|
||||
const ADT_TILE_COORDINATE_SCRIPT := preload("res://src/domain/coordinates/adt_tile_coordinate.gd")
|
||||
const ADT_TILE_LOCAL_POSITION_SCRIPT := preload("res://src/domain/coordinates/adt_tile_local_position.gd")
|
||||
const RENDERED_GROUND_SAMPLE_SCRIPT := preload("res://src/render/terrain/rendered_ground_sample.gd")
|
||||
const STREAMING_TARGET_PLANNER_SCRIPT := preload("res://src/render/streaming/streaming_target_planner.gd")
|
||||
const STREAMING_TARGET_POLICY_SCRIPT := preload("res://src/render/streaming/streaming_target_policy.gd")
|
||||
const RENDER_BUDGET_SCHEDULER_SCRIPT := preload("res://src/render/streaming/render_budget_scheduler.gd")
|
||||
@@ -25,6 +26,7 @@ const REQUIRED_SPLAT_TILE_FORMAT_VERSION := 1
|
||||
const REQUIRED_CONTROL_SPLAT_TILE_FORMAT_VERSION := 3
|
||||
const M2_MATERIAL_REFRESH_VERSION := 2
|
||||
const WMO_MATERIAL_REFRESH_VERSION := 10
|
||||
const RENDER_GROUND_QUERY_RAY_HEIGHT_UNITS := 5000.0
|
||||
|
||||
const TILE_SIZE := COORDINATE_MAPPER_SCRIPT.ADT_TILE_SIZE_YARDS
|
||||
const CHUNK_SIZE := COORDINATE_MAPPER_SCRIPT.ADT_CHUNK_SIZE_YARDS
|
||||
@@ -1038,6 +1040,173 @@ func render_baseline_snapshot() -> Dictionary:
|
||||
}
|
||||
|
||||
|
||||
## Samples already loaded render terrain at one typed Godot world position.
|
||||
## This diagnostic rendering view is not authoritative gameplay collision.
|
||||
func sample_rendered_ground_height(godot_world_position: GodotWorldPosition) -> RefCounted:
|
||||
var ground_query_snapshot := render_ground_query_snapshot(godot_world_position)
|
||||
if ground_query_snapshot.has("terrain_height"):
|
||||
return RENDERED_GROUND_SAMPLE_SCRIPT.available(float(ground_query_snapshot["terrain_height"]))
|
||||
var query_status := StringName(ground_query_snapshot.get("status", "render_terrain_unavailable"))
|
||||
match query_status:
|
||||
&"mesh_not_ready":
|
||||
return RENDERED_GROUND_SAMPLE_SCRIPT.unavailable(&"render_terrain_mesh_not_ready")
|
||||
&"no_intersection":
|
||||
return RENDERED_GROUND_SAMPLE_SCRIPT.unavailable(&"render_terrain_no_intersection")
|
||||
_:
|
||||
return RENDERED_GROUND_SAMPLE_SCRIPT.unavailable(&"render_terrain_unavailable")
|
||||
|
||||
|
||||
## Returns a detached diagnostic read model for the loaded terrain around one
|
||||
## typed position. Queue/cache ownership and mutable tile state stay internal.
|
||||
func render_ground_query_snapshot(godot_world_position: GodotWorldPosition) -> Dictionary:
|
||||
var world_position := Vector3(
|
||||
godot_world_position.x_units,
|
||||
godot_world_position.y_units,
|
||||
godot_world_position.z_units
|
||||
)
|
||||
var tile_coordinate = COORDINATE_MAPPER_SCRIPT.godot_to_adt_tile(godot_world_position)
|
||||
var highest_terrain_height := -INF
|
||||
var intersected_tile_key := ""
|
||||
var ready_mesh_count := 0
|
||||
for tile_y in range(tile_coordinate.tile_y - 1, tile_coordinate.tile_y + 2):
|
||||
for tile_x in range(tile_coordinate.tile_x - 1, tile_coordinate.tile_x + 2):
|
||||
var tile_key := _tile_key(tile_x, tile_y)
|
||||
if not _tile_states.has(tile_key):
|
||||
continue
|
||||
var terrain_state: Dictionary = _tile_states[tile_key]
|
||||
var terrain_height_variant = _intersect_rendered_terrain_state(terrain_state, world_position)
|
||||
if terrain_height_variant == null:
|
||||
continue
|
||||
ready_mesh_count += 1
|
||||
var terrain_height := float(terrain_height_variant)
|
||||
if terrain_height > highest_terrain_height:
|
||||
highest_terrain_height = terrain_height
|
||||
intersected_tile_key = tile_key
|
||||
|
||||
if is_finite(highest_terrain_height):
|
||||
return {
|
||||
"status": "sampled",
|
||||
"tile": intersected_tile_key,
|
||||
"terrain_height": highest_terrain_height,
|
||||
}
|
||||
|
||||
var focus_tile_key := _tile_key(tile_coordinate.tile_x, tile_coordinate.tile_y)
|
||||
var missing_snapshot := {
|
||||
"status": "no_intersection" if ready_mesh_count > 0 else "mesh_not_ready",
|
||||
"tile": focus_tile_key,
|
||||
}
|
||||
missing_snapshot.merge(
|
||||
_render_ground_tile_diagnostic(focus_tile_key, world_position),
|
||||
true
|
||||
)
|
||||
if bool(missing_snapshot.get("quality_mesh_present", false)) or bool(
|
||||
missing_snapshot.get("tile_lod_mesh_present", false)):
|
||||
missing_snapshot["status"] = "no_intersection"
|
||||
missing_snapshot.merge(_nearest_rendered_terrain_sample(world_position), true)
|
||||
if missing_snapshot.get("nearest_sample_distance", null) != null:
|
||||
missing_snapshot["status"] = "sampled_nearby"
|
||||
missing_snapshot["terrain_height"] = float(missing_snapshot["nearest_sample_height"])
|
||||
return missing_snapshot
|
||||
|
||||
|
||||
func _intersect_rendered_terrain_state(terrain_state: Dictionary, world_position: Vector3):
|
||||
var terrain_mesh: Mesh = terrain_state.get("quality_terrain_mesh", null)
|
||||
if terrain_mesh == null:
|
||||
terrain_mesh = terrain_state.get("tile_lod_mesh", null)
|
||||
if terrain_mesh == null:
|
||||
return null
|
||||
var triangle_mesh := terrain_mesh.generate_triangle_mesh()
|
||||
if triangle_mesh == null:
|
||||
return null
|
||||
var tile_root := terrain_state.get("root", null) as Node3D
|
||||
if tile_root == null or not is_instance_valid(tile_root):
|
||||
return null
|
||||
var inverse_transform := tile_root.global_transform.affine_inverse()
|
||||
var local_ray_origin := inverse_transform * Vector3(
|
||||
world_position.x,
|
||||
RENDER_GROUND_QUERY_RAY_HEIGHT_UNITS,
|
||||
world_position.z
|
||||
)
|
||||
var local_ray_direction := inverse_transform.basis * Vector3.DOWN
|
||||
var intersection: Dictionary = triangle_mesh.intersect_ray(local_ray_origin, local_ray_direction)
|
||||
if intersection.is_empty():
|
||||
return null
|
||||
var local_hit: Vector3 = intersection.get("position", Vector3.ZERO)
|
||||
return (tile_root.global_transform * local_hit).y
|
||||
|
||||
|
||||
func _render_ground_tile_diagnostic(tile_key: String, world_position: Vector3) -> Dictionary:
|
||||
var queued_index := -1
|
||||
for request_index in _tile_load_queue.size():
|
||||
var tile_request: Dictionary = _tile_load_queue[request_index]
|
||||
if String(tile_request.get("key", "")) == tile_key:
|
||||
queued_index = request_index
|
||||
break
|
||||
var diagnostic_snapshot := {
|
||||
"available": _available_tiles.has(tile_key),
|
||||
"queued_index": queued_index,
|
||||
"loading": _tile_loading_tasks.has(tile_key),
|
||||
"state_present": _tile_states.has(tile_key),
|
||||
"load_queue_size": _tile_load_queue.size(),
|
||||
}
|
||||
if not _tile_states.has(tile_key):
|
||||
return diagnostic_snapshot
|
||||
|
||||
var terrain_state: Dictionary = _tile_states[tile_key]
|
||||
var quality_mesh: Mesh = terrain_state.get("quality_terrain_mesh", null)
|
||||
var tile_lod_mesh: Mesh = terrain_state.get("tile_lod_mesh", null)
|
||||
diagnostic_snapshot["quality_mesh_present"] = quality_mesh != null
|
||||
diagnostic_snapshot["tile_lod_mesh_present"] = tile_lod_mesh != null
|
||||
diagnostic_snapshot["quality_source"] = String(terrain_state.get("quality_terrain_source", ""))
|
||||
diagnostic_snapshot["tile_lod"] = int(terrain_state.get("tile_lod", -1))
|
||||
var tile_root := terrain_state.get("root", null) as Node3D
|
||||
if tile_root != null and is_instance_valid(tile_root):
|
||||
var local_position := tile_root.global_transform.affine_inverse() * world_position
|
||||
diagnostic_snapshot["tile_root_position"] = _render_ground_vector3_array(tile_root.global_position)
|
||||
diagnostic_snapshot["probe_local_position"] = _render_ground_vector3_array(local_position)
|
||||
var diagnostic_mesh := quality_mesh if quality_mesh != null else tile_lod_mesh
|
||||
if diagnostic_mesh != null:
|
||||
var mesh_bounds := diagnostic_mesh.get_aabb()
|
||||
diagnostic_snapshot["mesh_aabb_position"] = _render_ground_vector3_array(mesh_bounds.position)
|
||||
diagnostic_snapshot["mesh_aabb_size"] = _render_ground_vector3_array(mesh_bounds.size)
|
||||
return diagnostic_snapshot
|
||||
|
||||
|
||||
func _nearest_rendered_terrain_sample(world_position: Vector3) -> Dictionary:
|
||||
for radius_units in [2.0, 5.0, 10.0, 20.0, 40.0]:
|
||||
for offset in [
|
||||
Vector2(radius_units, 0.0),
|
||||
Vector2(-radius_units, 0.0),
|
||||
Vector2(0.0, radius_units),
|
||||
Vector2(0.0, -radius_units),
|
||||
]:
|
||||
var sample_position := world_position + Vector3(offset.x, 0.0, offset.y)
|
||||
var typed_sample_position = GODOT_WORLD_POSITION_SCRIPT.new(
|
||||
sample_position.x,
|
||||
sample_position.y,
|
||||
sample_position.z
|
||||
)
|
||||
var sample_tile = COORDINATE_MAPPER_SCRIPT.godot_to_adt_tile(typed_sample_position)
|
||||
var sample_tile_key := _tile_key(sample_tile.tile_x, sample_tile.tile_y)
|
||||
if not _tile_states.has(sample_tile_key):
|
||||
continue
|
||||
var terrain_height_variant = _intersect_rendered_terrain_state(
|
||||
_tile_states[sample_tile_key],
|
||||
sample_position
|
||||
)
|
||||
if terrain_height_variant != null:
|
||||
return {
|
||||
"nearest_sample_distance": radius_units,
|
||||
"nearest_sample_height": float(terrain_height_variant),
|
||||
"nearest_sample_tile": sample_tile_key,
|
||||
}
|
||||
return {"nearest_sample_distance": null}
|
||||
|
||||
|
||||
func _render_ground_vector3_array(vector: Vector3) -> Array[float]:
|
||||
return [vector.x, vector.y, vector.z]
|
||||
|
||||
|
||||
func _tick_runtime_stats(delta: float) -> void:
|
||||
if not runtime_stats_enabled or Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user