204 lines
8.4 KiB
GDScript
204 lines
8.4 KiB
GDScript
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
|