from tkinter import *
import math
unitScale = 100
class Cube():
    def __init__(self, canvas):
        self.canvas = canvas
        self.canvasWidth=800
        self.canvasHeight = 500
        self.cubePos = {'x':0, 'y':0, 'z':0}
        self.cubeSize = 0.5
        self.cubeRadius = None
        self.zrot = 0
        self.sin = None
        self.cos = None
        self.vertices = {'a':[0, 0], #cube coordinates in 3D space: [x, y]
                         'b':[0, 0],
                         'c':[0, 0],
                         'd':[0, 0],
                         'z':[0, 0]} #[z0, z1] for all vertices
        self.uPos = -3
        self.vanishingPoints = [0, 0] #[x1, x2]
        self.pVertices = {'a0':[0, 0], #picturePlance vertices: [x, y]
                          'b0':[0, 0],
                          'c0':[0, 0],
                          'd0':[0, 0],
                          'a1':[0, 0],
                          'b1':[0, 0],
                          'c1':[0, 0],
                          'd1':[0, 0]}
        #keybinds
        self.canvas.bind("<ButtonPress-1>", self.KeyButtonPress)
        self.canvas.bind("<ButtonRelease-1>", self.KeyButtonRelease)
        self.canvas.bind("<B1-Motion>", self.KeyOnMotion)
        self.canvas.bind("<Shift-B1-Motion>", self.KeyShiftOnMotion)
        self.canvas.bind("<Control-B1-Motion>", self.KeyControlOnMotion)
        self.canvas.bind("<Alt-B1-Motion>", self.KeyAltOnMotion)
        self.canvas.bind('<Configure>', self.resize)
        self._drag_data = [0, 0] #[x, y]
        
        self.refresh()
    def KeyButtonPress(self, event):
        '''Begin drag of the cube'''
        self._drag_data = [event.x, event.y]
    def KeyButtonRelease(self, event):
        self._drag_data = [0, 0]
    def KeyOnMotion(self, event):
        global unitScale
        delta_x = event.x - self._drag_data[0]
        delta_y = event.y - self._drag_data[1]
        self.cubePos['x'] += delta_x/unitScale
        self.cubePos['z'] += delta_y/unitScale
        self.refresh()
        self._drag_data = [event.x, event.y]
    def KeyShiftOnMotion(self, event):
        delta_x = event.x - self._drag_data[0]
        self.zrot += delta_x * 0.2
        self.refresh()
        self._drag_data = [event.x, event.y]
    def KeyControlOnMotion(self, event):
        delta_y = event.y - self._drag_data[1]
        self.uPos += delta_y * -0.01*self.uPos
        self.refresh()
        self._drag_data = [event.x, event.y]
    def KeyAltOnMotion(self, event):
        delta_x = event.x - self._drag_data[0]
        delta_y = event.y - self._drag_data[1]
        delta = delta_x + delta_y
        
        self.cubeSize += delta * 0.01*self.cubeSize
        self.refresh()
        self._drag_data = [event.x, event.y]
    def draw(self):
        def drawCube(self):
            keys = ['a', 'b', 'c', 'd']
            for i in range(0,4):
                coords = self.pVertices[keys[i]+'0']
                coords += self.pVertices[keys[i-1]+'0']
                coords += self.pVertices[keys[i-1]+'1']
                coords += self.pVertices[keys[i]+'1']
                self.canvas.create_polygon(coords,tags = 'cube', width=1,
                                           fill='', outline='#000000')
        def drawVanishingPoints(self):
            global unitScale
            vp1 = [self.vanishingPoints[0]*unitScale, 0]
            vp2 = [self.vanishingPoints[1]*unitScale, 0]
            b0 = self.pVertices['b0']
            b1 = self.pVertices['b1']
            c0 = self.pVertices['c0']
            c1 = self.pVertices['c1']
            d0 = self.pVertices['d0']
            d1 = self.pVertices['d1']
            vpColor = '#aaaaaa'
            line1 = vp1+d0+d1
            line2 = vp2+b0+b1
            line3 = vp1+c0+vp2+c1
            self.canvas.create_polygon(line1, tags='vp', dash=(2, 1),
                                       fill='', outline=vpColor)
            self.canvas.create_polygon(line2, tags='vp', dash=(2, 1),
                                       fill='', outline=vpColor)
            self.canvas.create_polygon(line3, tags='vp', dash=(2, 1),
                                       fill='', outline=vpColor)
        self.canvas.delete('cube')
        self.canvas.delete('vp')
        drawVanishingPoints(self)
        drawCube(self)
        self.canvas.delete('lpup')
        self.canvas.delete('rpup')
        ex = self.cubePos['x']*3
        eu = (-10-self.uPos)
                
        w.create_arc(-150+ex-eu,-55,-100+ex-eu,-105,extent=180,tags='lpup',fill='#000000')
        w.create_arc( 100+ex+eu,-55, 150+ex+eu,-105,extent=180,tags='rpup',fill='#000000')
    def picturePlaneVerticesCoords(self):
        global unitScale
        uPos = self.uPos
        
        for key1 in ['a', 'b', 'c', 'd']:
            x = self.vertices[key1][0]
            y = self.vertices[key1][1]
            for n in ['0', '1']:
                z = self.vertices['z'][int(n)]
                key2 = key1+n
                ppx = (x-x/(y-uPos)*y)*unitScale
                ppy = (z-z/(y-uPos)*y)*unitScale
                self.pVertices[key2] = [ppx, ppy]
    def printCoords(self):
        print('Cube coordinates:')
        for key, value in sorted(self.vertices.items()):
            print(key, value)
        print('Screen coordinates:')
        for key, value in sorted(self.pVertices.items()):
            print(key, value)
    def refresh(self):
        self.cubePosChange()
        self.cubeSizeChange()
        self.uPosChange()
        self.zrotChange()
        self.verticesCoords()
        self.vanishingPointCoords()
        self.picturePlaneVerticesCoords()
        
        self.draw()
    def resize(self, event):
        w,h = event.width, event.height
        self.canvasWidth = w
        self.canvasHeight = h
        self.canvas.config(scrollregion=(-w/2,-h/2,w/2,h/2))
    def vanishingPointCoords(self):
        '''Sets coordinates for vanishing points'''
        vp1Rot = self.zrot-45
        vp1Tan = math.tan(math.radians(vp1Rot))
        vp2Rot = self.zrot+45
        vp2Tan = math.tan(math.radians(vp2Rot))
        vp1 = 0-self.uPos*vp1Tan
        vp2 = 0-self.uPos*vp2Tan
        self.vanishingPoints = [vp1, vp2]
        
    def verticesCoords(self):
        '''Sets coordinates for cube vertices'''
        x = self.cubePos['x']
        y = self.cubePos['y']
        z = self.cubePos['z']
        s = self.cubeSize
        r = self.cubeRadius
        sin = self.sin
        cos = self.cos
        
        self.vertices['a'][0] = x-r*sin
        self.vertices['b'][0] = x+r*cos
        self.vertices['c'][0] = x+r*sin
        self.vertices['d'][0] = x-r*cos
        a = y-r*cos
        b = y-r*sin
        c = y+r*cos
        d = y+r*sin
        yOffset = min([a, b, c, d])
        
        self.vertices['a'][1] = a-yOffset
        self.vertices['b'][1] = b-yOffset
        self.vertices['c'][1] = c-yOffset
        self.vertices['d'][1] = d-yOffset
        self.vertices['z'] = [z+s, z-s]
    def cubePosChange(self):
        global unitScale
        xlim = self.canvasWidth/(2*unitScale)
        zlim = self.canvasHeight/(2*unitScale)
        if self.cubePos['x'] < -xlim:
            self.cubePos['x'] = -xlim
        elif self.cubePos['x'] > xlim:
            self.cubePos['x'] = xlim
        if self.cubePos['z'] < -zlim:
            self.cubePos['z'] = -zlim
        elif self.cubePos['z'] > zlim:
            self.cubePos['z'] = zlim
        
    def cubeSizeChange(self):
        self.cubeRadius = (2*self.cubeSize**2)**0.5
    def uPosChange(self):
        uPosLim = (-0.05, -10)
        if self.uPos < uPosLim[1]:
            self.uPos = uPosLim[1]
        elif self.uPos > uPosLim[0]:
            self.uPos = uPosLim[0]
        
    def zrotChange(self):
        rotlim = 45 #rotation limit
        if self.zrot > rotlim:
            self.zrot -= rotlim*2
        elif self.zrot < -rotlim:
            self.zrot += rotlim*2
        self.sin = math.sin(math.radians(self.zrot))
        self.cos = math.cos(math.radians(self.zrot))
        
master = Tk()
w = Canvas(master, width=800, height = 500, bg='#ffffff')
w.pack(expand=TRUE, fill=BOTH)
w.config(scrollregion=(-400,-250,400,250))
w.create_line(-1000, 0, 1000, 0, fill='#40cff0') #horizon
w.create_arc(-220,-200,220,200,extent=180,tags='arc',style=ARC)
w.create_arc(-175,-30,-75,-130,extent=180,tags='leye')
w.create_arc(75,-30,175,-130,extent=180,tags='leye')
C = Cube(w)
mainloop()