Search code examples
pythontkinteropenglpyopengl

How to draw animation in pyopengltk framework


I am using pyopengl, tkinter, pyopengltk to draw a Rubik's cube and am going to implement a Rubik's cube recovery animation, now I have implemented to display a Rubik's cube in tkinter with this quiz. How to rotate slices of a Rubik's Cube in python PyOpenGL? But I can't implement the tesseract animation step by step now, how can I do it please? Now it can only keep repeating the same action

import tkinter as tk
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from pyopengltk import OpenGLFrame

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)
)
edges = ((0, 1), (0, 3), (0, 4), (2, 1), (2, 3), (2, 7), (6, 3), (6, 4), (6, 7), (5, 1), (5, 4), (5, 7))
surfaces = ((0, 1, 2, 3), (3, 2, 7, 6), (6, 7, 5, 4), (4, 5, 1, 0), (1, 5, 7, 2), (4, 0, 3, 6))
colors = ((1, 0, 0), (0, 1, 0), (1, 0.5, 0), (1, 1, 0), (1, 1, 1), (0, 0, 1))

rot_cube_map = {'K_UP': (-1, 0), 'K_DOWN': (1, 0), 'K_LEFT': (0, -1), 'K_RIGHT': (0, 1)}
rot_slice_map = {
    'K_1': (0, 0, 1), 'K_2': (0, 1, 1), 'K_3': (0, 2, 1), 'K_4': (1, 0, 1), 'K_5': (1, 1, 1),
    'K_6': (1, 2, 1), 'K_7': (2, 0, 1), 'K_8': (2, 1, 1), 'K_9': (2, 2, 1),
    'K_F1': (0, 0, -1), 'K_F2': (0, 1, -1), 'K_F3': (0, 2, -1), 'K_F4': (1, 0, -1), 'K_F5': (1, 1, -1),
    'K_F6': (1, 2, -1), 'K_F7': (2, 0, -1), 'K_F8': (2, 1, -1), 'K_F9': (2, 2, -1),
}


class Cube():
    def __init__(self, id, N, scale):
        self.N = 3
        self.scale = scale
        self.init_i = [*id]
        self.current_i = [*id]  # 表示填充,一个变量值代替多个
        self.rot = [[1 if i == j else 0 for i in range(3)] for j in range(3)]

    def isAffected(self, axis, slice, dir):
        return self.current_i[axis] == slice

    def update(self, axis, slice, dir):

        if not self.isAffected(axis, slice, dir):
            return

        i, j = (axis + 1) % 3, (axis + 2) % 3
        for k in range(3):
            self.rot[k][i], self.rot[k][j] = -self.rot[k][j] * dir, self.rot[k][i] * dir

        self.current_i[i], self.current_i[j] = (
            self.current_i[j] if dir < 0 else self.N - 1 - self.current_i[j],
            self.current_i[i] if dir > 0 else self.N - 1 - self.current_i[i])

    def transformMat(self):
        scaleA = [[s * self.scale for s in a] for a in self.rot]
        scaleT = [(p - (self.N - 1) / 2) * 2.1 * self.scale for p in self.current_i]
        return [*scaleA[0], 0, *scaleA[1], 0, *scaleA[2], 0, *scaleT, 1]

    def draw(self, col, surf, vert, animate, angle, axis, slice, dir):

        glPushMatrix()
        if animate and self.isAffected(axis, slice, dir):
            glRotatef(angle * dir, *[1 if i == axis else 0 for i in range(3)])  # 围着这个坐标点旋转
        glMultMatrixf(self.transformMat())

        glBegin(GL_QUADS)
        for i in range(len(surf)):
            glColor3fv(colors[i])
            for j in surf[i]:
                glVertex3fv(vertices[j])
        glEnd()

        glPopMatrix()


class mycube():
    def __init__(self, N, scale):
        self.N = N
        cr = range(self.N)
        self.cubes = [Cube((x, y, z), self.N, scale) for x in cr for y in cr for z in cr]  # 创建27

    def maindd(self):
        for cube in self.cubes:
            cube.draw(colors, surfaces, vertices, False, 0, 0, 0, 0)


class GLFrame(OpenGLFrame):
    def initgl(self):
        self.rota = 0
        self.count = 0

        self.ang_x, self.ang_y, self.rot_cube = 0, 0, (0, 0)
        self.animate1, self.animate_ang, self.animate_speed = False, 0, 0.5
        self.action = (0, 0, 0)
        glClearColor(0.0, 0.0, 0.0, 0.0)  # 背景黑色
        # glViewport(400, 400, 200, 200)  # 指定了视口的左下角位置

        glEnable(GL_DEPTH_TEST)  # 开启深度测试,实现遮挡关系
        glDepthFunc(GL_LEQUAL)  # 设置深度测试函数(GL_LEQUAL只是选项之一)

        glMatrixMode(GL_PROJECTION)  
        glLoadIdentity()  # 恢复原始坐标
        gluPerspective(30, self.width / self.height, 0.1, 50.0)

    def redraw(self):
        self.N = 3
        cr = range(self.N)
        self.cubes = [Cube((x, y, z), self.N, 1.5) for x in cr for y in cr for z in cr]

        self.animate, self.action = True, rot_slice_map['K_1']

        self.ang_x += self.rot_cube[0] * 2
        self.ang_y += self.rot_cube[1] * 2

        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glTranslatef(0, 0, -40)
        glRotatef(self.ang_y, 0, 1, 0)
        glRotatef(self.ang_x, 1, 0, 0)

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        if self.animate1:
            if self.animate_ang >= 90:
                for cube in self.cubes:
                    cube.update(*self.action)
                self.animate1, self.animate_ang = False, 0

        for cube in self.cubes:
            cube.draw(colors, surfaces, vertices, self.animate, self.animate_ang, *self.action)
        if self.animate:
            self.animate_ang += self.animate_speed

   


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title('Pineapple')
        self.glframe = GLFrame(self, width=800, height=600)
        self.glframe.pack(expand=True, fill=tk.BOTH)
        # self.glframe.focus_displayof()
        # self.glframe.animate = True


App().mainloop()

I do this by calling this statement twice

self.animate, self.action = True, rot_slice_map['K_1']

Expect it to be executed step by step, but it only executes the last sentence,There is very little information about pyopengltk on the internet, and I am still a newbie, so I would like to get help


Solution

  • You must implement the keyebord events similar as in the Pygame implementation decribed in the answer to How to rotate slices of a Rubik's Cube in python PyOpenGL?.

    Remove:

    self.animate, self.action = True, rot_slice_map['K_1']

    Change the key mapping to a mapping to be used with tkinter

    rot_cube_map = {'Up': (-1, 0), 'Down': (1, 0), 'Left': (0, -1), 'Right': (0, 1)}
    rot_slice_map = {
        '1': (0, 0, 1), '2': (0, 1, 1), '3': (0, 2, 1), '4': (1, 0, 1), '5': (1, 1, 1),
        '6': (1, 2, 1), '7': (2, 0, 1), '8': (2, 1, 1), '9': (2, 2, 1),
        'F1': (0, 0, -1), 'F2': (0, 1, -1), 'F3': (0, 2, -1), 'F4': (1, 0, -1), 'F5': (1, 1, -1),
        'F6': (1, 2, -1), 'F7': (2, 0, -1), 'F8': (2, 1, -1), 'F9': (2, 2, -1),
    }
    

    Create the 27 cubes in GLFrame.initgl and set self.animate = True. self.animate is the fagae that controls the animation loop of the the OpenGLFrame. Tha naimation of the Rubik's Cube is controled with animate1Cube:

    class GLFrame(OpenGLFrame):
        def initgl(self):
            self.animate = True
            
           # [...]
    
            self.N = 3
            cr = range(self.N)
            self.cubes = [Cube((x, y, z), self.N, 1.5) for x in cr for y in cr for z in cr]
    

    Add the callback methods for the keyboard event:

    class GLFrame(OpenGLFrame):
        # [...]
    
        def keydown(self, event):
            if event.keysym in rot_slice_map:
                self.animate1Cube, self.action = True, rot_slice_map[event.keysym]
            if event.keysym in rot_cube_map:
                self.rot_cube = rot_cube_map[event.keysym]
    
        def keyup(self, event):
            if event.keysym in rot_cube_map:
                self.rot_cube = (0, 0)
    

    Set the keyboard callbacks:

    class App(tk.Tk):
        def __init__(self):
            super().__init__()
            self.title('rubiks cube')
            self.glframe = GLFrame(self, width=800, height=600)
            self.bind("<KeyPress>", self.glframe.keydown)
            self.bind("<KeyRelease>", self.glframe.keyup)
            self.glframe.pack(expand=True, fill=tk.BOTH)
    

    The animation of the Rubikc's Cube depends on animateCube, but not on animate:

    class GLFrame(OpenGLFrame):
        # [...]
    
        def redraw(self):
            # [...]
    
            if self.animate1Cube:
                if self.animate_ang >= 90:
                    for cube in self.cubes:
                        cube.update(*self.action)
                    self.animate1Cube, self.animate_ang = False, 0
    
            for cube in self.cubes:
                cube.draw(colors, surfaces, vertices, self.animate, self.animate_ang, *self.action)
            if self.animate1Cube:
                self.animate_ang += self.animate_speed
    

    Complete and working example:

    import tkinter as tk
    from OpenGL.GL import *
    from OpenGL.GLU import *
    from OpenGL.GLUT import *
    from pyopengltk import OpenGLFrame
    
    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)
    )
    edges = ((0, 1), (0, 3), (0, 4), (2, 1), (2, 3), (2, 7), (6, 3), (6, 4), (6, 7), (5, 1), (5, 4), (5, 7))
    surfaces = ((0, 1, 2, 3), (3, 2, 7, 6), (6, 7, 5, 4), (4, 5, 1, 0), (1, 5, 7, 2), (4, 0, 3, 6))
    colors = ((1, 0, 0), (0, 1, 0), (1, 0.5, 0), (1, 1, 0), (1, 1, 1), (0, 0, 1))
    
    rot_cube_map = {'Up': (-1, 0), 'Down': (1, 0), 'Left': (0, -1), 'Right': (0, 1)}
    rot_slice_map = {
        '1': (0, 0, 1), '2': (0, 1, 1), '3': (0, 2, 1), '4': (1, 0, 1), '5': (1, 1, 1),
        '6': (1, 2, 1), '7': (2, 0, 1), '8': (2, 1, 1), '9': (2, 2, 1),
        'F1': (0, 0, -1), 'F2': (0, 1, -1), 'F3': (0, 2, -1), 'F4': (1, 0, -1), 'F5': (1, 1, -1),
        'F6': (1, 2, -1), 'F7': (2, 0, -1), 'F8': (2, 1, -1), 'F9': (2, 2, -1),
    }
    
    class Cube():
        def __init__(self, id, N, scale):
            self.N = 3
            self.scale = scale
            self.init_i = [*id]
            self.current_i = [*id]  # 表示填充,一个变量值代替多个
            self.rot = [[1 if i == j else 0 for i in range(3)] for j in range(3)]
    
        def isAffected(self, axis, slice, dir):
            return self.current_i[axis] == slice
    
        def update(self, axis, slice, dir):
    
            if not self.isAffected(axis, slice, dir):
                return
    
            i, j = (axis + 1) % 3, (axis + 2) % 3
            for k in range(3):
                self.rot[k][i], self.rot[k][j] = -self.rot[k][j] * dir, self.rot[k][i] * dir
    
            self.current_i[i], self.current_i[j] = (
                self.current_i[j] if dir < 0 else self.N - 1 - self.current_i[j],
                self.current_i[i] if dir > 0 else self.N - 1 - self.current_i[i])
    
        def transformMat(self):
            scaleA = [[s * self.scale for s in a] for a in self.rot]
            scaleT = [(p - (self.N - 1) / 2) * 2.1 * self.scale for p in self.current_i]
            return [*scaleA[0], 0, *scaleA[1], 0, *scaleA[2], 0, *scaleT, 1]
    
        def draw(self, col, surf, vert, animate, angle, axis, slice, dir):
    
            glPushMatrix()
            if animate and self.isAffected(axis, slice, dir):
                glRotatef(angle * dir, *[1 if i == axis else 0 for i in range(3)])  # 围着这个坐标点旋转
            glMultMatrixf(self.transformMat())
    
            glBegin(GL_QUADS)
            for i in range(len(surf)):
                glColor3fv(colors[i])
                for j in surf[i]:
                    glVertex3fv(vertices[j])
            glEnd()
    
            glPopMatrix()
    
    
    class mycube():
        def __init__(self, N, scale):
            self.N = N
            cr = range(self.N)
            self.cubes = [Cube((x, y, z), self.N, scale) for x in cr for y in cr for z in cr]  # 创建27
    
        def maindd(self):
            for cube in self.cubes:
                cube.draw(colors, surfaces, vertices, False, 0, 0, 0, 0)
    
    class GLFrame(OpenGLFrame):
        def initgl(self):
            self.animate = True
            self.rota = 0
            self.count = 0
    
            self.ang_x, self.ang_y, self.rot_cube = 0, 0, (0, 0)
            self.animate1Cube, self.animate_ang, self.animate_speed = False, 0, 2
            self.action = (0, 0, 0)
            glClearColor(0.0, 0.0, 0.0, 0.0)  # 背景黑色
            # glViewport(400, 400, 200, 200)  # 指定了视口的左下角位置
    
            glEnable(GL_DEPTH_TEST)  # 开启深度测试,实现遮挡关系
            glDepthFunc(GL_LEQUAL)  # 设置深度测试函数(GL_LEQUAL只是选项之一)
    
            glMatrixMode(GL_PROJECTION)  
            glLoadIdentity()  # 恢复原始坐标
            gluPerspective(30, self.width / self.height, 0.1, 50.0)
    
            self.N = 3
            cr = range(self.N)
            self.cubes = [Cube((x, y, z), self.N, 1.5) for x in cr for y in cr for z in cr]
    
        def keydown(self, event):
            if event.keysym in rot_slice_map:
                self.animate1Cube, self.action = True, rot_slice_map[event.keysym]
            if event.keysym in rot_cube_map:
                self.rot_cube = rot_cube_map[event.keysym]
    
        def keyup(self, event):
            if event.keysym in rot_cube_map:
                self.rot_cube = (0, 0)
    
        def redraw(self):
            self.ang_x += self.rot_cube[0] * 2
            self.ang_y += self.rot_cube[1] * 2
    
            glMatrixMode(GL_MODELVIEW)
            glLoadIdentity()
            glTranslatef(0, 0, -40)
            glRotatef(self.ang_y, 0, 1, 0)
            glRotatef(self.ang_x, 1, 0, 0)
    
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    
            if self.animate1Cube:
                if self.animate_ang >= 90:
                    for cube in self.cubes:
                        cube.update(*self.action)
                    self.animate1Cube, self.animate_ang = False, 0
    
            for cube in self.cubes:
                cube.draw(colors, surfaces, vertices, self.animate, self.animate_ang, *self.action)
            if self.animate1Cube:
                self.animate_ang += self.animate_speed
    
    
    class App(tk.Tk):
        def __init__(self):
            super().__init__()
            self.title('rubiks cube')
            self.glframe = GLFrame(self, width=800, height=600)
            #self.bind("<Key>", self.glframe.key)
            self.bind("<KeyPress>", self.glframe.keydown)
            self.bind("<KeyRelease>", self.glframe.keyup)
            self.glframe.pack(expand=True, fill=tk.BOTH)
            # self.glframe.focus_displayof()
            # self.animate = True
    
    App().mainloop()
    

    If you want to animate the cube automatically, you need to animate instead of keyboard events. Define a list of animationg. e.g.:

    animation_list = ['1', '3', '5', 'F2']
    

    Set self.action from the list. e.g.:

    class GLFrame(OpenGLFrame):
        # [...]
    
        def redraw(self):
            if not self.animate1Cube and animation_list:
                self.animate1Cube, self.action = True, rot_slice_map[animation_list[0]]
                del animation_list[0]
    
            # [...]