Skip to main content
 首页 » 编程设计

python之如何在 python PyOpenGL 中旋转魔方的切片

2025年05月04日14小虾米

我正在尝试用 Python 创建一个 Rubik 魔方,我已经达到了视觉上表示魔方的程度。为如何实现轮换而苦苦挣扎。

我想我是在征求有关如何着手进行此操作的反馈。我起初想到的是,旋转每个立方体的顶点集,但运气不佳。

我基本上想从立方体对象数组(大小不同)中选择一个切片,对每个对象执行旋转和平移。

import pygame 
import random 
from pygame.locals import * 
 
from OpenGL.GL import * 
from OpenGL.GLU import * 
 
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), #Red 
    (0,1,0), #Green 
    (1,0.5,0), #Orange 
    (1,1,0), #Yellow 
    (1,1,1), #White 
    (0,0,1), #Blue 
    ) 
 
class Cube(): 
    '''set the vertices edges and surfaces(colored) for a Cube''' 
    def __init__(self): 
        '''initiate the display to show the cube''' 
        pygame.init() 
        display = (800,600) 
        pygame.display.set_mode(display, DOUBLEBUF|OPENGL) 
        glEnable(GL_DEPTH_TEST)  
        gluPerspective(45, (display[0]/display[1]), 0.1, 50.0) 
 
        glTranslatef(1,1, -40) 
 
    def setVertices(self, xmove, ymove, zmove): 
        '''set predefined vertices''' 
        xValueChange = xmove 
        yValueChange = ymove 
        zValueChange = zmove 
 
        newVertices = [] 
 
        for vert in vertices: 
            newVert = [] 
 
            newX = vert[0] + xValueChange 
            newY = vert[1] + yValueChange 
            newZ = vert[2] + zValueChange 
 
            newVert.append(newX) 
            newVert.append(newY) 
            newVert.append(newZ) 
 
            newVertices.append(newVert) 
 
        return newVertices 
 
    def CreateCube(self, vertices): 
        '''create with OpenGL''' 
        glBegin(GL_QUADS) 
        x = 0 
        for surface in surfaces: 
            glColor3fv(colors[x]) 
            x+=1 
            for vertex in surface: 
                glVertex3fv(vertices[vertex]) 
        glEnd() 
 
class EntireCube(): 
    def __init__(self,typeOfCube): 
        self.typeOfCube = typeOfCube 
        self.NewCube = Cube() 
 
    def createEntireCube(self): 
        '''for each dimension x,y,z make a dictionary containing the vertices to be displayed''' 
        self.cubeDict = {} 
        count = 0 
        for x in range(self.typeOfCube): 
            for y in range(self.typeOfCube): 
                for z in range(self.typeOfCube): 
                    self.cubeDict[count] = self.NewCube.setVertices(x*2.1,y*2.1,z*2.1) 
                    count += 1 
 
    def mainloop(self): 
        '''key events, creates the matrix of cubes''' 
        rotateUpKey, rotateDownKey, rotateLeftKey, rotateRightKey = False, False, False, False 
        rotationalSensitivity = 2 
 
        while True: 
            for event in pygame.event.get(): 
                if event.type == pygame.QUIT: 
                    pygame.quit() 
                    quit() 
                if event.type == KEYDOWN: 
                    if event.key == K_UP: 
                        rotateUpKey = True 
                    if event.key == K_DOWN: 
                        rotateDownKey = True 
                    if event.key == K_LEFT: 
                        rotateLeftKey = True 
                    if event.key == K_RIGHT: 
                        rotateRightKey = True 
 
                if event.type == KEYUP: 
                    if event.key == K_UP: 
                        rotateUpKey = False 
                    if event.key == K_DOWN: 
                        rotateDownKey = False 
                    if event.key == K_LEFT: 
                        rotateLeftKey = False 
                    if event.key == K_RIGHT: 
                        rotateRightKey = False 
 
            if rotateUpKey: 
                glRotatef(rotationalSensitivity,-rotationalSensitivity,0,0) 
            if rotateDownKey: 
                glRotatef(rotationalSensitivity,rotationalSensitivity,0,0) 
            if rotateLeftKey: 
                glRotatef(rotationalSensitivity,0,-rotationalSensitivity,0) 
            if rotateRightKey: 
                glRotatef(rotationalSensitivity,0,rotationalSensitivity,0) 
 
            #eventually implement keysbindings to call function to rotate a slice of the matrix created 
 
            # x = glGetDoublev(GL_MODELVIEW_MATRIX) 
 
            glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) 
 
            for eachCube in self.cubeDict: 
                self.NewCube.CreateCube(self.cubeDict[eachCube]) 
 
            # glPushMatrix() 
            # glRotatef(1,3,1,1) 
            # glPopMatrix() 
 
            pygame.display.flip() 
            pygame.time.wait(10) 
 
def main(): 
    NewEntireCube = EntireCube(3) #create a 3x3x3 cube 
    NewEntireCube.createEntireCube() 
    NewEntireCube.mainloop() 
 
if __name__ == '__main__': 
    main() 
    pygame.quit() 
    quit() 

我希望对此有更多了解的人可以就如何进行提供一些指导。

请您参考如下方法:

魔方可以由 3x3x3 立方体的 3 维数组组织。旋转立方体的一个切片似乎很容易,但请注意,如果切片被旋转,立方体的位置会发生变化并且必须重新组织。不仅位置发生变化,(旋转的)单个立方体的方向也发生变化。

首先从类 Cube 的构造函数中删除 PyGame 和 OpenGL 初始化。这是错误的地方。下面会生成27个Cube类型的对象。

每个立方体都必须知道它最初位于何处 (self.init_i) 以及经过一些旋转后它当前位于何处 (self.current_i)。此信息被编码在一个包含 3 个元素的列表中,每个元素对应一个轴。这些值是 NxNxN 魔方中立方体的索引,范围为 [0, N[。
单个立方体的方向以 3 维编码 Rotation matrix (self.rot)。旋转矩阵必须由 identity matrix 初始化.

class Cube(): 
    def __init__(self, id, N, scale): 
        self.N = N 
        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)] 

创建 27 个立方体的列表

cr = range(3) 
self.cubes = [Cube((x, y, z), 3, scale) for x in cr for y in cr for z in cr] 

如果旋转魔方的一部分,则必须检查哪个单个立方体受到影响。这可以通过检查切片是否与当前位置的旋转轴条目相匹配来完成。

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

要旋转立方体,位置和方向必须围绕 轴 旋转 90°。一个 3 维旋转矩阵由 3 个方向向量组成。可以通过交换向量的坐标并反转结果的 x 坐标进行右旋转和反转结果的 y 坐标进行左旋转来旋转 d 维向量:

rotate right: (x, y) -> (-y, x) 
rotate left:  (x, y) -> (y, -x) 

由于旋转矩阵的所有向量都在轴对齐的平面中,因此该算法可用于更改立方体的方向和位置。 axis 旋转轴(x=0, y=1, z=2) dir 是旋转方向(1 在右边,-1 在左边) 要旋转轴矢量,必须交换矢量的 2 个分量并将其中之一反转。

例如绕Y轴向左旋转:

(x, y, z) -> (z, y, -x) 

旋转位置时,必须交换索引。反转索引意味着将索引i映射到索引N-1-i:

例如绕Y轴向左旋转:

(ix, iy, iz) -> (iz, iy, N-1-ix) 

单个立方体的旋转:

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] ) 

当需要绘制立方体时,那么立方体的当前位置(self.current_i)和方位self.rot可以用来设置4x4变换矩阵:

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] 

与 glPushMatrix分别glPushMatrix .通过 glMultMatrix一个矩阵可以与当前矩阵相乘。
以下函数绘制单个立方体。参数 angle, axis, slice, dir 甚至可以给立方体应用动画,通过设置animate=True 和设置参数angle, axis, slice, dir:

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

要绘制立方体,只需循环调用方法draw:

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

Cube 类的实现适用于任何 NxNxN 魔方。

请参阅3x3x3 立方体的示例程序。立方体的切片通过 1 到 9 键向右旋转,通过 F1 到 F9 键向左旋转:

当然代码使用了Legacy OpenGL关于您的原始代码。但是 Cube.transformMat 方法为单个部分立方体设置了一个通用的 4x4 模型矩阵。因此可以轻松地将此代码移植到现代 OpenGL。

import pygame 
import random 
from pygame.locals import * 
 
from OpenGL.GL import * 
from OpenGL.GLU import * 
 
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)) 
 
class Cube(): 
    def __init__(self, id, N, scale): 
        self.N = N 
        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 EntireCube(): 
    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 mainloop(self): 
 
        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), 
        }   
 
        ang_x, ang_y, rot_cube = 0, 0, (0, 0) 
        animate, animate_ang, animate_speed = False, 0, 5 
        action = (0, 0, 0) 
        while True: 
 
            for event in pygame.event.get(): 
                if event.type == pygame.QUIT: 
                    pygame.quit() 
                    quit() 
                if event.type == KEYDOWN: 
                    if event.key in rot_cube_map: 
                        rot_cube = rot_cube_map[event.key] 
                    if not animate and event.key in rot_slice_map: 
                        animate, action = True, rot_slice_map[event.key] 
                if event.type == KEYUP: 
                    if event.key in rot_cube_map: 
                        rot_cube = (0, 0) 
 
            ang_x += rot_cube[0]*2 
            ang_y += rot_cube[1]*2 
 
            glMatrixMode(GL_MODELVIEW) 
            glLoadIdentity() 
            glTranslatef(0, 0, -40) 
            glRotatef(ang_y, 0, 1, 0) 
            glRotatef(ang_x, 1, 0, 0) 
 
            glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) 
 
            if animate: 
                if animate_ang >= 90: 
                    for cube in self.cubes: 
                        cube.update(*action) 
                    animate, animate_ang = False, 0 
 
            for cube in self.cubes: 
                cube.draw(colors, surfaces, vertices, animate, animate_ang, *action) 
            if animate: 
                animate_ang += animate_speed 
 
            pygame.display.flip() 
            pygame.time.wait(10) 
 
def main(): 
 
    pygame.init() 
    display = (800,600) 
    pygame.display.set_mode(display, DOUBLEBUF|OPENGL) 
    glEnable(GL_DEPTH_TEST)  
 
    glMatrixMode(GL_PROJECTION) 
    gluPerspective(45, (display[0]/display[1]), 0.1, 50.0) 
 
    NewEntireCube = EntireCube(3, 1.5)  
    NewEntireCube.mainloop() 
 
if __name__ == '__main__': 
    main() 
    pygame.quit() 
    quit()