extends RefCounted ## Materialien aus Regeln, ohne eine einzige Datei. Je Stoff ein Eintrag: ## Farbverlauf, Rauschart, Reliefstärke, Rauheit, Maßstab. Daraus baut ## `mach()` ein StandardMaterial3D mit drei zur Laufzeit erzeugten Texturen: ## Farbe (Rauschen durch einen Farbverlauf), Relief (dasselbe Rauschen als ## Normalenkarte) und Rauheit. Das Relief ist der Unterschied zwischen ## "gemustertes Plastik" und Filz, Holz, Messing. ## ## Kein class_name: den Klassencache baut nur der Editor, headless fehlt er. const STOFFE := { "filz": { # Feiner Flaum, kaum Relief, ganz matt. Grobes, glänzendes Relief sah # nach nassem Moos aus. "farben": [Color(0.06, 0.18, 0.11), Color(0.08, 0.23, 0.14), Color(0.10, 0.26, 0.16)], "art": FastNoiseLite.TYPE_SIMPLEX_SMOOTH, "freq": 0.35, "oktaven": 3, "relief": 0.12, "rauheit": [0.96, 1.0], "metall": 0.0, "uv": Vector3(14, 14, 1), "glanz": 0.05, }, "holz": { "farben": [Color(0.20, 0.12, 0.06), Color(0.36, 0.23, 0.11), Color(0.46, 0.31, 0.16)], "art": FastNoiseLite.TYPE_SIMPLEX, "freq": 0.03, "oktaven": 5, "warp": 25.0, "relief": 0.6, "rauheit": [0.45, 0.75], "metall": 0.0, # stark gestreckt: aus dem Rauschen wird Maserung "uv": Vector3(1.0, 9.0, 1.0), }, "holz_dunkel": { "farben": [Color(0.14, 0.06, 0.04), Color(0.30, 0.12, 0.07), Color(0.40, 0.18, 0.10)], "art": FastNoiseLite.TYPE_SIMPLEX, "freq": 0.03, "oktaven": 5, "warp": 25.0, "relief": 0.5, "rauheit": [0.4, 0.7], "metall": 0.0, "uv": Vector3(1.0, 7.0, 1.0), }, "messing": { "farben": [Color(0.55, 0.40, 0.14), Color(0.80, 0.62, 0.26), Color(0.95, 0.80, 0.42)], "art": FastNoiseLite.TYPE_CELLULAR, "freq": 0.12, "oktaven": 2, "relief": 0.25, "rauheit": [0.2, 0.5], "metall": 0.9, "uv": Vector3(3, 3, 1), }, "knochen": { # Farbe kommt von den Vertexfarben (Körper hell, Augen dunkel); hier # nur feine Dellen und eine warme, leicht fettige Oberfläche. "farben": [], "art": FastNoiseLite.TYPE_SIMPLEX_SMOOTH, "freq": 0.25, "oktaven": 3, "relief": 0.2, "rauheit": [0.45, 0.65], "metall": 0.0, "uv": Vector3(4, 4, 1), }, } static var _fertig: Dictionary = {} ## name -> StandardMaterial3D, einmal gebaut static func _rauschen(r: Dictionary, seed: int) -> FastNoiseLite: var n := FastNoiseLite.new() n.seed = seed n.noise_type = r.art n.frequency = r.freq n.fractal_octaves = r.oktaven if r.has("warp"): n.domain_warp_enabled = true n.domain_warp_amplitude = r.warp return n static func _textur(n: FastNoiseLite, groesse: int) -> NoiseTexture2D: var t := NoiseTexture2D.new() t.noise = n t.width = groesse t.height = groesse t.seamless = true t.generate_mipmaps = true return t ## Baut das Material nach Regel `name`. Gleiche Regel, gleiches Material: ## alle Becher teilen eine Instanz, so bleibt es ein Renderobjekt je Stoff. static func mach(name: String) -> StandardMaterial3D: if _fertig.has(name): return _fertig[name] var r: Dictionary = STOFFE[name] var m := StandardMaterial3D.new() var basis := _rauschen(r, name.hash() % 1000) if not r.farben.is_empty(): var verlauf := Gradient.new() var farben: Array = r.farben verlauf.offsets = PackedFloat32Array([0.0, 0.5, 1.0]) verlauf.colors = PackedColorArray([farben[0], farben[1], farben[2]]) var farbe := _textur(basis, 256) farbe.color_ramp = verlauf m.albedo_texture = farbe else: m.vertex_color_use_as_albedo = true var relief := _textur(basis, 256) relief.as_normal_map = true relief.bump_strength = float(r.relief) * 8.0 m.normal_enabled = true m.normal_texture = relief m.normal_scale = 1.0 # Rauheit schwankt mit dem Rauschen zwischen den zwei Werten. var rau := _textur(_rauschen(r, name.hash() % 1000 + 7), 128) m.roughness_texture = rau m.roughness_texture_channel = BaseMaterial3D.TEXTURE_CHANNEL_RED m.roughness = float(r.rauheit[1]) m.metallic = float(r.metall) m.metallic_specular = float(r.get("glanz", 0.5 if r.metall > 0.5 else 0.3)) m.uv1_scale = r.uv m.texture_filter = BaseMaterial3D.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC _fertig[name] = m return m ## Eigene Instanz, wenn ein Objekt Farbe oder Leuchten ändern muss. static func eigene(name: String) -> StandardMaterial3D: return mach(name).duplicate() static func filz() -> StandardMaterial3D: return mach("filz") static func holz() -> StandardMaterial3D: return mach("holz") static func holz_dunkel() -> StandardMaterial3D: return mach("holz_dunkel") static func messing(emission := 0.0) -> StandardMaterial3D: var m := eigene("messing") if emission > 0.0: m.emission_enabled = true m.emission = Color(1.0, 0.75, 0.30) m.emission_energy_multiplier = emission return m static func knochen() -> StandardMaterial3D: return mach("knochen") ## Zonenrand: Holz, das leuchten kann, wenn der Platz dran ist. static func rand() -> StandardMaterial3D: var m := eigene("holz") m.emission = Color(1.0, 0.78, 0.30) m.emission_enabled = false return m