extends Node3D ## Gelaende, Vegetation und Umgebung. Baeume laufen ueber MultiMesh, ## damit auch mehrere hundert Stueck guenstig bleiben. const Mat = preload("res://scripts/mat.gd") const Deco = preload("res://scripts/deco.gd") const GRASS := Color("7cb85a") const GRASS_DARK := Color("74b054") const WATER := Color("4f9fd6") const SAND := Color("e0c98a") const EXTENT := 360.0 var _rng: RandomNumberGenerator func build(rng: RandomNumberGenerator, track, stations: Array) -> void: _rng = rng _ground() _scatter(track, stations) func _flat_disc(pos: Vector3, radius: float, color: Color, height := 0.06) -> void: var m := MeshInstance3D.new() m.mesh = Deco.cyl(radius, height, 24) m.material_override = Mat.flat(color) m.position = pos add_child(m) func _ground() -> void: var plane := PlaneMesh.new() plane.size = Vector2(1000, 1000) var g := MeshInstance3D.new() g.mesh = plane g.material_override = Mat.flat(GRASS) add_child(g) _flat_disc(Vector3(-230, 0.02, 150), 72.0, SAND, 0.05) _flat_disc(Vector3(-230, 0.04, 150), 60.0, WATER, 0.05) for i in 18: var p := Vector2(_rng.randf_range(-EXTENT, EXTENT), _rng.randf_range(-EXTENT, EXTENT)) _flat_disc(Vector3(p.x, 0.01, p.y), _rng.randf_range(18.0, 44.0), GRASS_DARK, 0.03) func _multi(mesh: Mesh, items: Array) -> void: if items.is_empty(): return var mm := MultiMesh.new() mm.transform_format = MultiMesh.TRANSFORM_3D mm.use_colors = true mm.mesh = mesh mm.instance_count = items.size() for i in items.size(): var it: Dictionary = items[i] mm.set_instance_transform(i, it["xf"]) # Instanzfarben gehen roh in den Shader, deshalb hier nach Linear wandeln. mm.set_instance_color(i, (it["color"] as Color).srgb_to_linear()) var mmi := MultiMeshInstance3D.new() mmi.multimesh = mm mmi.material_override = Mat.flat(Color.WHITE) add_child(mmi) func _scatter(track, stations: Array) -> void: var trunks: Array = [] var cones: Array = [] var blobs: Array = [] var rocks: Array = [] var placed := 0 var tries := 0 while placed < 620 and tries < 12000: tries += 1 var p := Vector2(_rng.randf_range(-EXTENT, EXTENT), _rng.randf_range(-EXTENT, EXTENT)) if track.distance_to_track(p) < 11.0: continue if p.distance_to(Vector2(-230, 150)) < 78.0: continue var blocked := false for st in stations: if p.distance_to(Vector2(st.global_position.x, st.global_position.z)) < 30.0: blocked = true break if blocked: continue placed += 1 var yaw := _rng.randf() * TAU if _rng.randf() < 0.6: var h := _rng.randf_range(4.0, 8.5) var col: Color = Deco.PINE[_rng.randi() % Deco.PINE.size()] trunks.append(_item(p, h * 0.16, Vector3(1, h * 0.3, 1), yaw, Deco.TRUNK)) var tiers := 3 if h > 6.0 else 2 for i in tiers: var f := float(i) / float(tiers) var r := lerpf(h * 0.3, h * 0.14, f) var th := h * 0.44 var y := h * 0.26 + f * h * 0.32 + th * 0.5 cones.append(_item(p, y, Vector3(r, th, r), yaw, col)) elif _rng.randf() < 0.85: var h := _rng.randf_range(3.2, 6.0) var col: Color = Deco.LEAF[_rng.randi() % Deco.LEAF.size()] trunks.append(_item(p, h * 0.28, Vector3(0.9, h * 0.55, 0.9), yaw, Deco.TRUNK)) var r := h * 0.44 blobs.append(_item(p, h * 0.55 + r * 0.5, Vector3(r, r, r), yaw, col)) else: var r := _rng.randf_range(0.7, 2.0) rocks.append(_item(p, r * 0.35, Vector3(r, r * 0.55, r * 1.1), yaw, Deco.ROCK)) _multi(Deco.cyl(0.18, 1.0, 6), trunks) _multi(Deco.cone(1.0, 1.0, 7), cones) _multi(Deco.blob(1.0), blobs) _multi(Deco.blob(1.0), rocks) func _item(p: Vector2, y: float, scale: Vector3, yaw: float, color: Color) -> Dictionary: var b := Basis(Vector3.UP, yaw).scaled(scale) return {"xf": Transform3D(b, Vector3(p.x, y, p.y)), "color": color}