update
This commit is contained in:
@@ -1,40 +1,38 @@
|
||||
class_name HumanDB
|
||||
## Die Kleinstadt auf der Erde. Die Gebäude im Ring um den Park stehen fest;
|
||||
## Wohnhäuser und ihre Bewohner kommen dazu, solange Menschen Hilfe brauchen.
|
||||
## Die Erde: ein Stadtbezirk nach japanischem Vorbild. Weite Arterien in einem
|
||||
## groben Raster spannen Blöcke auf. Am Blockrand stehen die Häuser direkt an
|
||||
## der Straße, ohne Vorgarten. Ins Blockinnere führen schmale Sackgassen --
|
||||
## roji --, an denen die hinteren Parzellen hängen. So entsteht die typische
|
||||
## Mischung aus klarer Ordnung außen und Gewinkel innen.
|
||||
##
|
||||
## Anders als das Traumdorf wächst hier nichts: alle Menschen sind von Anfang
|
||||
## an da und leben ihren Tag.
|
||||
|
||||
const HAEUSER_JE_RING := 8
|
||||
const RING_ERSTER := 62.0
|
||||
const RING_ABSTAND := 16.0
|
||||
const EINWOHNER := 16
|
||||
const ACHSEN := [-72.0, -24.0, 24.0, 72.0] # Arterien in x wie in z
|
||||
const ARTERIE := 7.0 # Breite einer Arterie
|
||||
const ROJI := 3.2 # Breite einer Gasse
|
||||
const RAND_TIEFE := 11.0 # Tiefe der Parzellen an der Straße
|
||||
const PARZELLE_MIN := 8.0 # schmalste Parzelle
|
||||
const PARZELLE_MAX := 12.0
|
||||
|
||||
## Gebäude liegen im Ring um den Park. Alle Türen zeigen zur Mitte.
|
||||
static var FEST: Dictionary = {
|
||||
"uni": {
|
||||
"name": "Hochschule", "pos": [-28.0, -27.0], "size": [14.0, 10.0],
|
||||
"dir": "back", "color": Color(0.72, 0.70, 0.66),
|
||||
},
|
||||
"cafe": {
|
||||
"name": "Café Nadel", "pos": [0.0, -27.0], "size": [12.0, 10.0],
|
||||
"dir": "back", "color": Color(0.82, 0.62, 0.45),
|
||||
},
|
||||
"buero": {
|
||||
"name": "Büro Ostwind", "pos": [28.0, -27.0], "size": [14.0, 10.0],
|
||||
"dir": "back", "color": Color(0.62, 0.66, 0.72),
|
||||
},
|
||||
"baeckerei": {
|
||||
"name": "Bäckerei Keller", "pos": [-31.0, 0.0], "size": [10.0, 12.0],
|
||||
"dir": "right", "color": Color(0.85, 0.72, 0.50),
|
||||
},
|
||||
"werkstatt": {
|
||||
"name": "Werkstatt Loos", "pos": [31.0, 0.0], "size": [10.0, 12.0],
|
||||
"dir": "left", "color": Color(0.70, 0.58, 0.55),
|
||||
},
|
||||
"park": {
|
||||
"name": "Stadtpark", "pos": [0.0, 4.0], "size": [0.0, 0.0],
|
||||
"dir": "none", "color": Color(0.45, 0.65, 0.38),
|
||||
},
|
||||
}
|
||||
## Alle Parzellen des Bezirks: {pos, size, dir, strasse}
|
||||
static var PARZELLEN: Array = []
|
||||
## Alle Straßenbänder: {a, b, breite}
|
||||
static var STRASSEN: Array = []
|
||||
static var NETZ: Netz = Netz.new()
|
||||
static var ANKER: Dictionary = {}
|
||||
|
||||
static var PLACES: Dictionary = FEST.duplicate(true)
|
||||
## Die fünf Sonderbauten. Sie bekommen eine Parzelle an einer Arterie.
|
||||
static var SONDER: Array = [
|
||||
["uni", "Hochschule", Color(0.72, 0.70, 0.66)],
|
||||
["cafe", "Café Nadel", Color(0.82, 0.62, 0.45)],
|
||||
["buero", "Büro Ostwind", Color(0.62, 0.66, 0.72)],
|
||||
["baeckerei", "Bäckerei Keller", Color(0.85, 0.72, 0.50)],
|
||||
["werkstatt", "Werkstatt Loos", Color(0.70, 0.58, 0.55)],
|
||||
]
|
||||
|
||||
static var PLACES: Dictionary = {}
|
||||
static var LIST: Array = []
|
||||
|
||||
## Arbeitsplätze, die ein erzeugter Mensch bekommen kann.
|
||||
@@ -125,46 +123,299 @@ static var ABLAEUFE: Array = [
|
||||
]
|
||||
|
||||
|
||||
## Zu jeder Technik ein Anliegen und ein Tagesablauf, bei dem sie greift:
|
||||
## [Anliegen, Ablauf]. Mensch i bekommt den Eintrag zu Technik i — und Technik i
|
||||
## ist genau dann entdeckt, wenn i Träume erfüllt sind. So hängt am Schwarzen
|
||||
## Brett immer mindestens ein Mensch, dem mit dem Gelernten zu helfen ist.
|
||||
static var PASSEND: Array = [
|
||||
[0, 0], # Pomodoro -> aufschieben, zögert
|
||||
[6, 4], # Zwei-Minuten -> Werkstatt, räumt auf
|
||||
[4, 3], # Wenn-Dann -> Gewohnheit, Morgenroutine
|
||||
[1, 1], # Gewohnheiten -> Klavier, isst / Abendroutine
|
||||
[9, 0], # Zerlegen -> Überforderung, grübelt
|
||||
[0, 4], # Umgebung -> Ablenkung, räumt auf
|
||||
[8, 0], # Umdeuten -> Selbstzweifel, grübelt
|
||||
[7, 2], # Box-Atmung -> Angst, angespannt
|
||||
[8, 2], # Selbstmitgefühl -> Rückschlag, grübelt
|
||||
[4, 1], # Schlaffenster -> Erschöpfung, ruht
|
||||
[5, 2], # Hilfe bitten -> Einsamkeit, wartet
|
||||
[3, 3], # Nein sagen -> Grenzen, arbeitet
|
||||
]
|
||||
|
||||
|
||||
static func heim_key(i: int) -> String:
|
||||
return "heim_%d" % i
|
||||
|
||||
|
||||
## Baut Wohnhäuser und Menschen für so viele Bewohner neu auf.
|
||||
static func erzeuge(menschen: int) -> void:
|
||||
PLACES = FEST.duplicate(true)
|
||||
## Baut den ganzen Bezirk neu auf: Straßen, Parzellen, Sonderbauten, Wohnhäuser.
|
||||
## Die Zahl der Menschen liegt fest -- sie sind alle von Anfang an da.
|
||||
static func erzeuge(_ungenutzt: int = 0) -> void:
|
||||
_bezirk()
|
||||
PLACES = {}
|
||||
LIST = []
|
||||
for i in range(maxi(menschen, 0)):
|
||||
PLACES[heim_key(i)] = _heim(i)
|
||||
_park_eintragen()
|
||||
_sonderbauten_verteilen()
|
||||
_wohnhaeuser_verteilen()
|
||||
_anker_setzen()
|
||||
for i in range(EINWOHNER):
|
||||
LIST.append(_mensch(i))
|
||||
|
||||
|
||||
static func _heim(i: int) -> Dictionary:
|
||||
var ring := i / HAEUSER_JE_RING
|
||||
var winkel := float(i % HAEUSER_JE_RING) * TAU / float(HAEUSER_JE_RING) + 0.39
|
||||
var r := RING_ERSTER + float(ring) * RING_ABSTAND
|
||||
var x := sin(winkel) * r
|
||||
var z := cos(winkel) * r
|
||||
return {
|
||||
"name": "%s %s" % [_nachname(i), "Weg %d" % (i + 1)],
|
||||
"pos": [x, z],
|
||||
"size": [12.0 + float(i % 3) * 2.0, 10.0],
|
||||
"dir": _tuer_zur_mitte(x, z),
|
||||
"color": Color.from_hsv(fmod(0.08 + float(i) * 0.113, 1.0), 0.28, 0.78),
|
||||
# ------------------------------------------------------------ Bezirk
|
||||
|
||||
## Arterien im Raster, Blöcke dazwischen, roji ins Blockinnere, Parzellen
|
||||
## an jeder Straße. Danach steht das Straßennetz, auf dem alle laufen.
|
||||
static func _bezirk() -> void:
|
||||
PARZELLEN = []
|
||||
STRASSEN = []
|
||||
NETZ = Netz.new()
|
||||
ANKER = {}
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = 31337
|
||||
|
||||
# Arterien: die Achsen leicht versetzt, damit kein reines Schachbrett steht
|
||||
var xs: Array = []
|
||||
var zs: Array = []
|
||||
for i in range(ACHSEN.size()):
|
||||
xs.append(float(ACHSEN[i]) + rng.randf_range(-3.0, 3.0))
|
||||
zs.append(float(ACHSEN[i]) + rng.randf_range(-3.0, 3.0))
|
||||
var von: float = float(ACHSEN[0]) - 6.0
|
||||
var bis: float = float(ACHSEN[ACHSEN.size() - 1]) + 6.0
|
||||
for x in xs:
|
||||
_strasse(Vector3(x, 0, von), Vector3(x, 0, bis), ARTERIE)
|
||||
for z in zs:
|
||||
_strasse(Vector3(von, 0, z), Vector3(bis, 0, z), ARTERIE)
|
||||
|
||||
# Blöcke zwischen den Arterien
|
||||
for ix in range(xs.size() - 1):
|
||||
for iz in range(zs.size() - 1):
|
||||
var x0: float = float(xs[ix]) + ARTERIE * 0.5
|
||||
var x1: float = float(xs[ix + 1]) - ARTERIE * 0.5
|
||||
var z0: float = float(zs[iz]) + ARTERIE * 0.5
|
||||
var z1: float = float(zs[iz + 1]) - ARTERIE * 0.5
|
||||
if ix == 1 and iz == 1:
|
||||
continue # die Mitte bleibt Park
|
||||
_block(Vector3((x0 + x1) * 0.5, 0, (z0 + z1) * 0.5),
|
||||
Vector2(x1 - x0, z1 - z0), rng)
|
||||
_netz_bauen()
|
||||
|
||||
|
||||
static func _strasse(a: Vector3, b: Vector3, breite: float) -> void:
|
||||
STRASSEN.append({"a": a, "b": b, "breite": breite})
|
||||
|
||||
|
||||
## Aus den Straßenbändern ein zusammenhängendes Netz machen: an jeder Kreuzung
|
||||
## sitzt ein Knoten, dazwischen alle 16 Meter einer.
|
||||
static func _netz_bauen() -> void:
|
||||
for i in range(STRASSEN.size()):
|
||||
var a: Vector3 = STRASSEN[i]["a"]
|
||||
var b: Vector3 = STRASSEN[i]["b"]
|
||||
var laenge := a.distance_to(b)
|
||||
if laenge < 0.5:
|
||||
continue
|
||||
var stellen: Array = [0.0, 1.0]
|
||||
for j in range(STRASSEN.size()):
|
||||
if j == i:
|
||||
continue
|
||||
var t := _kreuzung(a, b, STRASSEN[j]["a"], STRASSEN[j]["b"])
|
||||
if t >= 0.0:
|
||||
stellen.append(t)
|
||||
stellen.sort()
|
||||
var fein: Array = [float(stellen[0])]
|
||||
for k in range(1, stellen.size()):
|
||||
var von: float = float(fein[fein.size() - 1])
|
||||
var bis: float = float(stellen[k])
|
||||
if bis - von < 0.001:
|
||||
continue
|
||||
var n := maxi(1, int((bis - von) * laenge / 16.0))
|
||||
for m in range(1, n + 1):
|
||||
fein.append(von + (bis - von) * float(m) / float(n))
|
||||
var vorher := -1
|
||||
for t2 in fein:
|
||||
var idx := NETZ.fuege_knoten(a.lerp(b, float(t2)))
|
||||
if vorher >= 0:
|
||||
NETZ.verbinde(vorher, idx)
|
||||
vorher = idx
|
||||
|
||||
|
||||
## Parameter t auf der Strecke a-b, an dem sie c-d schneidet. -1 wenn nicht.
|
||||
static func _kreuzung(a: Vector3, b: Vector3, c: Vector3, d: Vector3) -> float:
|
||||
var r := b - a
|
||||
var s2 := d - c
|
||||
var nenner := r.x * s2.z - r.z * s2.x
|
||||
if absf(nenner) < 0.0001:
|
||||
return -1.0
|
||||
var t := ((c.x - a.x) * s2.z - (c.z - a.z) * s2.x) / nenner
|
||||
var u := ((c.x - a.x) * r.z - (c.z - a.z) * r.x) / nenner
|
||||
if t < 0.0 or t > 1.0 or u < 0.0 or u > 1.0:
|
||||
return -1.0
|
||||
return t
|
||||
|
||||
|
||||
## Ein Block: außen ein Kranz von Parzellen an den Arterien, innen eine Gasse
|
||||
## mit den hinteren Parzellen daran. Die Nord- und Südreihe laufen über die
|
||||
## volle Breite, die Ost- und Westreihe nur dazwischen -- sonst überlappen die
|
||||
## Ecken.
|
||||
static func _block(mitte: Vector3, groesse: Vector2, rng: RandomNumberGenerator) -> void:
|
||||
var tiefe := minf(RAND_TIEFE, minf(groesse.x, groesse.y) * 0.34)
|
||||
var seiten := [
|
||||
["back", Vector3(0, 0, 1), groesse.x, groesse.y],
|
||||
["forward", Vector3(0, 0, -1), groesse.x, groesse.y],
|
||||
["right", Vector3(1, 0, 0), groesse.y - tiefe * 2.0, groesse.x],
|
||||
["left", Vector3(-1, 0, 0), groesse.y - tiefe * 2.0, groesse.x],
|
||||
]
|
||||
for eintrag in seiten:
|
||||
var dir: String = eintrag[0]
|
||||
var n: Vector3 = eintrag[1]
|
||||
var laenge: float = eintrag[2]
|
||||
var quer_mass: float = eintrag[3]
|
||||
if laenge < PARZELLE_MIN:
|
||||
continue
|
||||
var quer := Vector3(absf(n.z), 0, absf(n.x))
|
||||
var anzahl := maxi(1, int(round(laenge / rng.randf_range(PARZELLE_MIN + 1.0,
|
||||
PARZELLE_MAX))))
|
||||
var breite := laenge / float(anzahl)
|
||||
var rand := mitte + n * (quer_mass * 0.5 - tiefe * 0.5)
|
||||
for k in range(anzahl):
|
||||
var versatz := (float(k) + 0.5 - float(anzahl) * 0.5) * breite
|
||||
var quer_ist := absf(n.z) > 0.0
|
||||
PARZELLEN.append({
|
||||
"pos": rand + quer * versatz,
|
||||
"size": Vector2(breite - 0.8, tiefe - 0.8) if quer_ist
|
||||
else Vector2(tiefe - 0.8, breite - 0.8),
|
||||
"dir": dir,
|
||||
})
|
||||
|
||||
# Blockinneres: eine Gasse von der längeren Seite hinein
|
||||
var innen := groesse - Vector2(tiefe, tiefe) * 2.0
|
||||
if innen.x < 12.0 or innen.y < 12.0:
|
||||
return
|
||||
var laengs := innen.y >= innen.x
|
||||
var richtung := Vector3(0, 0, -1) if laengs else Vector3(-1, 0, 0)
|
||||
# Die Gasse setzt an der Arterie an, sonst hinge sie in der Luft
|
||||
var aussen: float = (groesse.y if laengs else groesse.x) * 0.5 + ARTERIE * 0.5
|
||||
var start := mitte - richtung * aussen
|
||||
var ende := mitte + richtung * ((innen.y if laengs else innen.x) * 0.5 - 2.0)
|
||||
_strasse(start, ende, ROJI)
|
||||
var flanke := Vector3(1, 0, 0) if laengs else Vector3(0, 0, 1)
|
||||
var p_breite := (innen.x if laengs else innen.y) * 0.5 - ROJI * 0.5 - 0.8
|
||||
if p_breite < PARZELLE_MIN * 0.8:
|
||||
return
|
||||
# Nur der Teil im Blockinneren trägt Parzellen
|
||||
var innen_start := mitte - richtung * ((innen.y if laengs else innen.x) * 0.5)
|
||||
var laenge_g := innen_start.distance_to(ende)
|
||||
var anzahl_g := maxi(1, int(round(laenge_g / 13.0)))
|
||||
var tief_g := laenge_g / float(anzahl_g) - 0.8
|
||||
for seite: float in [1.0, -1.0]:
|
||||
for k in range(anzahl_g):
|
||||
var t := (float(k) + 0.5) / float(anzahl_g)
|
||||
var pos := innen_start.lerp(ende, t) + flanke * (seite * (ROJI * 0.5 + p_breite * 0.5))
|
||||
PARZELLEN.append({
|
||||
"pos": pos,
|
||||
"size": Vector2(p_breite, tief_g) if laengs else Vector2(tief_g, p_breite),
|
||||
"dir": ("left" if seite > 0.0 else "right") if laengs
|
||||
else ("forward" if seite > 0.0 else "back"),
|
||||
})
|
||||
|
||||
|
||||
# ------------------------------------------------------------ Belegung
|
||||
|
||||
static func _park_eintragen() -> void:
|
||||
PLACES["park"] = {
|
||||
"name": "Stadtpark", "pos": [0.0, 0.0], "size": [0.0, 0.0],
|
||||
"dir": "none", "color": Color(0.45, 0.65, 0.38),
|
||||
}
|
||||
|
||||
|
||||
static func _tuer_zur_mitte(x: float, z: float) -> String:
|
||||
if absf(x) > absf(z):
|
||||
return "left" if x > 0.0 else "right"
|
||||
return "forward" if z > 0.0 else "back"
|
||||
## Die Sonderbauten bekommen Parzellen an einer Arterie, gut verteilt.
|
||||
static func _sonderbauten_verteilen() -> void:
|
||||
var kandidaten: Array = []
|
||||
for i in range(PARZELLEN.size()):
|
||||
var p: Dictionary = PARZELLEN[i]
|
||||
if minf(p["size"].x, p["size"].y) >= 10.0:
|
||||
kandidaten.append(i)
|
||||
for s in range(SONDER.size()):
|
||||
if kandidaten.is_empty():
|
||||
break
|
||||
# Das Café gehört in die Mitte -- jeder Tagesablauf führt dorthin.
|
||||
# Die übrigen Sonderbauten verteilen sich rundum.
|
||||
var wunsch := Vector3.ZERO
|
||||
if str(SONDER[s][0]) != "cafe":
|
||||
var w := TAU * float(s) / float(SONDER.size() - 1)
|
||||
wunsch = Vector3(cos(w), 0, sin(w)) * 42.0
|
||||
var idx: int = -1
|
||||
var beste := INF
|
||||
for k in kandidaten:
|
||||
var q: Dictionary = PARZELLEN[k]
|
||||
if q.has("belegt"):
|
||||
continue
|
||||
var d: float = (q["pos"] as Vector3).distance_to(wunsch)
|
||||
if d < beste:
|
||||
beste = d
|
||||
idx = int(k)
|
||||
if idx < 0:
|
||||
continue
|
||||
var p: Dictionary = PARZELLEN[idx]
|
||||
p["belegt"] = true
|
||||
PLACES[str(SONDER[s][0])] = {
|
||||
"name": str(SONDER[s][1]),
|
||||
"pos": [p["pos"].x, p["pos"].z],
|
||||
"size": [p["size"].x, p["size"].y],
|
||||
"dir": p["dir"],
|
||||
"color": SONDER[s][2],
|
||||
}
|
||||
|
||||
|
||||
## Die Wohnhäuser: gleichmäßig über den Bezirk verteilt, damit die Wege
|
||||
## nicht alle in derselben Ecke enden.
|
||||
static func _wohnhaeuser_verteilen() -> void:
|
||||
var frei: Array = []
|
||||
for i in range(PARZELLEN.size()):
|
||||
if not PARZELLEN[i].has("belegt"):
|
||||
frei.append(i)
|
||||
if frei.is_empty():
|
||||
return
|
||||
for i in range(EINWOHNER):
|
||||
var idx: int = int(frei[(i * 11 + 5) % frei.size()])
|
||||
var p: Dictionary = PARZELLEN[idx]
|
||||
if p.has("belegt"):
|
||||
for versuch in range(frei.size()):
|
||||
idx = int(frei[(i * 11 + 5 + versuch) % frei.size()])
|
||||
p = PARZELLEN[idx]
|
||||
if not p.has("belegt"):
|
||||
break
|
||||
p["belegt"] = true
|
||||
PLACES[heim_key(i)] = {
|
||||
"name": "%s %d" % [_nachname(i), i + 1],
|
||||
"pos": [p["pos"].x, p["pos"].z],
|
||||
"size": [p["size"].x, p["size"].y],
|
||||
"dir": p["dir"],
|
||||
"color": Color.from_hsv(fmod(0.08 + float(i) * 0.113, 1.0), 0.28, 0.78),
|
||||
}
|
||||
|
||||
|
||||
static func _anker_setzen() -> void:
|
||||
for key in PLACES:
|
||||
var s: Array = PLACES[key]["size"]
|
||||
var tuer := Orte.door(PLACES, key)
|
||||
if float(s[0]) <= 0.0:
|
||||
tuer = Orte.center(PLACES, key)
|
||||
ANKER[key] = int(NETZ.andocken(tuer)["knoten"])
|
||||
|
||||
|
||||
static func _mensch(i: int) -> Dictionary:
|
||||
var anliegen: Dictionary = ANLIEGEN[i % ANLIEGEN.size()]
|
||||
var arbeit: String = str(ARBEIT[i % ARBEIT.size()])
|
||||
# Bis alle Wege entdeckt sind, folgt jeder Mensch dem Weg gleicher Nummer.
|
||||
# Danach mischt sich das Feld wieder.
|
||||
var a_nr: int = i % ANLIEGEN.size()
|
||||
var b_nr: int = i % ABLAEUFE.size()
|
||||
if i < PASSEND.size():
|
||||
a_nr = int(PASSEND[i][0])
|
||||
b_nr = int(PASSEND[i][1])
|
||||
var anliegen: Dictionary = ANLIEGEN[a_nr]
|
||||
var heim := heim_key(i)
|
||||
var arbeit := _naechster_arbeitsplatz(heim, i)
|
||||
var ablauf: Array = []
|
||||
for e in ABLAEUFE[i % ABLAEUFE.size()]:
|
||||
for e in ABLAEUFE[b_nr]:
|
||||
var ort: String = str(e[1])
|
||||
if ort == "heim":
|
||||
ort = heim
|
||||
@@ -186,6 +437,24 @@ static func _mensch(i: int) -> Dictionary:
|
||||
}
|
||||
|
||||
|
||||
## Wer wo wohnt, arbeitet in der Nähe. Sonst laufen alle quer durch den Bezirk
|
||||
## und kommen nie an.
|
||||
static func _naechster_arbeitsplatz(heim: String, i: int) -> String:
|
||||
if not PLACES.has(heim):
|
||||
return str(ARBEIT[i % ARBEIT.size()])
|
||||
var von := Orte.center(PLACES, heim)
|
||||
var beste := INF
|
||||
var ziel := str(ARBEIT[i % ARBEIT.size()])
|
||||
for a in ARBEIT:
|
||||
if not PLACES.has(a):
|
||||
continue
|
||||
var d := Orte.center(PLACES, str(a)).distance_to(von)
|
||||
if d < beste:
|
||||
beste = d
|
||||
ziel = str(a)
|
||||
return ziel
|
||||
|
||||
|
||||
static func _vorname(i: int) -> String:
|
||||
return str(VORNAMEN[i % VORNAMEN.size()])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user