scopedvr-map-editor/tests/test_builder_export.gd

141 lines
6.4 KiB
GDScript

extends SceneTree
## Unit-Tests des ScopedVR-Bibliothek-Exports (Karte t_82d2a8dd) — Kern-Logik
## aus addons/modular_builder_en/scopedvr_export.gd, komplett headless:
## - slugify (Umlaute, Leerzeichen, Zahlen-Anfang, Länge)
## - normalisiere (Fußpunkt y=0, XZ zentriert, Materialien bleiben)
## - trimesh_vertices (Dreiecks-Suppe, Dedup, Quantisierung)
## - glb_bytes (glTF-Magic, Texturen eingebettet, Dreiecke drin)
## Run: godot --headless --path . --script res://tests/test_builder_export.gd
const KERN := preload("res://addons/modular_builder_en/scopedvr_export.gd")
var _failures: Array[String] = []
var _checks: int = 0
func _initialize() -> void:
_test_slugify()
_test_normalisiere()
_test_trimesh()
_test_glb()
print("--------------------------------------------------")
print("CHECKS: %d FAILURES: %d" % [_checks, _failures.size()])
for f in _failures:
print("FAIL: " + f)
quit(0 if _failures.is_empty() else 1)
func _check(cond: bool, label: String) -> void:
_checks += 1
if cond:
print(" ok - " + label)
else:
_failures.append(label)
print(" FAIL - " + label)
func _test_slugify() -> void:
print("Testgruppe: slugify")
_check(KERN.slugify("Bauernhaus rot") == "bauernhaus_rot", "'Bauernhaus rot' → bauernhaus_rot")
_check(KERN.slugify("Häuschen Größe 2") == "haeuschen_groesse_2", "Umlaute werden aufgelöst (ist %s)" % KERN.slugify("Häuschen Größe 2"))
_check(KERN.slugify("Straße") == "strasse", "'Straße' → strasse")
_check(KERN.slugify("2 Häuser") == "haeuser", "führende Ziffern fliegen (ist %s)" % KERN.slugify("2 Häuser"))
_check(KERN.slugify(" schon_gut ") == "schon_gut", "Whitespace/Unterstrich-Ränder weg")
_check(KERN.slugify("!!!") == "", "nur Sonderzeichen → leer")
_check(KERN.slugify("a".repeat(60)).length() == 48, "max. 48 Zeichen")
_check(KERN.slugify("Bauernhaus--rot") == "bauernhaus_rot", "Doppel-Unterstrich kollabieren")
func _test_normalisiere() -> void:
print("Testgruppe: normalisiere")
var box := BoxMesh.new()
box.size = Vector3(2, 1, 4)
var mat := StandardMaterial3D.new()
mat.albedo_color = Color(1, 0, 0)
var root_mesh := _mesh_aus_bodies([{"mesh": box, "pos": Vector3(10, 5, -20), "mat": mat}])
var norm: Dictionary = KERN.normalisiere(root_mesh)
var m: ArrayMesh = norm["mesh"]
_check(m != null and m.get_surface_count() == 1, "normalisiere: 1 Oberfläche bleibt")
var aabb := m.get_aabb()
_check(absf(aabb.position.y) < 0.001, "normalisiere: Boden liegt auf y=0 (min.y=%s)" % aabb.position.y)
_check(absf(aabb.get_center().x) < 0.001 and absf(aabb.get_center().z) < 0.001,
"normalisiere: XZ zentriert (center=%s)" % aabb.get_center())
_check(aabb.size.is_equal_approx(Vector3(2, 1, 4)), "normalisiere: Größe unverändert")
var smat := m.surface_get_material(0)
_check(smat != null and (smat as StandardMaterial3D).albedo_color == Color(1, 0, 0),
"normalisiere: Material bleibt erhalten")
func _test_trimesh() -> void:
print("Testgruppe: trimesh_vertices")
# Zwei identische Dreiecke + ein echtes: Dedup lässt 3 Vertices-Tripel → 1 übrig.
var st := SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
st.add_vertex(Vector3(0, 0, 0)); st.add_vertex(Vector3(1, 0, 0)); st.add_vertex(Vector3(0, 0, 1))
st.add_vertex(Vector3(0, 0, 0)); st.add_vertex(Vector3(1, 0, 0)); st.add_vertex(Vector3(0, 0, 1))
st.add_vertex(Vector3(0, 1, 0)); st.add_vertex(Vector3(1, 1, 0)); st.add_vertex(Vector3(1, 1, 1))
var mesh := st.commit()
var faces := KERN.trimesh_vertices(mesh)
_check(faces.size() == 6, "Doppel-Dreieck dedupliziert: 9 → 6 Vertices (ist %d)" % faces.size())
# Entartetes Dreieck (zwei gleiche Punkte) fliegt raus.
var st2 := SurfaceTool.new()
st2.begin(Mesh.PRIMITIVE_TRIANGLES)
st2.add_vertex(Vector3(0, 0, 0)); st2.add_vertex(Vector3(0, 0, 0)); st2.add_vertex(Vector3(1, 0, 0))
var degeneriert := KERN.trimesh_vertices(st2.commit())
_check(degeneriert.is_empty(), "entartetes Dreieck → leer")
# Quantisierung: 0,2-mm-Unterschied (weit unter dem 1-mm-Quant) gilt als
# identisch — genau auf der Bucket-Grenze (0,5 mm) wäre das nicht garantiert.
var st3 := SurfaceTool.new()
st3.begin(Mesh.PRIMITIVE_TRIANGLES)
st3.add_vertex(Vector3(0, 0, 0)); st3.add_vertex(Vector3(1, 0, 0)); st3.add_vertex(Vector3(0, 0, 1))
st3.add_vertex(Vector3(0, 0.0002, 0)); st3.add_vertex(Vector3(1, 0, 0)); st3.add_vertex(Vector3(0, 0, 1))
_check(KERN.trimesh_vertices(st3.commit()).size() == 3, "Quantisierung 1 mm fasst 0,2-mm-Unterschied zusammen")
func _test_glb() -> void:
print("Testgruppe: glb_bytes")
var box := BoxMesh.new()
box.size = Vector3(1, 1, 1)
var img := Image.create(8, 8, false, Image.FORMAT_RGB8)
img.fill(Color(0.7, 0.3, 0.1))
var mat := StandardMaterial3D.new()
mat.albedo_texture = ImageTexture.create_from_image(img)
var mesh := _mesh_aus_bodies([{"mesh": box, "pos": Vector3.ZERO, "mat": mat}])
var bytes := KERN.glb_bytes(mesh, "testhaus")
_check(KERN.glb_gueltig(bytes), "GLB gültig (Magic + Version + JSON-Chunk, %d Bytes)" % bytes.size())
var json_len := bytes.decode_u32(12)
var parsed: Variant = JSON.parse_string(bytes.slice(20, 20 + json_len).get_string_from_utf8())
var gltf: Dictionary = parsed if parsed is Dictionary else {}
var tris := 0
for m in gltf.get("meshes", []):
for p in m.get("primitives", []):
var ai: int = int(p.get("indices", -1))
if ai >= 0:
tris += int(gltf["accessors"][ai].get("count", 0)) / 3
_check(tris == 12, "GLB enthält 12 Dreiecke (Box, ist %d)" % tris)
_check((gltf.get("images", []) as Array).size() == 1, "Textur ist EINGEBETTET (images=1)")
_check(KERN.glb_gueltig("müll".to_utf8_buffer()) == false, "Müll-Bytes werden erkannt")
_check(KERN.glb_bytes(null).is_empty(), "null-Mesh → leere Bytes (kein Engine-Error)")
## Hilfsfunktion: baut ein ArrayMesh aus BoxMeshes an Positionen (mit Material).
func _mesh_aus_bodies(bodies: Array) -> ArrayMesh:
var out := ArrayMesh.new()
for b in bodies:
var bm: Mesh = b["mesh"]
var st := SurfaceTool.new()
st.create_from(bm, 0)
if b.has("mat"):
st.set_material(b["mat"])
var tmp := st.commit()
var arrays := tmp.surface_get_arrays(0)
var verts: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
var off: Vector3 = b.get("pos", Vector3.ZERO)
for i in verts.size():
verts[i] = verts[i] + off
arrays[Mesh.ARRAY_VERTEX] = verts
out.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
if b.has("mat"):
out.surface_set_material(out.get_surface_count() - 1, b["mat"])
return out