15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту


График функции в 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