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

148 lines
No EOL
5.8 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

extends SceneTree
## Render-Probe M2 (Karte t_f9eff526): Terrain spiegeln im Symmetrie-Modus.
## Zwei Striche (Heben bei −260/−140, Malen Sand bei +150/+180) mit
## Symmetrie=Punkt über den ECHTEN terrain_tool-Pfad (demo_stroke) —
## draufsichtig gerendert müssen BOTHSEITIG Hügel + Sand erscheinen:
## - Hügel-Fenster und Zwilling-Hügel-Fenster nahezu gleich hell
## - Sand-Fenster und Zwilling-Sand-Fenster nahezu gleich farbig
## - beide deutlich verschieden zur unberührten Mitte
## PNGs landen im Profil-Scratch + /opt/data/scopedvr/work/m2_bilder/.
## Start (BRAUCHT Rendering — NICHT --headless):
## xvfb-run godot --path . --rendering-driver opengl3 -s res://tests/probe_terrain_mirror_render.gd
const RES := 65
const SIZE := 1024.0
var failures := 0
var checks := 0
var out_dir := "/opt/data/profiles/scopedvr-dev/cache/scratch"
class ToolMain:
var doc: Dictionary = {}
var sym_mode: int = SymmetryCore.Mode.POINT
var history: ActionHistory = ActionHistory.new()
func _set_status(_text: String, _err := false) -> void:
pass
func _on_terrain_stroke_committed() -> void:
pass
func _initialize() -> void:
await process_frame
await process_frame
# --- Doc + Terrain-Tool (echter Strich-Pfad inkl. Spiegel) ---
var doc := SvmapDocument.create_new("MirrorProbe", SIZE, "mittag")
var terrain: Dictionary = doc["terrain"]
terrain["resolution"] = RES
terrain["size_x_m"] = SIZE
terrain["size_z_m"] = SIZE
terrain["min_height_m"] = 0.0
terrain["max_height_m"] = 40.0
var flat := PackedFloat32Array()
flat.resize(RES * RES)
terrain["heightmap_r16_b64"] = TerrainField.encode(flat, 0.0, 40.0)
var mats := PackedByteArray()
mats.resize(RES * RES)
terrain["materials_r8_b64"] = SvmapIO.encode_materials(mats)
var tmain := ToolMain.new()
tmain.doc = doc
tmain.history.begin(doc)
var ttool := Node3D.new()
ttool.set_script(load("res://core/editor/terrain_tool.gd"))
ttool.setup(tmain, Camera3D.new(), null, null)
ttool.call("activate")
ttool.call("set_radius", 100.0)
ttool.call("set_paint_material", 1)
ttool.call("demo_stroke", -260.0, -140.0, TerrainField.Mode.RAISE, 2.0)
ttool.call("demo_stroke", 150.0, 180.0, TerrainField.Mode.PAINT, 1.0)
var heights := SvmapIO.decode_heights(terrain)
var got_mats := SvmapIO.decode_materials(terrain)
# Zellgleiche Hälften numerisch gegenprüfen (Punkt-Modus):
var bad_h := 0
var bad_m := 0
for z in RES:
for x in RES:
var mc: Vector2i = SymmetryCore.mirror_cell(SymmetryCore.Mode.POINT, x, z, RES)
if heights[z * RES + x] != heights[mc.y * RES + mc.x]:
bad_h += 1
if int(got_mats[z * RES + x]) != int(got_mats[mc.y * RES + mc.x]):
bad_m += 1
print("PROBE_MIRROR_ASYM hoehe=%d material=%d (0 = exakt zellgleich)" % [bad_h, bad_m])
_check(bad_h == 0, "Höhen-Hälften exakt zellgleich (0 Abweichungen)")
_check(bad_m == 0, "Material-Hälften exakt zellgleich (0 Abweichungen)")
# --- Draufsicht rendern (unshaded Vertex-Farben + Höhenlinien) ---
var view := Node3D.new()
view.set_script(load("res://core/viewport/terrain_mesh.gd"))
root.add_child(view)
view.call("update_heights", heights, RES, SIZE, SIZE, got_mats)
var cam := Camera3D.new()
root.add_child(cam)
cam.projection = Camera3D.PROJECTION_ORTHOGONAL
var vp := root.size
cam.size = SIZE * float(vp.y) / float(vp.x)
cam.position = Vector3(0.0, SIZE, 0.01)
cam.look_at(Vector3.ZERO, Vector3(0, 0, -1))
cam.current = true
await process_frame
await process_frame
await RenderingServer.frame_post_draw
var img := root.get_texture().get_image()
img.save_png(out_dir + "/terrain_mirror_beweis.png")
print("PROBE_SAVED %s/terrain_mirror_beweis.png" % out_dir)
# --- Fenster-Messungen ---
# Ortho-Kamera füllt exakt die BildBREITE: horizontale Ausdehnung =
# SIZE, vertikale = SIZE × (Höhe/Breite des Viewports) (KEEP_HEIGHT).
# Welt → Bild: sx = 0.5 + x/SIZE, sy = 0.5 + z/vert_extent.
var w := img.get_width()
var h := img.get_height()
var vert := SIZE * float(h) / float(w)
var hill_a := _window_mean(img, Vector2(w * (0.5 - 260.0 / SIZE), h * (0.5 - 140.0 / vert)), int(w * 0.04))
var hill_b := _window_mean(img, Vector2(w * (0.5 + 260.0 / SIZE), h * (0.5 + 140.0 / vert)), int(w * 0.04))
var sand_a := _window_mean(img, Vector2(w * (0.5 + 150.0 / SIZE), h * (0.5 + 180.0 / vert)), int(w * 0.04))
var sand_b := _window_mean(img, Vector2(w * (0.5 - 150.0 / SIZE), h * (0.5 - 180.0 / vert)), int(w * 0.04))
var mitte := _window_mean(img, Vector2(w * 0.5, h * 0.5), int(w * 0.03))
print("PROBE_HUEGEL a=%s b=%s" % [hill_a, hill_b])
print("PROBE_SAND a=%s b=%s" % [sand_a, sand_b])
print("PROBE_MITTE %s (unberührtes Gras)" % mitte)
var dh := _v3dist(hill_a, hill_b)
var ds := _v3dist(sand_a, sand_b)
print("PROBE_SYMDIFF huegel=%.4f sand=%.4f" % [dh, ds])
_check(dh < 0.02, "Hügel und Zwilling-Hügel im Bild gleich (Δ %.4f < 0.02)" % dh)
_check(ds < 0.02, "Sand und Zwilling-Sand im Bild gleich (Δ %.4f < 0.02)" % ds)
_check(_v3dist(hill_a, mitte) > 0.05, "Hügel deutlich sichtbar vs. Mitte (Δ %.4f)" % _v3dist(hill_a, mitte))
_check(_v3dist(sand_a, mitte) > 0.05, "Sand deutlich sichtbar vs. Gras-Mitte (Δ %.4f)" % _v3dist(sand_a, mitte))
if failures == 0:
print("ALL OK: %d checks" % checks)
quit(0)
else:
print("FAILED: %d/%d checks" % [failures, checks])
quit(1)
func _window_mean(img: Image, center: Vector2, radius_px: int) -> Vector3:
var s := Vector3.ZERO
var n := 0
var cx := int(center.x)
var cy := int(center.y)
for y in range(maxi(0, cy - radius_px), mini(img.get_height(), cy + radius_px)):
for x in range(maxi(0, cx - radius_px), mini(img.get_width(), cx + radius_px)):
var c := img.get_pixel(x, y)
s += Vector3(c.r, c.g, c.b)
n += 1
return s / float(maxi(n, 1))
func _v3dist(a: Vector3, b: Vector3) -> float:
return (a - b).length()
func _check(cond: bool, label: String) -> void:
checks += 1
if cond:
print(" ok: %s" % label)
else:
failures += 1
print(" FAIL: %s" % label)