extends StaticBody3D
## Procedural smooth grid for the physics ramp strip: raised hill or sunken dip.
## Uses HeightMapShape3D for collision (CharacterBody3D tunnels through ConcavePolygonShape3D
## trimesh).
## Keep the extension slab (`PhysicsRampTestFloorExtension`) short of this body's xz footprint so a
## dip is not covered by a flat floor collider/render.

enum PieceKind { HILL, DIP }

@export var kind: PieceKind = PieceKind.HILL
@export var footprint_x: float = 7.0
@export var footprint_z: float = 4.5
@export var amplitude: float = 0.42
@export var subdiv_x: int = 36
@export var subdiv_z: int = 22
## Slight Y offset so the rim is not coplanar with the slab (reduces z-fighting with the floor).
@export var base_lift: float = 0.022


func _ready() -> void:
	collision_layer = 1
	_build_mesh_and_visual()
	_build_heightmap_collision()


func _build_mesh_and_visual() -> void:
	var mi: MeshInstance3D = get_node_or_null("MeshInstance3D") as MeshInstance3D
	if mi == null:
		push_error("prototype_smooth_hill_piece: expected MeshInstance3D child.")
		return

	var sx: int = maxi(3, subdiv_x)
	var sz: int = maxi(3, subdiv_z)
	var half_x: float = footprint_x * 0.5
	var half_z: float = footprint_z * 0.5
	var height_sign := 1.0 if kind == PieceKind.HILL else -1.0

	var verts := PackedVector3Array()
	verts.resize((sx + 1) * (sz + 1))
	var uvs := PackedVector2Array()
	uvs.resize(verts.size())

	var idx := 0
	for j in sz + 1:
		var v: float = float(j) / float(sz)
		var z: float = lerpf(-half_z, half_z, v)
		for i in sx + 1:
			var u: float = float(i) / float(sx)
			var x: float = lerpf(-half_x, half_x, u)
			var envelope: float = sin(PI * u) * sin(PI * v)
			var y: float = base_lift + height_sign * amplitude * envelope
			verts[idx] = Vector3(x, y, z)
			uvs[idx] = Vector2(u, v)
			idx += 1

	var st := SurfaceTool.new()
	st.begin(Mesh.PRIMITIVE_TRIANGLES)
	for j in sz:
		for i in sx:
			var a: int = j * (sx + 1) + i
			var b: int = a + 1
			var c: int = a + (sx + 1)
			var d: int = c + 1
			for tri in [[a, c, b], [b, c, d]]:
				st.set_uv(uvs[tri[0]])
				st.add_vertex(verts[tri[0]])
				st.set_uv(uvs[tri[1]])
				st.add_vertex(verts[tri[1]])
				st.set_uv(uvs[tri[2]])
				st.add_vertex(verts[tri[2]])
	st.generate_normals()
	var mesh: ArrayMesh = st.commit()
	mi.mesh = mesh
	mi.material_override = _qa_material_for_kind()


func _build_heightmap_collision() -> void:
	var cs: CollisionShape3D = get_node_or_null("CollisionShape3D") as CollisionShape3D
	if cs == null:
		push_error("prototype_smooth_hill_piece: expected CollisionShape3D child.")
		return

	var sx: int = maxi(3, subdiv_x)
	var height_sign := 1.0 if kind == PieceKind.HILL else -1.0

	var mw: int = maxi(3, sx + 1)
	var md: int = maxi(3, subdiv_z + 1)
	var s: float = footprint_x / float(mw - 1)
	while (md - 1) * s < footprint_z - 0.0001 and md < 512:
		md += 1

	var data := PackedFloat32Array()
	data.resize(mw * md)
	for j in md:
		var v: float = float(j) / float(md - 1) if md > 1 else 0.0
		for i in mw:
			var u: float = float(i) / float(mw - 1) if mw > 1 else 0.0
			var envelope: float = sin(PI * u) * sin(PI * v)
			var y_world: float = base_lift + height_sign * amplitude * envelope
			data[j * mw + i] = y_world / s

	var hm := HeightMapShape3D.new()
	hm.map_width = mw
	hm.map_depth = md
	hm.map_data = data
	cs.shape = hm
	cs.scale = Vector3(s, s, s)
	cs.position = Vector3.ZERO


func _qa_material_for_kind() -> StandardMaterial3D:
	var m := StandardMaterial3D.new()
	m.cull_mode = BaseMaterial3D.CULL_DISABLED
	m.roughness = 0.45
	m.metallic = 0.0
	m.emission_enabled = true
	m.emission_energy_multiplier = 0.85
	if kind == PieceKind.HILL:
		m.albedo_color = Color(0.98, 0.12, 0.42)
		m.emission = Color(0.45, 0.02, 0.15)
	else:
		m.albedo_color = Color(0.08, 0.35, 0.98)
		m.emission = Color(0.02, 0.12, 0.45)
	return m
