From d0eaabdd875a1d9cbc49101d592f7713d2a33192 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Wed, 15 Oct 2025 11:16:51 +0200 Subject: [PATCH] some new stuff. idk its all pretty fun! some C++ too! --- .gitignore | 2 + cube/fibonnacci.py | 60 ++ cube/main.py | 142 +++ cube/pattern.py | 52 + cube/tktcl.py | 173 ++++ simulations/astar/main.py | 199 ++++ simulations/balls/bowling.py | 731 ++++++++++++++ simulations/balls/main.py | 275 ++++++ simulations/donut.c/donut.cpp | 57 ++ simulations/donut.c/main.py | 55 ++ simulations/donut.c/tktcl.py | 113 +++ simulations/mandelbrotset/CppProperties.json | 21 + simulations/mandelbrotset/cpp/main.cpp | 22 + .../mandelbrotset/cpp/mandelbrot_app.cpp | 927 ++++++++++++++++++ .../mandelbrotset/cpp/mandelbrot_app.h | 91 ++ simulations/mandelbrotset/generate.py | 19 + simulations/mandelbrotset/mandelbrotset.py | 95 ++ .../mandelbrotset/mandelbrotsetCPP.cpp | 247 +++++ simulations/pendulum/main.py | 92 ++ simulations/sorting_quicksort/setup.py | 10 + simulations/sorting_quicksort/sorting.c | 48 + simulations/sorting_quicksort/sorting.py | 4 + stmkbezirke/main.py | 13 + 23 files changed, 3448 insertions(+) create mode 100644 cube/fibonnacci.py create mode 100644 cube/main.py create mode 100644 cube/pattern.py create mode 100644 cube/tktcl.py create mode 100644 simulations/astar/main.py create mode 100644 simulations/balls/bowling.py create mode 100644 simulations/balls/main.py create mode 100644 simulations/donut.c/donut.cpp create mode 100644 simulations/donut.c/main.py create mode 100644 simulations/donut.c/tktcl.py create mode 100644 simulations/mandelbrotset/CppProperties.json create mode 100644 simulations/mandelbrotset/cpp/main.cpp create mode 100644 simulations/mandelbrotset/cpp/mandelbrot_app.cpp create mode 100644 simulations/mandelbrotset/cpp/mandelbrot_app.h create mode 100644 simulations/mandelbrotset/generate.py create mode 100644 simulations/mandelbrotset/mandelbrotset.py create mode 100644 simulations/mandelbrotset/mandelbrotsetCPP.cpp create mode 100644 simulations/pendulum/main.py create mode 100644 simulations/sorting_quicksort/setup.py create mode 100644 simulations/sorting_quicksort/sorting.c create mode 100644 simulations/sorting_quicksort/sorting.py create mode 100644 stmkbezirke/main.py diff --git a/.gitignore b/.gitignore index 8c07dae..0359e52 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,8 @@ ehthumbs.db # C / C++ / Rust builds (if you use extensions) *.o *.so +*.obj +*.exe *.a *.out target/ diff --git a/cube/fibonnacci.py b/cube/fibonnacci.py new file mode 100644 index 0000000..915e910 --- /dev/null +++ b/cube/fibonnacci.py @@ -0,0 +1,60 @@ +import math +import tkinter as tk + +class Fibonacci: + def s5(self, n, r): + """Generate Fibonacci spiral polar coordinates""" + spirals = [] + phi = (1 + 5 ** 0.5) / 2 # golden ratio + for i in range(n + 1): + angle = (i * 360 / phi) % 360 + spirals.append((r * (i ** 0.5), angle)) + return spirals + + def pol2cart(self, r, theta): + x = r * math.cos(math.radians(theta)) + y = r * math.sin(math.radians(theta)) + return x, y + + def calculate_coordinates(self, num_points=200, distance=15): + # Convert polar to Cartesian coordinates + self.coordinates = [self.pol2cart(r, t) for r, t in self.s5(num_points, distance)] + # Center for the canvas + self.coordinates = [(x + 250, y + 250) for x, y in self.coordinates] + + def plot_numbers(self, canvas): + self.calculate_coordinates() + for idx, (x, y) in enumerate(self.coordinates, start=1): + canvas.create_oval(x - 7, y - 7, x + 7, y + 7, fill="white") + canvas.create_text(x, y, text=str(idx)) + + def plot_lines(self, canvas): + for delta in [21, 34]: + for start in range(34): + x0, y0 = self.coordinates[start] + i = start + delta + while i < len(self.coordinates): + x1, y1 = self.coordinates[i] + canvas.create_line(x0, y0, x1, y1) + x0, y0 = x1, y1 + i += delta + + def create_gui(self): + master = tk.Tk() + master.title("Fibonacci Spiral") + canvas = tk.Canvas(master, width=500, height=500, bg="white") + canvas.pack() + + self.plot_numbers(canvas) + self.plot_lines(canvas) + + master.mainloop() + + +def main(): + f = Fibonacci() + f.create_gui() + + +if __name__ == "__main__": + main() diff --git a/cube/main.py b/cube/main.py new file mode 100644 index 0000000..7ac8ba0 --- /dev/null +++ b/cube/main.py @@ -0,0 +1,142 @@ +import numpy as np +import matplotlib.pyplot as plt +from mpl_toolkits.mplot3d import Axes3D +from matplotlib.animation import FuncAnimation +from matplotlib.widgets import Button + +cube_points = np.array([ + [-1, -1, -1], + [-1, -1, 1], + [-1, 1, -1], + [-1, 1, 1], + [ 1, -1, -1], + [ 1, -1, 1], + [ 1, 1, -1], + [ 1, 1, 1] +]) + +edges = [ + (0, 1), (0, 2), (0, 4), + (1, 3), (1, 5), + (2, 3), (2, 6), + (3, 7), + (4, 5), (4, 6), + (5, 7), + (6, 7) +] + +def rotation_y(theta): + return np.array([ + [np.cos(theta), 0, np.sin(theta)], + [0, 1, 0], # 0,1,0 + [-np.sin(theta), 0, np.cos(theta)] + ]) + + +def rotation_z(theta): + return np.array([ + [np.cos(theta), -np.sin(theta), 0], + [np.sin(theta), np.cos(theta), 0], + [0, 0, 1] + ]) + + +fig = plt.figure(figsize=(8, 6)) +ax = fig.add_subplot(111, projection='3d') +ax.set_xlim(-2, 2) +ax.set_ylim(-2, 2) +ax.set_zlim(-2, 2) +ax.set_box_aspect([1, 1, 1]) +ax.set_title("Matrixmultiplikation", pad=20) + +lines = [ax.plot([], [], [], color='red')[0] for _ in edges] +text = ax.text2D(1.02, 0.5, "", transform=ax.transAxes, + fontsize=10, color='black', family='monospace', va='center') + +paused = False +highlight = False +points_scat = None +labels = [] + + +def update(frame): + global points_scat, labels + + if paused: + return lines + [text] + + theta = np.radians(frame) + R_y = rotation_y(theta) + R_z = rotation_z(theta * 0.7) + R = R_y @ R_z + + rotated = cube_points @ R.T + + # Update edges + for line, (i1, i2) in zip(lines, edges): + p1, p2 = rotated[i1], rotated[i2] + line.set_data([p1[0], p2[0]], [p1[1], p2[1]]) + line.set_3d_properties([p1[2], p2[2]]) + + # Update rotation matrices display + matrix_str_y = "\n".join( + ["[" + " ".join(f"{val:+.2f}" for val in row) + "]" for row in R_y] + ) + matrix_str_z = "\n".join( + ["[" + " ".join(f"{val:+.2f}" for val in row) + "]" for row in R_z] + ) + matrix_str_R = "\n".join( + ["[" + " ".join(f"{val:+.2f}" for val in row) + "]" for row in R] + ) + + text.set_text( + f"θ = {np.degrees(theta):6.2f}°\n" + f"sin(θ) = {np.sin(theta): .3f}\n" + f"cos(θ) = {np.cos(theta): .3f}\n\n" + f"R_y(θ):\n{matrix_str_y}\n\n" + f"R_z(0.7θ):\n{matrix_str_z}\n\n" + f"R = R_y · R_z:\n{matrix_str_R}" + ) + + # Always show vertex points and labels + if points_scat is None: + points_scat = ax.scatter([], [], [], color='blue', s=40) + + points_scat._offsets3d = ( + rotated[:, 0], rotated[:, 1], rotated[:, 2] + ) + + for label in labels: + label.remove() + labels.clear() + + for i, p in enumerate(rotated): + labels.append(ax.text(p[0], p[1], p[2], f"P{i}", + color='black', fontsize=8)) + + return lines + [text] + + + +axpause = plt.axes([0.4, 0.02, 0.3, 0.05]) +bpause = Button(axpause, 'Pause / Resume') + + +def toggle_pause(event): + global paused + paused = not paused + + +def toggle_highlight(event): + global highlight + highlight = not highlight + + +bpause.on_clicked(toggle_pause) + +ani = FuncAnimation( + fig, update, frames=np.arange(0, 360, 2), + interval=50, blit=False +) + +plt.show() diff --git a/cube/pattern.py b/cube/pattern.py new file mode 100644 index 0000000..4fa2457 --- /dev/null +++ b/cube/pattern.py @@ -0,0 +1,52 @@ +import pygame +import math +import sys + +# --- Pygame setup --- +pygame.init() +WIDTH, HEIGHT = 800, 800 +screen = pygame.display.set_mode((WIDTH, HEIGHT)) +clock = pygame.time.Clock() + +# --- Colors --- +BLACK = (0, 0, 0) +colors = [(255, 100, 100), (100, 255, 150), (100, 150, 255), (255, 255, 100)] + +# --- Fibonacci sequence generator --- +def fibonacci(n): + fibs = [0, 1] + for i in range(2, n): + fibs.append(fibs[-1] + fibs[-2]) + return fibs + +# --- Spiral drawing --- +def draw_fibonacci_spiral(n, angle_offset): + fibs = fibonacci(n) + cx, cy = WIDTH//2, HEIGHT//2 + scale = 0.05 # scale down Fibonacci numbers to fit screen + + for i, f in enumerate(fibs[1:], 1): # skip the first 0 + angle = i * 137.5 + angle_offset # golden angle in degrees + rad = math.radians(angle) + x = cx + f * math.cos(rad) * scale + y = cy + f * math.sin(rad) * scale + color = colors[i % len(colors)] + pygame.draw.circle(screen, color, (int(x), int(y)), max(int(f*scale*0.5)+2, 2)) + +# --- Main loop --- +angle_offset = 0 +running = True +while running: + screen.fill(BLACK) + draw_fibonacci_spiral(50, angle_offset) + angle_offset += 1 # rotate the spiral over time + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + + pygame.display.flip() + clock.tick(60) + +pygame.quit() +sys.exit() \ No newline at end of file diff --git a/cube/tktcl.py b/cube/tktcl.py new file mode 100644 index 0000000..4349520 --- /dev/null +++ b/cube/tktcl.py @@ -0,0 +1,173 @@ +""" +rotating_cube_tk.py + +Simple Tkinter app that rotates a 3D cube using rotation matrices along X and Z axes. +No dependencies outside the Python standard library. +""" + +import tkinter as tk +import math +import time + +# Canvas size +W, H = 700, 700 + +# Cube definition (8 vertices of a cube centered at origin) +size = 150 +vertices = [ + (-1, -1, -1), + (-1, -1, 1), + (-1, 1, -1), + (-1, 1, 1), + ( 1, -1, -1), + ( 1, -1, 1), + ( 1, 1, -1), + ( 1, 1, 1), +] +# Scale vertices by size +vertices = [(x * size, y * size, z * size) for (x, y, z) in vertices] + +# Edges connecting vertices (pairs of indices) +edges = [ + (0,1), (0,2), (0,4), + (1,3), (1,5), + (2,3), (2,6), + (3,7), + (4,5), (4,6), + (5,7), + (6,7), +] + +# Rotation speeds (radians per frame) +rot_speed_x = 0.02 +rot_speed_z = 0.015 + +# Perspective parameters +viewer_distance = 600 # Larger -> weaker perspective +fov = 500 # Field of view scaling + +class CubeApp: + def __init__(self, master): + self.master = master + master.title("3D Rotating Cube (X and Z rotation matrices)") + + self.canvas = tk.Canvas(master, width=W, height=H, bg="white") + self.canvas.pack(fill="both", expand=True) + + # angles + self.ang_x = 0.0 + self.ang_z = 0.0 + + # control + self.paused = False + self.last_time = time.time() + + # drawn items (to update instead of recreating shapes each frame) + self.line_ids = [] + for _ in edges: + self.line_ids.append(self.canvas.create_line(0,0,0,0, width=2)) + + # instructions text + self.canvas.create_text(10, 10, anchor="nw", + text="Space: pause/resume Up/Down: speed X Left/Right: speed Z", + fill="black", font=("Helvetica", 10)) + + # Bind keys + master.bind("", self.toggle_pause) + master.bind("", self.speed_up_x) + master.bind("", self.speed_down_x) + master.bind("", self.speed_up_z) + master.bind("", self.speed_down_z) + + # Start animation + self.animate() + + # Rotation matrix around X for angle a + def rotate_x(self, point, a): + x, y, z = point + cos_a = math.cos(a) + sin_a = math.sin(a) + y2 = y * cos_a - z * sin_a + z2 = y * sin_a + z * cos_a + return (x, y2, z2) + + # Rotation matrix around Z for angle a + def rotate_z(self, point, a): + x, y, z = point + cos_a = math.cos(a) + sin_a = math.sin(a) + x2 = x * cos_a - y * sin_a + y2 = x * sin_a + y * cos_a + return (x2, y2, z) + + # Project 3D point to 2D using simple perspective + def project(self, point): + x, y, z = point + # shift z relative to viewer so we don't divide by zero + z_shifted = z + viewer_distance + if z_shifted == 0: + z_shifted = 0.0001 + factor = fov / z_shifted + x_proj = x * factor + W/2 + y_proj = -y * factor + H/2 # invert y for screen coords + return (x_proj, y_proj) + + def animate(self): + # compute time delta for smoother animation (in case of slow frame) + now = time.time() + dt = now - self.last_time + self.last_time = now + + if not self.paused: + # update angles (scale by dt to be time-based) + self.ang_x += rot_speed_x * (dt * 60) # adjust to feel like frame-based speeds + self.ang_z += rot_speed_z * (dt * 60) + + # compute rotated points + rotated = [] + for v in vertices: + r = self.rotate_x(v, self.ang_x) + r = self.rotate_z(r, self.ang_z) + rotated.append(r) + + # project all points + projected = [self.project(p) for p in rotated] + + # draw edges by updating canvas lines + for i, (a, b) in enumerate(edges): + x1, y1 = projected[a] + x2, y2 = projected[b] + # update existing line coordinates + self.canvas.coords(self.line_ids[i], x1, y1, x2, y2) + + # optionally: draw small circles at vertices (commented out to keep it clean) + for (x, y) in projected: + self.canvas.create_oval(x-3, y-3, x+3, y+3, fill="black") + + # schedule next frame (aiming ~60 FPS) + self.master.after(1, self.animate) + + # Controls + def toggle_pause(self, event=None): + self.paused = not self.paused + + def speed_up_x(self, event=None): + global rot_speed_x + rot_speed_x += 0.005 + + def speed_down_x(self, event=None): + global rot_speed_x + rot_speed_x -= 0.005 + + def speed_up_z(self, event=None): + global rot_speed_z + rot_speed_z += 0.005 + + def speed_down_z(self, event=None): + global rot_speed_z + rot_speed_z -= 0.005 + +if __name__ == "__main__": + root = tk.Tk() + app = CubeApp(root) + root.mainloop() diff --git a/simulations/astar/main.py b/simulations/astar/main.py new file mode 100644 index 0000000..9c01934 --- /dev/null +++ b/simulations/astar/main.py @@ -0,0 +1,199 @@ +import pygame +import random +from queue import PriorityQueue +import time + +WIDTH = 600 +ROWS = 20 +CELL_SIZE = WIDTH // ROWS +FPS = 60 +DELAY = 0 + +WHITE = (255, 255, 255) +BLACK = (0, 0, 0) +GREEN = (0, 255, 0) +RED = (255, 0, 0) +BLUE = (0, 0, 255) +GRAY = (200, 200, 200) +YELLOW = (255, 255, 0) + +class Cell: + def __init__(self, row, col): + self.row = row + self.col = col + self.x = col * CELL_SIZE + self.y = row * CELL_SIZE + self.walls = [True, True, True, True] # top, right, bottom, left + self.visited = False + + def draw(self, win): + if self.visited: + pygame.draw.rect(win, WHITE, (self.x, self.y, CELL_SIZE, CELL_SIZE)) + if self.walls[0]: pygame.draw.line(win, BLACK, (self.x, self.y), (self.x+CELL_SIZE, self.y), 2) + if self.walls[1]: pygame.draw.line(win, BLACK, (self.x+CELL_SIZE, self.y), (self.x+CELL_SIZE, self.y+CELL_SIZE), 2) + if self.walls[2]: pygame.draw.line(win, BLACK, (self.x+CELL_SIZE, self.y+CELL_SIZE), (self.x, self.y+CELL_SIZE), 2) + if self.walls[3]: pygame.draw.line(win, BLACK, (self.x, self.y+CELL_SIZE), (self.x, self.y), 2) + + def highlight(self, win, color): + pygame.draw.rect(win, color, (self.x, self.y, CELL_SIZE, CELL_SIZE)) + + +def generate_maze(grid, current): + stack = [] + current.visited = True + + while True: + neighbors = [] + r, c = current.row, current.col + if r > 0 and not grid[r-1][c].visited: neighbors.append(grid[r-1][c]) + if r < ROWS-1 and not grid[r+1][c].visited: neighbors.append(grid[r+1][c]) + if c > 0 and not grid[r][c-1].visited: neighbors.append(grid[r][c-1]) + if c < ROWS-1 and not grid[r][c+1].visited: neighbors.append(grid[r][c+1]) + + if neighbors: + next_cell = random.choice(neighbors) + stack.append(current) + remove_walls(current, next_cell) + next_cell.visited = True + current = next_cell + elif stack: + current = stack.pop() + else: + break + +def remove_walls(a, b): + dx = a.col - b.col + dy = a.row - b.row + if dx == 1: + a.walls[3] = False + b.walls[1] = False + elif dx == -1: + a.walls[1] = False + b.walls[3] = False + if dy == 1: + a.walls[0] = False + b.walls[2] = False + elif dy == -1: + a.walls[2] = False + b.walls[0] = False + +def heuristic(a, b): + return abs(a.row - b.row) + abs(a.col - b.col) + +def astar(grid, start, end, win): + count = 0 + open_set = PriorityQueue() + open_set.put((0, count, start)) + came_from = {} + g_score = {cell: float("inf") for row in grid for cell in row} + g_score[start] = 0 + f_score = {cell: float("inf") for row in grid for cell in row} + f_score[start] = heuristic(start, end) + open_set_hash = {start} + + while not open_set.empty(): + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + return [] + + current = open_set.get()[2] + open_set_hash.remove(current) + + if current == end: + path = [] + while current in came_from: + current = came_from[current] + path.append(current) + return path + + for neighbor in get_neighbors(grid, current): + temp_g = g_score[current] + 1 + if temp_g < g_score[neighbor]: + came_from[neighbor] = current + g_score[neighbor] = temp_g + f_score[neighbor] = temp_g + heuristic(neighbor, end) + if neighbor not in open_set_hash: + count += 1 + open_set.put((f_score[neighbor], count, neighbor)) + open_set_hash.add(neighbor) + + # visualize + draw_grid(win, grid) + for cell in open_set_hash: + cell.highlight(win, GREEN) + pygame.display.update() + time.sleep(DELAY) + + return [] + +def get_neighbors(grid, cell): + neighbors = [] + r, c = cell.row, cell.col + if not cell.walls[0] and r > 0: neighbors.append(grid[r-1][c]) + if not cell.walls[1] and c < ROWS-1: neighbors.append(grid[r][c+1]) + if not cell.walls[2] and r < ROWS-1: neighbors.append(grid[r+1][c]) + if not cell.walls[3] and c > 0: neighbors.append(grid[r][c-1]) + return neighbors + +# --- DRAWING --- +def draw_grid(win, grid): + win.fill(WHITE) + for row in grid: + for cell in row: + cell.draw(win) + +# --- MAIN --- +def main(): + global ROWS, CELL_SIZE + pygame.init() + win = pygame.display.set_mode((WIDTH, WIDTH)) + pygame.display.set_caption("A*") + + clock = pygame.time.Clock() + + def create_maze(rows): + global CELL_SIZE + CELL_SIZE = WIDTH // rows + grid = [[Cell(r, c) for c in range(rows)] for r in range(rows)] + generate_maze(grid, grid[0][0]) + return grid + + grid = create_maze(ROWS) + start = grid[0][0] + end = grid[ROWS-1][ROWS-1] + path = [] + + running = True + while running: + clock.tick(FPS) + draw_grid(win, grid) + start.highlight(win, BLUE) + end.highlight(win, RED) + for cell in path: + cell.highlight(win, YELLOW) + pygame.display.update() + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_SPACE: + path = astar(grid, start, end, win) + if event.key == pygame.K_UP: + ROWS += 1 + grid = create_maze(ROWS) + start = grid[0][0] + end = grid[ROWS-1][ROWS-1] + path = [] + if event.key == pygame.K_DOWN and ROWS > 5: + ROWS -= 1 + grid = create_maze(ROWS) + start = grid[0][0] + end = grid[ROWS-1][ROWS-1] + path = [] + + pygame.quit() + +if __name__ == "__main__": + main() diff --git a/simulations/balls/bowling.py b/simulations/balls/bowling.py new file mode 100644 index 0000000..2be59ca --- /dev/null +++ b/simulations/balls/bowling.py @@ -0,0 +1,731 @@ +import pygame +from pygame.locals import * +from OpenGL.GL import * +from OpenGL.GLU import * +import math +import random +import numpy as np + +class Ball: + def __init__(self): + self.pos = [0, 0.5, 15] + self.vel = [0, 0, 0] + self.rot = [0, 0, 0] + self.rot_vel = [0, 0, 0] + self.radius = 0.5 + self.mass = 7.0 + self.launched = False + self.finger_holes = [] + self.texture = None + self.load_texture() + + def load_texture(self): + # Create a simple procedural texture for the ball + texture_size = 64 + texture_data = np.zeros((texture_size, texture_size, 3), dtype=np.uint8) + + for i in range(texture_size): + for j in range(texture_size): + # Create a marble-like pattern + x = i / texture_size * 2 * math.pi + y = j / texture_size * 2 * math.pi + + # Base color (dark blue/black) + r = 20 + int(10 * math.sin(x * 5) * math.cos(y * 5)) + g = 20 + int(10 * math.sin(x * 3) * math.cos(y * 7)) + b = 60 + int(20 * math.sin(x * 7) * math.cos(y * 3)) + + texture_data[i, j] = [r, g, b] + + self.texture = glGenTextures(1) + glBindTexture(GL_TEXTURE_2D, self.texture) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture_size, texture_size, 0, + GL_RGB, GL_UNSIGNED_BYTE, texture_data) + + def reset(self): + self.pos = [0, 0.5, 15] + self.vel = [0, 0, 0] + self.rot = [0, 0, 0] + self.rot_vel = [0, 0, 0] + self.launched = False + + def launch(self, power, angle): + self.launched = True + self.vel[2] = -power * 20 # Increased power + self.vel[0] = math.sin(math.radians(angle)) * power * 8 + self.rot_vel[0] = -power * 300 # More realistic spin + + def update(self, dt, pins): + if not self.launched: + return False + + # Gravity + self.vel[1] -= 9.8 * dt + + # Floor collision with improved physics + if self.pos[1] - self.radius < 0: + self.pos[1] = self.radius + # Energy loss on bounce + self.vel[1] = -self.vel[1] * 0.6 # Bounce with energy loss + + # Improved friction based on surface contact + friction = 0.95 + self.vel[0] *= friction + self.vel[2] *= friction + + # Add slight random variation for more realistic movement + self.vel[0] += random.uniform(-0.1, 0.1) + self.vel[2] += random.uniform(-0.1, 0.1) + + # Air resistance + air_resistance = 0.99 + self.vel[0] *= air_resistance + self.vel[2] *= air_resistance + + # Update position + for i in range(3): + self.pos[i] += self.vel[i] * dt + self.rot[i] += self.rot_vel[i] * dt + + # Check collisions with pins + for pin in pins: + if pin.standing: + dx = self.pos[0] - pin.pos[0] + dz = self.pos[2] - pin.pos[2] + dist = math.sqrt(dx*dx + dz*dz) + + if dist < self.radius + pin.radius: + pin.knock_down(self.vel) + # More realistic bounce physics + normal = [dx/dist, 0, dz/dist] + dot = self.vel[0]*normal[0] + self.vel[2]*normal[2] + self.vel[0] -= 1.8 * dot * normal[0] + self.vel[2] -= 1.8 * dot * normal[2] + + # Add some vertical bounce + self.vel[1] += 0.5 + + # Check if ball is out of bounds + if self.pos[2] < -5 or abs(self.pos[0]) > 10: + return True + + # Check if ball has stopped + speed = math.sqrt(self.vel[0]**2 + self.vel[1]**2 + self.vel[2]**2) + if speed < 0.1 and self.pos[1] <= self.radius + 0.01: + return True + + return False + + def draw(self): + glPushMatrix() + glTranslatef(*self.pos) + glRotatef(self.rot[0], 1, 0, 0) + glRotatef(self.rot[1], 0, 1, 0) + + # Enable texturing for the ball + glEnable(GL_TEXTURE_2D) + glBindTexture(GL_TEXTURE_2D, self.texture) + + # Ball body with texture + glColor3f(0.8, 0.8, 1.0) # Lighter base color + quad = gluNewQuadric() + gluQuadricTexture(quad, GL_TRUE) + gluSphere(quad, self.radius, 32, 32) + + # Finger holes + glDisable(GL_TEXTURE_2D) + glColor3f(0.05, 0.05, 0.05) + for angle in [0, 120, 240]: + glPushMatrix() + glRotatef(angle, 0, 1, 0) + glTranslatef(0.3, 0, 0) + gluCylinder(quad, 0.06, 0.06, 0.2, 16, 1) + glPopMatrix() + + glDisable(GL_TEXTURE_2D) + glPopMatrix() + +class Pin: + def __init__(self, x, z): + self.pos = [x, 0.75, z] + self.vel = [0, 0, 0] + self.rot = [0, 0, 0] + self.rot_vel = [0, 0, 0] + self.radius = 0.15 + self.height = 0.75 + self.standing = True + self.fall_timer = 0 + self.initial_pos = [x, 0.75, z] # Store initial position for reset + + def knock_down(self, impact_vel): + if self.standing: + self.standing = False + # More realistic pin physics based on impact + self.vel = [ + impact_vel[0] * random.uniform(0.3, 0.7), + random.uniform(1.5, 3.0), + impact_vel[2] * random.uniform(0.3, 0.7) + ] + self.rot_vel = [ + random.uniform(-400, 400), + random.uniform(-400, 400), + random.uniform(-400, 400) + ] + + def reset(self): + self.pos = self.initial_pos.copy() + self.vel = [0, 0, 0] + self.rot = [0, 0, 0] + self.rot_vel = [0, 0, 0] + self.standing = True + self.fall_timer = 0 + + def update(self, dt): + if not self.standing: + # Gravity + self.vel[1] -= 9.8 * dt + + # Floor collision with improved physics + if self.pos[1] < 0.1: + self.pos[1] = 0.1 + self.vel[1] = -self.vel[1] * 0.3 # Bounce with energy loss + self.vel[0] *= 0.7 + self.vel[2] *= 0.7 + self.rot_vel = [v * 0.7 for v in self.rot_vel] + + # Update position + for i in range(3): + self.pos[i] += self.vel[i] * dt + self.rot[i] += self.rot_vel[i] * dt + + self.fall_timer += dt + + def draw(self): + glPushMatrix() + glTranslatef(*self.pos) + + if not self.standing: + glRotatef(self.rot[0], 1, 0, 0) + glRotatef(self.rot[1], 0, 1, 0) + glRotatef(self.rot[2], 0, 0, 1) + + # Pin body with improved materials + glColor3f(0.95, 0.95, 0.95) # Brighter white + quad = gluNewQuadric() + gluCylinder(quad, 0.15, 0.08, self.height, 16, 1) + + # Red stripes with gloss effect + glColor3f(0.9, 0.1, 0.1) + glPushMatrix() + glTranslatef(0, 0, self.height * 0.6) + gluCylinder(quad, 0.11, 0.09, self.height * 0.1, 16, 1) + glPopMatrix() + + # Second stripe + glPushMatrix() + glTranslatef(0, 0, self.height * 0.3) + gluCylinder(quad, 0.12, 0.11, self.height * 0.1, 16, 1) + glPopMatrix() + + # Top with rounded cap + glPushMatrix() + glTranslatef(0, 0, self.height) + gluSphere(quad, 0.08, 16, 16) + glPopMatrix() + + glPopMatrix() + +class BowlingGame: + def __init__(self): + pygame.init() + self.width, self.height = 1200, 800 + self.screen = pygame.display.set_mode((self.width, self.height), DOUBLEBUF | OPENGL) + pygame.display.set_caption("Enhanced 3D Bowling Game") + + self.setup_gl() + + self.ball = Ball() + self.pins = self.setup_pins() + + self.power = 0 + self.angle = 0 + self.charging = False + self.max_power = 1.0 + + self.score = 0 + self.frame = 1 + self.roll = 1 + self.game_over = False + + self.camera_pos = [0, 5, 20] + self.camera_target = [0, 1, 0] + self.camera_angle = 0 + self.camera_distance = 20 + + self.lane_texture = self.create_lane_texture() + self.background_color = [0.1, 0.2, 0.4] # Dark blue background + + # Game state + self.waiting_for_roll = True + self.ball_done = False + self.knocked_this_roll = 0 + + # Lighting + self.light_pos = [5, 10, 10] + self.ambient_light = [0.4, 0.4, 0.4, 1.0] + self.diffuse_light = [0.9, 0.9, 0.9, 1.0] + self.specular_light = [1.0, 1.0, 1.0, 1.0] + + def create_lane_texture(self): + texture_size = 256 + texture_data = np.zeros((texture_size, texture_size, 3), dtype=np.uint8) + + for i in range(texture_size): + for j in range(texture_size): + # Create wood grain pattern + wood_intensity = 150 + int(50 * math.sin(i/20) * math.cos(j/30)) + texture_data[i, j] = [wood_intensity, wood_intensity-20, wood_intensity-40] + + texture = glGenTextures(1) + glBindTexture(GL_TEXTURE_2D, texture) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture_size, texture_size, 0, + GL_RGB, GL_UNSIGNED_BYTE, texture_data) + return texture + + def setup_gl(self): + glEnable(GL_DEPTH_TEST) + glEnable(GL_LIGHTING) + glEnable(GL_LIGHT0) + glEnable(GL_LIGHT1) # Additional light + glEnable(GL_COLOR_MATERIAL) + glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE) + + # Improved lighting + glLightfv(GL_LIGHT0, GL_POSITION, (5, 10, 10, 1)) + glLightfv(GL_LIGHT0, GL_AMBIENT, (0.4, 0.4, 0.4, 1)) + glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.9, 0.9, 0.9, 1)) + glLightfv(GL_LIGHT0, GL_SPECULAR, (1.0, 1.0, 1.0, 1)) + + # Second light for better illumination + glLightfv(GL_LIGHT1, GL_POSITION, (-5, 8, 5, 1)) + glLightfv(GL_LIGHT1, GL_AMBIENT, (0.2, 0.2, 0.2, 1)) + glLightfv(GL_LIGHT1, GL_DIFFUSE, (0.5, 0.5, 0.5, 1)) + glLightfv(GL_LIGHT1, GL_SPECULAR, (0.5, 0.5, 0.5, 1)) + + # Material properties for better reflections + glMaterialfv(GL_FRONT, GL_SPECULAR, [1.0, 1.0, 1.0, 1.0]) + glMaterialf(GL_FRONT, GL_SHININESS, 50.0) + + glMatrixMode(GL_PROJECTION) + gluPerspective(45, self.width/self.height, 0.1, 100.0) + glMatrixMode(GL_MODELVIEW) + + def setup_pins(self): + pins = [] + start_z = -10 + spacing = 0.5 + + # Standard 10-pin triangle formation + for row in range(4): + for col in range(row + 1): + x = (col - row/2) * spacing + z = start_z - row * spacing * 0.866 + pins.append(Pin(x, z)) + + return pins + + def reset_pins(self): + for pin in self.pins: + pin.reset() + + def reset_ball(self): + self.ball.reset() + self.power = 0 + self.angle = 0 + self.charging = False + self.ball_done = False + self.waiting_for_roll = True + + def count_knocked_pins(self): + return sum(1 for pin in self.pins if not pin.standing) + + def draw_lane(self): + # Enable texturing for the lane + glEnable(GL_TEXTURE_2D) + glBindTexture(GL_TEXTURE_2D, self.lane_texture) + + # Lane with texture + glColor3f(0.9, 0.8, 0.6) # Wood color + glBegin(GL_QUADS) + glTexCoord2f(0, 0) + glVertex3f(-2, 0, 20) + glTexCoord2f(1, 0) + glVertex3f(2, 0, 20) + glTexCoord2f(1, 1) + glVertex3f(2, 0, -15) + glTexCoord2f(0, 1) + glVertex3f(-2, 0, -15) + glEnd() + + glDisable(GL_TEXTURE_2D) + + # Gutters with improved appearance + glColor3f(0.3, 0.3, 0.3) + glBegin(GL_QUADS) + # Left gutter + glVertex3f(-2.5, 0, 20) + glVertex3f(-2, 0, 20) + glVertex3f(-2, 0, -15) + glVertex3f(-2.5, 0, -15) + # Right gutter + glVertex3f(2, 0, 20) + glVertex3f(2.5, 0, 20) + glVertex3f(2.5, 0, -15) + glVertex3f(2, 0, -15) + glEnd() + + # Gutter edges + glColor3f(0.5, 0.5, 0.5) + glLineWidth(2.0) + glBegin(GL_LINES) + # Left edge + glVertex3f(-2.5, 0.1, 20) + glVertex3f(-2.5, 0.1, -15) + # Right edge + glVertex3f(2.5, 0.1, 20) + glVertex3f(2.5, 0.1, -15) + glEnd() + + # Lane boundaries + glColor3f(0.9, 0.9, 0.1) # Bright yellow + glLineWidth(3.0) + glBegin(GL_LINES) + # Left boundary + glVertex3f(-2, 0.01, 20) + glVertex3f(-2, 0.01, -15) + # Right boundary + glVertex3f(2, 0.01, 20) + glVertex3f(2, 0.01, -15) + glEnd() + + # Arrows with improved visibility + glColor3f(0.7, 0.7, 0.2) + for z in range(5, 15, 3): + glBegin(GL_TRIANGLES) + glVertex3f(0, 0.01, z) + glVertex3f(-0.2, 0.01, z + 0.5) + glVertex3f(0.2, 0.01, z + 0.5) + glEnd() + + # Foul line + glColor3f(1.0, 0.0, 0.0) # Red + glLineWidth(2.0) + glBegin(GL_LINES) + glVertex3f(-2, 0.02, 0) + glVertex3f(2, 0.02, 0) + glEnd() + + glEnable(GL_LIGHTING) + + def draw_hud(self): + glDisable(GL_LIGHTING) + glMatrixMode(GL_PROJECTION) + glPushMatrix() + glLoadIdentity() + glOrtho(0, self.width, self.height, 0, -1, 1) + glMatrixMode(GL_MODELVIEW) + glPushMatrix() + glLoadIdentity() + + glDisable(GL_DEPTH_TEST) + + # Semi-transparent HUD background + glEnable(GL_BLEND) + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + glColor4f(0.1, 0.1, 0.2, 0.7) + glBegin(GL_QUADS) + glVertex2f(0, 0) + glVertex2f(self.width, 0) + glVertex2f(self.width, 80) + glVertex2f(0, 80) + glEnd() + glDisable(GL_BLEND) + + # Score display + font = pygame.font.SysFont('Arial', 24) + score_text = font.render(f"Score: {self.score} | Frame: {self.frame} | Roll: {self.roll}", True, (255, 255, 255)) + text_surface = pygame.image.tostring(score_text, 'RGBA', True) + glRasterPos2f(20, 20) + glDrawPixels(score_text.get_width(), score_text.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface) + + # Power bar with improved visuals + if not self.ball.launched and self.waiting_for_roll: + bar_width = 200 + bar_height = 25 + x, y = 20, self.height - 60 + + # Background + glColor3f(0.3, 0.3, 0.3) + glBegin(GL_QUADS) + glVertex2f(x, y) + glVertex2f(x + bar_width, y) + glVertex2f(x + bar_width, y + bar_height) + glVertex2f(x, y + bar_height) + glEnd() + + # Power fill with gradient + fill = self.power * bar_width + if self.power < 0.3: + glColor3f(0.0, 1.0, 0.0) # Green + elif self.power < 0.7: + glColor3f(1.0, 1.0, 0.0) # Yellow + else: + glColor3f(1.0, 0.0, 0.0) # Red + + glBegin(GL_QUADS) + glVertex2f(x, y) + glVertex2f(x + fill, y) + glVertex2f(x + fill, y + bar_height) + glVertex2f(x, y + bar_height) + glEnd() + + # Border + glColor3f(1.0, 1.0, 1.0) + glLineWidth(2.0) + glBegin(GL_LINE_LOOP) + glVertex2f(x, y) + glVertex2f(x + bar_width, y) + glVertex2f(x + bar_width, y + bar_height) + glVertex2f(x, y + bar_height) + glEnd() + + # Power label + power_text = font.render("Power", True, (255, 255, 255)) + text_surface = pygame.image.tostring(power_text, 'RGBA', True) + glRasterPos2f(x, y - 25) + glDrawPixels(power_text.get_width(), power_text.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface) + + # Angle indicator + angle_y = y - 50 + glColor3f(0.5, 0.5, 1.0) + glLineWidth(3.0) + glBegin(GL_LINES) + glVertex2f(x + bar_width/2, angle_y) + glVertex2f(x + bar_width/2 + self.angle * 2, angle_y) + glEnd() + + # Angle label + angle_text = font.render("Angle", True, (255, 255, 255)) + text_surface = pygame.image.tostring(angle_text, 'RGBA', True) + glRasterPos2f(x, angle_y - 25) + glDrawPixels(angle_text.get_width(), angle_text.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface) + + # Instructions + instructions = font.render("Use LEFT/RIGHT to aim, SPACE to charge power", True, (200, 200, 100)) + text_surface = pygame.image.tostring(instructions, 'RGBA', True) + glRasterPos2f(self.width - instructions.get_width() - 20, 20) + glDrawPixels(instructions.get_width(), instructions.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface) + + # Display knocked pins for current roll + if self.ball_done: + knocked_text = font.render(f"Knocked down: {self.knocked_this_roll} pins", True, (255, 255, 255)) + text_surface = pygame.image.tostring(knocked_text, 'RGBA', True) + glRasterPos2f(self.width/2 - knocked_text.get_width()/2, 50) + glDrawPixels(knocked_text.get_width(), knocked_text.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface) + + continue_text = font.render("Press SPACE to continue", True, (200, 200, 100)) + text_surface = pygame.image.tostring(continue_text, 'RGBA', True) + glRasterPos2f(self.width/2 - continue_text.get_width()/2, 80) + glDrawPixels(continue_text.get_width(), continue_text.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface) + + glEnable(GL_DEPTH_TEST) + + glPopMatrix() + glMatrixMode(GL_PROJECTION) + glPopMatrix() + glMatrixMode(GL_MODELVIEW) + glEnable(GL_LIGHTING) + + def handle_input(self): + keys = pygame.key.get_pressed() + + # Camera controls + if keys[K_a]: + self.camera_angle -= 1 + if keys[K_d]: + self.camera_angle += 1 + if keys[K_w] and self.camera_distance > 10: + self.camera_distance -= 0.5 + if keys[K_s] and self.camera_distance < 30: + self.camera_distance += 0.5 + + # Update camera position based on angle + rad_angle = math.radians(self.camera_angle) + self.camera_pos[0] = math.sin(rad_angle) * self.camera_distance + self.camera_pos[2] = math.cos(rad_angle) * self.camera_distance + 5 + + if self.waiting_for_roll and not self.ball.launched: + # Adjust angle + if keys[K_LEFT]: + self.angle -= 1 + self.angle = max(-30, self.angle) + if keys[K_RIGHT]: + self.angle += 1 + self.angle = min(30, self.angle) + + # Charge power + if keys[K_SPACE]: + if not self.charging: + self.charging = True + self.power = 0 + else: + self.power += 0.02 + if self.power > self.max_power: + self.power = self.max_power + else: + if self.charging and self.power > 0.1: # Minimum power threshold + self.ball.launch(self.power, self.angle) + self.charging = False + self.waiting_for_roll = False + + # Continue to next roll + if self.ball_done and keys[K_SPACE]: + self.next_roll() + + def next_roll(self): + self.knocked_this_roll = self.count_knocked_pins() + self.score += self.knocked_this_roll + + # Game progression logic + if self.roll == 1: + if self.knocked_this_roll == 10: # Strike + self.roll = 3 # Skip second roll + else: + self.roll = 2 + elif self.roll == 2: + self.roll = 3 + + if self.roll == 3: + self.frame += 1 + self.roll = 1 + self.reset_pins() + + if self.frame > 10: # End of game + self.game_over = True + + self.reset_ball() + + def run(self): + clock = pygame.time.Clock() + running = True + + while running: + dt = clock.tick(60) / 1000.0 + + for event in pygame.event.get(): + if event.type == QUIT: + running = False + elif event.type == KEYDOWN: + if event.key == K_ESCAPE: + running = False + elif event.key == K_r: + self.reset_ball() + self.reset_pins() + self.score = 0 + self.frame = 1 + self.roll = 1 + self.game_over = False + + self.handle_input() + + # Update game objects + if not self.ball_done: + self.ball_done = self.ball.update(dt, self.pins) + + for pin in self.pins: + pin.update(dt) + + # Render + glClearColor(*self.background_color, 1.0) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + # Update camera target to follow ball + if self.ball.launched and not self.ball_done: + self.camera_target[0] = self.ball.pos[0] * 0.1 + self.camera_target[2] = self.ball.pos[2] * 0.1 + else: + self.camera_target = [0, 1, 0] + + gluLookAt(self.camera_pos[0], self.camera_pos[1], self.camera_pos[2], + self.camera_target[0], self.camera_target[1], self.camera_target[2], + 0, 1, 0) + + self.draw_lane() + self.ball.draw() + + for pin in self.pins: + pin.draw() + + self.draw_hud() + + # Game over screen + if self.game_over: + glDisable(GL_LIGHTING) + glMatrixMode(GL_PROJECTION) + glPushMatrix() + glLoadIdentity() + glOrtho(0, self.width, self.height, 0, -1, 1) + glMatrixMode(GL_MODELVIEW) + glPushMatrix() + glLoadIdentity() + + glDisable(GL_DEPTH_TEST) + + # Semi-transparent overlay + glEnable(GL_BLEND) + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + glColor4f(0.0, 0.0, 0.0, 0.7) + glBegin(GL_QUADS) + glVertex2f(0, 0) + glVertex2f(self.width, 0) + glVertex2f(self.width, self.height) + glVertex2f(0, self.height) + glEnd() + glDisable(GL_BLEND) + + # Game over text + font = pygame.font.SysFont('Arial', 48) + game_over_text = font.render("GAME OVER", True, (255, 255, 255)) + text_surface = pygame.image.tostring(game_over_text, 'RGBA', True) + glRasterPos2f(self.width/2 - game_over_text.get_width()/2, self.height/2 - 50) + glDrawPixels(game_over_text.get_width(), game_over_text.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface) + + score_text = font.render(f"Final Score: {self.score}", True, (255, 255, 255)) + text_surface = pygame.image.tostring(score_text, 'RGBA', True) + glRasterPos2f(self.width/2 - score_text.get_width()/2, self.height/2 + 20) + glDrawPixels(score_text.get_width(), score_text.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface) + + restart_text = pygame.font.SysFont('Arial', 24).render("Press R to restart", True, (200, 200, 100)) + text_surface = pygame.image.tostring(restart_text, 'RGBA', True) + glRasterPos2f(self.width/2 - restart_text.get_width()/2, self.height/2 + 80) + glDrawPixels(restart_text.get_width(), restart_text.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, text_surface) + + glEnable(GL_DEPTH_TEST) + glPopMatrix() + glMatrixMode(GL_PROJECTION) + glPopMatrix() + glMatrixMode(GL_MODELVIEW) + glEnable(GL_LIGHTING) + + pygame.display.flip() + + pygame.quit() + +if __name__ == "__main__": + game = BowlingGame() + game.run() \ No newline at end of file diff --git a/simulations/balls/main.py b/simulations/balls/main.py new file mode 100644 index 0000000..aa71c7d --- /dev/null +++ b/simulations/balls/main.py @@ -0,0 +1,275 @@ +import pygame +import math +import random + +# Initialize Pygame +pygame.init() + +# Screen setup +WIDTH, HEIGHT = 800, 600 +screen = pygame.display.set_mode((WIDTH, HEIGHT)) +pygame.display.set_caption("Enhanced Physics Bouncing Balls") + +# Colors +BLACK = (0, 0, 0) +WHITE = (255, 255, 255) +RED = (220, 50, 50) +GREEN = (50, 220, 50) +CYAN = (50, 220, 220) +YELLOW = (220, 220, 50) + +# Clock +clock = pygame.time.Clock() +FPS = 60 + +# Global physics +gravity = 0.5 +elasticity = 0.85 +air_resistance = 0.99 # 1.0 = no resistance, lower = more resistance +friction = 0.98 # Ground friction when rolling + +# Ball class +class Ball: + def __init__(self, x, y): + self.radius = 20 + self.x = x + self.y = y + self.mass = self.radius / 10.0 # Mass proportional to radius + # Give random initial velocity in both x and y + self.vx = random.uniform(-5, 5) + self.vy = random.uniform(-5, 0) + self.tracer_length = 50 + self.tracers = [] + self.dragging = False + self.drag_start_pos = None + self.drag_last_pos = (x, y) + self.drag_history = [] + + def update(self): + if not self.dragging: + # Apply gravity + self.vy += gravity + + # Apply air resistance (quadratic drag) + speed = math.hypot(self.vx, self.vy) + if speed > 0: + drag_force = (1 - air_resistance) * speed + drag_x = -drag_force * (self.vx / speed) + drag_y = -drag_force * (self.vy / speed) + self.vx += drag_x + self.vy += drag_y + + # Update position + self.x += self.vx + self.y += self.vy + + # Bounce off floor with friction + if self.y + self.radius > HEIGHT: + self.y = HEIGHT - self.radius + self.vy = -self.vy * elasticity + self.vx *= friction # Apply friction when bouncing on ground + + # Stop if moving very slowly + if abs(self.vy) < 0.1: + self.vy = 0 + if abs(self.vx) < 0.1: + self.vx = 0 + + # Bounce off ceiling + if self.y - self.radius < 0: + self.y = self.radius + self.vy = -self.vy * elasticity + + # Bounce off right wall + if self.x + self.radius > WIDTH: + self.x = WIDTH - self.radius + self.vx = -self.vx * elasticity + + # Bounce off left wall + if self.x - self.radius < 0: + self.x = self.radius + self.vx = -self.vx * elasticity + + # Update tracers + self.tracers.append((int(self.x), int(self.y))) + if len(self.tracers) > self.tracer_length: + self.tracers.pop(0) + + def start_drag(self, mx, my): + self.dragging = True + self.drag_start_pos = (mx, my) + self.drag_last_pos = (mx, my) + self.drag_history = [(mx, my)] + self.vx = 0 + self.vy = 0 + + def update_drag(self, mx, my): + if self.dragging: + self.x, self.y = mx, my + self.drag_history.append((mx, my)) + if len(self.drag_history) > 5: # Keep last 5 positions for velocity calculation + self.drag_history.pop(0) + self.drag_last_pos = (mx, my) + + def end_drag(self): + if self.dragging and len(self.drag_history) >= 2: + # Calculate velocity from drag motion (throw mechanics) + dx = self.drag_history[-1][0] - self.drag_history[0][0] + dy = self.drag_history[-1][1] - self.drag_history[0][1] + frames = len(self.drag_history) + + # Set velocity based on drag motion + self.vx = dx / frames * 2 + self.vy = dy / frames * 2 + + self.dragging = False + self.drag_start_pos = None + self.drag_history = [] + + def draw(self, surface): + # Draw tracers with fade effect + for i, pos in enumerate(self.tracers): + alpha = int(255 * (i / len(self.tracers))) + size = int(3 + 2 * (i / len(self.tracers))) + color = (50, 220, 220) + pygame.draw.circle(surface, color, pos, size) + + # Draw ball + pygame.draw.circle(surface, RED, (int(self.x), int(self.y)), self.radius) + + # Draw velocity vector (scaled for visibility) + if not self.dragging: + vel_scale = 3 + pygame.draw.line(surface, GREEN, + (int(self.x), int(self.y)), + (int(self.x + self.vx * vel_scale), + int(self.y + self.vy * vel_scale)), 3) + + # Draw drag trajectory preview + if self.dragging and self.drag_start_pos: + pygame.draw.line(surface, YELLOW, + self.drag_start_pos, + (int(self.x), int(self.y)), 2) + # Draw arrow at the end + pygame.draw.circle(surface, YELLOW, (int(self.x), int(self.y)), 5) + +# Ball-to-ball collision detection and response +def handle_ball_collisions(balls): + for i, ball1 in enumerate(balls): + for ball2 in balls[i+1:]: + dx = ball2.x - ball1.x + dy = ball2.y - ball1.y + distance = math.hypot(dx, dy) + + # Check if balls are colliding + if distance < ball1.radius + ball2.radius and distance > 0: + # Normalize collision vector + nx = dx / distance + ny = dy / distance + + # Separate balls + overlap = (ball1.radius + ball2.radius) - distance + ball1.x -= nx * overlap * 0.5 + ball1.y -= ny * overlap * 0.5 + ball2.x += nx * overlap * 0.5 + ball2.y += ny * overlap * 0.5 + + # Calculate relative velocity + dvx = ball2.vx - ball1.vx + dvy = ball2.vy - ball1.vy + + # Calculate relative velocity in collision normal direction + dot_product = dvx * nx + dvy * ny + + # Only resolve if balls are moving toward each other + if dot_product < 0: + # Calculate impulse (conservation of momentum with equal mass) + impulse = 2 * dot_product / (ball1.mass + ball2.mass) + + # Apply impulse to both balls + ball1.vx += impulse * ball2.mass * nx * elasticity + ball1.vy += impulse * ball2.mass * ny * elasticity + ball2.vx -= impulse * ball1.mass * nx * elasticity + ball2.vy -= impulse * ball1.mass * ny * elasticity + +# List of balls +balls = [Ball(WIDTH//2, HEIGHT//2)] + +running = True +while running: + mx, my = pygame.mouse.get_pos() + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + + # Drag start + elif event.type == pygame.MOUSEBUTTONDOWN: + for ball in balls: + if math.hypot(mx - ball.x, my - ball.y) < ball.radius + 10: + ball.start_drag(mx, my) + break + + # Drag end + elif event.type == pygame.MOUSEBUTTONUP: + for ball in balls: + if ball.dragging: + ball.end_drag() + + # Spawn new ball + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_SPACE: + balls.append(Ball(random.randint(50, WIDTH-50), + random.randint(50, HEIGHT-50))) + elif event.key == pygame.K_c: + balls.clear() + balls.append(Ball(WIDTH//2, HEIGHT//2)) + + # Keyboard control for physics + keys = pygame.key.get_pressed() + if keys[pygame.K_UP]: + gravity = min(2.0, gravity + 0.01) + if keys[pygame.K_DOWN]: + gravity = max(0, gravity - 0.01) + if keys[pygame.K_RIGHT]: + elasticity = min(1.0, elasticity + 0.01) + if keys[pygame.K_LEFT]: + elasticity = max(0, elasticity - 0.01) + if keys[pygame.K_w]: + air_resistance = min(1.0, air_resistance + 0.001) + if keys[pygame.K_s]: + air_resistance = max(0.9, air_resistance - 0.001) + + # Update dragging balls + for ball in balls: + if ball.dragging: + ball.update_drag(mx, my) + + # Update balls + for ball in balls: + ball.update() + + # Handle collisions between balls + handle_ball_collisions(balls) + + # Drawing + screen.fill(BLACK) + for ball in balls: + ball.draw(screen) + + # Physics info + font = pygame.font.SysFont(None, 22) + info = [ + f"Gravity: {gravity:.2f} (↑↓) Elasticity: {elasticity:.2f} (←→)", + f"Air Resistance: {air_resistance:.3f} (WS) Balls: {len(balls)}", + f"SPACE: Add ball C: Clear Drag to throw!" + ] + + for i, text in enumerate(info): + surface = font.render(text, True, WHITE) + screen.blit(surface, (10, 10 + i * 25)) + + pygame.display.flip() + clock.tick(FPS) + +pygame.quit() \ No newline at end of file diff --git a/simulations/donut.c/donut.cpp b/simulations/donut.c/donut.cpp new file mode 100644 index 0000000..d2ff3cd --- /dev/null +++ b/simulations/donut.c/donut.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include + +int main() { + float A = 0, B = 0; + float i, j; + int k; + float z[1760]; + char b[1760]; + + // Clear screen + std::printf("\x1b[2J"); + + for (;;) { + std::memset(b, 32, 1760); // fill with spaces + std::memset(z, 0, 1760 * sizeof(float)); + + for (j = 0; j < 6.28; j += 0.07) { + for (i = 0; i < 6.28; i += 0.02) { + float c = std::sin(i); + float d = std::cos(j); + float e = std::sin(A); + float f = std::sin(j); + float g = std::cos(A); + float h = d + 2; + float D = 1 / (c * h * e + f * g + 5); + float l = std::cos(i); + float m = std::cos(B); + float n = std::sin(B); + float t = c * h * g - f * e; + int x = int(40 + 30 * D * (l * h * m - t * n)); + int y = int(12 + 15 * D * (l * h * n + t * m)); + int o = x + 80 * y; + int N = int(8 * ((f * e - c * d * g) * m - c * d * e - f * g - l * d * n)); + + if (y >= 0 && y < 22 && x >= 0 && x < 80 && D > z[o]) { + z[o] = D; + b[o] = ".,-~:;=!*#$@"[N > 0 ? N : 0]; + } + } + } + + std::printf("\x1b[H"); + for (k = 0; k < 1761; k++) { + putchar(k % 80 ? b[k] : 10); + } + + A += 0.04f; + B += 0.02f; + + Sleep(10); // milliseconds + } + + return 0; +} diff --git a/simulations/donut.c/main.py b/simulations/donut.c/main.py new file mode 100644 index 0000000..40adfd0 --- /dev/null +++ b/simulations/donut.c/main.py @@ -0,0 +1,55 @@ +import math +def render_frame(A, B): + cosA = math.cos(A) + sinA = math.sin(A) + cosB = math.cos(B) + sinB = math.sin(B) + char_output = [] + zbuffer = [] + for i in range(screen_height + 1): + char_output.append([' '] * (screen_width + 0)) + zbuffer.append([0] * (screen_width + 0)) + theta = 0 + while (theta < 2* math.pi): + theta += theta_spacing + costheta = math.cos(theta) + sintheta = math.sin(theta) + phi = 0 + while (phi < 2*math.pi): + phi += phi_spacing + cosphi = math.cos(phi) + sinphi = math.sin(phi) + circlex = R2 + R1*costheta + circley = R1*sintheta + x = circlex*(cosB*cosphi + sinA*sinB*sinphi) - circley*cosA*sinB + y = circlex*(sinB*cosphi - sinA*cosB*sinphi) + circley*cosA*cosB + z = K2 + cosA*circlex*sinphi + circley*sinA + ooz = 1/z + xp = int(screen_width/2 + K1*ooz*x) + yp = int(screen_height/2 - K1*ooz*y) + L = cosphi*costheta*sinB - cosA*costheta*sinphi - sinA*sintheta + cosB*(cosA*sintheta - costheta*sinA*sinphi) + if L > 0: + if ooz > zbuffer[xp][yp]: + zbuffer[xp][yp] = ooz + luminance_index = L*8 + char_output[xp][yp] = '.,-~:;=!*#$@'[int(luminance_index)] + print('\x1b[H') + for i in range(screen_height): + for j in range(screen_width): + print(char_output[i][j], end='') + print() +theta_spacing = 0.07 +phi_spacing = 0.02 +R1 = 1 +R2 = 2 +K2 = 5 +screen_width = 35 +screen_height = 35 +K1 = screen_width*K2*3/(8*(R1+R2)) +print('\x1b[2J') +A = 1.0 +B = 1.0 +for i in range(250): + render_frame(A, B) + A += 0.08 + B += 0.03 \ No newline at end of file diff --git a/simulations/donut.c/tktcl.py b/simulations/donut.c/tktcl.py new file mode 100644 index 0000000..8f6ea16 --- /dev/null +++ b/simulations/donut.c/tktcl.py @@ -0,0 +1,113 @@ +import tkinter as tk +import math + +# Torus parameters +theta_spacing = 0.07 +phi_spacing = 0.02 +R1 = 1 +R2 = 2 +K2 = 5 + +chars = ".,-~:;=!*#$@" # luminance chars + +# Base character grid (like "screen resolution") +screen_width = 80 +screen_height = 24 + +# Tkinter setup +root = tk.Tk() +root.title("Spinning Torus Demo") + +# Get window size and calculate scaling +window_width = 800 +window_height = 400 +root.geometry(f"{window_width}x{window_height}") + +# Calculate character cell size to fit screen dimensions +cell_width = window_width // screen_width +cell_height = window_height // screen_height +font_size = min(cell_width, cell_height) +font = ("Courier", font_size) + +canvas = tk.Canvas(root, width=window_width, height=window_height, bg="black") +canvas.pack() + +# Pre-create text items for the character grid +text_items = [] +for y in range(screen_height): + row = [] + for x in range(screen_width): + item = canvas.create_text( + x*cell_width, y*cell_height, + text=' ', + anchor='nw', + fill='white', + font=font + ) + row.append(item) + text_items.append(row) + +# Calculate K1 based on screen width +K1 = screen_width * K2 * 3 / (8 * (R1 + R2)) + +def render_frame(A, B): + cosA = math.cos(A) + sinA = math.sin(A) + cosB = math.cos(B) + sinB = math.sin(B) + + output = [[' ' for _ in range(screen_height)] for _ in range(screen_width)] + zbuffer = [[0 for _ in range(screen_height)] for _ in range(screen_width)] + + theta = 0 + while theta < 2 * math.pi: + costheta = math.cos(theta) + sintheta = math.sin(theta) + + phi = 0 + while phi < 2 * math.pi: + cosphi = math.cos(phi) + sinphi = math.sin(phi) + + circlex = R2 + R1 * costheta + circley = R1 * sintheta + + x = circlex * (cosB * cosphi + sinA * sinB * sinphi) - circley * cosA * sinB + y = circlex * (sinB * cosphi - sinA * cosB * sinphi) + circley * cosA * cosB + z = K2 + cosA * circlex * sinphi + circley * sinA + ooz = 1 / z + + xp = int(screen_width / 2 + K1 * ooz * x) + yp = int(screen_height / 2 - K1 * ooz * y) + + L = cosphi * costheta * sinB - cosA * costheta * sinphi - sinA * sintheta + cosB * (cosA * sintheta - costheta * sinA * sinphi) + + if L > 0: + if 0 <= xp < screen_width and 0 <= yp < screen_height: + if ooz > zbuffer[xp][yp]: + zbuffer[xp][yp] = ooz + luminance_index = int(L * 8) + if luminance_index >= len(chars): + luminance_index = len(chars) - 1 + output[xp][yp] = chars[luminance_index] + + phi += phi_spacing + theta += theta_spacing + + # Update Tkinter canvas + for y in range(screen_height): + for x in range(screen_width): + canvas.itemconfigure(text_items[y][x], text=output[x][y]) + +# Animation loop +A = 0 +B = 0 +def animate(): + global A, B + render_frame(A, B) + A += 0.07 + B += 0.03 + root.after(30, animate) + +animate() +root.mainloop() diff --git a/simulations/mandelbrotset/CppProperties.json b/simulations/mandelbrotset/CppProperties.json new file mode 100644 index 0000000..659bf4e --- /dev/null +++ b/simulations/mandelbrotset/CppProperties.json @@ -0,0 +1,21 @@ +{ + "configurations": [ + { + "inheritEnvironments": [ + "msvc_x86" + ], + "name": "x86-Debug", + "includePath": [ + "${env.INCLUDE}", + "${workspaceRoot}\\**" + ], + "defines": [ + "WIN32", + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "intelliSenseMode": "windows-msvc-x86" + } + ] +} \ No newline at end of file diff --git a/simulations/mandelbrotset/cpp/main.cpp b/simulations/mandelbrotset/cpp/main.cpp new file mode 100644 index 0000000..c9ca1fb --- /dev/null +++ b/simulations/mandelbrotset/cpp/main.cpp @@ -0,0 +1,22 @@ +#include +#include "mandelbrot_app.h" + +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) { + // Check for DirectX 11 support + if (FAILED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0, + D3D11_SDK_VERSION, nullptr, nullptr, nullptr))) { + MessageBox(nullptr, L"DirectX 11 is not available on this system.", L"Error", MB_OK | MB_ICONERROR); + return 1; + } + + try { + MandelbrotApp app(hInstance); + app.Run(); + } + catch (...) { + MessageBox(nullptr, L"An unexpected error occurred.", L"Error", MB_OK | MB_ICONERROR); + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/simulations/mandelbrotset/cpp/mandelbrot_app.cpp b/simulations/mandelbrotset/cpp/mandelbrot_app.cpp new file mode 100644 index 0000000..cb3dc41 --- /dev/null +++ b/simulations/mandelbrotset/cpp/mandelbrot_app.cpp @@ -0,0 +1,927 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mandelbrot_app.h" + +#ifndef GET_X_LPARAM +#define GET_X_LPARAM(lParam) ((int)(short)LOWORD(lParam)) +#endif + +#ifndef GET_Y_LPARAM +#define GET_Y_LPARAM(lParam) ((int)(short)HIWORD(lParam)) +#endif + +const char* computeShaderSource = R"( +cbuffer CB : register(b0) { + float xmin, xmax, ymin, ymax; + int width, height, maxIter; + float time; +}; + +RWTexture2D Output : register(u0); + +float3 palette(float t, int scheme) { + if (scheme == 0) { + float3 a = float3(0.5, 0.5, 0.5); + float3 b = float3(0.5, 0.5, 0.5); + float3 c = float3(1.0, 1.0, 1.0); + float3 d = float3(0.0, 0.33, 0.67); + return a + b * cos(6.28318 * (c * t + d)); + } else if (scheme == 1) { + float3 a = float3(0.5, 0.5, 0.0); + float3 b = float3(0.5, 0.5, 0.0); + float3 c = float3(1.0, 0.7, 0.4); + float3 d = float3(0.0, 0.15, 0.20); + return a + b * cos(6.28318 * (c * t + d)); + } else if (scheme == 2) { + float3 a = float3(0.2, 0.5, 0.8); + float3 b = float3(0.2, 0.4, 0.2); + float3 c = float3(2.0, 1.0, 1.0); + float3 d = float3(0.0, 0.25, 0.25); + return a + b * cos(6.28318 * (c * t + d)); + } else if (scheme == 3) { + float3 a = float3(0.5, 0.2, 0.8); + float3 b = float3(0.5, 0.5, 0.5); + float3 c = float3(1.0, 1.0, 0.5); + float3 d = float3(0.8, 0.9, 0.3); + return a + b * cos(6.28318 * (c * t + d)); + } else { + float v = 0.5 + 0.5 * cos(6.28318 * t); + return float3(v, v, v); + } +} + +[numthreads(8, 8, 1)] +void main(uint3 DTid : SV_DispatchThreadID) { + int x = DTid.x; + int y = DTid.y; + + if (x >= width || y >= height) return; + + float cx = xmin + (xmax - xmin) * x / (float)width; + float cy = ymin + (ymax - ymin) * y / (float)height; + + float zx = 0.0; + float zy = 0.0; + float zx2 = 0.0; + float zy2 = 0.0; + int iter = 0; + + while (iter < maxIter && (zx2 + zy2) < 4.0) { + zy = 2.0 * zx * zy + cy; + zx = zx2 - zy2 + cx; + zx2 = zx * zx; + zy2 = zy * zy; + iter++; + } + + float4 color; + if (iter == maxIter) { + color = float4(0.0, 0.0, 0.0, 1.0); + } else { + float log_zn = log(zx2 + zy2) * 0.5; + float nu = log2(log_zn); + float smooth_iter = iter + 1.0 - nu; + + int scheme = maxIter >> 16; + int actualMaxIter = maxIter & 0xFFFF; + + float t = smooth_iter / 50.0 + time * 0.02; + float3 rgb = palette(t, scheme); + + float brightness = 0.5 + 0.5 * sin(smooth_iter * 0.1); + rgb *= brightness; + + color = float4(rgb, 1.0); + } + + Output[uint2(x, y)] = color; +} +)"; + +const char* vertexShaderSource = R"( +struct VS_OUTPUT { + float4 pos : SV_POSITION; + float2 tex : TEXCOORD0; +}; + +VS_OUTPUT main(uint id : SV_VertexID) { + VS_OUTPUT output; + output.tex = float2((id << 1) & 2, id & 2); + output.pos = float4(output.tex * float2(2, -2) + float2(-1, 1), 0, 1); + return output; +} +)"; + +const char* pixelShaderSource = R"( +Texture2D tex : register(t0); +SamplerState samp : register(s0); + +float4 main(float4 pos : SV_POSITION, float2 texCoord : TEXCOORD0) : SV_TARGET { + return tex.Sample(samp, texCoord); +} +)"; + +MandelbrotApp::MandelbrotApp(HINSTANCE hInstance) : hInstance_(hInstance) { + QueryPerformanceFrequency(&perfFreq_); + QueryPerformanceCounter(&lastFrameTime_); + + CreateMainWindow(); + if (hwnd_) { + InitD3D(); + CreateComputeShader(); + UpdateTitle(); + } +} + +MandelbrotApp::~MandelbrotApp() { + if (context_) context_->ClearState(); +} + +void MandelbrotApp::CreateMainWindow() { + WNDCLASSEX wc = {}; + wc.cbSize = sizeof(WNDCLASSEX); + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = WndProc; + wc.hInstance = hInstance_; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wc.lpszClassName = _T("MandelbrotViewerGPU"); + + if (!RegisterClassEx(&wc)) { + MessageBox(nullptr, L"Failed to register window class", L"Error", MB_OK); + return; + } + + RECT rc = { 0, 0, 1280, 720 }; + AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE); + + hwnd_ = CreateWindowEx( + 0, + _T("MandelbrotViewerGPU"), + _T("GPU Mandelbrot Viewer"), + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, + rc.right - rc.left, rc.bottom - rc.top, + nullptr, nullptr, hInstance_, this + ); + + if (!hwnd_) { + MessageBox(nullptr, L"Failed to create window", L"Error", MB_OK); + return; + } + + ShowWindow(hwnd_, SW_SHOWDEFAULT); + UpdateWindow(hwnd_); +} + +void MandelbrotApp::InitD3D() { + RECT rc; + GetClientRect(hwnd_, &rc); + + imageWidth_ = rc.right - rc.left; + imageHeight_ = rc.bottom - rc.top; + if (imageWidth_ < 1) imageWidth_ = 1; + if (imageHeight_ < 1) imageHeight_ = 1; + + DXGI_SWAP_CHAIN_DESC scd = {}; + scd.BufferCount = 2; + scd.BufferDesc.Width = imageWidth_; + scd.BufferDesc.Height = imageHeight_; + scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + scd.BufferDesc.RefreshRate.Numerator = 60; + scd.BufferDesc.RefreshRate.Denominator = 1; + scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + scd.OutputWindow = hwnd_; + scd.SampleDesc.Count = 1; + scd.SampleDesc.Quality = 0; + scd.Windowed = TRUE; + scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + + D3D_FEATURE_LEVEL featureLevels[] = { + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0 + }; + UINT numFeatureLevels = ARRAYSIZE(featureLevels); + + D3D_FEATURE_LEVEL featureLevel; + UINT flags = 0; + + HRESULT hr = D3D11CreateDeviceAndSwapChain( + nullptr, + D3D_DRIVER_TYPE_HARDWARE, + nullptr, + flags, + featureLevels, + numFeatureLevels, + D3D11_SDK_VERSION, + &scd, + &swapChain_, + &device_, + &featureLevel, + &context_ + ); + + if (FAILED(hr)) { + MessageBox(nullptr, L"Failed to create D3D11 device and swap chain", L"Error", MB_OK); + return; + } + + ComPtr backBuffer; + hr = swapChain_->GetBuffer(0, IID_PPV_ARGS(&backBuffer)); + if (FAILED(hr)) { + MessageBox(nullptr, L"Failed to get swap chain back buffer", L"Error", MB_OK); + return; + } + + hr = device_->CreateRenderTargetView(backBuffer.Get(), nullptr, &renderTargetView_); + if (FAILED(hr)) { + MessageBox(nullptr, L"Failed to create render target view", L"Error", MB_OK); + return; + } + + D3D11_SAMPLER_DESC sampDesc = {}; + sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; + sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; + sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; + sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER; + sampDesc.MinLOD = 0; + sampDesc.MaxLOD = D3D11_FLOAT32_MAX; + + hr = device_->CreateSamplerState(&sampDesc, &samplerState_); + if (FAILED(hr)) { + MessageBox(nullptr, L"Failed to create sampler state", L"Error", MB_OK); + return; + } + + // Set the viewport + D3D11_VIEWPORT vp; + vp.Width = (float)imageWidth_; + vp.Height = (float)imageHeight_; + vp.MinDepth = 0.0f; + vp.MaxDepth = 1.0f; + vp.TopLeftX = 0; + vp.TopLeftY = 0; + context_->RSSetViewports(1, &vp); + + // Set render target + context_->OMSetRenderTargets(1, renderTargetView_.GetAddressOf(), nullptr); + + ResizeBuffers(imageWidth_, imageHeight_); +} + +void MandelbrotApp::CreateComputeShader() { + if (!device_) return; + + ComPtr csBlob, errBlob; + + HRESULT hr = D3DCompile( + computeShaderSource, + strlen(computeShaderSource), + nullptr, + nullptr, + nullptr, + "main", + "cs_5_0", + D3DCOMPILE_ENABLE_STRICTNESS, + 0, + &csBlob, + &errBlob + ); + + if (FAILED(hr)) { + if (errBlob) { + OutputDebugStringA((char*)errBlob->GetBufferPointer()); + } + MessageBox(nullptr, L"Failed to compile compute shader", L"Error", MB_OK); + return; + } + + hr = device_->CreateComputeShader(csBlob->GetBufferPointer(), csBlob->GetBufferSize(), nullptr, &computeShader_); + if (FAILED(hr)) { + MessageBox(nullptr, L"Failed to create compute shader", L"Error", MB_OK); + return; + } + + // Vertex shader + ComPtr vsBlob; + hr = D3DCompile( + vertexShaderSource, + strlen(vertexShaderSource), + nullptr, + nullptr, + nullptr, + "main", + "vs_5_0", + D3DCOMPILE_ENABLE_STRICTNESS, + 0, + &vsBlob, + &errBlob + ); + if (SUCCEEDED(hr)) { + device_->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), nullptr, &vertexShader_); + } + else { + MessageBox(nullptr, L"Failed to compile vertex shader", L"Error", MB_OK); + } + + // Pixel shader + ComPtr psBlob; + hr = D3DCompile( + pixelShaderSource, + strlen(pixelShaderSource), + nullptr, + nullptr, + nullptr, + "main", + "ps_5_0", + D3DCOMPILE_ENABLE_STRICTNESS, + 0, + &psBlob, + &errBlob + ); + if (SUCCEEDED(hr)) { + device_->CreatePixelShader(psBlob->GetBufferPointer(), psBlob->GetBufferSize(), nullptr, &pixelShader_); + } + else { + MessageBox(nullptr, L"Failed to compile pixel shader", L"Error", MB_OK); + } + + // Constant buffer - FIXED: Ensure proper alignment + D3D11_BUFFER_DESC cbDesc = {}; + cbDesc.ByteWidth = (sizeof(ConstantBuffer) + 15) & ~15; // Align to 16 bytes + cbDesc.Usage = D3D11_USAGE_DYNAMIC; + cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + cbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + cbDesc.MiscFlags = 0; + cbDesc.StructureByteStride = 0; + + hr = device_->CreateBuffer(&cbDesc, nullptr, &constantBuffer_); + if (FAILED(hr)) { + MessageBox(nullptr, L"Failed to create constant buffer", L"Error", MB_OK); + } +} + +void MandelbrotApp::ResizeBuffers(int width, int height) { + if (width <= 0 || height <= 0 || !device_) return; + + outputTexture_.Reset(); + outputUAV_.Reset(); + outputSRV_.Reset(); + + // Create output texture for compute shader + D3D11_TEXTURE2D_DESC texDesc = {}; + texDesc.Width = width; + texDesc.Height = height; + texDesc.MipLevels = 1; + texDesc.ArraySize = 1; + texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + texDesc.SampleDesc.Count = 1; + texDesc.SampleDesc.Quality = 0; + texDesc.Usage = D3D11_USAGE_DEFAULT; + texDesc.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE; + texDesc.CPUAccessFlags = 0; + texDesc.MiscFlags = 0; + + HRESULT hr = device_->CreateTexture2D(&texDesc, nullptr, &outputTexture_); + if (FAILED(hr)) { + MessageBox(nullptr, L"Failed to create output texture", L"Error", MB_OK); + return; + } + + // Create UAV for compute shader + D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; + uavDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + uavDesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D; + uavDesc.Texture2D.MipSlice = 0; + + hr = device_->CreateUnorderedAccessView(outputTexture_.Get(), &uavDesc, &outputUAV_); + if (FAILED(hr)) { + MessageBox(nullptr, L"Failed to create UAV", L"Error", MB_OK); + return; + } + + // Create SRV for pixel shader + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; + srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MostDetailedMip = 0; + srvDesc.Texture2D.MipLevels = 1; + + hr = device_->CreateShaderResourceView(outputTexture_.Get(), &srvDesc, &outputSRV_); + if (FAILED(hr)) { + MessageBox(nullptr, L"Failed to create SRV", L"Error", MB_OK); + return; + } + + imageWidth_ = width; + imageHeight_ = height; +} + +void MandelbrotApp::UpdateTitle() { + double zoom = 4.0 / (xmax_ - xmin_); + std::wostringstream title; + title << L"Mandelbrot Viewer | Zoom: " << std::fixed << std::setprecision(2) << zoom + << L"x | Iter: " << maxIter_ + << L" | Color: " << colorScheme_ + 1 + << L" | Anim: " << (animationEnabled_ ? L"ON" : L"OFF") + << L" | [H for Help]"; + SetWindowText(hwnd_, title.str().c_str()); +} + +void MandelbrotApp::SaveBookmark() { + for (int i = 0; i < 10; i++) { + if (!bookmarks_[i].saved || i == 0) { + std::lock_guard lock(viewMutex_); + bookmarks_[i].xmin = xmin_; + bookmarks_[i].xmax = xmax_; + bookmarks_[i].ymin = ymin_; + bookmarks_[i].ymax = ymax_; + bookmarks_[i].maxIter = maxIter_; + bookmarks_[i].saved = true; + + std::wostringstream msg; + msg << L"Bookmark saved to slot " << i; + MessageBox(hwnd_, msg.str().c_str(), L"Bookmark", MB_OK | MB_ICONINFORMATION); + break; + } + } +} + +void MandelbrotApp::LoadBookmark(int slot) { + if (slot >= 0 && slot < 10 && bookmarks_[slot].saved) { + std::lock_guard lock(viewMutex_); + xmin_ = bookmarks_[slot].xmin; + xmax_ = bookmarks_[slot].xmax; + ymin_ = bookmarks_[slot].ymin; + ymax_ = bookmarks_[slot].ymax; + maxIter_ = bookmarks_[slot].maxIter; + UpdateTitle(); + } +} + +void MandelbrotApp::ResetView() { + std::lock_guard lock(viewMutex_); + xmin_ = -2.5; xmax_ = 1.5; + ymin_ = -1.5; ymax_ = 1.5; + maxIter_ = 256; + UpdateTitle(); +} + +void MandelbrotApp::ToggleAnimation() { + animationEnabled_ = !animationEnabled_; + if (!animationEnabled_) { + animTime_ = 0.0f; + } + UpdateTitle(); +} + +void MandelbrotApp::AdjustIterations(int delta) { + std::lock_guard lock(viewMutex_); + maxIter_ = std::max(64, std::min(8192, maxIter_ + delta)); + UpdateTitle(); +} + +void MandelbrotApp::CycleColorScheme() { + colorScheme_ = (colorScheme_ + 1) % 5; + UpdateTitle(); +} + +void MandelbrotApp::Zoom(double factor, int centerX, int centerY) { + std::lock_guard lock(viewMutex_); + + double centerReal = xmin_ + (xmax_ - xmin_) * centerX / imageWidth_; + double centerImag = ymin_ + (ymax_ - ymin_) * centerY / imageHeight_; + + double width = (xmax_ - xmin_) * factor; + double height = (ymax_ - ymin_) * factor; + + xmin_ = centerReal - width * 0.5; + xmax_ = centerReal + width * 0.5; + ymin_ = centerImag - height * 0.5; + ymax_ = centerImag + height * 0.5; +} + +void MandelbrotApp::Pan(int dx, int dy) { + std::lock_guard lock(viewMutex_); + + double deltaX = (xmax_ - xmin_) * dx / imageWidth_; + double deltaY = (ymax_ - ymin_) * dy / imageHeight_; + + xmin_ -= deltaX; + xmax_ -= deltaX; + ymin_ += deltaY; + ymax_ += deltaY; +} + +void MandelbrotApp::RenderMandelbrot() { + if (!device_ || !context_ || !swapChain_ || !computeShader_ || + !constantBuffer_ || !outputUAV_ || !renderTargetView_) { + return; + } + + ConstantBuffer cb; + { + std::lock_guard lock(viewMutex_); + + double zoom = 4.0 / (xmax_ - xmin_); + int adaptiveMaxIter = maxIter_; + if (zoom > 10) adaptiveMaxIter = std::max(maxIter_, 512); + if (zoom > 100) adaptiveMaxIter = std::max(maxIter_, 1024); + if (zoom > 1000) adaptiveMaxIter = std::max(maxIter_, 2048); + + cb.xmin = (float)xmin_; + cb.xmax = (float)xmax_; + cb.ymin = (float)ymin_; + cb.ymax = (float)ymax_; + cb.width = imageWidth_; + cb.height = imageHeight_; + cb.maxIter = (colorScheme_ << 16) | (adaptiveMaxIter & 0xFFFF); + cb.time = animTime_; + } + + // Update constant buffer + D3D11_MAPPED_SUBRESOURCE mapped; + HRESULT hr = context_->Map(constantBuffer_.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped); + if (SUCCEEDED(hr)) { + memcpy(mapped.pData, &cb, sizeof(ConstantBuffer)); + context_->Unmap(constantBuffer_.Get(), 0); + } + + // Run compute shader + context_->CSSetShader(computeShader_.Get(), nullptr, 0); + context_->CSSetConstantBuffers(0, 1, constantBuffer_.GetAddressOf()); + context_->CSSetUnorderedAccessViews(0, 1, outputUAV_.GetAddressOf(), nullptr); + + UINT dispatchX = (imageWidth_ + 7) / 8; + UINT dispatchY = (imageHeight_ + 7) / 8; + context_->Dispatch(dispatchX, dispatchY, 1); + + // Clear compute shader bindings + ID3D11UnorderedAccessView* nullUAV[] = { nullptr }; + context_->CSSetUnorderedAccessViews(0, 1, nullUAV, nullptr); + context_->CSSetShader(nullptr, nullptr, 0); + + // Clear render target + float clearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + context_->ClearRenderTargetView(renderTargetView_.Get(), clearColor); + + // Set viewport + D3D11_VIEWPORT vp = {}; + vp.Width = (float)imageWidth_; + vp.Height = (float)imageHeight_; + vp.MinDepth = 0.0f; + vp.MaxDepth = 1.0f; + context_->RSSetViewports(1, &vp); + + // Render to screen + context_->OMSetRenderTargets(1, renderTargetView_.GetAddressOf(), nullptr); + context_->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + context_->VSSetShader(vertexShader_.Get(), nullptr, 0); + context_->PSSetShader(pixelShader_.Get(), nullptr, 0); + context_->PSSetShaderResources(0, 1, outputSRV_.GetAddressOf()); + context_->PSSetSamplers(0, 1, samplerState_.GetAddressOf()); + context_->Draw(3, 0); + + swapChain_->Present(1, 0); + + if (animationEnabled_) { + animTime_ += 0.016f; + } +} + +LRESULT CALLBACK MandelbrotApp::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + MandelbrotApp* pApp = nullptr; + + if (msg == WM_NCCREATE) { + CREATESTRUCT* pCreate = reinterpret_cast(lParam); + pApp = reinterpret_cast(pCreate->lpCreateParams); + SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(pApp)); + } + else { + pApp = reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA)); + } + + if (pApp) { + return pApp->HandleMessage(hwnd, msg, wParam, lParam); + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +LRESULT MandelbrotApp::HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + switch (msg) { + case WM_SIZE: { + if (swapChain_ && wParam != SIZE_MINIMIZED) { + RECT rc; + GetClientRect(hwnd, &rc); + int width = rc.right - rc.left; + int height = rc.bottom - rc.top; + + if (width > 0 && height > 0) { + context_->OMSetRenderTargets(0, nullptr, nullptr); + renderTargetView_.Reset(); + + HRESULT hr = swapChain_->ResizeBuffers(0, width, height, DXGI_FORMAT_UNKNOWN, 0); + if (SUCCEEDED(hr)) { + ComPtr backBuffer; + hr = swapChain_->GetBuffer(0, IID_PPV_ARGS(&backBuffer)); + if (SUCCEEDED(hr)) { + device_->CreateRenderTargetView(backBuffer.Get(), nullptr, &renderTargetView_); + ResizeBuffers(width, height); + + if (context_ && renderTargetView_) { + context_->OMSetRenderTargets(1, renderTargetView_.GetAddressOf(), nullptr); + + // Reset viewport + D3D11_VIEWPORT vp; + vp.Width = (float)width; + vp.Height = (float)height; + vp.MinDepth = 0.0f; + vp.MaxDepth = 1.0f; + vp.TopLeftX = 0; + vp.TopLeftY = 0; + context_->RSSetViewports(1, &vp); + } + } + } + } + } + return 0; + } + + case WM_DESTROY: + PostQuitMessage(0); + return 0; + + case WM_MOUSEWHEEL: { + int delta = GET_WHEEL_DELTA_WPARAM(wParam); + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(hwnd, &pt); + + bool shiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) != 0; + double zoomFactor = shiftPressed ? + (delta > 0 ? 0.5 : 2.0) : + (delta > 0 ? 0.8 : 1.25); + + Zoom(zoomFactor, pt.x, pt.y); + UpdateTitle(); + return 0; + } + + case WM_RBUTTONDOWN: { + SetCapture(hwnd); + lastMouseX_ = GET_X_LPARAM(lParam); + lastMouseY_ = GET_Y_LPARAM(lParam); + rightDragging_ = true; + return 0; + } + + case WM_RBUTTONUP: { + if (rightDragging_) { + int currentX = GET_X_LPARAM(lParam); + int currentY = GET_Y_LPARAM(lParam); + + if (abs(currentX - lastMouseX_) < 5 && abs(currentY - lastMouseY_) < 5) { + ResetView(); + } + } + rightDragging_ = false; + ReleaseCapture(); + return 0; + } + + case WM_LBUTTONDOWN: { + SetCapture(hwnd); + lastMouseX_ = GET_X_LPARAM(lParam); + lastMouseY_ = GET_Y_LPARAM(lParam); + dragging_ = true; + SetCursor(LoadCursor(nullptr, IDC_SIZEALL)); + return 0; + } + + case WM_LBUTTONUP: { + dragging_ = false; + ReleaseCapture(); + SetCursor(LoadCursor(nullptr, IDC_ARROW)); + return 0; + } + + case WM_MBUTTONDOWN: { + int x = GET_X_LPARAM(lParam); + int y = GET_Y_LPARAM(lParam); + Zoom(1.0, x, y); + UpdateTitle(); + return 0; + } + + case WM_MOUSEMOVE: { + if (dragging_) { + int currentX = GET_X_LPARAM(lParam); + int currentY = GET_Y_LPARAM(lParam); + int dx = currentX - lastMouseX_; + int dy = currentY - lastMouseY_; + + if (dx != 0 || dy != 0) { + Pan(dx, dy); + lastMouseX_ = currentX; + lastMouseY_ = currentY; + } + } + else if (rightDragging_) { + int currentY = GET_Y_LPARAM(lParam); + int dy = currentY - lastMouseY_; + + if (abs(dy) > 2) { + AdjustIterations(-dy * 5); + lastMouseY_ = currentY; + } + } + return 0; + } + + case WM_KEYDOWN: { + keysPressed_[wParam] = true; + + switch (wParam) { + case 'H': { + const wchar_t* helpText = + L"MANDELBROT VIEWER CONTROLS\n\n" + L"Mouse:\n" + L" Left Drag - Pan view\n" + L" Wheel - Zoom in/out\n" + L" Shift+Wheel - Fast zoom\n" + L" Middle Click - Center on point\n" + L" Right Drag - Adjust iterations\n" + L" Right Click - Reset view\n\n" + L"Keyboard:\n" + L" Arrow Keys - Pan view\n" + L" Shift+Arrows - Fast pan\n" + L" +/= / - - Zoom in/out\n" + L" [ / ] - Adjust iterations\n" + L" C - Cycle color schemes (5 total)\n" + L" A - Toggle animation\n" + L" R - Reset view\n" + L" S - Save bookmark\n" + L" 0-9 - Load bookmark\n" + L" F11 - Toggle fullscreen\n" + L" ESC - Exit fullscreen\n" + L" H - This help"; + MessageBox(hwnd, helpText, L"Help", MB_OK | MB_ICONINFORMATION); + return 0; + } + + case 'R': + ResetView(); + return 0; + + case 'C': + CycleColorScheme(); + return 0; + + case 'A': + ToggleAnimation(); + return 0; + + case 'S': + SaveBookmark(); + return 0; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + LoadBookmark(wParam - '0'); + return 0; + + case VK_OEM_PLUS: + case VK_ADD: + Zoom(0.8, imageWidth_ / 2, imageHeight_ / 2); + UpdateTitle(); + return 0; + + case VK_OEM_MINUS: + case VK_SUBTRACT: + Zoom(1.25, imageWidth_ / 2, imageHeight_ / 2); + UpdateTitle(); + return 0; + + case 'D': // [ + AdjustIterations(-64); + return 0; + + case 'I': // ] + AdjustIterations(64); + return 0; + + case VK_F11: { + static bool fullscreen = false; + static RECT savedRect; + static DWORD savedStyle; + + if (!fullscreen) { + GetWindowRect(hwnd, &savedRect); + savedStyle = GetWindowLong(hwnd, GWL_STYLE); + + SetWindowLong(hwnd, GWL_STYLE, savedStyle & ~(WS_CAPTION | WS_THICKFRAME)); + + MONITORINFO mi = { sizeof(mi) }; + GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY), &mi); + SetWindowPos(hwnd, HWND_TOP, + mi.rcMonitor.left, mi.rcMonitor.top, + mi.rcMonitor.right - mi.rcMonitor.left, + mi.rcMonitor.bottom - mi.rcMonitor.top, + SWP_FRAMECHANGED); + fullscreen = true; + } + else { + SetWindowLong(hwnd, GWL_STYLE, savedStyle); + SetWindowPos(hwnd, nullptr, + savedRect.left, savedRect.top, + savedRect.right - savedRect.left, + savedRect.bottom - savedRect.top, + SWP_FRAMECHANGED); + fullscreen = false; + } + return 0; + } + + case VK_ESCAPE: { + DWORD currentStyle = GetWindowLong(hwnd, GWL_STYLE); + if (!(currentStyle & WS_CAPTION)) { + SetWindowLong(hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW); + SetWindowPos(hwnd, nullptr, 100, 100, 1280, 720, SWP_FRAMECHANGED); + } + return 0; + } + + case VK_LEFT: + case VK_RIGHT: + case VK_UP: + case VK_DOWN: { + bool shift = (GetKeyState(VK_SHIFT) & 0x8000) != 0; + double speed = shift ? 50 : 10; + + int dx = 0, dy = 0; + if (wParam == VK_LEFT) dx = (int)speed; + if (wParam == VK_RIGHT) dx = -(int)speed; + if (wParam == VK_UP) dy = (int)speed; + if (wParam == VK_DOWN) dy = -(int)speed; + + Pan(dx, dy); + return 0; + } + } + return 0; + } + + case WM_KEYUP: + keysPressed_[wParam] = false; + return 0; + + default: + return DefWindowProc(hwnd, msg, wParam, lParam); + } +} + +void MandelbrotApp::Run() { + MSG msg = {}; + LARGE_INTEGER lastTime; + QueryPerformanceCounter(&lastTime); + + double targetFrameTime = 1.0 / 120.0; // 60 FPS + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + + while (true) { + if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) + break; + TranslateMessage(&msg); + DispatchMessage(&msg); + } + else { + RenderMandelbrot(); + + // Limit frame rate + LARGE_INTEGER currentTime; + QueryPerformanceCounter(¤tTime); + double elapsed = double(currentTime.QuadPart - lastTime.QuadPart) / freq.QuadPart; + + if (elapsed < targetFrameTime) { + DWORD sleepMs = DWORD((targetFrameTime - elapsed) * 1000); + if (sleepMs > 0) Sleep(sleepMs); + } + QueryPerformanceCounter(&lastTime); + } + } +} diff --git a/simulations/mandelbrotset/cpp/mandelbrot_app.h b/simulations/mandelbrotset/cpp/mandelbrot_app.h new file mode 100644 index 0000000..4b9bee1 --- /dev/null +++ b/simulations/mandelbrotset/cpp/mandelbrot_app.h @@ -0,0 +1,91 @@ +#ifndef MANDELBROT_APP_H +#define MANDELBROT_APP_H + +#include +#include +#include +#include +#include +#include +#include + +using Microsoft::WRL::ComPtr; + +struct ConstantBuffer { + float xmin, xmax, ymin, ymax; + int width, height, maxIter; + float time; + float padding[3]; +}; + +struct Bookmark { + double xmin, xmax, ymin, ymax; + int maxIter; + bool saved = false; +}; + +class MandelbrotApp { +public: + MandelbrotApp(HINSTANCE hInstance); + ~MandelbrotApp(); + void Run(); + +private: + void CreateMainWindow(); + void InitD3D(); + void CreateComputeShader(); + void ResizeBuffers(int width, int height); + void RenderMandelbrot(); + void UpdateTitle(); + + void Zoom(double factor, int centerX, int centerY); + void Pan(int dx, int dy); + void ResetView(); + void AdjustIterations(int delta); + void CycleColorScheme(); + void ToggleAnimation(); + + void SaveBookmark(); + void LoadBookmark(int slot); + + static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + LRESULT HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + + HINSTANCE hInstance_; + HWND hwnd_; + + ComPtr device_; + ComPtr context_; + ComPtr swapChain_; + ComPtr renderTargetView_; + ComPtr computeShader_; + ComPtr vertexShader_; + ComPtr pixelShader_; + ComPtr constantBuffer_; + ComPtr outputTexture_; + ComPtr outputUAV_; + ComPtr outputSRV_; + ComPtr samplerState_; + + double xmin_ = -2.5, xmax_ = 1.5; + double ymin_ = -1.5, ymax_ = 1.5; + int maxIter_ = 256; + int colorScheme_ = 0; + bool animationEnabled_ = false; + float animTime_ = 0.0f; + + int imageWidth_ = 0; + int imageHeight_ = 0; + int lastMouseX_ = 0; + int lastMouseY_ = 0; + bool dragging_ = false; + bool rightDragging_ = false; + std::unordered_map keysPressed_; + std::mutex viewMutex_; + + LARGE_INTEGER perfFreq_; + LARGE_INTEGER lastFrameTime_; + Bookmark bookmarks_[10]; +}; + +#endif \ No newline at end of file diff --git a/simulations/mandelbrotset/generate.py b/simulations/mandelbrotset/generate.py new file mode 100644 index 0000000..75b7211 --- /dev/null +++ b/simulations/mandelbrotset/generate.py @@ -0,0 +1,19 @@ +from numba import njit +import numpy as np + +@njit +def generate_mandelbrot(xmin, xmax, ymin, ymax, width, height, max_iter): + img = np.zeros((height, width), dtype=np.uint32) + for i in range(height): + for j in range(width): + x0 = xmin + (xmax - xmin) * j / width + y0 = ymin + (ymax - ymin) * i / height + x, y = 0.0, 0.0 + iter = 0 + while x*x + y*y <= 4.0 and iter < max_iter: + xtemp = x*x - y*y + x0 + y = 2*x*y + y0 + x = xtemp + iter += 1 + img[i, j] = iter + return img diff --git a/simulations/mandelbrotset/mandelbrotset.py b/simulations/mandelbrotset/mandelbrotset.py new file mode 100644 index 0000000..4eb0f6c --- /dev/null +++ b/simulations/mandelbrotset/mandelbrotset.py @@ -0,0 +1,95 @@ +import tkinter as tk +from tkinter import ttk +from PIL import Image, ImageTk +import threading +import numpy as np +import generate # your mandelbrot_numba / generate_mandelbrot module + +class MandelbrotApp: + def __init__(self, root): + self.root = root + self.root.title("Mandelbrot Viewer") + + self.frame = ttk.Frame(root) + self.frame.pack(fill=tk.BOTH, expand=True) + + self.canvas = tk.Canvas(self.frame, bg="black", scrollregion=(0, 0, 2000, 2000)) + self.hbar = ttk.Scrollbar(self.frame, orient=tk.HORIZONTAL, command=self.canvas.xview) + self.vbar = ttk.Scrollbar(self.frame, orient=tk.VERTICAL, command=self.canvas.yview) + self.canvas.config(xscrollcommand=self.hbar.set, yscrollcommand=self.vbar.set) + + self.hbar.pack(side=tk.BOTTOM, fill=tk.X) + self.vbar.pack(side=tk.RIGHT, fill=tk.Y) + self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + + # Mandelbrot view window + self.xmin, self.xmax = -2.0, 1.0 + self.ymin, self.ymax = -1.5, 1.5 + self.zoom_factor = 1.0 + self.image_on_canvas = None + + self.render_image() + + # Mouse bindings + self.canvas.bind("", self.zoom) + self.canvas.bind("", self.drag_start) + self.canvas.bind("", self.drag_motion) + + def render_image(self): + width, height = 800, 600 + max_iter = 200 + + def render_task(): + # Generate the Mandelbrot array + img_array = generate.generate_mandelbrot( + self.xmin, self.xmax, self.ymin, self.ymax, + width, height, max_iter + ) + + # Convert NumPy array to RGB image + img_rgb = np.zeros((height, width, 3), dtype=np.uint8) + img_rgb[:, :, 0] = img_array % 256 + img_rgb[:, :, 1] = (img_array * 2) % 256 + img_rgb[:, :, 2] = (img_array * 3) % 256 + + # Convert NumPy array → PIL Image → ImageTk.PhotoImage + img = Image.fromarray(img_rgb) + self.tk_img = ImageTk.PhotoImage(img) + + # Update canvas + self.canvas.delete("all") + self.image_on_canvas = self.canvas.create_image(0, 0, anchor=tk.NW, image=self.tk_img) + self.canvas.config(scrollregion=(0, 0, width, height)) + + threading.Thread(target=render_task, daemon=True).start() + + def zoom(self, event): + zoom_amount = 0.9 if event.delta > 0 else 1.1 + self.zoom_factor *= zoom_amount + center_x = (self.xmin + self.xmax) / 2 + center_y = (self.ymin + self.ymax) / 2 + width = (self.xmax - self.xmin) * zoom_amount + height = (self.ymax - self.ymin) * zoom_amount + self.xmin, self.xmax = center_x - width / 2, center_x + width / 2 + self.ymin, self.ymax = center_y - height / 2, center_y + height / 2 + self.render_image() + + def drag_start(self, event): + self.last_x, self.last_y = event.x, event.y + + def drag_motion(self, event): + dx = event.x - self.last_x + dy = event.y - self.last_y + x_shift = (self.xmax - self.xmin) * dx / 800 + y_shift = (self.ymax - self.ymin) * dy / 600 + self.xmin -= x_shift + self.xmax -= x_shift + self.ymin += y_shift + self.ymax += y_shift + self.last_x, self.last_y = event.x, event.y + self.render_image() + +if __name__ == "__main__": + root = tk.Tk() + app = MandelbrotApp(root) + root.mainloop() diff --git a/simulations/mandelbrotset/mandelbrotsetCPP.cpp b/simulations/mandelbrotset/mandelbrotsetCPP.cpp new file mode 100644 index 0000000..9762de8 --- /dev/null +++ b/simulations/mandelbrotset/mandelbrotsetCPP.cpp @@ -0,0 +1,247 @@ +#ifndef UNICODE +#define UNICODE +#endif +#ifndef _UNICODE +#define _UNICODE +#endif + +#include +#include +#include +#include +#include +#include +#include + +// Window size +const int WIDTH = 800; +const int HEIGHT = 600; + +// Mandelbrot view +double xmin = -2.0, xmax = 1.0; +double ymin = -1.5, ymax = 1.5; +int max_iter = 500; + +// Pixel buffer +std::vector pixels(WIDTH* HEIGHT * 3); +HBITMAP hBitmap = nullptr; +std::mutex renderMutex; + +// Track ongoing rendering +std::atomic rendering(false); + +// Low-res rendering factor +int previewScale = 4; + +// Forward declarations +void generateMandelbrot(int width, int height, std::vector& buffer); + +// Create or update the bitmap +void updateBitmap(HDC hdc) +{ + std::lock_guard lock(renderMutex); + + BITMAPINFO bmi = {}; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = WIDTH; + bmi.bmiHeader.biHeight = -HEIGHT; // top-down + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 24; + bmi.bmiHeader.biCompression = BI_RGB; + + if (!hBitmap) + { + void* pBits; + hBitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &pBits, nullptr, 0); + } + + SetDIBits(hdc, hBitmap, 0, HEIGHT, pixels.data(), &bmi, DIB_RGB_COLORS); +} + +// Multi-threaded block render +void renderBlock(int yStart, int yEnd, int width, int height, std::vector& buffer) +{ + for (int py = yStart; py < yEnd; ++py) + { + double y0 = ymin + (ymax - ymin) * py / height; + for (int px = 0; px < width; ++px) + { + double x0 = xmin + (xmax - xmin) * px / width; + std::complex c(x0, y0), z(0, 0); + int iter = 0; + while (std::abs(z) <= 2.0 && iter < max_iter) z = z * z + c, iter++; + + int idx = (py * width + px) * 3; + buffer[idx + 0] = iter % 256; + buffer[idx + 1] = (iter * 2) % 256; + buffer[idx + 2] = (iter * 3) % 256; + } + } +} + +// Multi-threaded Mandelbrot +void generateMandelbrot(int width, int height, std::vector& buffer) +{ + int numThreads = std::thread::hardware_concurrency(); + std::vector threads; + int blockHeight = height / numThreads; + + for (int i = 0; i < numThreads; ++i) + { + int yStart = i * blockHeight; + int yEnd = (i == numThreads - 1) ? height : yStart + blockHeight; + threads.emplace_back(renderBlock, yStart, yEnd, width, height, std::ref(buffer)); + } + + for (auto& t : threads) t.join(); +} + +// Start rendering in a background thread +void startRender(HWND hwnd) +{ + if (rendering) return; // avoid multiple renders + rendering = true; + + std::thread([hwnd]() + { + // 1) Low-res preview + int lowW = WIDTH / previewScale, lowH = HEIGHT / previewScale; + std::vector preview(lowW * lowH * 3); + generateMandelbrot(lowW, lowH, preview); + + // Upscale preview to full size + { + std::lock_guard lock(renderMutex); + for (int y = 0;y < HEIGHT;y++) + for (int x = 0;x < WIDTH;x++) + { + int px = x / previewScale; + int py = y / previewScale; + int idx = (y * WIDTH + x) * 3; + int idxSmall = (py * lowW + px) * 3; + pixels[idx + 0] = preview[idxSmall + 0]; + pixels[idx + 1] = preview[idxSmall + 1]; + pixels[idx + 2] = preview[idxSmall + 2]; + } + } + InvalidateRect(hwnd, nullptr, TRUE); + + // 2) Full-res render + generateMandelbrot(WIDTH, HEIGHT, pixels); + InvalidateRect(hwnd, nullptr, TRUE); + + rendering = false; + }).detach(); +} + +// Dragging +int lastX = 0, lastY = 0; +bool dragging = false; + +// Window procedure +LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_DESTROY: + PostQuitMessage(0); + return 0; + + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hwnd, &ps); + updateBitmap(hdc); + + HDC memDC = CreateCompatibleDC(hdc); + HBITMAP oldBmp = (HBITMAP)SelectObject(memDC, hBitmap); + BitBlt(hdc, 0, 0, WIDTH, HEIGHT, memDC, 0, 0, SRCCOPY); + SelectObject(memDC, oldBmp); + DeleteDC(memDC); + + EndPaint(hwnd, &ps); + } + return 0; + + case WM_MOUSEWHEEL: + { + short delta = GET_WHEEL_DELTA_WPARAM(wParam); + double zoomFactor = (delta > 0) ? 0.8 : 1.25; + + double centerX = (xmin + xmax) / 2; + double centerY = (ymin + ymax) / 2; + double width = (xmax - xmin) * zoomFactor; + double height = (ymax - ymin) * zoomFactor; + + xmin = centerX - width / 2; + xmax = centerX + width / 2; + ymin = centerY - height / 2; + ymax = centerY + height / 2; + + startRender(hwnd); + } + return 0; + + case WM_LBUTTONDOWN: + dragging = true; + lastX = LOWORD(lParam); + lastY = HIWORD(lParam); + return 0; + + case WM_LBUTTONUP: + dragging = false; + return 0; + + case WM_MOUSEMOVE: + if (dragging) + { + int x = LOWORD(lParam); + int y = HIWORD(lParam); + double dx = (x - lastX) * (xmax - xmin) / WIDTH; + double dy = (y - lastY) * (ymax - ymin) / HEIGHT; + + xmin -= dx; xmax -= dx; + ymin += dy; ymax += dy; + + lastX = x; + lastY = y; + + startRender(hwnd); + } + return 0; + } + + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} + +// Entry point +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int nCmdShow) +{ + const wchar_t CLASS_NAME[] = L"MandelbrotWindow"; + + WNDCLASS wc = {}; + wc.lpfnWndProc = WindowProc; + wc.hInstance = hInstance; + wc.lpszClassName = CLASS_NAME; + RegisterClass(&wc); + + HWND hwnd = CreateWindowEx( + 0, CLASS_NAME, L"Mandelbrot Explorer", WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, WIDTH, HEIGHT, + nullptr, nullptr, hInstance, nullptr + ); + + ShowWindow(hwnd, nCmdShow); + + // Initial render + startRender(hwnd); + + MSG msg = {}; + while (GetMessage(&msg, nullptr, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return 0; +} diff --git a/simulations/pendulum/main.py b/simulations/pendulum/main.py new file mode 100644 index 0000000..f00aac9 --- /dev/null +++ b/simulations/pendulum/main.py @@ -0,0 +1,92 @@ +import pygame +import math + +# Initialize Pygame +pygame.init() + +# Screen setup +WIDTH, HEIGHT = 800, 600 +screen = pygame.display.set_mode((WIDTH, HEIGHT)) +pygame.display.set_caption("Interactive Adjustable Pendulum") + +# Colors +BLACK = (0, 0, 0) +WHITE = (255, 255, 255) +RED = (200, 50, 50) + +# Clock for FPS +clock = pygame.time.Clock() +FPS = 60 + +# Pendulum properties +origin = (WIDTH // 2, 100) +length = 300 +angle = math.pi / 4 +angular_velocity = 0 +angular_acceleration = 0 +gravity = 0.8 +dragging = False +drag_padding = 30 # extra radius for easier dragging + +def get_bob_position(angle): + x = origin[0] + length * math.sin(angle) + y = origin[1] + length * math.cos(angle) + return int(x), int(y) + +bob_pos = get_bob_position(angle) + +running = True +while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + # Start dragging + elif event.type == pygame.MOUSEBUTTONDOWN: + mx, my = event.pos + if math.hypot(mx - bob_pos[0], my - bob_pos[1]) < 20 + drag_padding: + dragging = True + # Stop dragging + elif event.type == pygame.MOUSEBUTTONUP: + dragging = False + + # Keyboard controls + keys = pygame.key.get_pressed() + if keys[pygame.K_UP]: + length += 1 # increase pendulum length + if keys[pygame.K_DOWN]: + length = max(50, length - 1) # decrease length, min 50 + if keys[pygame.K_RIGHT]: + gravity += 0.01 + if keys[pygame.K_LEFT]: + gravity = max(0.1, gravity - 0.01) + + if dragging: + mx, my = pygame.mouse.get_pos() + dx = mx - origin[0] + dy = my - origin[1] + angle = math.atan2(dx, dy) + angular_velocity = 0 + bob_pos = get_bob_position(angle) + else: + # Physics + angular_acceleration = (-gravity / length) * math.sin(angle) + angular_velocity += angular_acceleration + angle += angular_velocity + angular_velocity *= 0.99 # damping + bob_pos = get_bob_position(angle) + + # Drawing + screen.fill(BLACK) + pygame.draw.line(screen, WHITE, origin, bob_pos, 3) + pygame.draw.circle(screen, RED, bob_pos, 20) + pygame.draw.circle(screen, WHITE, origin, 5) + + # Display info + font = pygame.font.SysFont(None, 24) + info = font.render(f"Length: {int(length)} Gravity: {gravity:.2f}", True, WHITE) + screen.blit(info, (10, 10)) + + pygame.display.flip() + clock.tick(FPS) + +pygame.quit() diff --git a/simulations/sorting_quicksort/setup.py b/simulations/sorting_quicksort/setup.py new file mode 100644 index 0000000..5ac4ec3 --- /dev/null +++ b/simulations/sorting_quicksort/setup.py @@ -0,0 +1,10 @@ +from setuptools import setup, Extension + +module = Extension("sorting", sources=["sorting.c"]) + +setup( + name="sorting", + version="1.0", + description="Simple C sorting extension", + ext_modules=[module], +) diff --git a/simulations/sorting_quicksort/sorting.c b/simulations/sorting_quicksort/sorting.c new file mode 100644 index 0000000..439f9d1 --- /dev/null +++ b/simulations/sorting_quicksort/sorting.c @@ -0,0 +1,48 @@ +#include + +// The actual sorting implementation +static PyObject* sorting_sort(PyObject* self, PyObject* args) { + PyObject* list; + if (!PyArg_ParseTuple(args, "O!", &PyList_Type, &list)) + return NULL; + + Py_ssize_t n = PyList_Size(list); + for (Py_ssize_t i = 0; i < n - 1; i++) { + for (Py_ssize_t j = 0; j < n - i - 1; j++) { + PyObject* a = PyList_GetItem(list, j); + PyObject* b = PyList_GetItem(list, j + 1); + + int cmp = PyObject_RichCompareBool(a, b, Py_GT); + if (cmp == -1) return NULL; + if (cmp) { + Py_INCREF(a); + Py_INCREF(b); + PyList_SetItem(list, j, b); + PyList_SetItem(list, j + 1, a); + Py_DECREF(a); + Py_DECREF(b); + } + } + } + Py_RETURN_NONE; +} + +// Table of functions to export +static PyMethodDef SortingMethods[] = { + {"sort", sorting_sort, METH_VARARGS, "Sort a Python list in place."}, + {NULL, NULL, 0, NULL} +}; + +// Module definition +static struct PyModuleDef sortingmodule = { + PyModuleDef_HEAD_INIT, + "sorting", + NULL, + -1, + SortingMethods +}; + +// Module initialization +PyMODINIT_FUNC PyInit_sorting(void) { + return PyModule_Create(&sortingmodule); +} diff --git a/simulations/sorting_quicksort/sorting.py b/simulations/sorting_quicksort/sorting.py new file mode 100644 index 0000000..b090409 --- /dev/null +++ b/simulations/sorting_quicksort/sorting.py @@ -0,0 +1,4 @@ +import sorting +arr = [5, 3, 8, 1, 2] +sorting.sorting(arr) +print(arr) diff --git a/stmkbezirke/main.py b/stmkbezirke/main.py new file mode 100644 index 0000000..9aa862f --- /dev/null +++ b/stmkbezirke/main.py @@ -0,0 +1,13 @@ +import random + +bezirke = [ + "Graz", "Graz-Umgebung", "Leibnitz", "Deutschlandsberg", + "Voitsberg", "Weiz", "Hartberg-Fürstenfeld", "Südoststeiermark", + "Murau", "Murtal", "Liezen", "Bruck-Mürzzuschlag", "Leoben" +] + +gewinner = random.sample(bezirke, 5) + +print("Die 5 geförderten Bezirke sind:") +for bezirk in gewinner: + print("-", bezirk)