160 lines
6.8 KiB
GDScript
160 lines
6.8 KiB
GDScript
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")
|