174 lines
4.8 KiB
Python
174 lines
4.8 KiB
Python
"""
|
|
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("<space>", self.toggle_pause)
|
|
master.bind("<Up>", self.speed_up_x)
|
|
master.bind("<Down>", self.speed_down_x)
|
|
master.bind("<Right>", self.speed_up_z)
|
|
master.bind("<Left>", 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()
|