scopedvr-map-core/svmap/svmap_tint.gd
slyphax 17a8927fec Map-Kern aus dem Editor übernommen (Stand e8ab954)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:30:18 +01:00

149 lines
5.6 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)