OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / maps / operators / cable_bundle_operator.gd

200 lines · gd ref:
extends LevelOperator
## A BUNDLE BETWEEN TWO FACE SETS (#120 L3, docs/node-tree-and-stacks.md §14.6):
## Cablerator's Create a Cable Mass on the face set — exactly two islands. The preview draws
## the cables; Enter writes ONE gesture (N curves + cables and a "Bundle" collection holding
## them, map.cable_bundle). Adjust Last re-runs it with the same seed.
## Keys (Cablerator's, tool-local): Z count, D tension, N tension jitter, S radius — each
## focuses its value, then [ ] steps it (Tab cycles, as everywhere); R random points / face
## centres; Q a new seed.

const BundleCablesScript := preload("res://scenes/maps/curves/bundle_cables.gd")

var count := 8
var tension := 1.0
var jitter := 0.3
var radius := 0.03
var slack := 0.0
var random := true
var rng_seed := 1
var _faces: Array = []                # _selected_face_polys() at activation (Adjust Last: stored)
var _frozen := false


func op_name() -> String:
	return "cable bundle"


## The mouse drags the focused value (§17.7 Q2): this tool works on the selection.
func drags_value() -> bool:
	return true


func gesture_label() -> String:
	return "Cable bundle"


func preview_color() -> Color:
	return Color(1.0, 0.85, 0.3, 0.9)     # the curve view's amber


func setup_modal(m: ModalControls) -> void:
	m.confirm_label = "add the bundle"
	m.table([["R", "ends", func() -> String: return "random points" if random else "face centres"],
		["Q", "new seed", func() -> String: return "%d" % rng_seed]])
	m.bind("count", "cables (Z)", func() -> float: return float(count),
		func(v: float) -> void:
			count = clampi(roundi(v), 1, 64)
			update_preview(),
		1.0, "")
	m.bind("tension", "tension (D)", func() -> float: return tension,
		func(v: float) -> void:
			tension = clampf(v, 0.0, 5.0)
			update_preview(),
		0.1, "", 0.02)
	m.bind("jitter", "tension jitter (N)", func() -> float: return jitter,
		func(v: float) -> void:
			jitter = clampf(v, 0.0, 1.0)
			update_preview(),
		0.05, "", 0.01)
	m.bind("radius", "radius (S)", func() -> float: return radius,
		func(v: float) -> void:
			radius = clampf(v, 0.005, 2.0)
			update_preview(),
		0.01, " m", 0.002)
	m.bind("slack", "slack", func() -> float: return slack,
		func(v: float) -> void:
			slack = maxf(0.0, v)
			update_preview(),
		0.1, " m", 0.02)


func status() -> String:
	return modal().status_text()


func _opts() -> Dictionary:
	return {"count": count, "random": random, "seed": rng_seed, "tension": tension, "jitter": jitter, "slack": slack}


func on_activate() -> bool:
	if not _frozen:
		_faces = map._selected_face_polys() if map != null and map.has_method("_selected_face_polys") else []
	var n: int = BundleCablesScript.islands(_faces).size()
	if n != 2:
		print("[bundle] select exactly 2 face islands (face mode, Shift-click), found %d" % n)
		return false
	update_preview()
	return true


func on_deactivate() -> void:
	clear_preview()


func handle_key(event: InputEvent) -> bool:
	if not (event is InputEventKey) or not (event as InputEventKey).pressed:
		return false
	match KeyRules.key_of(event as InputEventKey):
		KEY_Z:
			return modal().focus("count")
		KEY_D:
			return modal().focus("tension")
		KEY_N:
			return modal().focus("jitter")
		KEY_S:
			return modal().focus("radius")
		KEY_R:
			random = not random
			update_preview()
			return true
		KEY_Q:
			rng_seed += 1
			update_preview()
			return true
	return modal().handle_key(event)


func update_preview() -> void:
	clear_preview()
	var lines := PackedVector3Array()
	for pts: Array in (BundleCablesScript.bundle(_faces, _opts())["cables"] as Array):
		var fs := CurveMath.sample(pts, false, 48)
		for i in range(1, fs.size()):
			lines.append(fs[i - 1]["p"])
			lines.append(fs[i]["p"])
	draw_lines(lines)


func confirm() -> bool:
	var dots: Array = map.cable_bundle(_faces, _opts(), {"radius": radius})
	if dots.is_empty():
		return false
	ModalControls.summary("Added a bundle of %d cable%s" % [dots.size(), "" if dots.size() == 1 else "s"])
	return true


func tool_settings() -> Array:
	return [
		{"id": "count", "label": "Cables", "type": "int", "value": count, "min": 1, "max": 64, "step": 1},
		{"id": "tension", "label": "Tension", "type": "float", "value": tension, "min": 0.0, "max": 5.0, "step": 0.1,
			"tip": "How far each end leaves its face along the normal, x chord / 3"},
		{"id": "jitter", "label": "Tension jitter", "type": "float", "value": jitter, "min": 0.0, "max": 1.0, "step": 0.05},
		{"id": "radius", "label": "Radius", "type": "float", "value": radius, "min": 0.005, "max": 2.0, "step": 0.01},
		{"id": "slack", "label": "Slack", "type": "float", "value": slack, "min": 0.0, "max": 20.0, "step": 0.1,
			"tip": "Extra length: each cable hangs"},
		{"id": "random", "label": "Ends", "type": "enum", "value": 0 if random else 1,
			"choices": ["Random points", "Face centres"], "tip": "R"},
		{"id": "seed", "label": "Seed", "type": "int", "value": rng_seed, "min": 0, "max": 1000000, "step": 1, "tip": "Q: a new one"},
	]


func set_tool_setting(sid: String, value: Variant) -> bool:
	match sid:
		"count":
			count = clampi(int(value), 1, 64)
		"tension":
			tension = clampf(float(value), 0.0, 5.0)
		"jitter":
			jitter = clampf(float(value), 0.0, 1.0)
		"radius":
			radius = clampf(float(value), 0.005, 2.0)
		"slack":
			slack = maxf(0.0, float(value))
		"random":
			random = int(value) == 0
		"seed":
			rng_seed = maxi(0, int(value))
		_:
			return false
	if not _frozen:
		update_preview()
	return true


func adjust_ids() -> Array:
	return ["count", "tension", "jitter", "radius", "slack", "random", "seed"]


func adjust_snapshot() -> Dictionary:
	var s := _opts()
	s["faces"] = _faces.duplicate(true)
	s["radius"] = radius
	return s


func adjust_restore(snap: Dictionary) -> void:
	_faces = (snap.get("faces", []) as Array).duplicate(true)
	count = int(snap.get("count", count))
	random = bool(snap.get("random", random))
	rng_seed = int(snap.get("seed", rng_seed))
	tension = float(snap.get("tension", tension))
	jitter = float(snap.get("jitter", jitter))
	slack = float(snap.get("slack", slack))
	radius = float(snap.get("radius", radius))
	_frozen = true


func adjust_end() -> void:
	_frozen = false
	clear_preview()