149 lines
5.6 KiB
GDScript
149 lines
5.6 KiB
GDScript
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)
|