extends Node var _cache: Dictionary = {} func _make_image(w: int, h: int, fill: Color) -> Image: var img: Image = Image.create(w, h, false, Image.FORMAT_RGB8) img.fill(fill) return img func _to_texture(img: Image) -> ImageTexture: return ImageTexture.create_from_image(img) func _get_or_create(key: String, maker: Callable) -> ImageTexture: if _cache.has(key): return _cache[key] var tex: ImageTexture = maker.call() _cache[key] = tex return tex func _noise(seed_v: int, freq: float, ntype: int = FastNoiseLite.TYPE_PERLIN) -> FastNoiseLite: var n: FastNoiseLite = FastNoiseLite.new() n.seed = seed_v n.frequency = freq n.noise_type = ntype return n # Бежевые обои — растительный узор поверх шумной базы func wallpaper() -> ImageTexture: return _get_or_create("wallpaper", func() -> ImageTexture: var size: int = 128 var base: Color = Color(0.83, 0.72, 0.55) var darker: Color = Color(0.7, 0.58, 0.42) var darker_dark: Color = Color(0.58, 0.46, 0.32) var img: Image = _make_image(size, size, base) var noise: FastNoiseLite = _noise(424242, 0.06) # Шумная база for y in range(size): for x in range(size): var n: float = noise.get_noise_2d(x, y) * 0.5 + 0.5 var c: Color = base.lerp(darker, n * 0.18) # Лёгкая горизонтальная полоса (стык обоев каждые 32px) if y % 32 == 0: c = c.darkened(0.04) img.set_pixel(x, y, c) # Цветочки шахматно var spacing: int = 22 for row in range(0, size / spacing + 1): for col in range(0, size / spacing + 1): var offset_x: int = (spacing / 2) if row % 2 == 0 else 0 var cx: int = (col * spacing + offset_x) % size var cy: int = row * spacing if cy >= size: continue # Лепестки крестом 3х3, центр темнее _draw_flower(img, cx, cy, size, darker, darker_dark) return _to_texture(img) ) func _draw_flower(img: Image, cx: int, cy: int, size: int, petal: Color, center: Color) -> void: var w: int = img.get_width() var h: int = img.get_height() # 5-точечный крест var pts: Array = [ [0, 0, center], [-1, 0, petal], [1, 0, petal], [0, -1, petal], [0, 1, petal], [-2, 0, petal], [2, 0, petal], [0, -2, petal], [0, 2, petal], ] for p in pts: var x: int = cx + p[0] var y: int = cy + p[1] if x >= 0 and x < w and y >= 0 and y < h: img.set_pixel(x, y, p[2]) # Линолеум: «под светлый паркет» — доски с волокном func linoleum() -> ImageTexture: return _get_or_create("linoleum", func() -> ImageTexture: var size: int = 128 var base: Color = Color(0.72, 0.6, 0.41) var dark: Color = Color(0.55, 0.43, 0.28) var darker: Color = Color(0.4, 0.3, 0.18) var lighter: Color = Color(0.85, 0.74, 0.55) var img: Image = _make_image(size, size, base) var grain_noise: FastNoiseLite = _noise(1234, 0.25) var plank_h: int = 16 # высота доски var plank_w: int = 64 # ширина for y in range(size): var plank_row: int = y / plank_h var plank_offset: int = (plank_row * 23) % plank_w for x in range(size): # Тон доски варьируется var plank_idx: int = (x + plank_offset) / plank_w var plank_tint: float = float((plank_row + plank_idx) % 4) / 4.0 var tinted: Color = base.lerp(lighter if plank_tint > 0.5 else dark, plank_tint * 0.25) # Волокно — вертикальные тонкие штрихи через шум var grain: float = grain_noise.get_noise_2d(x, y * 4) * 0.5 + 0.5 tinted = tinted.lerp(dark, grain * 0.25) img.set_pixel(x, y, tinted) # Швы между досками (горизонтальные) for y in range(0, size, plank_h): for x in range(size): img.set_pixel(x, y, darker) # Швы между досками (вертикальные, смещённые) for row in range(size / plank_h): var offset: int = (row * 23) % plank_w var y_start: int = row * plank_h for col in range(size / plank_w + 1): var seam_x: int = (col * plank_w + offset) % size for dy in range(1, plank_h): if y_start + dy < size: img.set_pixel(seam_x, y_start + dy, darker) return _to_texture(img) ) # Потолок: побелка с очень лёгким шумом + пятно протёчки func ceiling() -> ImageTexture: return _get_or_create("ceiling", func() -> ImageTexture: var size: int = 64 var base: Color = Color(0.92, 0.9, 0.85) var spot: Color = Color(0.85, 0.82, 0.74) var stain: Color = Color(0.78, 0.72, 0.62) var img: Image = _make_image(size, size, base) var noise: FastNoiseLite = _noise(8888, 0.12) for y in range(size): for x in range(size): var n: float = noise.get_noise_2d(x, y) * 0.5 + 0.5 var c: Color = base.lerp(spot, n * 0.18) img.set_pixel(x, y, c) # Пятно от протёчки в углу var cx: int = 12 var cy: int = 12 for r in range(8): for ang in range(8): var a: float = float(ang) * PI / 4.0 var px: int = cx + int(cos(a) * r) var py: int = cy + int(sin(a) * r) if px >= 0 and px < size and py >= 0 and py < size: var t: float = 1.0 - (float(r) / 8.0) img.set_pixel(px, py, base.lerp(stain, t * 0.4)) return _to_texture(img) ) # Бордовый ковёр персидский с медальоном func wall_carpet() -> ImageTexture: return _get_or_create("wall_carpet", func() -> ImageTexture: var size: int = 128 var base: Color = Color(0.36, 0.12, 0.13) var dark: Color = Color(0.22, 0.07, 0.08) var gold: Color = Color(0.78, 0.62, 0.2) var cream: Color = Color(0.85, 0.75, 0.5) var img: Image = _make_image(size, size, base) var noise: FastNoiseLite = _noise(5555, 0.1) # Шумная основа for y in range(size): for x in range(size): var n: float = noise.get_noise_2d(x, y) * 0.5 + 0.5 img.set_pixel(x, y, base.lerp(dark, n * 0.25)) # Внешняя рамка for i in range(size): for t in range(3): img.set_pixel(i, t, dark) img.set_pixel(i, size - 1 - t, dark) img.set_pixel(t, i, dark) img.set_pixel(size - 1 - t, i, dark) # Внутренняя золотая полоса for i in range(6, size - 6): img.set_pixel(i, 5, gold) img.set_pixel(i, size - 6, gold) img.set_pixel(5, i, gold) img.set_pixel(size - 6, i, gold) # Центральный медальон-ромб var cx: int = size / 2 var cy: int = size / 2 var med_r: int = 32 for r in range(med_r): var w: int = med_r - r for dx in range(-w, w + 1): var py1: int = cy - r var py2: int = cy + r var px: int = cx + dx if px >= 0 and px < size and py1 >= 0: if r == med_r - 1 or abs(dx) == w: img.set_pixel(px, py1, gold) elif r % 4 == 0 and abs(dx) % 4 == 0: img.set_pixel(px, py1, cream) if px >= 0 and px < size and py2 < size: if r == med_r - 1 or abs(dx) == w: img.set_pixel(px, py2, gold) elif r % 4 == 0 and abs(dx) % 4 == 0: img.set_pixel(px, py2, cream) # Уголки var corners: Array = [[20, 20], [size - 20, 20], [20, size - 20], [size - 20, size - 20]] for c in corners: for r in range(8): var w: int = 8 - r for dx in range(-w, w + 1): var py: int = c[1] + r var px: int = c[0] + dx if px >= 0 and px < size and py >= 0 and py < size: if abs(dx) == w or r == 7: img.set_pixel(px, py, gold) py = c[1] - r if px >= 0 and px < size and py >= 0 and py < size: if abs(dx) == w or r == 7: img.set_pixel(px, py, gold) return _to_texture(img) ) # Тёмно-синее одеяло «звёзды» func blanket() -> ImageTexture: return _get_or_create("blanket", func() -> ImageTexture: var size: int = 64 var base: Color = Color(0.13, 0.16, 0.32) var deeper: Color = Color(0.09, 0.11, 0.24) var star_w: Color = Color(0.9, 0.88, 0.78) var star_y: Color = Color(0.94, 0.85, 0.45) var img: Image = _make_image(size, size, base) var noise: FastNoiseLite = _noise(7777, 0.08) for y in range(size): for x in range(size): var n: float = noise.get_noise_2d(x, y) * 0.5 + 0.5 img.set_pixel(x, y, base.lerp(deeper, n * 0.4)) var rng: RandomNumberGenerator = RandomNumberGenerator.new() rng.seed = 9999 for i in range(30): var sx: int = rng.randi_range(2, size - 3) var sy: int = rng.randi_range(2, size - 3) var c: Color = star_y if rng.randf() > 0.7 else star_w var big: bool = rng.randf() > 0.7 img.set_pixel(sx, sy, c) if big: img.set_pixel(sx + 1, sy, c) img.set_pixel(sx - 1, sy, c) img.set_pixel(sx, sy + 1, c) img.set_pixel(sx, sy - 1, c) return _to_texture(img) ) # Постер CS:GO — жёлтый с силуэтом AWP + надпись func poster_csgo() -> ImageTexture: return _get_or_create("poster_csgo", func() -> ImageTexture: var w: int = 64 var h: int = 80 var base: Color = Color(0.94, 0.71, 0.1) var dark: Color = Color(0.18, 0.13, 0.08) var stripe: Color = Color(0.88, 0.65, 0.08) var img: Image = _make_image(w, h, base) # Диагональная полоса for y in range(h): for x in range(w): if (x + y) % 8 < 2: img.set_pixel(x, y, stripe) # Рамка for i in range(w): img.set_pixel(i, 0, dark) img.set_pixel(i, 1, dark) img.set_pixel(i, h - 1, dark) img.set_pixel(i, h - 2, dark) for i in range(h): img.set_pixel(0, i, dark) img.set_pixel(1, i, dark) img.set_pixel(w - 1, i, dark) img.set_pixel(w - 2, i, dark) # Силуэт AWP горизонтально по центру var by: int = 30 # приклад for x in range(8, 14): img.set_pixel(x, by + 2, dark) img.set_pixel(x, by + 3, dark) img.set_pixel(x, by + 4, dark) # ствол for x in range(14, 52): img.set_pixel(x, by + 3, dark) # прицел for x in range(28, 36): img.set_pixel(x, by + 1, dark) for x in range(30, 34): img.set_pixel(x, by, dark) # рукоятка img.set_pixel(18, by + 4, dark) img.set_pixel(18, by + 5, dark) img.set_pixel(17, by + 5, dark) # CS:GO текст внизу _draw_text_csgo(img, 18, 60, dark) return _to_texture(img) ) func _draw_text_csgo(img: Image, x0: int, y0: int, c: Color) -> void: # C for dy in range(8): img.set_pixel(x0, y0 + dy, c) for dx in range(1, 5): img.set_pixel(x0 + dx, y0, c) img.set_pixel(x0 + dx, y0 + 7, c) # S var sx: int = x0 + 7 for dx in range(5): img.set_pixel(sx + dx, y0, c) img.set_pixel(sx + dx, y0 + 3, c) img.set_pixel(sx + dx, y0 + 7, c) for dy in range(1, 3): img.set_pixel(sx, y0 + dy, c) for dy in range(4, 7): img.set_pixel(sx + 4, y0 + dy, c) # : img.set_pixel(x0 + 14, y0 + 2, c) img.set_pixel(x0 + 14, y0 + 5, c) # G var gx: int = x0 + 17 for dy in range(8): img.set_pixel(gx, y0 + dy, c) for dx in range(1, 5): img.set_pixel(gx + dx, y0, c) img.set_pixel(gx + dx, y0 + 7, c) img.set_pixel(gx + 4, y0 + 4, c) img.set_pixel(gx + 4, y0 + 5, c) img.set_pixel(gx + 4, y0 + 6, c) img.set_pixel(gx + 3, y0 + 4, c) # O var ox: int = x0 + 24 for dy in range(8): img.set_pixel(ox, y0 + dy, c) img.set_pixel(ox + 4, y0 + dy, c) for dx in range(1, 4): img.set_pixel(ox + dx, y0, c) img.set_pixel(ox + dx, y0 + 7, c) # Постер Моргенштерн — тёмно-красный с белой головой func poster_morgen() -> ImageTexture: return _get_or_create("poster_morgen", func() -> ImageTexture: var w: int = 64 var h: int = 80 var base: Color = Color(0.7, 0.13, 0.13) var light: Color = Color(0.9, 0.85, 0.8) var dark: Color = Color(0.2, 0.05, 0.05) var pink: Color = Color(0.95, 0.7, 0.65) var img: Image = _make_image(w, h, base) # Рамка for i in range(w): img.set_pixel(i, 0, dark) img.set_pixel(i, 1, dark) img.set_pixel(i, h - 1, dark) img.set_pixel(i, h - 2, dark) for i in range(h): img.set_pixel(0, i, dark) img.set_pixel(1, i, dark) img.set_pixel(w - 1, i, dark) img.set_pixel(w - 2, i, dark) # Голова в круге var cx: int = w / 2 var cy: int = 32 var radius: int = 18 for y in range(h): for x in range(w): var d: float = sqrt(pow(x - cx, 2) + pow(y - cy, 2)) if d <= float(radius): var t: float = d / float(radius) img.set_pixel(x, y, light.lerp(pink, t * 0.6)) if d > float(radius) - 1 and d <= float(radius): img.set_pixel(x, y, dark) # Глаза-щели img.set_pixel(cx - 5, cy - 2, dark) img.set_pixel(cx - 4, cy - 2, dark) img.set_pixel(cx + 4, cy - 2, dark) img.set_pixel(cx + 5, cy - 2, dark) # Рот for dx in range(-4, 5): img.set_pixel(cx + dx, cy + 6, dark) # Лого MS внизу _draw_text_ms(img, 22, 60, light) return _to_texture(img) ) func _draw_text_ms(img: Image, x0: int, y0: int, c: Color) -> void: # M for dy in range(8): img.set_pixel(x0, y0 + dy, c) img.set_pixel(x0 + 6, y0 + dy, c) img.set_pixel(x0 + 1, y0 + 1, c) img.set_pixel(x0 + 5, y0 + 1, c) img.set_pixel(x0 + 2, y0 + 2, c) img.set_pixel(x0 + 4, y0 + 2, c) img.set_pixel(x0 + 3, y0 + 3, c) # S var sx: int = x0 + 12 for dx in range(5): img.set_pixel(sx + dx, y0, c) img.set_pixel(sx + dx, y0 + 3, c) img.set_pixel(sx + dx, y0 + 7, c) for dy in range(1, 3): img.set_pixel(sx, y0 + dy, c) for dy in range(4, 7): img.set_pixel(sx + 4, y0 + dy, c) # Дверь: вертикальные деревянные планки + сучки func door() -> ImageTexture: return _get_or_create("door", func() -> ImageTexture: var w: int = 64 var h: int = 128 var base: Color = Color(0.45, 0.31, 0.18) var dark: Color = Color(0.28, 0.18, 0.09) var light: Color = Color(0.55, 0.4, 0.25) var knot: Color = Color(0.18, 0.1, 0.05) var img: Image = _make_image(w, h, base) # Вертикальное волокно через шум var grain: FastNoiseLite = _noise(3333, 0.2) grain.noise_type = FastNoiseLite.TYPE_PERLIN for y in range(h): for x in range(w): var n: float = grain.get_noise_2d(x * 0.5, y * 3) * 0.5 + 0.5 var c: Color = base.lerp(dark if n > 0.5 else light, abs(n - 0.5) * 1.2) img.set_pixel(x, y, c) # Панели — горизонтальные швы for y in [32, 64, 96]: for x in range(w): img.set_pixel(x, y, dark) if y + 1 < h: img.set_pixel(x, y + 1, dark) # Сучки var knots: Array = [[18, 20], [42, 50], [25, 85], [48, 110]] for k in knots: for r in range(4): for ang in range(12): var a: float = float(ang) * PI / 6.0 var px: int = k[0] + int(cos(a) * r) var py: int = k[1] + int(sin(a) * r) if px >= 0 and px < w and py >= 0 and py < h: var t: float = 1.0 - (float(r) / 4.0) img.set_pixel(px, py, base.lerp(knot, t)) return _to_texture(img) ) # Стол MICKE: белый с минимальной шумной фактурой + наклейка-стикер func desk_white() -> ImageTexture: return _get_or_create("desk_white", func() -> ImageTexture: var size: int = 64 var base: Color = Color(0.96, 0.95, 0.93) var nick: Color = Color(0.88, 0.86, 0.82) var img: Image = _make_image(size, size, base) var noise: FastNoiseLite = _noise(1337, 0.18) for y in range(size): for x in range(size): var n: float = noise.get_noise_2d(x, y) * 0.5 + 0.5 img.set_pixel(x, y, base.lerp(nick, n * 0.08)) # Стикер в углу — жёлтый Steam-inventory var sticker: Color = Color(0.94, 0.71, 0.1) for x in range(2, 8): for y in range(2, 8): img.set_pixel(x, y, sticker) # Царапины for x in range(20, 40): img.set_pixel(x, 32, nick.darkened(0.1)) return _to_texture(img) ) # Книжный корешок — цветной с полосами func book_color(color_idx: int) -> ImageTexture: var key: String = "book_%d" % color_idx return _get_or_create(key, func() -> ImageTexture: var palette: Array[Color] = [ Color(0.36, 0.18, 0.15), Color(0.2, 0.35, 0.5), Color(0.13, 0.13, 0.15), Color(0.55, 0.45, 0.25), Color(0.3, 0.5, 0.3), ] var base: Color = palette[color_idx % palette.size()] var trim: Color = base.darkened(0.4) var hi: Color = base.lightened(0.2) var w: int = 16 var h: int = 64 var img: Image = _make_image(w, h, base) # Верхние/нижние полосы for x in range(w): img.set_pixel(x, 0, trim) img.set_pixel(x, 1, trim) img.set_pixel(x, h - 1, trim) img.set_pixel(x, h - 2, trim) img.set_pixel(x, 6, hi) img.set_pixel(x, h - 7, hi) # Тонкая текстура корешка for y in range(8, h - 8): img.set_pixel(0, y, trim) img.set_pixel(w - 1, y, trim) return _to_texture(img) ) # Применить текстуру к материалу с заданным uv-scale + nearest filter func apply_to_material(mat: BaseMaterial3D, texture: ImageTexture, scale: Vector3 = Vector3.ONE) -> void: if mat == null: return mat.albedo_texture = texture mat.uv1_scale = scale mat.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST