StePe

Untitled

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