Модель кубика Рубика
Моя перша нормального розміру програмка під Лінукс, написана з 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; } } } };
А я почав скриптувати боссів в World of Warcraft. По-моєму, там набагато простіше писати гру, ніж починати нову з нуля. Розписана повністю структура, додані фічі – твоє завдання зробити свій світ. Досить легко для розуміння і кодування простих речей. Але коли треба реалізувати нестанадартні події – починається найцікавше )))
Дуже цікаво, не задумуюсь взагалі над ЛоуЛевел програмуванням, все проходить на ОО рівні
МаНГОС компілюється спокійно на лінуксі, єдине що з пам”яттю можуть бути проблеми=) для цього запускаєш сервер на чужому компі і по локалці заходиш в гру з свого ноута.
danbst
13 Квітня, 2009 at 22:29