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

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

Posts Tagged ‘C++

Stm32 Nucleo – вхідні сигнали і комунікація з компю’тером

with 10 comments

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

Якихось чітких інструкцій в інтернеті я не знайшов, зате в IDE було аж два демо проекти про кнопку:

  • “Read the user button state on the Nucleo board.”
  • “Read the user button using external interrupt.”

Код там досить простий, але я його ще спростив ось так:

#include "mbed.h"
 
DigitalIn mybutton(USER_BUTTON);
DigitalOut myled(LED1);
 
int main() {
  while(1) {
    myled = mybutton;
  }
}

Прочитати решту цього запису »

Written by bunyk

Лютий 21, 2015 at 15:04

Оприлюднено в Інструменти, Кодерство, Конспекти

Tagged with ,

Привіт “ядерному мікроконтролеру” ;)

with 3 comments

Вкотре переконуюсь що якщо чогось дуже хочеш – то отримаєш. Так от я хотів якось спробувати скласти гірлянду якою можна буде керувати з комп’ютера, трохи думав про Arduino, і недавно мені в руки для тестування потрапила плата NUCLEO-F411RE від компанії STMicroelectronics. Все завдяки автору сайту embedded.co.ua, Василю Йосипенку, якому за це величезне дякую.

nucleo

Почну з того що на платі надруковане посилання: www.st.com/stm32nucleo. І наклеєна наклейка NUCLEO-F411RE. З діаграми на сайті видно що F411 це найшвидша плата, яка має найбільший розмір флеш-пам’яті – 512K. Аж пів метра!

Далі я звісно перейшов на сторінку плати і почав RTFM. Ось інструкція.
Прочитати решту цього запису »

Written by bunyk

Лютий 18, 2015 at 00:39

Перший український технотриллер

with 15 comments


Технотриллер – це гібрид наукової фантастики і шпигунсько-військового роману а-ля Віктор Суворов. Це не кіберпанк. Кіберпанк – це фантастика на тему суспільства в якому маємо не одного кібера, і вони там всім заправляють. 🙂

Я почну з мінусів, але ви не переживайте, книжка – те що треба. Може бути й краще, але й так непогано.

В мене цієї неділі відбувся приблизно такий діалог з філологом:

– Добре що хоч програмісти не пишуть книжки.
– Пишуть, в мене зараз в рюкзаку якраз є одна. Хочеш покажу?
– Правда? Давай.
– Дивись, тут є код на C++, формула Мандельброта і 3d-моделі на ілюстраціях.
– Пахне третьосортною літературою… О, я вже знайшла три помилки!

Так, книжку писав не філолог. А редактори напевне побоялись чіпати, бо там своєрідна термінологія і лексика.

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

Written by bunyk

Жовтень 6, 2012 at 01:57

Оприлюднено в Нещоденник

Tagged with ,

Прочитати матрицю з файлу

with 22 comments

Думаєте просто? І я так думав.

Таки зробив, не знаю чи найкращим чином, але добре що добрі люди допомогли.

С++ STL

#include <vector> //for datatypes
#include <sstream> //for istringstream 
#include <fstream> // for ifstream
#include <iostream> // for cerr, cout
#include <string> // for string type
using namespace std;

vector<int> readRow(string row)
{
	vector<int> retval;
	istringstream is;
	is.str(row);
	int num;
	while (is >> num) retval.push_back(num);
	return retval;
}

vector< vector<int> > readVector(istream &is) 
{
	string line;
	vector<vector<int> > retval;
	while (getline(is, line))
		retval.push_back(readRow(line));
	return retval;
}

int main()
{
	ifstream infile("test.txt");
	if(!infile){
		cerr << "Error! Cant open file test.txt";
		return -1;
	}
	vector< vector<int> > data = readVector(infile);
	int rows = data.size();
	int cols = data[0].size();
	int i,j;
	for(i=0;i<rows;i++)
	{
		for(j=0;j<cols;j++)
		{
			cout << data[i][j] << " ";
		}
		cout << endl;
	}
}

Так це я такий, чи це C++ такий?

C++ без STL, масив має максимальний розмір

#include <sstream> //for istringstream 
#include <fstream> // for ifstream
#include <iostream> // for cerr, cout
#include <string> // for string type
using namespace std;

const int maxwidth=1000;
const int maxheight=100;
int x=0,y=0;
int width,height;
int array[maxwidth][maxheight];

void readRow(string row)
{
	istringstream is;
	is.str(row);
	x = 0;
	int num;
	while (is >> num) {
		array[x][y] = num;
		x++;}
	width=x;
}

void readVector(istream &is) 
{
	string line;
	y=0;
	while (getline(is, line)){
		readRow(line);
		y++;
		}
	height = y;
}

int main()
{
	ifstream infile("test.txt");
	if(!infile){
		cerr << "Error! Cant open file test.txt";
		return -1;
	}
	readVector(infile);
	int i,j;
	for(i=0;i<height;i++)
	{
		for(j=0;j<width;j++)
		{
			cout << array[j][i] << " ";
		}
		cout << endl;
	}
}

Python

f = open("test.txt")
lns = f.readlines()
nums = map(lambda x: x[:-1].split(" "),lns)
for i in nums:
    for j in i:
        print float(j)+0.1,
    print

# OUT: 1.1 2.1 3.1 4.1 5.1
# OUT: 6.1 7.1 8.1 9.1 0.1
# OUT: 9.1 8.1 7.1 6.1 5.1

Порівняйте розміри коду, враховуючи те, що в C++ використовується той же stl, і я не забув using namespace std; , як дехто робить щоб загадити код префіксами.

Знаєте як краще, чи маєте питання – пишіть нижче.

Written by bunyk

Лютий 22, 2011 at 00:17

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

Tagged with ,

Лінкер для чайників

with 4 comments

50 хвилинна лекція через jabber

pidginЗазвичай хороші лекції виходять при вузькій цільовій аудиторії. Якщо ця аудиторія – одна людина – то лекція стає майже ідеальна. Цікаво перевірити, чи ще хтось крім зможе розібратись з принципами компіляції багатомодульних програм на C++ за допомогою пояснення Улідтки? Тоді його витрачена година буде витрачена з більшою користю. Хоча текст концентрований. Якщо справитесь менш ніж за 5 хв, розкажіть, як враження.

(17:59:55) bunyk: А я от не знаю що прочитати, щоб зрозуміти що таке лінкер.
(18:00:31) Улідтко: дивись
(18:00:38) Улідтко: у тебе є main.c
(18:00:52) Улідтко: який робить #include "foo.h"
(18:01:11) Улідтко: відповідно, є окремі foo.c і foo.h
(18:01:56) Улідтко: в foo.h визначені деякі інтерфейсні типи даних та функція func, що працює з ними
(18:02:29) Улідтко: (як ми знаємо, в .h заголовках функції лише декларуються, коду там немає)
(18:02:45) Улідтко: власне код функції описується в foo.c
(18:02:58) bunyk: ну…
(18:03:11) Улідтко: Подумаєм, як проходить процес компіляції
(18:04:17) Улідтко: gcc foo.c -o foo.o дасть нам "об’єктний файл" foo.o
(18:04:32) Улідтко: що таке об’єктний файл?
(18:04:49) Улідтко: це файл, в якому зберігається машинний код функції func
(18:05:11) Улідтко: все, лише самі інструкції
(18:05:21) Улідтко: платформно-залежні і все таке
(18:05:56) Улідтко: для його генерації компілятор використовував метадані із foo.h і код із foo.c
(18:06:22) Улідтко: далі — як компілюється main.c?
(18:06:40) bunyk: gcc main.c main -lfoo
(18:06:43) bunyk: ?
(18:06:50) Улідтко: %)
(18:06:51) Улідтко: ні
(18:07:16) Улідтко: отримаєш якийсь різновид file not found
(18:07:45) Улідтко: зрозуміло, що gcc main.c -o main не піде
(18:07:47) Улідтко: чому?
(18:08:11) bunyk: бо нема коду func
(18:08:38) Улідтко: правильно, хоча власне компілятору це не заважає
(18:09:00) Улідтко: він знає, які у func аргументи і тип результату
(18:09:10) Улідтко: (із foo.h)
(18:09:49) Улідтко: і вільно перетворює виклики func у машинний код
(18:10:51) Улідтко: він може це робити, бо у .o файлах конкретні адреси (функцій, змінних) не використовуються
(18:11:10) Улідтко: використовуються символічні імена, references
(18:11:41) Улідтко: таким чином ми можемо скомпілювати_ main.c в main.o
(18:12:12) Улідтко: але воно не є придатним для виконання
(18:12:23) Улідтко: бо нема конкретних адрес, які потрібні процесору
(18:12:35) Улідтко: от в цьому і полягає задача лінкера
(18:13:43) Улідтко: він повинен сумістити main.o і foo.o в один виконуваний файл, склавши все це в купу і назначивши усім символічним іменам конкретні адреси
(18:14:20) Улідтко: і позамінявши усі звернення до цих символічних імен на їх конкретні адреси
(18:15:28) Улідтко: тобто, в коді функції main виклики call func будуть перетворені в call 0x04053a8, наприклад
(18:15:37) Улідтко: (в машинному коді)
(18:16:04) Улідтко: це все уже буде придатним до виконання на процесорі
(18:16:41) Улідтко: Прикол в тому, що gcc насправді для полегшення нашого життя викликає лінкера самотужки
(18:17:11) Улідтко: (а також препроцесора, асемблера та інші штуки)
(18:17:35) Улідтко: від чого, власне, і вийде помилка при спробі gcc main.c -o main
(18:17:47) Улідтко: воно скаже undefined reference
(18:18:18) Улідтко: бо ніякої конкретної адреси для func не знайдеться
(18:18:43) Улідтко: От це все називається статичним лінкуванням.
(18:19:13) Улідтко: весь код включається в результатуючий виконуваний файл
(18:20:31) bunyk: так, як таки його відкомпілювати
(18:20:49) Улідтко: gcc main.c foo.c -o main 🙂
(18:21:08) bunyk: ага. 🙂
(18:21:11) Улідтко: це найзручніший і найнеочевидніший спосіб, коротше 🙂
(18:21:38) Улідтко: якщо змусити gcc бути лише компілятором, то сценарій був би приблизно таким:
(18:21:50) Улідтко: gcc foo.c -o foo.o
(18:21:56) Улідтко: gcc main.c -o main.o
(18:22:16) Улідтко: ld foo.o main.o
(18:22:37) Улідтко: ну, і перейменувати готовий a.out
(18:23:40) bunyk: а -lGL що значить? Шукати об’єктний код десь в /usr/lib ?
(18:23:59) Улідтко: так, чекай
(18:24:05) Улідтко: це лише одна модель лінкування
(18:24:08) Улідтко: статичне
(18:24:45) Улідтко: Припустимо далі, шо ти використовуєш якусь велику класну бібліотеку, типу OpenGL
(18:25:31) Улідтко: якшо ти будеш лінкувати статично
(18:25:44) Улідтко: то весь код цієї бібліотеки потрабить до твого бінарника
(18:26:14) Улідтко: далі, хтось інший пише іншу прожку, яка теж використовує OpenGL
(18:26:32) Улідтко: або ти пишеш другу прожку, яка теж використовує OpenGL
(18:26:56) Улідтко: і, якщо лінкувати далі статично, то код бібліотеки потрапляє і туди, і туди, і туди.
(18:27:09) Улідтко: це викликає тучу проблем
(18:27:20) Улідтко: очевидно, що бінарний код бібліотеки дублюється
(18:27:24) bunyk: а це щось типу dll?
(18:27:34) Улідтко: витрачаючи різні види ресурсів
(18:28:40) Улідтко: … і ускладнюючи підтримку софта (обновлення бібліотеки не будуть використовуватися прожками, поки вони не будуть перекомпільовані)
(18:29:04) Улідтко: от для цього і є динамічне лінкування
(18:29:23) Улідтко: так, dll — це dynamic linking library
(18:31:09) Улідтко: воно заключається в тому, що твоя прожка має в бінарному вигляді має так звану таблицю імпорту
(18:31:33) Улідтко: це куча змінних
(18:31:38) Улідтко: вказівників
(18:31:55) Улідтко: а саме, вказівників на функції
(18:32:18) Улідтко: на функції з тих самих dll’ок
(18:33:44) Улідтко: при запуску такої прожки операційна система знаходить і завантажує усі потрібні dll’ки, а потім заносить у цю саму таблицю імпорту всі вказівники на реальні функції з бібліотек
(18:35:33) Улідтко: знову ж таки, власне компілятора не хвилює де саме будуть розміщені коди функцій
(18:35:50) Улідтко: хоча, насправді, ні, хвилює 🙂
(18:36:16) Улідтко: синтаксично функції позначаються як імпортовані із dll
(18:36:49) Улідтко: а компілятор замість call func генерує інструкціє вигляду call [_import_table_entry_for_func]
(18:37:33) Улідтко: тобто, адреса для call витягується із пам’яті за адресою _import_table_entry_for_func
(18:38:00) Улідтко: _import_table_entry_for_func — це, ефективно, змінна-вказівник
(18:38:35) Улідтко: знову ж таки, компілятор не знає (і не має знати) точної, конкретної адреси цього вказівника
(18:38:57) Улідтко: він звертається до нього символічно
(18:39:32) Улідтко: а задача лінкера в цьому випадку — окрім іншого, сформувати таблицю імпорту
(18:40:59) Улідтко: розмістити, як і раніше, змінні-вказівники, і на додаток до цього, надати інформацію, потрібну для операційної системи для заватнаження dll’ок
(18:41:50) Улідтко: *.dll під юніксами мають імена lib*.so
(18:42:15) bunyk: я знав! /usr/lib/ 🙂
(18:42:22) Улідтко: угу
(18:43:11) Улідтко: але в /usr/lib є два набори файлів: lib*.so та lib*.a
(18:44:18) Улідтко: .a — це просто архіви .o, тобто це static link library
(18:45:49) Улідтко: max@ulidtko:~$ ls /usr/lib/libGL.so
(18:46:19) Улідтко: відповідно, щоб скомпілювати прожку із використанням OpenGL, ти маєш сказати
(18:46:30) Улідтко: gcc main.c -lGL
(18:47:26) Улідтко: щоб лінкер, викликаний gcc, прикрутив усі gl* функції до динамічної бібліотеки libGL.so
(18:47:38) bunyk: Ооо! Тепер ясно.
(18:48:31) bunyk: Дякую.

Written by bunyk

Вересень 14, 2009 at 15:31

Оприлюднено в Інструменти, Кодерство

Tagged with ,

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

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 , ,