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

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

Posts Tagged ‘C++

Рендеринг тривимірної моделі

with 3 comments

Недавно, я розказував про розшифрування формату 3ds. Це доволі поширений формат тривимірної графіки, підтримується більшістю редакторів, Blender в тому числі. І написав програмку, яка витягує з фалу 3ds дані, і записує їх в простіший текстовий формат. Але текст, це хоч і добре, але зображення завжди красивіше. Тому зараз спробуємо завантажити і намалювати тривимірну модель.

Клас, який буде відповідати за модель, назвемо TriMesh (сітка трикутників), так як моделі в основному з трикутників і складаються.

Завантаження

Завантаження відбувається просто. Читаємо символ мітки блоку, і розмір блоку. Якщо мітка ‘N’, то далі читаємо рядок – ім’я мешу. Інакше, виділяємо достатню кількість пам’яті, і читаємо координати вершин, текстурні координати, чи номери вершин трикутників, залежно від мітки. Якщо зустріли якусь неправильну мітку – виводимо повідомлення про помилку.

Відтворення

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

Силует кораблика

Силует кораблика

Обчислення

Правда частіше за все, ми взагалі нічого не побачимо, просто тому, що неправильно відредагували модель. Наприклад вона може не стояти в центрі системи координат, і виводитись за межами екрану. А ми будем морочити собі голову тим, чому вона не виводиться. В мене так і було. Але на щастя, я здогадався, і написав метод void computeboundaries(), який обчислює центр обмежуючого паралелепіпеда для моделі, і переміщує модель так, щоб центр попав в точку (0,0,0). Також маштаб моделі може бути не таким як треба, але це я вже залишив на совість художника.

Ну, і виявляється в форматі 3ds переплутані осі Y і Z, тому я просто переставив ці дві змінні при завантаженні.

Щоб з’явилось світло, і наша модель виводилась об’ємною, потрібно обчислювати нормалі. Взагалі то нормалі обчислюються для кожної вершини, залежно від груп згладжування, але я вирішив поки що не морочити собі голову цими групами. В мене нормалі об’числюються для кожного трикутника.

Кожен трикутник лежить в площині яка задається двома векторами – якимись сторонами трикутника. Нормаль до площини – це нормалізований векторний добуток цих векторів. Оце й уся математика.

З світлом і нормалями наш корабель виглядатиме так:

Наїжачений кораблик

Наїжачений кораблик

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

І ще одна думка з’явилась. Завантаження моделі я писав вчора. Сьогодні в обід встав, і виявив, що випадково, засинаючи, замінив код модуля з класом TriMesh нульовим проектом GLUT. І вбив працю всього дня. Тому от зараз написав його заново, і вирішив зберегти резезрвну копію онлайн. Бо це завантаження я пишу, вже далебі не перший раз. Треба збільшувати повторне використання коду. Як і обчислення векторного добутку.

Так отаке розчарування наштовхнуло мене на думку, що клас трикутної сітки треба спростити, викинувши зайві обчислення. Бо потім треба буде ще додавати текстури, і навіть можливо анімації. Тому в цей клас додам функцію збереження моделі, і просто зроблю програмку, яка зберігає результат переміщення в центр, і обчислення нормалей.

І варто зауважити, що код не зовсім безпечний. Якщо вам раптом повідомляють про Access Violation, чи про Segmentation fault (в *nіx системі), то перевірте, чи все завантажилось, і чи не забули ви обчислити нормалі.

Код

#ifndef MESH_H_INCLUDED
#define MESH_H_INCLUDED

#include <stdio.h>
#include <math.h>

typedef struct VertexS
{
    float x,y,z;
} Vertex;

typedef struct TriangleS
{
    int a,b,c;
} Triangle;

typedef struct MapCoordS
{
    float u,v;
} MapCoord;

Vertex Normal(Vertex a, Vertex b, Vertex c)
{
    Vertex u,v,res;
    u.x=b.x-a.x;
    u.y=b.y-a.y;
    u.z=b.z-a.z;

    v.x=c.x-a.x;
    v.y=c.y-a.y;
    v.z=c.z-a.z;

    res.x=-u.y*v.z+u.z*v.y;
    res.y=u.x*v.z-u.z*v.x;
    res.z=-u.x*v.y+u.y*v.x;
    return res;
}

Vertex Normalize(Vertex x)
{
    float length=-sqrt(x.x*x.x+x.y*x.y+x.z*x.z);
    x.x/=length;
    x.y/=length;
    x.z/=length;
    return x;
}

class TriMesh
{
    char name[30];
    int vertcount,tricount;
    Vertex *vertexes;
    Triangle *triangles;
    Vertex* normals;
    MapCoord* mapping;
public:
    TriMesh()
    {
        vertcount=0;tricount=0;
        vertexes=NULL;triangles=NULL;
        normals=NULL;
    }
    void clear()
    {
        if(vertexes) free(vertexes);
        if(triangles) free(triangles);
        if(normals) free(normals);
        vertcount=0; tricount=0;
    }
    void LoadFromFile(char *fn)
    {
        clear();
        FILE *f;
        f=fopen(fn,"r");
        if(!f)
        {
            printf("Error: can't open file \"%s\"\n",fn);
            return;
        }
        char label;
        int count;
        while(!feof(f))
        {
            fscanf(f,"%c %i",&label,&count);
            switch(label)
            {
                case 'N':
                    fscanf(f,"%s\n",name);
                break;
                case 'V':
                    vertcount=count;
                    vertexes=(Vertex *)malloc(sizeof(Vertex)*count);
                    for(int i=0;i<count;i++)
                    {
                        fscanf(f,"%f %f %f\n",&vertexes[i].x,&vertexes[i].z,&vertexes[i].y);
                    };
                break;
                case 'P':
                    tricount=count;
                    triangles=(Triangle *)malloc(sizeof(Triangle)*count);
                    for(int i=0;i<count;i++)
                    {
                        fscanf(f,"%i %i %i\n",&triangles[i].a,&triangles[i].b,&triangles[i].c);
                    }
                break;
                case 'M':
                    if(count!=vertcount) printf("Error: Count of mapping coords isn't equal to count of vertexes\n");
                    mapping=(MapCoord *)malloc(sizeof(MapCoord)*vertcount);
                    for(int i=0;i<count;i++)
                    {
                        fscanf(f,"%f %f\n",&mapping[i].u,&mapping[i].v);
                    }
                break;

                default:
                    printf("Error: Unknown label: '%s'",label);
                break;
            }
        }
        fclose(f);
    }
    void computeboundaries()
    {
        if(!vertcount) return;

        Vertex min,max;

        min=max=vertexes[0];
        for(int i=1;i<vertcount;i++)
        {
            if(min.x>vertexes[i].x) min.x=vertexes[i].x;
            if(min.y>vertexes[i].y) min.y=vertexes[i].y;
            if(min.z>vertexes[i].z) min.z=vertexes[i].z;

            if(max.x<vertexes[i].x) max.x=vertexes[i].x;
            if(max.y<vertexes[i].y) max.y=vertexes[i].y;
            if(max.z<vertexes[i].z) max.z=vertexes[i].z;
        }
        min.x=(min.x+max.x)/2; // compute center
        min.y=(min.y+max.y)/2;
        min.z=(min.z+max.z)/2;

        for(int i=0;i<vertcount;i++) // move to center
        {
            vertexes[i].x-=min.x;
            vertexes[i].y-=min.y;
            vertexes[i].z-=min.z;
        }
    }
    void computenormals()
    {
        if(!normals) normals=(Vertex *)malloc(sizeof(Vertex)*tricount);
        for(int i=0;i<tricount;i++)
        {
            normals[i]=Normalize(Normal(vertexes[triangles[i].a],vertexes[triangles[i].b],vertexes[triangles[i].c]));
        }
    }
    void draw()
    {
        glBegin(GL_TRIANGLES);
            for(int i=0;i<tricount;i++)
            {
                glNormal3f(normals[i].x,normals[i].y,normals[i].z);

                glVertex3f(vertexes[triangles[i].a].x,vertexes[triangles[i].a].y,vertexes[triangles[i].a].z);
                glVertex3f(vertexes[triangles[i].b].x,vertexes[triangles[i].b].y,vertexes[triangles[i].b].z);
                glVertex3f(vertexes[triangles[i].c].x,vertexes[triangles[i].c].y,vertexes[triangles[i].c].z);
            }
        glEnd();
    }
    void drawnormals()
    {
        Vertex centre;
        glBegin(GL_LINES);
            for(int i=0;i<tricount;i++)
            {
                centre.x=(vertexes[triangles[i].a].x+vertexes[triangles[i].b].x+vertexes[triangles[i].c].x)/3;
                centre.y=(vertexes[triangles[i].a].y+vertexes[triangles[i].b].y+vertexes[triangles[i].c].y)/3;
                centre.z=(vertexes[triangles[i].a].z+vertexes[triangles[i].b].z+vertexes[triangles[i].c].z)/3;

                glVertex3f(centre.x,centre.y,centre.z);
                glVertex3f(centre.x-normals[i].x*10,centre.y-normals[i].y*10,centre.z-normals[i].z*10);
            }
        glEnd();
    }
    void SaveToFile(char *fn)
    {
        if(!mapping) mapping=(MapCoord *)calloc(vertcount,sizeof(MapCoord));
        FILE *f;
        f=fopen(fn,"w");

        fprintf(f,"%i\n",vertcount);
        for(int i=0;i<vertcount;i++)
            fprintf(f,"%f %f %f %f %f\n",vertexes[i].x,vertexes[i].y,vertexes[i].z,mapping[i].u,mapping[i].v);

        fprintf(f,"%i\n",tricount);
        for(int i=0;i<tricount;i++)
            fprintf(f,"%i %i %i %f %f %f\n",triangles[i].a,triangles[i].b,triangles[i].c,normals[i].x,normals[i].y,normals[i].z);
        fclose(f);
    }
};

#endif // MESH_H_INCLUDED

Конструктивні наїзди приймаються :).

Written by bunyk

24 Травня, 2009 at 17:16

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

Tagged with

n-вимірний куб

with 15 comments

Щойно подумав, що добре було б мати свій логотип. Щось красиве, що за одно відображало програмерсько-математичне спрямування мого блогу, а заодно оригінальне. І таки одна річ спала на думку. Недавно робив кубик рубика. Всім сподобалося. Але кубик – це занадто просто. І на думку спав тессеракт.

Десь рік тому, випадково я запрограмував повертання каркасу тессеракта в Паскалі. А недавно подумав – та що там той тессеракт. Фігня. Дай я намалюю n-вимірний куб. Це ж аналогічно до тривимірного.

Одна з проекцій каркасу п'ятивимірного куба

Одна з проекцій каркасу п'ятивимірного куба

Теорія

Спочатку теоретичні міркування. Хай ми перебуваємо в n-вимірному просторі. Тому далі все по замовчуванню буде вважатись n-вимірне. Для простоти розуміння уявляйте собі наш простір (тривимірний).

Дамо означення деяким поняттям:

Точка (вершина)
вектор з n компонентами (координатами)
Відрізок
множина точок яка знаходиться між двома точками. Нехай це точки A=(a_1,a_2,\ldots,a_n) і B=(b_1,b_2,\ldots,b_n). Тоді відрізок AB – це множина AB=\{ X = (x_1,x_2,\ldots,x_n) | \forall i=\overline{1..n},\, t \in [0;1],\, x_i = a_i + t(b_i - a_i) \}
Куб
множина точок, модуль максимальної компоненти яких менший 1. (Таке визначення задає лише куб в центрі координат, зі стороною 2, і ребрами паралельними осям координат, але спрощує означення. Інші куби можна утворити афінними перетвореннями).
Вершина куба
точка, в якої модуль кожної координати дорівнює одиниці.
Ребро куба
відрізок між вершинами, які відрізняються лише одною координатою.

Для малювання каркасу, цього має бути достатньо. З означень виходить, що одновимірний куб є відрізком [-1;1], двовимірний – квадратом, ну і нормальний тривимірний куб теж підпадає під означення.

Тепер треба думати, як це все будемо малювати. Малювати можна або на папері, або на дисплеї. Обоє з цих пристроїв двовимірні. Тому прийдеться генерувати проекцію.

Проекція точки
двовимірна точка з координатами рівними двом першим компонентам точки яка проектується. (Також доволі спрощене означення, яке задає строгу ортогональну проекцію.)
Проекція відрізка
Проекція всіх його точок. А також, очевидно, відрізок на площині, який сполучає проекції кінців нашого відрізка.

Згадавши означення куба, можна зробити правильний висновок, що ортогональна проекція будь-якого n-мірного куба на площину є квадрат. Тоді нащо скільки мороки, щоб намалювати квадрат? Круто би було мати перспективну проекцію, але як її здійснити? А, така проста на перший погляд ортогональна проекція легко стає ізометричною після здійснення повороту на 45° навколо кожної з осей. (Це для тривимірного простору, який вчать на кресленні. Для n-вимірного повороту треба давати означення).

Поворот доволі цікава штука. Означення дати не так і просто. Очевидно, що поворот – це рух. Кожен рух характеризується ступенем свободи. В повороту є тільки один ступінь свободи – кут. Крім кута, для повороту задають вісь, навколо якої обертають. Стоп! Це твердження стосується тільки тривимірного простору. На площині повертають навколо прямої, а навколо точки. Тоді навколо чого обертають точки в гіперпросторі. Логічно припустити що навколо площини. Уявили собі? Отож-бо!

А я думаю, що ми обмежуємо себе, говорячи “поворот навколо”. Насправді поворот відбувається в площині. Площина двовимірна, і з цього випливає розмірність об’єктів навколо яких ми робим поворот в різних просторах.

  1. Повороту в одновимірному просторі не існує, бо площина не влізається в одновимірний простір.
  2. В двовимірному просторі для об’єкта який задає поворот залишається 2-2=0 вимірів. 0 вимірним простором є точка. Тому в площині ми робимо поворот навколо точки.
  3. В тривимірному просторі для фіксації повороту використовують 3-2=1 одновимірний об’єкт. Одновимірна в нас пряма. Тому ми кажемо, що поворот відбувається навколо прямої. В площині ортогональній до прямої.
  4. Про чотиривиміриний простір говорити не будем, бо як ви собі уявите поворот навколо площини? Краще думати що поворот відбувається в площині.

Раз ми вже домовились, що поворот відбувається в площині, то пригадаємо, що таке поворот на площині. Для повороту на кут α нам було досить двох шкільних формул:

\begin{cases} x' = x \cos \alpha - y \sin \alpha \\ y' = x \sin \alpha + y \cos \alpha \end{cases}\ (1)

Тепер трошки ближче до означень. Знову ж таки для нашої святої простоти будемо вважати, що поворот виконується в площині паралельній якимось двом осям нашої системи координат. Правда простота не завжди означає обмеженість. Один вчений на ім’я Ейлер, колись довів, що будь-яку орієнтацію тіла в просторі можна задати кутами повороту навколо трьох осей координат. Тобто будь-який поворот навколо довільної осі можна замінити трьома поворотами навколо осей координат. Ці три кути називаються кутами Ейлера. Він звісно говорив про тривимірний простір, але немає ніяких причин думати, що його ідея буде невірною в інших просторах. Просто в n-вимірному просторі для задання орієнтації треба зробити n поворотів.

Тепер означення повороту для нас дати зовсім легко:

Поворот
Зміна двох вибраних координат точки за системою (1). (Вибір координат задає площину повороту)

Проектування

Трошки комбінаторних розрахунків. З означення випливає, що вершин у куба 2n. В тривимірного 8=23. Ребер n2n-1. В тривимірного куба 12=3*22.

Можна просто створити функцію яка за номером вершини визначає її координати. Як я вже казав в означенні, всі координати вершини рівні за модулем одиниці. Тобто кожна з них дорівнює або -1, або 1. А це не що інше, як двійкове представлення номера вершини, де замість 0 взято -1.

Добре було б аналогічно придумати функцію, яка за номером ребра повертає номери вершин, які це ребро з’єднує. Вершини з’єднані ребром відрізняються лише однією координатою. Тому, ребро можна задати як номер координати в якій відрізняються вершини ребра, і бітовий код решти вершин. Цей двійковий код вершин, які не змінюються буде займати n-1 біт на початку числа. Все решта буде номером координати яка міняється.

Схема побітових перетворень

Схема побітових перетворень

Тобто, спочатку зсувом на n вправо визначаємо x. Потім вирізаємо перших x-1 cимволів з нашого числа, вставляємо на х-тій позиції нуль, або одиничку (залежно від того яку точку ми хочемо отримати), і дописуємо до кінця решту бітів.

Код

#include <GL/glut.h>
#include <stdlib.h>
#include <math.h>

const int DIMENSIONS=5;
const int VERTEXES=1<<DIMENSIONS;
const int EDGES=DIMENSIONS*(1<<(DIMENSIONS-1));

typedef float Vertex[DIMENSIONS];

void GenVertexBin(int num, Vertex ver) // generate vertex from binary form of integer num
{
    for(int i=0;i<DIMENSIONS;i++)
    {
        ver[i]=(0.5-(num&1))*2;
        num>>=1;
    }
};

int Avertex(int num) // get number of first vertex of edge
{
    int x=num>>(DIMENSIONS-1);
    num=num%(1<<(DIMENSIONS-1));
    int res=num%(1<<x);
    num>>=x;
    num<<=x+1;
    return res|num;
}
int Bvertex(int num) // second vertex of edge
{
    int x=num>>(DIMENSIONS-1);
    return Avertex(num)|(1<<x);
}

class Ncube // n dimensional cube
{
    Vertex vertexes[VERTEXES];
public:
    void reset(void) // sets cube into orthogonal position
    {
        for(int i=0;i<VERTEXES;i++)
            GenVertexBin(i,vertexes[i]);
    }
    void rotate(int a,int b,float angle) // rotates cube inside plane ab
    {
        float x,y;
        angle*=3.1415926/180;
        for(int i=0;i<VERTEXES;i++)
        {
            x=vertexes[i][a]*cos(angle)-vertexes[i][b]*sin(angle);
            y=vertexes[i][a]*sin(angle)+vertexes[i][b]*cos(angle);
            vertexes[i][a]=x;
            vertexes[i][b]=y;
        }
    }
    void draw() // draws flat projection of cube onto xy plane
    {
        glColor3f(1,1,1);
        glBegin(GL_LINES);
            for(int i=0;i<EDGES;i++)
            {
                glVertex3f(vertexes[Avertex(i)][0],vertexes[Avertex(i)][1],0);
                glVertex3f(vertexes[Bvertex(i)][0],vertexes[Bvertex(i)][1],0);
            }
        glEnd();
    }
};

Ncube cube;

І щоб відрендерити картинку на початку цієї статті я використав таку послідовність поворотів:

    cube.reset();

    cube.rotate(0,1,45);
    cube.rotate(4,3,45);
    cube.rotate(3,2,45);
    cube.rotate(2,1,45);
    cube.rotate(0,4,45);

Written by bunyk

27 Квітня, 2009 at 07:18

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

Tagged with , ,

Нульовий проект GLUT в Code::Blocks

with 6 comments

Середовище

Середовище

Поясню спочатку свій вибір середовища Code::Blocks. На жаль в Убунті немає своєї Visual Studio. Тим не менше вибір середовища програмування широченний. Спочатку я вирішив спробувати Eclipse, так як всі казали, що це найпотужніша платформа в Убунті. Але в неї дуже багато мінусів. По-перше, вона написана на Java, тому для своєї роботи запускає ще і віртуальну машину. Як результат – жре дуже багато пам’яті і процесора. Крім того, вона перекомпільовує код зразу після збереження, хоча не розумію який в цьому сенс. Ну і звісно, компіляція працює довго. Ще одним мінусом є відсутність підказки редактора. Можливо в Eclipse і зручно щось писати, але скоріше в Яві.

А для C++ і OpenGL, Code::Blocks підходить якнайкраще. По-перше, він не написаний на Java, і працює шустріше. По-друге, компіляція і запуск відбувається по натисненні кнопки F9, що в мене викликає приємні спогади про дитинство (точка зупинки ставиться по F5). По-третє, там є підказка редактора, і майстер GLUT.

Якщо в вас все вже встановлено, то запускаємо команду Файл-Новий-Проект. Вибираємо GLUT. Далі з’являється майстер, і починає ставити питання. Коли просять вибрати розтащування GLUT напишіть “/usr“. (Звісно якщо ви встановили GLUT). Після роботи майстра з’являється код, написаний якимось Нігелем Стьюартом (Nigel Stewart). Якщо він зразу не компілюється, а виводить незрозумілу помилку Cannot find -lXxf86vm нам треба доставити пакунок libxxf86vm-dev. Рішення інших проблем можна шукати там. Коли програма запуститься ми побачимо вікно з різними геометричними об’єктами, які за традицією вертяться. Ура!

Щоправда я пропоную трохи інше привітання світу. Забравши вікно, і додавши повноекранний режим і перспективну проекцію. Режим екрану вибирається командою glutGameModeString( "1280x800:32@60" );. Тут 1280×800 – вибрана роздільна здатність, 32 – глибина кольору, 60 – частота монітора. Я ввів зручні для мене, ви виберіть свої. Після вибору режиму в нього переходять по команді glutEnterGameMode();. І звісно варто не забути повернутися в нормальний стан перед завершенням роботи програми: glutLeaveGameMode();. І щоб вертілося, я намалював щось, що гордо названо конденсатором, а є двома квадратами.

#include <GL/glut.h>
#include <stdlib.h>
#include <math.h>

static void resize(int width, int height)
{
    const float ar = (float) width / (float) height;

    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60,ar,1,100);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity() ;
}

void drawcondensator()
{
    glBegin(GL_QUADS);

        glColor3f(1,0,0);
        glNormal3d(0,0,-1);
        glVertex3d(1,1,0.1);
        glVertex3d(1,-1,0.1);
        glVertex3d(-1,-1,0.1);
        glVertex3d(-1,1,0.1);

        glColor3f(0,0,1);
        glNormal3d(0,0,1);
        glVertex3d(1,1,-0.1);
        glVertex3d(1,-1,-0.1);
        glVertex3d(-1,-1,-0.1);
        glVertex3d(-1,1,-0.1);

    glEnd();
}

static void display(void)
{
    const double t = glutGet(GLUT_ELAPSED_TIME) / 1000.0;
    const double a = t*90.0;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glColor3d(1,0,0);

    glPushMatrix();
    gluLookAt(0,0,-5,0,0,0,0,1,0);
    glRotated(a,1,1,1);
    drawcondensator();
    glPopMatrix();
    glutSwapBuffers();
}


static void key(unsigned char key, int x, int y)
{
    switch (key)
    {
        case 27 : // esc for exit
        case 'q':
            glutLeaveGameMode();
            exit(0);
            break;
    }

    glutPostRedisplay();
}

static void idle(void)
{
    glutPostRedisplay();
}

const GLfloat light_ambient[]  = { 0.0f, 0.0f, 0.0f, 1.0f };
const GLfloat light_diffuse[]  = { 1.0f, 1.0f, 1.0f, 1.0f };
const GLfloat light_specular[] = { 1.0f, 1.0f, 1.0f, 1.0f };
const GLfloat light_position[] = { 0.0f, 0.0f, 5.0f, 0.0f };

int main(int argc, char *argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
    glutGameModeString( "1280x800:32@60" ); //the settings for fullscreen mode
    glutEnterGameMode();

    glutReshapeFunc(resize);
    glutDisplayFunc(display);
    glutKeyboardFunc(key);
    glutIdleFunc(idle);

    glClearColor(0,0,0,1);

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    glEnable(GL_LIGHT0);
    glEnable(GL_NORMALIZE);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_LIGHTING);

    glLightfv(GL_LIGHT0, GL_AMBIENT,  light_ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE,  light_diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);

    glutMainLoop();

    return EXIT_SUCCESS;
}

Якщо є якісь запитання – запитуйте нижче.

Written by bunyk

10 Квітня, 2009 at 15:33

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

Tagged with , ,

Ассемблер в Visual C++

leave a comment »

Вчитися програмувати на ассемблері важко, бо щоб написати програму яка видає хоч якийсь результат (типу „Привіт світ!”) потрібно дуже багато знати. Ввід і вивід в ассемблері пов’язаний з складними функціями API, викликати які — велика морока. Крім того більшість компіляторів — консольні набори з компілятора, лінкера, дебагера, які потрібно запускати з складними параметрами командного рядка і окремо. Але є набагато простіший спосіб вивчення ассемблера, для таких чайників як і я.

Для цього не потрібно нічого, крім знайомого Visual C++. Можна навіть щось інше, навіть древній Turbo C, але там взаємодія з підпрограмами може бути інша, крім того навчитися програмувати для систем i386, можна тільки в 32 розрядних середовищах. Чим добре Visual C++? Тут не потрібно абсолютно все писати на ассемблері. Ввід і вивід можна здійснювати за допомогою зручних scanf і printf.

Досить вступів, думаю можна перейти до теорії. Часто можна почути слова x86, i386 сумісність. Це слово означає, що програма виконується на даній і всіх наступних архітектурах. Я вже згадував про архітектури до i386. В архітектурі x86 (процесор 8086), були 16 розрядні регістри ax,bx, cx, dx. В 32 розрядній архітектурі i386 їх було розширено, і до імені додали e (extended). Проте старі назви все ще залишились, для досягнення сумісності. Також, можна було отримувати доступ до окремих байтів регістру за допомогою букв h (hi – старший) l (lo – молодший). Загалом до того що міститься скажімо в регістрі eax можна було дістатись за такими іменами: eax,ax,ah,al. Ось маленька схемка:

Регістр eax/ax/ah/al.

А тепер краще не покладатись на слова, а перевірити все на практиці. Відкриваємо студію, і створюємо новий консольний проект

#include <stdlib.h> //Підключаємо стандартні бібліотеки
#include <stdio.h>

unsigned int LoWord(unsigned int a) // Підпрограма написана на ассемблері
{
	_asm // така директива каже компілятору, що після неї іде оператор на ассемблері. 
	{  //(або кілька операторів, якщо в дужках.
		mov eax,a; // Переносимо значення зі змінної а в регістр eax
		mov ebx,0; // Ініціалізуємо ebx нулем
		mov bx,ax; // Переносимо значення з ax в bx
		mov a,ebx; // Тепер записуємо значення ebx назад в а
	};
	return a; // Повертаємо результат функції
}

unsigned int LoByte(unsigned int a)
{
	_asm
	{
		mov ebx,a;
		mov eax,0; 
		mov al,bl; // Переміщуємо молодщий байт ax в bx
	}
	//Тут я забув написати return, але нічого страшного. Результат по замовчуванню береться з eax
}

int main(void)
{
	unsigned int x; // Можна і зі знаком, але давайте зробимо все простіше
	scanf("%X",&x); // Вводимо натуральне 32 розрядне число в шіснадцятковій системі
	printf("\n Lo word (ax): %X\n",LoWord(x)); // і виводимо молодше слово числа
	printf("\n Lo byte (al): %X\n",LoByte(x)); // і виводимо молодше слово числа
	system("pause"); // Почекати поки користувач побачить результат
	return 0;
}

Приклад, як для привітання зі світом, трохи заскладний, зате ілюстративний. Використовується шістнадцядкова система, тому для заповнення всіх 32 бітів, потрібно ввести 8 цифр. Програма занесе їх в eax, і виведе вміст ax і al.

От і все. Розберусь більше — напишу більше.

Written by bunyk

18 Березня, 2009 at 22:39

Опубліковано в Кодерство

Tagged with ,

Клас – многочлен

with 3 comments

Маленький клас на C++, який задає тип даних – многочлен, і основні оператори над цим типом. Створений як елемент лаболаторної, тому функціональність не повна. В пам’яті представляється списком одночленів відсортованих за спаданням. Тестувався з Microsoft Visual C++ 2008.

Структура tmonomial задає структуру одночлена. float k – коефіцієнт біля ікса, int pow – степінь ікса.
struct smonomial *next; – посилання на наступний одночлен многочлена.

Клас tpolynomial задає многочлен. В нього є тільки одне захищене поле – head – посилання на список одночленів. Конструктор надає йому нульового значення.

Методи

void addmonomial(float k,int pow) – додає до многочлена одночлен, з коефіцієнтом k і степінню ікса pow. Все одразу скорочується і відсортовується.
void print() – виводить многочлен на консоль в форматі подібному на 2.23x4+1.45x2-7.20x1.
void sscan(char *str) – читає многочлен з рядка такого ж формату.
int maxpow() – максимальна степінь многочлену, або порядок іншими словами
void clear() – обнулити многочлен
void sscan(char *str) – читає многочлен з рядка такого ж формату.

Оператори

+,-,*,/,=,+=,-=,*= – як звичайно, а крім того є *= на тип одночлен, і на число з плаваючою крапкою.

typedef struct smonomial
{
	float k;
	int pow;
	struct smonomial *next;
} tmonomial;
typedef tmonomial *pmonomial;
class tpolynomial
{
protected:
	pmonomial head;
public:
	tpolynomial()
	{
		head=NULL;
	}
	void addmonomial(float k,int pow)
	{
		if(!k) return;
		if(!head)
		{
			head = new tmonomial;
			head-&gt;k=k;
			head-&gt;pow=pow;
			head-&gt;next=NULL;
			return ;
		}
		else
		{
			pmonomial cur=head,curpr;
			pmonomial ins;
			if(cur-&gt;pow==pow)
			{
				cur-&gt;k+=k;
				return; 
			}
			if(cur-&gt;pow&lt;pow) // the highest power
			{
				ins = new tmonomial;
				ins-&gt;k=k;
				ins-&gt;pow=pow;
				ins-&gt;next=cur;
				head=ins;
				return;
			}
			while(cur-&gt;next)
			{
				curpr=cur;
				cur=cur-&gt;next;
				if(cur-&gt;pow==pow) // equal power
				{
					cur-&gt;k+=k;
					return; 
				}
				if((cur-&gt;pow&lt;pow)&amp;&amp;(curpr-&gt;pow&gt;pow))
				{
					//insert between two cursors
					ins= new tmonomial;
					ins-&gt;next=cur;
					ins-&gt;k=k;
					ins-&gt;pow=pow;
					curpr-&gt;next=ins;
					return;
				}
			}
			// else the lowest power
			ins=new tmonomial;
			ins-&gt;k=k;
			ins-&gt;pow=pow;
			ins-&gt;next=NULL;
			cur-&gt;next=ins;
		}
	}
	void print()
	{
		pmonomial cur=head;
		if(!cur) 
		{
			printf(&quot;0x0&quot;);
			return;
		}
		while(1)
		{
			printf(&quot;%.2fx%d&quot;,cur-&gt;k,cur-&gt;pow);
			if(cur-&gt;next)
			{
				if (cur-&gt;next-&gt;k&gt;=0) printf(&quot;+&quot;);
			}
			else
				return;
			cur=cur-&gt;next;
		}
	}
	void sscan(char *str)
	{
		int p;
		float k;
		char extend[1000];
		char *ep=extend;
		while(*str)
		{
			if(*str=='-')
			{
				*ep='+';
				ep++;
				*ep='-';
			}
			else
			{
				*ep=*str;
			}
			str++;
			ep++;
		}
		*ep='';
		str=extend;
		while(*str)
		{
			sscanf(str,&quot;%fx%d&quot;,&amp;k,&amp;p);
			addmonomial(k,p);
			while((*str)&amp;&amp;(*str!='+'))
			{
				str++;
			}
			if(*str=='+') str++;
		}
	}
	int maxpow()
	{
		if(head)
		{
			if(head-&gt;k!=0)
				return head-&gt;pow;
			else
			{
				pmonomial cur=head;
				while((cur)&amp;&amp;(cur-&gt;k==0))
				{
					cur=cur-&gt;next;
				}
				if(cur)
					return cur-&gt;pow;
				else 
					return -1;
			}
		}
		else return -1;
	}
	void clear()
	{
		pmonomial cur=head;
		pmonomial del;
		while(cur)
		{
			del=cur;
			cur=cur-&gt;next;
			delete del;
		}
		head=NULL;
	}
	tpolynomial operator=(tpolynomial op)
	{
		clear();
		pmonomial cur=op.head;
		while(cur)
		{
			addmonomial(cur-&gt;k,cur-&gt;pow);
			cur=cur-&gt;next;
		}
		return *this;
	}
	tpolynomial operator+=(tpolynomial op2)
	{
		pmonomial cur=op2.head;
		while(cur)
		{
			addmonomial(cur-&gt;k,cur-&gt;pow);
			cur=cur-&gt;next;
		}
		return *this;
	}
	tpolynomial operator-=(tpolynomial op2)
	{
		pmonomial cur=op2.head;
		while(cur)
		{
			addmonomial(-cur-&gt;k,cur-&gt;pow);
			cur=cur-&gt;next;
		}
		return *this;
	}
	tpolynomial operator+(tpolynomial op2)
	{
		tpolynomial res;
		res=*this;
		res+=op2;
		return res;
	}
	tpolynomial operator-(tpolynomial op2)
	{
		tpolynomial res;
		res=*this;
		res-=op2;
		return res;
	}
	tpolynomial operator*=(float sc)
	{
		pmonomial cur=head;
		while(cur)
		{
			cur-&gt;k*=sc;
			cur=cur-&gt;next;
		}
		return *this;
	}
	tpolynomial operator*=(tmonomial mul)
	{
		pmonomial cur=head;
		while(cur)
		{
			cur-&gt;k*=mul.k;
			cur-&gt;pow+=mul.pow;
			cur=cur-&gt;next;
		}
		return *this;	
	}
	tpolynomial operator*(tmonomial mul)
	{
		tpolynomial res;
		res=*this;
		res*=mul;
		return res;
	}
	tpolynomial operator*=(tpolynomial op2)
	{
		pmonomial cur=op2.head;
		tpolynomial res;
		while(cur)
		{
			res+=*this*(*cur);
			cur=cur-&gt;next;
		}
		*this=res;
		return res;
	}
	tpolynomial operator*(tpolynomial op2)
	{
		tpolynomial res;
		res=*this;
		res*=op2;
		return res;
	}
	tpolynomial operator/(tpolynomial op2)
	{
		tpolynomial res;
		tpolynomial temp=*this;
		tmonomial t;
		if(temp.maxpow()&lt;op2.maxpow()) return res;
		while(temp.maxpow()&gt;=op2.maxpow())
		{
			t.k=temp.head-&gt;k/op2.head-&gt;k;
			t.pow=temp.head-&gt;pow-op2.head-&gt;pow;
			res.addmonomial(t.k,t.pow);
			temp=temp-op2*t;
		}
		return res;
	}

};

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

Written by bunyk

22 Лютого, 2009 at 20:55

Опубліковано в Кодерство

Tagged with ,