""" 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()