Как вращать кусочки кубика Рубика в Python PyOpenGL? - PullRequest
0 голосов
/ 12 мая 2018

Я пытаюсь создать кубик Рубика в 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 Ответ

0 голосов
/ 02 марта 2019

Кубик Рубика может быть организован в виде трехмерного массива кубов 3x3x3 .Кажется, что легко повернуть срез куба, но обратите внимание, что при вращении среза положения куба меняются и должны быть реорганизованы.Меняется не только положение, но и ориентация (повернутых) отдельных кубов.

Прежде всего удалите инициализацию PyGame и OpenGL из конструктора класса Cube.Это неподходящее место для этого.Далее будет сгенерировано 27 объектов типа Cube.

Каждый куб должен знать, где он изначально находится (self.init_i) и где он находится в настоящий момент после некоторых вращений (self.current_i).Эта информация кодируется в виде списка из 3 элементов, по одному для каждой оси.Значения являются индексами куба в NxNxN кубике Рубика в диапазоне [0, N [.
. Ориентация одного куба закодирована в 3-мерной матрице вращения. (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 ° вокруг axis.Трехмерная матрица вращения состоит из 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 влево) Чтобы повернуть вектор оси, необходимо поменять местами 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()
...