如何在Python PyOpenGL中旋转魔方的切片?

3

我正在尝试用Python创建魔方,已经成功地完成了魔方的可视化。但是在实现旋转时遇到了一些困难。

我想知道如何解决这个问题,最初的想法是旋转每个立方体的顶点集,但并没有成功。

基本上,我想从一个立方体对象数组中选择一个切片,对每个对象执行旋转和平移操作。

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

希望了解更多的人能够给我一些指导,告诉我如何继续进行。

1个回答

11
一个魔方可以通过3维的3x3x3的立方体数组来组织。似乎很容易旋转魔方的一层,但请注意,如果旋转了一层,则魔方的位置会改变并且必须重新组织。不仅位置发生变化,旋转的单个立方体的方向也会发生变化。
首先,从类Cube的构造函数中删除PyGame和OpenGL初始化。那里是错误的地方。接下来将生成27个Cube类型的对象。
每个立方体都必须知道它最初位于哪里(self.init_i),以及在某些旋转后它当前所在的位置(self.current_i)。这些信息被编码在一个有3个元素的列表中,每个轴一个元素。值是范围为[0,N]的NxNxN魔方中的立方体索引。
单个立方体的方向是用三维旋转矩阵(self.rot)编码的。旋转矩阵必须由单位矩阵初始化。
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个方向向量组成。d维向量可以通过交换向量的坐标并反转结果的x坐标进行旋转,以进行右旋转,并反转结果的y坐标进行左旋转:

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

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

例如,围绕 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可以将矩阵乘到当前矩阵中。
以下函数绘制一个单独的立方体。参数angleaxisslicedir,甚至可以通过设置animate=True和设置参数angleaxisslicedir来对立方体应用动画:

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魔方。通过按下键盘上的19将魔方切片向右旋转,通过按下键盘上的F1F9将魔方切片向左旋转。

当然,这段代码在原始代码中使用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()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接