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