График функции в 3D
Автор: Alexander Chernosvitov.
Увеличить картинку
Совместимость: VC6, NT4 SP3
Здесь представлено простое Windows приложение,
которое создает и отображает 3D изображение
(поверхность) функции, используя рендеринг OpenGL.
Графические данные могут быть произвольными
(указываются пользователем). С форматом данных
можно разобраться, если заглянуть в функцию
COGView::DefaultGraphic. При помощи данного приложения
удобно просматривать результаты вычислений
некоторых полей (например: поверхности равной
температуры, давление скорости или магнитная
плотность потока в трехмерном пространстве).
Изображение можно свободно вращать мышкой, опции
позволяют настроить желаемые параметры
изображения OpenGL.
Итак, запускаем MFC AppWizard, выбираем шаблон SDI и
называем его OG. В файл StdAfx.h необходимо поместить
следующие директивы, чтобы в проекте были
доступны библиотеки OpenGL и STL.
#include < afxdlgs.h >
#include < math.h >
#include < gl/gl.h >
#include < gl/glu.h >
#include < vector >
using namespace std;
Создаём структуру данных
Нам понадобится хранилище для вершин
поверхности.Разместите следующий код в файле
OGView.h. Упростим изначальный код класса View, удаляя
некоторые функции и добавляя новые.
class CPoint3D
{
public:
float x, y, z;
CPoint3D () { x=y=z=0; }
CPoint3D (float c1, float c2, float c3)
{
x = c1; z = c2; y = c3;
}
CPoint3D& operator=(const CPoint3D& pt)
{
x = pt.x; z = pt.z; y = pt.y;
return *this;
}
CPoint3D (const CPoint3D& pt)
{
*this = pt;
}
};
class COGView : public CView
{
protected:
COGView();
DECLARE_DYNCREATE(COGView)
public:
//======== New data
long m_BkClr; // цвет фона
HGLRC m_hRC; // контекст рендеринга OpenGL
HDC m_hdc; // контекст устройства Windows
GLfloat m_AngleX; // угол вращения (по оси X)
GLfloat m_AngleY; // угол вращения (по оси Y)
GLfloat m_AngleView; // угол перспективы
GLfloat m_fRangeX; // диапазон графика (по оси X)
GLfloat m_fRangeY; // диапазон графика (по оси Y)
GLfloat m_fRangeZ; // диапазон графика (по оси Z)
GLfloat m_dx; // прирост смещения (по оси X)
GLfloat m_dy; // прирост смещения (по оси Y)
GLfloat m_xTrans; // смещение (по оси X)
GLfloat m_yTrans; // смещение (по оси Y)
GLfloat m_zTrans; // смещение (по оси Z)
GLenum m_FillMode; // режим заполнения полигонами
bool m_bCaptured; // флаг захвата мышки
bool m_bRightButton; // флаг правой кнопки мыши
bool m_bQuad; // Флаг использования GL_QUAD (либо GL_QUAD_STRIP)
CPoint m_pt; // текущая позиция мышки
UINT m_xSize; // Текущий размер клиентского окна (по оси X)
UINT m_zSize; // Текущий размер клиентского окна
vector < CPoint3D > m_cPoints; // Graphics dimension (по оси X)
int m_LightParam[11]; //
CPropDlg *m_pDlg; //
//======== Public methods
COGDoc* GetDocument()
{ return DYNAMIC_DOWNCAST(COGDoc,m_pDocument); }
virtual ~COGView();
//======== New methods
void DrawScene(); // Подготовка и хранение изображение
void DefaultGraphic(); // Create and save the default plot
void ReadData(); // Манипуляции с файлом данных
bool DoRead(HANDLE hFile); // чтение файла данных
//===== Берём данные из файла и помещаем в m_cPoints
void SetGraphPoints(BYTE* buff, DWORD nSize);
void SetLightParam (short lp, int nPos); // Устанавливаем параметры освещения
void GetLightParams(int *pPos); // Получаем параметры освещения
void SetLight(); // Устанавливаем освещение
void SetBkColor(); // Устанавливаем цвет фона
void LimitAngles(); // Ограничение углов вращения
//{{AFX_VIRTUAL(COGView)
public:
virtual void OnDraw(CDC* pDC);
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
//}}AFX_VIRTUAL
protected:
//{{AFX_MSG(COGView)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
Добавляем обработчики
Используя ClassWizard добавляем в COGView обработчики
для сообщений: WM_CREATE, WM_DESTROY, WM_ERASEBKGND, WM_LBUTTONDOWN,
WM_LBUTTONUP, WM_MOUSEMOVE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SIZE и WM_TIMER.
Добавляем в конструктор класса View инициализацию
контролируемых параметров.
COGView::COGView()
{
//====== Контекста пока ещё не существует
m_hRC = 0;
//====== Изначальный поворот изображения
m_AngleX = 35.f;
m_AngleY = 20.f;
//====== Угол проекции матрицы
m_AngleView = 45.f;
//====== Изначальный цвет фона
m_BkClr = RGB(0, 0, 96);
// Инициализируем режим заполнения точек внутренних многоугольников (четвёрок)
m_FillMode = GL_FILL;
//====== Изначальное создание графика
DefaultGraphic();
//====== Начальный сдвиг изображения
//====== целое и половина размера объекта
m_zTrans = -1.5f*m_fRangeX;
m_xTrans = m_yTrans = 0.f;
//== Начальный квант сдвига (для анимации вращения)
m_dx = m_dy = 0.f;
//====== Мышка не захвачена
m_bCaptured = false;
//====== Правая кнопка мыши не нажата
m_bRightButton = false;
//====== Для создания поверхности мы используем четвёрки
m_bQuad = true;
//====== Инициализация параметров освещения
m_LightParam[0] = 50; // направление по оси X
m_LightParam[1] = 80; // направление по оси Y
m_LightParam[2] = 100; // направление по оси Z
m_LightParam[3] = 15; // Отражённый свет
m_LightParam[4] = 70; // Рассеянный свет
m_LightParam[5] = 100; // Зеркальный свет
m_LightParam[6] = 100; // Отражательность материала
m_LightParam[7] = 100; // Рассеивание материала
m_LightParam[8] = 40; // Зеркальность материала
m_LightParam[9] = 70; // Блеск материала
m_LightParam[10] = 0; // Эмиссия материала
}
Подготовка к открытию контекста OpenGL
Возможно Вы знаете, что для того, чтобы
подготовить окно для рисования в нём при помощи
OpenGL Вам необходимо:
- выбрать и установить формат пикселя,
- получить и сохранить контекст устройства окна,
- создать и сохранить контекст OpenGL,
- добавить к окну стили WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
- убрать очистку фона перед каждой перерисовкой
(обработать WM_ERASEBKGND и возвращать только TRUE),
- специфически обработать сообщения WM_SIZE, WM_PAINT и
- освободить контексты, когда отображение будет
закрыто.
Основные события для использования OpenGL
помещаются в обработчик WM_CREATE.
int COGView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// Структура, описывающая формат
PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR),
1, // Версия
PFD_DRAW_TO_WINDOW | // Поддерживает GDI
PFD_SUPPORT_OPENGL | // Поддерживает OpenGL
PFD_DOUBLEBUFFER, // Используем двойную буферизацию
// (более эффективная прорисовка)
PFD_TYPE_RGBA, // No pallettes
24, // Number of color planes
// in each color buffer
24, 0, // для компоненты Red
24, 0, // для компоненты Green
24, 0, // для компоненты Blue
24, 0, // для компоненты Alpha
0, // Number of planes
// of Accumulation buffer
0, // для компоненты Red
0, // для компоненты Green
0, // для компоненты Blue
0, // для компоненты Alpha
32, // Глубина Z-buffer
0, // Глубина Stencil-buffer
0, // Глубина Auxiliary-buffer
0, // Now is ignored
0, // Number of planes
0, // Now is ignored
0, // Цвет прозрачной маски
0 // Now is ignored
};
//====== Получаем контекст текущего окна
m_hdc = ::GetDC(GetSafeHwnd());
//====== Делаем запрос ближайшего совместимого формата пикселей
int iD = ChoosePixelFormat(m_hdc, &pfd);
if ( !iD )
{
MessageBox("ChoosePixelFormat::Error");
return -1;
}
//====== Устанавливаем данный формат
if ( !SetPixelFormat (m_hdc, iD, &pfd) )
{
MessageBox("SetPixelFormat::Error");
return -1;
}
//====== создаём контекст OpenGL
if ( !(m_hRC = wglCreateContext (m_hdc)))
{
MessageBox("wglCreateContext::Error");
return -1;
}
//====== Делаем его текущим
if ( !wglMakeCurrent (m_hdc, m_hRC))
{
MessageBox("wglMakeCurrent::Error");
return -1;
}
//====== Теперь можно выполнять команды OpenGL
// (т.e. вызывать функции gl***)
glEnable(GL_LIGHTING); // Будет использоваться освещение
//====== Только один (первый) источник света
glEnable(GL_LIGHT0);
//====== Depth of the Z-buffer will be taken into account
glEnable(GL_DEPTH_TEST);
//====== Material colors will be taken into account
glEnable(GL_COLOR_MATERIAL);
//====== Наша функция, устанавливающая бэкграунд
SetBkColor();
//====== Создаём и сохраняем изображение в специальном списке
// команд OpenGL
DrawScene();
return 0;
}
Перерисовка
Каждый раз, когда Вы (или Windows) решаете
перерировать отображение, необходимо обработать
сообщение WM_PAINT. Принимая во внимание особенности
архитектуры Документ-Вид, Вы помещаете код
перерисовки на в OnPaint, а в функцию OnDraw.
void COGView:: OnDraw(CDC* pDC)
{
//========== Очищаем фон и Z-буфер
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//========== Очищаем моделирующую матрицу
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
//======= Первичная установка света
// (иначе он будет вращаться вместе с изображением)
SetLight();
//====== Изменяем порядок коэффициентов матрицы
glTranslatef(m_xTrans,m_yTrans,m_zTrans); // для смещения
glRotatef (m_AngleX, 1.0f, 0.0f, 0.0f ); // и для вращения
glRotatef (m_AngleY, 0.0f, 1.0f, 0.0f );
//====== следующие координаты вершин
glCallList(1);
//====== Переключение между буферами
// (для отображения изменений)
SwapBuffers(m_hdc);
}
Освещение
Здесь представлен простой способ вычисления
цвета каждого пикселя.
void COGView::SetLight()
{
//====== При вычислении цвета каждого пикселя при помощи формулы освещения
//====== принимаются во внимание обе стороны поверхности
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,1);
//====== положение источника света зависит от
// размеров объекта, мастабируемого от (0,100)
float fPos[] =
{
(m_LightParam[0]-50)*m_fRangeX/100,
(m_LightParam[1]-50)*m_fRangeY/100,
(m_LightParam[2]-50)*m_fRangeZ/100,
1.f
};
glLightfv(GL_LIGHT0, GL_POSITION, fPos);
//====== Интенсивность отражённого света
float f = m_LightParam[3]/100.f;
float fAmbient[4] = { f, f, f, 0.f };
glLightfv(GL_LIGHT0, GL_AMBIENT, fAmbient);
//====== Интенсивность разброса света
f = m_LightParam[4]/100.f;
float fDiffuse[4] = { f, f, f, 0.f };
glLightfv(GL_LIGHT0, GL_DIFFUSE, fDiffuse);
//====== Интенсивность зеркальности света
f = m_LightParam[5]/100.f;
float fSpecular[4] = { f, f, f, 0.f };
glLightfv(GL_LIGHT0, GL_SPECULAR, fSpecular);
//====== Свойства отражения поверхности материала для
// каждой компоненты света
f = m_LightParam[6]/100.f;
float fAmbMat[4] = { f, f, f, 0.f };
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, fAmbMat);
f = m_LightParam[7]/100.f;
float fDifMat[4] = { f, f, f, 1.f };
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, fDifMat);
f = m_LightParam[8]/100.f;
float fSpecMat[4] = { f, f, f, 0.f };
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, fSpecMat);
//====== Освещённость материала
float fShine = 128 * m_LightParam[9]/100.f;
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, fShine);
//====== Свойство световой эмиссии материала
f = m_LightParam[10]/100.f;
float fEmission[4] = { f, f, f, 0.f };
glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, fEmission);
}
Установка цвета фона очень проста.
void COGView::SetBkColor()
{
//====== Разделяем цвет на три составляющие
GLclampf red = GetRValue(m_BkClr)/255.f,
green = GetGValue(m_BkClr)/255.f,
blue = GetBValue(m_BkClr)/255.f;
//====== Устанавливаем белый (фон) цвет
glClearColor (red, green, blue, 0.f);
//====== Стираем фон
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
Создание и хранение изображения
Основные операции по созданию изображения
хранятся в функции DrawScene, которая создаёт и
хранит изображение, состоящее из координат
вершин. Мы предполагаем, что все координаты уже
размещены в STL-контейнере m_cPoints. Изображение
сконструировано как серия изогнутых
четырёхугольников (GL_QUADS) или как серия изогнутых
четвёрок (GL_QUAD_STRIP). Ниже мы добавим команды по
переключению между двумя этими режимами. Точки
поверхности располагаются в координатной сетке
(X, Z). Размер этой сетки хранится в переменных m_xSize
и m_zSize. Несмотря на 2-мерность сетки, мы используем
линейный массив (одномерный) m_cPoints или (точнее)
контейнер типа вектор для хранения значений
координат. Это съэкономит усилия при файловых
операциях.
Выбор 4 соседних точек одного примитива (например
GL_QUADS) будет сделан при помощи 4 индексов (n, i, j, k).
Индекс n проходит последовательно через все
вершины по порядку слева направо по оси X (Z=0) а
затем процедура повторяется (постепенно
увеличивая значение Z). Другие индексы
вычисляются относительно индекса n. Обратите
внимание, что только два индекса работают в
режиме связанных четвёрок, который включается
или выключается флагом m_bQuad flag.
void COGView::DrawScene()
{
//====== Создаём новый список команд OpenGL
glNewList(1, GL_COMPILE);
//====== Устанавливаем режим заполнения полигонами
glPolygonMode(GL_FRONT_AND_BACK, m_FillMode);
//====== Размеры сетки изображения
UINT nx = m_xSize-1,
nz = m_zSize-1;
//====== Включаем режим связанных примитивов
// (не связан)
if (m_bQuad)
glBegin (GL_QUADS);
for (UINT z=0, i=0; z < nz; z++, i++)
{
//====== Включаем режим связанных примитивов
// (связан)
//====== The strip of connected quads begins here
if (!m_bQuad)
glBegin(GL_QUAD_STRIP);
for (UINT x=0; x < nx; x++, i++)
{
// i, j, k, n — 4 индекса четвёрки
// Counter Clockwise direction
int j = i + m_xSize, // Другие индексы вершин
k = j+1,
n = i+1;
//=== Получаем координаты четырёх вершин
float
xi = m_cPoints[i].x,
yi = m_cPoints[i].y,
zi = m_cPoints[i].z,
xj = m_cPoints[j].x,
yj = m_cPoints[j].y,
zj = m_cPoints[j].z,
xk = m_cPoints[k].x,
yk = m_cPoints[k].y,
zk = m_cPoints[k].z,
xn = m_cPoints[n].x,
yn = m_cPoints[n].y,
zn = m_cPoints[n].z,
//=== Координаты векторов линий четвёрки
ax = xi-xn,
ay = yi-yn,
by = yj-yi,
bz = zj-zi,
//====== Нормальные координаты вектора
vx = ay*bz,
vy = -bz*ax,
vz = ax*by,
//====== Нормальная длина вектора
v = float(sqrt(vx*vx + vy*vy + vz*vz));
//====== масштабирование
vx /= v;
vy /= v;
vz /= v;
//====== Устанавливаем нормальный вектор
glNormal3f (vx,vy,vz);
//===== Not connected quads branch
if (m_bQuad)
{
//====== Vertices are given in counter clockwise
// direction order
glColor3f (0.2f, 0.8f, 1.f);
glVertex3f (xi, yi, zi);
glColor3f (0.6f, 0.7f, 1.f);
glVertex3f (xj, yj, zj);
glColor3f (0.7f, 0.9f, 1.f);
glVertex3f (xk, yk, zk);
glColor3f (0.7f, 0.8f, 1.f);
glVertex3f (xn, yn, zn);
}
else
//===== Connected quads branch
{
glColor3f (0.9f, 0.9f, 1.0f);
glVertex3f (xi, yi, zi);
glColor3f (0.5f, 0.8f, 1.0f);
glVertex3f (xj, yj, zj);
}
}
//====== Закрываем блок команд GL_QUAD_STRIP
if (!m_bQuad)
glEnd();
}
//====== Закрываем блок команд GL_QUADS
if (m_bQuad)
glEnd();
//====== Закрываем список команд OpenGL
glEndList();
}
Нормальные векторы должны быть правильно
расчитаны для каждого примитива. Иначе мы не
получим качественного освещения и, как следствие
качественного изображения.
Операции с файлом
Контейнер m_cPoints будет заполнен после того как
данные будут считаны из файла. Нам необходимо
иметь файл данных, который будет загружаться по
умолчанию при запуске приложения. Для этого мы
создаём бинарный файл и размещаем в самом начале
размеры сетки (m_xSize и m_zSize) , а затем значения
функции y = f (x, z). Перед записью в файл, все данные
размещены во временном буфере типа BYTE (т.е. unsigned
char).
void COGView::DefaultGraphic()
{
//====== Размеры сетки
m_xSize = m_zSize = 33;
//====== Количество граней меньше, чем количество узлов
UINT nz = m_zSize - 1,
nx = m_xSize - 1;
// Размер файла в байта
DWORD nSize =
m_xSize * m_zSize * sizeof(float) + 2*sizeof(UINT);
//====== Временный буфер
BYTE *buff = new BYTE[nSize+1];
//====== Указатель на начало буфера
UINT *p = (UINT*)buff;
//====== помещаем два UINT-а
*p++ = m_xSize;
*p++ = m_zSize;
//====== Изменяем тип указателя, чтобы продолжать работать
// числами с плавающей точкой
float *pf = (float*)p;
//=== коэффициенты формулы по умолчанию
double fi = atan(1.)*6,
kx = fi/nx,
kz = fi/nz;
//====== Проводим вычисления для всех узлов сетки
//=== вычисляем и размещаем значения функции по умолчанию
// в том же буфере
for (UINT i=0; i < m_zSize; i++)
{
for (UINT j=0; j < m_xSize; j++)
{
//====== Пример функции
*pf++ =
float (sin(kz*(i-nz/2.)) * sin(kx*(j-nx/2.)));
}
}
//=== Нам необходимо знать реальное количество байт, записанных
// в файл
DWORD nBytes;
//=== Создаём и открываем файл данных по умолчанию (sin.dat)
HANDLE hFile = CreateFile(_T("sin.dat"),
GENERIC_WRITE,
0,0,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
0);
//====== Записываем содержимое буфера
WriteFile(hFile,(LPCVOID)buff, nSize,&nBytes, 0);
//====== Закрываем файл
CloseHandle(hFile);
//====== Создаём и заполняем контейнер m_cPoints
// (используя тот же буфер)
SetGraphPoints (buff, nSize);
//====== Освобождаем временный буфер
delete [] buff;
}
Чтение данных
Функция ReadData создаёт файловый диалог,
позволяющий пользователю выбрать файл данных,
считать данные и создать изображение.
Стандартный файловый диалог использует стиль
OFN_EXPLORER, который работает только в Windows 2000.
void COGView::ReadData()
{
//=== Здесь мы помещаем путь к файлу
TCHAR szFile[MAX_PATH] = { 0 };
//====== фильтр расширений файла
TCHAR *szFilter =TEXT("Graphics Data Files (*.dat)\0")
TEXT("*.dat\0")
TEXT("All Files\0")
TEXT("*.*\0");
//====== Запрашиваем в текущей директории
TCHAR szCurDir[MAX_PATH];
::GetCurrentDirectory(MAX_PATH-1,szCurDir);
//=== Структура, используемая стандартным файловым диалогом
OPENFILENAME ofn;
ZeroMemory(&ofn,sizeof(OPENFILENAME));
//====== Параметры диалога
ofn.lStructSize = sizeof(OPENFILENAME);
//====== Окно, которое владеет диалогом
ofn.hwndOwner = GetSafeHwnd();
ofn.lpstrFilter = szFilter;
//====== Фильтры строкового индекса (начиная с 1)
ofn.nFilterIndex = 1;
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
//====== Заголовок диалога
ofn.lpstrTitle = _T("Find a data file");
ofn.nMaxFileTitle = sizeof (ofn.lpstrTitle);
//====== Стиль диалога (только в Win2K)
ofn.Flags = OFN_EXPLORER;
//====== Создаём и открываем диалог (возвращая 0 при ошибке)
if (GetOpenFileName(&ofn))
{
// Пытаемся открыть файл (который должен существовать)
HANDLE hFile = CreateFile(ofn.lpstrFile, GENERIC_READ,
FILE_SHARE_READ, 0, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, 0);
//=== При ошибке CreateFile возвращаем -1
if (hFile == (HANDLE)-1)
{
MessageBox(_T("Could not open this file"));
return;
}
//====== Пытаемся считать данные
if (!DoRead(hFile))
return;
//====== Создаём изображение
DrawScene();
//====== Перерисовываем клиентскую часть окна
Invalidate(FALSE);
}
}
Макрос TEXT эквивалентен следующей строке
TEXT("Graphics Data Files (*.dat)\0*.dat\0All Files\0*.*\0");
Фактически чтение производится в DoRead, где мы
запрашиваем размер файла, распределяем
необходимое количество памяти, а так же
считываем весь файл в буфер. После этого мы
устанавливаем контейнер точек m_cPoints при помощи
функции SetGraphPoints.
bool COGView::DoRead(HANDLE hFile)
{
//===== Запрашиваем размер файла
DWORD nSize = GetFileSize (hFile, 0);
if (nSize == 0xFFFFFFFF)
{
GetLastError();
MessageBox(_T("Could not get file size"));
CloseHandle(hFile);
return false;
}
//===== Пытаемся распределить буфер
BYTE *buff = new BYTE[nSize+1];
if (!buff)
{
MessageBox(_T("The data file is too big"));
CloseHandle(hFile);
return false;
}
DWORD nBytes;
//===== Читаем содержимое файла в буфер
ReadFile (hFile, buff, nSize, &nBytes, 0);
CloseHandle(hFile);
if (nSize != nBytes)
{
MessageBox(_T("Error while reading data file"));
return false;
}
//===== Устанавливаем вектор координат вершин
SetGraphPoints (buff, nSize);
delete [] buff;
return true;
}
void COGView::SetGraphPoints(BYTE* buff, DWORD nSize)
{
//===== Опять используем технологию изменения
// типа указателя
UINT *p = (UINT*)buff;
//==== Считываем размеры сетки
m_xSize = *p;
m_zSize = *++p;
//===== Проверяем размер файла (в данном случае)
if (m_xSize<2 || m_zSize < 2 ||
m_xSize*m_zSize*sizeof(float) + 2 * sizeof(UINT) != nSize)
{
MessageBox(_T("Wrong data format"));
return;
}
//===== Изменяем размер контейнера
m_cPoints.resize(m_xSize*m_zSize);
if (m_cPoints.empty())
{
MessageBox(_T("Can not allocate the data"));
return;
}
//====== Изменяем тип указателя
float x, z,
*pf = (float*)++p,
fMinY = *pf,
fMaxY = *pf,
right = (m_xSize-1)/2.f,
left = -right,
rear = (m_zSize-1)/2.f,
front = -rear,
range = (right + rear)/2.f;
UINT i, j, n;
m_fRangeY = range;
m_fRangeX = float(m_xSize);
m_fRangeZ = float(m_zSize);
//===== Насколько далеко мы разместим содержимое изображения
m_zTrans = -1.5f * m_fRangeZ;
//===== Заполняем контейнер 3D точками
for (z=front, i=0, n=0; i < m_zSize; i++, z+=1.f)
{
for (x=left, j=0; j < m_xSize; j++, x+=1.f, n++)
{
MinMax (*pf, fMinY, fMaxY);
m_cPoints[n] = CPoint3D(x,z,*pf++);
}
}
//===== Масштабируем данные, чтобы они "приятно" смотрелись
float zoom = fMaxY > fMinY ? range/(fMaxY-fMinY) : 1.f;
for (n=0; n < m_xSize*m_zSize; n++)
{
m_cPoints[n].y = zoom * (m_cPoints[n].y - fMinY) - range/2.f;
}
}
Манипуляции с мышкой
Левая кнопка мыши позволяет контролировать два
направления вращения, а так же включать и
выключать режим автоматического вращения.
Правая же кнопка используется для перемещения по
оси Z, т.е. удаления или приближения графика.
void COGView::LimitAngles()
{
while (m_AngleX < -360.f)
m_AngleX += 360.f;
while (m_AngleX > 360.f)
m_AngleX -= 360.f;
while (m_AngleY < -360.f)
m_AngleY += 360.f;
while (m_AngleY > 360.f)
m_AngleY -= 360.f;
}
void COGView::OnLButtonDown(UINT nFlags, CPoint point)
{
//====== Останавливаем вращение
KillTimer(1);
//====== Zero the quantums
m_dx = 0.f;
m_dy = 0.f;
//====== Захватываем сообщение мышки и направляем его
// в наше окно
SetCapture();
//====== Запоминаем захват мышки
m_bCaptured = true;
//====== и точку, в которой это произошло
m_pt = point;
}
void COGView::OnRButtonDown(UINT nFlags, CPoint point)
{
//====== Запоминаем нажатие правой кнопки мыши
m_bRightButton = true;
//====== and reproduce the left button response
OnLButtonDown(nFlags, point);
}
void COGView::OnLButtonUp(UINT nFlags, CPoint point)
{
//====== Если мы захватили мышку,
if (m_bCaptured)
{
//=== query the desired quantum value
//=== if it exeeds the sensativity threshold
if (fabs(m_dx) > 0.5f || fabs(m_dy) > 0.5f)
//=== Turn on the constant rotation
SetTimer(1,33,0);
else
//=== Выключаем константу вращения
KillTimer(1);
//====== Очищаем флаг захвата
m_bCaptured = false;
ReleaseCapture();
}
}
void COGView::OnRButtonUp(UINT nFlags, CPoint point)
{
m_bRightButton = m_bCaptured = false;
ReleaseCapture();
}
void COGView::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_bCaptured)
{
// Desired rotation speed components
m_dy = float(point.y - m_pt.y)/40.f;
m_dx = float(point.x - m_pt.x)/40.f;
//====== Если Ctrl была нажата
if (nFlags & MK_CONTROL)
{
//=== сдвигаем изображение
m_xTrans += m_dx;
m_yTrans -= m_dy;
}
else
{
//====== Если нажата правая кнопка мыши
if (m_bRightButton)
//====== сдвигаем по оси z
m_zTrans += (m_dx + m_dy)/2.f;
else
{
//====== иначе мы вращаем изображение
LimitAngles();
double a = fabs(m_AngleX);
if (90. < a && a < 270.)
m_dx = -m_dx;
m_AngleX += m_dy;
m_AngleY += m_dx;
}
}
//=== В любом случае сохраняем координаты
m_pt = point;
Invalidate(FALSE);
}
}
void COGView::OnTimer(UINT nIDEvent)
{
LimitAngles();
//====== Увеличиваем углы
m_AngleX += m_dy;
m_AngleY += m_dx;
Invalidate(FALSE);
}
Управление освещением вынесено в модальный
диалог (см. пример.).
Downloads
Скачать демонстрационный проект - 19
Kb
Скачать исходник - 30 Kb
|