Кубик Рубика может быть организован в виде трехмерного массива кубов 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()