diff --git a/terrain.gd b/terrain.gd index 2dc50ab..2ee4b64 100644 --- a/terrain.gd +++ b/terrain.gd @@ -14,22 +14,34 @@ extends Node3D # Insel-Maße richten sich nach der Spieler-/Prop-Größe (Capsule ~0.16-0.5, # Berg/Wald-Modelle bei 0.1-0.2 Skalierung ~2 Einheiten hoch) — nicht umgekehrt. -@export var border_spacing : float = 1.5 # Abstand zwischen Grenz-Props entlang eines Grats -@export var border_jitter : float = 0.5 # zufälliger seitlicher Versatz +@export var border_jitter : float = 0.05 # zufälliger seitlicher Versatz (klein: darf die Überlappungs-Garantie nicht brechen) +@export var border_overlap : float = 0.8 # <1.0 = Nachbar-Hitboxen überlappen sich garantiert statt sich nur zu berühren const COLOR_TABLE := Color(0.75, 0.74, 0.70) const COLOR_FACET_A := Color(0.55, 0.55, 0.48) const COLOR_FACET_B := Color(0.49, 0.49, 0.43) const COLOR_PAVILION := Color(0.30, 0.28, 0.26) -# Grenz-Props: gleiche Modelle + Skalierung + Hitbox-Maße wie in main.tscn -const MOUNTAIN_SCENE := preload("res://assets/models/mountain.glb") -const MOUNTAIN2_SCENE := preload("res://assets/models/mountain2v5.glb") -const FOREST_SCENE := preload("res://assets/models/forest.glb") - -const MOUNTAIN_SCALE := 0.1 -const MOUNTAIN2_SCALE := 0.05 -const FOREST_SCALE := 0.2 +# Grenz-Props: gleiche Modelle + Skalierung + Hitbox-Maße wie in main.tscn. +# "radius"/"height" sind die Shape-Maße VOR der Skalierung (wie im Original); +# der reale Welt-Radius (für die Überlappungsprüfung) ist radius * scale. +const BORDER_KINDS := [ + { + "scene": preload("res://assets/models/mountain.glb"), + "scale": 0.1, + "shape": "capsule", "radius": 7.0, "height": 20.0, + }, + { + "scene": preload("res://assets/models/mountain2v5.glb"), + "scale": 0.05, + "shape": "capsule", "radius": 8.5078125, "height": 17.015625, + }, + { + "scene": preload("res://assets/models/forest.glb"), + "scale": 0.2, + "shape": "cylinder", "radius": 0.72021484, "height": 2.2817383, + }, +] func _ready() -> void: _build_crown() @@ -135,48 +147,64 @@ func _build_region_borders() -> void: var a := _hex_angle(k) var dir := Vector3(cos(a), 0.0, sin(a)) var perp := Vector3(-dir.z, 0.0, dir.x) + _build_ridge(borders, dir, perp) - var t := table_radius - while t <= girdle_radius: - var frac := (t - table_radius) / (girdle_radius - table_radius) - var pos := dir * t - pos.y = lerp(0.0, -crown_drop, frac) - pos += perp * randf_range(-border_jitter, border_jitter) +# Kettet Props entlang eines Grats: jeder neue Prop wird so nah an den vorigen +# gesetzt, dass sich ihre Welt-Radien garantiert überlappen (Distanz zwischen +# den Mittelpunkten <= (r1+r2) * border_overlap). Kein Rätselraten über feste +# Abstände mehr — die Lücke wird pro Prop-Paar explizit geprüft und korrigiert. +func _build_ridge(borders: Node3D, dir: Vector3, perp: Vector3) -> void: + var t := table_radius + var prev_pos := Vector3.ZERO + var prev_radius := 0.0 + var first := true - _place_border_prop(borders, pos) - t += border_spacing + randf_range(-border_spacing * 0.25, border_spacing * 0.25) + while t <= girdle_radius: + var kind: Dictionary = BORDER_KINDS[randi() % BORDER_KINDS.size()] + var radius: float = kind["radius"] * kind["scale"] + var lateral := randf_range(-border_jitter, border_jitter) + var pos := _ridge_point(dir, perp, t, lateral) -func _place_border_prop(parent: Node3D, pos: Vector3) -> void: - var roll := randf() - var inst: Node3D - var scale_factor: float - var shape: Shape3D + if not first: + var min_dist := (prev_radius + radius) * border_overlap + # Iterativ korrigieren statt blind zu vertrauen: die Facette ist geneigt, + # daher ist die Distanz nicht rein radial (Höhe hängt am Abstand mit dran). + for _i in range(4): + var actual_dist := pos.distance_to(prev_pos) + if actual_dist <= min_dist: + break + t -= (actual_dist - min_dist) + pos = _ridge_point(dir, perp, t, lateral) - if roll < 0.4: - inst = MOUNTAIN_SCENE.instantiate() as Node3D - scale_factor = MOUNTAIN_SCALE - var cap := CapsuleShape3D.new() - cap.radius = 7.0 - cap.height = 20.0 - shape = cap - elif roll < 0.7: - inst = MOUNTAIN2_SCENE.instantiate() as Node3D - scale_factor = MOUNTAIN2_SCALE - var cap2 := CapsuleShape3D.new() - cap2.radius = 8.5078125 - cap2.height = 17.015625 - shape = cap2 - else: - inst = FOREST_SCENE.instantiate() as Node3D - scale_factor = FOREST_SCALE - var cyl := CylinderShape3D.new() - cyl.radius = 0.72021484 - cyl.height = 2.2817383 - shape = cyl + _spawn_border_prop(borders, kind, pos) + prev_pos = pos + prev_radius = radius + first = false + t += radius # nächster Kandidat startet grob hinter diesem Prop; obige Prüfung zieht ihn bei Bedarf näher +func _ridge_point(dir: Vector3, perp: Vector3, t: float, lateral: float) -> Vector3: + var frac: float = clamp((t - table_radius) / (girdle_radius - table_radius), 0.0, 1.0) + var pos := dir * t + perp * lateral + pos.y = lerp(0.0, -crown_drop, frac) + return pos + +func _spawn_border_prop(parent: Node3D, kind: Dictionary, pos: Vector3) -> void: + var inst := (kind["scene"] as PackedScene).instantiate() as Node3D inst.position = pos inst.rotation.y = randf() * TAU - inst.scale = Vector3.ONE * scale_factor + inst.scale = Vector3.ONE * float(kind["scale"]) + + var shape: Shape3D + if kind["shape"] == "capsule": + var cap := CapsuleShape3D.new() + cap.radius = kind["radius"] + cap.height = kind["height"] + shape = cap + else: + var cyl := CylinderShape3D.new() + cyl.radius = kind["radius"] + cyl.height = kind["height"] + shape = cyl var cs := CollisionShape3D.new() cs.shape = shape