Задача: спроектировать трёхмерную модель бриллианта и предоставить пользователю параметры настройки проекции; Данная программа считвает из файла координаты трёхмерной фигуры и проецирует её на экран с возможностью изменения проекции. Для построения бриллианта я использовал пропорции Далее исходя из пропорций бриллианта я принял 1% за 1 деление моей трёхмерной системы координат, и сразу же получил несколько координат точек. Далее, зная радуис описаной окружности правильного многоугольника и угол между осью координат и радиусом, проходящего через 1у из точек многоугольника по теореме косинусов можно найти проекцию на эту ось, соответственно координату. В случае со 2ым уровнем с радиусом пришлось погадать, а в 4ом уровне его находить пришлось используя уравнения прямой, т. е. я находил точки лежащие на прямой от Шипа до одной из точек 3его уровня. Перейдём к алгоритму отображения фигуры. Последовательность действий: Разберём подробно каждый пункт. 1. У нас имеется главная структура, которая имеет указатели на массив структур точек и массив структур граней. Каждая структура грани имеет массив указателей на указатели на точки, из которых состоит эта грань. Панель с кнопками, флажками, метками и полями ввода делается в gtk достаточно просто и алгоритм её построения понятно изъяснён в коментариях кода: #include <stdlib.h> #include <stdio.h> #include <gtk/gtk.h> #include <math.h> #include <string.h> #define c -80 typedef struct point point; typedef struct edge edge; typedef struct manager manager; struct point //структура с координатами точки { double ax, ay, az; int number; }; struct edge //структура грани, которая содержит кол-во точек, видимость и указатель на указатель структуры с координатами { int count; gboolean vis; point **np; }; struct manager //главная структура, сожержащая в себе виджет, угол поворота, указатели на точки и грани, кол-ва точек и граней { GtkWidget *wd; float angle; point *pt; edge *ed; int edcount; int ptcount; }; //------------------------------------------------ //глобальные переменные, изменяемые пользователем float glmas, glx, gly, glz; //маштаб, координаты вектора, вокруг которого вращается фигура gboolean glf, glp; //флажки заливки и перспективы //------------------------------------------------ void callback( GtkWidget *widget, gpointer data ) //функция обратного вызова, которая вызывается при каждом нажатии на кнопку или флажок { switch ((int)data) { case '+' : glmas+=0.2; break; //увеличиваем фигуру case '-' : glmas-=0.2; break; //уменьшеаем фигуру case 'f' : if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) glf=TRUE; //если флажок активен то заливаем else glf=FALSE; //в ином случае не заливаем break; case 'p' : if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) glp=TRUE; //аналогично с перспективой else glp=FALSE; break; default : break; } } void osx_callback (GtkWidget *widget, GtkWidget *osi) //функция обратного вызова, которая вызывается при каждом изменении координаты Х вектора вращения { const gchar *entry_text; entry_text = gtk_entry_get_text (GTK_ENTRY (osi)); //получаем текст с поля ввода float x=*entry_text - 48; //конвертируем данные в число if (gly==0 && glz==0 && x==0.000000) return; //предотвращаем случай с вектором (0,0,0) иначе при нормировании вектора получим деление на 0 else glx=x; g_print ("New vector of rotating:\n%f %f %f\n", glx, gly, glz); } void osy_callback (GtkWidget *widget, GtkWidget *osi) //аналогично { const gchar *entry_text; entry_text = gtk_entry_get_text (GTK_ENTRY (osi)); float y=*entry_text - 48; if (glx==0 && glz==0 && y==0.000000) return; else gly=y; g_print ("New vector of rotating:\n%f %f %f\n", glx, gly, glz); } void osz_callback (GtkWidget *widget, GtkWidget *osi) { const gchar *entry_text; entry_text = gtk_entry_get_text (GTK_ENTRY (osi)); float z=*entry_text - 48; if (glx==0 && gly==0 && z==0.000000) return; else glz=z; g_print ("New vector of rotating:\n%f %f %f\n", glx, gly, glz); } manager *InitData() //инициализирование файла с координатами фигуры { manager *mr = NULL; FILE *f = NULL; if ((f=fopen("coordinates.txt", "r"))==NULL) { g_print("Ошибка открытия файла!"); return mr; } gint i, j; mr=(manager *)malloc(sizeof(manager)); //выделяем память для главной структуры fscanf(f, "%d %d", &mr->ptcount, &mr->edcount);//считываем кол-во точек и играней mr->ed=(edge *)malloc(mr->edcount*sizeof(edge));//выделяем память для массива структур с гранями mr->pt=(point *)malloc(mr->ptcount*sizeof(point));//выделяем память для массива структур с точками for(i=0; i<mr->ptcount; i++) //проходимся по координатам точек и записываем их в массив структур с точками fscanf(f, "%lf %lf %lf", &mr->pt[i].ax, &mr->pt[i].ay, &mr->pt[i].az); int t; for(i=0; i<mr->edcount; i++) //проходимся по граням { fscanf(f, "%d", &mr->ed[i].count); //считываем кол-во точек в этой грани mr->ed[i].np=(point **)malloc(mr->ed[i].count*sizeof(point *)); //выделяем необходимую память для указателей на точки for(j=0; j<mr->ed[i].count; j++) //проходимся по номерам точек и записываем соответствующие указатели на них { fscanf(f, "%d", &t); mr->ed[i].np[j]=&mr->pt[t]; mr->pt[t].number=t; } } fclose(f); return mr; } void turn(float x,float y,float z, manager *m) //вращение фигуры { double d=sqrt((float)x*x + y*y + z*z); //вычисляем длину вектора вращения x=x/d; // y=y/d; // нормируем его z=z/d; // double MATR[4][4]; //транспонированая матрица для поворота фигуры вокруг заданого вектора <a href="http://www.devmaster.net/wiki/images/math/fe382245e2210c46cae61f32dbb27305.png " title="http://www.devmaster.net/wiki/images/math/fe382245e2210c46cae61f32dbb27305.png ">http://www.devmaster.net/wiki/images/math/fe382245e2210c46cae61f32dbb273...</a> MATR[0][0] = (1-cos(m->angle))*(x*x-1)+1; MATR[0][1] = sin(m->angle)*z+(1-cos(m->angle))*x*y; MATR[0][2] = (1-cos(m->angle))*x*z-sin(m->angle)*y; MATR[1][0] = (1-cos(m->angle))*x*y-sin(m->angle)*z; MATR[1][1] = (1-cos(m->angle))*(y*y-1)+1; MATR[1][2] = (1-cos(m->angle))*y*z+sin(m->angle)*x; MATR[2][0] = (1-cos(m->angle))*x*z+sin(m->angle)*y; MATR[2][1] = (1-cos(m->angle))*y*z-sin(m->angle)*x; MATR[2][2] = (1-cos(m->angle))*(z*z-1)+1; MATR[3][3] = 1.0; MATR[1][3] = 0; MATR[2][3] = 0; MATR[3][1] = 0; MATR[3][2] = 0; MATR[0][3] = 0; gint i; double vec[4]; for(i=0; i<m->ptcount; i++) //проходимся по всем точкам фигуры { vec[0]=m->pt[i].ax; vec[1]=m->pt[i].ay; vec[2]=m->pt[i].az; vec[3]=1; float np[4]; gint j,n; for (j = 0; j < 4; j++) { double Sum = 0; for (n = 0; n < 4; n++) { Sum += vec[n] * MATR[n][j]; //каждую точку поворачиваем с помощью матрицы поворота } np[j] = Sum; } m->pt[i].ax=np[0]; m->pt[i].ay=np[1]; m->pt[i].az=np[2]; } } void CheckVisibility(manager *m) //проверка видимости { double x1, x2, x3, y1, y2, y3, p; gint i; for(i=0; i < m->edcount; i++) { if (glp==TRUE) p = 1 -m->ed[i].np[0]->az/c; else p=1; //проверка включённой/выключеной перспективы x1 = m->ed[i].np[0]->ax/p; y1 = m->ed[i].np[0]->ay/p; if (glp==TRUE) p = 1 -m->ed[i].np[1]->az/c; else p=1; x2 = m->ed[i].np[1]->ax/p; y2 = m->ed[i].np[1]->ay/p; if (glp==TRUE) p = 1 -m->ed[i].np[2]->az/c; else p=1; x3 = m->ed[i].np[2]->ax/p; y3 = m->ed[i].np[2]->ay/p; if ( ( ((x2-x1)*(y3-y1) - (y2-y1)*(x3-x1) )*c ) > 0 ) m->ed[i].vis = TRUE; //проверка знака скалярного произведения вектора камеры и вектора нормали грани (вектор нормали находится путём векторного произведения 2ух векторов грани) else m->ed[i].vis = FALSE; } } gint draw(GtkWidget *wid, GdkEventExpose *event, gpointer data) //рисование фигуры { manager *m=(manager*)data; gint cx=300; //координаты центра рисования gint cy=300; gint i, n; double p; double x, y; double dashes[] = {20.0}; cairo_t *cr=gdk_cairo_create(m->wd->window); //инициализация cairo for(n=0; n < m->edcount; n++) //проходися по граням { if (m->ed[n].vis == FALSE) //если грань невидима { cairo_set_line_width(cr, 1); //то устанавливаем толщину линии 1 cairo_set_dash(cr, dashes, 1, 0); //и активируем пунктир } else //иначе { cairo_set_dash(cr, dashes, 0, 0); //убираем пунктир cairo_set_line_width(cr, 2); //ставим толщину линий 2 } if (glp==TRUE) p = 1 -m->ed[n].np[0]->az/c; else p=1; //проверка наличия перспективы x = (int)(cx + glmas*m->ed[n].np[0]->ax/p); //проекция точек на экран с учётом масштаба, центра и перспективы y = (int)(cy + glmas*m->ed[n].np[0]->ay/p); cairo_move_to(cr, x, y); //начинаем нанесение маски for (i=1; i<m->ed[n].count; i++)//проходимся по точкам грани { if (glp==TRUE) p = (1 -m->ed[n].np[i]->az/c); else p=1; x = (int)(cx + glmas*m->ed[n].np[i]->ax/p); y = (int)(cy + glmas*m->ed[n].np[i]->ay/p); cairo_line_to(cr, x, y); //двигаем кисть } if (glp==TRUE) p = 1 -m->ed[n].np[0]->az/c; else p=1; x = (int)(cx + glmas*m->ed[n].np[0]->ax/p); y = (int)(cy + glmas*m->ed[n].np[0]->ay/p); cairo_line_to(cr, x, y); //заканчиваем нанесение маски cairo_set_source_rgb(cr, 0, 0, 0); //устанавливаем чёрный цвет if (glf==TRUE) //если заливка включена { cairo_stroke_preserve(cr); //наносим контур грани сохраняя маску cairo_set_source_rgba(cr, 0.2, 0.4, 0.8, 0.2); //устанавливаем цвет заливки cairo_fill(cr); //заливаем } else cairo_stroke(cr); //иначе рисуем контур сбрасывая маску } cairo_destroy(cr);//деиницализация cairo return 1; } void check(manager *m) //функция проверки структур с точками и гранями { g_print("Points: %d Edges: %d\n", m->ptcount, m->edcount); gint i, j; for(i=0; i<m->ptcount; i++) g_print("%f %f %f\n", m->pt[i].ax, m->pt[i].ay, m->pt[i].az); for(i=0; i<m->edcount; i++) { g_print("%d : ", m->ed[i].count); for(j=0; j<m->ed[i].count; j++) g_print(" %d ", m->ed[i].np[j]->number); g_print("\n"); } } gint HOST(gpointer data) // главная функция, вызываемая таймером { manager *m=(manager *)data; //получаем главную структуру turn(glx, gly, glz, m); //поворачиваем фигуру CheckVisibility(m); //проверяем видимость gtk_widget_queue_draw(m->wd); //рисуем return 1; } int main (int argc, char *argv[]) { GtkWidget *wid; //окно GtkWidget *area; //область рисования GtkWidget *table; //таблица GtkWidget *button; //кнопка GtkWidget *flag; //флажок GtkWidget *label; //надпись GtkWidget *osi; //поле ввода /* Initialize GTK+ */ g_log_set_handler ("Gtk", G_LOG_LEVEL_WARNING, (GLogFunc) gtk_false, NULL); gtk_init (&argc, &argv); g_log_set_handler ("Gtk", G_LOG_LEVEL_WARNING, g_log_default_handler, NULL); manager *m; m=InitData(); //инициализируем данные if (m == NULL) return 1; m->angle=M_PI/180; //устанавливаем угол вражения 1 градус /* Create the main window */ wid = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (wid), "3D"); gtk_container_set_border_width (GTK_CONTAINER (wid), 0); gtk_window_set_position (GTK_WINDOW (wid), GTK_WIN_POS_CENTER); //располагаем окно по центру gtk_widget_set_size_request(wid, 800, 600); //устанавливаем размер окна gtk_drawing_area_size(area, 600, 600); //размер области рисования gtk_widget_realize (wid); table = gtk_table_new (24, 12, TRUE); //создаём таблицу 24х12 gtk_container_add (GTK_CONTAINER (wid), table); //добавляем таблицу в контейнер area = gtk_drawing_area_new(); //инициализируем область рисования gtk_table_attach_defaults (GTK_TABLE (table), area, 0, 9, 0, 24); //размещаем область рисования в таблице gtk_widget_show (area); //------------------ //ПАНЕЛЬ УПРАВЛЕНИЯ //------------------ button = gtk_button_new_with_label ("Увеличить"); //инициализируем кнопочку увеличения g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (callback), (gpointer)'+'); //подключаем сигнал нажатия на кнопку, при активации передаём символ '+' gtk_table_attach_defaults (GTK_TABLE (table), button, 9, 12, 0, 2); //размещаем кнопку в таблице gtk_widget_show (button); button = gtk_button_new_with_label ("Уменьшить");//аналогично g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (callback), (gpointer)'-'); gtk_table_attach_defaults (GTK_TABLE (table), button, 9, 12, 2, 4); gtk_widget_show (button); flag = gtk_check_button_new_with_label ( "Заливка" ); gtk_toggle_button_set_active( flag, TRUE );//устанавливаем флажог в активное состояние g_signal_connect (G_OBJECT (flag), "clicked", G_CALLBACK (callback), (gpointer)'f'); gtk_table_attach_defaults (GTK_TABLE (table), flag, 9, 12, 5, 6); gtk_widget_show (flag); flag = gtk_check_button_new_with_label ( "Перспектива" ); gtk_toggle_button_set_active( flag, TRUE ); g_signal_connect (G_OBJECT (flag), "clicked", G_CALLBACK (callback), (gpointer)'p'); gtk_table_attach_defaults (GTK_TABLE (table), flag, 9, 12, 6, 7); gtk_widget_show (flag); label=gtk_label_new("Вектор вращения (x, y, z)"); //создаём надпись gtk_table_attach_defaults (GTK_TABLE (table), label, 9, 12, 8, 9); gtk_widget_show (label); osi = gtk_entry_new(); //инициализируем поле ввода координаты X gtk_entry_set_max_length (GTK_ENTRY (osi), 1); //устанавливаем максимальное количество введёных символов 1 g_signal_connect (G_OBJECT (osi), "activate", G_CALLBACK (osx_callback), (gpointer) osi); //подключаем сигнал активирования поля ввода gtk_entry_set_text (GTK_ENTRY (osi), "1"); //устанавливаем текст по умолчанию "1" gtk_table_attach_defaults (GTK_TABLE (table), osi, 9, 10, 9, 10); gtk_widget_show (osi); osi = gtk_entry_new(); //аналогично gtk_entry_set_max_length (GTK_ENTRY (osi), 1); g_signal_connect (G_OBJECT (osi), "activate", G_CALLBACK (osy_callback), (gpointer) osi); gtk_entry_set_text (GTK_ENTRY (osi), "1"); gtk_table_attach_defaults (GTK_TABLE (table), osi, 10, 11, 9, 10); gtk_widget_show (osi); osi = gtk_entry_new(); gtk_entry_set_max_length (GTK_ENTRY (osi), 1); g_signal_connect (G_OBJECT (osi), "activate", G_CALLBACK (osz_callback), (gpointer) osi); gtk_entry_set_text (GTK_ENTRY (osi), "0"); gtk_table_attach_defaults (GTK_TABLE (table), osi, 11, 12, 9, 10); gtk_widget_show (osi); //---------------------------------------------------------------------------------- m->wd=area; glmas=4; glf=TRUE; glp=TRUE; glx=1; gly=1; glz=0; check(m); g_timeout_add(35, HOST, (gpointer)m); //подключаем таймер, который будет 1 раз в 0,035 секунды вызывасть главную функцию HOST и передавать ей главную структуру g_signal_connect (G_OBJECT (area), "expose_event", G_CALLBACK (draw), (gpointer)m); g_signal_connect (G_OBJECT (wid), "destroy", G_CALLBACK(gtk_main_quit), NULL); /* Enter the main loop */ gtk_widget_show_all (wid); gtk_main (); return 0; }
Ключевые слова:
Бриллиант, паралельная проекция
|
|||||||