From 966968e54d1ab21a494b7ad8a789b96b6557ebd9 Mon Sep 17 00:00:00 2001 From: Voyager Date: Mon, 24 Aug 2026 13:51:11 -0400 Subject: [PATCH] Restrict generated clam spots to visible sand --- tests/digging_prototype_validation.gd | 56 +++++++++++++++++++++++++++ world/digging/diggable_area_3d.gd | 19 ++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/tests/digging_prototype_validation.gd b/tests/digging_prototype_validation.gd index c19bf0b..21f8629 100644 --- a/tests/digging_prototype_validation.gd +++ b/tests/digging_prototype_validation.gd @@ -11,6 +11,15 @@ const Gatherables: GatherableCatalog = preload( "res://gathering/catalog/gatherable_catalog.tres" ) +class PrimaryTerrainProvider: + extends Node3D + + var primary_mesh: MeshInstance3D + + + func get_primary_terrain_meshes() -> Array[MeshInstance3D]: + return [primary_mesh] if primary_mesh != null else [] + func _initialize() -> void: call_deferred("_run") @@ -20,6 +29,7 @@ func _run() -> void: _validate_catalog_content() _validate_flat_shovel() await _validate_beach_authoring() + _validate_primary_terrain_provider() print("Digging prototype validation: PASS") quit() @@ -81,6 +91,52 @@ func _validate_beach_authoring() -> void: region.queue_free() +func _validate_primary_terrain_provider() -> void: + var fixture := Node3D.new() + fixture.name = "PrimaryTerrainFixture" + root.add_child(fixture) + var provider := PrimaryTerrainProvider.new() + provider.name = "Provider" + fixture.add_child(provider) + provider.primary_mesh = _flat_sand_triangle(0.0) + provider.add_child(provider.primary_mesh) + var buried_base := _flat_sand_triangle(10.0) + buried_base.name = "BuriedSandBase" + provider.add_child(buried_base) + var area := DiggableArea3D.new() + area.name = "DiggableArea" + area.area_id = &"provider_test" + area.terrain_source = NodePath("../Provider") + area.surface_materials = [&"sand"] + area.generation_bounds = Rect2(-20.0, -20.0, 40.0, 40.0) + fixture.add_child(area) + var triangles := area.get_surface_triangles() + assert(triangles.size() == 1) + var center := (triangles[0][0] + triangles[0][1] + triangles[0][2]) / 3.0 + assert(center.x < 2.0) + fixture.free() + + +func _flat_sand_triangle(x_offset: float) -> MeshInstance3D: + var arrays: Array = [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = PackedVector3Array([ + Vector3(x_offset, 0.0, 0.0), + Vector3(x_offset, 0.0, 1.0), + Vector3(x_offset + 1.0, 0.0, 0.0), + ]) + arrays[Mesh.ARRAY_INDEX] = PackedInt32Array([0, 1, 2]) + var mesh := ArrayMesh.new() + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) + var material := StandardMaterial3D.new() + material.resource_name = "sand" + mesh.surface_set_material(0, material) + var mesh_instance := MeshInstance3D.new() + mesh_instance.name = "PrimarySand" + mesh_instance.mesh = mesh + return mesh_instance + + func _collect_meshes( root_node: Node, result: Array[MeshInstance3D], diff --git a/world/digging/diggable_area_3d.gd b/world/digging/diggable_area_3d.gd index 1c34070..47270b1 100644 --- a/world/digging/diggable_area_3d.gd +++ b/world/digging/diggable_area_3d.gd @@ -18,7 +18,7 @@ func get_surface_triangles() -> Array[PackedVector3Array]: var terrain_root: Node = get_node_or_null(terrain_source) if terrain_root == null: return triangles - for mesh_instance: MeshInstance3D in _collect_mesh_instances(terrain_root): + for mesh_instance: MeshInstance3D in _terrain_mesh_instances(terrain_root): var mesh: Mesh = mesh_instance.mesh if mesh == null: continue @@ -92,6 +92,23 @@ func _append_triangle( result.append(PackedVector3Array([a, b, c])) +func _terrain_mesh_instances(terrain_root: Node) -> Array[MeshInstance3D]: + # Generated terrain can contain authored base layers beneath raised visual + # overlays. Those meshes are useful for closing terrain seams, but they are + # not necessarily the visible surface and must not produce buried dig spots. + # A terrain provider may therefore expose its authoritative primary meshes. + if terrain_root.has_method(&"get_primary_terrain_meshes"): + var provided: Variant = terrain_root.call(&"get_primary_terrain_meshes") + var meshes: Array[MeshInstance3D] = [] + if provided is Array: + for value: Variant in provided: + var mesh_instance := value as MeshInstance3D + if mesh_instance != null: + meshes.append(mesh_instance) + return meshes + return _collect_mesh_instances(terrain_root) + + func _collect_mesh_instances(root: Node) -> Array[MeshInstance3D]: var meshes: Array[MeshInstance3D] = [] if root is MeshInstance3D: