227 lines
7.3 KiB
GDScript
227 lines
7.3 KiB
GDScript
class_name Prims
|
|
## Kleine Helfer, um die Welt aus Godot-Primitiven zusammenzusetzen.
|
|
|
|
## Gleiche Farbe, gleiches Material: tausend Klötze brauchen keine tausend
|
|
## Materialien. Wer ein Material verändern will, dupliziert es sich.
|
|
static var _materialien: Dictionary = {}
|
|
|
|
static func mat(c: Color, emis: float = 0.0, rough: float = 0.85) -> StandardMaterial3D:
|
|
var key := "%s|%.2f|%.2f" % [str(c), emis, rough]
|
|
if _materialien.has(key):
|
|
return _materialien[key]
|
|
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
|
|
_materialien[key] = m
|
|
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:
|
|
# 3 cm über dem Gelände, sonst flimmert der Boden gegen die Insel
|
|
box(parent, center + Vector3(0, -0.07, 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["teile"]:
|
|
_sammle(n, out)
|
|
for w in b["waende"]:
|
|
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
|
|
|
|
|
|
const STUMPF := 0.6 # so hoch bleiben die Wände, wenn man drinnen steht
|
|
|
|
## Schnitt wie bei den Sims: steht der Spieler in einem Bau, verschwinden Dach
|
|
## und Schild, und alle Wände des Hauses werden zu kniehohen Stümpfen gekürzt.
|
|
## Nichts wird durchsichtig. Ein Bau: {c, size, hoch, teile, waende}.
|
|
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
|
|
if drin == bool(b.get("drin", false)):
|
|
continue # nur anfassen, wenn sich etwas ändert
|
|
b["drin"] = drin
|
|
for t in b["teile"]:
|
|
(t as Node3D).visible = not drin
|
|
for w in b["waende"]:
|
|
_stumpf(w as MeshInstance3D, c.y, drin)
|
|
|
|
|
|
## Eine Wand kürzen oder wiederherstellen. Was ganz oben sitzt (der Türsturz),
|
|
## verschwindet drinnen ganz.
|
|
static func _stumpf(w: MeshInstance3D, boden: float, kurz: bool) -> void:
|
|
var bm := w.mesh as BoxMesh
|
|
if bm == null:
|
|
w.visible = not kurz
|
|
return
|
|
if not w.has_meta("voll"):
|
|
w.set_meta("voll", Vector2(bm.size.y, w.position.y))
|
|
var voll: Vector2 = w.get_meta("voll")
|
|
var unten: float = voll.y - voll.x * 0.5 - boden
|
|
if unten > 0.3:
|
|
w.visible = not kurz
|
|
return
|
|
w.visible = true
|
|
bm.size.y = STUMPF if kurz else voll.x
|
|
w.position.y = (boden + STUMPF * 0.5) if kurz else voll.y
|
|
|
|
|
|
## 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
|