K1d t_6c1027c1: Startansicht 80 m über der Kartenmitte (Pos1 bleibt ganze Karte)
fit_to_document setzt die Ansicht jetzt auf START_DISTANCE_M = 80 m über der Kartenmitte (XZ-Mitte der Map-Bounds, Höhe = Gelände dort), yaw 45°, pitch 35°. Die Regel ist unabhängig von Kartengröße (256/1024/4096) und Inhaltslage; leeres Dokument ebenfalls 80 m. Statuszeile: „Ansicht: Kartenmitte, Abstand 80 m (Pos1 = ganze Karte).“ Pos1/Home bleibt fit_to_bounds = ganze Karte (MAP-Bounds werden weiterhin gemerkt, nicht die Content-Bounds). Ziel-Y bewusst auf der Geländehöhe: die y-Mitte der Bounds liegt bei neuen Maps 35 m über dem Boden — damit hätte die Ansicht über den Boden gezielt und die Objekte stünden am unteren Bildrand. Suite test_orbit_camera auf die neue Regel umgestellt (50 Checks): Startabstand/Richtung, 80-m-Regel über Größen und Inhaltslagen, leeres Dokument, „Pos1 ≠ Startansicht“ (80 m → 1215,6 m, alle 8 Map-Ecken). Dazu Renderprobe tests/probe_k1d_render.gd (2 PNGs).
This commit is contained in:
parent
85bee00f3d
commit
40449932b9
4 changed files with 260 additions and 108 deletions
|
|
@ -34,8 +34,11 @@ const SvmapIO := preload("res://src/svmap/svmap_io.gd")
|
|||
const ORBIT_DEG_PER_PX := 0.3 # Karte K1: Drehgeschwindigkeit gekapselt — kein Nachlaufen
|
||||
const DISTANCE_MIN := 5.0
|
||||
const FIT_FLOOR_MIN := 30.0 # fit_to_bounds: nie näher als 30 m an die Map ran
|
||||
const FIT_CONTENT_MIN := 40.0 # Karte K1b: Objekt-Ansicht nie näher als 40 m ran
|
||||
const FIT_CONTENT_MARGIN := 1.3 # Karte K1b: Kantenlänge × 1,3 Rand
|
||||
## Karte K1d (t_6c1027c1): Startabstand nach Laden/Neu — Slys Wahl („Nah dran:
|
||||
## startet ~80 m über der Kartenmitte, Objekte sind sofort groß und greifbar").
|
||||
const START_DISTANCE_M := 80.0
|
||||
const START_YAW_DEG := 45.0
|
||||
const START_PITCH_DEG := 35.0
|
||||
|
||||
var target := Vector3.ZERO
|
||||
var yaw_deg := 45.0
|
||||
|
|
@ -334,39 +337,44 @@ func fit_to_bounds(mn: Vector3, mx: Vector3) -> void:
|
|||
_apply()
|
||||
|
||||
|
||||
## Karte K1b (t_eca890c5): Startansicht auf den Karten-INHALT — Bounding-Box
|
||||
## aller Objekte + Bauteile + Spawnpunkte (Terrain zählt NICHT) bestimmt Mitte
|
||||
## und Abstand. Ohne Inhalt gilt wie bisher die ganze Karte. Pos1 (Home)
|
||||
## bleibt GANZE Karte: dafür werden die MAP-Bounds gemerkt, nicht die
|
||||
## Content-Bounds.
|
||||
## Karte K1d (t_6c1027c1, Neuauflage von K1c): Startansicht NAH am Karteninhalt.
|
||||
## Laden/Neu setzt die Kamera auf die KARTENMITTE (XZ-Mitte der Map-Bounds,
|
||||
## Höhe = Gelände dort) mit festem Abstand START_DISTANCE_M (80 m), yaw 45°,
|
||||
## pitch 35° — Objekte sind
|
||||
## sofort groß und greifbar (Slys Wahl), statt die 1-km-Fläche von weitem zu
|
||||
## zeigen. Die Regel ist bewusst UNABHÄNGIG von Größe/Lage des Inhalts: immer
|
||||
## 80 m. Pos1 (Home) bleibt die GANZE Karte — dafür werden hier die MAP-Bounds
|
||||
## gemerkt (NICHT die Content-Bounds), sodass fit_to_bounds danach die Karte
|
||||
## einpasst; Startansicht und Pos1 sind also zwei verschiedene Ansichten.
|
||||
func fit_to_document(doc: Dictionary, map_mn: Vector3, map_mx: Vector3) -> void:
|
||||
var content := content_bounds(doc)
|
||||
if int(content["count"]) == 0:
|
||||
fit_to_bounds(map_mn, map_mx)
|
||||
_status("Ansicht: ganze Karte — kein Inhalt (Objekte/Bauteile/Spawnpunkte) im Dokument.")
|
||||
return
|
||||
var mn := Vector3(minf(map_mn.x, map_mx.x), minf(map_mn.y, map_mx.y),
|
||||
minf(map_mn.z, map_mx.z))
|
||||
var mx := Vector3(maxf(map_mn.x, map_mx.x), maxf(map_mn.y, map_mx.y),
|
||||
maxf(map_mn.z, map_mx.z))
|
||||
_fly_held = false
|
||||
_fly_locked = false
|
||||
_flying = false
|
||||
# Pos1 = ganze Karte: MAP-Bounds merken (NICHT die Content-Bounds).
|
||||
_fit_bounds_mn = Vector3(minf(map_mn.x, map_mx.x), minf(map_mn.y, map_mx.y),
|
||||
minf(map_mn.z, map_mx.z))
|
||||
_fit_bounds_mx = Vector3(maxf(map_mn.x, map_mx.x), maxf(map_mn.y, map_mx.y),
|
||||
maxf(map_mn.z, map_mx.z))
|
||||
_fit_bounds_mn = mn
|
||||
_fit_bounds_mx = mx
|
||||
_fit_bounds_valid = true
|
||||
var cmn: Vector3 = content["mn"]
|
||||
var cmx: Vector3 = content["mx"]
|
||||
var span := cmx - cmn
|
||||
target = cmn + span * 0.5
|
||||
# Abstand laut Karte K1b: größte Content-Kantenlänge × 1,3, mindestens
|
||||
# 40 m, nie weiter als die GANZE Karte reicht (Karten-Fit-Abstand).
|
||||
var edge := maxf(span.x, maxf(span.y, span.z))
|
||||
var d_map := _fit_distance((map_mx - map_mn).length() * 0.5)
|
||||
distance = clampf(edge * FIT_CONTENT_MARGIN, FIT_CONTENT_MIN, d_map)
|
||||
yaw_deg = 45.0
|
||||
pitch_deg = 35.0
|
||||
# Kartenmitte in XZ — in y aber die GELÄNDE-Oberfläche, nicht die Mitte der
|
||||
# Bounds: die Bounds reichen bei neuen Maps von −10 bis 80 m, ihre Mitte
|
||||
# (35 m) liegt 35 m über dem Boden. Damit hätte die Ansicht 35 m über den
|
||||
# Boden gezielt und die Objekte stünden am unteren Bildrand.
|
||||
var mitte := mn + (mx - mn) * 0.5
|
||||
target = Vector3(mitte.x, SvmapDocument.terrain_height_at(doc, mitte.x, mitte.z), mitte.z)
|
||||
distance = clampf(START_DISTANCE_M, DISTANCE_MIN, distance_max())
|
||||
yaw_deg = START_YAW_DEG
|
||||
pitch_deg = START_PITCH_DEG
|
||||
_apply()
|
||||
_status("Ansicht: %s — Abstand %.0f m." % [_content_label(doc, int(content["count"])), distance])
|
||||
var count := int(content_bounds(doc)["count"])
|
||||
if count == 0:
|
||||
_status("Ansicht: Kartenmitte, Abstand %d m (kein Inhalt — Pos1 = ganze Karte)." %
|
||||
int(START_DISTANCE_M))
|
||||
else:
|
||||
_status("Ansicht: Kartenmitte, Abstand %d m (Pos1 = ganze Karte)." %
|
||||
int(START_DISTANCE_M))
|
||||
|
||||
|
||||
## Bounding-Box des Dokument-INHALTS: Objekte, Bauteile („gebaeude") und
|
||||
|
|
@ -407,21 +415,6 @@ static func _content_points(items: Array) -> Array:
|
|||
return pts
|
||||
|
||||
|
||||
## Klartext für die Statuszeile: welche Inhaltstypen die Ansicht bestimmt hat.
|
||||
static func _content_label(doc: Dictionary, count: int) -> String:
|
||||
var n_obj := _content_points(doc.get("objects", []) as Array).size()
|
||||
var n_bau := _content_points(doc.get("gebaeude", []) as Array).size()
|
||||
var n_spawn := count - n_obj - n_bau
|
||||
var teile: Array[String] = []
|
||||
if n_obj > 0:
|
||||
teile.append("%d Objekte" % n_obj)
|
||||
if n_bau > 0:
|
||||
teile.append("%d Bauteile" % n_bau)
|
||||
if n_spawn > 0:
|
||||
teile.append("%d Spawnpunkte" % n_spawn)
|
||||
return ", ".join(teile)
|
||||
|
||||
|
||||
## Abstand, damit die Umkugel (Radius um den Ziel-Punkt) komplett sichtbar
|
||||
## bleibt: d = R / sin(halber FOV der SCHMALEREN Bildachse) + 2 % Reserve.
|
||||
func _fit_distance(radius: float) -> float:
|
||||
|
|
|
|||
126
tests/probe_k1d_render.gd
Normal file
126
tests/probe_k1d_render.gd
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
extends SceneTree
|
||||
## RENDER-PROBE Karte t_6c1027c1 (K1d: Startansicht = Kartenmitte, 80 m) — 2 Bilder 1600×900:
|
||||
## (a) Karte „frisch geladen“ (main._refresh_all → fit_to_document): Blick aus
|
||||
## 80 m auf die Kartenmitte, Lager im Nahbereich groß im Bild
|
||||
## (b) Pos1 (Home) → GANZE Karte im Bild (Startansicht ist bewusst NICHT Pos1)
|
||||
## Ausführen (KEIN --headless, sonst rendert nichts):
|
||||
## xvfb-run -a /opt/data/home/bin/godot4 --path . --rendering-driver opengl3 -s res://tests/probe_k1d_render.gd
|
||||
## PNGs landen in /opt/data/scopedvr/work/k1d_bilder/ (Scratch-Ordner stirbt nach der Karte).
|
||||
## Echte GLB-Modelle: die Probe braucht ein Mesh-Lager als Sibling
|
||||
## (../scopedvr-client/assets/objekte) — ohne das zeigen die Marker Platzhalter.
|
||||
|
||||
const OUT_DIR := "/opt/data/scopedvr/work/k1d_bilder/"
|
||||
|
||||
var main: Control
|
||||
var camera: Camera3D
|
||||
var subvp: SubViewport
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
_run.call_deferred()
|
||||
|
||||
|
||||
func _shot(name: String) -> void:
|
||||
await process_frame
|
||||
await RenderingServer.frame_post_draw
|
||||
var img := subvp.get_texture().get_image()
|
||||
var path := OUT_DIR + name
|
||||
var err := img.save_png(path)
|
||||
print("PROBE_PNG=%s (%dx%d, err=%d)" % [path, img.get_width(), img.get_height(), err])
|
||||
|
||||
|
||||
func _find_subvp(node: Node) -> SubViewport:
|
||||
if node is SubViewport:
|
||||
return node as SubViewport
|
||||
for c in node.get_children():
|
||||
var f := _find_subvp(c)
|
||||
if f != null:
|
||||
return f
|
||||
return null
|
||||
|
||||
|
||||
func _key(code: Key) -> void:
|
||||
var ev := InputEventKey.new()
|
||||
ev.keycode = code
|
||||
ev.physical_keycode = code
|
||||
ev.pressed = true
|
||||
subvp.push_input(ev)
|
||||
|
||||
|
||||
## Lager um die Kartenmitte: Gebäude/Container/Fässer im Nahbereich (das soll
|
||||
## die 80-m-Ansicht „groß und greifbar“ zeigen) + ein paar Objekte weiter
|
||||
## draußen für den Karten-Kontext.
|
||||
func _lager_doc() -> Dictionary:
|
||||
var doc: Dictionary = SvmapDocument.create_new("K1d-Beweis", 1024.0, "mittag")
|
||||
doc["objects"] = [
|
||||
{"id": "barracks_001", "pos": [-16.0, 0.0, -8.0], "scale": 1.0, "rot_y_deg": 20.0},
|
||||
{"id": "building_001", "pos": [14.0, 0.0, -14.0], "scale": 1.0, "rot_y_deg": -35.0},
|
||||
{"id": "container_001", "pos": [3.0, 0.0, 7.0], "scale": 1.0, "rot_y_deg": 90.0},
|
||||
{"id": "container_004", "pos": [7.5, 0.0, 7.0], "scale": 1.0, "rot_y_deg": 90.0},
|
||||
{"id": "barrel_001", "pos": [-4.0, 0.0, -2.0], "scale": 1.0, "rot_y_deg": 0.0},
|
||||
{"id": "barrel_003", "pos": [-3.0, 0.0, -4.0], "scale": 1.0, "rot_y_deg": 0.0},
|
||||
{"id": "barrel_005", "pos": [-4.6, 0.0, -4.6], "scale": 1.0, "rot_y_deg": 0.0},
|
||||
{"id": "barrier_001", "pos": [10.0, 0.0, 22.0], "scale": 1.0, "rot_y_deg": 0.0},
|
||||
{"id": "apc_001", "pos": [22.0, 0.0, 6.0], "scale": 1.0, "rot_y_deg": 140.0},
|
||||
{"id": "air_defense_001", "pos": [-24.0, 0.0, 16.0], "scale": 1.0, "rot_y_deg": 70.0},
|
||||
{"id": "container_007", "pos": [-80.0, 0.0, -90.0], "scale": 1.0, "rot_y_deg": 15.0},
|
||||
{"id": "barracks_001", "pos": [130.0, 0.0, 90.0], "scale": 1.0, "rot_y_deg": -60.0},
|
||||
]
|
||||
return doc
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var scene: PackedScene = load("res://scenes/main.tscn")
|
||||
main = scene.instantiate()
|
||||
root.add_child(main)
|
||||
await process_frame
|
||||
await process_frame
|
||||
camera = main.get("camera")
|
||||
subvp = _find_subvp(main)
|
||||
var container: SubViewportContainer = subvp.get_parent()
|
||||
container.stretch = false
|
||||
subvp.size = Vector2i(1600, 900)
|
||||
await process_frame
|
||||
|
||||
var doc := _lager_doc()
|
||||
main.set("doc", doc)
|
||||
main.call("_refresh_all")
|
||||
await process_frame
|
||||
await process_frame
|
||||
|
||||
var target: Vector3 = camera.get("target")
|
||||
print("ZAHLEN (a) target=", target, " distance=", camera.get("distance"),
|
||||
" yaw=", camera.get("yaw_deg"), " pitch=", camera.get("pitch_deg"))
|
||||
print("ZAHLEN (a) Kamera-Position=", camera.position,
|
||||
" Höhe über Kartenmitte=", camera.position.y - target.y)
|
||||
print("ZAHLEN (a) Status: ", main.status_label.text)
|
||||
# Wie viele Objekt-Marker liegen wirklich im Bild?
|
||||
var builder = main.get("view_builder")
|
||||
var markers: Array = builder.get("object_markers")
|
||||
var drin := 0
|
||||
var rect: Rect2 = Rect2(Vector2.ZERO, Vector2(subvp.size))
|
||||
for m in markers:
|
||||
if not (m is Node3D) or not is_instance_valid(m):
|
||||
continue # unbekannte Objekt-ID → kein Marker (Platzhalter-Zweig)
|
||||
var mp: Vector3 = (m as Node3D).global_position
|
||||
var p := camera.unproject_position(mp)
|
||||
if rect.has_point(p) and camera.is_position_in_frustum(mp):
|
||||
drin += 1
|
||||
print("ZAHLEN (a) Objekt-Marker im Bild: %d / %d" % [drin, markers.size()])
|
||||
await _shot("k1d_a_startansicht_80m.png")
|
||||
|
||||
_key(KEY_HOME)
|
||||
await process_frame
|
||||
await process_frame
|
||||
print("ZAHLEN (b) nach Pos1: target=", camera.get("target"),
|
||||
" distance=", camera.get("distance"), " Status: ", main.status_label.text)
|
||||
var ecken := 0
|
||||
for x in [-512.0, 512.0]:
|
||||
for y in [-10.0, 80.0]:
|
||||
for z in [-512.0, 512.0]:
|
||||
if camera.is_position_in_frustum(Vector3(x, y, z)):
|
||||
ecken += 1
|
||||
print("ZAHLEN (b) Map-Ecken im Frustum: %d/8" % ecken)
|
||||
await _shot("k1d_b_pos1_ganze_karte.png")
|
||||
print("PROBE_FERTIG")
|
||||
quit(0)
|
||||
1
tests/probe_k1d_render.gd.uid
Normal file
1
tests/probe_k1d_render.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://caesddjxc0ur3
|
||||
|
|
@ -1,15 +1,17 @@
|
|||
extends SceneTree
|
||||
## Editor-Test (headless): Karte t_950fddaa (K1) — Kamera-Drehen fixen —
|
||||
## erweitert um Karte t_eca890c5 (K1b): Startansicht auf den Karten-INHALT.
|
||||
## erweitert um K1b (t_eca890c5, Inhalts-Ansicht) und K1d (t_6c1027c1).
|
||||
## Ausführen: godot4 --headless --path . --script res://tests/test_orbit_camera.gd
|
||||
## Exit 0 = alles grün.
|
||||
##
|
||||
## Instanziert die ECHTE Main-UI (wie test_rmb_vorrang) und prüft:
|
||||
## 1. STARTANSICHT (K1b): Laden/Neu → fit_to_document — Bounding-Box aus
|
||||
## Objekten + Bauteilen + Spawnpunkten (Terrain NICHT), Mitte/Abstand
|
||||
## laut Regel (Kantenlänge × 1,3, min 40 m, nie weiter als die ganze
|
||||
## Karte), Klartext-Statuszeile; leeres Dokument → ganze Karte;
|
||||
## 256-m- und 1024-m-Map; Mindestabstand; Karten-Deckel.
|
||||
## 1. STARTANSICHT (K1d): Laden/Neu → fit_to_document — Kamera auf der
|
||||
## KARTENMITTE (Mitte der Map-Bounds) mit START_DISTANCE_M = 80 m,
|
||||
## yaw 45°, pitch 35°; Statuszeile „Ansicht: Kartenmitte, Abstand 80 m
|
||||
## (Pos1 = ganze Karte).“; Startansicht ≠ ganze Karte (Ecken NICHT alle
|
||||
## im Frustum). Regel gilt unabhängig von Kartengröße (256/1024/4096)
|
||||
## und Inhaltslage (Objekte am Rand ändern nichts); leeres Dokument
|
||||
## ebenfalls 80 m (mit „kein Inhalt“ im Status).
|
||||
## 2. KEIN SPRUNG: MMT-Druck ändert Position/Basis NICHT (1e-4), aber
|
||||
## target = Trefferpunkt; Projektion des target ≈ Cursor (1 px).
|
||||
## Vergleichszeile dokumentiert, wie weit das ALTE Verhalten weg war.
|
||||
|
|
@ -75,7 +77,7 @@ func _run_all() -> void:
|
|||
_check(subvp.size == Vector2i(1600, 900), "SubViewport auf 1600×900 (headless)")
|
||||
|
||||
# Test-Map anlegen (gleicher Pfad wie „Neue Map“): _refresh_all löst
|
||||
# fit_to_document in _refresh_viewport aus (Karte K1b — Inhalts-Ansicht).
|
||||
# fit_to_document in _refresh_viewport aus (Karte K1d — 80 m auf Kartenmitte).
|
||||
var doc: Dictionary = SvmapDocument.create_new("K1-Test", 1024.0, "mittag")
|
||||
doc["objects"] = [
|
||||
{"id": "crate_wood", "pos": [0.0, 0.0, 0.0], "scale": 1.0, "rot_y_deg": 0.0},
|
||||
|
|
@ -100,41 +102,51 @@ func _run_all() -> void:
|
|||
_finish()
|
||||
|
||||
|
||||
# ------------------------------------------------- 1. STARTANSICHT (K1b) ---
|
||||
# ------------------------------------------------- 1. STARTANSICHT (K1d) ---
|
||||
func _test_start_view() -> void:
|
||||
print("Testgruppe: Startansicht = Karten-INHALT (K1b, Laden/Neu)")
|
||||
print("Testgruppe: Startansicht = Kartenmitte, 80 m (K1d, Laden/Neu)")
|
||||
var target: Vector3 = camera.get("target")
|
||||
var dist: float = float(camera.get("distance"))
|
||||
# Dokument: 2 Kisten (0,0,0) + (450,0,450) und 8 Default-Spawnpunkte
|
||||
# (x ±409,6 / z ±307,2 … / y 0,2) → BBox x −409,6…450, y 0…0,2, z −307,2…450.
|
||||
# Mitte (20,2 / 0,1 / 71,4); größte Kante 859,6 × 1,3 = 1117,48; der
|
||||
# Karten-Deckel (Umkugel 1024er Map = 1215,6) wird NICHT erreicht.
|
||||
_check(target.distance_to(Vector3(20.2, 0.1, 71.4)) < 0.01,
|
||||
"target = Mitte der Inhalts-BBox (20,2 / 0,1 / 71,4) — Objekte+Spawns, NICHT Kartemitte")
|
||||
_check(absf(dist - 1117.48) < 0.5,
|
||||
"distance = Kantenlänge × 1,3 (859,6 × 1,3 = 1117,5 m): %.1f m" % dist)
|
||||
_check(dist < 1215.56,
|
||||
"Deckel greift nicht (Karte kleiner als ganze Map): %.1f < 1215,6 m" % dist)
|
||||
# 1024er Map: Map-Bounds min (−512, −10, −512), max (512, 80, 512)
|
||||
# → XZ-Mitte (0, 0); Höhe = Gelände dort (neue Map: 0) — NICHT die
|
||||
# y-Mitte der Bounds (35 m), sonst zielt die Ansicht in die Luft.
|
||||
# Inhalt (2 Kisten + 8 Default-Spawns) ist für die Startansicht seit K1d
|
||||
# IRRELEVANT — Sly will 80 m, nicht 1 km.
|
||||
_check(target.distance_to(Vector3(0.0, 0.0, 0.0)) < 1e-4,
|
||||
"target = Kartenmitte auf dem Gelände (0 / 0 / 0) — NICHT Content-Mitte, NICHT Bounds-Mitte (35 m)")
|
||||
_check(absf(dist - 80.0) < 1e-4,
|
||||
"distance = START_DISTANCE_M = 80 m (%.4f) — Slys Wahl „nah dran“" % dist)
|
||||
_check(absf(float(camera.get("yaw_deg")) - 45.0) < 1e-4, "yaw = 45 Grad")
|
||||
_check(absf(float(camera.get("pitch_deg")) - 35.0) < 1e-4, "pitch = 35 Grad")
|
||||
# „80 m über der Kartenmitte“: Blickachse 35° nach unten, also liegt die
|
||||
# Kamera 80 × sin(35°) = 45,9 m über der Kartenmitte.
|
||||
var hub := camera.position.y - target.y
|
||||
_check(absf(hub - 80.0 * sin(deg_to_rad(35.0))) < 1e-3,
|
||||
"Kamera %.2f m über der Kartenmitte (= 80 × sin 35°)" % hub)
|
||||
_check(absf(camera.position.distance_to(target) - 80.0) < 1e-3,
|
||||
"Kamera-Abstand zum Drehpunkt = 80 m (%.4f)" % camera.position.distance_to(target))
|
||||
var status: String = str((main.get("status_label") as Label).text)
|
||||
_check(status.contains("Ansicht:") and status.contains("2 Objekte")
|
||||
and status.contains("8 Spawnpunkte") and status.contains("1117"),
|
||||
_check(status.contains("Ansicht: Kartenmitte") and status.contains("Abstand 80 m")
|
||||
and status.contains("Pos1 = ganze Karte"),
|
||||
"Statuszeile Klartext: „%s“" % status)
|
||||
var alle_drin := true
|
||||
for c in _corners(Vector3(-409.6, 0.0, -307.2), Vector3(450.0, 0.2, 450.0)):
|
||||
if not camera.is_position_in_frustum(c):
|
||||
alle_drin = false
|
||||
_check(alle_drin, "alle 8 Inhalts-Ecken im Frustum")
|
||||
# Startansicht ist NAH dran → die 1-km-Karte passt NICHT mehr ins Bild.
|
||||
var ecken_drin := 0
|
||||
for c in _corners(Vector3(-512.0, -10.0, -512.0), Vector3(512.0, 80.0, 512.0)):
|
||||
if camera.is_position_in_frustum(c):
|
||||
ecken_drin += 1
|
||||
_check(ecken_drin < 8,
|
||||
"Startansicht ≠ ganze Karte: nur %d/8 Map-Ecken im Frustum (Pos1 zeigt alle 8)" % ecken_drin)
|
||||
|
||||
|
||||
# --------------------------------- 1b. INHALTS-REGELN: Größen/leer (K1b) ---
|
||||
# ------------------------- 1b. 80-m-REGEL: Größe/Inhaltslage/leer (K1d) ---
|
||||
func _test_content_rules() -> void:
|
||||
print("Testgruppe: Inhalts-Ansicht — Regeln (256 m, leer, Min/Deckel)")
|
||||
print("Testgruppe: 80-m-Regel — unabhängig von Größe und Inhaltslage (K1d)")
|
||||
var mn256 := Vector3(-128.0, -10.0, -128.0)
|
||||
var mx256 := Vector3(128.0, 80.0, 128.0)
|
||||
# (a) 256-m-Map, keine Spawns, 3 Objekte → BBox 40×20×15, Kante 40 × 1,3 = 52 m
|
||||
var doc256: Dictionary = SvmapDocument.create_new("K1b-256", 256.0, "mittag")
|
||||
var KM := Vector3(0.0, 0.0, 0.0) # Kartenmitte (XZ 0/0, Geländehöhe 0)
|
||||
# (a) 256-m-Map, KEINE Spawns, 3 Objekte um x 0…40 → trotzdem 80 m
|
||||
# auf der KARTENMITTE (Inhalt zieht die Ansicht NICHT mehr weg).
|
||||
var doc256: Dictionary = SvmapDocument.create_new("K1d-256", 256.0, "mittag")
|
||||
doc256["spawn_zones"] = {}
|
||||
doc256["objects"] = [
|
||||
{"id": "crate_wood", "pos": [0.0, 10.0, 0.0], "scale": 1.0, "rot_y_deg": 0.0},
|
||||
|
|
@ -144,53 +156,57 @@ func _test_content_rules() -> void:
|
|||
camera.call("fit_to_document", doc256, mn256, mx256)
|
||||
var target: Vector3 = camera.get("target")
|
||||
var dist: float = float(camera.get("distance"))
|
||||
_check(target.distance_to(Vector3(20.0, 20.0, -2.5)) < 0.01,
|
||||
"256-m-Map: target = Content-Mitte (20 / 20 / −2,5)")
|
||||
_check(absf(dist - 52.0) < 0.01,
|
||||
"256-m-Map: distance = 40 × 1,3 = 52 m (%.2f) — Terrain zählt NICHT (sonst 312,5)" % dist)
|
||||
_check(String((main.get("status_label") as Label).text).contains("3 Objekte"),
|
||||
"256-m-Map: Status meldet „3 Objekte“")
|
||||
var alle_drin := true
|
||||
for c in _corners(Vector3(0.0, 10.0, -10.0), Vector3(40.0, 30.0, 5.0)):
|
||||
if not camera.is_position_in_frustum(c):
|
||||
alle_drin = false
|
||||
_check(alle_drin, "256-m-Map: alle 8 Inhalts-Ecken im Frustum")
|
||||
_check(target.distance_to(KM) < 1e-4,
|
||||
"256-m-Map: target = Kartenmitte (0 / 0 / 0), nicht Content-Mitte (20 / 20 / −2,5)")
|
||||
_check(absf(dist - 80.0) < 1e-4, "256-m-Map: distance = 80 m (%.2f)" % dist)
|
||||
|
||||
# (b) Deckel: Content diagonal über die Karte → nie weiter als die ganze Karte
|
||||
# (b) Inhalt diagonal über die GANZE Karte → immer noch 80 m (kein Deckel
|
||||
# mehr, keine Kantenlänge × 1,3).
|
||||
doc256["objects"] = [
|
||||
{"id": "crate_wood", "pos": [-128.0, 0.0, -128.0], "scale": 1.0, "rot_y_deg": 0.0},
|
||||
{"id": "crate_wood", "pos": [128.0, 0.0, 128.0], "scale": 1.0, "rot_y_deg": 0.0},
|
||||
]
|
||||
camera.call("fit_to_document", doc256, mn256, mx256)
|
||||
dist = float(camera.get("distance"))
|
||||
var d_map := Vector3(256.0, 90.0, 256.0).length() * 0.5 / sin(deg_to_rad(37.5)) * 1.02
|
||||
_check(absf(dist - d_map) < 0.5,
|
||||
"Deckel: distance = Karten-Fit (%.1f m), Kante×1,3 (332,8 m) wird GESCHNITTEN" % dist)
|
||||
_check(absf(dist - 80.0) < 1e-4,
|
||||
"Inhalt über die ganze Karte: trotzdem 80 m (%.2f) — keine Kanten-Regel mehr" % dist)
|
||||
|
||||
# (c) Mindestabstand: einzelnes Objekt (Kante 0) → 40 m
|
||||
# (c) einzelnes Objekt abseits der Mitte → 80 m auf der KARTENMITTE
|
||||
doc256["objects"] = [
|
||||
{"id": "crate_wood", "pos": [10.0, 5.0, 10.0], "scale": 1.0, "rot_y_deg": 0.0},
|
||||
]
|
||||
camera.call("fit_to_document", doc256, mn256, mx256)
|
||||
target = camera.get("target")
|
||||
dist = float(camera.get("distance"))
|
||||
_check(target.distance_to(Vector3(10.0, 5.0, 10.0)) < 0.01,
|
||||
"1 Objekt: target = Objekt-Position")
|
||||
_check(absf(dist - 40.0) < 0.01,
|
||||
"Mindestabstand: 1 Objekt → 40 m statt 0 × 1,3 = 0 (%.2f)" % dist)
|
||||
_check(target.distance_to(KM) < 1e-4, "1 Objekt: target bleibt Kartenmitte")
|
||||
_check(absf(dist - 80.0) < 1e-4, "1 Objekt: distance = 80 m (%.2f) — nicht 40 m" % dist)
|
||||
|
||||
# (d) leeres Dokument (ohne Default-Spawns!) → wie bisher: Kartenmitte, ganze Karte
|
||||
var doc_leer: Dictionary = SvmapDocument.create_new("K1b-leer", 256.0, "mittag")
|
||||
# (d) leeres Dokument (ohne Default-Spawns!) → ebenfalls 80 m + Klartext
|
||||
var doc_leer: Dictionary = SvmapDocument.create_new("K1d-leer", 256.0, "mittag")
|
||||
doc_leer["spawn_zones"] = {}
|
||||
camera.call("fit_to_document", doc_leer, mn256, mx256)
|
||||
target = camera.get("target")
|
||||
dist = float(camera.get("distance"))
|
||||
_check(target.distance_to(Vector3(0.0, 35.0, 0.0)) < 0.01,
|
||||
"leeres Dokument: target = Kartemitte (0, 35, 0)")
|
||||
_check(absf(dist - d_map) < 0.5,
|
||||
"leeres Dokument: distance = Umkugel der GANZEN Karte (%.1f m)" % dist)
|
||||
_check(String((main.get("status_label") as Label).text).contains("ganze Karte"),
|
||||
"leeres Dokument: Status meldet „ganze Karte“")
|
||||
_check(target.distance_to(KM) < 1e-4, "leeres Dokument: target = Kartenmitte")
|
||||
_check(absf(dist - 80.0) < 1e-4, "leeres Dokument: distance = 80 m (%.2f)" % dist)
|
||||
var st_leer: String = str((main.get("status_label") as Label).text)
|
||||
_check(st_leer.contains("kein Inhalt") and st_leer.contains("Pos1 = ganze Karte"),
|
||||
"leeres Dokument: Status „%s“" % st_leer)
|
||||
|
||||
# (e) große Karte (4096 m) → auch 80 m (Regel skaliert NICHT mit der Karte)
|
||||
var mn4k := Vector3(-2048.0, -10.0, -2048.0)
|
||||
var mx4k := Vector3(2048.0, 80.0, 2048.0)
|
||||
var doc4k: Dictionary = SvmapDocument.create_new("K1d-4096", 4096.0, "mittag")
|
||||
camera.call("fit_to_document", doc4k, mn4k, mx4k)
|
||||
target = camera.get("target")
|
||||
dist = float(camera.get("distance"))
|
||||
_check(target.distance_to(KM) < 1e-4 and absf(dist - 80.0) < 1e-4,
|
||||
"4096-m-Map: ebenfalls Kartenmitte + 80 m (%.2f)" % dist)
|
||||
|
||||
# Test-Dokument der Suite wiederherstellen (Folgegruppen erwarten es).
|
||||
main.set("doc", base_doc)
|
||||
main.call("_refresh_all")
|
||||
await process_frame
|
||||
|
||||
|
||||
# ------------------------------------------ 1c. DREHPUNKT AUF TERRAIN (K1b)
|
||||
|
|
@ -260,17 +276,22 @@ func _test_terrain_pivot() -> void:
|
|||
func _test_pivot_no_jump() -> void:
|
||||
print("Testgruppe: Drehpunkt unter dem Mauszeiger (kein Sprung)")
|
||||
var markers: Array = view_builder.get("object_markers")
|
||||
var marker: Node3D = markers[1] # Objekt WEIT abseits der Kartenmitte
|
||||
# K1d: Die Startansicht steht 80 m über der Kartenmitte — sichtbar ist der
|
||||
# Nahbereich, also der Marker an (0 / 0 / 0) (der zweite liegt bei
|
||||
# 450 / 450 und wäre außerhalb des Bildes; ein Klick dort träfe nichts).
|
||||
var marker: Node3D = markers[0]
|
||||
_check(marker != null and is_instance_valid(marker),
|
||||
"Objekt-Marker vorhanden (crate_wood bei 450, 450)")
|
||||
"Objekt-Marker vorhanden (crate_wood bei 0, 0)")
|
||||
if marker == null:
|
||||
return
|
||||
var center: Vector3 = marker.global_position \
|
||||
+ Vector3.UP * float(marker.get_meta("half_height", 0.0))
|
||||
var press: Vector2 = camera.unproject_position(center)
|
||||
# Altes Verhalten dokumentieren: Drehpunkt war der Fit-Ziel-Punkt — seine
|
||||
# Projektion lag weit weg vom Cursor.
|
||||
var alt_target: Vector3 = Vector3(0.0, 35.0, 0.0)
|
||||
_check(subvp.get_visible_rect().has_point(press),
|
||||
"Objekt liegt in der Startansicht im Bild (Cursor %.0f / %.0f)" % [press.x, press.y])
|
||||
# Altes Verhalten dokumentieren: Drehpunkt war der (35 m höhere) Ziel-Punkt
|
||||
# der Ansicht — seine Projektion lag weit weg vom Cursor.
|
||||
var alt_target: Vector3 = camera.get("target")
|
||||
var alt_px: Vector2 = camera.unproject_position(alt_target)
|
||||
print(" ALT (Vergleich, alter Code): Drehpunkt-Projektion lag %.0f px vom Cursor entfernt" % alt_px.distance_to(press))
|
||||
|
||||
|
|
@ -357,7 +378,11 @@ func _test_fly_transition() -> void:
|
|||
|
||||
# ------------------------------------------------------------- 5. POS1 -----
|
||||
func _test_home_key() -> void:
|
||||
print("Testgruppe: Pos1 = ganze Karte ins Bild")
|
||||
print("Testgruppe: Pos1 = ganze Karte ins Bild (≠ Startansicht, K1d)")
|
||||
# Vorher bewusst in die Startansicht (80 m) zurück — Pos1 muss sie verlassen.
|
||||
camera.call("fit_to_document", base_doc, Vector3(-512.0, -10.0, -512.0),
|
||||
Vector3(512.0, 80.0, 512.0))
|
||||
var dist_start: float = float(camera.get("distance"))
|
||||
var key := InputEventKey.new()
|
||||
key.keycode = KEY_HOME
|
||||
key.physical_keycode = KEY_HOME
|
||||
|
|
@ -367,8 +392,15 @@ func _test_home_key() -> void:
|
|||
var target: Vector3 = camera.get("target")
|
||||
_check(target.distance_to(Vector3(0.0, 35.0, 0.0)) < 1e-4, "Pos1: target = Kartemitte")
|
||||
var radius := Vector3(1024.0, 90.0, 1024.0).length() * 0.5
|
||||
_check(float(camera.get("distance")) >= radius / sin(deg_to_rad(37.5)) - 1.0,
|
||||
var dist_home: float = float(camera.get("distance"))
|
||||
_check(dist_home >= radius / sin(deg_to_rad(37.5)) - 1.0,
|
||||
"Pos1: distance = Umkugel-Formel (ganze Map sichtbar)")
|
||||
# K1d: Startansicht (80 m) und Pos1 sind ZWEI verschiedene Ansichten —
|
||||
# Pos1 zieht bewusst wieder weit heraus.
|
||||
_check(absf(dist_start - 80.0) < 1e-4 and dist_home > dist_start * 2.0,
|
||||
"Pos1 ≠ Startansicht: 80 m → %.1f m (Faktor %.1f)" % [dist_home, dist_home / maxf(dist_start, 0.001)])
|
||||
_check(str((main.get("status_label") as Label).text).contains("ganze Karte"),
|
||||
"Pos1: Status meldet „ganze Karte“ („%s“)" % str((main.get("status_label") as Label).text))
|
||||
_check(absf(float(camera.get("yaw_deg")) - 45.0) < 1e-4, "Pos1: yaw = 45 Grad")
|
||||
_check(absf(float(camera.get("pitch_deg")) - 35.0) < 1e-4, "Pos1: pitch = 35 Grad")
|
||||
var alle_drin := true
|
||||
|
|
|
|||
Loading…
Reference in a new issue