farming-simulator/scripts/util/texture_generator.gd
Dmitrii Bykov 773b15fd8e
Some checks failed
Godot CI / Import + smoke test (push) Failing after 14s
act1: фиксы багов + crosshair + улучшенные текстуры
БАГ-ФИКСЫ:
- Трясёт у двери: Interactable теперь extends Area3D (не StaticBody3D).
  RayCast3D детектит Area3D через collide_with_areas=true.
  Игрок физически проходит через интерактивы (gift box, флешка,
  записка, дверь) — не толкается в узких местах. Бампается только
  в стены/мебель/пол.
- Все 6 интерактивов в act1_room.tscn и act1_kitchen.tscn
  переведены на type="Area3D".

UI:
- Crosshair в центре экрана: 8×8 точка с тёмной рамкой и белой
  центральной 2×2 точкой. set_crosshair_visible() для 2D-сцен
  (Акт 2 прячет его).
- SubtitlePanel расширен: 16-624 × 240-354 (было 24-616 × 270-350).
  Text-лейбл заменён на RichTextLabel с fit_content + bbcode.
  Длинные реплики отца теперь нормально влезают.
- NoteOverlay аналогично RichTextLabel.
- ControlsHint перенесён сверху (под HUD-датой), 14px меньше.

ТЕКСТУРЫ через FastNoiseLite + 128×128 разрешалка:
- wallpaper: шумная бежевая база + растительный крест-цветок
  каждые 22px, шахматное смещение, тонкая горизонтальная полоса
  каждые 32px (стык обоев).
- linoleum: настоящая паркетная доска 16×64px со смещением между
  рядами, тонкие швы, волокно через шум.
- ceiling: побелка с пятном протёчки в углу (canon-деталь).
- wall_carpet: бордовая база + двойная золотая рамка + ромб-
  медальон с золотыми углами + декоративные уголки.
- blanket: 30 звёздочек (белые/жёлтые) разного размера на шумной
  тёмно-синей базе.
- poster_csgo: диагональная штриховка + силуэт AWP по центру +
  пиксельный «CS:GO» текст внизу.
- poster_morgen: круг с лицом, глаза-щели, рот + пиксельный «MS».
- door: вертикальное волокно Perlin + 3 горизонтальных шва (панели)
  + 4 сучка разного размера.
- desk_white: чистый белый + жёлтый Steam-стикер в углу + царапина.

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

541 lines
17 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)
)
# Применить текстуру к материалу с заданным 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