Malen: Regler „Kante hart ↔ weich“ + Pinsel ab 1 m
Sly 25.10.: harte Kanten an Strichrändern, Pinsel auf 1 m, Regler für die Kantenschärfe. - apply_material_brush(…, weichheit): im äußeren Anteil des Radius wird gestreut ausgedünnt (fester Hash je Punkt → deterministisch, Nachmalen füllt nicht auf); der Shader macht daraus einen ausfransenden Übergang. Kein Formatwechsel (ein Material je Punkt bleibt). - Panel: Regler 0–100 % (Default 40 %), nur im Malen-Modus; Pinselgröße ab 1 m. - test_mal_fein 26/26 (Dichte fällt nach außen, deterministisch, 1-m-Pinsel), test_terrain_mirror an die Tool-Weichheit angepasst. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
40ca5ab6ba
commit
4a7b99ee3e
5 changed files with 116 additions and 6 deletions
|
|
@ -30,6 +30,8 @@ var interaction # Objekt-Interaktion (für Modus-Wechsel)
|
||||||
var active := false
|
var active := false
|
||||||
var brush_mode: int = TerrainField.Mode.RAISE
|
var brush_mode: int = TerrainField.Mode.RAISE
|
||||||
var brush_radius := 12.0 # Meter
|
var brush_radius := 12.0 # Meter
|
||||||
|
## Kante beim Malen: 0 = hart, 1 = ganz weich (Karte 25.10.).
|
||||||
|
var brush_weichheit := 0.4
|
||||||
var brush_strength := 8.0 # Meter pro Sekunde (Heben/Senken)
|
var brush_strength := 8.0 # Meter pro Sekunde (Heben/Senken)
|
||||||
var paint_material: int = 0 # Material-ID im Malen-Modus (0=Gras, 1=Sand, 2=Fels)
|
var paint_material: int = 0 # Material-ID im Malen-Modus (0=Gras, 1=Sand, 2=Fels)
|
||||||
|
|
||||||
|
|
@ -137,10 +139,15 @@ func set_brush_mode(mode: int) -> void:
|
||||||
|
|
||||||
|
|
||||||
func set_radius(m: float) -> void:
|
func set_radius(m: float) -> void:
|
||||||
brush_radius = clampf(m, 2.0, 64.0)
|
brush_radius = clampf(m, 1.0, 64.0) # ab 1 m (Karte 25.10., feines Raster)
|
||||||
_refresh_ring_mesh()
|
_refresh_ring_mesh()
|
||||||
|
|
||||||
|
|
||||||
|
## Regler „Kante: hart ↔ weich“ (nur Malen).
|
||||||
|
func set_weichheit(w: float) -> void:
|
||||||
|
brush_weichheit = clampf(w, 0.0, 1.0)
|
||||||
|
|
||||||
|
|
||||||
func set_strength(mps: float) -> void:
|
func set_strength(mps: float) -> void:
|
||||||
brush_strength = clampf(mps, 1.0, 20.0)
|
brush_strength = clampf(mps, 1.0, 20.0)
|
||||||
|
|
||||||
|
|
@ -449,7 +456,7 @@ func _paint_step(x: float, z: float) -> bool:
|
||||||
if _sym_active() and _mats.size() >= _mres * _mres:
|
if _sym_active() and _mats.size() >= _mres * _mres:
|
||||||
snap = _snap_mats(_brush_region_r(g, _mres))
|
snap = _snap_mats(_brush_region_r(g, _mres))
|
||||||
var changed := TerrainField.apply_material_brush(_mats, _mres, _sx, _sz,
|
var changed := TerrainField.apply_material_brush(_mats, _mres, _sx, _sz,
|
||||||
x, z, brush_radius, _stroke_material)
|
x, z, brush_radius, _stroke_material, brush_weichheit)
|
||||||
if changed and not snap.is_empty():
|
if changed and not snap.is_empty():
|
||||||
_mirror_mats(snap, g)
|
_mirror_mats(snap, g)
|
||||||
return changed
|
return changed
|
||||||
|
|
|
||||||
|
|
@ -97,9 +97,17 @@ static func apply_brush(heights: PackedFloat32Array, res: int, size_x: float, si
|
||||||
## die Vertex-Interpolation des Meshes glättet die Grenze optisch.
|
## die Vertex-Interpolation des Meshes glättet die Grenze optisch.
|
||||||
## mats = dekodierte Material-Schicht (PackedByteArray, res² Einträge).
|
## mats = dekodierte Material-Schicht (PackedByteArray, res² Einträge).
|
||||||
static func apply_material_brush(mats: PackedByteArray, res: int, size_x: float, size_z: float,
|
static func apply_material_brush(mats: PackedByteArray, res: int, size_x: float, size_z: float,
|
||||||
cx: float, cz: float, radius: float, material_id: int) -> bool:
|
cx: float, cz: float, radius: float, material_id: int, weichheit: float = 0.0) -> bool:
|
||||||
if res < 2 or radius <= 0.0 or mats.size() < res * res:
|
if res < 2 or radius <= 0.0 or mats.size() < res * res:
|
||||||
return false
|
return false
|
||||||
|
# Weiche Kante (Karte 25.10., Sly: Regler „hart ↔ weich“): Die Schicht kennt
|
||||||
|
# pro Punkt nur EIN Material — deshalb wird der Rand gestreut ausgedünnt:
|
||||||
|
# im äußeren Anteil `weichheit` des Radius bekommt ein Punkt das Material
|
||||||
|
# nur mit fallender Wahrscheinlichkeit. Der Zufall ist ein fester Hash je
|
||||||
|
# Punkt (deterministisch: gleicher Strich = gleiches Ergebnis, Nachmalen
|
||||||
|
# füllt nicht auf). Der Shader macht aus der Streuung einen weichen Verlauf.
|
||||||
|
var w := clampf(weichheit, 0.0, 1.0)
|
||||||
|
var inner := radius * (1.0 - w)
|
||||||
var gx := (cx / size_x + 0.5) * float(res - 1)
|
var gx := (cx / size_x + 0.5) * float(res - 1)
|
||||||
var gz := (cz / size_z + 0.5) * float(res - 1)
|
var gz := (cz / size_z + 0.5) * float(res - 1)
|
||||||
var r_cells_x := radius / (size_x / float(res - 1))
|
var r_cells_x := radius / (size_x / float(res - 1))
|
||||||
|
|
@ -115,7 +123,9 @@ static func apply_material_brush(mats: PackedByteArray, res: int, size_x: float,
|
||||||
while x <= x1:
|
while x <= x1:
|
||||||
var dx := (float(x) - gx) * (size_x / float(res - 1))
|
var dx := (float(x) - gx) * (size_x / float(res - 1))
|
||||||
var dz := (float(z) - gz) * (size_z / float(res - 1))
|
var dz := (float(z) - gz) * (size_z / float(res - 1))
|
||||||
if dx * dx + dz * dz < radius * radius:
|
var d2 := dx * dx + dz * dz
|
||||||
|
if d2 < radius * radius and (w <= 0.0 or d2 <= inner * inner \
|
||||||
|
or punkt_hash(x, z) < rand_schwelle((sqrt(d2) - inner) / maxf(radius - inner, 0.0001))):
|
||||||
var idx := z * res + x
|
var idx := z * res + x
|
||||||
if mats[idx] != material_id:
|
if mats[idx] != material_id:
|
||||||
mats[idx] = material_id
|
mats[idx] = material_id
|
||||||
|
|
@ -125,6 +135,20 @@ static func apply_material_brush(mats: PackedByteArray, res: int, size_x: float,
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
|
|
||||||
|
## Deterministischer Zufall 0..1 je Rasterpunkt (Streu-Rand der weichen Kante).
|
||||||
|
static func punkt_hash(x: int, z: int) -> float:
|
||||||
|
var h := (x * 73856093) ^ (z * 19349663)
|
||||||
|
h = (h ^ (h >> 13)) * 1274126177
|
||||||
|
h = h ^ (h >> 16)
|
||||||
|
return float(h & 0xFFFF) / 65535.0
|
||||||
|
|
||||||
|
|
||||||
|
## Malwahrscheinlichkeit am Rand: t = 0 (Innenkante) → 1, t = 1 (Außenkante) → 0.
|
||||||
|
static func rand_schwelle(t: float) -> float:
|
||||||
|
var u := clampf(t, 0.0, 1.0)
|
||||||
|
return 1.0 - u * u * (3.0 - 2.0 * u)
|
||||||
|
|
||||||
|
|
||||||
## Glätten: Mischung Richtung 3×3-Umgebungsmittel (zweiphasig — das Mittel wird
|
## Glätten: Mischung Richtung 3×3-Umgebungsmittel (zweiphasig — das Mittel wird
|
||||||
## aus dem STAND der Feldes berechnet, sonst wandert die Welle in eine Richtung).
|
## aus dem STAND der Feldes berechnet, sonst wandert die Welle in eine Richtung).
|
||||||
static func _apply_smooth(heights: PackedFloat32Array, res: int, hmin: float, hmax: float,
|
static func _apply_smooth(heights: PackedFloat32Array, res: int, hmin: float, hmax: float,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ var terrain_radius_label: Label
|
||||||
var terrain_strength_slider: HSlider
|
var terrain_strength_slider: HSlider
|
||||||
var terrain_strength_label: Label
|
var terrain_strength_label: Label
|
||||||
var terrain_paint_hint: Label # Malen: Hinweis auf die Raster-Feinheit (24.10.)
|
var terrain_paint_hint: Label # Malen: Hinweis auf die Raster-Feinheit (24.10.)
|
||||||
|
var terrain_weich_label: Label # Malen: Kante hart ↔ weich (25.10.)
|
||||||
|
var terrain_weich_slider: HSlider
|
||||||
|
|
||||||
# --- Bauen (Karte t_97ea59c5) — im selben Werkzeug-Tab (aus main.gd ausgezogen)
|
# --- Bauen (Karte t_97ea59c5) — im selben Werkzeug-Tab (aus main.gd ausgezogen)
|
||||||
var build_edit_btn: Button
|
var build_edit_btn: Button
|
||||||
|
|
@ -132,7 +134,7 @@ func build() -> void:
|
||||||
terrain_radius_label = _field_label("Pinsel-Größe: 12 m")
|
terrain_radius_label = _field_label("Pinsel-Größe: 12 m")
|
||||||
terrain_panel.add_child(terrain_radius_label)
|
terrain_panel.add_child(terrain_radius_label)
|
||||||
terrain_radius_slider = HSlider.new()
|
terrain_radius_slider = HSlider.new()
|
||||||
terrain_radius_slider.min_value = 2.0
|
terrain_radius_slider.min_value = 1.0
|
||||||
terrain_radius_slider.max_value = 64.0
|
terrain_radius_slider.max_value = 64.0
|
||||||
terrain_radius_slider.step = 1.0
|
terrain_radius_slider.step = 1.0
|
||||||
terrain_radius_slider.value = 12.0
|
terrain_radius_slider.value = 12.0
|
||||||
|
|
@ -159,6 +161,22 @@ func build() -> void:
|
||||||
terrain_paint_hint.visible = false
|
terrain_paint_hint.visible = false
|
||||||
terrain_panel.add_child(terrain_paint_hint)
|
terrain_panel.add_child(terrain_paint_hint)
|
||||||
|
|
||||||
|
# Kante beim Malen (Karte 25.10., Sly): 0 % = harter Rand, 100 % = der ganze
|
||||||
|
# Pinsel läuft weich aus (gestreut, der Shader glättet die Streuung).
|
||||||
|
terrain_weich_label = _field_label("Kante: 40 % weich")
|
||||||
|
terrain_weich_label.visible = false
|
||||||
|
terrain_panel.add_child(terrain_weich_label)
|
||||||
|
terrain_weich_slider = HSlider.new()
|
||||||
|
terrain_weich_slider.min_value = 0.0
|
||||||
|
terrain_weich_slider.max_value = 100.0
|
||||||
|
terrain_weich_slider.step = 5.0
|
||||||
|
terrain_weich_slider.value = 40.0
|
||||||
|
terrain_weich_slider.custom_minimum_size = UIScale.v2(0, 32)
|
||||||
|
terrain_weich_slider.tooltip_text = "Links = harte Kante, rechts = weicher Übergang"
|
||||||
|
terrain_weich_slider.visible = false
|
||||||
|
terrain_weich_slider.value_changed.connect(_on_terrain_weich_changed)
|
||||||
|
terrain_panel.add_child(terrain_weich_slider)
|
||||||
|
|
||||||
terrain_panel.add_child(_muted_label(
|
terrain_panel.add_child(_muted_label(
|
||||||
"Linksklick-Ziehen formt bzw. malt (je nach Werkzeug), Strg+Linksklick macht das Gegenteil — im Malen-Modus malt Strg+Klick Gras zurück. "
|
"Linksklick-Ziehen formt bzw. malt (je nach Werkzeug), Strg+Linksklick macht das Gegenteil — im Malen-Modus malt Strg+Klick Gras zurück. "
|
||||||
+ "Jeder Strich ist EIN Rückgängig-Schritt (Strg+Z). Rechte Maustaste = Flug, ESC beendet den Terrain-Modus."))
|
+ "Jeder Strich ist EIN Rückgängig-Schritt (Strg+Z). Rechte Maustaste = Flug, ESC beendet den Terrain-Modus."))
|
||||||
|
|
@ -250,6 +268,9 @@ func _on_terrain_mode_changed(_pressed: bool) -> void:
|
||||||
if terrain_strength_slider != null:
|
if terrain_strength_slider != null:
|
||||||
terrain_strength_slider.visible = not paint
|
terrain_strength_slider.visible = not paint
|
||||||
terrain_strength_label.visible = not paint
|
terrain_strength_label.visible = not paint
|
||||||
|
if terrain_weich_slider != null:
|
||||||
|
terrain_weich_slider.visible = paint
|
||||||
|
terrain_weich_label.visible = paint
|
||||||
if terrain_paint_hint != null:
|
if terrain_paint_hint != null:
|
||||||
terrain_paint_hint.visible = paint
|
terrain_paint_hint.visible = paint
|
||||||
terrain_paint_hint.text = paint_hint_text(main.doc.get("terrain", {}) if main.doc is Dictionary else {})
|
terrain_paint_hint.text = paint_hint_text(main.doc.get("terrain", {}) if main.doc is Dictionary else {})
|
||||||
|
|
@ -302,6 +323,12 @@ func _on_terrain_radius_changed(value: float) -> void:
|
||||||
main.terrain_tool.call("set_radius", value)
|
main.terrain_tool.call("set_radius", value)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_terrain_weich_changed(value: float) -> void:
|
||||||
|
terrain_weich_label.text = "Kante: %d %% weich" % int(value) if value > 0.0 else "Kante: hart"
|
||||||
|
if main.terrain_tool != null:
|
||||||
|
main.terrain_tool.call("set_weichheit", value / 100.0)
|
||||||
|
|
||||||
|
|
||||||
func _on_terrain_strength_changed(value: float) -> void:
|
func _on_terrain_strength_changed(value: float) -> void:
|
||||||
terrain_strength_label.text = "Stärke: %.1f m pro Sekunde" % value
|
terrain_strength_label.text = "Stärke: %.1f m pro Sekunde" % value
|
||||||
if main.terrain_tool != null:
|
if main.terrain_tool != null:
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ func _initialize() -> void:
|
||||||
_f2_f4_f6_f7()
|
_f2_f4_f6_f7()
|
||||||
_f3()
|
_f3()
|
||||||
_f5()
|
_f5()
|
||||||
|
_f8_weich()
|
||||||
print("test_mal_fein: %d/%d ok" % [checks - failures, checks])
|
print("test_mal_fein: %d/%d ok" % [checks - failures, checks])
|
||||||
quit(1 if failures > 0 else 0)
|
quit(1 if failures > 0 else 0)
|
||||||
|
|
||||||
|
|
@ -126,3 +127,54 @@ func _only_checksum(errs: PackedStringArray) -> bool:
|
||||||
if not str(e).begins_with("V13"):
|
if not str(e).begins_with("V13"):
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
## F8 (25.10.): Kante hart ↔ weich. 0 = voller Kreis, weich = Innenkern voll,
|
||||||
|
## Rand gestreut, Ausdünnung nimmt nach außen zu, deterministisch.
|
||||||
|
func _f8_weich() -> void:
|
||||||
|
var r := 1025
|
||||||
|
var hart := PackedByteArray()
|
||||||
|
hart.resize(r * r)
|
||||||
|
TerrainField.apply_material_brush(hart, r, 1024.0, 1024.0, 0.0, 0.0, 10.0, 1, 0.0)
|
||||||
|
var weich := PackedByteArray()
|
||||||
|
weich.resize(r * r)
|
||||||
|
TerrainField.apply_material_brush(weich, r, 1024.0, 1024.0, 0.0, 0.0, 10.0, 1, 0.8)
|
||||||
|
var weich2 := PackedByteArray()
|
||||||
|
weich2.resize(r * r)
|
||||||
|
TerrainField.apply_material_brush(weich2, r, 1024.0, 1024.0, 0.0, 0.0, 10.0, 1, 0.8)
|
||||||
|
var n_h := _count(hart)
|
||||||
|
var n_w := _count(weich)
|
||||||
|
_check(n_w < n_h and n_w > n_h / 4, "F8 weich malt weniger Punkte als hart (%d vs %d)" % [n_w, n_h])
|
||||||
|
_check(weich == weich2, "F8 deterministisch (gleicher Strich = gleiches Ergebnis)")
|
||||||
|
# Ring-Dichten: innen (d<2) voll, Mitte (4..6) teils, außen (8..10) dünn
|
||||||
|
var d_in := _ring(weich, r, 0.0, 2.0)
|
||||||
|
var d_mid := _ring(weich, r, 4.0, 6.0)
|
||||||
|
var d_out := _ring(weich, r, 8.0, 10.0)
|
||||||
|
_check(d_in > 0.99 and d_mid > d_out and d_mid < 1.0 and d_out < 0.5,
|
||||||
|
"F8 Dichte fällt nach außen (innen %.2f, Mitte %.2f, Rand %.2f)" % [d_in, d_mid, d_out])
|
||||||
|
var eins := PackedByteArray()
|
||||||
|
eins.resize(r * r)
|
||||||
|
TerrainField.apply_material_brush(eins, r, 1024.0, 1024.0, 0.3, 0.2, 1.0, 1, 0.0)
|
||||||
|
_check(_count(eins) >= 1 and _count(eins) <= 5, "F8 1-m-Pinsel trifft %d Punkte" % _count(eins))
|
||||||
|
|
||||||
|
|
||||||
|
func _count(m: PackedByteArray) -> int:
|
||||||
|
var n := 0
|
||||||
|
for v in m:
|
||||||
|
if v != 0:
|
||||||
|
n += 1
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
|
func _ring(m: PackedByteArray, r: int, a: float, b: float) -> float:
|
||||||
|
var c := (r - 1) / 2
|
||||||
|
var tot := 0
|
||||||
|
var hit := 0
|
||||||
|
for z in range(c - 12, c + 13):
|
||||||
|
for x in range(c - 12, c + 13):
|
||||||
|
var d := Vector2(x - c, z - c).length()
|
||||||
|
if d >= a and d < b:
|
||||||
|
tot += 1
|
||||||
|
if m[z * r + x] != 0:
|
||||||
|
hit += 1
|
||||||
|
return float(hit) / float(maxi(tot, 1))
|
||||||
|
|
|
||||||
|
|
@ -418,7 +418,7 @@ func _test_off_mode_unchanged() -> void:
|
||||||
ref_mats.resize(RES * RES)
|
ref_mats.resize(RES * RES)
|
||||||
for i in int(ceil(0.3 / 0.05)):
|
for i in int(ceil(0.3 / 0.05)):
|
||||||
TerrainField.apply_material_brush(ref_mats, RES, SIZE, SIZE,
|
TerrainField.apply_material_brush(ref_mats, RES, SIZE, SIZE,
|
||||||
-100.0, -60.0, RADIUS, 1)
|
-100.0, -60.0, RADIUS, 1, float(ttool_p.get("brush_weichheit")))
|
||||||
var ref_mats_b64 := SvmapIO.encode_materials(ref_mats)
|
var ref_mats_b64 := SvmapIO.encode_materials(ref_mats)
|
||||||
_check(str(doc_p["terrain"]["materials_r8_b64"]) == ref_mats_b64,
|
_check(str(doc_p["terrain"]["materials_r8_b64"]) == ref_mats_b64,
|
||||||
"AUS/Malen: Material-Schicht byte-identisch zur direkten Anwendung")
|
"AUS/Malen: Material-Schicht byte-identisch zur direkten Anwendung")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue