WYSIWYG Camo-Texturen: Live-Viewport lädt Multicam-Texturen (Cache, Warnung bei Fehlen)
This commit is contained in:
parent
c5bdb0518e
commit
aa94bde708
6 changed files with 383 additions and 58 deletions
|
|
@ -29,6 +29,29 @@ const MANIFEST_PATH := "res://library/objects.json"
|
|||
static var _manifest: Dictionary = {}
|
||||
static var _manifest_geladen := false
|
||||
|
||||
# Camo-Fix (t_50a07d4b): GLTFDocument löst externe Textur-URIs (Sibling-Repos,
|
||||
# außerhalb res://) NICHT auf — im Editor-Viewport blieben die GLB-Objekte
|
||||
# untexturiert/Editor-Farbe, während sie im Spiel getextured sind
|
||||
# (WYSIWYG-Bruch, belegt in t_c8748efc). camo_tex_setzen lädt die Multicam-
|
||||
# Textur DIREKT von der Platte (Image.load_from_file, ohne Import-System) und
|
||||
# setzt sie auf alle Flächen ohne albedo_texture — derselbe Mechanismus wie
|
||||
# im Thumbnail-Pfad (gen_thumbs.gd). Zuordnung je Kategorie exakt wie in
|
||||
# tools/CAMO_TEXTURE_MAPPING.md (Client-Repo, verbindlich):
|
||||
# _1 = Objekte, _2 = Boden (nature: ground*/decal*), _3 = ungenutzt.
|
||||
## Textur-Praefixe der nature-Unterkategorie, die Bodentextur _2 bekommen.
|
||||
const CAMO_TEX_BODEN_PRAEFIXE := ["ground", "decal"]
|
||||
|
||||
# ImageTexture pro Textur-Pfad gecacht — sonst erzeugt jede der 291 GLBs
|
||||
# ihre eigene 2048²-Textur (~16 MB VRAM je Stück statt 2 × 16 MB gesamt).
|
||||
# false = Ladeversuch fehlgeschlagen (kein endloser Retry + Warn-Flut).
|
||||
static var _camo_tex_cache: Dictionary = {}
|
||||
## Dekodierte Multicam-Bilder (RAM, 2 × 2048² ≈ 32 MB) für den Inhalts-
|
||||
## Vergleich gegen GLTF-eigene Textur-Instanzen.
|
||||
static var _camo_bild_cache: Dictionary = {}
|
||||
## Urteil pro GLTF-Textur-Instanz (instance_id → geteilte Textur | false),
|
||||
## damit der 16-MB-Datenvergleich nur einmal je Instanz läuft.
|
||||
static var _camo_ersetzung_cache: Dictionary = {}
|
||||
|
||||
# Cache pro OID: Mesh (normalisiert) oder `false` = GLB gefunden, aber nicht
|
||||
# ladbar (Parse-Fehler — kein endloser Retry pro Rebuild). Fehlende Datei
|
||||
# bleibt UNGECACHED (nur ein billiger Datei-Check pro Rebuild; legt man die
|
||||
|
|
@ -89,6 +112,7 @@ static func mesh_fuer(oid: String) -> Mesh:
|
|||
if FileAccess.file_exists(pfad):
|
||||
var m := _glb_mesh(pfad)
|
||||
if m != null:
|
||||
camo_tex_setzen(m, pfad)
|
||||
_cache[oid] = m
|
||||
return m
|
||||
# Datei da, aber kaputt → NEGATIV cachen (Parse-Fehler wiederholt
|
||||
|
|
@ -114,6 +138,9 @@ static func fehlt(oid: String) -> bool:
|
|||
## Cache leeren (Tests / nach manuellem Eingriff ins Mesh-Lager).
|
||||
static func cache_leeren() -> void:
|
||||
_cache.clear()
|
||||
_camo_tex_cache.clear()
|
||||
_camo_bild_cache.clear()
|
||||
_camo_ersetzung_cache.clear()
|
||||
_manifest.clear()
|
||||
_manifest_geladen = false
|
||||
|
||||
|
|
@ -200,3 +227,139 @@ static func _normalisiert(mesh: ArrayMesh) -> ArrayMesh:
|
|||
if mat != null:
|
||||
out.surface_set_material(out.get_surface_count() - 1, mat)
|
||||
return out
|
||||
|
||||
|
||||
# ------------------------------------------------------- Camo-Textur --------
|
||||
|
||||
## Camo-Fix (t_50a07d4b): Multicam-Textur von der Platte laden und auf alle
|
||||
## Flächen ohne albedo_texture setzen (GLTFDocument löst externe URIs der
|
||||
## Sibling-Repos nicht auf). Zuordnung exakt wie tools/CAMO_TEXTURE_MAPPING.md
|
||||
## bzw. gen_thumbs._camo_tex_setzen — diese Funktion ist ab jetzt DIE EINE
|
||||
## Quelle der Zuordnung (gen_thumbs delegiert hierher). Objekte außerhalb
|
||||
## military_desert/<kategorie>/<oid>.glb bleiben unangetastet. Fehlt die
|
||||
## Textur-Datei, wird klar gewarnt (push_warning) statt still weiß zu rendern.
|
||||
##
|
||||
## EINE-Quelle-Dedup (t_50a07d4b): GLTFDocument lädt externe Texturen
|
||||
## inzwischen selbst — aber als EIGENE ImageTexture-Instanz JEDES Parses
|
||||
## (291 GLBs = 291 × 16 MB VRAM statt 2 × 16 MB, bewiesen per Probe:
|
||||
## iid pro Parse unterschiedlich, Inhalt identisch zur Datei). Deshalb wird
|
||||
## hier ZUSÄTZLICH geprüft: stimmt der Inhalt einer GLTF-Textur mit einer
|
||||
## Multicam-Datei überein, ersetzen wir sie durch DIE eine geteilte Instanz
|
||||
## aus dem Cache — gleiche Optik (WYSIWYG bleibt), eine Quelle statt Kopien.
|
||||
## Eingebettete/Fremd-Texturen (Alt-Bestand, Waffen-GLBs) bleiben unangetastet.
|
||||
static func camo_tex_setzen(mesh: Mesh, glb_pfad: String) -> void:
|
||||
# Kategorie + OID aus dem Pfad: .../military_desert/<kategorie>/<oid>.glb
|
||||
var teile := glb_pfad.split("/")
|
||||
var kategorie := ""
|
||||
var oid := ""
|
||||
for i in teile.size():
|
||||
if teile[i] == "military_desert" and i + 2 < teile.size():
|
||||
kategorie = teile[i + 1]
|
||||
oid = teile[i + 2].get_basename()
|
||||
break
|
||||
if kategorie.is_empty():
|
||||
return
|
||||
var nr := 1
|
||||
if kategorie == "nature":
|
||||
for p in CAMO_TEX_BODEN_PRAEFIXE:
|
||||
if oid.begins_with(p):
|
||||
nr = 2
|
||||
break
|
||||
var tex_dir := glb_pfad.get_base_dir().path_join("../textures")
|
||||
# Kategorie-Textur (Fächer OHNE albedo_texture) + Vergleichs-Set: BEIDE
|
||||
# Multicam-Dateien — gebackene GLBs können Misch-Referenzen haben (z. B.
|
||||
# barrel_001: _2 + _1). Die Dedup-Zuordnung geschieht pro Fläche über den
|
||||
# INHALT, nicht per Kategorie-Blindregel.
|
||||
var kategorie_tex_pfad := tex_dir.path_join("desert_multicam_%d.png" % nr)
|
||||
var kandidaten: Array = [kategorie_tex_pfad]
|
||||
var andere_nr := 2 if nr == 1 else 1
|
||||
var andere_pfad := tex_dir.path_join("desert_multicam_%d.png" % andere_nr)
|
||||
if FileAccess.file_exists(andere_pfad):
|
||||
kandidaten.append(andere_pfad)
|
||||
var kategorie_tex := _camo_tex(kategorie_tex_pfad) # null + push_warning, wenn fehlt
|
||||
for s in mesh.get_surface_count():
|
||||
var mat := mesh.surface_get_material(s)
|
||||
if not (mat is BaseMaterial3D):
|
||||
continue
|
||||
var bm := mat as BaseMaterial3D
|
||||
var alt := bm.albedo_texture
|
||||
if alt == null:
|
||||
# Fläche ohne Textur (GLTF-Fallback) → Kategorie-Textur
|
||||
# (gen_thumbs-Mechanismus, t_c8748efc). Fehlt die Datei: warnt
|
||||
# _camo_tex (push_warning) statt still weiß zu rendern.
|
||||
if kategorie_tex != null:
|
||||
bm.albedo_texture = kategorie_tex
|
||||
else:
|
||||
push_warning("GlbMeshStore: %s (%s) bleibt untexturiert — Camo-Textur fehlt: %s"
|
||||
% [oid, kategorie, kategorie_tex_pfad])
|
||||
continue
|
||||
var ersetzung := _camo_ersetzung_fuer(alt, kandidaten)
|
||||
if ersetzung != null:
|
||||
bm.albedo_texture = ersetzung
|
||||
|
||||
|
||||
## Dekodierte Multicam-Bilddatei (RAM-gecacht, einmal je Pfad dekodiert).
|
||||
## warnen=true: fehlende/nicht ladbare Datei → push_warning (Karten-Pflicht:
|
||||
## klar warnen statt still grau/weiß zu rendern). null = nicht ladbar.
|
||||
static func _camo_image(pfad: String, warnen: bool) -> Image:
|
||||
if _camo_bild_cache.has(pfad):
|
||||
var v: Variant = _camo_bild_cache[pfad]
|
||||
return v if v is Image else null
|
||||
if not FileAccess.file_exists(pfad):
|
||||
if warnen:
|
||||
push_warning("GlbMeshStore: Camo-Textur fehlt: %s" % pfad)
|
||||
_camo_bild_cache[pfad] = false
|
||||
return null
|
||||
var img := Image.load_from_file(pfad)
|
||||
if img == null:
|
||||
if warnen:
|
||||
push_warning("GlbMeshStore: Camo-Textur nicht ladbar: %s" % pfad)
|
||||
_camo_bild_cache[pfad] = false
|
||||
return null
|
||||
_camo_bild_cache[pfad] = img
|
||||
return img
|
||||
|
||||
|
||||
## DIE EINE geteilte ImageTexture für eine Multicam-Datei (VRAM-Cache).
|
||||
## Alle getextureden GLB-Flächen teilen sich genau diese Instanz.
|
||||
static func _camo_tex(tex_pfad: String) -> ImageTexture:
|
||||
if _camo_tex_cache.has(tex_pfad):
|
||||
var v: Variant = _camo_tex_cache[tex_pfad]
|
||||
return v if v is ImageTexture else null
|
||||
var img := _camo_image(tex_pfad, true)
|
||||
if img == null:
|
||||
_camo_tex_cache[tex_pfad] = false
|
||||
return null
|
||||
var tex := ImageTexture.create_from_image(img)
|
||||
_camo_tex_cache[tex_pfad] = tex
|
||||
return tex
|
||||
|
||||
|
||||
## Ersetzt eine GLTF-eigene ImageTexture durch die geteilte Multicam-Instanz,
|
||||
## WENN der Inhalt (Größe, Format, Pixeldaten) mit einer der Kandidaten-
|
||||
## Dateien übereinstimmt. null = kein Match → Textur bleibt unangetastet
|
||||
## (eingebettete/Fremd-Texturen). Urteil je Instanz gecacht.
|
||||
static func _camo_ersetzung_fuer(tex: Texture2D, kandidaten: Array) -> ImageTexture:
|
||||
if not (tex is ImageTexture):
|
||||
return null
|
||||
var iid := tex.get_instance_id()
|
||||
if _camo_ersetzung_cache.has(iid):
|
||||
var v: Variant = _camo_ersetzung_cache[iid]
|
||||
return v if v is ImageTexture else null
|
||||
var img: Image = (tex as ImageTexture).get_image()
|
||||
if img == null:
|
||||
_camo_ersetzung_cache[iid] = false
|
||||
return null
|
||||
var out: ImageTexture = null
|
||||
for pfad in kandidaten:
|
||||
var vergleich := _camo_image(str(pfad), false)
|
||||
if vergleich == null:
|
||||
continue
|
||||
if img.get_width() == vergleich.get_width() \
|
||||
and img.get_height() == vergleich.get_height() \
|
||||
and img.get_format() == vergleich.get_format() \
|
||||
and img.data == vergleich.data:
|
||||
out = _camo_tex(str(pfad))
|
||||
break
|
||||
_camo_ersetzung_cache[iid] = out if out != null else false
|
||||
return out
|
||||
|
|
|
|||
22
tests/probe_cache_keys.gd
Normal file
22
tests/probe_cache_keys.gd
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
extends SceneTree
|
||||
## WEGWERF-Diagnose (t_50a07d4b): Warum hat _camo_tex_cache 4 Einträge?
|
||||
## Druckt die Pfade (Keys) des Caches nach dem Aufbau der Testszene.
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
_run.call_deferred()
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var scene: PackedScene = load("res://scenes/main.tscn")
|
||||
var main = scene.instantiate()
|
||||
root.add_child(main)
|
||||
await process_frame
|
||||
await process_frame
|
||||
var main_glb: Array = main.get("glb_quellen")
|
||||
print("main.gd glb_quellen=%s" % str(main_glb))
|
||||
var store = load("res://src/viewport/glb_mesh_store.gd")
|
||||
var cache: Dictionary = store.get("_camo_tex_cache")
|
||||
for k in cache.keys():
|
||||
print(" CACHE_KEY: '%s'" % str(k))
|
||||
quit(0)
|
||||
82
tests/probe_camo_render.gd
Normal file
82
tests/probe_camo_render.gd
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
extends SceneTree
|
||||
## WEGWERF-Beweis-Probe (Karte t_50a07d4b): Editor-Ansicht mit echten
|
||||
## Camo-GLBs — WYSIWYG-Check. 4 Objekte (Wall, Ground, Barrel, Cactus)
|
||||
## im echten Map Builder, CAMO-Zuordnung wie im Spiel. Screenshot nach
|
||||
## PROBE_PNG-Umgebungsvariable. Zahlen-Beweis zusätzlich auf stdout.
|
||||
## Aufruf: xvfb-run -a godot4 --path . --rendering-driver opengl3 \
|
||||
## -s res://tests/probe_camo_render.gd
|
||||
|
||||
const OBJEKTE := [
|
||||
{"id": "wall_001", "pos": [4.0, 0.0, 4.0], "scale": 1.0},
|
||||
{"id": "ground_001", "pos": [10.0, 0.0, 4.0], "scale": 1.0},
|
||||
{"id": "barrel_001", "pos": [16.0, 0.0, 4.0], "scale": 1.0},
|
||||
{"id": "cactus_001", "pos": [22.0, 0.0, 4.0], "scale": 1.0},
|
||||
]
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
_run.call_deferred()
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
# GLB-Quelle = Client-Repo (wie im Spiel-Stand, t_c8748efc-Beweis).
|
||||
ProjectSettings.set_setting("scopedvr/editor/glb_quellen",
|
||||
["../scopedvr-client/assets/objekte"])
|
||||
load("res://src/viewport/glb_mesh_store.gd").call("cache_leeren")
|
||||
|
||||
var scene: PackedScene = load("res://scenes/main.tscn")
|
||||
var main = scene.instantiate()
|
||||
root.add_child(main)
|
||||
await process_frame
|
||||
await process_frame
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
|
||||
DisplayServer.window_set_size(Vector2i(1600, 900))
|
||||
for p in OBJEKTE:
|
||||
(main.doc["objects"] as Array).append(p.duplicate(true))
|
||||
main.call("_after_structure_change")
|
||||
await process_frame
|
||||
await process_frame
|
||||
var cam = main.get("camera")
|
||||
if cam != null:
|
||||
cam.set("distance", 32.0)
|
||||
cam.call("focus_on", Vector3(12.0, 0.0, 4.0))
|
||||
await process_frame
|
||||
await process_frame
|
||||
await process_frame
|
||||
|
||||
# Zahlen-Beweis: JEDE GLB-Fläche trägt die GEMEINSAME Camo-Textur-Instanz.
|
||||
var builder = main.get("view_builder")
|
||||
var store = load("res://src/viewport/glb_mesh_store.gd")
|
||||
var cache: Dictionary = store.get("_camo_tex_cache")
|
||||
print("EINE-QUELLE-CHECK: Cache-Größe=%d (soll 2: Objekt _1 + Boden _2)" % cache.size())
|
||||
var alle_ok := true
|
||||
for i in OBJEKTE.size():
|
||||
var marker = builder.object_markers[i]
|
||||
var mi := marker.get_child(0) as MeshInstance3D
|
||||
var oid: String = OBJEKTE[i]["id"]
|
||||
if mi.mesh == null:
|
||||
print(" FAIL: %s mesh null" % oid)
|
||||
alle_ok = false
|
||||
continue
|
||||
var n := mi.mesh.get_surface_count()
|
||||
var tex_iids := []
|
||||
for s in n:
|
||||
var mat := mi.mesh.surface_get_material(s)
|
||||
if mat is BaseMaterial3D:
|
||||
var t := (mat as BaseMaterial3D).albedo_texture
|
||||
tex_iids.append(t.get_instance_id() if t != null else -1)
|
||||
print(" %s: %d Flächen, Textur-iids=%s" % [oid, n, str(tex_iids)])
|
||||
for iid in tex_iids:
|
||||
if int(iid) == -1:
|
||||
alle_ok = false
|
||||
print(" FAIL: %s Fläche OHNE albedo_texture" % oid)
|
||||
print("EINE-QUELLE-RESULT: %s" % ("ALLE GETEXTURED" if alle_ok else "FEHLER"))
|
||||
|
||||
# Screenshot
|
||||
var img := root.get_viewport().get_texture().get_image()
|
||||
var pfad := OS.get_environment("PROBE_PNG")
|
||||
if pfad == "":
|
||||
pfad = "/tmp/probe_camo.png"
|
||||
img.save_png(pfad)
|
||||
print("PROBE_PNG=%s" % pfad)
|
||||
quit(0 if alle_ok else 1)
|
||||
43
tests/probe_getimg.gd
Normal file
43
tests/probe_getimg.gd
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
extends SceneTree
|
||||
## WEGWERF-Probe: Prüft, ob ImageTexture.get_image() im Headless-Modus
|
||||
## funktioniert (fuer Inhaltsvergleich GLTF-Textur vs. Cache-Textur)
|
||||
## und ob die GLTF-Textur wirklich identischen Inhalt hat wie die Datei.
|
||||
|
||||
const PFAD := "/opt/data/scopedvr/work/sync_c8748efc/scopedvr-client/assets/objekte/military_desert/walls/wall_001.glb"
|
||||
const TEX := "/opt/data/scopedvr/work/sync_c8748efc/scopedvr-client/assets/objekte/military_desert/textures/desert_multicam_1.png"
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
_run.call_deferred()
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var gltf_mod := load("res://src/viewport/glb_mesh_store.gd")
|
||||
var res = gltf_mod.call("_glb_mesh", PFAD)
|
||||
if res == null:
|
||||
print("FEHLER: _glb_mesh null")
|
||||
quit(1)
|
||||
return
|
||||
var mesh: Mesh = res
|
||||
for si in mesh.get_surface_count():
|
||||
var mat = mesh.surface_get_material(si)
|
||||
if mat is BaseMaterial3D:
|
||||
var tex: Texture2D = (mat as BaseMaterial3D).albedo_texture
|
||||
print("surface %d: tex=%s iid=%d" % [si, tex.get_class(), tex.get_instance_id()])
|
||||
if tex is ImageTexture:
|
||||
var img: Image = (tex as ImageTexture).get_image()
|
||||
if img == null:
|
||||
print(" get_image() -> NULL (Headless kann nicht lesen)")
|
||||
else:
|
||||
print(" get_image() -> %dx%d format=%d" % [img.get_width(), img.get_height(), img.get_format()])
|
||||
var p: Color = img.get_pixel(0, 0)
|
||||
print(" pixel00=%s" % str(p))
|
||||
var p2: Color = img.get_pixel(1023, 1023)
|
||||
print(" pixel1023_1023=%s" % str(p2))
|
||||
# Unsere Version direkt von der Platte zum Vergleich
|
||||
var ours: Image = Image.load_from_file(TEX)
|
||||
if ours != null:
|
||||
print("unsere Datei: %dx%d format=%d pixel00=%s pixel1023=%s" % [
|
||||
ours.get_width(), ours.get_height(), ours.get_format(),
|
||||
str(ours.get_pixel(0, 0)), str(ours.get_pixel(1023, 1023))])
|
||||
quit(0)
|
||||
|
|
@ -41,6 +41,7 @@ func _run_tests() -> void:
|
|||
_test_store_ladet_moosfelsen()
|
||||
_test_store_normalisierung()
|
||||
_test_store_cache()
|
||||
_test_camo_tex()
|
||||
_test_viewport_rebuild_echt()
|
||||
_test_viewport_rebuild_fehlend()
|
||||
|
||||
|
|
@ -114,6 +115,74 @@ func _test_store_cache() -> void:
|
|||
_check(GlbMeshStore.mesh_fuer(PROBE_OID) != null, "cache_leeren → wird neu geladen")
|
||||
|
||||
|
||||
# ------------------------------------------------------------ Camo-Textur ----
|
||||
|
||||
## Beweis (t_50a07d4b): das Live-GLB-Mesh trägt ECHTE Multicam-Texturen
|
||||
## (albedo_texture != null je Fläche), Zuordnung je Kategorie wie
|
||||
## tools/CAMO_TEXTURE_MAPPING.md (Client-Repo, verbindlich).
|
||||
## Die ImageTexture wird zur Laufzeit von der Platte geladen → resource_path
|
||||
## ist leer; der Pfad steht im Store-Cache (_camo_tex_cache) und wird hier
|
||||
## ausgegeben (Zahlen/Pfade ins Handoff).
|
||||
func _test_camo_tex() -> void:
|
||||
print("Testgruppe: Camo-Textur im Live-Pfad — WYSIWYG Editor = Spiel (t_50a07d4b)")
|
||||
var mesh_w := GlbMeshStore.mesh_fuer("wall_001")
|
||||
_check(mesh_w != null, "wall_001 (walls → _1) geladen")
|
||||
if mesh_w != null:
|
||||
_camo_flachen_pruefen(mesh_w, "wall_001", "desert_multicam_1.png")
|
||||
var mesh_g := GlbMeshStore.mesh_fuer("ground_001")
|
||||
_check(mesh_g != null, "ground_001 (nature/ground → _2) geladen")
|
||||
if mesh_g != null:
|
||||
_camo_flachen_pruefen(mesh_g, "ground_001", "desert_multicam_2.png")
|
||||
# Eine-Quelle-Cache: wall_002 teilt DIESELBE ImageTexture-Instanz wie
|
||||
# wall_001 (291 GLBs teilen sich 2 Texturen statt je eine Kopie).
|
||||
var mesh_w2 := GlbMeshStore.mesh_fuer("wall_002")
|
||||
if mesh_w2 != null and mesh_w != null:
|
||||
var t1 := _camo_mat_tex(mesh_w, 0)
|
||||
var t2 := _camo_mat_tex(mesh_w2, 0)
|
||||
_check(t1 != null and t1 == t2,
|
||||
"wall_001 und wall_002 teilen DIESELBE Textur-Instanz (VRAM-Cache)")
|
||||
_check(GlbMeshStore._camo_tex_cache.size() == 2,
|
||||
"Camo-Textur-Cache: genau 2 Texturen (Objekt _1 + Boden _2), ist %d" % GlbMeshStore._camo_tex_cache.size())
|
||||
|
||||
|
||||
func _camo_flachen_pruefen(mesh: Mesh, oid: String, erwartet_name: String) -> void:
|
||||
var count := mesh.get_surface_count()
|
||||
_check(count > 0, "%s hat Flächen (%d)" % [oid, count])
|
||||
var textured := 0
|
||||
for s in count:
|
||||
if _camo_mat_tex(mesh, s) != null:
|
||||
textured += 1
|
||||
_check(textured == count,
|
||||
"%s: ALLE %d Flächen haben albedo_texture != null (getextured: %d)" % [oid, count, textured])
|
||||
# Pfad + Zuordnung: die Textur am Mesh ist DIESELBE Instanz wie der
|
||||
# Cache-Eintrag mit dem erwarteten Multicam-Pfad.
|
||||
var erwartet: ImageTexture = null
|
||||
var pfad_text := ""
|
||||
for pfad in GlbMeshStore._camo_tex_cache.keys():
|
||||
var p := str(pfad)
|
||||
if p.ends_with(erwartet_name):
|
||||
var v: Variant = GlbMeshStore._camo_tex_cache[pfad]
|
||||
if v is ImageTexture:
|
||||
erwartet = v
|
||||
pfad_text = p
|
||||
_check(erwartet != null, "%s: Multicam-Textur %s im Cache geladen" % [oid, erwartet_name])
|
||||
if erwartet != null:
|
||||
print(" info: %s → Textur-Pfad: %s" % [oid, pfad_text])
|
||||
var instanz_ok := false
|
||||
for s in count:
|
||||
if _camo_mat_tex(mesh, s) == erwartet:
|
||||
instanz_ok = true
|
||||
_check(instanz_ok,
|
||||
"%s: Flächen tragen DIESELBE Textur-Instanz wie der Cache (%s)" % [oid, erwartet_name])
|
||||
|
||||
|
||||
func _camo_mat_tex(mesh: Mesh, s: int) -> Texture2D:
|
||||
var mat := mesh.surface_get_material(s)
|
||||
if mat is BaseMaterial3D:
|
||||
return (mat as BaseMaterial3D).albedo_texture
|
||||
return null
|
||||
|
||||
|
||||
# --------------------------------------------------------- Viewport-Rebuild --
|
||||
|
||||
func _proben_doc(oid: String, scale: float) -> Dictionary:
|
||||
|
|
|
|||
|
|
@ -131,68 +131,14 @@ func _mesh_fuer(oid: String, col: Dictionary) -> Mesh:
|
|||
if FileAccess.file_exists(pfad):
|
||||
var m := _glb_mesh(pfad)
|
||||
if m != null:
|
||||
_camo_tex_setzen(m, pfad)
|
||||
# Camo-Textur-EINE-Quelle (t_50a07d4b): die Zuordnung/Logik
|
||||
# lebt jetzt in GlbMeshStore.camo_tex_setzen (Live-Pfad
|
||||
# Editor-Viewport nutzt denselben Code, WYSIWYG garantiert).
|
||||
GlbMeshStore.camo_tex_setzen(m, pfad)
|
||||
return m
|
||||
return MeshShapes.make_placeholder(str(col.get("shape", "box")), col, 1.0)
|
||||
|
||||
|
||||
## Pack-Palette (t_279df760): GLTFDocument kann externe Texturen (Sibling-Repos,
|
||||
## außerhalb res://) nicht über den ResourceLoader laden — das Material bleibt
|
||||
## dann untexturiert/weiß. Diese Funktion lädt die Pack-Textur DIREKT von der
|
||||
## Platte (Image.load_from_file, ohne Import-System) und setzt sie auf alle
|
||||
## Flächen ohne albedo_texture. Zuordnung je MATERIALNAME exakt wie in
|
||||
## tools/PACK_TEXTURE_MAPPING.md (Client-Repo, verbindliche Quelle der Wahrheit):
|
||||
## military_base -> Demonstration_Textures1.png, picture_1/_2 -> Demonstration_picture_N.png.
|
||||
## (Alte Camo-Zuordnung je KATEGORIE ist seit t_279df760 abgelöst.)
|
||||
const PACK_TEX_ORDNER := "../scopedvr-client/assets/textures/military_desert"
|
||||
const PACK_MAPPING := {
|
||||
"military_base": "Demonstration_Textures1.png",
|
||||
"picture_1": "Demonstration_picture_1.png",
|
||||
"picture_2": "Demonstration_picture_2.png",
|
||||
}
|
||||
|
||||
func _camo_tex_setzen(mesh: Mesh, glb_pfad: String) -> void:
|
||||
# Kategorie + OID aus dem Pfad: .../military_desert/<kategorie>/<oid>.glb
|
||||
# (nur für die Warnung; die Texturwahl hängt am MATERIALNAMEN)
|
||||
var teile := glb_pfad.split("/")
|
||||
var kategorie := ""
|
||||
for i in teile.size():
|
||||
if teile[i] == "military_desert" and i + 2 < teile.size():
|
||||
kategorie = teile[i + 1]
|
||||
break
|
||||
if kategorie.is_empty():
|
||||
return
|
||||
# Materialnamen aus dem Mesh lesen — jede Surface bekommt ihre Pack-Textur.
|
||||
var benoetigt: Dictionary = {}
|
||||
for s in mesh.get_surface_count():
|
||||
var mat := mesh.surface_get_material(s)
|
||||
if mat is BaseMaterial3D and (mat as BaseMaterial3D).albedo_texture == null:
|
||||
var name := (mat as BaseMaterial3D).resource_name
|
||||
if name.is_empty():
|
||||
name = "military_base" # unbenannte Pack-Materialien = Basis-Palette
|
||||
benoetigt[s] = name
|
||||
if benoetigt.is_empty():
|
||||
return
|
||||
for s in benoetigt:
|
||||
var name: String = benoetigt[s]
|
||||
var datei: String = PACK_MAPPING.get(name, "")
|
||||
if datei.is_empty():
|
||||
push_warning("gen_thumbs: unbekanntes Material %s — keine Pack-Textur (siehe PACK_TEXTURE_MAPPING.md)" % name)
|
||||
continue
|
||||
var tex_pfad := ProjectSettings.globalize_path("res://").path_join(PACK_TEX_ORDNER).path_join(datei)
|
||||
if not FileAccess.file_exists(tex_pfad):
|
||||
push_warning("gen_thumbs: Pack-Textur fehlt: %s" % tex_pfad)
|
||||
continue
|
||||
var img := Image.load_from_file(tex_pfad)
|
||||
if img == null:
|
||||
push_warning("gen_thumbs: Pack-Textur nicht ladbar: %s" % tex_pfad)
|
||||
continue
|
||||
var tex := ImageTexture.create_from_image(img)
|
||||
var mat := mesh.surface_get_material(s) as BaseMaterial3D
|
||||
if mat != null:
|
||||
mat.albedo_texture = tex
|
||||
|
||||
|
||||
## Kandidaten-Pfade (absolut) für eine OID — Registry-Pfad vor flacher Alt-Ablage.
|
||||
func _kandidaten(oid: String) -> Array:
|
||||
var basis := ProjectSettings.globalize_path("res://")
|
||||
|
|
|
|||
Loading…
Reference in a new issue