Anchor beetles to generated tree surfaces

This commit is contained in:
Alexander Sellite 2026-08-24 10:36:45 -04:00
parent f1c952f5d6
commit 5ed4cccf76
18 changed files with 446 additions and 71 deletions

View file

@ -1131,49 +1131,36 @@ func _validate_tree_gatherable_anchors(
decorations: Node3D,
anchors: GatherableAnchorSet3D,
) -> void:
var eligible_props: Array[Node3D] = []
var eligible_props: Dictionary[StringName, Node3D] = {}
for child: Node in decorations.get_children():
var prop := child as Node3D
if prop == null:
continue
var prop_id := StringName(prop.get_meta(&"terrain_prop_id", &""))
var definition := region.get_prop_catalog().definition_for_id(prop_id)
if definition != null and definition.gatherable_anchor_height > 0.0:
eligible_props.append(prop)
if definition != null and definition.has_gatherable_surface():
eligible_props[prop.name] = prop
var positions := anchors.get_spawn_positions()
assert(positions.size() == eligible_props.size())
for prop: Node3D in eligible_props:
assert(positions.size() >= 12)
for child: Node in anchors.get_children():
var anchor := child as Marker3D
assert(anchor != null)
assert(bool(anchor.get_meta(&"mesh_surface_sampled", false)))
var prop_name := StringName(anchor.get_meta(&"terrain_prop_name", &""))
assert(eligible_props.has(prop_name))
var prop: Node3D = eligible_props[prop_name]
var prop_id := StringName(prop.get_meta(&"terrain_prop_id", &""))
var definition := region.get_prop_catalog().definition_for_id(prop_id)
var visual_scale := float(
prop.get_meta(&"terrain_prop_visual_scale", 1.0)
)
var nearest_anchor := Vector3(INF, INF, INF)
var nearest_distance_squared := INF
for position: Vector3 in positions:
var distance_squared := position.distance_squared_to(
prop.global_position
)
if distance_squared < nearest_distance_squared:
nearest_distance_squared = distance_squared
nearest_anchor = position
assert(nearest_anchor.is_finite())
var horizontal_distance := Vector2(
nearest_anchor.x - prop.global_position.x,
nearest_anchor.z - prop.global_position.z,
).length()
assert(definition != null and definition.has_gatherable_surface())
var local_anchor := prop.to_local(anchor.global_position)
assert(
horizontal_distance
>= definition.gatherable_anchor_surface_radius() * visual_scale
local_anchor.y
>= definition.gatherable_surface_minimum_height - 0.001
)
assert(
absf(
nearest_anchor.y
- (
prop.global_position.y
+ definition.gatherable_anchor_height * visual_scale
)
) <= 0.001
local_anchor.y
<= definition.gatherable_surface_maximum_height + 0.001
)

View file

@ -7,6 +7,9 @@ const Gatherables: GatherableCatalog = preload(
"res://gathering/catalog/gatherable_catalog.tres"
)
const FishCatalog: FishPool = preload("res://fish/pools/fish_catalog.tres")
const MeshSurfaceAnchorSamplerType = preload(
"res://world/generation/mesh_surface_anchor_sampler.gd"
)
func _initialize() -> void:
@ -15,6 +18,7 @@ func _initialize() -> void:
func _run() -> void:
_validate_beetle_data()
await _validate_mesh_surface_sampler()
await _validate_tree_anchors()
_validate_anchored_presentation()
_validate_three_dimensional_targeting()
@ -32,11 +36,74 @@ func _validate_beetle_data() -> void:
assert(beetle.required_tool_id == &"crab_net")
assert(beetle.spawn_anchor_set_id == &"starter_reachable_tree_trunks")
assert(beetle.population == 3)
assert(is_equal_approx(beetle.spawn_anchor_occupancy_ratio, 0.35))
assert(beetle.maximum_anchor_population == 64)
assert(beetle.target_population_for_anchor_count(8) == 3)
assert(beetle.target_population_for_anchor_count(40) == 14)
assert(beetle.target_population_for_anchor_count(400) == 64)
assert(is_equal_approx(beetle.sprite_pixel_size, 0.005))
assert(beetle.is_stationary_spawn())
assert(not beetle.can_be_scared())
func _validate_mesh_surface_sampler() -> void:
var prop := Node3D.new()
# The accessibility band is local to the planted prop, so the same tree on
# a raised cliff remains reachable from that cliff's walkable surface.
prop.position = Vector3(3.0, 12.0, -4.0)
root.add_child(prop)
var visual := MeshInstance3D.new()
var box := BoxMesh.new()
box.size = Vector3(2.0, 4.0, 2.0)
var wood := StandardMaterial3D.new()
wood.resource_name = "wood"
box.material = wood
visual.mesh = box
visual.position.y = 2.0
prop.add_child(visual)
await process_frame
var random := RandomNumberGenerator.new()
random.seed = 115
var sample: Dictionary = MeshSurfaceAnchorSamplerType.sample_vertical_surface(
prop,
prop,
PackedStringArray(["wood"]),
0.7,
1.5,
0.35,
0.025,
random,
)
assert(not sample.is_empty())
var surface_position: Vector3 = sample["surface_position"]
var anchor_position: Vector3 = sample["position"]
assert(surface_position.y >= 0.7 and surface_position.y <= 1.5)
var world_anchor_position := prop.to_global(anchor_position)
assert(
world_anchor_position.y >= 12.7
and world_anchor_position.y <= 13.5
)
assert(is_equal_approx(
maxf(absf(surface_position.x), absf(surface_position.z)),
1.0,
))
assert(is_equal_approx(
anchor_position.distance_to(surface_position),
0.025,
))
assert(MeshSurfaceAnchorSamplerType.sample_vertical_surface(
prop,
prop,
PackedStringArray(["leaf"]),
0.7,
1.5,
0.35,
0.025,
random,
).is_empty())
prop.queue_free()
func _validate_tree_anchors() -> void:
var region := StarterIslandScene.instantiate() as WorldRegion
root.add_child(region)

View file

@ -73,6 +73,8 @@ func _validate_catalog_statuses() -> void:
assert(beetle.catch_data.collection_method == FishData.CollectionMethod.NET)
assert(beetle.required_tool_id == &"crab_net")
assert(beetle.spawn_anchor_set_id == &"starter_reachable_tree_trunks")
assert(is_equal_approx(beetle.spawn_anchor_occupancy_ratio, 0.35))
assert(beetle.maximum_anchor_population == 64)
assert(beetle.is_stationary_spawn())
assert(not beetle.is_stationary_hotspot())
assert(not beetle.requires_sneaking)