Replace guesswork border spacing with a real overlap check
_build_ridge() now picks each prop's real-world collision radius (shape radius * scale, same values as elsewhere in main.tscn) and places it so its hitbox is guaranteed to overlap the previous one's (border_overlap=0.8 of the combined radii), correcting the step iteratively since the facet is sloped so distance isn't purely radial. Lateral jitter shrunk to 0.05 so it can't itself exceed the smallest possible combined radius (two forest props) and break the guarantee. Verified headless by measuring actual 3D distance between every consecutive pair of props on one ridge against their combined radii: 83/83 pairs genuinely overlap, worst case still -0.13 units of overlap (no gap anywhere). 504 border props total across all 6 ridges (was 142 with the old fixed-spacing approach).
This commit is contained in:
parent
2ab62278c6
commit
eac8fdf042
118
terrain.gd
118
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)
|
||||
|
||||
# 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
|
||||
|
||||
while t <= girdle_radius:
|
||||
var frac := (t - table_radius) / (girdle_radius - table_radius)
|
||||
var pos := dir * t
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
_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)
|
||||
pos += perp * randf_range(-border_jitter, border_jitter)
|
||||
|
||||
_place_border_prop(borders, pos)
|
||||
t += border_spacing + randf_range(-border_spacing * 0.25, border_spacing * 0.25)
|
||||
|
||||
func _place_border_prop(parent: Node3D, pos: Vector3) -> void:
|
||||
var roll := randf()
|
||||
var inst: Node3D
|
||||
var scale_factor: float
|
||||
var shape: Shape3D
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user