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")