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,
|
# 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.
|
# 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.05 # zufälliger seitlicher Versatz (klein: darf die Überlappungs-Garantie nicht brechen)
|
||||||
@export var border_jitter : float = 0.5 # zufälliger seitlicher Versatz
|
@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_TABLE := Color(0.75, 0.74, 0.70)
|
||||||
const COLOR_FACET_A := Color(0.55, 0.55, 0.48)
|
const COLOR_FACET_A := Color(0.55, 0.55, 0.48)
|
||||||
const COLOR_FACET_B := Color(0.49, 0.49, 0.43)
|
const COLOR_FACET_B := Color(0.49, 0.49, 0.43)
|
||||||
const COLOR_PAVILION := Color(0.30, 0.28, 0.26)
|
const COLOR_PAVILION := Color(0.30, 0.28, 0.26)
|
||||||
|
|
||||||
# Grenz-Props: gleiche Modelle + Skalierung + Hitbox-Maße wie in main.tscn
|
# Grenz-Props: gleiche Modelle + Skalierung + Hitbox-Maße wie in main.tscn.
|
||||||
const MOUNTAIN_SCENE := preload("res://assets/models/mountain.glb")
|
# "radius"/"height" sind die Shape-Maße VOR der Skalierung (wie im Original);
|
||||||
const MOUNTAIN2_SCENE := preload("res://assets/models/mountain2v5.glb")
|
# der reale Welt-Radius (für die Überlappungsprüfung) ist radius * scale.
|
||||||
const FOREST_SCENE := preload("res://assets/models/forest.glb")
|
const BORDER_KINDS := [
|
||||||
|
{
|
||||||
const MOUNTAIN_SCALE := 0.1
|
"scene": preload("res://assets/models/mountain.glb"),
|
||||||
const MOUNTAIN2_SCALE := 0.05
|
"scale": 0.1,
|
||||||
const FOREST_SCALE := 0.2
|
"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:
|
func _ready() -> void:
|
||||||
_build_crown()
|
_build_crown()
|
||||||
@ -135,48 +147,64 @@ func _build_region_borders() -> void:
|
|||||||
var a := _hex_angle(k)
|
var a := _hex_angle(k)
|
||||||
var dir := Vector3(cos(a), 0.0, sin(a))
|
var dir := Vector3(cos(a), 0.0, sin(a))
|
||||||
var perp := Vector3(-dir.z, 0.0, dir.x)
|
var perp := Vector3(-dir.z, 0.0, dir.x)
|
||||||
|
_build_ridge(borders, dir, perp)
|
||||||
|
|
||||||
var t := table_radius
|
# Kettet Props entlang eines Grats: jeder neue Prop wird so nah an den vorigen
|
||||||
while t <= girdle_radius:
|
# gesetzt, dass sich ihre Welt-Radien garantiert überlappen (Distanz zwischen
|
||||||
var frac := (t - table_radius) / (girdle_radius - table_radius)
|
# den Mittelpunkten <= (r1+r2) * border_overlap). Kein Rätselraten über feste
|
||||||
var pos := dir * t
|
# Abstände mehr — die Lücke wird pro Prop-Paar explizit geprüft und korrigiert.
|
||||||
pos.y = lerp(0.0, -crown_drop, frac)
|
func _build_ridge(borders: Node3D, dir: Vector3, perp: Vector3) -> void:
|
||||||
pos += perp * randf_range(-border_jitter, border_jitter)
|
var t := table_radius
|
||||||
|
var prev_pos := Vector3.ZERO
|
||||||
|
var prev_radius := 0.0
|
||||||
|
var first := true
|
||||||
|
|
||||||
_place_border_prop(borders, pos)
|
while t <= girdle_radius:
|
||||||
t += border_spacing + randf_range(-border_spacing * 0.25, border_spacing * 0.25)
|
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:
|
if not first:
|
||||||
var roll := randf()
|
var min_dist := (prev_radius + radius) * border_overlap
|
||||||
var inst: Node3D
|
# Iterativ korrigieren statt blind zu vertrauen: die Facette ist geneigt,
|
||||||
var scale_factor: float
|
# daher ist die Distanz nicht rein radial (Höhe hängt am Abstand mit dran).
|
||||||
var shape: Shape3D
|
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:
|
_spawn_border_prop(borders, kind, pos)
|
||||||
inst = MOUNTAIN_SCENE.instantiate() as Node3D
|
prev_pos = pos
|
||||||
scale_factor = MOUNTAIN_SCALE
|
prev_radius = radius
|
||||||
var cap := CapsuleShape3D.new()
|
first = false
|
||||||
cap.radius = 7.0
|
t += radius # nächster Kandidat startet grob hinter diesem Prop; obige Prüfung zieht ihn bei Bedarf näher
|
||||||
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
|
|
||||||
|
|
||||||
|
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.position = pos
|
||||||
inst.rotation.y = randf() * TAU
|
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()
|
var cs := CollisionShape3D.new()
|
||||||
cs.shape = shape
|
cs.shape = shape
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user