PyOpenGLtk框架下魔方分步复原动画实现问题求助
魔方分步复原动画问题解决方案
问题背景
我使用PyOpenGL、Tkinter和PyOpenGLtk绘制魔方,已实现魔方展示及切片旋转功能,但无法完成分步复原动画。当重复执行动画指令(如两次调用self.animate, self.action = True, rot_slice_map['K_1'])时,仅最后一条指令生效,动画始终重复同一动作。相关代码如下:
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] 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) glEnable(GL_DEPTH_TEST) glDepthFunc(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) App().mainloop()
问题分析与解决方案
核心问题点
- 魔方状态被频繁重置:
redraw方法每次都会重新创建self.cubes,导致之前的旋转操作全部丢失,相当于每次都从零开始画魔方。 - 动画指令被直接覆盖:重复赋值
self.action会把前一个动作覆盖,程序只会执行最后一次设置的动作。 - 动画状态逻辑混乱:
animate和animate1两个变量职责重叠,没有清晰的动画完成判定与后续动作衔接逻辑。
具体修改方案
1. 把魔方初始化移到initgl
将创建魔方的代码从redraw移到初始化方法,确保魔方状态可以持续保存:
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.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.action_queue = [] self.current_action = None # OpenGL初始化配置 glClearColor(0.0, 0.0, 0.0, 0.0) glEnable(GL_DEPTH_TEST) glDepthFunc(GL_LEQUAL) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(30, self.width / self.height, 0.1, 50.0)
2. 添加动作队列管理函数
用队列来存储需要执行的动画,完成一个动作后自动启动下一个:
def add_action(self, action): self.action_queue.append(action) # 如果当前没有正在执行的动画,直接启动第一个动作 if not self.animate1 and not self.current_action: self.start_next_action() def start_next_action(self): # 从队列取出下一个动作 if self.action_queue: self.current_action = self.action_queue.pop(0) self.animate1 = True self.animate_ang = 0 else: # 队列为空,结束动画 self.current_action = None self.animate1 = False
3. 重构redraw方法的动画逻辑
移除重复创建魔方的代码,修正动画完成后的处理逻辑:
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.animate1: if self.animate_ang >= 90: # 动画完成,更新魔方实际状态 for cube in self.cubes: cube.update(*self.current_action) # 启动下一个动作 self.start_next_action() else: # 累加动画角度 self.animate_ang += self.animate_speed # 绘制魔方 for cube in self.cubes: if self.animate1 and self.current_action: # 正在执行动画,绘制带旋转的切片 cube.draw(colors, surfaces, vertices, True, self.animate_ang, *self.current_action) else: # 无动画,绘制当前状态的魔方 cube.draw(colors, surfaces, vertices, False, 0, 0, 0, 0)
4. 测试分步动画
在App类中添加多个动作,测试分步执行效果:
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.add_action(rot_slice_map['K_1']) self.glframe.add_action(rot_slice_map['K_2']) self.glframe.add_action(rot_slice_map['K_3'])
修改后的完整代码
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
相关产品推荐
相关产品推荐

