StePe

Untitled

Mar 10th, 2026
74
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 15.45 KB | None | 0 0
  1. import math
  2. import random
  3. import sys
  4. import colorsys
  5. from dataclasses import dataclass
  6.  
  7. import pygame
  8.  
  9. GRID_SIZE = 32
  10. CELL_SIZE = 16
  11. GRID_PIXELS = GRID_SIZE * CELL_SIZE
  12. LEFT_GRAPH_WIDTH = 170
  13. BOTTOM_GRAPH_HEIGHT = 170
  14. RIGHT_UI_WIDTH = 310
  15. MARGIN = 20
  16. FPS = 60
  17. ROW_SHIFT_PIXELS = 1.8
  18. COL_SHIFT_PIXELS = 1.8
  19. DEFAULT_FADE_PER_FRAME = 0.99
  20. COLOR_SHIFT_DEFAULT = 0.10
  21. UI_SLIDER_START_Y = 80
  22. UI_SLIDER_GAP_Y = 70
  23.  
  24. WINDOW_WIDTH = MARGIN * 3 + LEFT_GRAPH_WIDTH + GRID_PIXELS + RIGHT_UI_WIDTH
  25. WINDOW_HEIGHT = MARGIN * 3 + GRID_PIXELS + BOTTOM_GRAPH_HEIGHT
  26.  
  27. BG = (15, 18, 24)
  28. GRID_BG = (24, 29, 40)
  29. GRID_LINE = (42, 49, 64)
  30. GRID_VALUE = (68, 210, 255)
  31. AXIS_COLOR = (230, 230, 240)
  32. GRAPH_X_COLOR = (255, 176, 60)
  33. GRAPH_Y_COLOR = (92, 245, 174)
  34. TEXT = (230, 230, 240)
  35. TRACK = (50, 56, 70)
  36. FILL = (70, 130, 255)
  37. KNOB = (235, 239, 248)
  38. PANEL = (22, 26, 36)
  39. GRAPH_GRID = (44, 50, 64)
  40.  
  41.  
  42. class Perlin2D:
  43.     def __init__(self, seed: int):
  44.         rng = random.Random(seed)
  45.         p = list(range(256))
  46.         rng.shuffle(p)
  47.         self.perm = p + p
  48.  
  49.     @staticmethod
  50.     def _fade(t: float) -> float:
  51.         return t * t * t * (t * (t * 6 - 15) + 10)
  52.  
  53.     @staticmethod
  54.     def _lerp(a: float, b: float, t: float) -> float:
  55.         return a + t * (b - a)
  56.  
  57.     def _grad(self, h: int, x: float, y: float) -> float:
  58.         h = h & 7
  59.         if h == 0:
  60.             return x + y
  61.         if h == 1:
  62.             return -x + y
  63.         if h == 2:
  64.             return x - y
  65.         if h == 3:
  66.             return -x - y
  67.         if h == 4:
  68.             return x
  69.         if h == 5:
  70.             return -x
  71.         if h == 6:
  72.             return y
  73.         return -y
  74.  
  75.     def noise(self, x: float, y: float) -> float:
  76.         xi = math.floor(x) & 255
  77.         yi = math.floor(y) & 255
  78.         xf = x - math.floor(x)
  79.         yf = y - math.floor(y)
  80.         u = self._fade(xf)
  81.         v = self._fade(yf)
  82.  
  83.         aa = self.perm[self.perm[xi] + yi]
  84.         ab = self.perm[self.perm[xi] + yi + 1]
  85.         ba = self.perm[self.perm[xi + 1] + yi]
  86.         bb = self.perm[self.perm[xi + 1] + yi + 1]
  87.  
  88.         x1 = self._lerp(self._grad(aa, xf, yf), self._grad(ba, xf - 1.0, yf), u)
  89.         x2 = self._lerp(self._grad(ab, xf, yf - 1.0), self._grad(bb, xf - 1.0, yf - 1.0), u)
  90.         return self._lerp(x1, x2, v)
  91.  
  92.  
  93. @dataclass
  94. class Slider:
  95.     label: str
  96.     min_value: float
  97.     max_value: float
  98.     value: float
  99.     rect: pygame.Rect
  100.     decimals: int = 2
  101.     dragging: bool = False
  102.  
  103.     def _set_from_x(self, x: int) -> None:
  104.         t = (x - self.rect.left) / self.rect.width
  105.         t = max(0.0, min(1.0, t))
  106.         self.value = self.min_value + t * (self.max_value - self.min_value)
  107.  
  108.     def handle_event(self, event: pygame.event.Event) -> None:
  109.         if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1 and self.rect.collidepoint(event.pos):
  110.             self.dragging = True
  111.             self._set_from_x(event.pos[0])
  112.         elif event.type == pygame.MOUSEBUTTONUP and event.button == 1:
  113.             self.dragging = False
  114.         elif event.type == pygame.MOUSEMOTION and self.dragging:
  115.             self._set_from_x(event.pos[0])
  116.  
  117.     def draw(self, surface: pygame.Surface, font: pygame.font.Font) -> None:
  118.         pygame.draw.rect(surface, TRACK, self.rect, border_radius=5)
  119.  
  120.         t = (self.value - self.min_value) / (self.max_value - self.min_value)
  121.         fill_width = int(self.rect.width * t)
  122.         if fill_width > 0:
  123.             pygame.draw.rect(
  124.                 surface,
  125.                 FILL,
  126.                 (self.rect.left, self.rect.top, fill_width, self.rect.height),
  127.                 border_radius=5,
  128.             )
  129.  
  130.         knob_x = self.rect.left + fill_width
  131.         pygame.draw.circle(surface, KNOB, (knob_x, self.rect.centery), self.rect.height // 2 + 3)
  132.  
  133.         label = f"{self.label}: {self.value:.{self.decimals}f}"
  134.         text_surface = font.render(label, True, TEXT)
  135.         surface.blit(text_surface, (self.rect.left, self.rect.top - 20))
  136.  
  137.  
  138. def sample_profile(
  139.     noise: Perlin2D, t: float, speed: float, amplitude: float, scale: float, count: int
  140. ) -> list[float]:
  141.     values = []
  142.     spatial_freq = 0.23
  143.     scroll_y = t * speed
  144.     for i in range(count):
  145.         n = noise.noise(i * spatial_freq * scale, scroll_y)
  146.         values.append(max(-1.0, min(1.0, n * amplitude)))
  147.     return values
  148.  
  149.  
  150. def draw_grid(surface: pygame.Surface, rect: pygame.Rect, x_values: list[float], y_values: list[float]) -> None:
  151.     _ = x_values, y_values
  152.     pygame.draw.rect(surface, (0, 0, 0), rect)
  153.  
  154.  
  155. def advect_axes_and_dim(
  156.     grid_surface: pygame.Surface, x_values: list[float], y_values: list[float], fade_factor: float
  157. ) -> None:
  158.     src = grid_surface.copy()
  159.     rows_shifted = pygame.Surface((GRID_SIZE, GRID_SIZE))
  160.  
  161.     # Pass 1: Horizontal shift per row based on Y-axis noise.
  162.     for y in range(GRID_SIZE):
  163.         shift = y_values[y] * ROW_SHIFT_PIXELS
  164.         for x in range(GRID_SIZE):
  165.             sample_x = (x - shift) % GRID_SIZE
  166.             x0 = int(math.floor(sample_x)) % GRID_SIZE
  167.             x1 = (x0 + 1) % GRID_SIZE
  168.             frac = sample_x - math.floor(sample_x)
  169.  
  170.             c0 = src.get_at((x0, y))
  171.             c1 = src.get_at((x1, y))
  172.             r = (c0.r * (1.0 - frac)) + (c1.r * frac)
  173.             g = (c0.g * (1.0 - frac)) + (c1.g * frac)
  174.             b = (c0.b * (1.0 - frac)) + (c1.b * frac)
  175.             rows_shifted.set_at((x, y), (int(r), int(g), int(b)))
  176.  
  177.     # Pass 2: Vertical shift per column based on X-axis noise, then dim.
  178.     for x in range(GRID_SIZE):
  179.         shift = x_values[x] * COL_SHIFT_PIXELS
  180.         for y in range(GRID_SIZE):
  181.             sample_y = (y - shift) % GRID_SIZE
  182.             y0 = int(math.floor(sample_y)) % GRID_SIZE
  183.             y1 = (y0 + 1) % GRID_SIZE
  184.             frac = sample_y - math.floor(sample_y)
  185.  
  186.             c0 = rows_shifted.get_at((x, y0))
  187.             c1 = rows_shifted.get_at((x, y1))
  188.  
  189.             r = ((c0.r * (1.0 - frac)) + (c1.r * frac)) * fade_factor
  190.             g = ((c0.g * (1.0 - frac)) + (c1.g * frac)) * fade_factor
  191.             b = ((c0.b * (1.0 - frac)) + (c1.b * frac)) * fade_factor
  192.  
  193.             grid_surface.set_at((x, y), (int(r), int(g), int(b)))
  194.  
  195.  
  196. def draw_x_graph(surface: pygame.Surface, rect: pygame.Rect, values: list[float], amplitude: float, title: str, font: pygame.font.Font) -> None:
  197.     pygame.draw.rect(surface, PANEL, rect, border_radius=10)
  198.     for i in range(GRID_SIZE):
  199.         x = rect.left + 12 + i * ((rect.width - 24) / (GRID_SIZE - 1))
  200.         pygame.draw.line(surface, GRAPH_GRID, (x, rect.top + 12), (x, rect.bottom - 12), 1)
  201.  
  202.     center_y = rect.centery
  203.     pygame.draw.line(surface, AXIS_COLOR, (rect.left + 12, center_y), (rect.right - 12, center_y), 2)
  204.  
  205.     if len(values) < 2:
  206.         return
  207.  
  208.     amp_px = max(6, int((rect.height * 0.42) * min(1.0, amplitude)))
  209.     points = []
  210.     for i, val in enumerate(values):
  211.         x = rect.left + 12 + i * ((rect.width - 24) / (len(values) - 1))
  212.         y = center_y + val * amp_px
  213.         points.append((x, y))
  214.     pygame.draw.lines(surface, GRAPH_X_COLOR, False, points, 3)
  215.  
  216.     surface.blit(font.render(title, True, TEXT), (rect.left + 12, rect.top + 8))
  217.  
  218.  
  219. def draw_y_graph(surface: pygame.Surface, rect: pygame.Rect, values: list[float], amplitude: float, title: str, font: pygame.font.Font) -> None:
  220.     pygame.draw.rect(surface, PANEL, rect, border_radius=10)
  221.     for i in range(GRID_SIZE):
  222.         y = rect.top + 12 + i * ((rect.height - 24) / (GRID_SIZE - 1))
  223.         pygame.draw.line(surface, GRAPH_GRID, (rect.left + 12, y), (rect.right - 12, y), 1)
  224.  
  225.     center_x = rect.centerx
  226.     pygame.draw.line(surface, AXIS_COLOR, (center_x, rect.top + 12), (center_x, rect.bottom - 12), 2)
  227.  
  228.     if len(values) < 2:
  229.         return
  230.  
  231.     amp_px = max(6, int((rect.width * 0.42) * min(1.0, amplitude)))
  232.     points = []
  233.     for i, val in enumerate(values):
  234.         y = rect.top + 12 + i * ((rect.height - 24) / (len(values) - 1))
  235.         x = center_x + val * amp_px
  236.         points.append((x, y))
  237.     pygame.draw.lines(surface, GRAPH_Y_COLOR, False, points, 3)
  238.  
  239.     surface.blit(font.render(title, True, TEXT), (rect.left + 12, rect.top + 8))
  240.  
  241.  
  242. def rainbow_color_with_phase(t: float, speed: float, phase: float) -> tuple[int, int, int]:
  243.     hue = (t * speed + phase) % 1.0
  244.     r, g, b = colorsys.hsv_to_rgb(hue, 1.0, 1.0)
  245.     return int(r * 255), int(g * 255), int(b * 255)
  246.  
  247.  
  248. def blend_subpixel(grid_surface: pygame.Surface, x: float, y: float, color: tuple[int, int, int]) -> None:
  249.     x0 = int(math.floor(x))
  250.     y0 = int(math.floor(y))
  251.     fx = x - x0
  252.     fy = y - y0
  253.  
  254.     for ox, wx in ((0, 1.0 - fx), (1, fx)):
  255.         for oy, wy in ((0, 1.0 - fy), (1, fy)):
  256.             px = x0 + ox
  257.             py = y0 + oy
  258.             if not (0 <= px < GRID_SIZE and 0 <= py < GRID_SIZE):
  259.                 continue
  260.             w = wx * wy
  261.             if w <= 0.0:
  262.                 continue
  263.             old = grid_surface.get_at((px, py))
  264.             nr = int(old.r * (1.0 - w) + color[0] * w)
  265.             ng = int(old.g * (1.0 - w) + color[1] * w)
  266.             nb = int(old.b * (1.0 - w) + color[2] * w)
  267.             grid_surface.set_at((px, py), (nr, ng, nb))
  268.  
  269.  
  270. def blend_pixel_weighted(grid_surface: pygame.Surface, px: int, py: int, color: tuple[int, int, int], w: float) -> None:
  271.     if not (0 <= px < GRID_SIZE and 0 <= py < GRID_SIZE):
  272.         return
  273.     w = max(0.0, min(1.0, w))
  274.     if w <= 0.0:
  275.         return
  276.     old = grid_surface.get_at((px, py))
  277.     nr = int(old.r * (1.0 - w) + color[0] * w)
  278.     ng = int(old.g * (1.0 - w) + color[1] * w)
  279.     nb = int(old.b * (1.0 - w) + color[2] * w)
  280.     grid_surface.set_at((px, py), (nr, ng, nb))
  281.  
  282.  
  283. def draw_aa_endpoint_disc(grid_surface: pygame.Surface, cx: float, cy: float, color: tuple[int, int, int], radius: float = 0.75) -> None:
  284.     min_x = max(0, int(math.floor(cx - radius - 1.0)))
  285.     max_x = min(GRID_SIZE - 1, int(math.ceil(cx + radius + 1.0)))
  286.     min_y = max(0, int(math.floor(cy - radius - 1.0)))
  287.     max_y = min(GRID_SIZE - 1, int(math.ceil(cy + radius + 1.0)))
  288.     for py in range(min_y, max_y + 1):
  289.         for px in range(min_x, max_x + 1):
  290.             dx = (px + 0.5) - cx
  291.             dy = (py + 0.5) - cy
  292.             dist = math.hypot(dx, dy)
  293.             # 1px soft edge for antialiasing.
  294.             w = max(0.0, min(1.0, radius + 0.5 - dist))
  295.             blend_pixel_weighted(grid_surface, px, py, color, w)
  296.  
  297.  
  298. def draw_aa_subpixel_line(
  299.     grid_surface: pygame.Surface,
  300.     x0: float,
  301.     y0: float,
  302.     x1: float,
  303.     y1: float,
  304.     t: float,
  305.     color_shift: float,
  306. ) -> None:
  307.     dx = x1 - x0
  308.     dy = y1 - y0
  309.     steps = max(1, int(max(abs(dx), abs(dy)) * 3))
  310.     for i in range(steps + 1):
  311.         u = i / steps
  312.         x = x0 + dx * u
  313.         y = y0 + dy * u
  314.         xi = math.floor(x)
  315.         yi = math.floor(y)
  316.         fx = x - xi
  317.         fy = y - yi
  318.         color = rainbow_color_with_phase(t, color_shift, u)
  319.  
  320.         blend_pixel_weighted(grid_surface, int(xi), int(yi), color, (1.0 - fx) * (1.0 - fy))
  321.         blend_pixel_weighted(grid_surface, int(xi + 1), int(yi), color, fx * (1.0 - fy))
  322.         blend_pixel_weighted(grid_surface, int(xi), int(yi + 1), color, (1.0 - fx) * fy)
  323.         blend_pixel_weighted(grid_surface, int(xi + 1), int(yi + 1), color, fx * fy)
  324.  
  325.  
  326. def inject_rainbow_border_rect(grid_surface: pygame.Surface, t: float, color_shift: float) -> None:
  327.     border_points = []
  328.     for x in range(GRID_SIZE):
  329.         border_points.append((x, 0))
  330.     for y in range(1, GRID_SIZE):
  331.         border_points.append((GRID_SIZE - 1, y))
  332.     for x in range(GRID_SIZE - 2, -1, -1):
  333.         border_points.append((x, GRID_SIZE - 1))
  334.     for y in range(GRID_SIZE - 2, 0, -1):
  335.         border_points.append((0, y))
  336.  
  337.     total = len(border_points)
  338.     for i, (x, y) in enumerate(border_points):
  339.         color = rainbow_color_with_phase(t, color_shift, i / total)
  340.         grid_surface.set_at((x, y), color)
  341.  
  342.  
  343. def main() -> None:
  344.     pygame.init()
  345.     screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
  346.     pygame.display.set_caption("Perlin Grid Visualisierung")
  347.     clock = pygame.time.Clock()
  348.  
  349.     font = pygame.font.SysFont("arial", 18)
  350.     small_font = pygame.font.SysFont("arial", 15)
  351.  
  352.     grid_rect = pygame.Rect(MARGIN + LEFT_GRAPH_WIDTH + MARGIN, MARGIN, GRID_PIXELS, GRID_PIXELS)
  353.     x_graph_rect = pygame.Rect(grid_rect.left, grid_rect.bottom + MARGIN, GRID_PIXELS, BOTTOM_GRAPH_HEIGHT)
  354.     y_graph_rect = pygame.Rect(MARGIN, grid_rect.top, LEFT_GRAPH_WIDTH, GRID_PIXELS)
  355.     grid_surface = pygame.Surface((GRID_SIZE, GRID_SIZE))
  356.     grid_surface.fill((0, 0, 0))
  357.  
  358.     ui_x = grid_rect.right + MARGIN
  359.     panel_rect = pygame.Rect(ui_x, MARGIN, RIGHT_UI_WIDTH, WINDOW_HEIGHT - 2 * MARGIN)
  360.     slider_y = lambda idx: MARGIN + UI_SLIDER_START_Y + idx * UI_SLIDER_GAP_Y
  361.  
  362.     sliders = [
  363.         Slider("X Speed", -2.00, 2.00, 0.10, pygame.Rect(ui_x + 20, slider_y(0), RIGHT_UI_WIDTH - 40, 14), 2),
  364.         Slider("X Amplitude", 0.10, 1.00, 1.00, pygame.Rect(ui_x + 20, slider_y(1), RIGHT_UI_WIDTH - 40, 14), 2),
  365.         Slider("X Frequency", 0.10, 4.00, 0.33, pygame.Rect(ui_x + 20, slider_y(2), RIGHT_UI_WIDTH - 40, 14), 2),
  366.         Slider("Y Speed", -2.00, 2.00, 0.10, pygame.Rect(ui_x + 20, slider_y(3), RIGHT_UI_WIDTH - 40, 14), 2),
  367.         Slider("Y Amplitude", 0.10, 1.00, 1.00, pygame.Rect(ui_x + 20, slider_y(4), RIGHT_UI_WIDTH - 40, 14), 2),
  368.         Slider("Y Frequency", 0.10, 4.00, 0.32, pygame.Rect(ui_x + 20, slider_y(5), RIGHT_UI_WIDTH - 40, 14), 2),
  369.         Slider("Color Shift", 0.00, 1.00, COLOR_SHIFT_DEFAULT, pygame.Rect(ui_x + 20, slider_y(6), RIGHT_UI_WIDTH - 40, 14), 2),
  370.         Slider("Fade %", 90.0, 99.999, 99.922, pygame.Rect(ui_x + 20, slider_y(7), RIGHT_UI_WIDTH - 40, 14), 3),
  371.     ]
  372.  
  373.     noise_x = Perlin2D(seed=42)
  374.     noise_y = Perlin2D(seed=1337)
  375.  
  376.     start_time = pygame.time.get_ticks() / 1000.0
  377.     running = True
  378.     while running:
  379.         dt = clock.tick(FPS) / 1000.0
  380.         _ = dt
  381.  
  382.         for event in pygame.event.get():
  383.             if event.type == pygame.QUIT:
  384.                 running = False
  385.             for slider in sliders:
  386.                 slider.handle_event(event)
  387.  
  388.         t = pygame.time.get_ticks() / 1000.0 - start_time
  389.  
  390.         x_speed, x_amp, x_scale, y_speed, y_amp, y_scale, color_shift, fade_percent = [s.value for s in sliders]
  391.         x_profile = sample_profile(noise_x, t, x_speed, x_amp, x_scale, GRID_SIZE)
  392.         x_profile = list(reversed(x_profile))
  393.         y_profile = sample_profile(noise_y, t, y_speed, y_amp, y_scale, GRID_SIZE)
  394.         fade_factor = fade_percent / 100.0
  395.  
  396.         inject_rainbow_border_rect(grid_surface, t, color_shift)
  397.         advect_axes_and_dim(grid_surface, x_profile, y_profile, fade_factor)
  398.  
  399.         screen.fill(BG)
  400.         draw_grid(screen, grid_rect, x_profile, y_profile)
  401.         scaled_grid = pygame.transform.scale(grid_surface, (GRID_PIXELS, GRID_PIXELS))
  402.         screen.blit(scaled_grid, grid_rect.topleft)
  403.         draw_x_graph(screen, x_graph_rect, x_profile, x_amp, "x controls columns", font)
  404.         draw_y_graph(screen, y_graph_rect, y_profile, y_amp, "y controls rows", font)
  405.  
  406.         pygame.draw.rect(screen, PANEL, panel_rect, border_radius=10)
  407.         title = font.render("Steuerung", True, TEXT)
  408.         screen.blit(title, (panel_rect.left + 20, panel_rect.top + 20))
  409.  
  410.         for slider in sliders:
  411.             slider.draw(screen, font)
  412.  
  413.         pygame.display.flip()
  414.  
  415.     pygame.quit()
  416.     sys.exit(0)
  417.  
  418.  
  419. if __name__ == "__main__":
  420.     main()
  421.  
Advertisement
Add Comment
Please, Sign In to add comment