scopedvr-map-editor/tests/test_pinsel_werkzeuge.gd
slyphax 26bcbe0cdd Terrain-Pinsel: Kante-Regler (Falloff), Einebnen, Rampe, Druck-Stärke, Mal-Slot-Ersatz
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 09:52:03 +01:00

160 lines
6.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
## Pinsel-Falloff, Mal-Slot-Logik, Einebnen, Rampe, Druck (Karte 25.10.).
## Ausführen: godot --headless --path . --script res://tests/test_pinsel_werkzeuge.gd
var failures := 0
var checks := 0
const R := 129
const S := 1024.0
func _init() -> void:
_falloff()
_druck()
_flatten()
_ramp()
_glaetten()
_mal_slots()
_undo()
if failures == 0:
print("ALL OK: %d checks" % checks)
quit(0)
else:
print("FAILED: %d/%d checks" % [failures, checks])
quit(1)
func _check(cond: bool, label: String) -> void:
checks += 1
if cond:
print(" ok: %s" % label)
else:
failures += 1
print(" FAIL: %s" % label)
func _flat(h := 0.0) -> PackedFloat32Array:
var a := PackedFloat32Array()
a.resize(R * R)
a.fill(h)
return a
func _falloff() -> void:
print("Testgruppe: Falloff lerp(t, t², f)")
_check(is_equal_approx(TerrainField.falloff_weight(0.0, 10.0, 0.0), 1.0), "Mitte = 1 (hart)")
_check(is_equal_approx(TerrainField.falloff_weight(0.0, 10.0, 1.0), 1.0), "Mitte = 1 (weich)")
_check(is_equal_approx(TerrainField.falloff_weight(5.0, 10.0, 0.0), 0.5), "hart: halber Radius = 0.5")
_check(is_equal_approx(TerrainField.falloff_weight(5.0, 10.0, 1.0), 0.25), "weich: halber Radius = 0.25")
_check(is_equal_approx(TerrainField.falloff_weight(5.0, 10.0, 0.5), 0.375), "mittel: lerp = 0.375")
_check(TerrainField.falloff_weight(10.0, 10.0, 0.5) == 0.0, "Rand = 0")
var h := _flat()
TerrainField.apply_brush(h, R, S, S, -1e5, 1e5, 0.0, 0.0, 40.0, TerrainField.Mode.RAISE, 1.0, 0.0)
var h2 := _flat()
TerrainField.apply_brush(h2, R, S, S, -1e5, 1e5, 0.0, 0.0, 40.0, TerrainField.Mode.RAISE, 1.0, 1.0)
var i := 64 * R + 64 + 3 # ~24 m neben der Mitte
_check(h[i] > h2[i] and h2[i] > 0.0, "Raise: hart hebt am Rand mehr als weich")
func _druck() -> void:
print("Testgruppe: Druck")
_check(TerrainField.pressure_of(0.0, 0.0) == 1.0, "Maus = 1.0")
_check(is_equal_approx(TerrainField.pressure_of(0.4, 0.9), 0.4), "Pen vor Trigger")
_check(is_equal_approx(TerrainField.pressure_of(0.0, 0.6), 0.6), "Trigger")
_check(TerrainField.pressure_of(0.001, 0.0) == 0.05 or TerrainField.pressure_of(0.001, 0.0) == 1.0, "Minimum begrenzt")
var t := Node3D.new()
t.set_script(load("res://src/editor/terrain_tool.gd"))
t.set_pressure(0.5)
var a: float = t.call("_step_amount", TerrainField.Mode.RAISE, 1.0)
t.set_pressure(1.0)
var b: float = t.call("_step_amount", TerrainField.Mode.RAISE, 1.0)
_check(is_equal_approx(a * 2.0, b), "Stärke skaliert mit Druck")
t.free()
func _flatten() -> void:
print("Testgruppe: Einebnen")
var h := _flat(0.0)
for i in h.size():
h[i] = float((i % R)) * 0.1 # Rampe in x
var target := 6.4
for k in 40:
TerrainField.apply_brush(h, R, S, S, -1e5, 1e5, 0.0, 0.0, 60.0, TerrainField.Mode.FLATTEN, 0.3, 0.3, target)
_check(absf(h[64 * R + 64] - target) < 0.05, "Mitte erreicht Zielhöhe")
_check(absf(h[64 * R + 4] - 0.4) < 0.0001, "weit außerhalb unverändert")
_check(absf(h[64 * R + 60] - target) < absf(0.1 * 60 - target) , "Rand näher am Ziel als vorher")
func _ramp() -> void:
print("Testgruppe: Rampe")
var h := _flat(0.0)
var a := Vector2(-80.0, 0.0)
var b := Vector2(80.0, 0.0)
TerrainField.apply_ramp(h, R, S, S, a, b, 2.0, 12.0, 30.0, 0.5)
var cx := func(wx: float) -> int: return int(round((wx / S + 0.5) * float(R - 1)))
var iz := 64
var mid: float = h[iz * R + cx.call(0.0)]
_check(absf(mid - 7.0) < 0.3, "Mitte ≈ lineare Höhe 7 (%.2f)" % mid)
var q: float = h[iz * R + cx.call(40.0)]
_check(absf(q - 9.5) < 0.4, "Viertelpunkt ≈ 9.5 (%.2f)" % q)
_check(h[iz * R + cx.call(-200.0)] == 0.0 and h[iz * R + cx.call(200.0)] == 0.0, "außerhalb der Enden unverändert")
var seitlich: float = h[(iz + 4) * R + cx.call(0.0)] # ≈ 32 m quer: außerhalb der halben Breite 15
_check(seitlich == 0.0, "außerhalb der Breite unverändert")
var rand: float = h[(iz + 1) * R + cx.call(0.0)] # 8 m quer: Falloff
_check(rand > 0.0 and rand < mid, "Rand mit Falloff schwächer als Achse")
func _glaetten() -> void:
print("Testgruppe: Glätten mit Falloff")
var h := _flat(0.0)
h[64 * R + 64] = 10.0
var changed := TerrainField.apply_brush(h, R, S, S, -1e5, 1e5, 0.0, 0.0, 30.0, TerrainField.Mode.SMOOTH, 1.0, 0.5)
_check(changed and h[64 * R + 64] < 10.0 and h[64 * R + 65] > 0.0, "Spitze verteilt sich")
var h2 := _flat(0.0)
h2[64 * R + 64] = 10.0
TerrainField.apply_brush(h2, R, S, S, -1e5, 1e5, 0.0, 0.0, 30.0, TerrainField.Mode.SMOOTH, 1.0)
_check(h2[64 * R + 64] < 10.0, "ohne Falloff (alt: Kosinus) funktioniert weiter")
func _mal_slots() -> void:
print("Testgruppe: Mal-Slots")
var n := 33
var a := PackedByteArray(); a.resize(n * n); a.fill(0)
var b := PackedByteArray(); b.resize(n * n); b.fill(0)
var w := PackedByteArray(); w.resize(n * n); w.fill(0)
var mid := 16 * n + 16
# Mitte: Slot a=1 (Anteil 255−200=55), Slot b=2 (Anteil 200 = stärker).
a[mid] = 1; b[mid] = 2; w[mid] = 200
# Ziel 3 mit voller Stärke: ersetzt das SCHWÄCHERE (1) → übrig: 2 + 3.
TerrainField.apply_material_brush_mix(a, b, w, n, 32.0, 32.0, 0.0, 0.0, 6.0, 3, 0.0, 0.5)
var slots := [int(a[mid]), int(b[mid])]
_check(slots.has(2) and slots.has(3) and not slots.has(1), "schwächeres Slot-Material ersetzt (%s)" % str(slots))
_check(int(w[mid]) > 100 and int(w[mid]) < 140 and int(b[mid]) == 3, "Anteil schiebt weich Richtung Ziel (w=%d)" % int(w[mid]))
TerrainField.apply_material_brush_mix(a, b, w, n, 32.0, 32.0, 0.0, 0.0, 6.0, 3, 0.0, 1.0)
_check(int(a[mid]) == 3 and int(w[mid]) == 0, "voll gemalt → reines Zielmaterial")
# Ziel ist schon in Slot b: Anteil wächst, kein Wechsel.
a[mid] = 1; b[mid] = 2; w[mid] = 100
TerrainField.apply_material_brush_mix(a, b, w, n, 32.0, 32.0, 0.0, 0.0, 6.0, 2, 0.0, 0.8)
_check(int(a[mid]) == 1 and int(b[mid]) == 2 and int(w[mid]) > 100, "Ziel in Slot b: Anteil wächst")
# Schwacher Strich schwächt nie ab.
var before := int(w[mid])
TerrainField.apply_material_brush_mix(a, b, w, n, 32.0, 32.0, 0.0, 0.0, 6.0, 2, 0.0, 0.1)
_check(int(w[mid]) == before, "schwächerer Strich ändert nichts")
func _undo() -> void:
print("Testgruppe: Einebnen/Rampe + Undo")
var doc := SvmapDocument.create_new("Pinsel", 1024.0, "mittag")
var hist := ActionHistory.new()
hist.begin(doc)
var terrain: Dictionary = doc["terrain"]
var h := SvmapIO.decode_heights(terrain)
var vor := str(terrain["heightmap_r16_b64"])
var before: Dictionary = hist.snapshot(["terrain"])
TerrainField.apply_ramp(h, 129, 1024.0, 1024.0, Vector2(-50, 0), Vector2(50, 0), 0.0, 20.0, 20.0, 0.3)
terrain["max_height_m"] = 30.0
terrain["heightmap_r16_b64"] = TerrainField.encode(h, float(terrain["min_height_m"]), 30.0)
hist.commit("Boden geformt (Rampe)", before, hist.snapshot(["terrain"]))
_check(str(terrain["heightmap_r16_b64"]) != vor, "Rampe verändert Doc")
hist.undo()
_check(str(terrain["heightmap_r16_b64"]) == vor, "Undo stellt Höhenfeld wieder her")