extends SceneTree
## GATE — box / cylinder / cone / sphere exist as PARAMETRIC types and generate exactly
## what the baked tool made (docs/parametric-shapes-design.md §13, hyper-games #109:
## "make the create tool make parametric shapes").
##
## P1 ParamShapes lists box, stairs, spiral stairs, tunnel, cylinder, cone, sphere,
## in that order (the create tool's Tab order)
## P2 for each of box / cylinder / cone / sphere, at sizes 2x3x4 and 5x1x5 with its
## params, the local pieces' planes equal the baked BrushShape's for the bounds
## 0..size placed y-up, run +x
## P3 a shape.create of each folds into brush.create records whose planes are the
## pieces under the frame (a translated frame moves every plane by n·t)
##
## RUN: CI=true "$GODOT" --headless --path . --script res://scenes/test/gate_param_builtins.gd
const ParamShapes := preload("res://scenes/maps/shapes/param/param_shapes.gd")
const ShapeFold := preload("res://scenes/maps/shapes/param/shape_fold.gd")
const ParamBasic := preload("res://scenes/maps/shapes/param/param_basic.gd")
var fails := 0
var checks := 0
func _p(c: bool, m: String, got: Variant = "") -> void:
checks += 1
if not c:
fails += 1
print(" %-66s %s %s" % [m, "OK" if c else "**FAIL**", str(got)])
func _init() -> void:
var want := ["box", "stairs", "spiral_stairs", "tunnel", "cylinder", "cone", "sphere"]
var ids: Array = ParamShapes.ids()
_p(ids == want, "P1 the types, in Tab order", ids)
var baked := {"box": BoxShape.new(), "cylinder": CylinderShape.new(), "cone": ConeShape.new(),
"sphere": SphereShape.new()}
var given := {"box": {}, "cylinder": {"segments": 7}, "cone": {"segments": 9},
"sphere": {"segments": 10, "rings": 5}}
for tid: String in baked:
var t: Object = ParamShapes.get_type(tid)
if t == null:
_p(false, "P2 %s exists" % tid)
continue
for size: Vector3 in [Vector3(2, 3, 4), Vector3(5, 1, 5)]:
var p: Dictionary = t.resolve(given[tid])
var mine: Array = t.local_pieces(size, p)
var theirs: Array = (baked[tid] as BrushShape).pieces(Vector3.ZERO, size, ParamBasic.local_ctx(p))
var same: bool = mine.size() == theirs.size() and not mine.is_empty()
for i in mini(mine.size(), theirs.size()):
same = same and (mine[i]["planes"] as PackedFloat32Array) == (theirs[i]["planes"] as PackedFloat32Array)
_p(same, "P2 %s %s = the baked shape's pieces" % [tid, size], "%d vs %d" % [mine.size(), theirs.size()])
# P3: through the fold, frame translated by t
var tr := Vector3(10, 2, -3)
var rec := {"author": "a", "seq": 5, "kind": "shape.create", "type": tid, "gv": 1,
"params": given[tid], "frame": Transform3D(Basis.IDENTITY, tr), "size": Vector3(2, 3, 4),
"material_id": 0, "level_id": 0}
var out: Array = ShapeFold.expand([rec])
var local: Array = t.local_pieces(Vector3(2, 3, 4), t.resolve(given[tid]))
var moved := out.size() == local.size() and not out.is_empty()
for i in mini(out.size(), local.size()):
var a: PackedFloat32Array = local[i]["planes"]
var b: PackedFloat32Array = out[i]["planes"]
for k in range(0, a.size(), 4):
var n := Vector3(a[k], a[k + 1], a[k + 2])
moved = moved and absf(b[k + 3] - (a[k + 3] + n.dot(tr))) < 1.0e-4
_p(moved, "P3 %s folds into its pieces under the frame" % tid, "%d records" % out.size())
# P4: stairs auto steps are configurable (§13.5a). A record WITHOUT auto_riser keeps
# 0.2 m risers (gv 1 unchanged); auto_riser 0.5 gives 0.5 m risers.
var st: Object = ParamShapes.get_type("stairs")
var sz := Vector3(4, 2, 1)
_p((st.local_pieces(sz, st.resolve({})) as Array).size() == 10, "P4 a record without auto_riser: 2 m / 0.2 = 10 steps",
(st.local_pieces(sz, st.resolve({})) as Array).size())
_p((st.local_pieces(sz, st.resolve({"auto_riser": 0.5})) as Array).size() == 4, "P4 auto_riser 0.5: 2 m / 0.5 = 4 steps",
(st.local_pieces(sz, st.resolve({"auto_riser": 0.5})) as Array).size())
print("\nGATE param_builtins: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
quit(0 if fails == 0 else 1)