OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / maps / shapes / param / param_prism.gd

85 lines · gd ref:
extends "res://scenes/maps/shapes/param/param_shape.gd"
## PRISM — THE BOXCUTTER'S CUTTER (#120 L2, docs/node-tree-and-stacks.md §13.1).
##
## A footprint polygon on the bottom of the shape's box, extruded up its height: ONE convex
## piece, the hull of the bottom and top sections (FaceKit.hull_sections, the same hull rule
## as cables and merges). The footprint is stored in the box's UNIT xz (0..1), so the
## shape's size stretches it, as hull's planes are.
##
##   poly      [[u, v], …]  the footprint (hidden: data drawn by the tool, not a knob). A
##                          concave one is used as its CONVEX HULL: a cutter is convex.
##   taper     the top section's scale about the footprint's centre (1 = straight sides,
##             0.5 = a frustum; 0.05 is the floor, a near-apex)
##   shear_x   the top section's offset, as a fraction of size.x / size.z (a wedge, a lean)
##   shear_z
##
## Taper and shear are PARAMS, not modifiers: a tapered piece is not a rigid transform of
## the untapered one, and ModFold applies only those (§13.1). A hull of two convex
## sections is convex, so the result always is.
##
## Not in the create tool's Tab list (ParamShapes.ids): BoxCutter draws it.
## ⛔ VERSIONING as every generator: a change to the output is a new GV (1 here).


func id() -> String:
	return "prism"


func label() -> String:
	return "Prism"


func params() -> Array:
	return [
		{"id": "poly", "type": "list", "default": [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]],
			"hidden": true, "tip": "the footprint, [u, v] in the box's unit xz"},
		{"id": "taper", "label": "Taper", "type": "float", "default": 1.0, "min": 0.05, "max": 4.0,
			"step": 0.05, "tip": "Top size against the footprint (1 = straight sides)"},
		{"id": "shear_x", "label": "Shear X", "type": "float", "default": 0.0, "min": -4.0, "max": 4.0,
			"step": 0.05, "tip": "Top offset along x, a fraction of the width (a wedge)"},
		{"id": "shear_z", "label": "Shear Z", "type": "float", "default": 0.0, "min": -4.0, "max": 4.0,
			"step": 0.05, "tip": "Top offset along z, a fraction of the depth (a wedge)"},
	]


func local_pieces(size: Vector3, p: Dictionary) -> Array:
	var pts := PackedVector2Array()
	for q: Variant in p.get("poly", []):
		if q is Array and (q as Array).size() >= 2:
			pts.append(Vector2(float(q[0]), float(q[1])))
	if pts.size() < 3:
		return []
	var hull := Geometry2D.convex_hull(pts)        # closed: the last point repeats the first
	if hull.size() < 4:
		return []
	hull.resize(hull.size() - 1)
	var lo := hull[0]
	var hi := hull[0]
	for v: Vector2 in hull:
		lo = lo.min(v)
		hi = hi.max(v)
	var c := (lo + hi) * 0.5
	var taper := float(p.get("taper", 1.0))
	var sh := Vector2(float(p.get("shear_x", 0.0)), float(p.get("shear_z", 0.0)))
	var bottom := PackedVector3Array()
	var top := PackedVector3Array()
	for v: Vector2 in hull:
		bottom.append(Vector3(v.x * size.x, 0.0, v.y * size.z))
		var t: Vector2 = c + (v - c) * taper + sh
		top.append(Vector3(t.x * size.x, size.y, t.y * size.z))
	var planes: PackedFloat32Array = FaceKit.hull_sections(bottom, top)
	if planes.is_empty():
		return []
	return [{"planes": planes, "edges": PackedVector3Array()}]


## A regular n-gon inscribed in the unit box (centre 0.5, 0.5, radius 0.5), the first point
## at -z: the footprint of BoxCutter's ngon, and of its circle at n = segments.
static func ngon_poly(sides: int) -> Array:
	var n := maxi(3, sides)
	var out: Array = []
	for k in n:
		var a := TAU * float(k) / float(n) - PI * 0.5
		out.append([0.5 + 0.5 * cos(a), 0.5 + 0.5 * sin(a)])
	return out