OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_proportional_vertex.gd

176 lines · gd ref:
extends Node
## GATE — PROPORTIONAL EDITING, ELEMENT LEVEL (#109; user 2026-10-13: "also element
## vertices"). G on a vertex with O on: the corners near it follow by the falloff, each
## one re-hulled (brush.reshape), on its own brush and — unless Alt+O (connected only) —
## on the brushes beside it, so shared seams move together.
##   2 m cube A at the origin, G on its corner (1,1,1), straight up one grid step d;
##   linear, radius 3. Cube B beside it: its corner (2.5,1,1) is 1.5 m from the vertex.
##   V1  the vertex moves by d
##   V2  A's corner (-1,1,1), 2 m away, follows by d/3
##   V3  A's far corner (-1,-1,-1), beyond the radius, stays
##   V4  B's corner (2.5,1,1) follows by d/2
##   V5  one undo puts every corner back
##   V6  connected only (Alt+O): B stays, A's corner still follows
##   V7  off: only the vertex moves
##
## RUN AS A SCENE: "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_proportional_vertex.tscn

var fails := 0
var checks := 0
const CUBE := [1, 0, 0, 1, -1, 0, 0, 1, 0, 1, 0, 1, 0, -1, 0, 1, 0, 0, 1, 1, 0, 0, -1, 1]


func _ok(what: String, cond: bool, note: Variant = "") -> void:
	checks += 1
	if not cond:
		fails += 1
	print("  %-64s %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)
	for i in 8:
		await get_tree().process_frame
	await _run(map)
	get_tree().root.get_node("/root/GameMode").set_mode("play")
	if checks < 16:
		print("  **FAIL** only %d checks ran -- setup died before the assertions" % checks)
		fails += 1
	print("\nGATE proportional_vertex: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
	get_tree().quit(0 if fails == 0 else 1)


func _key(code: Key) -> InputEventKey:
	var e := InputEventKey.new()
	e.keycode = code
	e.physical_keycode = code
	e.pressed = true
	return e


func _cube(map: Node, at: Vector3) -> String:
	var persist: Node = map.get("_persist")
	var pl: Array = CUBE.duplicate()
	for f in 6:
		pl[f * 4 + 3] = float(pl[f * 4 + 3]) + Vector3(pl[f * 4], pl[f * 4 + 1], pl[f * 4 + 2]).dot(at)
	var dot: String = persist.record("brush.create", {"planes": pl, "proj": [], "material_id": 2,
		"level_id": 0, "sky": false, "group": ""})
	persist.refold()
	return dot


func _verts(level: Node, key: String) -> PackedVector3Array:
	var out := PackedVector3Array()
	for p: Vector3 in level.get_brush_edges_of(level.brush_by_key(key)):
		var seen := false
		for q: Vector3 in out:
			if q.distance_to(p) < 1e-3:
				seen = true
				break
		if not seen:
			out.append(p)
	return out


func _has(vs: PackedVector3Array, p: Vector3) -> bool:
	for q: Vector3 in vs:
		if q.distance_to(p) < 0.01:
			return true
	return false


func _select_vertex(map: Node, key: String, point: Vector3) -> void:
	var level: Node = map.get("level")
	var b: int = level.brush_by_key(key)
	map.select_brushes(PackedInt32Array([b]))
	var idx: PackedInt32Array = map.call("_planes_through", b, point, 1e-3)
	var keys: PackedStringArray = level.get_brush(b).get("plane_keys", PackedStringArray())
	var sel := PackedStringArray()
	for i in idx:
		sel.append(keys[i])
	map.set("_sel_mode", "vertex")
	map.set("_sel_kind", "vertex")
	map.set("_sel_keys", sel)
	map.set("_sel_point", point)


## G on the selected vertex, straight up `steps` grid steps, Enter. Returns d.
func _grab_up(map: Node, steps: int) -> Vector3:
	var stack: Node = map.call("_ensure_ops")
	map._unhandled_key_input(_key(KEY_G))
	var g: Object = stack.get("active")
	if not (g is ElementOperator):
		return Vector3.ZERO
	g.set_tool_setting("axis", ElementOperator.AXIS_ROWS.find("y"))
	g.set_tool_setting("distance", steps)
	stack.call("confirm")
	return Vector3(0, float(map.grid_step()) * float(steps), 0)


func _run(map: Node3D) -> void:
	var persist: Node = map.get("_persist")
	var level: Node = map.get("level")
	var a := _cube(map, Vector3.ZERO)
	var b := _cube(map, Vector3(3.5, 0, 0))
	map.set("proportional", true)
	map.set("prop_falloff", "linear")
	map.set("prop_radius", 3.0)
	map.set("prop_connected", false)

	print("-- V1-V5: G on a vertex, proportional on --")
	_select_vertex(map, a, Vector3(1, 1, 1))
	var d := _grab_up(map, 1)
	_ok("(setup) G moved the vertex up one grid step", d.y > 0.0, d)
	var va := _verts(level, a)
	var vb := _verts(level, b)
	_ok("V1 the vertex moved by d", _has(va, Vector3(1, 1, 1) + d), va)
	_ok("V2 A's corner 2 m away followed by d/3", _has(va, Vector3(-1, 1, 1) + d / 3.0), va)
	_ok("V3 A's far corner stayed", _has(va, Vector3(-1, -1, -1)))
	_ok("V4 B's corner 1.5 m away followed by d/2", _has(vb, Vector3(2.5, 1, 1) + d * 0.5), vb)
	_ok("V4 B's far corner stayed", _has(vb, Vector3(4.5, -1, -1)))
	persist.undo_gesture(persist.newest_live_gesture())
	persist.refold()
	va = _verts(level, a)
	vb = _verts(level, b)
	_ok("V5 one undo: the vertex is back", _has(va, Vector3(1, 1, 1)), va)
	_ok("V5 ... and A's corner", _has(va, Vector3(-1, 1, 1)))
	_ok("V5 ... and B's corner", _has(vb, Vector3(2.5, 1, 1)), vb)

	print("-- V6: connected only --")
	map.set("prop_connected", true)
	_select_vertex(map, a, Vector3(1, 1, 1))
	d = _grab_up(map, 1)
	va = _verts(level, a)
	vb = _verts(level, b)
	_ok("V6 A's corner still follows", _has(va, Vector3(-1, 1, 1) + d / 3.0), va)
	_ok("V6 B stays (another brush)", _has(vb, Vector3(2.5, 1, 1)), vb)
	persist.undo_gesture(persist.newest_live_gesture())
	persist.refold()

	print("-- V8: the [ ] nudge too --")
	map.set("prop_connected", false)
	_select_vertex(map, a, Vector3(1, 1, 1))
	map._unhandled_key_input(_key(KEY_BRACKETRIGHT))
	vb = _verts(level, b)
	_ok("V8 ] on the vertex: B's corner 1.5 m away followed", not _has(vb, Vector3(2.5, 1, 1)), vb)
	persist.undo_gesture(persist.newest_live_gesture())
	persist.refold()
	_ok("V8 and one undo puts it back", _has(_verts(level, b), Vector3(2.5, 1, 1)))
	_ok("V8 ... the vertex too (one gesture)", _has(_verts(level, a), Vector3(1, 1, 1)), _verts(level, a))

	print("-- V7: off --")
	map.set("proportional", false)
	map.set("prop_connected", false)
	_select_vertex(map, a, Vector3(1, 1, 1))
	d = _grab_up(map, 1)
	va = _verts(level, a)
	_ok("V7 off: the vertex moved", _has(va, Vector3(1, 1, 1) + d), va)
	_ok("V7 and A's other corner stayed", _has(va, Vector3(-1, 1, 1)), va)