Karte t_5c94d098 Terrain-Blending v2: Splat-Texel-Dichte folgt der KARTENGROESSE
Gleiche Ursache wie im Client (BEWEISE.md in work/terrain_blending_v2_t_5c94d098_bilder/): splat_side_for(129) = 512 px = 2 m/Texel auf der 1024-m-Standardmap (8 m/Zelle) -> Gewichtsrampe in 2-m-Stufen, sichtbar als Baender/Stufen an der Materialgrenze. Aenderung Editor: - library/terrain_arrays.gd (Spiegel-Datei, byte-identisch zum Client): splat_side_for(res, map_size_m = 0.0) = MAX(4 Texel/Zelle, 1 m/Texel aus der Karten-Kantenlaenge), Zweierpotenz, geklemmt [256, 1024]; build_splat_bytes ohne Allokation je Texel + Schnellpfad (gleiche ID in allen vier Nachbarzellen) -> unveraenderte Bytes, deutlich schneller. - src/viewport/terrain_mesh.gd: _build_splat bekommt size_x/size_z und gibt sie an splat_side_for; der Live-Sub-Rect-Pfad greift nur noch, wenn auch die Kantengroesse stimmt (Size-Aenderung ohne res-Aenderung baut die Splat neu). - tests/test_terrain_splat.gd (Spiegel, identisch im Client): +6 Kartenmass-Checks (31/31). Suiten (frischer Forgejo-Klon): test_terrain_splat 31/31, test_terrain 155/155, test_terrain_mirror 106/106.
This commit is contained in:
parent
04c6c67ba8
commit
1d8763b881
4 changed files with 99 additions and 32 deletions
|
|
@ -31,17 +31,28 @@ class_name TerrainArrays
|
|||
# die zwei Layer: mix(tex(id1), tex(id0), w0). Datenformat UNVERÄNDERT — die
|
||||
# Zell-Schicht bleibt die Quelle der Wahrheit.
|
||||
#
|
||||
# Texel-Dichte: SPLAT_TEXELS_PER_CELL Texel je Terrain-Zelle (4 × res−1),
|
||||
# als Zweierpotenz geklemmt auf [SPLAT_SIDE_MIN, SPLAT_SIDE_MAX] — beim Malen
|
||||
# kann dann ein kleiner Sub-Rect neu abgeleitet werden (Live-Pfad).
|
||||
# Texel-Dichte: SPLAT_TEXELS_PER_CELL Texel je Terrain-Zelle (4 × res−1) UND
|
||||
# mindestens SPLAT_TARGET_M_PER_TEXEL Meter je Texel (Karte t_5c94d098 —
|
||||
# Messung: 8 m/Zelle × 4 = 2 m/Texel bei der 1024-m-Standardmap ergab eine
|
||||
# in 2-m-Stufen gerasterte Gewichtsrampe, sichtbar als Bänder/Stufen quer zur
|
||||
# Materialgrenze), als Zweierpotenz geklemmt auf [SPLAT_SIDE_MIN, SPLAT_SIDE_MAX]
|
||||
# — beim Malen kann dann ein kleiner Sub-Rect neu abgeleitet werden (Live-Pfad).
|
||||
const SPLAT_TEXELS_PER_CELL := 4
|
||||
const SPLAT_SIDE_MIN := 256
|
||||
const SPLAT_SIDE_MAX := 1024
|
||||
const SPLAT_TARGET_M_PER_TEXEL := 1.0
|
||||
|
||||
|
||||
## Splat-Textur-Kantengröße für ein res×res-Zellraster (Zweierpotenz).
|
||||
static func splat_side_for(res: int) -> int:
|
||||
## map_size_m = Kantenlänge der Map in Weltmetern (optional, 0 = nicht gegeben):
|
||||
## die Karten-GRÖSSE begrenzt die erreichbare Weichheit, nicht die Zellzahl —
|
||||
## dieselbe Zellzahl liefert auf einer großen Map viel gröbere Texel.
|
||||
## Ergebnis (Budget-geklemmt, 4 MB RGBA8 bei 1024²): 1024 m → 1024 (1 m/Texel),
|
||||
## 256 m → 512 (0,5 m/Texel), 4096 m → 1024 (4 m/Texel, Klemme).
|
||||
static func splat_side_for(res: int, map_size_m: float = 0.0) -> int:
|
||||
var target := maxi(1, res - 1) * SPLAT_TEXELS_PER_CELL
|
||||
if map_size_m > 0.0:
|
||||
target = maxi(target, int(ceilf(map_size_m / SPLAT_TARGET_M_PER_TEXEL)))
|
||||
var side := SPLAT_SIDE_MIN
|
||||
while side < target:
|
||||
side *= 2
|
||||
|
|
@ -95,6 +106,17 @@ static func build_splat_bytes(mats: PackedByteArray, res: int, side: int,
|
|||
iz1[j] = mini(j0 + 1, res - 1)
|
||||
fz[j] = t - float(j0)
|
||||
var ofs := 0
|
||||
# Wiederverwendete Kandidaten-Slots: die Ableitung läuft bei 1024² über
|
||||
# 1 Mio. Texel — Allokationen je Texel (alte Fassung: 2 Packed-Arrays +
|
||||
# Array-Literale) kosteten messbar Zeit (Karte t_5c94d098).
|
||||
var cids := PackedInt32Array()
|
||||
var cws := PackedFloat32Array()
|
||||
var mids := PackedInt32Array()
|
||||
var mws := PackedFloat32Array()
|
||||
cids.resize(4)
|
||||
cws.resize(4)
|
||||
mids.resize(4)
|
||||
mws.resize(4)
|
||||
for j in rh:
|
||||
var wz0 := 1.0 - fz[j]
|
||||
var wz1 := fz[j]
|
||||
|
|
@ -103,38 +125,68 @@ static func build_splat_bytes(mats: PackedByteArray, res: int, side: int,
|
|||
for i in rw:
|
||||
var wx0 := 1.0 - fx[i]
|
||||
var wx1 := fx[i]
|
||||
# ≤ 4 Zellen mit bilinearen Gewichten; Duplikat-IDs zusammenziehen:
|
||||
var ids := PackedInt32Array()
|
||||
var ws := PackedFloat32Array()
|
||||
var cells := PackedInt32Array([
|
||||
row0 + ix0[i], row0 + ix1[i], row1 + ix0[i], row1 + ix1[i]])
|
||||
var weights := [wx0 * wz0, wx1 * wz0, wx0 * wz1, wx1 * wz1]
|
||||
# Die vier Nachbarzellen (bilineare Gewichte) — Reihenfolge identisch
|
||||
# zur Vorversion (Referenz-Checks in tests/test_terrain_splat.gd).
|
||||
cids[0] = int(mats[row0 + ix0[i]])
|
||||
cids[1] = int(mats[row0 + ix1[i]])
|
||||
cids[2] = int(mats[row1 + ix0[i]])
|
||||
cids[3] = int(mats[row1 + ix1[i]])
|
||||
cws[0] = wx0 * wz0
|
||||
cws[1] = wx1 * wz0
|
||||
cws[2] = wx0 * wz1
|
||||
cws[3] = wx1 * wz1
|
||||
var a0 := cids[0]
|
||||
if a0 == cids[1] and a0 == cids[2] and a0 == cids[3]:
|
||||
# SCHNELLPFAD (Loch im Material, der Regelfall): alle vier Zellen
|
||||
# dieselbe ID → w0 = 1, keine Mischung. Überspringt das
|
||||
# Zusammenziehen der Gewichte (innerhalb einer Materialfläche
|
||||
# praktisch jeder Texel, macht die 1024²-Ableitung bezahlbar).
|
||||
out.encode_u8(ofs, a0)
|
||||
out.encode_u8(ofs + 1, 255)
|
||||
out.encode_u8(ofs + 2, a0)
|
||||
out.encode_u8(ofs + 3, 0)
|
||||
ofs += 4
|
||||
continue
|
||||
# Duplikat-IDs zusammenziehen (Gewichte aufsummieren):
|
||||
var n := 0
|
||||
for c in 4:
|
||||
var id := int(mats[cells[c]])
|
||||
var w: float = weights[c]
|
||||
var w := cws[c]
|
||||
if w <= 0.0:
|
||||
continue
|
||||
var cid := cids[c]
|
||||
var found := false
|
||||
for k in ids.size():
|
||||
if ids[k] == id:
|
||||
ws[k] += w
|
||||
for k in n:
|
||||
if mids[k] == cid:
|
||||
mws[k] += w
|
||||
found = true
|
||||
break
|
||||
if not found and w > 0.0:
|
||||
ids.append(id)
|
||||
ws.append(w)
|
||||
if not found:
|
||||
mids[n] = cid
|
||||
mws[n] = w
|
||||
n += 1
|
||||
if n == 0:
|
||||
# Kann rechnerisch nicht auftreten (die 4 Gewichte summieren zu 1);
|
||||
# defensiv: Texel = Layer 0 mit vollem Gewicht.
|
||||
out.encode_u8(ofs, 0)
|
||||
out.encode_u8(ofs + 1, 255)
|
||||
out.encode_u8(ofs + 2, 0)
|
||||
out.encode_u8(ofs + 3, 0)
|
||||
ofs += 4
|
||||
continue
|
||||
# Top-2 IDs nach Gewicht (Fenster von max. 4 Kandidaten):
|
||||
var id0 := ids[0]
|
||||
var w0 := ws[0]
|
||||
var id0 := mids[0]
|
||||
var w0 := mws[0]
|
||||
var id1 := id0
|
||||
var w1 := 0.0
|
||||
for k in range(1, ids.size()):
|
||||
if ws[k] > w0:
|
||||
for k in range(1, n):
|
||||
if mws[k] > w0:
|
||||
id1 = id0
|
||||
w1 = w0
|
||||
id0 = ids[k]
|
||||
w0 = ws[k]
|
||||
elif ws[k] > w1 or id1 == id0:
|
||||
id1 = ids[k]
|
||||
w1 = ws[k]
|
||||
id0 = mids[k]
|
||||
w0 = mws[k]
|
||||
elif mws[k] > w1 or id1 == id0:
|
||||
id1 = mids[k]
|
||||
w1 = mws[k]
|
||||
# Normalisieren (w0 + w1 = 1) und 8-bit-packen:
|
||||
var wsum := maxf(w0 + w1, 0.0001)
|
||||
var b0 := clampi(roundi(w0 / wsum * 255.0), 0, 255)
|
||||
|
|
|
|||
|
|
@ -319,10 +319,11 @@ func update_heights(heights: PackedFloat32Array, res: int = 0, size_x: float = 0
|
|||
var shm := mat as ShaderMaterial
|
||||
shm.set_shader_parameter("map_size_x", size_x)
|
||||
shm.set_shader_parameter("map_size_z", size_z)
|
||||
if splat_rect_cells.size.x > 0 and _splat_tex != null and _splat_res == res:
|
||||
if splat_rect_cells.size.x > 0 and _splat_tex != null and _splat_res == res \
|
||||
and _splat_side == TerrainArrays.splat_side_for(res, maxf(size_x, size_z)):
|
||||
_refresh_splat_region(splat_rect_cells)
|
||||
elif _splat_tex == null or _splat_res != res:
|
||||
_build_splat(shm, mats, res)
|
||||
else:
|
||||
_build_splat(shm, mats, res, size_x, size_z)
|
||||
|
||||
|
||||
## Höhenlinien-Uniform an die aktuelle Situation anpassen (Pref ∧ Relief ≥ 2 m).
|
||||
|
|
@ -381,11 +382,14 @@ func resolution() -> int:
|
|||
## (mats, res²) bleibt die Quelle der Wahrheit — die Textur ist reine
|
||||
## Ableitung (Datenformat unverändert). Unbrauchbare Schicht → alte Textur
|
||||
## bleibt (Robustheit, kein Crash).
|
||||
func _build_splat(mat: ShaderMaterial, mats: PackedByteArray, res: int) -> void:
|
||||
## size_x/size_z (t_5c94d098): Karten-Kantenlänge in Metern → Texel-Dichte der
|
||||
## Splat (1 m/Texel bei 1024 m; vorher 2 m/Texel = sichtbare Stufen).
|
||||
func _build_splat(mat: ShaderMaterial, mats: PackedByteArray, res: int,
|
||||
size_x: float = 0.0, size_z: float = 0.0) -> void:
|
||||
if res < 2:
|
||||
_splat_tex = null
|
||||
return
|
||||
var side := TerrainArrays.splat_side_for(res)
|
||||
var side := TerrainArrays.splat_side_for(res, maxf(size_x, size_z))
|
||||
var bytes := TerrainArrays.build_splat_bytes(mats, res, side)
|
||||
if bytes.size() != side * side * 4:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -53,6 +53,16 @@ func _test_side_for() -> void:
|
|||
_check(TerrainArrays.splat_side_for(129) == 512, "res 129 → 512 (Ziel 512)")
|
||||
_check(TerrainArrays.splat_side_for(257) == 1024, "res 257 → 1024 (Ziel 1024)")
|
||||
_check(TerrainArrays.splat_side_for(513) == 1024, "res 513 → 1024 (Maximum-Klemme)")
|
||||
# Karten-KANTENLÄNGE (Karte t_5c94d098): die Texel-Dichte hängt auch an der
|
||||
# Map-Größe, nicht nur an der Zellzahl — 129 Zellen sind auf einer
|
||||
# 1024-m-Map 8 m/Zelle (4 Texel/Zelle = 2 m/Texel, sichtbare Stufen), auf
|
||||
# einer 256-m-Map 2 m/Zelle (4 Texel/Zelle = 0,5 m/Texel, fein genug).
|
||||
_check(TerrainArrays.splat_side_for(129, 256.0) == 512, "res 129 + 256 m → 512 (0,5 m/Texel)")
|
||||
_check(TerrainArrays.splat_side_for(129, 1024.0) == 1024, "res 129 + 1024 m → 1024 (1 m/Texel statt 2)")
|
||||
_check(TerrainArrays.splat_side_for(129, 2048.0) == 1024, "res 129 + 2048 m → 1024 (Budget-Klemme)")
|
||||
_check(TerrainArrays.splat_side_for(257, 1024.0) == 1024, "res 257 + 1024 m → 1024")
|
||||
_check(TerrainArrays.splat_side_for(65, 512.0) == 512, "res 65 + 512 m → 512 (Meter-Ziel gewinnt)")
|
||||
_check(TerrainArrays.splat_side_for(129, 0.0) == 512, "ohne Kartenmaß → alte res-Regel (512)")
|
||||
|
||||
|
||||
func _test_uniform_region() -> void:
|
||||
|
|
|
|||
1
tests/test_terrain_splat.gd.uid
Normal file
1
tests/test_terrain_splat.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://yo5xqgub0r50
|
||||
Loading…
Reference in a new issue