Playtest-Kollision: echter Registry-Fußabdruck statt Pick-Radius-Kugel
Sly-Befund 20.10.: Im Editor-Testmodus hing man an unsichtbaren Wänden weit VOR den Gebäuden. Ursache: _collect_obstacles nutzte die Viewport-Metas pick_radius (Kugel-Näherung = volle Raumdiagonale + Zuschlag) — bei der Kaserne (17×35 m) blockte das ~40×40 m. Die Trimesh-Fixes aus t_9558038d wirken nur im Spiel-Client, der Editor-Playtest baut seine Blockierung selbst (keine Physik-Welt, nur AABBs). Fix: Hindernis-AABB aus der Registry (collision.size × scale, um rot_y_deg konservativ gedreht, Y vom Fußpunkt bis Fußpunkt + Höhe — gleiche Konvention wie der Client-Kollider). Fallback: Kugel-Näherung bleibt für Objekte ohne Registry-Eintrag. Regression in test_playtest.gd: gedreht/ungedreht, Scale, Fallback (20 checks, alle grün). Suiten: playtest_comfort 51, playtest_spawn 56, resize_dialog 44, terrain 153 — ALL OK.
This commit is contained in:
parent
1f1a413065
commit
fabbbcce20
2 changed files with 134 additions and 3 deletions
|
|
@ -145,16 +145,25 @@ func _spawn_start(doc: Dictionary) -> Vector3:
|
||||||
|
|
||||||
|
|
||||||
## Hindernis-AABBs sammeln (nur LESEND, keine neue Physik-Welt):
|
## Hindernis-AABBs sammeln (nur LESEND, keine neue Physik-Welt):
|
||||||
## - Objekt-Marker: die VORHANDENEN Viewport-Metas — pick_radius (x/z-
|
## - Objekt-Marker: der ECHTE Fußabdruck aus der Registry (collision.size ×
|
||||||
## Halbausdehnung, Kugel-Approximation konservativ) + half_height
|
## scale, um rot_y_deg konservativ gedreht — Sly-Befund 20.10.: die alte
|
||||||
## (Center-Höhe über dem Fußpunkt → Objekt-Top = 2× half_height).
|
## pick_radius-Kugel-Näherung blockte ~40×40 m um eine 17×35 m-Kaserne =
|
||||||
|
## „unsichtbare Wand“ weit vor dem Modell).
|
||||||
## - Bau-Teile: exakt über BuildOps.piece_aabb (Dokument-Teile → AABB).
|
## - Bau-Teile: exakt über BuildOps.piece_aabb (Dokument-Teile → AABB).
|
||||||
|
## - Fallback: Objekte ohne Registry-Eintrag behalten die alte Kugel-Näherung.
|
||||||
func _collect_obstacles(doc: Dictionary, view_builder: Node3D) -> Array:
|
func _collect_obstacles(doc: Dictionary, view_builder: Node3D) -> Array:
|
||||||
var out: Array = []
|
var out: Array = []
|
||||||
|
var lib := _lade_objekt_registry()
|
||||||
if view_builder != null:
|
if view_builder != null:
|
||||||
for child in view_builder.get_children():
|
for child in view_builder.get_children():
|
||||||
if child is Node3D and (child as Node3D).has_meta("obj_index"):
|
if child is Node3D and (child as Node3D).has_meta("obj_index"):
|
||||||
var m := child as Node3D
|
var m := child as Node3D
|
||||||
|
var idx := int(m.get_meta("obj_index", -1))
|
||||||
|
var aabb_v: Variant = _objekt_aabb_aus_registry(doc, idx, lib, m)
|
||||||
|
if aabb_v != null:
|
||||||
|
out.append(aabb_v)
|
||||||
|
continue
|
||||||
|
# Fallback: alte Kugel-Näherung (Objekt fehlt in der Registry).
|
||||||
var r := float(m.get_meta("pick_radius", 1.0))
|
var r := float(m.get_meta("pick_radius", 1.0))
|
||||||
var ch := float(m.get_meta("half_height", 0.5))
|
var ch := float(m.get_meta("half_height", 0.5))
|
||||||
var p := m.position
|
var p := m.position
|
||||||
|
|
@ -170,6 +179,50 @@ func _collect_obstacles(doc: Dictionary, view_builder: Node3D) -> Array:
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
## objects.json einmal je Sammel-Lauf laden (klein genug, Playtest startet
|
||||||
|
## selten) — Key „objects“ → Dictionary oid → Entry.
|
||||||
|
func _lade_objekt_registry() -> Dictionary:
|
||||||
|
var parsed: Variant = JSON.parse_string(
|
||||||
|
FileAccess.get_file_as_string(SvmapIO.MANIFEST_PATH))
|
||||||
|
if parsed is Dictionary and (parsed as Dictionary).has("objects"):
|
||||||
|
return (parsed as Dictionary)["objects"]
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
## Rotierter Fußabdruck-AABB eines Objekts aus der Registry — null wenn keine
|
||||||
|
## belastbaren Daten da sind (Caller fällt auf die Kugel-Näherung zurück).
|
||||||
|
## Y läuft vom Fußpunkt (Marker-Position) bis Fußpunkt + size.y × scale —
|
||||||
|
## gleiche Konvention wie der Client-Kollider.
|
||||||
|
func _objekt_aabb_aus_registry(doc: Dictionary, idx: int, lib: Dictionary,
|
||||||
|
marker: Node3D) -> Variant:
|
||||||
|
var objects: Array = doc.get("objects", [])
|
||||||
|
if idx < 0 or idx >= objects.size() or not (objects[idx] is Dictionary):
|
||||||
|
return null
|
||||||
|
var obj: Dictionary = objects[idx]
|
||||||
|
var entry: Dictionary = lib.get(str(obj.get("id", "")), {})
|
||||||
|
if entry.is_empty():
|
||||||
|
return null
|
||||||
|
var col: Dictionary = entry.get("collision", {})
|
||||||
|
var size_v: Variant = col.get("size", null)
|
||||||
|
if not (size_v is Array) or (size_v as Array).size() != 3:
|
||||||
|
return null
|
||||||
|
var skala := float(obj.get("scale", 1.0))
|
||||||
|
var sy := float(size_v[1]) * skala
|
||||||
|
var hx := float(size_v[0]) * skala * 0.5
|
||||||
|
var hz := float(size_v[2]) * skala * 0.5
|
||||||
|
if hx <= 0.0 or hz <= 0.0:
|
||||||
|
return null
|
||||||
|
var p := marker.position
|
||||||
|
var rot := deg_to_rad(float(obj.get("rot_y_deg", 0.0)))
|
||||||
|
var c := cos(rot)
|
||||||
|
var s := sin(rot)
|
||||||
|
# 4 Grundflächen-Ecken rotieren → konservative xz-Ausdehnung (symmetrisch
|
||||||
|
# um die Position, da der Fußabdruck zentriert ist).
|
||||||
|
var ex := maxf(absf(hx * c + hz * s), absf(hx * c - hz * s))
|
||||||
|
var ez := maxf(absf(hx * s + hz * c), absf(hx * s - hz * c))
|
||||||
|
return AABB(Vector3(p.x - ex, p.y, p.z - ez), Vector3(ex * 2.0, sy, ez * 2.0))
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------- HUD ----
|
# ------------------------------------------------------------------- HUD ----
|
||||||
|
|
||||||
func _build_hud() -> void:
|
func _build_hud() -> void:
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ func _run_all() -> void:
|
||||||
_test_terrain_height_matches_document()
|
_test_terrain_height_matches_document()
|
||||||
_test_obstacle_blocks_player()
|
_test_obstacle_blocks_player()
|
||||||
_test_run_and_jump()
|
_test_run_and_jump()
|
||||||
|
_test_obstacles_use_registry_footprint()
|
||||||
_test_exit_restores_editor()
|
_test_exit_restores_editor()
|
||||||
|
|
||||||
_finish()
|
_finish()
|
||||||
|
|
@ -210,6 +211,83 @@ func _test_obstacle_blocks_player() -> void:
|
||||||
body.get("obstacles").erase(wall)
|
body.get("obstacles").erase(wall)
|
||||||
|
|
||||||
|
|
||||||
|
## --- Test 3b: Hindernisse = echter Registry-Fußabdruck (Sly 20.10.) ---------
|
||||||
|
## Die alte pick_radius-Kugel-Näherung blockte ~40×40 m um eine 17×35 m-Kaserne
|
||||||
|
## (= „unsichtbare Wand“ weit vor dem Modell). Jetzt: collision.size × scale,
|
||||||
|
## um rot_y_deg gedreht; Fallback Kugel-Näherung nur ohne Registry-Eintrag.
|
||||||
|
func _test_obstacles_use_registry_footprint() -> void:
|
||||||
|
print("Testgruppe: Hindernis-AABBs aus Registry-Fußabdruck")
|
||||||
|
var pt := _pt()
|
||||||
|
if pt == null:
|
||||||
|
_check(false, "PlaytestMode noch aktiv (Vorbedingung)")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Faken Viewport-Baukasten: 1 Marker mit obj_index + absurder Pick-Kugel.
|
||||||
|
var builder := Node3D.new()
|
||||||
|
var marker := Node3D.new()
|
||||||
|
marker.set_meta("obj_index", 0)
|
||||||
|
marker.set_meta("pick_radius", 20.3)
|
||||||
|
marker.set_meta("half_height", 3.1)
|
||||||
|
marker.position = Vector3(0.0, 0.0, -5.0)
|
||||||
|
builder.add_child(marker)
|
||||||
|
root.add_child(builder)
|
||||||
|
|
||||||
|
# Kaserne um 90° gedreht: lange Achse (35.5 m) liegt dann auf X.
|
||||||
|
var doc: Dictionary = {"objects": [{
|
||||||
|
"id": "barracks_001", "scale": 1.0, "rot_y_deg": 90.0, "pos": [0.0, 0.0, -5.0],
|
||||||
|
}]}
|
||||||
|
var obs: Array = pt.call("_collect_obstacles", doc, builder)
|
||||||
|
_check(obs.size() == 1, "Genau 1 Hindernis gesammelt (ist %d)" % obs.size())
|
||||||
|
if obs.size() == 1:
|
||||||
|
var ab: AABB = obs[0]
|
||||||
|
_check(ab.size.x < 38.0,
|
||||||
|
"X-Ausdehnung unter Pick-Kugel-Blob (ist %.1f < 38)" % ab.size.x)
|
||||||
|
_check(absf(ab.size.x - 35.456) < 1.2,
|
||||||
|
"Gedrehte Länge auf X ≈ 35.5 m (ist %.2f)" % ab.size.x)
|
||||||
|
_check(absf(ab.size.z - 16.99) < 1.2,
|
||||||
|
"Gedrehte Breite auf Z ≈ 17.0 m (ist %.2f)" % ab.size.z)
|
||||||
|
_check(absf(ab.size.y - 6.247) < 0.5,
|
||||||
|
"Höhe ≈ 6.2 m (ist %.2f)" % ab.size.y)
|
||||||
|
_check(absf(ab.position.y - 0.0) < 0.01, "Fußpunkt an Marker-Y")
|
||||||
|
|
||||||
|
# Rotation 0: Länge wieder auf Z.
|
||||||
|
var doc0: Dictionary = {"objects": [{
|
||||||
|
"id": "barracks_001", "scale": 1.0, "rot_y_deg": 0.0, "pos": [0.0, 0.0, -5.0],
|
||||||
|
}]}
|
||||||
|
var obs0: Array = pt.call("_collect_obstacles", doc0, builder)
|
||||||
|
if obs0.size() == 1:
|
||||||
|
var ab0: AABB = obs0[0]
|
||||||
|
_check(absf(ab0.size.z - 35.456) < 1.2,
|
||||||
|
"Rotation 0: Länge auf Z ≈ 35.5 m (ist %.2f)" % ab0.size.z)
|
||||||
|
else:
|
||||||
|
_check(false, "Rotation-0-Fall liefert 1 Hindernis")
|
||||||
|
|
||||||
|
# Scale wird mitgenommen (2× → doppelte Grundfläche).
|
||||||
|
var doc2: Dictionary = {"objects": [{
|
||||||
|
"id": "barracks_001", "scale": 2.0, "rot_y_deg": 0.0, "pos": [0.0, 0.0, -5.0],
|
||||||
|
}]}
|
||||||
|
var obs2: Array = pt.call("_collect_obstacles", doc2, builder)
|
||||||
|
if obs2.size() == 1:
|
||||||
|
_check(absf((obs2[0] as AABB).size.z - 70.9) < 2.4,
|
||||||
|
"Scale 2× verdoppelt die Länge (ist %.1f)" % (obs2[0] as AABB).size.z)
|
||||||
|
else:
|
||||||
|
_check(false, "Scale-Fall liefert 1 Hindernis")
|
||||||
|
|
||||||
|
# Fallback: unbekannte OID → alte Kugel-Näherung bleibt.
|
||||||
|
var docx: Dictionary = {"objects": [{
|
||||||
|
"id": "gibts_nicht_999", "scale": 1.0, "rot_y_deg": 0.0, "pos": [0.0, 0.0, -5.0],
|
||||||
|
}]}
|
||||||
|
var obsx: Array = pt.call("_collect_obstacles", docx, builder)
|
||||||
|
if obsx.size() == 1:
|
||||||
|
var abx: AABB = obsx[0]
|
||||||
|
_check(absf(abx.size.x - 40.6) < 0.2,
|
||||||
|
"Fallback: Kugel-Näherung 2×pick_radius (ist %.1f)" % abx.size.x)
|
||||||
|
else:
|
||||||
|
_check(false, "Fallback-Fall liefert 1 Hindernis")
|
||||||
|
|
||||||
|
builder.queue_free()
|
||||||
|
|
||||||
|
|
||||||
## --- Test 4: Rennen/Springen ------------------------------------------------
|
## --- Test 4: Rennen/Springen ------------------------------------------------
|
||||||
func _test_run_and_jump() -> void:
|
func _test_run_and_jump() -> void:
|
||||||
print("Testgruppe: Rennen/Springen wirkt korrekt")
|
print("Testgruppe: Rennen/Springen wirkt korrekt")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue