Terrain-Pinsel: Kante-Regler (Falloff), Einebnen, Rampe, Druck-Stärke, Mal-Slot-Ersatz
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b1d8f7626d
commit
26bcbe0cdd
9 changed files with 469 additions and 61 deletions
|
|
@ -60,6 +60,8 @@ const ACTIONS := {
|
||||||
"editor_brush_lower": [KEY_2],
|
"editor_brush_lower": [KEY_2],
|
||||||
"editor_brush_smooth": [KEY_3],
|
"editor_brush_smooth": [KEY_3],
|
||||||
"editor_brush_paint": [KEY_4],
|
"editor_brush_paint": [KEY_4],
|
||||||
|
"editor_brush_flatten": [KEY_5],
|
||||||
|
"editor_brush_ramp": [KEY_6],
|
||||||
# --- Datei & Undo (Karte t_11b5c25b, Karte 3/3) --------------------------
|
# --- Datei & Undo (Karte t_11b5c25b, Karte 3/3) --------------------------
|
||||||
# Strg+Z/Strg+Y laufen seit dem M2-UX-Fundament; hier NACHGEBUGGEN als
|
# Strg+Z/Strg+Y laufen seit dem M2-UX-Fundament; hier NACHGEBUGGEN als
|
||||||
# dokumentierte Actions (alle 3 Shortcut-Karten: ACTIONS-Dict = Source of
|
# dokumentierte Actions (alle 3 Shortcut-Karten: ACTIONS-Dict = Source of
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ extends Node3D
|
||||||
## liest daraus. Undo/Redo läuft über die gemeinsame ActionHistory (terrain-Block).
|
## liest daraus. Undo/Redo läuft über die gemeinsame ActionHistory (terrain-Block).
|
||||||
|
|
||||||
const SMOOTH_RATE_DIVISOR := 20.0 # Stärke-Slider (m/s) → Glätt-Tempo (Anteil/s)
|
const SMOOTH_RATE_DIVISOR := 20.0 # Stärke-Slider (m/s) → Glätt-Tempo (Anteil/s)
|
||||||
const MODE_LABELS := ["Heben", "Senken", "Glätten", "Malen"]
|
const MODE_LABELS := ["Heben", "Senken", "Glätten", "Malen", "Einebnen", "Rampe"]
|
||||||
|
const FLATTEN_RATE_DIVISOR := 8.0 # Stärke-Slider → Einebnen-Tempo (Anteil/s)
|
||||||
|
|
||||||
var main # Hauptfenster (script-erweitert, Variant)
|
var main # Hauptfenster (script-erweitert, Variant)
|
||||||
var camera: Camera3D
|
var camera: Camera3D
|
||||||
|
|
@ -32,6 +33,10 @@ 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.).
|
## Kante beim Malen: 0 = hart, 1 = ganz weich (Karte 25.10.).
|
||||||
var brush_weichheit := 0.4
|
var brush_weichheit := 0.4
|
||||||
|
## Pinsel-Falloff (Karte 25.10.): 0 = hart (linear), 1 = weich (quadratisch).
|
||||||
|
var brush_falloff := 0.5
|
||||||
|
## Druck 0.05–1 (Pen-Druck / Controller-Trigger / Maus = 1.0), skaliert die Stärke.
|
||||||
|
var pressure := 1.0
|
||||||
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)
|
||||||
|
|
||||||
|
|
@ -39,6 +44,10 @@ var paint_material: int = 0 # Material-ID im Malen-Modus (0=Gras, 1=Sand, 2=Fels
|
||||||
var _stroking := false
|
var _stroking := false
|
||||||
var _stroke_mode: int = TerrainField.Mode.RAISE
|
var _stroke_mode: int = TerrainField.Mode.RAISE
|
||||||
var _stroke_material: int = 0
|
var _stroke_material: int = 0
|
||||||
|
var _flatten_target := 0.0 # Einebnen: Höhe unter dem Cursor beim Strichstart
|
||||||
|
var _ramp_a := Vector2.ZERO # Rampe: Startpunkt (Welt-X/Z) + Höhe
|
||||||
|
var _ramp_ha := 0.0
|
||||||
|
var _ramp_b := Vector2.ZERO # Rampe: aktueller Endpunkt
|
||||||
var _heights := PackedFloat32Array()
|
var _heights := PackedFloat32Array()
|
||||||
var _mats := PackedByteArray() # Material-Schicht (res²) — seit _refresh_
|
var _mats := PackedByteArray() # Material-Schicht (res²) — seit _refresh_
|
||||||
# _from_doc immer dekodiert (t_55ed041a),
|
# _from_doc immer dekodiert (t_55ed041a),
|
||||||
|
|
@ -56,6 +65,7 @@ var _cursor := Vector3.ZERO
|
||||||
|
|
||||||
# Pinsel-Ring
|
# Pinsel-Ring
|
||||||
var _ring: MeshInstance3D
|
var _ring: MeshInstance3D
|
||||||
|
var _ramp_line: MeshInstance3D # Rampen-Vorschau: Linie Start → Cursor
|
||||||
var _ring_radius := -1.0
|
var _ring_radius := -1.0
|
||||||
var _ring_mode := -1
|
var _ring_mode := -1
|
||||||
|
|
||||||
|
|
@ -74,6 +84,15 @@ func _ensure_ring() -> void:
|
||||||
_ring = MeshInstance3D.new()
|
_ring = MeshInstance3D.new()
|
||||||
_ring.visible = false
|
_ring.visible = false
|
||||||
add_child(_ring)
|
add_child(_ring)
|
||||||
|
_ramp_line = MeshInstance3D.new()
|
||||||
|
_ramp_line.visible = false
|
||||||
|
_ramp_line.mesh = ImmediateMesh.new()
|
||||||
|
var lm := StandardMaterial3D.new()
|
||||||
|
lm.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||||
|
lm.albedo_color = Color(1.0, 0.85, 0.3)
|
||||||
|
lm.no_depth_test = true
|
||||||
|
_ramp_line.material_override = lm
|
||||||
|
add_child(_ramp_line)
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------ Aktivierung ---
|
# ------------------------------------------------------------ Aktivierung ---
|
||||||
|
|
@ -102,6 +121,8 @@ func deactivate() -> void:
|
||||||
_end_stroke()
|
_end_stroke()
|
||||||
if _ring != null and is_instance_valid(_ring):
|
if _ring != null and is_instance_valid(_ring):
|
||||||
_ring.visible = false
|
_ring.visible = false
|
||||||
|
if _ramp_line != null and is_instance_valid(_ramp_line):
|
||||||
|
_ramp_line.visible = false
|
||||||
|
|
||||||
|
|
||||||
## Nach Undo/Redo/Laden/Neue-Map: Feld neu dekodieren (Cache ist sonst alt).
|
## Nach Undo/Redo/Laden/Neue-Map: Feld neu dekodieren (Cache ist sonst alt).
|
||||||
|
|
@ -177,6 +198,16 @@ func set_weichheit(w: float) -> void:
|
||||||
brush_weichheit = clampf(w, 0.0, 1.0)
|
brush_weichheit = clampf(w, 0.0, 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
func set_falloff(f: float) -> void:
|
||||||
|
brush_falloff = clampf(f, 0.0, 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
## Druck setzen (XR-Controller-Trigger 0–1 ruft das direkt; Pen/Maus laufen
|
||||||
|
## über _unhandled_input). Wert wird über TerrainField.pressure_of begrenzt.
|
||||||
|
func set_pressure(p: float) -> void:
|
||||||
|
pressure = TerrainField.pressure_of(p, 0.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)
|
||||||
|
|
||||||
|
|
@ -192,7 +223,10 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||||
if not active:
|
if not active:
|
||||||
return
|
return
|
||||||
if event is InputEventMouseMotion:
|
if event is InputEventMouseMotion:
|
||||||
_update_cursor((event as InputEventMouseMotion).position)
|
var mm := event as InputEventMouseMotion
|
||||||
|
_update_cursor(mm.position)
|
||||||
|
# Stift: pressure > 0; Maus meldet 0 (oder 1) → bleibt voll.
|
||||||
|
pressure = TerrainField.pressure_of(mm.pressure, _trigger_axis())
|
||||||
return
|
return
|
||||||
if event is InputEventMouseButton:
|
if event is InputEventMouseButton:
|
||||||
var mb := event as InputEventMouseButton
|
var mb := event as InputEventMouseButton
|
||||||
|
|
@ -220,6 +254,15 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||||
# t_2470de89), in jedem Werkzeugzustand.
|
# t_2470de89), in jedem Werkzeugzustand.
|
||||||
|
|
||||||
|
|
||||||
|
## Gamepad-Trigger (rechts) des ersten Pads; 0 ohne Pad. Nur Eingabe-Lesen,
|
||||||
|
## keine OS-Annahme.
|
||||||
|
func _trigger_axis() -> float:
|
||||||
|
var pads := Input.get_connected_joypads()
|
||||||
|
if pads.is_empty():
|
||||||
|
return 0.0
|
||||||
|
return Input.get_joy_axis(pads[0], JOY_AXIS_TRIGGER_RIGHT)
|
||||||
|
|
||||||
|
|
||||||
func _update_cursor(viewport_pos: Vector2) -> void:
|
func _update_cursor(viewport_pos: Vector2) -> void:
|
||||||
var hit: Variant = TerrainField.raycast(_heights, _res, _sx, _sz,
|
var hit: Variant = TerrainField.raycast(_heights, _res, _sx, _sz,
|
||||||
camera.project_ray_origin(viewport_pos), camera.project_ray_normal(viewport_pos))
|
camera.project_ray_origin(viewport_pos), camera.project_ray_normal(viewport_pos))
|
||||||
|
|
@ -256,6 +299,12 @@ func _begin_stroke(mode: int, material_id: int = -1) -> void:
|
||||||
_stroke_mode = mode
|
_stroke_mode = mode
|
||||||
_stroke_material = paint_material if material_id < 0 else material_id
|
_stroke_material = paint_material if material_id < 0 else material_id
|
||||||
_stroking = true
|
_stroking = true
|
||||||
|
if mode == TerrainField.Mode.FLATTEN:
|
||||||
|
_flatten_target = _height_at_stroke(_cursor.x, _cursor.z)
|
||||||
|
elif mode == TerrainField.Mode.RAMP:
|
||||||
|
_ramp_a = Vector2(_cursor.x, _cursor.z)
|
||||||
|
_ramp_ha = _height_at_stroke(_cursor.x, _cursor.z)
|
||||||
|
_ramp_b = _ramp_a
|
||||||
# Im Malen-Modus lebt die Material-Schicht während des Strichs
|
# Im Malen-Modus lebt die Material-Schicht während des Strichs
|
||||||
# (dekodiert — Legacy-Maps ohne Schicht starten mit alles-Gras).
|
# (dekodiert — Legacy-Maps ohne Schicht starten mit alles-Gras).
|
||||||
if mode == TerrainField.Mode.PAINT:
|
if mode == TerrainField.Mode.PAINT:
|
||||||
|
|
@ -288,11 +337,12 @@ func _process(delta: float) -> void:
|
||||||
view_builder.call("update_terrain", _heights, _mats, _splat_rect)
|
view_builder.call("update_terrain", _heights, _mats, _splat_rect)
|
||||||
_ring.position.y = _height_at_stroke(_cursor.x, _cursor.z) + 0.3
|
_ring.position.y = _height_at_stroke(_cursor.x, _cursor.z) + 0.3
|
||||||
return
|
return
|
||||||
var amount: float
|
if _stroke_mode == TerrainField.Mode.RAMP:
|
||||||
if _stroke_mode == TerrainField.Mode.SMOOTH:
|
# Rampe wirkt erst beim Loslassen — hier nur der Endpunkt + Ring-Hinweis.
|
||||||
amount = clampf(brush_strength / SMOOTH_RATE_DIVISOR * delta, 0.0, 1.0)
|
_ramp_b = Vector2(_cursor.x, _cursor.z)
|
||||||
else:
|
_update_ramp_line()
|
||||||
amount = brush_strength * delta
|
return
|
||||||
|
var amount := _step_amount(_stroke_mode, delta)
|
||||||
# Während des Strichs NICHT auf die Format-Range clampen — die zieht der
|
# Während des Strichs NICHT auf die Format-Range clampen — die zieht der
|
||||||
# Range-Expander danach nach (sonst friert Heben/Senken an der Kante ein).
|
# Range-Expander danach nach (sonst friert Heben/Senken an der Kante ein).
|
||||||
var changed := _height_step(_cursor.x, _cursor.z, amount)
|
var changed := _height_step(_cursor.x, _cursor.z, amount)
|
||||||
|
|
@ -306,6 +356,17 @@ func _process(delta: float) -> void:
|
||||||
_ring.position.y = _height_at_stroke(_cursor.x, _cursor.z) + 0.3
|
_ring.position.y = _height_at_stroke(_cursor.x, _cursor.z) + 0.3
|
||||||
|
|
||||||
|
|
||||||
|
## Stärke dieses Frames: Meter (Heben/Senken) bzw. Mischfaktor 0–1 (Glätten/
|
||||||
|
## Einebnen) — skaliert mit dem Druck.
|
||||||
|
func _step_amount(mode: int, dt: float) -> float:
|
||||||
|
match mode:
|
||||||
|
TerrainField.Mode.SMOOTH:
|
||||||
|
return clampf(brush_strength / SMOOTH_RATE_DIVISOR * dt * pressure, 0.0, 1.0)
|
||||||
|
TerrainField.Mode.FLATTEN:
|
||||||
|
return clampf(brush_strength / FLATTEN_RATE_DIVISOR * dt * pressure, 0.0, 1.0)
|
||||||
|
return brush_strength * dt * pressure
|
||||||
|
|
||||||
|
|
||||||
## Höhe spannt das Höhenfeld automatisch auf, wenn der Pinsel über die
|
## Höhe spannt das Höhenfeld automatisch auf, wenn der Pinsel über die
|
||||||
## Format-Grenzen (min/max_height) hinaus will — sonst wäre Senken auf einer
|
## Format-Grenzen (min/max_height) hinaus will — sonst wäre Senken auf einer
|
||||||
## frisch angelegten (flachen) Map unmöglich und langes Heben würde clampen.
|
## frisch angelegten (flachen) Map unmöglich und langes Heben würde clampen.
|
||||||
|
|
@ -329,6 +390,11 @@ func _end_stroke() -> void:
|
||||||
if not _stroking:
|
if not _stroking:
|
||||||
return
|
return
|
||||||
_stroking = false
|
_stroking = false
|
||||||
|
if _stroke_mode == TerrainField.Mode.RAMP:
|
||||||
|
_ramp_line.visible = false
|
||||||
|
if not _ramp_apply():
|
||||||
|
main.call("_set_status", "Rampe: Start und Ende liegen zu nah beieinander — Klick, ziehen, loslassen.")
|
||||||
|
return # nichts geändert → kein leerer Undo-Eintrag
|
||||||
# Doc erst jetzt ändern: der GANZE Strich ist ein History-Eintrag.
|
# Doc erst jetzt ändern: der GANZE Strich ist ein History-Eintrag.
|
||||||
var before: Dictionary = main.history.snapshot(["terrain"])
|
var before: Dictionary = main.history.snapshot(["terrain"])
|
||||||
var terrain: Dictionary = main.doc.get("terrain", {})
|
var terrain: Dictionary = main.doc.get("terrain", {})
|
||||||
|
|
@ -346,6 +412,40 @@ func _end_stroke() -> void:
|
||||||
main.call("_on_terrain_stroke_committed")
|
main.call("_on_terrain_stroke_committed")
|
||||||
|
|
||||||
|
|
||||||
|
## Rampe anwenden (Strichende): Start-Höhe = Höhe beim Klick, End-Höhe = Höhe
|
||||||
|
## unter dem Endpunkt. Breite = 2 × Radius. Symmetrie wie bei den Pinseln.
|
||||||
|
func _ramp_apply() -> bool:
|
||||||
|
if _ramp_a.distance_to(_ramp_b) < 1.0:
|
||||||
|
return false
|
||||||
|
var hb := _height_at_stroke(_ramp_b.x, _ramp_b.y)
|
||||||
|
var width := brush_radius * 2.0
|
||||||
|
var snap := {}
|
||||||
|
var g := _world_to_grid(_ramp_a.x, _ramp_a.y)
|
||||||
|
if _sym_active():
|
||||||
|
var g2 := _world_to_grid(_ramp_b.x, _ramp_b.y)
|
||||||
|
var r := _brush_region(g).merge(_brush_region(g2))
|
||||||
|
snap = _snap_heights(r)
|
||||||
|
var changed := TerrainField.apply_ramp(_heights, _res, _sx, _sz,
|
||||||
|
_ramp_a, _ramp_b, _ramp_ha, hb, width, brush_falloff)
|
||||||
|
if changed and not snap.is_empty():
|
||||||
|
_mirror_heights(snap, g)
|
||||||
|
if changed:
|
||||||
|
_expand_range_if_needed()
|
||||||
|
view_builder.call("update_terrain", _heights, _mats)
|
||||||
|
return changed
|
||||||
|
|
||||||
|
|
||||||
|
## Rampen-Vorschau: gelbe Linie vom Startpunkt zum Cursor (auf Geländehöhe).
|
||||||
|
func _update_ramp_line() -> void:
|
||||||
|
var im := _ramp_line.mesh as ImmediateMesh
|
||||||
|
im.clear_surfaces()
|
||||||
|
im.surface_begin(Mesh.PRIMITIVE_LINES)
|
||||||
|
im.surface_add_vertex(Vector3(_ramp_a.x, _ramp_ha + 0.5, _ramp_a.y))
|
||||||
|
im.surface_add_vertex(Vector3(_ramp_b.x, _height_at_stroke(_ramp_b.x, _ramp_b.y) + 0.5, _ramp_b.y))
|
||||||
|
im.surface_end()
|
||||||
|
_ramp_line.visible = true
|
||||||
|
|
||||||
|
|
||||||
## Programmgesteuerter Stroke (Demo/Screenshot-Hook): macht exakt, was eine
|
## Programmgesteuerter Stroke (Demo/Screenshot-Hook): macht exakt, was eine
|
||||||
## Maus auch tun würde — inkl. History-Eintrag.
|
## Maus auch tun würde — inkl. History-Eintrag.
|
||||||
func demo_stroke(x: float, z: float, mode: int, seconds: float) -> void:
|
func demo_stroke(x: float, z: float, mode: int, seconds: float) -> void:
|
||||||
|
|
@ -358,11 +458,7 @@ func demo_stroke(x: float, z: float, mode: int, seconds: float) -> void:
|
||||||
var save_x := _cursor.x
|
var save_x := _cursor.x
|
||||||
var save_z := _cursor.z
|
var save_z := _cursor.z
|
||||||
for i in steps:
|
for i in steps:
|
||||||
var amount: float
|
var amount := _step_amount(mode, dt)
|
||||||
if mode == TerrainField.Mode.SMOOTH:
|
|
||||||
amount = clampf(brush_strength / SMOOTH_RATE_DIVISOR * dt, 0.0, 1.0)
|
|
||||||
else:
|
|
||||||
amount = brush_strength * dt
|
|
||||||
if mode == TerrainField.Mode.PAINT:
|
if mode == TerrainField.Mode.PAINT:
|
||||||
_paint_step(save_x, save_z)
|
_paint_step(save_x, save_z)
|
||||||
continue
|
continue
|
||||||
|
|
@ -472,7 +568,8 @@ func _height_step(x: float, z: float, amount: float) -> bool:
|
||||||
if _sym_active():
|
if _sym_active():
|
||||||
snap = _snap_heights(_brush_region(g))
|
snap = _snap_heights(_brush_region(g))
|
||||||
var changed := TerrainField.apply_brush(_heights, _res, _sx, _sz,
|
var changed := TerrainField.apply_brush(_heights, _res, _sx, _sz,
|
||||||
-100000.0, 100000.0, x, z, brush_radius, _stroke_mode, amount)
|
-100000.0, 100000.0, x, z, brush_radius, _stroke_mode, amount,
|
||||||
|
brush_falloff, _flatten_target)
|
||||||
if changed and not snap.is_empty():
|
if changed and not snap.is_empty():
|
||||||
_mirror_heights(snap, g)
|
_mirror_heights(snap, g)
|
||||||
return changed
|
return changed
|
||||||
|
|
@ -489,7 +586,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_mix(_mats, _mix_b, _mix_w, _mres,
|
var changed := TerrainField.apply_material_brush_mix(_mats, _mix_b, _mix_w, _mres,
|
||||||
_sx, _sz, x, z, brush_radius, _stroke_material, brush_weichheit)
|
_sx, _sz, x, z, brush_radius, _stroke_material, brush_weichheit, pressure)
|
||||||
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
|
||||||
|
|
@ -614,6 +711,10 @@ func _refresh_ring_mesh() -> void:
|
||||||
var mc: int = clampi(paint_material, 0, SvmapIO.MATERIAL_IDS.size() - 1)
|
var mc: int = clampi(paint_material, 0, SvmapIO.MATERIAL_IDS.size() - 1)
|
||||||
mat.albedo_color = (terrain_mesh_color(mc) as Color)
|
mat.albedo_color = (terrain_mesh_color(mc) as Color)
|
||||||
mat.albedo_color.a = 0.9
|
mat.albedo_color.a = 0.9
|
||||||
|
TerrainField.Mode.FLATTEN:
|
||||||
|
mat.albedo_color = Color(1.0, 0.85, 0.3, 0.9)
|
||||||
|
TerrainField.Mode.RAMP:
|
||||||
|
mat.albedo_color = Color(0.85, 0.5, 1.0, 0.9)
|
||||||
_:
|
_:
|
||||||
mat.albedo_color = Color(0.4, 0.7, 1.0, 0.9)
|
mat.albedo_color = Color(0.4, 0.7, 1.0, 0.9)
|
||||||
mat.emission_enabled = true
|
mat.emission_enabled = true
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ class_name TerrainField
|
||||||
##
|
##
|
||||||
## Der eigentliche Doc-Mutator bleibt main.gd/terrain_tool.gd — hier nur Mathematik.
|
## Der eigentliche Doc-Mutator bleibt main.gd/terrain_tool.gd — hier nur Mathematik.
|
||||||
|
|
||||||
enum Mode { RAISE, LOWER, SMOOTH, PAINT }
|
enum Mode { RAISE, LOWER, SMOOTH, PAINT, FLATTEN, RAMP }
|
||||||
|
|
||||||
## Editor-MVP-Perf-Limit (Karte t_9aba04f1): das Format erlaubt 17–513, aber
|
## Editor-MVP-Perf-Limit (Karte t_9aba04f1): das Format erlaubt 17–513, aber
|
||||||
## der Editor formt im MVP nur Grids bis 129 (≈ 32k Dreiecke — flüssig beim
|
## der Editor formt im MVP nur Grids bis 129 (≈ 32k Dreiecke — flüssig beim
|
||||||
|
|
@ -39,6 +39,99 @@ static func height_at(heights: PackedFloat32Array, res: int, size_x: float, size
|
||||||
return lerpf(lerpf(h00, h10, tx), lerpf(h01, h11, tx), tz)
|
return lerpf(lerpf(h00, h10, tx), lerpf(h01, h11, tx), tz)
|
||||||
|
|
||||||
|
|
||||||
|
## Pinsel-Gewicht über einen einzigen Regler (Karte 25.10.): t = 1 − d/radius,
|
||||||
|
## w = lerp(t, t·t, falloff). falloff 0 = linear (hart, breite Mitte),
|
||||||
|
## 1 = quadratisch (weich, spitze Mitte). Ausserhalb des Radius 0.
|
||||||
|
static func falloff_weight(d: float, radius: float, falloff: float) -> float:
|
||||||
|
if radius <= 0.0 or d >= radius:
|
||||||
|
return 0.0
|
||||||
|
var t := clampf(1.0 - d / radius, 0.0, 1.0)
|
||||||
|
return lerpf(t, t * t, clampf(falloff, 0.0, 1.0))
|
||||||
|
|
||||||
|
|
||||||
|
## Pinsel-Stärke aus Eingabe: Pen-Druck (>0) gewinnt, sonst Controller-Trigger
|
||||||
|
## (> 0.02), sonst Maus = 1.0. Immer 0.05…1.
|
||||||
|
static func pressure_of(pen: float, trigger: float) -> float:
|
||||||
|
if pen > 0.0:
|
||||||
|
return clampf(pen, 0.05, 1.0)
|
||||||
|
if trigger > 0.02:
|
||||||
|
return clampf(trigger, 0.05, 1.0)
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
|
||||||
|
## Flatten: zieht Höhen im Kreis gewichtet zur Zielhöhe. amount = Mischfaktor 0–1.
|
||||||
|
## (Eigene Funktion, damit apply_brush-Signatur stabil bleibt.)
|
||||||
|
static func apply_flatten(heights: PackedFloat32Array, res: int, size_x: float, size_z: float,
|
||||||
|
cx: float, cz: float, radius: float, target: float, amount: float,
|
||||||
|
falloff: float) -> bool:
|
||||||
|
if res < 2 or radius <= 0.0 or amount <= 0.0 or heights.size() < res * res:
|
||||||
|
return false
|
||||||
|
var cell_x := size_x / float(res - 1)
|
||||||
|
var cell_z := size_z / float(res - 1)
|
||||||
|
var gx := (cx / size_x + 0.5) * float(res - 1)
|
||||||
|
var gz := (cz / size_z + 0.5) * float(res - 1)
|
||||||
|
var x0 := maxi(int(floor(gx - radius / cell_x)) - 1, 0)
|
||||||
|
var x1 := mini(int(ceil(gx + radius / cell_x)) + 1, res - 1)
|
||||||
|
var z0 := maxi(int(floor(gz - radius / cell_z)) - 1, 0)
|
||||||
|
var z1 := mini(int(ceil(gz + radius / cell_z)) + 1, res - 1)
|
||||||
|
var changed := false
|
||||||
|
for z in range(z0, z1 + 1):
|
||||||
|
for x in range(x0, x1 + 1):
|
||||||
|
var dx := (float(x) - gx) * cell_x
|
||||||
|
var dz := (float(z) - gz) * cell_z
|
||||||
|
var w := falloff_weight(sqrt(dx * dx + dz * dz), radius, falloff)
|
||||||
|
if w <= 0.0:
|
||||||
|
continue
|
||||||
|
var idx := z * res + x
|
||||||
|
var h := heights[idx]
|
||||||
|
var nh := lerpf(h, target, clampf(amount * w, 0.0, 1.0))
|
||||||
|
if nh != h:
|
||||||
|
heights[idx] = nh
|
||||||
|
changed = true
|
||||||
|
return changed
|
||||||
|
|
||||||
|
|
||||||
|
## Rampe: Höhe linear von ha (Punkt a) nach hb (Punkt b), Breite `width` m
|
||||||
|
## quer zur Achse; Kern (halbe Breite) voll, Falloff am Rand. a/b = Welt-X/Z (Vector2). Rückgabe: geändert.
|
||||||
|
static func apply_ramp(heights: PackedFloat32Array, res: int, size_x: float, size_z: float,
|
||||||
|
a: Vector2, b: Vector2, ha: float, hb: float, width: float, falloff: float) -> bool:
|
||||||
|
if res < 2 or width <= 0.0 or heights.size() < res * res:
|
||||||
|
return false
|
||||||
|
var cell_x := size_x / float(res - 1)
|
||||||
|
var cell_z := size_z / float(res - 1)
|
||||||
|
var half := width * 0.5
|
||||||
|
var ab := b - a
|
||||||
|
var len2 := ab.length_squared()
|
||||||
|
var lo := Vector2(minf(a.x, b.x) - half, minf(a.y, b.y) - half)
|
||||||
|
var hi := Vector2(maxf(a.x, b.x) + half, maxf(a.y, b.y) + half)
|
||||||
|
var x0 := maxi(int(floor((lo.x / size_x + 0.5) * float(res - 1))) - 1, 0)
|
||||||
|
var x1 := mini(int(ceil((hi.x / size_x + 0.5) * float(res - 1))) + 1, res - 1)
|
||||||
|
var z0 := maxi(int(floor((lo.y / size_z + 0.5) * float(res - 1))) - 1, 0)
|
||||||
|
var z1 := mini(int(ceil((hi.y / size_z + 0.5) * float(res - 1))) + 1, res - 1)
|
||||||
|
var changed := false
|
||||||
|
for z in range(z0, z1 + 1):
|
||||||
|
for x in range(x0, x1 + 1):
|
||||||
|
var p := Vector2((float(x) / float(res - 1) - 0.5) * size_x,
|
||||||
|
(float(z) / float(res - 1) - 0.5) * size_z)
|
||||||
|
var s := 0.0 if len2 < 0.000001 else clampf((p - a).dot(ab) / len2, 0.0, 1.0)
|
||||||
|
var d := p.distance_to(a + ab * s)
|
||||||
|
# Innere Hälfte der Breite voll (ebene Fahrfläche), äußere Hälfte
|
||||||
|
# läuft mit dem Falloff-Regler ins Gelände aus.
|
||||||
|
var core := half * 0.5
|
||||||
|
var w := 1.0 if d <= core else falloff_weight(d - core, half - core, falloff)
|
||||||
|
if d >= half:
|
||||||
|
w = 0.0
|
||||||
|
if w <= 0.0:
|
||||||
|
continue
|
||||||
|
var idx := z * res + x
|
||||||
|
var h := heights[idx]
|
||||||
|
var nh := lerpf(h, lerpf(ha, hb, s), w)
|
||||||
|
if nh != h:
|
||||||
|
heights[idx] = nh
|
||||||
|
changed = true
|
||||||
|
return changed
|
||||||
|
|
||||||
|
|
||||||
## Pinsel anwenden. amount:
|
## Pinsel anwenden. amount:
|
||||||
## RAISE/LOWER → Meter (bereits strength × dt vom Aufrufer),
|
## RAISE/LOWER → Meter (bereits strength × dt vom Aufrufer),
|
||||||
## SMOOTH → Mischfaktor 0–1 (Anteil Richtung 3×3-Umgebungsmittel).
|
## SMOOTH → Mischfaktor 0–1 (Anteil Richtung 3×3-Umgebungsmittel).
|
||||||
|
|
@ -46,7 +139,14 @@ static func height_at(heights: PackedFloat32Array, res: int, size_x: float, size
|
||||||
## Kegel. Rückgabe true, wenn mindestens ein Punkt verändert wurde.
|
## Kegel. Rückgabe true, wenn mindestens ein Punkt verändert wurde.
|
||||||
static func apply_brush(heights: PackedFloat32Array, res: int, size_x: float, size_z: float,
|
static func apply_brush(heights: PackedFloat32Array, res: int, size_x: float, size_z: float,
|
||||||
hmin: float, hmax: float, cx: float, cz: float, radius: float,
|
hmin: float, hmax: float, cx: float, cz: float, radius: float,
|
||||||
mode: int, amount: float) -> bool:
|
mode: int, amount: float, falloff: float = -1.0, target: float = 0.0) -> bool:
|
||||||
|
## falloff < 0 = alter Kosinus-Falloff (Kompatibilität), sonst Regler-Falloff.
|
||||||
|
## target nur für FLATTEN (Zielhöhe).
|
||||||
|
if mode == Mode.RAMP:
|
||||||
|
return false # Rampe hat ein eigenes Zwei-Punkt-API (apply_ramp)
|
||||||
|
if mode == Mode.FLATTEN:
|
||||||
|
return apply_flatten(heights, res, size_x, size_z, cx, cz, radius, target,
|
||||||
|
clampf(amount, 0.0, 1.0), maxf(falloff, 0.0))
|
||||||
if mode == Mode.PAINT:
|
if mode == Mode.PAINT:
|
||||||
# PAINT läuft über apply_material_brush (Material-Schicht, Karte
|
# PAINT läuft über apply_material_brush (Material-Schicht, Karte
|
||||||
# t_603ec1a8) — Höhe bleibt unangetastet. Hier ohne Material-Argument
|
# t_603ec1a8) — Höhe bleibt unangetastet. Hier ohne Material-Argument
|
||||||
|
|
@ -66,7 +166,7 @@ static func apply_brush(heights: PackedFloat32Array, res: int, size_x: float, si
|
||||||
var z1 := mini(int(ceil(gz + r_cells_z)) + 1, res - 1)
|
var z1 := mini(int(ceil(gz + r_cells_z)) + 1, res - 1)
|
||||||
if mode == Mode.SMOOTH:
|
if mode == Mode.SMOOTH:
|
||||||
return _apply_smooth(heights, res, hmin, hmax, gx, gz, r_cells_x, r_cells_z,
|
return _apply_smooth(heights, res, hmin, hmax, gx, gz, r_cells_x, r_cells_z,
|
||||||
x0, x1, z0, z1, clampf(amount, 0.0, 1.0))
|
x0, x1, z0, z1, clampf(amount, 0.0, 1.0), falloff)
|
||||||
var changed := false
|
var changed := false
|
||||||
var z := z0
|
var z := z0
|
||||||
while z <= z1:
|
while z <= z1:
|
||||||
|
|
@ -78,7 +178,8 @@ static func apply_brush(heights: PackedFloat32Array, res: int, size_x: float, si
|
||||||
if d >= radius:
|
if d >= radius:
|
||||||
x += 1
|
x += 1
|
||||||
continue
|
continue
|
||||||
var w := 0.5 * (1.0 + cos(PI * d / radius))
|
var w := 0.5 * (1.0 + cos(PI * d / radius)) if falloff < 0.0 \
|
||||||
|
else falloff_weight(d, radius, falloff)
|
||||||
var idx := z * res + x
|
var idx := z * res + x
|
||||||
var h := heights[idx]
|
var h := heights[idx]
|
||||||
var nh := h + amount * w if mode == Mode.RAISE else h - amount * w
|
var nh := h + amount * w if mode == Mode.RAISE else h - amount * w
|
||||||
|
|
@ -131,10 +232,13 @@ static func apply_material_brush(mats: PackedByteArray, res: int, size_x: float,
|
||||||
## radius·(1−weichheit) wird voll gemalt, außen fällt der Anteil des neuen
|
## radius·(1−weichheit) wird voll gemalt, außen fällt der Anteil des neuen
|
||||||
## Materials weich auf 0 (Smoothstep). Ein Punkt wird nur verstärkt, nie
|
## Materials weich auf 0 (Smoothstep). Ein Punkt wird nur verstärkt, nie
|
||||||
## abgeschwächt (Nachmalen am Rand macht ihn kräftiger, nicht schwächer).
|
## abgeschwächt (Nachmalen am Rand macht ihn kräftiger, nicht schwächer).
|
||||||
## Mischung bleibt zweiteilig: das neue Material + das bisher stärkste andere.
|
## Mischung bleibt zweiteilig: das neue Material + das bisher stärkste andere
|
||||||
|
## (kommt ein drittes Material dazu, wird das schwächere der beiden ersetzt).
|
||||||
|
## staerke (0–1, Controller-Trigger/Pen-Druck) deckelt den Zielanteil.
|
||||||
static func apply_material_brush_mix(mats: PackedByteArray, mix_b: PackedByteArray,
|
static func apply_material_brush_mix(mats: PackedByteArray, mix_b: PackedByteArray,
|
||||||
mix_w: PackedByteArray, res: int, size_x: float, size_z: float,
|
mix_w: PackedByteArray, res: int, size_x: float, size_z: float,
|
||||||
cx: float, cz: float, radius: float, material_id: int, weichheit: float) -> bool:
|
cx: float, cz: float, radius: float, material_id: int, weichheit: float,
|
||||||
|
staerke: float = 1.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 \
|
||||||
or mix_b.size() < res * res or mix_w.size() < res * res:
|
or mix_b.size() < res * res or mix_w.size() < res * res:
|
||||||
return false
|
return false
|
||||||
|
|
@ -160,7 +264,7 @@ static func apply_material_brush_mix(mats: PackedByteArray, mix_b: PackedByteArr
|
||||||
if d > inner:
|
if d > inner:
|
||||||
var t := clampf((d - inner) / maxf(radius - inner, 0.0001), 0.0, 1.0)
|
var t := clampf((d - inner) / maxf(radius - inner, 0.0001), 0.0, 1.0)
|
||||||
f = 1.0 - t * t * (3.0 - 2.0 * t)
|
f = 1.0 - t * t * (3.0 - 2.0 * t)
|
||||||
var fb := int(roundf(f * 255.0))
|
var fb := int(roundf(f * clampf(staerke, 0.0, 1.0) * 255.0))
|
||||||
if fb <= 0:
|
if fb <= 0:
|
||||||
continue
|
continue
|
||||||
var idx := z * res + x
|
var idx := z * res + x
|
||||||
|
|
@ -175,7 +279,15 @@ static func apply_material_brush_mix(mats: PackedByteArray, mix_b: PackedByteArr
|
||||||
mix_b[idx] = material_id
|
mix_b[idx] = material_id
|
||||||
mix_w[idx] = 0
|
mix_w[idx] = 0
|
||||||
else:
|
else:
|
||||||
var andere := a if a != material_id else b
|
# Ziel in keinem Slot → das SCHWÄCHERE Slot-Material wird ersetzt
|
||||||
|
# (Anteil von b = w/255, a hat 255−w): w ≥ 128 → b bleibt, sonst a.
|
||||||
|
var andere: int
|
||||||
|
if a == material_id:
|
||||||
|
andere = b
|
||||||
|
elif b == material_id:
|
||||||
|
andere = a
|
||||||
|
else:
|
||||||
|
andere = b if w >= 128 else a
|
||||||
mats[idx] = andere
|
mats[idx] = andere
|
||||||
mix_b[idx] = material_id
|
mix_b[idx] = material_id
|
||||||
mix_w[idx] = fb
|
mix_w[idx] = fb
|
||||||
|
|
@ -187,52 +299,35 @@ static func apply_material_brush_mix(mats: PackedByteArray, mix_b: PackedByteArr
|
||||||
## 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,
|
||||||
gx: float, gz: float, r_cells_x: float, r_cells_z: float,
|
gx: float, gz: float, r_cells_x: float, r_cells_z: float,
|
||||||
x0: int, x1: int, z0: int, z1: int, amount: float) -> bool:
|
x0: int, x1: int, z0: int, z1: int, amount: float, falloff: float = -1.0) -> bool:
|
||||||
|
# Mittelwerte nur für das Pinsel-Rechteck (statt Puffer in Feldgröße pro Schritt).
|
||||||
|
var bw := x1 - x0 + 1
|
||||||
var avg := PackedFloat32Array()
|
var avg := PackedFloat32Array()
|
||||||
avg.resize(heights.size())
|
avg.resize(bw * (z1 - z0 + 1))
|
||||||
var z := z0
|
for z in range(z0, z1 + 1):
|
||||||
while z <= z1:
|
for x in range(x0, x1 + 1):
|
||||||
var x := x0
|
|
||||||
while x <= x1:
|
|
||||||
var dx := (float(x) - gx) / maxf(r_cells_x, 0.0001)
|
|
||||||
var dz := (float(z) - gz) / maxf(r_cells_z, 0.0001)
|
|
||||||
var d := sqrt(dx * dx + dz * dz)
|
|
||||||
if d >= 1.0:
|
|
||||||
x += 1
|
|
||||||
continue
|
|
||||||
var sum := 0.0
|
var sum := 0.0
|
||||||
var count := 0
|
var count := 0
|
||||||
var zi := maxi(z - 1, 0)
|
for zi in range(maxi(z - 1, 0), mini(z + 1, res - 1) + 1):
|
||||||
while zi <= mini(z + 1, res - 1):
|
for xi in range(maxi(x - 1, 0), mini(x + 1, res - 1) + 1):
|
||||||
var xi := maxi(x - 1, 0)
|
|
||||||
while xi <= mini(x + 1, res - 1):
|
|
||||||
sum += heights[zi * res + xi]
|
sum += heights[zi * res + xi]
|
||||||
count += 1
|
count += 1
|
||||||
xi += 1
|
avg[(z - z0) * bw + (x - x0)] = sum / float(maxi(count, 1))
|
||||||
zi += 1
|
|
||||||
avg[z * res + x] = sum / float(maxi(count, 1))
|
|
||||||
x += 1
|
|
||||||
z += 1
|
|
||||||
var changed := false
|
var changed := false
|
||||||
z = z0
|
for z in range(z0, z1 + 1):
|
||||||
while z <= z1:
|
for x in range(x0, x1 + 1):
|
||||||
var x := x0
|
|
||||||
while x <= x1:
|
|
||||||
var dx := (float(x) - gx) / maxf(r_cells_x, 0.0001)
|
var dx := (float(x) - gx) / maxf(r_cells_x, 0.0001)
|
||||||
var dz := (float(z) - gz) / maxf(r_cells_z, 0.0001)
|
var dz := (float(z) - gz) / maxf(r_cells_z, 0.0001)
|
||||||
var d := sqrt(dx * dx + dz * dz)
|
var d := sqrt(dx * dx + dz * dz)
|
||||||
if d >= 1.0:
|
if d >= 1.0:
|
||||||
x += 1
|
|
||||||
continue
|
continue
|
||||||
var w := 0.5 * (1.0 + cos(PI * d))
|
var w := falloff_weight(d, 1.0, falloff)
|
||||||
var idx := z * res + x
|
var idx := z * res + x
|
||||||
var nh := lerpf(heights[idx], avg[idx], clampf(amount * w, 0.0, 1.0))
|
var nh := lerpf(heights[idx], avg[(z - z0) * bw + (x - x0)], clampf(amount * w, 0.0, 1.0))
|
||||||
nh = clampf(nh, hmin, hmax)
|
nh = clampf(nh, hmin, hmax)
|
||||||
if nh != heights[idx]:
|
if nh != heights[idx]:
|
||||||
heights[idx] = nh
|
heights[idx] = nh
|
||||||
changed = true
|
changed = true
|
||||||
x += 1
|
|
||||||
z += 1
|
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1673,7 +1673,7 @@ func _unhandled_key_input(event: InputEvent) -> void:
|
||||||
if tool_mode == "terrain":
|
if tool_mode == "terrain":
|
||||||
_terrain_brush_size_step(key == KEY_BRACKETRIGHT)
|
_terrain_brush_size_step(key == KEY_BRACKETRIGHT)
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
KEY_1, KEY_2, KEY_3, KEY_4:
|
KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6:
|
||||||
# Terrain-Pinsel-Modi 1–4 (Karte t_55257792): Heben/Senken/Glätten/
|
# Terrain-Pinsel-Modi 1–4 (Karte t_55257792): Heben/Senken/Glätten/
|
||||||
# Malen — NUR im Terrain-Werkzeug (Konfliktfreiheit, Punkt 6; im
|
# Malen — NUR im Terrain-Werkzeug (Konfliktfreiheit, Punkt 6; im
|
||||||
# Objekt-/Bauen-Modus bleiben die Ziffern unbenutzt).
|
# Objekt-/Bauen-Modus bleiben die Ziffern unbenutzt).
|
||||||
|
|
@ -1924,15 +1924,19 @@ func _terrain_brush_mode_key(mode_idx: int) -> void:
|
||||||
btn = t.terrain_mode_smooth
|
btn = t.terrain_mode_smooth
|
||||||
3:
|
3:
|
||||||
btn = t.terrain_mode_paint
|
btn = t.terrain_mode_paint
|
||||||
|
4:
|
||||||
|
btn = t.terrain_mode_flatten
|
||||||
|
5:
|
||||||
|
btn = t.terrain_mode_ramp
|
||||||
if btn == null or terrain_tool == null:
|
if btn == null or terrain_tool == null:
|
||||||
return
|
return
|
||||||
btn.set_pressed(true)
|
btn.set_pressed(true)
|
||||||
var labels := ["Heben", "Senken", "Glätten", "Malen"]
|
var labels := ["Heben", "Senken", "Glätten", "Malen", "Einebnen", "Rampe"]
|
||||||
var extra := ""
|
var extra := ""
|
||||||
if mode_idx == 3:
|
if mode_idx == 3:
|
||||||
var mats: Array = SvmapIO.MATERIAL_IDS
|
var mats: Array = SvmapIO.MATERIAL_IDS
|
||||||
extra = " (Material: %s — mit der Palette rechts wechseln)" % str(mats[clampi(int(terrain_tool.get("paint_material")), 0, mats.size() - 1)])
|
extra = " (Material: %s — mit der Palette rechts wechseln)" % str(mats[clampi(int(terrain_tool.get("paint_material")), 0, mats.size() - 1)])
|
||||||
_set_status("Pinsel-Modus: %s%s — Linksklick-Ziehen formt/malt jetzt so (Ziffern 1–4 wechseln)." % [str(labels[mode_idx]), extra])
|
_set_status("Pinsel-Modus: %s%s — Linksklick-Ziehen formt/malt jetzt so (Ziffern 1–6 wechseln)." % [str(labels[mode_idx]), extra])
|
||||||
|
|
||||||
|
|
||||||
## Ein Pinsel-Strich ist fertig (Doc + History schon aktualisiert vom Tool).
|
## Ein Pinsel-Strich ist fertig (Doc + History schon aktualisiert vom Tool).
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@ var terrain_mode_raise: Button
|
||||||
var terrain_mode_lower: Button
|
var terrain_mode_lower: Button
|
||||||
var terrain_mode_smooth: Button
|
var terrain_mode_smooth: Button
|
||||||
var terrain_mode_paint: Button
|
var terrain_mode_paint: Button
|
||||||
|
var terrain_mode_flatten: Button
|
||||||
|
var terrain_mode_ramp: Button
|
||||||
|
var terrain_falloff_label: Label
|
||||||
|
var terrain_falloff_slider: HSlider
|
||||||
var terrain_palette: VBoxContainer
|
var terrain_palette: VBoxContainer
|
||||||
var terrain_mat_buttons: Array = [] # Gras/Sand/Fels (ButtonGroup)
|
var terrain_mat_buttons: Array = [] # Gras/Sand/Fels (ButtonGroup)
|
||||||
var terrain_size_btn_small: Button
|
var terrain_size_btn_small: Button
|
||||||
|
|
@ -49,8 +53,10 @@ func build() -> void:
|
||||||
add_child(terrain_panel)
|
add_child(terrain_panel)
|
||||||
|
|
||||||
terrain_panel.add_child(_field_label("Pinsel-Werkzeug:"))
|
terrain_panel.add_child(_field_label("Pinsel-Werkzeug:"))
|
||||||
var mode_row := HBoxContainer.new()
|
var mode_row := GridContainer.new()
|
||||||
mode_row.add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster
|
mode_row.columns = 3
|
||||||
|
mode_row.add_theme_constant_override("h_separation", UIScale.px(8)) # U7-Raster
|
||||||
|
mode_row.add_theme_constant_override("v_separation", UIScale.px(8))
|
||||||
terrain_panel.add_child(mode_row)
|
terrain_panel.add_child(mode_row)
|
||||||
var mode_group := ButtonGroup.new()
|
var mode_group := ButtonGroup.new()
|
||||||
terrain_mode_raise = _terrain_mode_button("Heben ⬆", mode_group,
|
terrain_mode_raise = _terrain_mode_button("Heben ⬆", mode_group,
|
||||||
|
|
@ -61,7 +67,13 @@ func build() -> void:
|
||||||
"Pinsel-Modus Glätten: Höhenunterschiede verrutschen (Taste 3)")
|
"Pinsel-Modus Glätten: Höhenunterschiede verrutschen (Taste 3)")
|
||||||
terrain_mode_paint = _terrain_mode_button("Malen 🖌", mode_group,
|
terrain_mode_paint = _terrain_mode_button("Malen 🖌", mode_group,
|
||||||
"Pinsel-Modus Malen: Material auf den Boden malen (Taste 4)")
|
"Pinsel-Modus Malen: Material auf den Boden malen (Taste 4)")
|
||||||
|
terrain_mode_flatten = _terrain_mode_button("Einebnen ▬", mode_group,
|
||||||
|
"Pinsel-Modus Einebnen: zieht den Boden auf die Höhe, die beim Strichstart unter dem Cursor war")
|
||||||
|
terrain_mode_ramp = _terrain_mode_button("Rampe ◢", mode_group,
|
||||||
|
"Pinsel-Modus Rampe: Klick = Start, ziehen und loslassen = Ende — Höhe läuft gleichmäßig dazwischen (Breite = Pinselgröße)")
|
||||||
terrain_mode_raise.button_pressed = true
|
terrain_mode_raise.button_pressed = true
|
||||||
|
terrain_mode_flatten.toggled.connect(_on_terrain_mode_changed)
|
||||||
|
terrain_mode_ramp.toggled.connect(_on_terrain_mode_changed)
|
||||||
terrain_mode_raise.toggled.connect(_on_terrain_mode_changed)
|
terrain_mode_raise.toggled.connect(_on_terrain_mode_changed)
|
||||||
terrain_mode_lower.toggled.connect(_on_terrain_mode_changed)
|
terrain_mode_lower.toggled.connect(_on_terrain_mode_changed)
|
||||||
terrain_mode_smooth.toggled.connect(_on_terrain_mode_changed)
|
terrain_mode_smooth.toggled.connect(_on_terrain_mode_changed)
|
||||||
|
|
@ -70,6 +82,8 @@ func build() -> void:
|
||||||
mode_row.add_child(terrain_mode_lower)
|
mode_row.add_child(terrain_mode_lower)
|
||||||
mode_row.add_child(terrain_mode_smooth)
|
mode_row.add_child(terrain_mode_smooth)
|
||||||
mode_row.add_child(terrain_mode_paint)
|
mode_row.add_child(terrain_mode_paint)
|
||||||
|
mode_row.add_child(terrain_mode_flatten)
|
||||||
|
mode_row.add_child(terrain_mode_ramp)
|
||||||
|
|
||||||
# Material-Palette (Karte t_603ec1a8): nur sichtbar im Malen-Modus.
|
# Material-Palette (Karte t_603ec1a8): nur sichtbar im Malen-Modus.
|
||||||
terrain_palette = VBoxContainer.new()
|
terrain_palette = VBoxContainer.new()
|
||||||
|
|
@ -153,6 +167,20 @@ func build() -> void:
|
||||||
terrain_strength_slider.value_changed.connect(_on_terrain_strength_changed)
|
terrain_strength_slider.value_changed.connect(_on_terrain_strength_changed)
|
||||||
terrain_panel.add_child(terrain_strength_slider)
|
terrain_panel.add_child(terrain_strength_slider)
|
||||||
|
|
||||||
|
# Pinsel-Kante (Falloff, Karte 25.10.): ein Regler hart ↔ weich für alle
|
||||||
|
# Höhen-Pinsel (Malen hat seinen eigenen Kanten-Regler weiter unten).
|
||||||
|
terrain_falloff_label = _field_label("Kante: 50 % weich")
|
||||||
|
terrain_panel.add_child(terrain_falloff_label)
|
||||||
|
terrain_falloff_slider = HSlider.new()
|
||||||
|
terrain_falloff_slider.min_value = 0.0
|
||||||
|
terrain_falloff_slider.max_value = 100.0
|
||||||
|
terrain_falloff_slider.step = 5.0
|
||||||
|
terrain_falloff_slider.value = 50.0
|
||||||
|
terrain_falloff_slider.custom_minimum_size = UIScale.v2(0, 32)
|
||||||
|
terrain_falloff_slider.tooltip_text = "Links = harte Kante (breiter Kern), rechts = weich auslaufend"
|
||||||
|
terrain_falloff_slider.value_changed.connect(_on_terrain_falloff_changed)
|
||||||
|
terrain_panel.add_child(terrain_falloff_slider)
|
||||||
|
|
||||||
# Malen kennt keine Stärke: das Material wird zellenweise gesetzt
|
# Malen kennt keine Stärke: das Material wird zellenweise gesetzt
|
||||||
# (Material-Schicht = ein Wert pro Rasterpunkt, weiche Übergänge macht
|
# (Material-Schicht = ein Wert pro Rasterpunkt, weiche Übergänge macht
|
||||||
# der Splat-Renderer). Statt eines wirkungslosen Reglers steht im
|
# der Splat-Renderer). Statt eines wirkungslosen Reglers steht im
|
||||||
|
|
@ -177,6 +205,9 @@ func build() -> void:
|
||||||
terrain_weich_slider.value_changed.connect(_on_terrain_weich_changed)
|
terrain_weich_slider.value_changed.connect(_on_terrain_weich_changed)
|
||||||
terrain_panel.add_child(terrain_weich_slider)
|
terrain_panel.add_child(terrain_weich_slider)
|
||||||
|
|
||||||
|
terrain_panel.add_child(_muted_label(
|
||||||
|
"Stärke folgt dem Stift-Druck bzw. dem Controller-Trigger (Maus = voll). "
|
||||||
|
+ "Rampe: Start klicken, ziehen, loslassen. Einebnen: Höhe am Strichstart wird zum Ziel."))
|
||||||
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 Steppe (Grundboden) zurück. "
|
"Linksklick-Ziehen formt bzw. malt (je nach Werkzeug), Strg+Linksklick macht das Gegenteil — im Malen-Modus malt Strg+Klick Steppe (Grundboden) 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."))
|
||||||
|
|
@ -257,6 +288,10 @@ func _selected_terrain_mode() -> int:
|
||||||
return TerrainField.Mode.SMOOTH
|
return TerrainField.Mode.SMOOTH
|
||||||
if terrain_mode_paint.button_pressed:
|
if terrain_mode_paint.button_pressed:
|
||||||
return TerrainField.Mode.PAINT
|
return TerrainField.Mode.PAINT
|
||||||
|
if terrain_mode_flatten != null and terrain_mode_flatten.button_pressed:
|
||||||
|
return TerrainField.Mode.FLATTEN
|
||||||
|
if terrain_mode_ramp != null and terrain_mode_ramp.button_pressed:
|
||||||
|
return TerrainField.Mode.RAMP
|
||||||
return TerrainField.Mode.RAISE
|
return TerrainField.Mode.RAISE
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -268,6 +303,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_falloff_slider != null:
|
||||||
|
terrain_falloff_slider.visible = not paint
|
||||||
|
terrain_falloff_label.visible = not paint
|
||||||
if terrain_weich_slider != null:
|
if terrain_weich_slider != null:
|
||||||
terrain_weich_slider.visible = paint
|
terrain_weich_slider.visible = paint
|
||||||
terrain_weich_label.visible = paint
|
terrain_weich_label.visible = paint
|
||||||
|
|
@ -329,6 +367,12 @@ func _on_terrain_weich_changed(value: float) -> void:
|
||||||
main.terrain_tool.call("set_weichheit", value / 100.0)
|
main.terrain_tool.call("set_weichheit", value / 100.0)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_terrain_falloff_changed(value: float) -> void:
|
||||||
|
terrain_falloff_label.text = "Kante: %d %% weich" % int(value) if value > 0.0 else "Kante: hart"
|
||||||
|
if main.terrain_tool != null:
|
||||||
|
main.terrain_tool.call("set_falloff", 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:
|
||||||
|
|
|
||||||
160
tests/test_pinsel_werkzeuge.gd
Normal file
160
tests/test_pinsel_werkzeuge.gd
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
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")
|
||||||
|
|
@ -180,6 +180,8 @@ func _test_inputmap_actions() -> void:
|
||||||
"editor_brush_lower": KEY_2,
|
"editor_brush_lower": KEY_2,
|
||||||
"editor_brush_smooth": KEY_3,
|
"editor_brush_smooth": KEY_3,
|
||||||
"editor_brush_paint": KEY_4,
|
"editor_brush_paint": KEY_4,
|
||||||
|
"editor_brush_flatten": KEY_5,
|
||||||
|
"editor_brush_ramp": KEY_6,
|
||||||
}
|
}
|
||||||
for action in expected_keys_2:
|
for action in expected_keys_2:
|
||||||
if not InputMap.has_action(action):
|
if not InputMap.has_action(action):
|
||||||
|
|
@ -918,4 +920,4 @@ func _test_brush_paint_mode() -> void:
|
||||||
tool.call("set_brush_mode", TerrainField.Mode.RAISE)
|
tool.call("set_brush_mode", TerrainField.Mode.RAISE)
|
||||||
_check(int(tool.get("brush_mode")) == TerrainField.Mode.RAISE, "set_brush_mode(Heben) bleibt Heben")
|
_check(int(tool.get("brush_mode")) == TerrainField.Mode.RAISE, "set_brush_mode(Heben) bleibt Heben")
|
||||||
tool.call("set_brush_mode", 99)
|
tool.call("set_brush_mode", 99)
|
||||||
_check(int(tool.get("brush_mode")) == TerrainField.Mode.PAINT, "Out-of-Range clampet auf den höchsten Modus")
|
_check(int(tool.get("brush_mode")) == TerrainField.Mode.RAMP, "Out-of-Range clampet auf den höchsten Modus")
|
||||||
|
|
@ -177,7 +177,7 @@ func _reference_heights(steps: int, x: float, z: float, mode: int) -> PackedFloa
|
||||||
var amount := STRENGTH * dt
|
var amount := STRENGTH * dt
|
||||||
for i in steps:
|
for i in steps:
|
||||||
TerrainField.apply_brush(ref, RES, SIZE, SIZE, -100000.0, 100000.0,
|
TerrainField.apply_brush(ref, RES, SIZE, SIZE, -100000.0, 100000.0,
|
||||||
x, z, RADIUS, mode, amount)
|
x, z, RADIUS, mode, amount, 0.5) # Falloff = Tool-Standard
|
||||||
return ref
|
return ref
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Regression: alle Editor-Testsuiten (Karte t_3a998ae4)
|
# Regression: alle Editor-Testsuiten (Karte t_3a998ae4)
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
GODOT=/opt/data/home/bin/godot4
|
GODOT=${GODOT:-/opt/data/home/bin/godot4}
|
||||||
rc=0
|
rc=0
|
||||||
for t in test_ux test_shortcuts test_editor_svmap test_terrain test_library test_glb_view test_spawns test_testrun test_bauen test_resize_dialog test_updater test_gui_smoke test_rmb_vorrang test_inspector; do
|
for t in test_ux test_shortcuts test_editor_svmap test_terrain test_library test_glb_view test_spawns test_testrun test_bauen test_resize_dialog test_updater test_gui_smoke test_rmb_vorrang test_inspector test_pinsel_werkzeuge test_terrain_mirror test_mal_fein; do
|
||||||
"$GODOT" --headless --path . --script "res://tests/$t.gd" > "/tmp/$t.log" 2>&1
|
"$GODOT" --headless --path . --script "res://tests/$t.gd" > "/tmp/$t.log" 2>&1
|
||||||
code=$?
|
code=$?
|
||||||
line=$(grep -E "ALL OK|FAILED" "/tmp/$t.log" | tail -1)
|
line=$(grep -E "ALL OK|FAILED" "/tmp/$t.log" | tail -1)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue