Реализация вращения трекбола в opengl с помощью FLTK: как "запомнить" последовательные вращения
Я работаю над проектом FLTK (моя первая попытка с графическим интерфейсом и opengl: пожалуйста, потерпите меня!) И у меня есть Fl_Gl_Window, которое отображает различные вещи в зависимости от некоторых других виджетов. Один из вариантов - отображать содержимое экрана в 3D и иметь возможность поворачивать его в 3D с помощью мыши. В принципе все хорошо (я использую функции Fl::event в обработчике окна для достижения положения мыши / окна и просто обновляю угол поворота x,y и z, которые были применены в заданном порядке), но так, как я это делал это было не интуитивно понятно (из-за некоммутирующих вращений и т. д.), поэтому я использую трекбол (похожий на тот, что здесь: http://www.csee.umbc.edu/~squire/download/trackball.c). Я понимаю, как все это работает, и могу заставить его вращаться, как и ожидалось, вдоль правильной оси при первом перетаскивании мышью. Но...
Проблема, насколько я могу судить, состоит в том, что для того, чтобы работать в целом (то есть с несколькими перетаскиваниями мышью), необходимо поддерживать матрицу просмотра модели, чтобы повернуть объект относительно отображаемой в данный момент ориентации так, чтобы был применен glRotatef. к нему с каждым движением мыши. Теперь способ, которым я изучил некоторые базовые openGL с FLTK, состоит в том, чтобы иметь функцию draw(), которая вызывается всякий раз, когда что-то меняется, но это (насколько я могу судить в FLTK и для меня) должно начинаться с нуля каждый раз, когда в этом окне есть содержимое, которое изменяется в зависимости от пользовательских параметров (они могут выбирать 2D-вид и т. д. и т. д.), а также рисует объекты, которые не предназначены для поворота. Таким образом, я не вижу, как это закодировать, чтобы матрица вида модели обновлялась при каждом перерисовке, как
а) при некоторых перерисовках необходимо по умолчанию вернуться к отсутствию поворотов (например, вариант 2D) (но я все же хотел бы "запомнить" поворот трехмерного объекта)
б) остальная часть сцены не должна быть повернута, поэтому я должен вернуть матрицу к предыдущему экрану...
Непосредственные пути вокруг этого я вижу
1) создайте массив всех перетаскиваний мышью и запишите соответственно так, чтобы, например, при 10-м перетаскивании мыши 10 поворотов применялись через glRotatef (мне не нравится это как решение, это ужасно!)
2) Запишите состояние матрицы просмотра модели и сохраните и загрузите ее, когда это необходимо. (Я читал, что это не то, как предполагается использовать openGL?)
3) Найти математическое преобразование, которое удается уменьшить
glRotatef(ang1,ax1_1,ax2_1,ax3_1);
glRotatef(ang2,ax1_2,ax2_2,ax3_2);
в
glRotatef(ang_tot,ax1_tot,ax2_tot,ax3_tot);
Это решение было бы самым... приятным.
(Psuedo-) код:
class MyGl : public Fl_Gl_Window {
void draw();
int handle(int);
void map_to_trackball(double*);
void calc_rotation();
//data including:
double old_vec[3];//old mouse position on hemisphere
double new_vec[3];//new mouse position on hemisphere
double ang; //rotation amount;
double rot[3]; //axis of rotation information
public:
MyGl(int X, int Y, int W, int H, const char *L): Fl_Gl_Window(X, Y, W, H, L) {
//constructor...
ang=0;rot[0]=0;rot[1]=0;rot[2]=0;
}
}
void MyGl::draw(){
if (3D){
glLoadIdentity();
glViewport(0,0,w(),h());
glOrtho(minx,maxx,miny,maxy,minz,maxz);
glPushMatrix();
glRotatef(ang,rot[0],rot[1],rot[2]);
//could save previous rotations and put them here ie:
// glRotatef(ang_old,rot_old[0],rot_old[1],rot_old[2]);
// seems clunky and would require a limitless number of rotations and memory if
// the user keeps tracking
draw_object(); //including glBegin(), glVertex3f() and glEnd() etc.
glPopMatrix();
// draw other non rotated things
}
}
int MyGl::handle(int e){
switch(e){
case: MOUSE_DOWN
map_to_trackball(old_vec);//projects starting mouse position onto hemisphere
//if recording all old rotations for successive implementation in draw()
// would save them here.
return 1; //<-- needed in FLTK to recognise drag event
case: DRAG (//pseudocode
map_to_trackball(new_vec);//projects current dragged mouse
//position onto hemisphere
calc_rotation(); //calculates and sets ang and rot[3]
//using old_vec and new_vec
break;
}
return Fl_Gl_Window::handle(e);
}
void map_to_trackball(double* v){
// basically trackball_ptov() from http://www.csee.umbc.edu/~squire/download/trackball.c
}
void calc_rotation(){
// basically mouseMotion() from http://www.csee.umbc.edu/~squire/download/trackball.c
}
2 ответа
Вот довольно приличная реализация: https://github.com/sanko/fltk-2.0.x/blob/master/test/trackball.c.
Второй способ - это то, что обычно делается здесь, но в вашем собственном коде. Под капотом glRotatef не делает ничего, кроме умножения матриц: он устанавливает матрицу вращения и умножает ее на текущую выбранную матрицу (предположительно, вид модели), которая затем сохраняется в этой матрице.
Я полагаю, что вы действительно имели в виду, когда использовали второй способ - использовать glLoadMatrixf и glGet для загрузки и сохранения матрицы, изменяя ее в стеке матриц GL. Однако современный способ сделать это - выполнить все матричные вычисления самостоятельно (все модификации матрицы не рекомендуются в последних версиях OpenGL). Библиотеки, такие как GLM, очень помогают, и вы можете просто хранить несколько матриц и загружать их в GL по мере необходимости.
Что касается третьего способа, я полагаю, что причина, по которой он вам нравится, заключается в том, что вам не нужно читать и писать в GL, а только писать в него. Если это так, то я бы порекомендовал выполнять операции с матрицами самостоятельно, используя что-то вроде GLM. Если "приятным" компонентом является то, что вы храните только 4 значения вместо 16, я предлагаю вам взглянуть на кватернионы, которые могут хранить вращения и легко объединять вращения в форме, очень похожей на ось-угол, которую использует glRotatef, - и это легко может быть преобразуется в и из оси-угла. Конечно, вращательные матрицы также можно преобразовать в осевой угол, но преобразование немного сложнее.