3-D Рассеивание при помощи
OpenGL
This article was contributed by William Heitler.
Компилятор: Visual C++ 6.0
Описание
Пример использует OpenGL для создания 3-D
рассеивания, которое можно посмотреть под любым
углом при помощи мышки. Данные так же можно
просматривать в номальной проекции так и в
перспективной. Данные и цвет каждой точки
загружаются из текстового файла. При изменении
размера окна приложения, график тоже
пропорционально меняет размер. Присутствует
возможность выделить определённый регион точек
и изменить его масштаб.
Проект представляет из себя диалоговое
приложение MFC. В диалоге содержатся все
необходимые для управления элементы, а так же
наследованный от CWnd класс, который включает в
себя все возможности для рисования графики.
На рисунке, пользователь выделил голубые точки
путём нажатия в секции кнопки Make. Если после
этого нажать Zoom, то график будет
перемасштабирован, чтобы показать только
выбранную группу точек. Чтобы облегчить
масштабирование по другим измерениям, можно
воспользоваться опцией Autoscale.
Графические классы
class COpenGLWnd : public CWnd
Это универсальный класс, основанный на CWnd, в
котором присутствует всё необходимое для
отображения OpenGL графики.
Основные, отличительные особенности этого
класса:
- К классу добавлена дополнительная поддержка
текста, при помощи функции wglUseFontBitmaps. Такой
способ намного проще, чем использование
собственных изображений шрифта в стандартных
методах OpenGL. Активировать поддержку текста можно
вызовом COpenGLWnd::MakeFont() в том же месте, где
происходит процесс инициализации. В данный
момент доступен только шрифт sysem, однако не
составляет большого труда позволить
пользователю выбирать любой другой шрифт. Для
печати текста сперва вызываем glRasterPos3f (для
установки координат вывода текста), а затем
COpenGLWnd::PrintString(const char* str) (для печати строки str).
- Добавлена поддержка рисования GDI. Это
виртуальная функция COpenGLWnd::OnDrawGDI(CPaintDC *pDC) которая
вызывается из OnPaint, и в которую можно добавить
свой собственный код GDI. Я не уверен в
стабильности данного решения, так как
единственное, что я нашёл относящееся к
смешиванию OpenGL и GDI - это то, что это нехорошая
идея. Однако, этот метод работает.
- Картинку можно скопировать в буфер обмена (clipboard)
как bitmap.
class CGLMouseRotate : public COpenGLWnd
Класс содержит код для кнопок и мышки,
позволяющий вращать изображение. Флаг
определяет - разрешено ли вращение мышкой или нет.
class CGLScatterGraph : public CGLMouseRotate
Этот класс содержит необходимые функции для
рисования графика рассеивания.
Данные передаются в класс при помощи
CGLScatterGraph::SetData(int count,COLORREF col,float *pCoords, COLORREF *pColList),
где count это количество точек в графике, col -
цвет, используемый для рисования точек (используется
только если pColList==NULL), pCoords - указатель на
массив, содержащий x, y и z координаты каждой точки
(соответственно длина массива = count*3), и pColList -
это либо NULL, либо указатель на массив, содержащий
цвета для каждой точки отдельно.
Указатели массва pCoords и pColList должны быть
поддержаны вызывающим классом, и не должны
удаляться либо выходить за пределы диапазона,
пока рассеивание существует, до тех пор, пока не
будет сделан новый вызов в SetData с count = 0.
CGLSelectableScatterGraph : public CGLScatterGraph
Этот класс позволяет выделять точки в графике
путём рисования кривой вокруг них. Вход в режим
выделения осуществляется при помощи
CGLSelectableScatterGraph::StartMakeSel(). При этом запрещается
возможность вращения мышкой и разрешает обычное
рисование мышкой. Класс содержит список CPoints
который описывает нарисованную пользователем
поверхность. Вызов функции CGLSelectableScatterGraph::CancelSel()
убирает выделение и разрешает вращение мышкой.
Выделение поверхности реализовано в функции
CGLSelectableScatterGraph::ZoomSel().
Отображение экранной поверхности в точки
сделано при помощи режима обратной связи в OpenGL.
Приведённый ниже код, демонстрирует эту
реализацию:
BOOL CGLSelectableScatterGraph::ZoomSel()
{
int i,j,count;
int id=0;
GLint rv;
GLfloat token;
GLfloat *pBuf;
if (m_SelPts.GetSize()>2)
{
CPoint *pt=new CPoint[m_SelPts.GetSize()];
for (i=0; i<m_SelPts.GetSize(); i++)
pt[i]=m_SelPts.GetAt(i);
CRgn rgn;
VERIFY(rgn.CreatePolygonRgn(pt,
m_SelPts.GetSize(),
ALTERNATE));
delete [] pt;
pBuf=new GLfloat[200+m_Count*4];
BeginGLCommands();
glFeedbackBuffer(200+m_Count*4,GL_3D,pBuf);
glRenderMode(GL_FEEDBACK);
EndGLCommands();
Invalidate();
UpdateWindow();
BeginGLCommands();
rv=glRenderMode(GL_RENDER);
int *pInSel=new int[m_Count];
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT,viewport);
count=rv;
while (count)
{
token=pBuf[rv-count];
count--;
if (token==GL_POINT_TOKEN)
{
GLdouble coords[3];
for (j=0; j<3; j++)
{
coords[j]=pBuf[rv-count];
count--;
}
CPoint pt;
pt.x=int(coords[0]);
pt.y=int(viewport[3]-coords[1]-1);
pInSel[id++]=rgn.PtInRegion(pt);
}
}
EndGLCommands();
m_bAutoScaleX=m_bAutoScaleY=m_bAutoScaleZ=FALSE;
float xMax,xMin,yMax,yMin,zMax,zMin;
xMax=yMax=zMax=-FLT_MAX;
xMin=yMin=zMin=FLT_MAX;
int drawCount=-1;
for (i=0; i<m_Count; i++)
{
if (!PtWithinAxes(m_pDat[i*3],m_pDat[i*3+1],m_pDat[i*3+2]))
continue;
drawCount++;
if (pInSel[drawCount]==0)
continue;
xMax=max(m_pDat[i*3],xMax);
xMin=min(m_pDat[i*3],xMin);
yMax=max(m_pDat[i*3+1],yMax);
yMin=min(m_pDat[i*3+1],yMin);
zMax=max(m_pDat[i*3+2],zMax);
zMin=min(m_pDat[i*3+2],zMin);
}
m_MaxX=NextAbove(xMax,5);
m_MinX=NextBelow(xMin,5);
m_MaxY=NextAbove(yMax,5);
m_MinY=NextBelow(yMin,5);
m_MaxZ=NextAbove(zMax,5);
m_MinZ=NextBelow(zMin,5);
Invalidate();
delete [] pBuf;
delete [] pInSel;
rgn.DeleteObject();
}
m_bAllowMouseRotate=m_bOldAllowRotate;
CancelSel();
return TRUE;
}
Формат данных
Данные можно загружать из основной программы,
используя кнопку Load, либо перетаскивая файл на
основное окно. Программа умеет считывать файлы
трёх форматов.
Во всех форматах каждая точка занимает
отдельную строку в текстовом файле.
Формат 1: Имеет три колонки, разделённые
табуляцией или пробелами, содержащие координаты
точки x, y и z. В таком формате все точки рисуются
одним цветом, который установлен в программе.
Формат 2: Имеет четыре колонки. Первые три
содержат координаты точки, а четвёртая имеет
целое значение (integer value) в диапазоне COLORREF, которая
является цветом точки.
Формат 3: Имеет шесть колонок. Первые три содержат
координаты, а другие три содержат значения RGB (в
диапазоне 0-255), которые в совокупности дают цвет
точки.
Файлы test1.txt, test2.txt и test3.txt иллюстрирую все три
формата.
Downloads
Скачать демонстрационный
проект - 59 Kb
|