Malen mit Symmetrie: nur wirklich bemalte Punkte spiegeln (harte gerade Kanten weg)

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) <noreply@anthropic.com>
This commit is contained in:
slyphax 2026-10-25 00:50:09 +02:00
parent 4e973795f6
commit 7f06e28472
2 changed files with 48 additions and 5 deletions

View file

@ -549,22 +549,30 @@ func _mirror_mats(snap: Dictionary, g: Vector2) -> void:
var positions: Array = snap.keys() var positions: Array = snap.keys()
positions.sort_custom(func(a: Vector2i, b: Vector2i) -> bool: positions.sort_custom(func(a: Vector2i, b: Vector2i) -> bool:
return (a.y * _mres + a.x) < (b.y * _mres + b.x)) 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: for pos: Vector2i in positions:
if done.has(pos): if done.has(pos):
continue continue
var idx: int = pos.y * _mres + pos.x
var mirror: Vector2i = SymmetryCore.mirror_cell(sym, pos.x, pos.y, _mres)
done[pos] = true 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: if mirror == pos:
continue continue
var midx: int = mirror.y * _mres + mirror.x var midx: int = mirror.y * _mres + mirror.x
var value: int = _punkt(idx) 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): if _cell_dist(mirror, g, cell_x, cell_z) < _cell_dist(pos, g, cell_x, cell_z):
value = _punkt(midx) value = _punkt(midx)
_setze_punkt(idx, value) _setze_punkt(idx, value)
done[mirror] = true
_setze_punkt(midx, value) _setze_punkt(midx, value)
done[mirror] = true
# ----------------------------------------------------------- Ring-Optik ----- # ----------------------------------------------------------- Ring-Optik -----

View file

@ -63,6 +63,7 @@ func _run_tests() -> void:
_test_off_mode_unchanged() _test_off_mode_unchanged()
_test_live_rebuild_inputs() _test_live_rebuild_inputs()
_test_undo_one_step() _test_undo_one_step()
_test_mirror_nur_bemalte()
# ------------------------------------------------------------- Hilfsbau ----- # ------------------------------------------------------------- Hilfsbau -----
@ -514,3 +515,37 @@ 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 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) var fz: int = clampi(int(roundf((at.y / SIZE + 0.5) * float(RES - 1))), 0, RES - 1)
return Vector2i(fx, fz) 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)")