scopedvr-map-core/editor/bau_ops.gd
2026-11-01 19:31:33 +01:00

204 lines
8.4 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

extends RefCounted
class_name BauOps
## Dokument-Operationen des Bauen-Modus (pure Funktionen, headless testbar).
##
## Ein Gebäude = eine Gruppe (GroupOps, Feld "group" + group_names) aus `bau_*`-
## Objekten. Alles Weitere — Verschieben, Drehen, Kopieren, Löschen, Symmetrie,
## Speichern — läuft über die normalen Objekt-Funktionen. Dieser Teil kennt nur,
## was speziell am Bauen ist: Teil setzen/ersetzen, Gebäude-Gruppen, Migration
## der alten "gebaeude"-Sektion (Karte t_97ea59c5 → Umbau 25.10.).
static func teile_zahl(doc: Dictionary) -> int:
var n := 0
for o in (doc.get("objects", []) as Array):
if o is Dictionary and BauKatalog.ist_bau(str((o as Dictionary).get("id", ""))):
n += 1
return n
## Dreiecke aller Bauteile (grobe Poly-Übersicht für die Statuszeile).
static func dreiecke_gesamt(doc: Dictionary) -> int:
var n := 0
for o in (doc.get("objects", []) as Array):
if o is Dictionary:
n += BauKatalog.dreiecke(str((o as Dictionary).get("id", "")))
return n
## Neue Gebäude-Gruppe anlegen und benennen. Rückgabe: Gruppen-ID.
static func gruppe_neu(doc: Dictionary) -> int:
var gid := GroupOps.next_group_id(doc)
GroupState.set_group_name(doc, gid, "Gebäude %d" % (gebaeude_zahl(doc) + 1))
return gid
## Anzahl Gebäude-Gruppen (Gruppen, deren erstes Mitglied ein Bauteil ist).
static func gebaeude_zahl(doc: Dictionary) -> int:
return gebaeude_gruppen(doc).size()
## [{gid, name, indizes, dreiecke}] für alle Gebäude-Gruppen.
static func gebaeude_gruppen(doc: Dictionary) -> Array:
var out: Array = []
var objects: Array = doc.get("objects", [])
for gid in GroupOps.active_group_ids(doc):
var members := GroupOps.members_of(doc, int(gid))
if members.is_empty():
continue
var bau := false
var tris := 0
for i in members:
var oid := str((objects[i] as Dictionary).get("id", ""))
if BauKatalog.ist_bau(oid):
bau = true
tris += BauKatalog.dreiecke(oid)
if bau:
out.append({"gid": int(gid), "name": GroupState.display_name(doc, int(gid)),
"indizes": members, "dreiecke": tris})
return out
## Teil setzen oder ersetzen (snap = BuildSnap.berechne-Ergebnis, ok=true).
## Mutiert doc["objects"]; der Aufrufer macht snapshot/commit.
## gid: Zielgruppe (Gebäude); bei Symmetrie bekommt der Zwilling ein eigenes
## Spiegel-Gebäude (spiegel_gid, vom Aufrufer gemerkt: 1 Original-Gebäude ↔ 1 Spiegel).
## Rückgabe: {index, twin, ersetzt}.
static func setze_teil(doc: Dictionary, oid: String, snap: Dictionary, gid: int,
sym_mode: int, spiegel_gid := -1) -> Dictionary:
var objects: Array = doc["objects"]
var pos: Vector3 = snap["pos"]
var rot := float(snap["rot"])
var ersetze := int(snap.get("ersetze", -1))
if ersetze >= 0 and ersetze < objects.size():
var o: Dictionary = objects[ersetze]
o["id"] = oid
o["pos"] = [pos.x, pos.y, pos.z]
SvmapIO.rot_setzen(o, Vector3(0.0, rot, 0.0))
# Symmetrie: der Zwilling bekommt dasselbe Teil an seiner (gespiegelten) Stelle.
var key := SymmetryCore.sym_of(o)
if sym_mode != SymmetryCore.Mode.OFF and not key.is_empty():
var partner := SymmetryCore.find_partner_index(objects, key, ersetze)
if partner >= 0 and objects[partner] is Dictionary:
(objects[partner] as Dictionary)["id"] = oid
return {"index": ersetze, "twin": -1, "ersetzt": true}
var neu := {"id": oid, "pos": [pos.x, pos.y, pos.z], "scale": 1.0, "rot_y_deg": rot, "group": gid}
objects.append(neu)
var index := objects.size() - 1
var twin := -1
if sym_mode != SymmetryCore.Mode.OFF:
var res: Dictionary = ObjectOps.symmetry_place_twin(doc, index, sym_mode)
twin = int(res.get("twin", -1))
if twin >= 0 and spiegel_gid >= 0:
(objects[twin] as Dictionary)["group"] = spiegel_gid
return {"index": index, "twin": twin, "ersetzt": false}
## Eine ganze Gebäude-Gruppe als Indizes (für „Ganzes Gebäude wählen“).
static func gebaeude_von(doc: Dictionary, index: int) -> Array:
var objects: Array = doc.get("objects", [])
if index < 0 or index >= objects.size():
return []
var gid := GroupOps.group_id(objects[index])
if gid == GroupOps.UNGROUPED:
return [index]
return GroupOps.members_of(doc, gid)
# ----------------------------------------------------------- Demo / Test ----
## Kleines Haus über die echte Klick-Logik setzen (Fundament, Wände, Tür, Fenster,
## Giebel, Dach, Treppe) — für Demo und Tests. Jeder Schritt = BuildSnap-Treffer
## wie im Editor. Rückgabe: Gruppen-ID. history kann null sein.
static func haus_demo(doc: Dictionary, history: Variant, mitte: Vector3) -> int:
var gid := gruppe_neu(doc)
var terrain := func(_x: float, _z: float) -> float: return mitte.y
var before: Dictionary = history.snapshot(["objects", "group_names"]) if history != null else {}
# 2×2 Zellen (4×4 m): Zellmitten bei (±1, ±1) relativ zur Mitte.
var ox := floorf(mitte.x / 2.0) * 2.0
var oz := floorf(mitte.z / 2.0) * 2.0
var schritte := [
["bau_fundament", Vector3(ox - 1, mitte.y, oz - 1), 0.0],
["bau_fundament", Vector3(ox + 1, mitte.y, oz - 1), 0.0],
["bau_fundament", Vector3(ox - 1, mitte.y, oz + 1), 0.0],
["bau_fundament", Vector3(ox + 1, mitte.y, oz + 1), 0.0],
# Wände liegen GANZ in der Zelle, in der die Maus ist (Außenseite bündig mit
# der Kante) — deshalb zielen die Klicks 10 cm ins Hausinnere.
["bau_wand", Vector3(ox - 1, mitte.y, oz - 1.9), 0.0],
["bau_wand_fenster", Vector3(ox + 1, mitte.y, oz - 1.9), 0.0],
["bau_wand_tuer", Vector3(ox - 1, mitte.y, oz + 1.9), 0.0],
["bau_wand", Vector3(ox + 1, mitte.y, oz + 1.9), 0.0],
["bau_wand", Vector3(ox - 1.9, mitte.y, oz - 1), 90.0],
["bau_wand_scharte", Vector3(ox - 1.9, mitte.y, oz + 1), 90.0],
["bau_wand", Vector3(ox + 1.9, mitte.y, oz - 1), 90.0],
["bau_wand", Vector3(ox + 1.9, mitte.y, oz + 1), 90.0],
]
for st in schritte:
_demo_setzen(doc, str(st[0]), st[1] as Vector3, float(st[2]), gid, terrain)
# Ein großes Satteldach (4×4 m) auf die Wandoberkante.
var oben := mitte.y + BauKatalog.WAND_H
_demo_setzen(doc, "bau_dach_giebel_4", Vector3(ox, oben, oz), 0.0, gid, terrain)
if history != null:
history.commit("Haus gebaut (Demo)", before, history.snapshot(["objects", "group_names"]))
return gid
static func _demo_setzen(doc: Dictionary, oid: String, hit: Vector3, dreh: float, gid: int,
gelaende: Callable) -> void:
var teile := BuildSnap.sammle_teile(doc)
var snap := BuildSnap.berechne(oid, hit, dreh, teile, gelaende)
if bool(snap["ok"]):
setze_teil(doc, oid, snap, gid, SymmetryCore.Mode.OFF)
# --------------------------------------------------------------- Migration --
## Alte Sektion doc["gebaeude"] (Karte t_97ea59c5, 10 prozedurale Teile ohne
## Registry) → bau_*-Objekte in EINER Gruppe, dann Sektion entfernen. Beste
## Näherung: Teil-Ursprünge der alten Teile lagen an Wandanfang/Zellmitte, die
## neuen Bauteile an der Mitte der Grundfläche; Höhen (2 m → 3 m) ändern sich.
## Rückgabe: Anzahl übernommener Teile.
const _ALT := {
"fundament": "bau_fundament", "boden": "bau_boden", "wand": "bau_wand",
"tuer": "bau_wand_tuer", "fenster": "bau_wand_fenster",
"giebelwand": "bau_giebelwand", "giebelwand_rechts": "bau_giebelwand",
"dach_giebel": "bau_dach_giebel", "dach_pyramide": "bau_dach_pyramide",
"treppe": "bau_treppe",
}
static func migriere_gebaeude(doc: Dictionary) -> int:
if not doc.has("gebaeude"):
return 0
var alt: Variant = doc["gebaeude"]
doc.erase("gebaeude")
if not (alt is Array) or (alt as Array).is_empty():
return 0
if not doc.has("objects") or not (doc["objects"] is Array):
doc["objects"] = []
var objects: Array = doc["objects"]
var gid := gruppe_neu(doc)
var n := 0
for teil in alt:
if not (teil is Dictionary):
continue
var oid: String = _ALT.get(str((teil as Dictionary).get("id", "")), "")
if oid.is_empty():
continue
var p: Array = (teil as Dictionary).get("pos", [0.0, 0.0, 0.0])
var rot := float((teil as Dictionary).get("rot", 0.0))
var pos := Vector3(float(p[0]), float(p[1]), float(p[2]))
var local := Vector3.ZERO
match str((teil as Dictionary).get("id", "")):
"wand", "tuer", "fenster", "giebelwand", "giebelwand_rechts":
local = Vector3(1.0, 0.0, 0.0) # alt: Ursprung am Wandanfang
"treppe":
local = Vector3(2.0, 0.0, 0.0) # alt: 2 m lang ab Ursprung
"boden":
pos.y -= BauKatalog.DICKE_M # alt: Oberkante am Ursprung
pos += Basis(Vector3.UP, deg_to_rad(rot)) * local
objects.append({"id": oid, "pos": [pos.x, pos.y, pos.z], "scale": 1.0,
"rot_y_deg": fposmod(rot, 360.0), "group": gid})
n += 1
if n == 0:
GroupState.set_group_name(doc, gid, "")
return n