OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_rotate.gd

200 lines · gd ref:
extends SceneTree
## GATE — the rotate tool (#41 §7 item 4) on a real map_flat, through the real keys.
##
## R starts it on the selection; ] steps 15° (Shift 1°); the brushes are shown as
## themselves (BrushProxies) and the outline turns with them; Enter commits one
## brush.rotate per brush about ONE pivot; a refold reproduces it; Escape cancels.
## The geometry / texture-lock maths is libdc's (tests/test_brush_rotate.cpp).
##
## Run: HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_rotate.gd

var ok := true
var checks := 0
var _frame := 0
var _map: Node
var _level: Node
var _persist: Node


func _p(c: bool, m: String, got: Variant = "") -> void:
	checks += 1
	print(("ok   " if c else "FAIL: ") + m + ("" if str(got) == "" else "  (" + str(got) + ")"))
	if not c:
		ok = false


func _process(_d: float) -> bool:
	_frame += 1
	if _frame == 1:
		_map = (load("res://scenes/maps/map_flat.tscn") as PackedScene).instantiate()
		root.add_child(_map)
		return false
	if _frame < 30:
		return false
	_run()
	if checks < 12:
		_p(false, "only %d checks ran" % checks)
	print("GATE rotate: %s (%d checks)" % ["PASS" if ok else "FAIL", checks])
	quit(0 if ok else 1)
	return true


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


func _key(code: int, shift := false) -> InputEventKey:
	var e := InputEventKey.new()
	e.physical_keycode = code
	e.keycode = code
	e.shift_pressed = shift
	e.pressed = true
	return e


func _make(lo: Vector3, hi: Vector3) -> int:
	var ft: Array = []
	for i in 6:
		ft.append({"tex": "kenney-prototype/dark_01", "mode": 1})
	var dot: String = _persist.record("brush.create", {"planes": _box(lo, hi), "proj": [], "material_id": 1,
		"level_id": 0, "sky": false, "face_tex": ft})
	_persist.refold()
	return _level.brush_by_key(dot)


func _bounds(ids: Array) -> AABB:
	var lo := Vector3.INF
	var hi := -Vector3.INF
	for b: int in ids:
		var d: Dictionary = _level.get_brush(b)
		lo = lo.min(d["aabb_min"] as Vector3)
		hi = hi.max(d["aabb_max"] as Vector3)
	return AABB(lo, hi - lo)


func _proxies() -> Array:
	var out: Array = []
	for c in _level.get_children():
		if String(c.name).begins_with("MoveProxy_") and not c.is_queued_for_deletion():
			out.append(c)
	return out


func _rotate_records() -> Array:
	var out: Array = []
	for r: Dictionary in _persist.records():
		if String(r.get("kind", "")) == "brush.rotate":
			out.append(r)
	return out


func _run() -> void:
	_level = _map.get_node("DCLevel")
	_persist = _map.get("_persist")
	var keet: Node = root.get_node_or_null("/root/KeetService")
	if keet != null:
		keet.current_member_id = "gate-author"
	var gs: Node = root.get_node_or_null("/root/GameState")
	if gs != null:
		gs.route("world:level:*", 1, "peer")
	root.get_node("/root/GameMode").set_mode("build")
	var b := _make(Vector3(0, 0, 0), Vector3(2, 1, 1))

	print("-- R on the selection: proxy up, brush hidden --")
	_map.select_brushes(PackedInt32Array([b]))
	_map._unhandled_key_input(_key(KEY_R))
	var stack: Node = _map.call("_ensure_ops")
	_p(stack.active is RotateOperator, "R starts the rotate tool", stack.active)
	_p(_proxies().size() == 1 and _level.is_brush_hidden(b), "the brush is shown as its proxy while turning")

	print("-- ] x6 = 90° about Y --")
	for i in 6:
		_map._unhandled_key_input(_key(KEY_BRACKETRIGHT))
	var px: Array = _proxies()
	var want := Basis(Vector3.UP, deg_to_rad(90.0))
	_p(px.size() == 1 and (px[0] as Node3D).transform.basis.is_equal_approx(want), "the proxy turned 90° about Y")
	var ol: Node3D = _map.get("_outline")
	_p(ol == null or ol.transform.basis.is_equal_approx(want), "the outline turned with it")

	print("-- Enter commits about the centre (1, 0.5, 0.5) --")
	var nrec := _rotate_records().size()
	stack.confirm()
	var bb := _bounds([b])
	_p(bb.position.is_equal_approx(Vector3(0.5, 0, -0.5)) and bb.size.is_equal_approx(Vector3(1, 1, 2)),
		"x / z extents swapped about the centre", bb)
	_p(not _level.is_brush_hidden(b) and _proxies().is_empty(), "brush back, proxy freed")
	var recs := _rotate_records()
	_p(recs.size() == nrec + 1 and is_equal_approx(float(recs[-1].get("angle", 0)), 90.0), "one brush.rotate record, 90°")
	_persist.refold()
	_p(_bounds([b]).is_equal_approx(bb), "a refold reproduces the rotation", _bounds([b]))

	print("-- Shift+] = 1°, Escape cancels --")
	_map.select_brushes(PackedInt32Array([b]))
	_map._unhandled_key_input(_key(KEY_R))
	_map._unhandled_key_input(_key(KEY_BRACKETRIGHT, true))
	_p(is_equal_approx(float(stack.active.get("_deg")), 1.0), "Shift+] steps 1°", stack.active.get("_deg"))
	stack.cancel()
	_p(_bounds([b]).is_equal_approx(bb) and not _level.is_brush_hidden(b), "cancel: unchanged and visible")

	print("-- a group turns about ONE shared pivot --")
	var c1 := _make(Vector3(5, 0, 0), Vector3(6, 1, 1))
	var c2 := _make(Vector3(8, 0, 0), Vector3(9, 1, 1))
	var before := _bounds([c1, c2])           # x 5..9, z 0..1, centre (7, 0.5, 0.5)
	_map.select_brushes(PackedInt32Array([c1, c2]))
	_map._unhandled_key_input(_key(KEY_R))
	for i in 6:
		_map._unhandled_key_input(_key(KEY_BRACKETRIGHT))
	stack.confirm()
	var after := _bounds([c1, c2])
	_p(after.position.is_equal_approx(Vector3(6.5, 0, -1.5)) and after.size.is_equal_approx(Vector3(1, 1, 4)),
		"the pair's union turned as one object (x 6.5..7.5, z -1.5..2.5)", "%s -> %s" % [before, after])
	var single := _bounds([c1])
	_p(single.size.is_equal_approx(Vector3(1, 1, 1)), "each brush kept its own size", single)

	print("-- an OBJECT turns too (models need it): live, cancel restores, confirm journals --")
	var od: String = _map.create_object("marker", Transform3D(Basis(), Vector3(3, 0, 3)))
	var on := _map.object_node(od) as Node3D
	_map.select_object(od)
	_map._unhandled_key_input(_key(KEY_R))
	_p(stack.active is RotateOperator and String(stack.active.get("_object")) == od,
		"R on a selected object rotates the object", stack.active.get("_object") if stack.active != null else "none")
	for i in 6:
		_map._unhandled_key_input(_key(KEY_BRACKETRIGHT))
	_p(on.transform.basis.is_equal_approx(want) and on.transform.origin.is_equal_approx(Vector3(3, 0, 3)),
		"it turns live, 90° about its own origin", on.transform)
	stack.cancel()
	_p(on.transform.basis.is_equal_approx(Basis()), "cancel puts it back", on.transform.basis)
	_map.select_object(od)
	_map._unhandled_key_input(_key(KEY_R))
	for i in 6:
		_map._unhandled_key_input(_key(KEY_BRACKETRIGHT))
	stack.confirm()
	_persist.refold()
	on = _map.object_node(od) as Node3D
	_p(on.transform.basis.is_equal_approx(want), "confirm: the turn survives a refold (object.transform)", on.transform.basis)
	_persist.undo_gesture(_persist.newest_live_gesture())
	_persist.refold()
	_p((_map.object_node(od) as Node3D).transform.basis.is_equal_approx(Basis()), "one undo takes the turn back")

	print("-- G moves an OBJECT live (user: 'doesn't move interactively'), once on confirm --")
	_map.select_object(od)
	var o0: Vector3 = (_map.object_node(od) as Node3D).transform.origin
	_map._unhandled_key_input(_key(KEY_G))
	_p(stack.active is MoveOperator and String(stack.active.get("_object")) == od, "G on a selected object moves the object")
	_map._unhandled_key_input(_key(KEY_BRACKETRIGHT))
	_map._unhandled_key_input(_key(KEY_BRACKETRIGHT))
	var step: float = float(stack.active.call("grid_step"))
	var live: Vector3 = (_map.object_node(od) as Node3D).transform.origin
	_p(live.is_equal_approx(o0 + Vector3(2.0 * step, 0, 0)), "  it moves LIVE while dragging (2 steps on X)", "%s -> %s" % [o0, live])
	stack.cancel()
	_p((_map.object_node(od) as Node3D).transform.origin.is_equal_approx(o0), "  cancel puts it back")
	_map.select_object(od)
	_map._unhandled_key_input(_key(KEY_G))
	_map._unhandled_key_input(_key(KEY_BRACKETRIGHT))
	_map._unhandled_key_input(_key(KEY_BRACKETRIGHT))
	stack.confirm()
	_persist.refold()
	var after_o: Vector3 = (_map.object_node(od) as Node3D).transform.origin
	_p(after_o.is_equal_approx(o0 + Vector3(2.0 * step, 0, 0)), "  confirm journals it ONCE (not start+2+2)", "%s -> %s" % [o0, after_o])
	root.get_node("/root/GameMode").set_mode("play")