extends Node
## GATE — THE CHAMFER MODIFIER (#120 F2d; DCLevel BrushChamfer, after the cuts).
## H1 a box shape + chamfer (0.1, 1 segment): its piece has 6 + 12 planes
## H2 mirrored first: both halves chamfered, but NOT at the mirror seam — the top face
## runs right up to it on both sides (no groove)
## H3 editing the width moves the chamfer planes (the faces are re-keyed)
## H4 off -> plain box; on -> chamfered; Ctrl+Z of the add -> plain box
## H5 after a cut: the cut pieces are chamfered too (more planes than the cut alone)
##
## RUN AS A SCENE: CI=true "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_mod_chamfer.tscn
var fails := 0
var checks := 0
var map: Node3D
var persist: Node
var level: Node
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")
map = (load("res://scenes/maps/map_dev_test.tscn") as PackedScene).instantiate()
add_child(map)
for i in 6:
await get_tree().process_frame
await _run()
get_tree().root.get_node("/root/GameMode").set_mode("play")
if checks < 9:
print(" **FAIL** only %d checks ran -- setup died before the assertions" % checks)
fails += 1
print("\nGATE mod_chamfer: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
get_tree().quit(0 if fails == 0 else 1)
func _box(at: Vector3, size: Vector3) -> String:
var d: String = persist.record("shape.create", {"type": "box", "gv": 1, "params": {},
"frame": SceneObjects.xform_to(Transform3D(Basis.IDENTITY, at)),
"size": [size.x, size.y, size.z], "material_id": 2, "level_id": 0})
persist.refold()
return d
func _solids(dot: String) -> Array:
var out: Array = []
for b: int in (level.get_group_brush_ids(dot) as PackedInt32Array):
var d: Dictionary = level.get_brush(b)
if not bool(d.get("clearance", false)) and not bool(d.get("cutter", false)):
out.append(b)
return out
func _live_planes(b: int) -> int:
var n := 0
var pl: PackedFloat32Array = level.get_brush(b)["planes"]
for i in pl.size() / 4:
if FaceKit.face_polygon(pl, i).size() >= 3:
n += 1
return n
func _corner(b: int, at: Vector3) -> bool:
return Array(FaceKit.corners(level.get_brush(b)["planes"])).any(func(v: Vector3) -> bool: return v.distance_to(at) < 1.0e-3)
func _run() -> void:
persist = map.get("_persist")
level = map.get("level")
persist.refold()
# ---- H1 ----
var s := _box(Vector3(40, 0, 40), Vector3(1, 1, 1))
var m: String = map.add_modifier("chamfer", s)
var p1: int = _solids(s)[0]
_ok("H1 a chamfered box: 6 + 12 faces", _live_planes(p1) == 18, _live_planes(p1))
_ok("H1 ... its corner cut back: (40.1, 0, 40.1) is a corner", _corner(p1, Vector3(40.1, 0, 40.1)))
# ---- H3 ----
map.set_modifier(m, {"width": 0.2})
_ok("H3 width 0.2 moves the chamfer: (40.2, 0, 40.2)", _corner(_solids(s)[0], Vector3(40.2, 0, 40.2))
and not _corner(_solids(s)[0], Vector3(40.1, 0, 40.1)))
# ---- H4 ----
map.set_modifier(m, {}, false)
_ok("H4 off: the plain box", _live_planes(_solids(s)[0]) == 6)
map.set_modifier(m, {}, true)
_ok("H4 on: chamfered again", _live_planes(_solids(s)[0]) == 18)
# ---- H2 mirror + chamfer ----
var t := _box(Vector3(60, 0, 60), Vector3(1, 1, 1))
map.add_modifier("mirror", t)
map.add_modifier("chamfer", t)
var halves: Array = _solids(t)
_ok("H2 two halves", halves.size() == 2)
var seam_ok := true
for b: int in halves:
seam_ok = seam_ok and _corner(b, Vector3(60, 1, 60.1)) and _corner(b, Vector3(60, 1, 60.9))
_ok("H2 no groove: the top face reaches the mirror seam on both halves", seam_ok)
_ok("H2 the outer edges are chamfered: (61, 1, 60) gone", not halves.any(func(b: int) -> bool: return _corner(b, Vector3(61, 1, 60))))
# ---- H5 a cut, then the chamfer ----
var u := _box(Vector3(80, 0, 80), Vector3(2, 1, 2))
var cutter := _box(Vector3(80.5, 0.5, 80.5), Vector3(1, 1, 1))
persist.begin_gesture("Cut")
persist.record("op.create", {"op": "subtract", "cutter": cutter, "targets": [u]})
persist.end_gesture()
persist.refold()
var planes_cut := 0
for b: int in _solids(u):
planes_cut += _live_planes(b)
map.add_modifier("chamfer", u)
var planes_both := 0
for b: int in _solids(u):
planes_both += _live_planes(b)
_ok("H5 the cut pieces are chamfered too", planes_both > planes_cut, [planes_cut, planes_both])
# ---- H4 undo ----
persist.undo_last()
persist.refold()
var planes_undone := 0
for b: int in _solids(u):
planes_undone += _live_planes(b)
_ok("H4 Ctrl+Z of the add: back to the cut alone", planes_undone == planes_cut, [planes_undone, planes_cut])