From 7f06e284720bb44edac56a254759218aa6f2341c Mon Sep 17 00:00:00 2001 From: slyphax Date: Sun, 25 Oct 2026 00:50:09 +0200 Subject: [PATCH] Malen mit Symmetrie: nur wirklich bemalte Punkte spiegeln (harte gerade Kanten weg) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sly 25.10.: „teilweise sehr harte Kanten in den Übergängen“. Ursache: bei aktiver Symmetrie kopierte jeder Mal-Schritt das GANZE Snapshot-Quadrat um den Pinsel auf die Gegenseite — dort vorhandene weiche Verläufe wurden an den Quadratkanten gerade abgeschnitten. Jetzt werden nur Punkte gespiegelt, die der Schritt verändert hat; überlappen beide Seiten, gewinnt wie bisher der näher am Strichpunkt liegende Wert. Test: test_terrain_mirror 109/109 (neuer Fall schlägt mit altem Code fehl). Co-Authored-By: Claude Opus 5.5 (1M context) --- src/editor/terrain_tool.gd | 16 ++++++++++++---- tests/test_terrain_mirror.gd | 37 +++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/editor/terrain_tool.gd b/src/editor/terrain_tool.gd index 54a2a2f..ad195a0 100644 --- a/src/editor/terrain_tool.gd +++ b/src/editor/terrain_tool.gd @@ -549,22 +549,30 @@ func _mirror_mats(snap: Dictionary, g: Vector2) -> void: var positions: Array = snap.keys() positions.sort_custom(func(a: Vector2i, b: Vector2i) -> bool: return (a.y * _mres + a.x) < (b.y * _mres + b.x)) + # Karte 25.10. (Sly: harte gerade Kanten mit Symmetrie): NUR Punkte spiegeln, + # die dieser Pinsel-Schritt wirklich verändert hat. Vorher wurde das ganze + # Snapshot-Quadrat auf die Gegenseite kopiert — weiche Verläufe drüben + # wurden an den Quadratkanten hart abgeschnitten. for pos: Vector2i in positions: if done.has(pos): continue - var idx: int = pos.y * _mres + pos.x - var mirror: Vector2i = SymmetryCore.mirror_cell(sym, pos.x, pos.y, _mres) done[pos] = true + var idx: int = pos.y * _mres + pos.x + if _punkt(idx) == int(snap[pos]): + continue # nicht bemalt → Gegenseite bleibt unangetastet + var mirror: Vector2i = SymmetryCore.mirror_cell(sym, pos.x, pos.y, _mres) if mirror == pos: continue var midx: int = mirror.y * _mres + mirror.x var value: int = _punkt(idx) - if snap.has(mirror): + if snap.has(mirror) and _punkt(midx) != int(snap[mirror]): + # Beide Seiten bemalt (Pinsel überlappt die Achse): der näher am + # Strichpunkt liegende Wert gewinnt, beide bekommen ihn. if _cell_dist(mirror, g, cell_x, cell_z) < _cell_dist(pos, g, cell_x, cell_z): value = _punkt(midx) _setze_punkt(idx, value) + done[mirror] = true _setze_punkt(midx, value) - done[mirror] = true # ----------------------------------------------------------- Ring-Optik ----- diff --git a/tests/test_terrain_mirror.gd b/tests/test_terrain_mirror.gd index 2d00dd3..4d38c61 100644 --- a/tests/test_terrain_mirror.gd +++ b/tests/test_terrain_mirror.gd @@ -63,6 +63,7 @@ func _run_tests() -> void: _test_off_mode_unchanged() _test_live_rebuild_inputs() _test_undo_one_step() + _test_mirror_nur_bemalte() # ------------------------------------------------------------- Hilfsbau ----- @@ -513,4 +514,38 @@ func _test_undo_one_step() -> void: func _world_to_cell(at: Vector2) -> Vector2i: var fx: int = clampi(int(roundf((at.x / SIZE + 0.5) * float(RES - 1))), 0, RES - 1) var fz: int = clampi(int(roundf((at.y / SIZE + 0.5) * float(RES - 1))), 0, RES - 1) - return Vector2i(fx, fz) \ No newline at end of file + return Vector2i(fx, fz) + +## Karte 25.10.: Mit Symmetrie wird NUR gespiegelt, was der Pinsel wirklich +## bemalt hat — die Gegenseite außerhalb des Spiegelkreises bleibt unberührt +## (vorher: ganzes Snapshot-Quadrat kopiert → harte gerade Kanten). +func _test_mirror_nur_bemalte() -> void: + print("Testgruppe: Spiegeln nur bemalter Punkte (Quadrat-Kanten-Bug)") + var parts := _make_tool(SymmetryCore.Mode.POINT) + var ttool = parts[0] + var doc: Dictionary = parts[3] + # Gegenseite vorbereiten: Fels-Fleck dort, wo das Spiegel-QUADRAT, aber + # nicht der Spiegel-KREIS des Strichs liegt (Ecke des Quadrats). + var t: Dictionary = doc["terrain"] + var mats := SvmapIO.decode_materials(t) + var cell := SIZE / float(RES - 1) + var ecke := Vector2(100.0 + RADIUS * 0.9, 60.0 + RADIUS * 0.9) # Spiegelpunkt der Quadratecke + var gx := int(roundf((ecke.x / SIZE + 0.5) * float(RES - 1))) + var gz := int(roundf((ecke.y / SIZE + 0.5) * float(RES - 1))) + mats[gz * RES + gx] = 2 + t["materials_r8_b64"] = SvmapIO.encode_materials(mats) + ttool.call("refresh_from_doc") + ttool.call("set_paint_material", 1) + ttool.set("brush_weichheit", 0.8) + _stroke(ttool, -100.0, -60.0, TerrainField.Mode.PAINT, 0.2) + var after := SvmapIO.decode_materials(doc["terrain"]) + _check(int(after[gz * RES + gx]) == 2, + "Fels-Fleck auf der Gegenseite (Quadratecke, außerhalb des Kreises) bleibt (Zelle %d,%d)" % [gx, gz]) + var mm := SymmetryCore.mirror_cell(SymmetryCore.Mode.POINT, + int(roundf((-100.0 / SIZE + 0.5) * float(RES - 1))), + int(roundf((-60.0 / SIZE + 0.5) * float(RES - 1))), RES) + var mix := SvmapIO.decode_mix(doc["terrain"]) + var share := func(i: int) -> float: + var w := float(mix[1][i]) / 255.0 + return (1.0 - w if after[i] == 1 else 0.0) + (w if mix[0][i] == 1 else 0.0) + _check(share.call(mm.y * RES + mm.x) > 0.99, "Strichmitte auf der Gegenseite gespiegelt (Sand)")