Кубик рубик на OpenGl

Кубик рубик

Задача: Построить 3D модель кубика-рубика используя графическую библиотеку OpenGL.

Кубик рубик размера 3*3*3 состоит из 27 мелких кубиков.
Будет удобно создать класс мелкого кубика (Small_Cube).
В нём будет хранится цвета шести граней и размер.
Методы:
1) нарисовать (draw);
2) повернуть на угол 90 градусов вдоль координатной оси (rotateX, rotateY, rotateZ);
3) установить цвет грани с конкретным номером от 0 до 5 (setColor).

Следующий класс - класс самого кубика-рубика (Cube).
В нём хранится размер, 6 цветов, массив 3*3*3 объектов Small_Cube, углы поворота каждой грани.
Методы:
1) Повернуть грань с конкретным номером на угол 90 градусов. Для этого просто будем менять местами элементы 3-х мерного массива, и при этом поварачивать сами части тоже на 90 градусов.
2) Повернуть грань с конкретным номером на угол A градусов (A < 90). Для каждой грани будем хранить угол, на который она сейчас повёрнута. Если он становится кратным 90, то вызываем метод под пунктом 1.
3) Нарисовать. Рисуем грань которая повёрнута на угол не кратный 90, затем все остальные.

Клавиши в промежутке ['0', '5'] - команды поворота грани.
Правая кнопка мыши - смена режима (автоматическая прокрутка и управление вручную).
Клавиши вверх, вниз, влево, вправо - вращение.
Home, End - приближение/отдаление

Файл "Small_Cube.h"

#ifndef _SMALL_CUBE_H
#define _SMALL_CUBE_H
 
#include "D:\\Common\\OpenGLSB.h" // тут подключение библиотек glut, glu, gl
 
// класс частей кубика-рубика, большой кубик будет состоять из 27 маленьких.
struct Small_Cube
{
	// шесть граней куба - шесть цветов
	unsigned int color[6]; // (верх, низ, впереди, сзади, лево, право)
	// размер ребра
	double size;
 
	Small_Cube() 
	{
		// по умолчанию черный цвет
		memset(color, 0, sizeof(color));
		size = 0.0;
	}
 
	// поворот на плоскости X0Y
	void rotateZ()
	{
		unsigned int tmp = color[5];
		color[5] = color[3];
		color[3] = color[4];
		color[4] = color[2];
		color[2] = tmp;
	}
 
	// поворот на плоскости X0Z
	void rotateY()
	{
		unsigned int tmp = color[2];
		color[2] = color[1];
		color[1] = color[3];
		color[3] = color[0];
		color[0] = tmp;
	}
 
	// поворот на плоскости Y0Z
	void rotateX()
	{
		unsigned int tmp = color[0];
		color[0] = color[4];
		color[4] = color[1];
		color[1] = color[5];
		color[5] = tmp;
	}
 
	void setColor(int i, int color)
	{
		this->color[i] = color;
	}
 
	unsigned char *at(int i)
	{
		// разбиваем color[i] на 3 составляющих
		// например для 0xFF0000 RGB(FF, 0, 00) - красный цвет;
		_color[0] = color[i] >> 16;
		_color[1] = color[i] >>  8;
		_color[2] = color[i]      ;
		return _color;
	}
 
	// отрисовка куба:
	// устанавливаем цвет и нормали
	void draw()
	{
		glPushMatrix();
		glBegin(GL_QUADS);
 
		// верх
		glColor3ubv(at(0));
		glNormal3f(0, 0, 1);
		glVertex3f(size, size, size);          
		glVertex3f(0, size, size);          
		glVertex3f(0, 0, size);          
		glVertex3f(size, 0, size);      
 
		// низ
		glColor3ubv(at(1));
		glNormal3f(0, 0, -1);
		glVertex3f(size, 0, 0);
		glVertex3f(0, 0, 0);   
		glVertex3f(0, size, 0);  
		glVertex3f(size, size, 0);
 
		// спереди
		glColor3ubv(at(2));
		glNormal3f(0, -1, 0);
		glVertex3f(size, 0, size);
		glVertex3f(0, 0, size);   
		glVertex3f(0, 0, 0);      
		glVertex3f(size, 0, 0); 
 
		// сзади
		glColor3ubv(at(3));
		glNormal3f(0, 1, 0);
		glVertex3f(size, size, 0);   
		glVertex3f(0, size, 0);      
		glVertex3f(0, size, size);   
		glVertex3f(size, size, size);
 
		// слева
		glColor3ubv(at(4));
		glNormal3f(-1, 0, 0);
		glVertex3f(0, size, size);   
		glVertex3f(0, size, 0);      
		glVertex3f(0, 0, 0);         
		glVertex3f(0, 0, size);      
 
		// справа
		glColor3ubv(at(5));
		glNormal3f(1, 0, 0);
		glVertex3f(size, size, 0);
		glVertex3f(size, size, size);
		glVertex3f(size, 0, size);   
		glVertex3f(size, 0, 0);      
 
		glEnd();
		glPopMatrix();
	}
 
	// отрисовка куба со смещением (x, y, z)
	void draw(double x, double y, double z)
	{
		glPushMatrix();
		glTranslated(x, y, z);
		draw();
		glPopMatrix();
	}
 
private:
	unsigned char _color[4];
};
 
 
#endif;

Файл "Cube.h"

#ifndef _CUBE_H
#define _CUBE_H
 
#include "Small_Cube.h"
 
class Cube
{
	// 27 частей
	Small_Cube a[3][3][3];
	// храним угол поворота каждой грани
	int rotate[6];
	// размер кубика-рубика
	double size;
	// цвета граней
	unsigned int color[6];
 
public:
	// храним номер грани, которая в данный момент поварачивается, или -1 если ничего не поварачивается
	int current;
 
	Cube()
	{
	}
 
	Cube(double size, unsigned int *color) 
	{
		clear(size, color);
	}
 
	void clear(double size, unsigned int *color) 
	{
		memset(rotate, 0, sizeof(rotate));
		this->size = size;
		current = -1;
 
		int i, j, k;
		for(i = 0; i < 6; i++)
			this->color[i] = color[i];
 
		// верх
		for(i = 0; i < 3; i++)
			for(j = 0; j < 3; j++)
				a[i][j][2].setColor(0, color[0]);
 
		// низ
		for(i = 0; i < 3; i++)
			for(j = 0; j < 3; j++)
				a[i][j][0].setColor(1, color[1]);
 
		// спереди
		for(k = 0; k < 3; k++)
			for(j = 0; j < 3; j++)
				a[j][0][k].setColor(2, color[2]);
 
		// сзади
		for(k = 0; k < 3; k++)
			for(j = 0; j < 3; j++)
				a[j][2][k].setColor(3, color[3]);
 
		// слева
		for(i = 0; i < 3; i++)
			for(k = 0; k < 3; k++)
				a[0][k][i].setColor(4, color[4]);
 
		// справа
		for(i = 0; i < 3; i++)
			for(k = 0; k < 3; k++)
				a[2][k][i].setColor(5, color[5]);
 
		// устанавливаем размеры мелких деталей
		// это будет треть всего размера, умноженная на коэффициент немного меньший еденицы
		// (чтобы детали не были слишком плотно)
		for(i = 0; i < 3; i++)
			for(j = 0; j < 3; j++)
				for(k = 0; k < 3; k++)
					a[i][j][k].size = (size / 3.0) * 0.95;
	}
 
	void draw()
	{
		const double K = 0.65;
		// рисуем корпус - это просто куб черного цвета, размер которого равен K*size
		glPushMatrix();
		glColor3f(0, 0, 0);
		glTranslatef(((1.0 - K)/2)*size + K*size/2, ((1.0 - K)/2)*size + K*size/2, ((1.0 - K)/2)*size + K*size/2);
		glutSolidCube(size * K);
		glPopMatrix();
 
		// ok[i][j][k] показывает, находится ли в состоянии покоя деталь с координатами (i, j, k)
		memset(ok, true, sizeof(ok));
		if (current != -1)
		{
			glPushMatrix();
			int i, j, k;
 
			if (current == 0 || current == 1)
			{
				// 0 <= current <= 1 показывает, что сейчас крутится грань на плоскости X0Y
				// current = 0 - нижняя часть
				// current = 1 - верхняя часть
				k = (current & 1) * 2;
				// следовательно ok слоя  k  устанавливаем в false
				for(i = 0; i < 3; i++)
					for(j = 0; j < 3; j++)
						ok[i][j][k] = false;
 
				// теперь нужно покрутить грань под номером current на угол rotate[current]
				// относительно центра этой грани
				// для этого сдвинемся к центру, покрутим, сдвинемся обратно
				glTranslated(size / 2, size / 2, 0);   // сдвигаемся к центру
				glRotatef(rotate[current], 0, 0, 1);   // крутим
				glTranslated(-size / 2, -size / 2, 0); // сдвигаемся обратно
				// рисуем
				for(i = 0; i < 3; i++)
					for(j = 0; j < 3; j++)
						a[i][j][k].draw(size / 3 * i, size / 3 * j, size / 3 * k);
			}
			// аналагично с остальными четырмя гранями
			else if (current == 2 || current == 3)
			{
				j = (current & 1) * 2;
				for(i = 0; i < 3; i++)
					for(k = 0; k < 3; k++)
						ok[i][j][k] = false;
 
				glTranslated(size / 2, 0, size / 2);
				glRotatef(rotate[current], 0, 1, 0);
				glTranslated(-size / 2, 0, -size / 2);
				for(i = 0; i < 3; i++)
					for(k = 0; k < 3; k++)
						a[i][j][k].draw(size / 3 * i, size / 3 * j, size / 3 * k);
			}
			else if (current == 4 || current == 5)
			{
				i = (current & 1) * 2;
				for(j = 0; j < 3; j++)
					for(k = 0; k < 3; k++)
						ok[i][j][k] = false;
 
				glTranslated(0, size / 2, size / 2);
				glRotatef(rotate[current], 1, 0, 0);
				glTranslated(0, -size / 2, -size / 2);
				for(j = 0; j < 3; j++)
					for(k = 0; k < 3; k++)
						a[i][j][k].draw(size / 3 * i, size / 3 * j, size / 3 * k);
			}
			glPopMatrix();
		}
 
		for(int i = 0; i < 3; i++)
			for(int j = 0; j < 3; j++)
				for(int k = 0; k < 3; k++)
					if (ok[i][j][k])
						// теперь рисуем те детали, которые не поварачивались выше,
						// они отмечены ok[i][j][k] = true
						a[i][j][k].draw(size / 3 * i, size / 3 * j, size / 3 * k);
	}
 
public:
	void rot90(int idx, int sign)
	{
		int i, j, k;
		// sign задаётся в зависимости он направления
		// sign = -1, sign = 1
		// если sign = -1, значит крутим 3 раза
		if (sign == -1)
			sign = 3;
		while(sign--)
		{
			if (idx == 0 || idx == 1)
			{
				// низ/верх
				k = (idx & 1) * 2;
				// копируем повёрнутую на 90 градусов верхнюю/нижнюю грань
				// в массив tmp, затем грани присваиваем tmp
				// и не забываем повернуть каждую деталь этой грани
				for(i = 0; i < 3; i++)
					for(j = 0; j < 3; j++)
						tmp[j][2 - i] = a[i][j][k];
				for(i = 0; i < 3; i++)
					for(j = 0; j < 3; j++)
						tmp[i][j].rotateZ(), a[i][j][k] = tmp[i][j];
			}
			// аналогично с остальными четырмя гранями
			else if (idx == 2 || idx == 3)
			{
				// лево/право
				j = (idx & 1) * 2;
				for(i = 0; i < 3; i++)
					for(k = 0; k < 3; k++)
						tmp[k][2 - i] = a[i][j][k];
				for(i = 0; i < 3; i++)
					for(k = 0; k < 3; k++)
						tmp[i][k].rotateX(), a[i][j][k] = tmp[i][k];
			}
			else if (idx == 4 || idx == 5)
			{
				// впереди/сзади
				i = (idx & 1) * 2;
				for(j = 0; j < 3; j++)
					for(k = 0; k < 3; k++)
						tmp[k][2 - j] = a[i][j][k];
				for(j = 0; j < 3; j++)
					for(k = 0; k < 3; k++)
						tmp[j][k].rotateY(), a[i][j][k] = tmp[j][k];
			}
		}
	}
 
	// крутит грань под номером idx на угол angle (в градусах)
	void Rotate(int idx, int angle)
	{
		// мы пытаемся покрутить грань с номером idx
		// значит нужно проверить что другая грань уже не крутится
		if (current == -1 || current == idx)
		{
			// обновляем поворот
			rotate[idx] += angle;
 
			if (rotate[idx] % 90 != 0)
			{
				current = idx;
			}
			else
			{
				// если угол стал кратным 90, то поварачиваем на массиве
				if ((rotate[idx] < 0) ^ (current == 2 || current == 3))
					rot90(idx, 1);
				else
					rot90(idx, -1);
				rotate[idx] = 0;
				current = -1;
			}
		}
	}
private:
	int _angle[4];
	bool ok[4][4][4];
	Small_Cube tmp[4][4];
};
 
#endif;

Файл "Cube.cpp"

#include "Cube.h"
#include <time.h>
#include <stdlib.h>
 
#define CUBE_SIZE 13
#define TIMER 30
// обозначаем цвета:
//                    (верх,      низ,   впереди,   сзади,    лево,      право)
unsigned int c[9] = {0xFFFFFF, 0xFFFF00, 0x0000FF, 0x00FF00, 0xFF0000, 	0xCD853F};
 
// координаты источника света
GLfloat lightPos[] = {0, 100, 200, 0}; 
// проекции угла поворота на оси
int xRot = 24, yRot = 34, zRot = 0;
// отдаление
double translateZ = -35.0;
// кубик-рубик
Cube cube;
// флаг того, крутится куб сам, или нет (будет переключаться правой кнопкой мыши)
int timerOn = 0;
 
void display()
{
	glPushMatrix();
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glColor3f(1, 0, 0);
	glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
	glTranslatef(0, 0, translateZ);
	glRotatef(xRot, 1, 0, 0);
	glRotatef(yRot, 0, 1, 0);
	glTranslatef(CUBE_SIZE / -2.0, CUBE_SIZE / -2.0, CUBE_SIZE / -2.0);
	cube.draw();
	glPopMatrix();
	glutSwapBuffers();
}
 
void reshape(int w, int h)
{
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    GLfloat fAspect = (GLfloat)w/(GLfloat)h;
    gluPerspective(60, fAspect, 1, 1000.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}
 
void init()
{
	glClearColor(1.0, 1.0, 1.0, 0.0);
	// инициализируем случайные числа
	srand(time(0));
 
	// освещение
	float mat_specular[] = {0.3, 0.3, 0.3, 0};
	float diffuseLight[] = {0.2, 0.2, 0.2, 1};
	float ambientLight[] = {0.9, 0.9, 0.9, 1.0};
	glShadeModel(GL_SMOOTH);
	glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
	glMateriali(GL_FRONT, GL_SHININESS, 128);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
	glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
	glEnable(GL_LIGHT0);
	glEnable(GL_COLOR_MATERIAL);
	glColorMaterial(GL_FRONT, GL_AMBIENT);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
 
	// инициализируем куб
	cube.clear(CUBE_SIZE, c);
}
 
void specialKeys(int key, int, int)
{
	// клавиши влево/вправо вращают по Y
	// клавиши вверх/вниз вращают по X
	// F1 - возвращает в начальное положение
	if (key == GLUT_KEY_DOWN)
	{
		xRot += 3;
		if (xRot >= 360)
			xRot -= 360;
		glutPostRedisplay();
	}
 
	if (key == GLUT_KEY_UP)
	{
		xRot -= 3;
		if (xRot < 0)
			xRot += 360;
		glutPostRedisplay();
	}
 
	if (key == GLUT_KEY_RIGHT)
	{
		yRot += 3;
		if (yRot >= 360)
			yRot -= 360;
		glutPostRedisplay();
	}
 
	if (key == GLUT_KEY_LEFT)
	{
		yRot -= 3;
		if (yRot < 0)
			yRot += 360;
		glutPostRedisplay();
	}
 
	if (key == GLUT_KEY_HOME)
	{
		translateZ += 5;
		glutPostRedisplay();
	}
 
	if (key == GLUT_KEY_END)
	{
		translateZ -= 5;
		glutPostRedisplay();
	}
 
	if (key == GLUT_KEY_F1)
	{
		cube.clear(CUBE_SIZE, c);
		glutPostRedisplay();
	}
}
 
void keys(unsigned char key, int, int)
{
	// если нажали клавишу от 0 до 5 - начинаем поворот на 3 градуса
	if (cube.current == -1 && key >= '0' && key < '6')
	{
		cube.Rotate(key - '0', 3);
		display();
	}
}
 
void mouse(int key, int state, int, int)
{
	if (key == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
	{
		// переключаем флаг
		timerOn = 1 - timerOn;
	}
}
 
void timer(int)
{
	glutTimerFunc(TIMER, timer, 0);
	if (timerOn)
	{
		// если включен автоматический поворот, и смотрим
		// если сейчас никакая грань не крутится, то начинаем крутить случайную,
		// иначе крутим текущую
		if (cube.current == -1)
			keys(rand() % 6 + '0', 0, 0);
		else
			cube.Rotate(cube.current, 3);
	}
	else
	{
		if (cube.current != -1)
			cube.Rotate(cube.current, 3);
	}
	display();
}
 
int main(int argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(800, 700);
	glutInitWindowPosition(1, 1);
	glutCreateWindow("Cube");
	init();
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keys);
	glutMouseFunc(mouse);
	glutTimerFunc(TIMER, timer, 0);
	glutSpecialFunc(specialKeys);
	glutMainLoop();
	return 0;
}

P.S. Эту реализацию можно переделать на случай большего размера кубика.

Ключевые слова: 
Кубик-рубик, моделирование, OpenGl, C++
ВложениеРазмер
Cube.rar94.78 кб