class_name Prims ## Kleine Helfer, um die Welt aus Godot-Primitiven zusammenzusetzen. static func mat(c: Color, emis: float = 0.0, rough: float = 0.85) -> StandardMaterial3D: var m := StandardMaterial3D.new() m.albedo_color = c m.roughness = rough m.metallic = 0.0 if emis > 0.0: m.emission_enabled = true m.emission = c m.emission_energy_multiplier = emis if c.a < 1.0: m.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA return m static func box(parent: Node, pos: Vector3, size: Vector3, c: Color, collide: bool = true, emis: float = 0.0) -> MeshInstance3D: var mi := MeshInstance3D.new() var bm := BoxMesh.new() bm.size = size mi.mesh = bm mi.material_override = mat(c, emis) mi.position = pos parent.add_child(mi) if collide: _add_box_body(mi, size) return mi static func _add_box_body(mi: MeshInstance3D, size: Vector3) -> void: var sb := StaticBody3D.new() var cs := CollisionShape3D.new() var sh := BoxShape3D.new() sh.size = size cs.shape = sh sb.add_child(cs) mi.add_child(sb) static func cyl(parent: Node, pos: Vector3, radius: float, height: float, c: Color, collide: bool = true, emis: float = 0.0) -> MeshInstance3D: var mi := MeshInstance3D.new() var cm := CylinderMesh.new() cm.top_radius = radius cm.bottom_radius = radius cm.height = height mi.mesh = cm mi.material_override = mat(c, emis) mi.position = pos parent.add_child(mi) if collide: var sb := StaticBody3D.new() var cs := CollisionShape3D.new() var sh := CylinderShape3D.new() sh.radius = radius sh.height = height cs.shape = sh sb.add_child(cs) mi.add_child(sb) return mi static func sphere(parent: Node, pos: Vector3, radius: float, c: Color, emis: float = 0.0) -> MeshInstance3D: var mi := MeshInstance3D.new() var sm := SphereMesh.new() sm.radius = radius sm.height = radius * 2.0 mi.mesh = sm mi.material_override = mat(c, emis) mi.position = pos parent.add_child(mi) return mi static func capsule(parent: Node, pos: Vector3, radius: float, height: float, c: Color) -> MeshInstance3D: var mi := MeshInstance3D.new() var cm := CapsuleMesh.new() cm.radius = radius cm.height = height mi.mesh = cm mi.material_override = mat(c) mi.position = pos parent.add_child(mi) return mi static func label(parent: Node, pos: Vector3, text: String, size: float = 0.35, c: Color = Color.WHITE) -> Label3D: var l := Label3D.new() l.text = text l.font_size = 96 l.pixel_size = size / 96.0 l.position = pos l.billboard = BaseMaterial3D.BILLBOARD_ENABLED l.no_depth_test = false l.modulate = c l.outline_size = 10 l.outline_modulate = Color(0, 0, 0, 0.85) l.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER parent.add_child(l) return l ## Ein rechteckiger Raum aus vier Wänden mit einer Türlücke. ## door_dir: Vector3.FORWARD/BACK/LEFT/RIGHT -- auf welcher Seite die Lücke sitzt. static func room(parent: Node, center: Vector3, size: Vector3, wall_c: Color, floor_c: Color, door_dir: Vector3, door_w: float = 3.0, with_floor: bool = true) -> Array: var t := 0.4 var h := size.y var waende: Array = [] if with_floor: box(parent, center + Vector3(0, -0.1, 0), Vector3(size.x, 0.2, size.z), floor_c) var seiten: Array[Vector3] = [Vector3.FORWARD, Vector3.BACK, Vector3.LEFT, Vector3.RIGHT] for d in seiten: var quer := absf(d.z) > 0.0 # Wand liegt entlang der X-Achse var along: Vector3 = Vector3(1, 0, 0) if quer else Vector3(0, 0, 1) var len_along: float = size.x if quer else size.z var off: Vector3 = Vector3(d.x * (size.x * 0.5 - t * 0.5), h * 0.5, d.z * (size.z * 0.5 - t * 0.5)) var voll: Vector3 = Vector3(size.x, h, t) if quer else Vector3(t, h, size.z) if d.is_equal_approx(door_dir) and door_w > 0.0 and door_w < len_along: var seg := (len_along - door_w) * 0.5 var seg_size: Vector3 = Vector3(seg, h, t) if quer else Vector3(t, h, seg) for s: float in [-1.0, 1.0]: var seg_pos: Vector3 = center + off + along * (s * (door_w * 0.5 + seg * 0.5)) waende.append(_wand(box(parent, seg_pos, seg_size, wall_c), d)) if h >= 3.0: var sturz: Vector3 = Vector3(door_w, h * 0.3, t) if quer else Vector3(t, h * 0.3, door_w) waende.append(_wand(box(parent, center + off + Vector3(0, h * 0.35, 0), sturz, wall_c), d)) else: waende.append(_wand(box(parent, center + off, voll, wall_c), d)) return waende static func _meshes_von(b: Dictionary) -> Array: var out: Array = [] for n in b["etagen"]: _sammle(n, out) for n in b["dach"]: _sammle(n, out) for liste in b["waende"]: for w in liste: if not out.has(w): out.append(w) return out static func _sammle(n: Node, out: Array) -> void: if n is MeshInstance3D and not out.has(n): out.append(n) for k in n.get_children(): _sammle(k, out) static func _wand(mi: MeshInstance3D, normale: Vector3) -> MeshInstance3D: mi.set_meta("normal", normale) return mi ## Etagenschnitt wie bei den Sims: steht der Spieler in einem Bau, verschwinden ## das Dach, die Etagen über ihm und die beiden Wände, die zur Kamera zeigen. ## Ein Bau: {c, size, hoch, etagen, dach, waende, voll}. "voll" heißt: ein ## geschlossener Klotz, der ganz verschwindet. ## Ein Bau zwischen Kamera und Spieler wird durchsichtig, sonst verschwände ## der Spieler hinter jedem Nachbarhaus. static func schnitt(bauten: Array, spieler: Vector3, kamera: Vector3) -> void: for b in bauten: var c: Vector3 = b["c"] var g: Vector2 = b["size"] var drin: bool = absf(spieler.x - c.x) < g.x * 0.5 + 0.3 \ and absf(spieler.z - c.z) < g.y * 0.5 + 0.3 \ and spieler.y > c.y - 0.5 and spieler.y < c.y + float(b["hoch"]) + 0.5 var kasten := AABB(Vector3(c.x - g.x * 0.5, c.y, c.z - g.y * 0.5), Vector3(g.x, float(b["hoch"]), g.y)) var im_weg: bool = not drin \ and kasten.intersects_segment(kamera, spieler + Vector3(0, 0.8, 0)) != null if bool(b.get("im_weg", false)) != im_weg: b["im_weg"] = im_weg if not b.has("meshes"): b["meshes"] = _meshes_von(b) for mi in b["meshes"]: (mi as GeometryInstance3D).transparency = 0.82 if im_weg else 0.0 var etagen: Array = b["etagen"] var pf := clampi(int(floor((spieler.y - c.y + 0.6) / Orte.ETAGE)), 0, maxi(0, etagen.size() - 1)) for f in range(etagen.size()): (etagen[f] as Node3D).visible = not drin or (f <= pf and not bool(b.get("voll", false))) for d in b["dach"]: (d as Node3D).visible = not drin var waende: Array = b["waende"] for f in range(waende.size()): for w in waende[f]: var n: Vector3 = w.get_meta("normal") (w as Node3D).visible = not (drin and f == pf and n.dot(kamera - c) > 0.0) ## Ein Satteldach: ein geschlossenes Prisma über den Wänden. Der First läuft ## entlang der längeren Seite, das Dach steht ringsum 0,6 über. static func dach(parent: Node, center: Vector3, size: Vector3, c: Color) -> MeshInstance3D: var quer := size.x >= size.z # First entlang X var spann: float = (size.z if quer else size.x) + 1.2 var laenge: float = (size.x if quer else size.z) + 1.2 var hoehe := spann * 0.5 * tan(0.62) var mi := MeshInstance3D.new() var pm := PrismMesh.new() pm.size = Vector3(spann, hoehe, laenge) mi.mesh = pm mi.material_override = mat(c.darkened(0.3)) mi.position = center + Vector3(0, size.y + hoehe * 0.5, 0) if quer: mi.rotation.y = PI * 0.5 parent.add_child(mi) return mi