neon-sprawl/client/scripts/random_floor_bumps.gd

128 lines
3.8 KiB
GDScript

extends RefCounted
## Prototype QA: two short bumps on StaticBody3D siblings of Floor (same NavigationRegion3D parent).
## Avoids one body with floor + cylinder (compound internal edges caused idle jitter beside bumps).
## Bump mesh group and collision fudge: random_floor_bump_collision_constants.gd.
## Spawner called from main.gd before nav bake.
## Collision radius > mesh radius; spawn before `NavigationRegion3D.bake_navigation_mesh()`.
const BUMP_COLLISION_CONSTS_SCRIPT: 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(BUMP_COLLISION_CONSTS_SCRIPT.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 + BUMP_COLLISION_CONSTS_SCRIPT.COLLISION_RADIUS_EXTRA,
BUMP_COLLISION_CONSTS_SCRIPT.COLLISION_RADIUS_MAX
)
col.shape = csh
col.position = Vector3.ZERO
bump_root.add_child(col)