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://src/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