farming-simulator/scripts/util/texture_generator.gd
Dmitrii Bykov 579df43289
Some checks failed
Godot CI / Import + smoke test (push) Failing after 13s
fix: скролл лицензии, отец лицом к игроку, банка-цилиндр, экран
АКТ 2 СКРОЛЛ:
- Body RichTextLabel: fit_content=true → false, теперь label
  остаётся в фиксированном размере 540×220 и скроллит контент
  внутри. Раньше label расширялся по тексту и обрезался панелью.
- scroll_following=false (не следить за концом).
- mouse_filter=STOP — теперь mouse wheel над label скроллит.
- font_size снижен с дефолта на 11 — больше пунктов помещается
  на экране, читается комфортно.
- Window custom_minimum_size = 580×348 (было 560×320) — больше
  места под лицензию.

ОТЕЦ:
- FatherNPC initial transform теперь rotation_y = +π/2
  (Transform3D(0,0,-1,0,1,0,1,0,0,1.7,0,0)) — лицом к игроку
  на запад. Игрок видит лицо отца, а не профиль.
- В _walk_father_out tween поворачивает rotation_y → -π/2
  (180° поворот через 0/север). Шагает на восток к двери
  с правильно повёрнутым корпусом.

МОНИТОР:
- К Desk/OldPC добавлен child Screen — BoxMesh 0.21×0.15×0.005
  с эмиссивной материалом mat_screen (тёмный с голубым свечением).
- Стоит на передней грани монитора, имитирует тёмный экран
  с легким bluish-glow CRT-вибом.

ADRENALINE BANK → ЦИЛИНДР:
- mesh_can: BoxMesh → CylinderMesh top=bottom=0.03 height=0.13.
  Теперь это реально банка по форме, а не куб.

ТЕКСТУРЫ ПРЕДМЕТОВ (TextureGen новые):
- adrenaline_label: синий с тёмно-синими диагональными штрихами +
  жёлтая молния по центру + тёмные верхняя/нижняя кромки.
- lays_wrapper: жёлтый с красной волнистой лентой посередине
  (синус) + коричневая чипса-клякса внизу + тёмные уголки.
- sock_fabric: тёмно-синий с горизонтальными светлыми полосками
  каждые 6 пикселей + тёмный шов.
- monitor_screen: тёмное стекло с edge-glow + scanlines каждые
  2 строки (CRT-вайб).
- Применены в act1.gd._apply_textures на AdrenalineCan,
  LaysWrapper, Sock1/Sock2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:36:19 +03:00

663 lines
21 KiB
GDScript
Raw 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 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)
)
# Этикетка Adrenaline Rush — синий со штрихами + молния
func adrenaline_label() -> ImageTexture:
return _get_or_create("adrenaline_label", func() -> ImageTexture:
var w: int = 64
var h: int = 64
var blue_dark: Color = Color(0.08, 0.18, 0.45)
var blue: Color = Color(0.13, 0.32, 0.68)
var blue_light: Color = Color(0.3, 0.5, 0.85)
var yellow: Color = Color(0.95, 0.85, 0.2)
var img: Image = _make_image(w, h, blue)
# Диагональные полосы
for y in range(h):
for x in range(w):
var d: int = (x + y) % 12
if d < 2:
img.set_pixel(x, y, blue_dark)
elif d < 4:
img.set_pixel(x, y, blue_light)
# Молния по центру
var bolt: Array = [
[32, 10], [32, 12], [31, 14], [31, 16], [30, 18], [33, 20],
[33, 22], [32, 24], [32, 26], [31, 28], [34, 30], [34, 32],
[33, 34], [33, 36], [32, 38], [35, 40], [34, 42], [33, 44],
[32, 46], [31, 48], [30, 50], [29, 52]
]
for p in bolt:
img.set_pixel(p[0], p[1], yellow)
if p[0] + 1 < w:
img.set_pixel(p[0] + 1, p[1], yellow)
if p[0] - 1 >= 0:
img.set_pixel(p[0] - 1, p[1], yellow)
# Верх/низ — тёмные полосы
for x in range(w):
img.set_pixel(x, 0, blue_dark)
img.set_pixel(x, 1, blue_dark)
img.set_pixel(x, h - 1, blue_dark)
img.set_pixel(x, h - 2, blue_dark)
return _to_texture(img)
)
# Обёртка Lay's — жёлтая с красной волной
func lays_wrapper() -> ImageTexture:
return _get_or_create("lays_wrapper", func() -> ImageTexture:
var w: int = 64
var h: int = 48
var yellow: Color = Color(0.96, 0.84, 0.18)
var red: Color = Color(0.78, 0.12, 0.12)
var red_dark: Color = Color(0.55, 0.08, 0.08)
var brown: Color = Color(0.35, 0.22, 0.1)
var img: Image = _make_image(w, h, yellow)
# Красная волнистая лента посередине
for x in range(w):
var wave: int = int(sin(x * 0.3) * 4.0)
var band_y: int = 20 + wave
for dy in range(-4, 5):
var y: int = band_y + dy
if y >= 0 and y < h:
if dy == -4 or dy == 4:
img.set_pixel(x, y, red_dark)
else:
img.set_pixel(x, y, red)
# Силуэт чипса (овальная коричневая клякса)
for ang in range(20):
var a: float = float(ang) * PI / 10.0
var px: int = 16 + int(cos(a) * 6.0)
var py: int = 38 + int(sin(a) * 3.0)
if px >= 0 and px < w and py >= 0 and py < h:
img.set_pixel(px, py, brown)
# Уголки обёртки темнее
for i in range(3):
img.set_pixel(i, h - 1 - i, red_dark)
img.set_pixel(w - 1 - i, h - 1 - i, red_dark)
return _to_texture(img)
)
# Носок — тёмно-серый со светлыми полосками
func sock_fabric() -> ImageTexture:
return _get_or_create("sock_fabric", func() -> ImageTexture:
var w: int = 32
var h: int = 32
var base: Color = Color(0.18, 0.22, 0.32)
var stripe: Color = Color(0.28, 0.32, 0.42)
var dark: Color = Color(0.1, 0.13, 0.2)
var img: Image = _make_image(w, h, base)
for y in range(h):
if y % 6 == 0 or y % 6 == 1:
for x in range(w):
img.set_pixel(x, y, stripe)
elif y % 6 == 3:
for x in range(w):
img.set_pixel(x, y, dark)
return _to_texture(img)
)
# Экран монитора — тёмное стекло с легким bluish свечением
func monitor_screen() -> ImageTexture:
return _get_or_create("monitor_screen", func() -> ImageTexture:
var w: int = 64
var h: int = 48
var dark: Color = Color(0.04, 0.05, 0.08)
var glow: Color = Color(0.15, 0.2, 0.3)
var scan: Color = Color(0.08, 0.1, 0.14)
var img: Image = _make_image(w, h, dark)
# Боковые градиенты к свечению
for y in range(h):
for x in range(w):
var dx: float = abs(float(x) - float(w) / 2.0) / (float(w) / 2.0)
var dy: float = abs(float(y) - float(h) / 2.0) / (float(h) / 2.0)
var edge: float = max(dx, dy)
var c: Color = dark.lerp(glow, (1.0 - edge) * 0.4)
img.set_pixel(x, y, c)
# Горизонтальные scanlines каждые 2 строки
for y in range(0, h, 2):
for x in range(w):
img.set_pixel(x, y, img.get_pixel(x, y).lerp(scan, 0.4))
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