OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_param_prism.gd

99 lines · gd ref:
extends SceneTree
## GATE — THE PRISM CUTTER SHAPE (#120 L2 §13.1, param_prism.gd).
##   P1  the unit square, size (2, 1, 3): ONE piece, exactly the box 0..size (8 corners)
##   P2  taper 0.5: the top is the footprint halved about its centre; the bottom unchanged
##   P3  shear [0.5, 0]: the top moves half the size along x (a wedge / lean)
##   P4  ngon_poly(6): six points on the unit circle's box; its prism has 6 + 2 faces
##   P5  a concave footprint (an L) becomes its CONVEX HULL (cutters are convex)
##   P6  registered, and NOT in the create tool's Tab list (BoxCutter draws it)
##   P7  the same params give the same planes (deterministic)
##
## RUN: CI=true HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$GODOT" --headless --path . --script res://scenes/test/gate_param_prism.gd

const ParamShapes := preload("res://scenes/maps/shapes/param/param_shapes.gd")

var fails := 0
var checks := 0


func _p(c: bool, m: String, got: Variant = "") -> void:
	checks += 1
	if not c:
		fails += 1
	print("  %-62s %s  %s" % [m, "OK" if c else "**FAIL**", str(got)])


func _corners(t: Object, size: Vector3, params: Dictionary) -> Array:
	var pcs: Array = t.local_pieces(size, t.resolve(params))
	if pcs.size() != 1:
		return []
	var out: Array = []
	for v: Vector3 in FaceKit.corners(pcs[0]["planes"]):
		out.append(Vector3(snappedf(v.x, 0.001), snappedf(v.y, 0.001), snappedf(v.z, 0.001)))
	out.sort()
	return out


func _at_y(cs: Array, y: float) -> Array:
	return cs.filter(func(v: Vector3) -> bool: return absf(v.y - y) < 1.0e-3)


func _initialize() -> void:
	var t: Object = ParamShapes.get_type("prism")
	# ---- P6 ----
	_p(t != null, "P6 prism is a registered shape type")
	_p(not ParamShapes.ids().has("prism"), "P6 ... and not in the create tool's Tab list", ParamShapes.ids())
	if t == null:
		_end()
		return
	var sq := [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]
	var size := Vector3(2, 1, 3)
	# ---- P1 ----
	var c1 := _corners(t, size, {"poly": sq})
	var box: Array = []
	for x: float in [0.0, 2.0]:
		for y: float in [0.0, 1.0]:
			for z: float in [0.0, 3.0]:
				box.append(Vector3(x, y, z))
	box.sort()
	_p(c1 == box, "P1 the unit square: one piece, the box 0..size", c1)
	# ---- P2 ----
	var c2 := _corners(t, size, {"poly": sq, "taper": 0.5})
	var top2: Array = _at_y(c2, 1.0)
	var want2: Array = [Vector3(0.5, 1, 0.75), Vector3(0.5, 1, 2.25), Vector3(1.5, 1, 0.75), Vector3(1.5, 1, 2.25)]
	_p(top2 == want2, "P2 taper 0.5: the top is the footprint halved about its centre", top2)
	_p(_at_y(c2, 0.0).size() == 4 and (_at_y(c2, 0.0)[3] as Vector3) == Vector3(2, 0, 3), "P2 ... the bottom unchanged", _at_y(c2, 0.0))
	# ---- P3 ----
	var c3 := _corners(t, size, {"poly": sq, "shear_x": 0.5})
	var top3: Array = _at_y(c3, 1.0)
	_p(top3.size() == 4 and (top3[0] as Vector3).x == 1.0 and (top3[3] as Vector3).x == 3.0,
		"P3 shear 0.5 x: the top moves one (half of 2) along x", top3)
	# ---- P4 ----
	var hexp: Array = t.ngon_poly(6)
	var pc4: Array = t.local_pieces(size, t.resolve({"poly": hexp}))
	var faces := 0
	if pc4.size() == 1:
		var pl: PackedFloat32Array = pc4[0]["planes"]
		for i in pl.size() / 4:
			if FaceKit.face_polygon(pl, i).size() >= 3:
				faces += 1
	_p(hexp.size() == 6, "P4 ngon_poly(6): six points", hexp.size())
	_p(faces == 8, "P4 ... its prism has 6 sides + top + bottom", faces)
	# ---- P5 ----
	var ell := [[0.0, 0.0], [1.0, 0.0], [1.0, 0.4], [0.4, 0.4], [0.4, 1.0], [0.0, 1.0]]
	var c5 := _corners(t, Vector3(1, 1, 1), {"poly": ell})
	_p(c5.size() == 10 and not c5.has(Vector3(0.4, 0, 0.4)), "P5 an L footprint becomes its convex hull (no inner corner)", c5.size())
	# ---- P7 ----
	var a: Array = t.local_pieces(size, t.resolve({"poly": hexp, "taper": 0.7}))
	var b: Array = t.local_pieces(size, t.resolve({"poly": hexp, "taper": 0.7}))
	_p(a.size() == 1 and b.size() == 1 and a[0]["planes"] == b[0]["planes"], "P7 the same params give the same planes")
	_end()


func _end() -> void:
	if checks < 9:
		print("  **FAIL** only %d checks ran" % checks)
		fails += 1
	print("\nGATE param_prism: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
	quit(0 if fails == 0 else 1)