extends RefCounted class_name SvmapTint ## Per-Objekt-Farbe (Tint) für den Map Builder — Karte t_b0edb807. ## ## Format (svmap, OPTIONALES Feld je Objekt): ## "tint": [r, g, b] — Display-Farben (sRGB, 0..1, auf 4 Stellen gerundet) ## ## FARBRAUM-REGEL (Lektion D9, 19.10.): gespeichert wird der ANZEIGE-Wert ## (sRGB, wie der Godot-Farbpicker ihn zeigt). Beim Anwenden wird der Wert ## DIREKT in `albedo_color` geschrieben — Godot interpretiert albedo_color ## als Anzeige-Farbe (flach beleuchtet zeigt der Pixel exakt diesen Wert). ## Das ist bewusst NICHT der glTF-Weg (dort wäre baseColorFactor linear): ## solange Editor und Client denselben Wert denselben Weg schreiben, ## rendern sie identisch — und genau das garantiert dieser Helfer. ## ## Semantik des Anwendens: `albedo_color` wird ERSETZT (nicht multipliziert). ## - Ungetexturierte Materialien (Military-Desert-Pack, bewiesenermaßen ohne ## Texturen) nehmen die gewählte Farbe 1:1 an — das ist die intuitive ## Erwartung an „Farbe wählen“. ## - Texturierte Materialien (Alt-Bestand/Waffen) bekommen die Textur × ## Farbe (Godot multipliziert albedo_texture × albedo_color) — Weiß der ## Palette lässt sie unverändert, dunkle Palette tönt sie ab. ## „Zurücksetzen (Pack-Farbe)“ entfernt das Feld; ohne Feld läuft ALLES wie ## vorher (Rückwärtskompatibilität: alte Maps ohne tint laden unverändert). ## ## Anwendung als SURFACE-OVERRIDE-Materialien (Kopien): die Original- ## Materialien am Mesh bleiben unangetastet, das Feld ist damit reversibel, ## und das Auswahl-Orange (material_override) gewinnt weiterhin — Godot ## gibt material_override Vorrang vor surface_override_materials. ## ## DIESE DATEI ist bewusst 1:1 im Client-Repo vorhanden ## (scopedvr-client scripts/world/svmap_tint.gd) — Editor und Client müssen ## denselben Code nutzen, damit gespeicherte Farbe = gespielte Farbe. ## Bei Änderungen: BEIDE Kopien identisch anpassen. const FIELD := "tint" ## Feste Palette (Namen wie auf der Karte vereinbart). Reihenfolge = UI-Reihenfolge. const PALETTE := [ ["Wüstensand", Color(0.76, 0.70, 0.50)], ["Oliv", Color(0.50, 0.52, 0.30)], ["Tarn-Grün", Color(0.30, 0.40, 0.24)], ["Stahlgrau", Color(0.45, 0.47, 0.50)], ["Holzbraun", Color(0.55, 0.40, 0.25)], ["Dunkelbraun", Color(0.32, 0.23, 0.14)], ["Weiß", Color(0.92, 0.92, 0.92)], ] const EPS := 0.003 # Farb-Gleichheit (4 Nachkommastellen gerundet) ## Objekt hat ein gültiges tint-Feld? static func hat(obj: Dictionary) -> bool: return gueltig(obj.get(FIELD, null)) ## Feld typo-/bereichsgeprüft: Array mit 3 Zahlen 0..1. static func gueltig(v: Variant) -> bool: if not (v is Array) or (v as Array).size() != 3: return false for c in v: if not (c is float or c is int): return false if float(c) < 0.0 or float(c) > 1.0: return false return true ## Farbe eines Objekts lesen (ohne Feld → Weiß = neutral, Anzeigewert). static func von(obj: Dictionary) -> Color: var v: Variant = obj.get(FIELD, null) if gueltig(v): var a: Array = v return Color(float(a[0]), float(a[1]), float(a[2])) return Color.WHITE ## Farbe speichern (Anzeigewert, 4 Stellen — reicht für Visuelles, klein im JSON). static func setze(obj: Dictionary, c: Color) -> void: obj[FIELD] = [ roundf(c.r * 10000.0) / 10000.0, roundf(c.g * 10000.0) / 10000.0, roundf(c.b * 10000.0) / 10000.0, ] ## Feld entfernen („Zurücksetzen (Pack-Farbe)“). static func entferne(obj: Dictionary) -> void: obj.erase(FIELD) ## Zwei tint-Werte „gleich“ (Anzeige-Toleranz)? static func gleich(a: Color, b: Color) -> bool: return absf(a.r - b.r) <= EPS and absf(a.g - b.g) <= EPS and absf(a.b - b.b) <= EPS ## Paletten-Namen zu einer Farbe finden („“ = keine Palette-Farbe / gemischt). static func palette_name(c: Color) -> String: for eintrag in PALETTE: if gleich(c, eintrag[1]): return str(eintrag[0]) return "" ## Tint auf eine Visual-Struktur anwenden: ALLE MeshInstance3D darunter ## bekommen pro Fläche eine KOPIE ihres aktiven Materials mit der neuen ## albedo_color. Vorhandene Surface-Overrides werden ersetzt (idempotent). ## material_override (Auswahl-Orange) bleibt unangetastet und gewinnt. static func anwenden(visual: Node, c: Color) -> void: if visual == null or not is_instance_valid(visual): return _walk_anwenden(visual, c) static func _walk_anwenden(n: Node, c: Color) -> void: if n is MeshInstance3D: var mi := n as MeshInstance3D if mi.mesh != null: for s in mi.mesh.get_surface_count(): var mat := mi.get_active_material(s) # Ausgewählte Objekte (material_override = Auswahl-Material) # überspringt der AUFRUFER, indem er tint erst nach dem # Abwählen anwendet — hier nichts Sonderbehandeln. if mat is BaseMaterial3D: var dup: BaseMaterial3D = (mat as BaseMaterial3D).duplicate() dup.albedo_color = Color(c.r, c.g, c.b, (mat as BaseMaterial3D).albedo_color.a) mi.set_surface_override_material(s, dup) for kind in n.get_children(): _walk_anwenden(kind, c) ## Tint visuell zurücknehmen: alle gesetzten Surface-Override-Materialien ## entfernen → die Original-Materialien am Mesh zeigen wieder die Pack-Farbe ## (Platzhalter fallen auf ihr material_override/_base zurück). static func zuruecknehmen(visual: Node) -> void: if visual == null or not is_instance_valid(visual): return _walk_zurueck(visual) static func _walk_zurueck(n: Node) -> void: if n is MeshInstance3D: var mi := n as MeshInstance3D if mi.mesh != null: for s in mi.mesh.get_surface_count(): mi.set_surface_override_material(s, null) for kind in n.get_children(): _walk_zurueck(kind)