OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_edge_set.gd

171 lines · gd ref:
extends Node
## GATE — THE EDGE SET (#120 L3, docs/node-tree-and-stacks.md §14.4): edge mode, Shift-click
## builds a cross-brush SET of edges, as face mode does for faces. An edge is held by
## durable names — the brush key and its two plane keys — never by a picked point.
##   E1  a plain click on an edge: a set of one, {b, p: the two plane keys, sorted}
##   E2  Shift-click another edge of the SAME brush: two, and the brush stays selected
##   E3  Shift-click an edge of another brush: three
##   E4  Shift-click the first edge again: removed (two)
##   E5  selected_edges() resolves each from its planes: the right endpoints and normals
##   E6  the set survives a re-fold
##   E7  the highlight draws every edge in the set
##   E8  a brush gone: its edge drops out of selected_edges()
##   E9  a plain click restarts the set
##   E10 face mode clears it
##
## RUN AS A SCENE: CI=true "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_edge_set.tscn

var fails := 0
var checks := 0
var _cam: Camera3D = null


func _ok(what: String, cond: bool, note: Variant = "") -> void:
	checks += 1
	if not cond:
		fails += 1
	print("  %-66s %s  %s" % [what, "OK" if cond else "**FAIL**", str(note)])


func _ready() -> void:
	var gs: Node = get_tree().root.get_node_or_null("/root/GameState")
	gs.route("world:level:*", 1, "peer")
	var keet: Node = get_tree().root.get_node_or_null("/root/KeetService")
	if keet != null:
		keet.current_member_id = "gate-author"
	get_tree().root.get_node("/root/GameMode").set_mode("build")
	var map: Node3D = (load("res://scenes/maps/map_test.tscn") as PackedScene).instantiate()
	add_child(map)
	_cam = Camera3D.new()
	add_child(_cam)
	_cam.current = true
	for i in 6:
		await get_tree().process_frame
	await _run(map)
	get_tree().root.get_node("/root/GameMode").set_mode("play")
	if checks < 13:
		print("  **FAIL** only %d checks ran -- setup died before the assertions" % checks)
		fails += 1
	print("\nGATE edge_set: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
	get_tree().quit(0 if fails == 0 else 1)


func _box_planes(lo: Vector3, hi: Vector3) -> Array:
	return [-1, 0, 0, -lo.x, 1, 0, 0, hi.x, 0, -1, 0, -lo.y, 0, 1, 0, hi.y, 0, 0, -1, -lo.z, 0, 0, 1, hi.z]


func _wall(map: Node, lo: Vector3, hi: Vector3) -> int:
	return map._create_brush_journalled({"planes": _box_planes(lo, hi),
		"proj": [0, 0, 0, 0, 0, 0], "material_id": 7, "level_id": 0})


## Aim along -n at `at` (a point on a face, near the edge wanted) and click.
func _pick(map: Node, at: Vector3, n: Vector3, add: bool) -> void:
	_cam.global_position = at + n * 3.0
	_cam.look_at(at, Vector3.UP if absf(n.y) < 0.9 else Vector3.FORWARD)
	await get_tree().physics_frame
	map._pick_element(add, get_viewport().get_visible_rect().size * 0.5)


## The plane key of `brush` whose outward normal is `n`.
func _key(level: Node, brush: int, n: Vector3) -> String:
	var d: Dictionary = level.get_brush(brush)
	var pl: PackedFloat32Array = d["planes"]
	var keys: PackedStringArray = d["plane_keys"]
	for i in pl.size() / 4:
		if Vector3(pl[i * 4], pl[i * 4 + 1], pl[i * 4 + 2]).normalized().distance_to(n) < 1.0e-4:
			return keys[i]
	return ""


func _pair(level: Node, brush: int, n1: Vector3, n2: Vector3) -> Array:
	var p := [_key(level, brush, n1), _key(level, brush, n2)]
	p.sort()
	return p


func _run(map: Node3D) -> void:
	map._ensure_persistence()
	var persist: Node = map.get("_persist")
	var level: Node = map.get("level")
	var y0 := 60.0
	var wa := _wall(map, Vector3(200, y0, 0), Vector3(201, y0 + 2, 2))
	var wb := _wall(map, Vector3(203, y0, 0), Vector3(204, y0 + 2, 2))
	level.rebuild()
	for i in 3:
		await get_tree().physics_frame
	var ka := String(level.get_brush_key(wa))
	var kb := String(level.get_brush_key(wb))
	var has := map.get("_sel_edges") != null and map.has_method("selected_edges")
	_ok("setup: the map has _sel_edges and selected_edges()", has)
	if not has:
		return
	map.set_select_mode("edge")
	map.clear_selection()

	# ---- E1: the top edge of wa's +X face ----
	await _pick(map, Vector3(201, y0 + 1.9, 1), Vector3.RIGHT, false)
	var se: Array = map.get("_sel_edges")
	var e_top := {"b": ka, "p": _pair(level, wa, Vector3.RIGHT, Vector3.UP)}
	_ok("E1 a plain click: one edge, wa's +X / +Y planes", se.size() == 1 and se[0] == e_top, se)

	# ---- E2: wa's +X face, its front (+Z) edge ----
	await _pick(map, Vector3(201, y0 + 1, 1.9), Vector3.RIGHT, true)
	se = map.get("_sel_edges")
	var e_front := {"b": ka, "p": _pair(level, wa, Vector3.RIGHT, Vector3.BACK)}
	_ok("E2 Shift-click another edge of the same brush: two", se.size() == 2 and se.has(e_front), se)
	_ok("E2 ... and the brush stays selected", (map.get("_sel_brushes") as Array).has(wa), map.get("_sel_brushes"))

	# ---- E3: wb's -X face, its top edge ----
	await _pick(map, Vector3(203, y0 + 1.9, 1), Vector3.LEFT, true)
	se = map.get("_sel_edges")
	var e_b := {"b": kb, "p": _pair(level, wb, Vector3.LEFT, Vector3.UP)}
	_ok("E3 Shift-click an edge of another brush: three", se.size() == 3 and se.has(e_b), se)

	# ---- E4 ----
	await _pick(map, Vector3(201, y0 + 1.9, 1), Vector3.RIGHT, true)
	se = map.get("_sel_edges")
	_ok("E4 Shift-click the first edge again: removed", se.size() == 2 and not se.has(e_top), se)

	# ---- E5 ----
	var res: Array = map.selected_edges()
	var front: Dictionary = {}
	for r: Dictionary in res:
		if String(r["b"]) == ka:
			front = r
	var ends: Array = [front.get("a", Vector3.INF), front.get("e", Vector3.INF)] if not front.is_empty() else []
	var want := [Vector3(201, y0, 2), Vector3(201, y0 + 2, 2)]
	var ends_ok := ends.size() == 2 and ((ends[0] as Vector3).distance_to(want[0]) < 1.0e-3 and (ends[1] as Vector3).distance_to(want[1]) < 1.0e-3
		or (ends[0] as Vector3).distance_to(want[1]) < 1.0e-3 and (ends[1] as Vector3).distance_to(want[0]) < 1.0e-3)
	_ok("E5 the front edge resolves to (201, y0, 2) .. (201, y0+2, 2)", res.size() == 2 and ends_ok, ends)
	var ns: Array = front.get("normals", [])
	_ok("E5 ... with its two face normals (+X, +Z)", ns.size() == 2 and (ns.has(Vector3.RIGHT) or (ns[0] as Vector3).distance_to(Vector3.RIGHT) < 1.0e-4
		or (ns[1] as Vector3).distance_to(Vector3.RIGHT) < 1.0e-4), ns)

	# ---- E6 ----
	persist.refold()
	_ok("E6 a re-fold keeps the set (both still resolve)", (map.get("_sel_edges") as Array).size() == 2
		and (map.selected_edges() as Array).size() == 2, map.selected_edges().size())

	# ---- E7 ----
	var lines: PackedVector3Array = map.edge_set_lines() if map.has_method("edge_set_lines") else PackedVector3Array()
	_ok("E7 the highlight's lines: every edge in the set (2 x 2 points)", lines.size() == 4, lines.size())

	# ---- E8 ----
	persist.begin_gesture("Delete wb")
	persist.record("brush.remove", {"brush_key": kb})
	persist.end_gesture()
	persist.refold()
	_ok("E8 wb gone: its edge drops out of selected_edges()", (map.selected_edges() as Array).size() == 1,
		(map.selected_edges() as Array).size())
	persist.undo_last()
	persist.refold()

	# ---- E9 ----
	await _pick(map, Vector3(201, y0 + 1.9, 1), Vector3.RIGHT, false)
	_ok("E9 a plain click restarts the set", (map.get("_sel_edges") as Array).size() == 1, map.get("_sel_edges"))

	# ---- E10 ----
	map.set_select_mode("face")
	_ok("E10 face mode clears the edge set", (map.get("_sel_edges") as Array).is_empty(), map.get("_sel_edges"))