Блоґ одного кібера

Історія хвороби контуженого інформаційним вибухом

Модель кубика Рубика

with one comment

Модель кубика Рубика

Модель кубика Рубика

Моя перша нормального розміру програмка під Лінукс, написана з Code::Blocks. Тут даю кростплатформенний кусок коду. Можна записувати в файл .h і підключати в своїх проектах. На картинці – результат роботи програми під GLUT в моїй Убунті. (покращено Гаусовим розмиванням радіусу 2 пікселі, в GIMP. Просто плагін антиаліасингу лінь було шукати). З цією моделлю кубика Рубика можна робити всі законні дії (повороти) що і з нормальним пласмасовим кубиком. Не можна розбити, але суті гри це не міняє. Ще додати збереження, заплутування, і таблицю рекордів, і можна поповнювати світ Open Source новою грою :).

Все написано в доволі простому класі, який малює кубик, і виконує всі анімації. Можна додавати в будь-які тривимірні проекти з OpenGL. Функціональність стосується тільки рендерингу, і анімації. Алгоритм складання треба дописати, можливо в інших класах.

Як його використовувати?

Вставляєте код десь на початку програми, і створюєте новий кубик Рубика. Наприклад:

Cubik cubik1;

Тепер цей кубик можна малювати під час обновлення екрану

cubik1.Draw();

Мало не забув! Також можна малювати розгортку. Її розміри 24х18х2. Тому треба ставити її трохи далі від камери.

Вибір шару який повертається проводиться за допомогою методу

cubik1.selh(1); // Наступний шар
cubik1.selh(-1); // Попередній шар

Вибір осі обертання шару методами:

cubik1.selax(UP); // горизонтальні шари
cubik1.selax(FD); // шари площини екрану
cubik1.selax(SD); // ортогональні обом попереднім

Запуск виконання повороту відбувається командою

cubik.startanimation();

під час відтворення анімації повороту не можна змінювати шар, і запускати іншу анімацію. Анімація триває 90 кадрів (градус за кадр). Її швидкість можна змінити підредагувавши приріст кута повороту в функції animationframe(). Ну, в мене все відбувається десь за секунду.

Щоб швидко скласти кубик використовується функція

cubik1.resetchanges();

Правда в ній ніяких анімацій не відбувається, і взагалі вона чітерська.

Як воно працює?

Код містить два класи.

Cybel

Названо по аналогії зі словом піксель. Піксель – це елемент зображення, а кубель – це елемент кубика Рубика. Кубик Рубика складається з 8 таких елеметів. (В мене з 9, просто тому, що масив 3х3х3 має дев’ять елементів, а використовувати складнішу структуру – задротство). На всяк випадок, щоб сховати центральний кубик, я cкопіював з форуму GameDev.net функцію виводу геосфери, видалив з неї текстурні координати, і змінив параметри.

Але це я відхилився від теми. Кубель по суті є звичайним кубиком, з кольоровими гранями. Але так він виглядав доволі бідно, тому я додав фаску. Це додало ще 20 полігонів (з них тільки 8 трикутники) і збільшило кількість вершин в три рази. Але, краса вимагає жертв. Поле chamfer містить в собі розмір фаски. Щоб довго не пояснювати я покажу “креслення”:

Розміри кубеля

Розміри кубеля

Крім рендерингу себе на екрані, кубель вміє робити шіть різних поворотів (за і проти годинникової стрілки навколо одної з трьох осей). Осі я назвав – UP – верхня (відповідає Y системи координат), SD – бічна (відповідає X) і FD – передня (відповідає Z). Поворот являє собою перестановку кольорів чотирьох граней, і працює аналогічно алгоритму обміну двох змінних. Більше від кубеля я нічого більше не вимагав.

Cybik

А от кубик Рубика річ складніша. Як я вже казав, він складається з 9 маленьких кубиків. Порядок їх виводу робиться таким, щоб перший індекс масиву відповідав X координаті, другий Y, ну і третій Z. Малюється кубик рубика в початку локальної системи координат, і має розміри 6 на 6 на 6 одиниць. Вміє виконувати повороти одного трьох шарів кубелів, навколо однієї з осей. selectax – вибрана вісь, selecth – координата вибраного шару [0..2]. В функціях повороту, окрім поворотів кожного кубеля є довгі послідовності присвоєнь. Ці присвоєння відбуваються за такою схемою:

Схема повороту шару кубелів

Схема повороту шару кубелів

Анімації працюють дуже просто. Якщо анімація включена, то з кожним кадром кут повороту змінюється на маленьку величину. Коли шар повернеться на 90 градусів, виконується поворот внутрішньої структури, кут повороту повертається до нуля, і анімація виключається.

Код

#define NOR glNormal3d // I want write less
#define VER glVertex3d


enum axis {UP,SD,FD}; // axes of rotation - up, side , forward

#include <math.h>    // just for geosphere
#define fcos (float)cos
#define fsin (float)sin

void geosphere(float xcoord, float ycoord, float zcoord, float radius, int sides=3)
{
    const float PI=3.1415926535;
    const float TWOPI=2*PI;
    float theta1 = 0, theta2 = 0, theta3 = 0;

	float ex = 0, px = 0, cx = xcoord;
	float ey = 0, py = 0, cy = ycoord;
	float ez = 0, pz = 0, cz = 0,		r = radius;

	for (int j = 0; j < sides / 2; j++)
	{
		theta1 =    j    * TWOPI / sides - PI/2;
		theta2 = (j + 1) * TWOPI / sides - PI/2;

		glBegin(GL_QUAD_STRIP);
		{
			for (int i = 0; i <= sides; i++)
			{
				theta3 = i * TWOPI / sides;

				ex = fcos(theta1) * fcos(theta3);
				ey = fsin(theta1);
				ez = fcos(theta1) * fsin(theta3);
				px = cx + r * ex;
				py = cy + r * ey;
				pz = cz + r * ez;

				glNormal3f(ex,ey,ez);
				glVertex3f(px, py, pz);

				ex = fcos(theta2) * fcos(theta3);
				ey = fsin(theta2);
				ez = fcos(theta2) * fsin(theta3);
				px = cx + r * ex;
				py = cy + r * ey;
				pz = cz + r * ez;

				glNormal3f(ex,ey,ez);
				glVertex3f(px, py, pz);
			}
		}
		glEnd();
	}
}

void COL(int num) // set color of face, 0 - color of nonselected chamfer, 7 - color of selected chamfer
{
    const float colors[8][3]={{0,0,0}, {1,1,0},{1,0,0},{0,1,0},{0,0,1},{1,0,1},{0,1,1},{1,1,1}};
    glColor3f(colors[num][0],colors[num][1],colors[num][2]);
}

class Cubel // class for cube element
{
    double a; // half size of face
    double chamfer; // size of chamfer
    double space; // scale coeficient of cube
    int top,bottom,left,right,front,back; // colors of faces
    bool faces; // faces is visible?
    bool chamfers; // chamfers is visible?
public:
    int selected; // element is selected (white)?

    Cubel()
    {
        chamfers=true; // all initial settings
        faces=true;
        space=0.9;
        chamfer=0.2;
        a=1-chamfer;
        selected=0;
        top=1;
        bottom=2;
        left=3;
        right=4;
        front=5;
        back=6;
    }
    void reset() // return colors of faces to initial state
    {
        top=1;
        bottom=2;
        left=3;
        right=4;
        front=5;
        back=6;
    }

    ///////// Rotation of faces
    void rotateup() //conterclockwise
    {
        int tmp=front;front=left;left=back;back=right;right=tmp;
    }
    void rotatefd()
    {
        int tmp=left;left=top;top=right;right=bottom;bottom=tmp;
    }
    void rotatesd()
    {
        int tmp=front;front=top;top=back;back=bottom;bottom=tmp;
    }
    void rotateupcw() //clockwise
    {
        int tmp=right;right=back;back=left;left=front;front=tmp;
    }
    void rotatefdcw()
    {
        int tmp=bottom;bottom=right;right=top;top=left;left=tmp;
    }
    void rotatesdcw()
    {
        int tmp=bottom;bottom=back;back=top;top=front;front=tmp;
    }

    void draw() // drawing
    {
        glPushMatrix(); // we will scale our cube, so must save model matrix
        glScaled(space,space,space); // just for fun we make space between cubels
        if(chamfers) // draw chamfers
        {
        if(selected) COL(7); // set color
            else COL(0);
        glBegin(GL_TRIANGLES); // draw corner triangles
            NOR(1,1,-1);
            VER(a,a,-1);
            VER(1,a,-a);
            VER(a,1,-a);

            NOR(1,1,1);
            VER(a,a,1);
            VER(1,a,a);
            VER(a,1,a);

            NOR(-1,1,-1);
            VER(-a,a,-1);
            VER(-1,a,-a);
            VER(-a,1,-a);

            NOR(1,-1,-1);
            VER(a,-a,-1);
            VER(1,-a,-a);
            VER(a,-1,-a);
            ///
            NOR(-1,-1,1);
            VER(-a,-a,1);
            VER(-1,-a,a);
            VER(-a,-1,a);

            NOR(1,-1,1);
            VER(a,-a,1);
            VER(1,-a,a);
            VER(a,-1,a);

            NOR(-1,1,1);
            VER(-a,a,1);
            VER(-1,a,a);
            VER(-a,1,a);

            NOR(-1,-1,-1);
            VER(-a,-a,-1);
            VER(-1,-a,-a);
            VER(-a,-1,-a);
        glEnd();
        }
        glBegin(GL_QUADS);
        if(chamfers)
        {
        // chamfer ring 1
            NOR(1,0,-1);
            VER(a,a,-1);
            VER(a,-a,-1);
            VER(1,-a,-a);
            VER(1,a,-a);

            NOR(1,0,1);
            VER(a,a,1);
            VER(a,-a,1);
            VER(1,-a,a);
            VER(1,a,a);

            NOR(-1,0,-1);
            VER(-a,a,-1);
            VER(-a,-a,-1);
            VER(-1,-a,-a);
            VER(-1,a,-a);

            NOR(-1,0,1);
            VER(-a,a,1);
            VER(-a,-a,1);
            VER(-1,-a,a);
            VER(-1,a,a);

        // chamfer ring 2
            NOR(0,1,-1);
            VER(a,a,-1);
            VER(-a,a,-1);
            VER(-a,1,-a);
            VER(a,1,-a);

            NOR(0,1,1);
            VER(a,a,1);
            VER(-a,a,1);
            VER(-a,1,a);
            VER(a,1,a);

            NOR(0,-1,-1);
            VER(a,-a,-1);
            VER(-a,-a,-1);
            VER(-a,-1,-a);
            VER(a,-1,-a);

            NOR(0,-1,1);
            VER(a,-a,1);
            VER(-a,-a,1);
            VER(-a,-1,a);
            VER(a,-1,a);

        // chamfer ring 3
            NOR(-1,1,0);
            VER(-1,a,a);
            VER(-1,a,-a);
            VER(-a,1,-a);
            VER(-a,1,a);

            NOR(1,1,0);
            VER(1,a,a);
            VER(1,a,-a);
            VER(a,1,-a);
            VER(a,1,a);

            NOR(-1,-1,0);
            VER(-1,-a,a);
            VER(-1,-a,-a);
            VER(-a,-1,-a);
            VER(-a,-1,a);

            NOR(1,-1,0);
            VER(1,-a,a);
            VER(1,-a,-a);
            VER(a,-1,-a);
            VER(a,-1,a);
        }
////////////////////////////// faces
            if(faces)
            {
            COL(front);
            NOR(0,0,-1); // front
            VER(a,a,-1);
            VER(a,-a,-1);
            VER(-a,-a,-1);
            VER(-a,a,-1);

            COL(back);
            NOR(0,0,1); // back
            VER(a,a,1);
            VER(a,-a,1);
            VER(-a,-a,1);
            VER(-a,a,1);

            COL(right);
            NOR(1,0,0); //right
            VER(1,a,a);
            VER(1,a,-a);
            VER(1,-a,-a);
            VER(1,-a,a);

            COL(left);
            NOR(-1,0,0); //left
            VER(-1,a,a);
            VER(-1,a,-a);
            VER(-1,-a,-a);
            VER(-1,-a,a);


            COL(top);
            NOR(0,1,0); //up
            VER(-a,1,-a);
            VER(a,1,-a);
            VER(a,1,a);
            VER(-a,1,a);

            COL(bottom);
            NOR(0,-1,0); //down
            VER(-a,-1,-a);
            VER(a,-1,-a);
            VER(a,-1,a);
            VER(-a,-1,a);
            }
        glEnd();
        glPopMatrix(); // restore all changes of position


    }
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class Cubik{
    Cubel body[3][3][3]; // elements of cube
    int selecth; // selected layer
    axis selectax; // selected axis of rotation
    double selectangle; // angle of selected layer (for animation)
    int animation; // while animation is on, we can't make any another animation (rotation)

    void reselectbody() // when we change axis or layer cubels must change colors of chamfers
    {
        int i,j,k;
        if(selectax==SD)
        {
            for(i=0;i<3;i++)
            for(j=0;j<3;j++)
            for(k=0;k<3;k++)
                body[i][j][k].selected=(i==selecth);
        }else
        if(selectax==UP)
        {
            for(i=0;i<3;i++)
            for(j=0;j<3;j++)
            for(k=0;k<3;k++)
                body[i][j][k].selected=(j==selecth);
        }else
        if(selectax==FD)
        {
            for(i=0;i<3;i++)
            for(j=0;j<3;j++)
            for(k=0;k<3;k++)
                body[i][j][k].selected=(k==selecth);
        };

    }
    void drawuplayer(int h) // we must have funtions for drawing one selected layer normal to every axis
    {
        int i,j;
        glPushMatrix();
        glTranslated(-2,(h-1)*2,-2);

        for(i=0;i<3;i++)
        {
            for(j=0;j<3;j++)
            {
                body[j][h][i].draw();
                glTranslated(2,0,0);
            }
            glTranslated(-6,0,2);
        }
        glPopMatrix();
    }
    void drawsdlayer(int h)
    {
        int i,j;
        glPushMatrix();
        glTranslated((h-1)*2,-2,-2);

        for(i=0;i<3;i++)
        {
            for(j=0;j<3;j++)
            {
                body[h][i][j].draw();
                glTranslated(0,0,2);
            }
            glTranslated(0,2,-6);
        }
        glPopMatrix();
    }
    void drawfdlayer(int h)
    {
        int i,j;
        glPushMatrix();
        glTranslated(-2,-2,(h-1)*2);

        for(i=0;i<3;i++)
        {
            for(j=0;j<3;j++)
            {
                body[i][j][h].draw();
                glTranslated(0,2,0);
            }
            glTranslated(2,-6,0);
        }
        glPopMatrix();
    }
    void rotateup() // rotation of selected layer arount vertical axis (y)
    {
        int i,j,h=selecth;
        for(i=0;i<3;i++)
        for(j=0;j<3;j++)
            body[i][h][j].rotateup();

        Cubel tmp=body[2][h][2]; //corner elements
        body[2][h][2]=body[2][h][0];
        body[2][h][0]=body[0][h][0];
        body[0][h][0]=body[0][h][2];
        body[0][h][2]=tmp;

        tmp=body[2][h][1]; // side elements
        body[2][h][1]=body[1][h][0];
        body[1][h][0]=body[0][h][1];
        body[0][h][1]=body[1][h][2];
        body[1][h][2]=tmp;
    }
    void rotatefd() // around fordward axis (z)
    {
        int i,j,h=selecth;
        for(i=0;i<3;i++)
        for(j=0;j<3;j++)
            body[i][j][h].rotatefdcw();

        Cubel tmp=body[2][2][h]; //corner elements
        body[2][2][h]=body[2][0][h];
        body[2][0][h]=body[0][0][h];
        body[0][0][h]=body[0][2][h];
        body[0][2][h]=tmp;

        tmp=body[2][1][h]; // side elements
        body[2][1][h]=body[1][0][h];
        body[1][0][h]=body[0][1][h];
        body[0][1][h]=body[1][2][h];
        body[1][2][h]=tmp;
    }
    void rotatesd() // around side axis (x)
    {
        int i,j,h=selecth;
        for(i=0;i<3;i++)
        for(j=0;j<3;j++)
            body[h][i][j].rotatesd();

         Cubel tmp=body[h][2][2]; //corner elements
        body[h][2][2]=body[h][0][2];
        body[h][0][2]=body[h][0][0];
        body[h][0][0]=body[h][2][0];
        body[h][2][0]=tmp;

        tmp=body[h][1][2]; // side elements
        body[h][1][2]=body[h][0][1];
        body[h][0][1]=body[h][1][0];
        body[h][1][0]=body[h][2][1];
        body[h][2][1]=tmp;
    }
//////////////////////////////////////////////////////////////////////////////////////////
public:
    Cubik()
    {
        animation=0;
        selecth=1;
        selectax=UP;
        selectangle=0;
        reselectbody();
    }
    void selax(axis a) // change selected axis
    {
        if(animation) return;
        selectax=a;
        reselectbody();
    }
    void selh(int dif) // change selected layer
    {
        if(animation) return;
        selecth+=dif;
        if(selecth>2) selecth=0;
        if(selecth<0) selecth=2;
        reselectbody();
    }
    void resetchanges() // assembles cubic into start position
    {
        for(int i=0;i<3;i++)
        for(int j=0;j<3;j++)
        for(int k=0;k<3;k++)
            body[i][j][k].reset();
    }
    void draw() // draws cube layers along selected axis
    {
        glColor3f(0,0.1,0);
        geosphere(0,0,0,2);
        int i;
        switch(selectax)
        {
            case UP:
                for(i=0;i<3;i++)
                {
                    if(i==selecth) // we can rotate selected layer
                    {
                        glPushMatrix();
                        glRotated(selectangle,0,1,0);
                        drawuplayer(i);
                        glPopMatrix();
                    }
                    else drawuplayer(i);
                }
            break;
            case SD:
                for(i=0;i<3;i++)
                {
                    if(i==selecth)
                    {
                        glPushMatrix();
                        glRotated(selectangle,1,0,0);
                        drawsdlayer(i);
                        glPopMatrix();
                    }
                    else drawsdlayer(i);
                }
            break;
            case FD:
                for(i=0;i<3;i++)
                {
                    if(i==selecth)
                    {
                        glPushMatrix();
                        glRotated(selectangle,0,0,1);
                        drawfdlayer(i);
                        glPopMatrix();
                    }
                    else drawfdlayer(i);
                }
            break;
        }
    }
    void drawscanning() // draws scanning of text
    {
        glPushMatrix();

        glTranslated(0,0,-2);
        drawfdlayer(0); //front

        glPushMatrix(); //top
        glTranslated(0,6,0);
        glRotated(90,1,0,0);
        drawuplayer(0);
        glPopMatrix();

        glPushMatrix(); //bottom
        glTranslated(0,-6,0);
        glRotated(-90,1,0,0);
        drawuplayer(2);
        glPopMatrix();

        glPushMatrix(); //left
        glTranslated(-6,0,0);
        glRotated(-90,0,1,0);
        drawsdlayer(0);
        glPopMatrix();

        glPushMatrix(); //right
        glTranslated(6,0,0);
        glRotated(90,0,1,0);
        drawsdlayer(2);
        glPopMatrix();

        glPushMatrix(); //back
        glTranslated(12,0,0);
        glRotated(180,0,1,0);
        drawfdlayer(2);
        glPopMatrix();

        glPopMatrix();
    }
    void startanimation() // sets initial animation values
    {
        if(animation) return;
        animation=1;
        selectangle=0;
    }
    void animationframe() // change state of cubic for next frame
    {
        if(!animation) return; // if animation is played;
        selectangle-=1;    // visual rotatation layer changing angle  /// Edit here to change speed of rotation
        if(selectangle<=-90) // if rotation is finished
        {
            animation=0;
            selectangle=0;
            switch(selectax)  // perform logical 90 degrees rotation
            {
                case UP: rotateup();break;
                case FD: rotatefd();break;
                case SD: rotatesd();break;
            }
        }
    }
};

Advertisements

Written by bunyk

Квітень 12, 2009 at 20:59

Оприлюднено в Графіка, Кодерство

Tagged with ,

Одна відповідь

Subscribe to comments with RSS.

  1. А я почав скриптувати боссів в World of Warcraft. По-моєму, там набагато простіше писати гру, ніж починати нову з нуля. Розписана повністю структура, додані фічі – твоє завдання зробити свій світ. Досить легко для розуміння і кодування простих речей. Але коли треба реалізувати нестанадартні події – починається найцікавше )))

    Дуже цікаво, не задумуюсь взагалі над ЛоуЛевел програмуванням, все проходить на ОО рівні

    МаНГОС компілюється спокійно на лінуксі, єдине що з пам”яттю можуть бути проблеми=) для цього запускаєш сервер на чужому компі і по локалці заходиш в гру з свого ноута.

    danbst

    Квітень 13, 2009 at 22:29


Залишити відповідь

Заповніть поля нижче або авторизуйтесь клікнувши по іконці

Лого WordPress.com

Ви коментуєте, використовуючи свій обліковий запис WordPress.com. Log Out / Змінити )

Twitter picture

Ви коментуєте, використовуючи свій обліковий запис Twitter. Log Out / Змінити )

Facebook photo

Ви коментуєте, використовуючи свій обліковий запис Facebook. Log Out / Змінити )

Google+ photo

Ви коментуєте, використовуючи свій обліковий запис Google+. Log Out / Змінити )

З’єднання з %s

%d блогерам подобається це: