extends RefCounted ## Prototype QA: two short **cylinder** bumps, each on its own **`StaticBody3D`** **sibling** of **`Floor`** (same parent: ## **`NavigationRegion3D`**). Avoids **compound internal edges** (floor box + cylinder on one body), which caused strong ## idle jitter beside the bump. Meshes use group **`random_floor_bump_mesh`** (see **`random_floor_bump_collision_constants.gd`**). Loaded via **`main.gd`** **`load().call`**. ## **Collision radius > mesh radius** (lip before visual rim); values in **`random_floor_bump_collision_constants.gd`**. Spawn **before** `NavigationRegion3D.bake_navigation_mesh()`. const _random_floor_bump_collision: Script = preload( "res://scripts/random_floor_bump_collision_constants.gd" ) const COUNT: int = 2 const HEIGHT_MIN: float = 0.08 const HEIGHT_MAX: float = 0.14 const RADIUS_MIN: float = 0.36 const RADIUS_MAX: float = 0.62 const FLOOR_SINK: float = 0.008 const XZ_RANGE: float = 8.15 const MIN_PAIR_SEPARATION: float = 2.85 const MAX_PLACE_ATTEMPTS: int = 64 const EXCLUSION_MARGIN: float = 0.72 static func spawn_short_random_bumps(floor_node: StaticBody3D) -> void: var rng := RandomNumberGenerator.new() rng.randomize() var mat := StandardMaterial3D.new() mat.albedo_color = Color(0.22, 0.52, 0.32, 1.0) mat.roughness = 1.0 mat.metallic = 0.0 mat.specular_mode = 2 var placed_xz: Array[Vector2] = [] for idx: int in COUNT: var xz: Vector2 = Vector2.ZERO var ok := false for _a: int in MAX_PLACE_ATTEMPTS: xz = Vector2( rng.randf_range(-XZ_RANGE, XZ_RANGE), rng.randf_range(-XZ_RANGE, XZ_RANGE) ) if _xz_in_exclusion(xz): continue var far_enough := true for p: Vector2 in placed_xz: if xz.distance_to(p) < MIN_PAIR_SEPARATION: far_enough = false break if not far_enough: continue ok = true break if not ok: push_warning("RandomFloorBumps: could not place bump %d after %d tries" % [idx, MAX_PLACE_ATTEMPTS]) continue placed_xz.append(xz) var h: float = rng.randf_range(HEIGHT_MIN, HEIGHT_MAX) var r: float = rng.randf_range(RADIUS_MIN, RADIUS_MAX) _add_cylinder_bump(floor_node, xz.x, xz.y, h, r, mat, idx) static func _xz_in_exclusion(xz: Vector2) -> bool: var ex: Array[Vector2] = [ Vector2(-5.0, -5.0), Vector2(0.0, 0.0), Vector2(6.0, 5.0), Vector2(7.5, -6.5), Vector2(9.0, 9.0), ] var rad: Array[float] = [2.65, 1.2, 2.25, 1.65, 1.85] for i: int in ex.size(): if xz.distance_to(ex[i]) < rad[i] + EXCLUSION_MARGIN: return true return false static func _add_cylinder_bump( floor_node: StaticBody3D, x: float, z: float, height: float, radius: float, mat: StandardMaterial3D, idx: int ) -> void: var cy: float = height * 0.5 - FLOOR_SINK var local_center := Vector3(x, cy, z) var nav_parent := floor_node.get_parent() if nav_parent == null: push_error("RandomFloorBumps: Floor has no parent; cannot spawn bump") return var bump_root := StaticBody3D.new() bump_root.name = "RandomTestBump%d" % idx bump_root.add_to_group("walkable") bump_root.collision_layer = floor_node.collision_layer bump_root.collision_mask = floor_node.collision_mask nav_parent.add_child(bump_root) bump_root.global_position = floor_node.to_global(local_center) var mesh_inst := MeshInstance3D.new() mesh_inst.name = "RandomTestBump%d_mesh" % idx mesh_inst.add_to_group(_random_floor_bump_collision.RANDOM_FLOOR_BUMP_MESH_GROUP) var cm := CylinderMesh.new() cm.height = height cm.top_radius = radius cm.bottom_radius = radius cm.radial_segments = 24 mesh_inst.mesh = cm mesh_inst.set_surface_override_material(0, mat) mesh_inst.position = Vector3.ZERO bump_root.add_child(mesh_inst) var col := CollisionShape3D.new() col.name = "RandomTestBump%d_shape" % idx var csh := CylinderShape3D.new() csh.height = height csh.radius = minf( radius + _random_floor_bump_collision.COLLISION_RADIUS_EXTRA, _random_floor_bump_collision.COLLISION_RADIUS_MAX ) col.shape = csh col.position = Vector3.ZERO bump_root.add_child(col)