extends "res://scenes/maps/shapes/param/param_shape.gd"
## HULL — ANY BRUSH AS A PARAMETRIC SHAPE (#120 F2, docs/node-tree-and-stacks.md §3.1:
## "you should always be able to turn any brush into a parametric object").
##
## The identity generator: it emits the brush it was made from. Its planes are stored in
## the shape's UNIT box (0..1 on each axis of the shape's frame), so the shape's size
## stretches it like any other generator, and Make Real gives the brush back. Each plane
## keeps its own texture (face_tex, parallel to planes), so nothing the brush carried is
## lost. Both params are HIDDEN: data, not knobs (ParamShapes.rows skips them), and the
## create tool never offers this type (ParamShapes.ids).
##
## ⛔ VERSIONING as every generator: a change to the output is a new GV (ParamShape.GV,
## 1 here).
func id() -> String:
return "hull"
func label() -> String:
return "Hull"
func params() -> Array:
return [
{"id": "planes", "type": "planes", "default": [], "hidden": true,
"tip": "n.x n.y n.z d per plane, n·u <= d, in the shape's unit box"},
{"id": "face_tex", "type": "list", "default": [], "hidden": true,
"tip": "one texture intent per plane (DCFaceTex shape), or empty"},
]
## Unit-box planes -> the size box 0..size: u = x / size, so n·u <= d becomes
## (n / size)·x <= d, renormalised.
func local_pieces(size: Vector3, p: Dictionary) -> Array:
var src: Array = p.get("planes", [])
if src.size() < 16: # fewer than 4 planes encloses nothing
return []
var s := Vector3(maxf(size.x, 1.0e-6), maxf(size.y, 1.0e-6), maxf(size.z, 1.0e-6))
var out := PackedFloat32Array()
for i in range(0, src.size() - 3, 4):
var n := Vector3(float(src[i]) / s.x, float(src[i + 1]) / s.y, float(src[i + 2]) / s.z)
var l := n.length()
if l < 1.0e-12:
l = 1.0
out.append(n.x / l)
out.append(n.y / l)
out.append(n.z / l)
out.append(float(src[i + 3]) / l)
var piece := {"planes": out, "edges": PackedVector3Array()}
var ft: Array = p.get("face_tex", [])
if ft.size() == out.size() / 4:
piece["face_tex"] = ft.duplicate(true)
return [piece]