scopedvr-map-editor/tests/probe_d6_mapbuilder.gd
2026-11-01 19:34:05 +01:00

170 lines
6.3 KiB
GDScript

extends SceneTree
## D6-Beweis (t_31e800f2): Map Builder platziert 3 Fahrzeuge (Panzer + Panzer-Turm
## aufgesetzt + Transportpanzer) und zeigt die Kollisionsboxen als Drahtgitter
## (Groesse aus registry `collision.size` — die Deckungsprobe als Bild).
## Zusaetzlich aufgesetzt: die Panzerkanone, damit sichtbar ist, dass Turm und
## Kanone an der richtigen Stelle sitzen (Pack-Prefab-Hoehen 1,5929 m / 0,5344 m).
##
## xvfb-run -a /opt/data/home/bin/godot4 --rendering-driver opengl3 --path . \
## -s res://tests/probe_d6_mapbuilder.gd
##
## Ausgabe: user://probe_d6_mapbuilder.png + Pruefzeilen
const AUSGABE := "user://probe_d6_mapbuilder.png"
const TURM_RING_Y := 1.5928574
const KANONE_Y := TURM_RING_Y + 0.53440094
const KANONE_Z := 1.4340973
const PLATZ := [
{"id": "tank_001", "pos": [0.0, 0.0, 0.0], "rot_y_deg": 0.0, "aufsatz": false},
{"id": "tank_tower_001", "pos": [0.0, TURM_RING_Y, 0.0], "rot_y_deg": 0.0, "aufsatz": true},
{"id": "tank_gun_001", "pos": [0.0, KANONE_Y, KANONE_Z], "rot_y_deg": 0.0, "aufsatz": true},
{"id": "apc_001", "pos": [9.5, 0.0, 0.0], "rot_y_deg": 0.0, "aufsatz": false},
]
const MapDocument := preload("res://core/svmap/map_document.gd")
var vp: SubViewport
var bauer: Node3D
var kamera: Camera3D
var rahmen := 0
var fehler: Array = []
func _initialize() -> void:
vp = SubViewport.new()
vp.size = Vector2i(1600, 1000)
vp.own_world_3d = true
vp.render_target_update_mode = SubViewport.UPDATE_ALWAYS
root.add_child(vp)
var env := Environment.new()
env.background_mode = Environment.BG_COLOR
env.background_color = Color(0.55, 0.62, 0.72)
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
env.ambient_light_color = Color(1, 1, 1)
env.ambient_light_energy = 0.85
var we := WorldEnvironment.new()
we.environment = env
vp.add_child(we)
var sonne := DirectionalLight3D.new()
sonne.rotation_degrees = Vector3(-48.0, 35.0, 0.0)
sonne.light_energy = 1.2
vp.add_child(sonne)
# Bodenplatte (Terrain-Ersatz): zeigt, dass die Fahrzeuge AUF dem Boden stehen.
# Bewusst ein flacher Kasten (nicht PlaneMesh) + unshaded: keine Z-Artefakte
# und keine Beleuchtungsflecken im llvmpipe-Renderer.
var boden := MeshInstance3D.new()
var bm_boden := BoxMesh.new()
bm_boden.size = Vector3(90.0, 0.4, 90.0)
boden.mesh = bm_boden
boden.position = Vector3(0.0, -0.19, 0.0) # Oberkante y=+0.01: Sand liegt 1 cm ueber der Builder-Bodenebene (kein Z-Kampf)
var bmat := StandardMaterial3D.new()
bmat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
bmat.albedo_color = Color(0.76, 0.66, 0.44)
boden.material_override = bmat
vp.add_child(boden)
# --- der ECHTE Map Builder (gleiche Datei wie der Editor benutzt) ---
var manifest: Dictionary = JSON.parse_string(
FileAccess.get_file_as_string("res://library/objects.json"))
var doc: Dictionary = MapDocument.create_new("D6-Beweis", 256.0, "mittag")
doc["objects"] = PLATZ
bauer = load("res://core/viewport/map_view_builder.gd").new()
vp.add_child(bauer)
await process_frame
bauer.rebuild(doc, manifest)
var lib: Dictionary = manifest.get("objects", {})
print("Bibliothek: %d Objekte" % lib.size())
for i in PLATZ.size():
var oid := str(PLATZ[i]["id"])
var marker: Node3D = bauer.object_markers[i]
if marker == null:
fehler.append("%s: kein Marker" % oid)
continue
var entry: Dictionary = lib.get(oid, {})
if entry.is_empty():
fehler.append("%s: nicht in der Bibliothek" % oid)
continue
var col: Dictionary = entry.get("collision", {})
var groesse: Array = col.get("size", [0.0, 0.0, 0.0])
# Kollisionsbox als Drahtgitter (Fusspunkt: Unterkante y=0 der Platzierung).
# 12 Kanten als PRIMITIVE_LINES — `wireframe` gibt es in der
# Compatibility-Pipeline nicht (Godot-3-Remap-Warnung).
var box := MeshInstance3D.new()
var im := ImmediateMesh.new()
var mat := StandardMaterial3D.new()
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
mat.albedo_color = Color(0.0, 1.0, 0.2)
im.surface_begin(Mesh.PRIMITIVE_LINES, mat)
var h := Vector3(float(groesse[0]), float(groesse[1]), float(groesse[2])) * 0.5
var ecken: Array = []
for sx in [-1.0, 1.0]:
for sy in [-1.0, 1.0]:
for sz in [-1.0, 1.0]:
ecken.append(Vector3(sx * h.x, sy * h.y, sz * h.z))
for a in ecken.size():
for b in range(a + 1, ecken.size()):
var d: Vector3 = ecken[a] - ecken[b]
# Kante = genau eine Achse unterschiedlich
var gleich := 0
if abs(d.x) < 1e-4:
gleich += 1
if abs(d.y) < 1e-4:
gleich += 1
if abs(d.z) < 1e-4:
gleich += 1
if gleich == 2:
im.surface_add_vertex(ecken[a])
im.surface_add_vertex(ecken[b])
im.surface_end()
box.mesh = im
box.material_override = mat
box.position = Vector3(0.0, float(groesse[1]) * 0.5, 0.0)
marker.add_child(box)
var echt := bool(marker.get_meta("glb"))
var mi := marker.get_child(0) as MeshInstance3D
var mesh_aabb := mi.mesh.get_aabb() if (mi != null and mi.mesh != null) else AABB()
print("%-16s echtesGLB=%-5s aufsatz=%-5s registry=%.3f/%.3f/%.3f mesh_aabb=%.3f/%.3f/%.3f mesh_min_y=%.3f box_fuss=%.3f"
% [oid, str(echt), str(PLATZ[i]["aufsatz"]),
float(groesse[0]), float(groesse[1]), float(groesse[2]),
mesh_aabb.size.x, mesh_aabb.size.y, mesh_aabb.size.z, mesh_aabb.position.y,
marker.position.y])
if not echt:
fehler.append("%s: rendert nur den Platzhalter (GLB fehlt)" % oid)
if abs(mesh_aabb.position.y) > 0.01:
fehler.append("%s: Mesh-Unterkante %.3f (soll 0)" % [oid, mesh_aabb.position.y])
# Turm muss exakt auf dem Turm-Ring des Panzers sitzen (Pack-Prefab-Wert).
var turm: Node3D = bauer.object_markers[1]
if turm != null and abs(turm.position.y - TURM_RING_Y) > 0.001:
fehler.append("Turm sitzt bei y=%.4f (Pack-Prefab: %.4f)" % [turm.position.y, TURM_RING_Y])
kamera = Camera3D.new()
kamera.fov = 42.0
kamera.current = true
vp.add_child(kamera)
func _process(_delta: float) -> bool:
rahmen += 1
if rahmen < 4:
return false
if rahmen == 4:
kamera.look_at_from_position(Vector3(16.0, 9.0, 20.0), Vector3(3.0, 2.4, 0.0), Vector3.UP)
return false
if rahmen == 8:
var pfad := ProjectSettings.globalize_path(AUSGABE)
var img := vp.get_texture().get_image()
img.save_png(pfad)
print("Bild: %s (%dx%d)" % [pfad, img.get_width(), img.get_height()])
for f in fehler:
print(" FEHLER: %s" % f)
print("PROBE_D6_MAPBUILDER_%s" % ("FAIL" if not fehler.is_empty() else "OK"))
quit(1 if not fehler.is_empty() else 0)
return true
return false