From 5272ebdc0aa331fea49988c5d7c8dc72b3335c0c Mon Sep 17 00:00:00 2001 From: Rob Kelly Date: Fri, 3 Oct 2025 17:11:59 -0600 Subject: [PATCH] Constrained grid generation layer --- .../generation/feature/generation_feature.gd | 24 +++- .../generation/layer/grid_layer/grid_layer.gd | 117 ++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/world/generation/layer/grid_layer/grid_layer.gd diff --git a/src/world/generation/feature/generation_feature.gd b/src/world/generation/feature/generation_feature.gd index c77ba6c..06c9812 100644 --- a/src/world/generation/feature/generation_feature.gd +++ b/src/world/generation/feature/generation_feature.gd @@ -3,7 +3,29 @@ class_name GenerationFeature extends Node3D ## ## Layers contain features. Some features may contain layers. +@export var noise_scale := Vector3.ONE +@export var noise_offset := Vector3.ZERO + +@export var sub_layers: Array[GenerationLayer] + +var _generated := false + func probe() -> void: # TODO may want to make low-detail & high-detail probes distinct - pass # Implemented in derived type. + if not _generated: + generate() + _generated = true + + for layer in sub_layers: + layer.probe() + + +## Generate elements of this feature. Called by default on the first call to `probe` +func generate() -> void: + pass # Implemented in derived type + + +func sample_noise() -> float: + var sample_point := global_position * noise_scale + noise_offset + return WorldGenManager.noise.get_noise_3dv(sample_point) diff --git a/src/world/generation/layer/grid_layer/grid_layer.gd b/src/world/generation/layer/grid_layer/grid_layer.gd new file mode 100644 index 0000000..dc7caa1 --- /dev/null +++ b/src/world/generation/layer/grid_layer/grid_layer.gd @@ -0,0 +1,117 @@ +@tool +class_name GridLayer extends GenerationLayer +## A layer that generates tiles in a locally-constrained grid. + +## Bounding box for the grid on the local XZ plane. +## +## Note that only feature handles are checked to be within the bounding box. +## The Y component of this AABB is not used. +@export var bounding_box: AABB: + set(value): + bounding_box = value + if _debug_box: + _set_debug_box_shape(bounding_box) + +## Size of a grid tile in the local XZ plane. +@export var grid_size: Vector2 + +@export_group("Debug Draw") +@export var draw_debug := true +@export var debug_color := Color("#a486006b"): + set(value): + debug_color = value + if _debug_box: + _set_debug_box_color(debug_color) + +var _debug_meshinstance: MeshInstance3D +var _debug_box: BoxMesh + + +func _ready() -> void: + if Engine.is_editor_hint(): + _init_debug_draw() + + +func probe() -> void: + var world_pos := WorldGenManager.get_generation_point() + probe_radius(world_pos, WorldGenManager.med_detail_radius) + # TODO high-detail & low-detail probes + + +func probe_radius(center: Vector3, radius: float) -> void: + var rad_diff := Vector3(radius, 0, radius) + + # Translate probe box limits to grid space + var grid_low := world_to_local(center - rad_diff).floor() + var grid_high := world_to_local(center + rad_diff).floor() + var grid_max := _plane_size().floor() + + # Constrain to bounding box + var x_min := maxf(grid_low.x, 0) + var x_max := minf(grid_high.x + 1, grid_max.x) + var y_min := maxf(grid_low.y, 0) + var y_max := minf(grid_high.y + 1, grid_max.y) + + # Probe everything within radius + for i in range(x_min, x_max): + for j in range(y_min, y_max): + probe_grid(Vector2(i, j)) + + +func probe_grid(_grid_pos: Vector2) -> void: + pass # Implement in derived type + + +func _plane_size() -> Vector2: + return Vector2(bounding_box.size.x / grid_size.x, bounding_box.size.z / grid_size.y) + + +func is_bounding_box_valid() -> bool: + return bounding_box.size.length_squared() > 0 + + +func is_point_in_bounds(local_pos: Vector2) -> bool: + local_pos = local_pos.floor() + var grid_max := _plane_size() + return ( + 0 <= local_pos.x + and local_pos.x < grid_max.x + and 0 <= local_pos.y + and local_pos.y < grid_max.y + ) + + +func local_to_world(local_pos: Vector2) -> Vector3: + var v3_pos := Vector3(local_pos.x * grid_size.x, 0, local_pos.y * grid_size.y) + return global_transform * (bounding_box.position + v3_pos) + + +func world_to_local(world_pos: Vector3) -> Vector2: + var rel_pos := world_pos * global_transform - bounding_box.position + return Vector2(rel_pos.x / grid_size.x, rel_pos.z / grid_size.y) + + +#region debug draw +func _init_debug_draw() -> void: + _debug_box = BoxMesh.new() + var mat := StandardMaterial3D.new() + mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + mat.cull_mode = BaseMaterial3D.CULL_DISABLED + mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + _debug_box.material = mat + _debug_meshinstance = MeshInstance3D.new() + _debug_meshinstance.mesh = _debug_box + add_child(_debug_meshinstance) + _set_debug_box_color(debug_color) + _set_debug_box_shape(bounding_box) + + +func _set_debug_box_color(color: Color) -> void: + var mat := _debug_box.material as StandardMaterial3D + mat.albedo_color = color + + +func _set_debug_box_shape(aabb: AABB) -> void: + _debug_meshinstance.position = aabb.position + aabb.size / 2 + _debug_box.size = aabb.size +#endregion