Учебник по Visual C++ .Net

         

Чтение данных В теле следующей


COGView: : ReadData ()

{

//=== Строка, в которую будет помещен файловый путь

TCHAR szFile[MAX_PATH] = { 0 } ;

//====== Строка фильтров демонстрации файлов

TCHAR *szFilter =TEXT ("Graphics Files (*.dat)\0")

TEXT("*.dat\0")

TEXT ("All FilesNO")

TEXT ( " * . * \ 0 " ) ;

//====== Выявляем текущую директорию

TCHAR szCurDir[MAX_PATH] ;

: :GetCurrentDirectory (MAX_PATH-1, szCurDir) ;

//== Структура данных, используемая файловым диалогом

OPENFILENAME ofn;

ZeroMemory (&ofn,sizeof (OPENFILENAME) ) ;

//====== Установка параметров будущего диалога

ofn.lStructSize = sizeof (OPENFILENAME) ;

и * . *, текстовые описания которых можно увидеть в одном из окон стандартного диалога поиска и открытия файлов:

//====== Функция непосредственного чтения данных

bool COGView: : DoRead ( HANDLE hFile) {

//====== Сначала узнаем размер файла

DWORD nSize = GetFileSize (hFile, 0) ;

//=== Если не удалось определить размер, GetFileSize

//====== возвращает 0xFFFFFFFF

if (nSize == 0xFFFFFFFF)

{

GetLastError () ;

MessageBox (_T ("Некорректный размер файла"));

CloseHandle (hFile) ;

return false;

//=== Создаем временный буфер размером в весь файл BYTE

*buff = new BYTE [nSize+1] ;

//====== Обработка отказа выделить память

if (Ibuff) {

MessageBox (_T ("Слишком большой размер файла"))

CloseHandle (hFile) ;

return false;

//====== Реальный размер файла

DWORD nBytes;

//====== Попытка прочесть файл

ReadFile (hFile, buff, nSize, &nBytes, 0) ; CloseHandle (hFile) ;

//====== Если реально прочитано меньшее число байт

if (nSize != nBytes)

{

MessageBox (_T ("Ошибка при чтении файла"));

return false;

}

//====== Генерация точек изображения

SetGraphPoints (buff, nSize) ;

//====== Освобождение временного буфера

delete [] buff;

// ====== Возвращаем успех

return true;

}

В данный момент можно запустить приложение, и оно должно работать. В окне вы должны увидеть изображение поверхности, которое приведено на рис. 7.1. Для создания рисунка мы изменили цвет фона на белый, так как в книге этот вариант считается более предпочтительным. Попробуйте изменить размеры окна. Изображение поверхности должно пропорционально изменить свои размеры. Оцените качество интерполяции цветов внутренних точек примитивов и степень влияния освещения. Позже мы создадим диалог для управления параметрами света и отражающих свойств материала. А сейчас отметим, что напрашивается введение возможности управлять ориентацией и местоположением поверхности с помощью мыши. Для того чтобы убедиться в сложности автомата состояний OpenGL, a также в том, что все в нем взаимосвязано, временно поменяйте местами две строки программы: glVertexSf (xi, yi, zi); и glVertex3f (xn, yn, zn);. Вы найдете их в теле функции DrawScene.

Рис. 7.1. Вид освещенной поверхности в 3D



Диалог по управлению светом В


Вид окна диалога по управлению параметрами света

Обратите внимание на то, что справа от каждого движка расположен элемент типа static Text, в окне которого будет отражено текущее положение движка в числовой форме. Три регулятора (элемента типа Slider Control) в левом верхнем углу окна диалога предназначены для управления свойствами света. Группа регуляторов справа от них поможет пользователю изменить координаты источника света. Группа регуляторов, объединенная рамкой (типа Group Box) с заголовком Material, служит для изменения отражающих свойств материала. Кнопка с надписью Data File позволит пользователю открыть файловый диалог и выбрать файл с данными для нового изображения. Для диалогов, предназначенных для работы в немодальном режиме, необходимо установить стиль Visible. Сделайте это в окне Properties > Behavior. Идентификаторы элементов управления мы сведем в табл. 7.1.

Таблица 7.1. Идентификаторы элементов управления

Элемент

Идентификатор

Диалог

IDD_PROP

Ползунок Ambient в группе Light

IDC_AMBIENT

Ползунок Diffuse в группе Light

IDC_DIFFUSE

Ползунок Specular в группе Light

IDC_SPECULAR

; Static Text справа от Ambient в группе Light

IDC_AMB_TEXT

, Static Text справа от Diffuse в группе Light

IDC_DIFFUSE_TEXT

Static Text справа от Specular в группе Light

IDC_SPECULAR_TEXT

Ползунок Ambient в группе Material

IDC_AMBMAT

Ползунок Diffuse в группе Material

IDC_DIFFMAT

' Ползунок Specular в группе Material

IDC_SPECMAT

f Static Text справа от Ambient в группе Material

IDC_AMBMAT_TEXT

:! Static Text справа от Diffuse. в группе Material

IDC_DIFFMATJFEXT

; Static Text справа от Specular в группе Material

IDC_SPECMAT_TEXT

Ползунок Shim'ness

IDC_SHINE

Ползунок Emission

IDC_EMISSION

« Static Text справа от Shininess

IDC_SHINE_TEXT

Static Text справа от Emission

IDC_EMISSION_TEXT

Ползунок X

IDC_XPOS

| Ползунок Y

IDC_YPOS

1 Ползунок Z

IDC_ZPOS

Static Text справа от X

IDC_XPOS_TEXT

Static Text справа от Y

IDC_YPOS_TEXT

Static Text справа от Z

IDC_ZPOS_TEXT

Кнопка Data File

IDC_FILENAME

<
Диалоговый класс

Для управления диалогом следует создать новый класс. Для этого можно воспользоваться контекстным меню, вызванным над формой диалога.

Выберите в контекстном меню команду Add Class.

В левом окне диалога Add Class раскройте дерево Visual C++, сделайте выбор MFC > MFC Class и нажмите кнопку Open.

В окне мастера MFC Class Wizard задайте имя класса CPropDlg, в качестве базового класса выберите CDialog. При этом станет доступным ноле Dialog ID.

В это поле введите или выберите из выпадающего списка идентификатор шаблона диалога IDD_PROP и нажмите кнопку Finish.

Просмотрите объявление класса CPropDlg, которое должно появиться в новом окне PropDlg.h. Как видите, мастер сделал заготовку функции DoDataExchange для обмена данными с элементами управления на форме диалога. Однако она нам не понадобится, так как обмен данными будет производиться в другом стиле, характерном для приложений не MFC-происхождения. Такое решение выбрано в связи с тем, что мы собираемся перенести рассматриваемый код в приложение, созданное на основе библиотеки шаблонов ATL. Это будет сделано в уроке 9 при разработке элемента ActiveX, а сейчас введите в диалоговый класс новые данные. Они необходимы для эффективной работы с диалогом в немодальном режиме. Важным моментом в таких случаях является использование указателя на оконный класс. С его помощью легко управлять окном прямо из диалога. Мы слегка изменили конструктор и ввели вспомогательный метод GetsiiderNum. Изменения косметического характера вы обнаружите сами:

#pragma once

class COGView; // Упреждающее объявление

class CPropDlg : public CDialog

{

DECLARE_DYNAMIC(CPropDlg)

public:

COGView *m_pView; // Адрес представления

int m_Pos[ll]; // Массив позиций ползунков

CPropDlg(COGView* p) ;

virtual ~CPropDlg();

// Метод для выяснения ID активного ползунка int GetsiiderNum(HWND hwnd, UINT& nID) ;

enum { IDD = IDD_PROP };

protected: virtual void DoDataExchange(CDataExchange* pDX);

DECLARE_MESSAGE_MAP()



};

Откройте файл реализации диалогового класса и с учетом сказанного про адрес окна введите изменение в тело конструктора, который должен приобрести такой вид:

CPropDlg::CPropDlg(COGView* p)

: CDialog(CPropDlg::IDD, p)

{

//====== Запоминаем адрес объекта

m_pView = p;

}

Инициализация диалога

При каждом открытии диалога все его элементы управления должны отражать текущие состояния регулировок (положения движков), которые хранятся в классе представления. Обычно эти установки производят в коде функции OninitDialog. Введите в класс CPropDlg стартовую заготовку этой функции (CPropDlg > Properties > Overrides > OninitDialog > Add) и наполните ее кодами, как показано ниже:

BOOL CPropDlg: rOnlnitDialog (void)

{ CDialog: :OnInitDialog () ;

//====== Заполняем массив текущих параметров света

m_pView->GetLightParams (m _Pos) ;

//====== Массив идентификаторов ползунков

UINT IDs[] =

{

IDC_XPOS, IDC_YPOS, IDC_ZPOS,

IDC_AMBIENT,

IDC_DIFFUSE,

IDC_SPECULAR,

IDC_AMBMAT,

IDC_DIFFMAT,

IDC_SPECMAT,

IDC_SHINE,

IDCEMISSION

//====== Цикл прохода по всем регуляторам

for (int i=0; Ksizeof (IDs) /sizeof (IDs [ 0] ) ; i++)

{

//=== Добываем Windows-описатель окна ползунка H

WND hwnd = GetDlgItem(IDs[i] } ->GetSafeHwnd () ;

UINT nID;

//====== Определяем его идентификатор

int num = GetSliderNum(hwnd, nID) ;

// Требуем установить ползунок в положение m_Pos[i]

: :SendMessage(hwnd, TBM_SETPOS, TRUE, (LPARAM) m_Pos [i] )

char s [ 8 ] ;



//====== Готовим текстовый аналог текущей позиции

sprintf (s, "%d" ,m_Pos [ i] ) ;

//====== Помещаем текст в окно справа от ползунка

SetDlgltemText (nID, (LPCTSTR) s) ;

}

return TRUE;

}

Вспомогательная функция GetsliderNum по переданному ей описателю окна (hwnd ползунка) определяет идентификатор связанного с ним информационного окна (типа Static text) и возвращает индекс соответствующей ползунку пози ции в массиве регуляторов:

int CPropDlg: :GetSliderNum (HWND hwnd, UINT& nID)



{

//==== GetDlgCtrllD по известному hwnd определяет

//==== и возвращает идентификатор элемента управления

switch ( : : GetDlgCtrllD (hwnd) )

{

// ====== Выясняем идентификатор окна справа

case IDC_XPOS:

nID = IDC_XPOS_TEXT;

return 0;

case IDC_YPOS:

nID = IDC_YPOS_TEXT;

return 1;

case IDC_ZPOS:

nID = IDC_ZPOS_TEXT;

return 2;

case IDC_AMBIENT:

nID = IDC_AMB_TEXT;

return 3;

case IDC_DIFFUSE:

nID = IDC_DIFFUSE_TEXT;

return 4 ;

case IDC_SPECULAR:

nID = IDC_SPECULAR_TEXT;

return 5; case IDC_AMBMAT:

nID = IDC_AMBMAT_TEXT;

return 6 ;

case IDC_DIFFMAT:

nID = IDC_DIFFMAT_TEXT;

return 7 ;

case IDC_SPECMAT:

nID = IDC_SPECMAT_TEXT;

return 8 ; case IDC_SHINE:

nID = IDC_SHINE_TEXT;

return 9;

case IDC_EMISSION:

nID = IDC_EMISSION_TEXT;

return 10;

}

return 0;

}

Работа с группой регуляторов



В диалоговый класс введите обработчики сообщений WM_HSCROLL и WM_CLOSE, a также реакцию на нажатие кнопки IDC_FILENAME. Воспользуйтесь для этого окном Properties и его кнопками Messages и Events. В обработчик OnHScroll введите логику определения ползунка и управления им с помощью мыши и клавиш. Подобный код мы подробно рассматривали в уроке 4. Прочтите объяснения вновь, если это необходимо, Вместе с сообщением WM_HSCROLL система прислала нам адрес объекта класса GScrollBar, связанного с активным ползунком. Мы добываем Windows-описатель его окна (hwnd) и передаем его в функцию GetsliderNum, которая возвращает целочисленный индекс. Последний используется для доступа к массиву позиций ползунков. Кроме этого, система передает nSBCode, который соответствует сообщению об одном из множества событий, которые могут произойти с ползунком (например, управление клавишей левой стрелки — SB_LINELEFT). В зависимости от события мы выбираем для ползунка новую позицию:

void CPropDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)

{

//====== Windows-описатель окна активного ползунка

HWND hwnd = pScrollBar->GetSafeHwnd();

UINT nID;



//=== Определяем индекс в массиве позиций ползунков

int num = GetSliderNum(hwnd, nID) ;

int delta, newPos;

//====== Анализируем код события

switch (nSBCode)

{

case SBJTHUMBTRACK:

case SB_THUMBPOSITION: // Управление мышью

m_Pos[num] = nPos;

break; case SB_LEFT: // Клавиша Home

delta = -100;

goto New_Pos; case SB_RIGHT: // Клавиша End

delta = + 100;

goto New__Pos; case SB_LINELEFT: // Клавиша <-

delta = -1;

goto New_Pos; case SB_LINERIGHT: // Клавиша ->

delta = +1;

goto New_Pos; case SB_PAGELEFT: // Клавиша PgUp

delta = -20;

goto New_Pos; case SB_PAGERIGHT: // Клавиша PgDn

delta = +20-;

goto New_Pos;

New_Pos: // Общая ветвь

//====== Устанавливаем новое значение регулятора

newPos = m_Pos[num] + delta;

//====== Ограничения

m_Pos[num] = newPos<0 ? 0 :

newPos>100 ? 100 : newPos;

break; case SB ENDSCROLL:

default:

return;

}

//====== Синхронизируем текстовый аналог позиции

char s [ 8 ] ;

sprintf (s, "%d",m__Pos [num] ) ;

SetDlgltemText (nID, (LPCTSTR)s);

//---- Передаем изменение в класс COGView

m_pView->SetLightParam (num, m_Pos [num] ) ;

}

Особенности немодального режима

Рассматриваемый диалог используется в качестве панели управления освещением сцены, поэтому он должен работать в немодальном режиме. Особенностью такого режима, как вы знаете, является то, что при закрытии диалога он сам должен позаботиться об освобождении памяти, выделенной под объект собственного класса. Эту задачу можно решить разными способами. Здесь мы покажем, как это делается в функции обработки сообщения WM_CLOSE. До того как уничтожено Windows-окно диалога, мы обнуляем указатель m_pDlg, который должен храниться в классе COGView и содержать адрес объекта диалогового класса. Затем вызываем родительскую версию функции OnClose, которая уничтожает Windows-окно. Только после этого мы можем освободить память, занимаемую объектом своего класса:

void CPropDlg: :OnClose (void)

{

//=== Обнуляем указатель на объект своего класса



m_pView->m_pDlg = 0;

//====== Уничтожаем окно

CDialog: :OnClose () ;

//====== Освобождаем память

delete this;

}

Реакция на нажатие кнопки IDC_FILENAME совсем проста, так как основную работу выполняет класс COGView. Мы лишь вызываем функцию, которая реализована в этом классе:

void CPropDlg:: OnClickedFilename (void)

{

//=== Открываем файловый диалог и читаем данные

m_pView->ReadData ( ) ;

}

Создание немодального диалога должно происходить в ответ на выбор команды меню Edit > Properties. Обычно объект диалогового класса, используемого в немодальном режиме, создается динамически. При этом предполагается, что класс родительского окна хранит указатель m_pDlg на объект класса диалога. Значение указателя обычно используется не только для управления им, но и как признак его наличия в данный момент. Это позволяет правильно обработать ситуацию, когда диалог уже существует и вновь приходит команда о его открытии. Введите в класс COGView новую public-переменную:

CPropDlg *m_pDlg; // Указатель на объект диалога

В начало файла заголовков OGView.h вставьте упреждающее объявление класса

CPropDlg:

class CPropDlg; // Упреждающее объявление

В конструктор COGView вставьте обнуление указателя:

m_pDlg =0; // Диалог отсутствует

Для обеспечения видимости класса CPropDlg дополните список директив препроцессора файла OGView.cpp директивой:

linclude "PropDlg.h"

Теперь можно ввести коды функции, которая создает диалог и запускает его вызовом функции Create (в отличие от DoModal для модального режима). Если происходит попытка повторного открытия диалога, то возможны два варианта развития событий:

новый диалог не создается, но окно существующего диалога делается активным;

команда открытия диалога недоступна, так как ее состояние зависит от значения указателя m_pDlg.

Реализуем первый вариант:

void COGView::OnEditProperties (void)

{

//====== Если диалог еще не открыт

if (!m_pDlg)

{

//=== Создаем его и запускаем в немодальном режиме



m_pDlg = new CPropDlg(this);

m_pDlg->Create(IDD_PROP);

}

else

// Иначе, переводим фокус в окно диалога

m_pDlg->SetActiveWindow();

}

Реакция на команду обновления пользовательского интерфейса при этом может быть такой:

void COGView::OnUpdateEditProperties(CCmdUI *pCmdUI)

{

pCmdUI->SetCheck (m_pDlg != 0);

}

Второй вариант потребует меньше усилий:

void COGView::OnEditProperties (void)

{

m_pDlg = new CPropDlg(this);

m_pDlg->Create(IDD_PROP); }

Но при этом необходима другая реакция на команду обновления интерфейса:

void COGView::OnUpdateEditProperties(CCmdUI *pCmdUI)

{

pCmdUI->Enable(m_pDlg == 0);

}

Выберите и реализуйте один из вариантов.

Панель управления

Завершая разработку приложения, вставьте в панель управления четыре кнопки

Для команд ID_EDIT_BACKGROUND, ID_EDIT_PROPERTIES, ID_VIEW_FILL И ID_VIEW_

QUAD. Заодно уберите из нее неиспользуемые нами кнопки с идентификаторами

ID_FILE_NEW, ID_FILE_OPEN, ID_FILE_SAVE, ID_FILE_PRINT, ID__EDIT_CUT,

ID_EDIT_COPY, ID_EDIT_PASTE. Запустите приложение, включите диалог Edit > Properties и попробуйте управлять регуляторами параметров света. Отметьте, что далеко не все из них отчетливым образом изменяют облик поверхности. Нажмите кнопку Data File, при этом должен открыться файловый диалог, но мы не сможем открыть никакого другого файла, кроме того, что был создан по умолчанию. Он имеет имя «Sin.dat» и должен находиться (и быть виден) в папке проекта. В качестве упражнения создайте какой-либо другой файл с данными, отражающими какую-либо поверхность в трехмерном пространстве. Вы можете воспользоваться для этой цели функцией DefaultGraphic, немного модифицировав ее код. На рис. 7.5 и 7.6 приведены поверхности, полученные таким способом. Вы можете видеть эффект, вносимый различными настройками параметров освещения.

Если вы тщательно протестируете поведение приложения, то обнаружите недостатки. Отметим один из них. Закрытые части изображения при некотором ракурсе просвечивают сквозь те части поверхности, которые находятся ближе к наблюдателю. Причину этого дефекта было достаточно трудно выявить. И здесь опять пришли на помощь молодые, талантливые слушатели Microsoft Authorized Educational Center (www.Avalon.ru) Кондрашов С. С. (scondor@rambler.ru) и Фролов Д. С. (dmfrolov@rambler.ru). Оказалось, что при задании типа проекции с помощью команды gluPerspective значения ближней границы фрустума не должны быть слишком маленькими:



gluPerspective (45., dAspect, 0.01, 10000.);

В нашем случае этот параметр равен 0.01. Замените его на 10. и сравните качество генерируемой поверхности.

Подведем итог. В этой главе мы:

научились превращать окно, поддерживаемое классом cview, в окно OpenGL;

вновь использовали стандартный контейнер объектов класса GPoint3D, который удобен для хранения вершин изображаемой поверхности;





Рис. 7.5. Вид поверхности, освещенной слева





Рис. 7.6. Вид той же поверхности, но освещенной справа

убедились, что использование списка команд OpenGL повышает эффективность передачи сложного изображения;

применили формулу вычисления нормали к поверхности и убедились в необходимости уделять этой проблеме достаточное внимание;

научились управлять освещенностью сцены OpenGL с помощью группы регуляторов;

оценили удобство управления группой регуляторов типа slider Control в функции обработки сообщения о прокрутке WM_HSCROLL.


График по умолчанию Пришла пора


COGView::DefaultGraphic()

{

//====== Размеры сетки узлов

m xSize = m zSize = 33;

//====Число ячеек на единицу меньше числа узлов

UINTnz = m_zSize - 1, nx = m_xSize - 1;

// Размер файла в байтах для хранения значений функции

DWORD nSize = m_xSize * m_zSize * sizeof (float) + 2*sizeof (UINT) ;

//====== Временный буфер для хранения данных

BYTE *buff = new BYTE[nSize+l] ;

//====== Показываем на него указателем целого типа

UINT *p = (UINT*)buff;

//====== Размещаем данные целого типа

*р++ = m_xSize;

*р++ = m_zSize;

//====== Меняем тип указателя, так как дальше

//====== собираемся записывать вещественные числа

float *pf = (float*)?;

//=== Предварительно вычисляем коэффициенты уравнения

double fi = atan(l.)*6,

kx = fi/nx,

kz = fi/nz;

//====== В двойном цикле пробега по сетке узлов

//=== вычисляем и помещаем в буфер данные типа float

for (UINT i=0; i<ra_zSize;

for (UINT j=0; j<m_xSize;

{

*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, SnBytes, 0) ;

//====== Закрываем файл

CloseHandle (hFile) ;

//====== Создание динамического массива m_cPoints

SetGraphPoints (buff, nSize) ;

//====== Освобождаем временный буфер

delete [] buff;

}

В процессе создания, открытия и записи в файл мы пользуемся API-функциями CreateFile, WriteFile и CloseHandle, которые предоставляют значительно больше возможностей управлять файловых хозяйством, чем, например, методы класса CFile или функции из библиотек stdio.h или iostream.h. Обратитесь к документации, для того чтобы получить представление о них.



Настройка проекта


На странице VS Home Page выберите команду (гиперссылку) Create New Project.

В окне диалога New Project выберите уже знакомый вам тип проекта: MFC Application, задайте имя проекта OG и нажмите ОК.

В окне мастера MFC Application Wizard выберите вкладку Application Type и задайте такие настройки проекта: Single documents, MFC Standard, Document/View architecture support, Use MFC in a shared DLL.

Перейдите на страницу Advanced Features диалога и снимите флажки Printing and print preview, ActiveX Controls, так как мы не будем использовать эти возможности.

Нажмите кнопку Finish.

Этот тип стартовой заготовки позволяет работать с окном (cocview), которое помещено в клиентскую область окна-рамки (CMainFrame), и создать в этом окне контекст передачи OpenGL. Класс документа нам не понадобится, так как мы собираемся производить все файловые операции самостоятельно, используя свой собственный двоичный формат данных. В связи с этим нам не нужна помощь в сериализации данных, которую предоставляет документ. Для использования функций библиотеки OpenGL надо сообщить компоновщику, чтобы он подключил необходимые библиотеки OpenGL, на сей раз только две.

Поставьте фокус на элемент OG в окне Solution Explorer и дайте Команду Project > Properties (или ее эквивалент View > Property Pages).

В окне открывшегося диалога OG Property Pages выберите элемент дерева Linker > Input.

Переведите фокус в поле Additional Inputs окна справа и добавьте в конец существующего текста имена файлов с описаниями трех библиотек: OPENGL32.LIB GLU32.LIB. Убедитесь в том, что все имена разделены пробелами и нажмите ОК.

Чтобы покончить с настройками общего характера, вставьте в конец файла StdAfx.h строки, которые обеспечивают видимость библиотеки OpenGL, а также некоторых ресурсов библиотеки STL:

#include <afxdlgs.h>

#include <raath.h>

//=== Подключение заголовков библиотек OpenGL

#include <gl/gl.h>

#include <gl/glu.h>

#include <vector>

using namespace std;



Подготовка изображения Разработаем


COGView: : DrawScene ()

{

//====== Создание списка рисующих команд

glNewList (1, GL_COMPILE) ;

//====== Установка режима заполнения

//====== внутренних точек полигонов

glPolygonMode (GL_FRONT_AND_BACK, m_FillMode) ;

//====== Размеры изображаемого объекта

UINTnx = m_xSize-l, nz = m_zSize-l;

//====== Выбор способа создания полигонов

if (m_bQuad)

glBegin (GL_QUADS) ;

//====== Цикл прохода по слоям изображения (ось Z)

for (UINT z=0, i=0; z<nz; z++)

//====== Связанные полигоны начинаются

//====== на каждой полосе вновь

if (!m_bQuad)

glBegin (GLJ2UAD_STRIP) ;

//====== Цикл прохода вдоль оси X

for (UINT x=0; x<nx; x++)

// i, j, k, n — 4 индекса вершин примитива при

// обходе в направлении против часовой стрелки

int j = i + m_xSize, // Индекс узла с большим Z

k = j+1/ // Индекс узла по диагонали

n = i+1; // Индекс узла справа

//=== Выбор координат 4-х вершин из контейнера

float

xi = m_cPoints [i] .x,

yi = m_cPoints [i] .у,

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,

//=== Координаты векторов боковых сторон ах = 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;

//====== Задание вектора нормали

glNorma!3f (vx,vy,vz);

// Ветвь создания несвязанных четырехугольников

if (m_bQuad)

{

//==== Обход вершин осуществляется

//==== в направлении против часовой стрелки

glColorSf (0.2f, 0.8f, l.f);

glVertexSf (xi, yi, zi) ;

glColor3f (0.6f, 0.7f, l.f);

glVertexSf (xj, у j , zj);

glColorSf (0.7f, 0.9f, l.f);

glVertexSf (xk, yk, zk) ;

glColor3f (0.7f, 0.8f, l.f);

glVertexSf (xn, yn, zn) ;


}

else

//==== Ветвь создания цепочки четырехугольников

{

glColor3f (0.9f, 0.9f, l.0f);

glVertexSf (xn, yn, zn) ;

glColorSf (0.5f, 0.8f, l.0f);

glVertexSf (xj, у j , zj);

//====== Закрываем блок команд GL_QUAD_STRIP

if (!m_bQuad) glEnd ( ) ; } //====== Закрываем блок команд GL_QUADS

if (m_bQuad)

glEnd() ;

// ====== Закрываем список команд OpenGL

glEndList() ;

}

При анализе кода обратите внимание на тот факт, что вектор нормали вычисляется по упрощенной формуле, так как линии сетки узлов, над которой расположены вершины поверхности, параллельны осям координат (X, Z). В связи с этим равны нулю компоненты az и bх векторов боковых сторон в формуле для нормали (см. раздел «Точное вычисление нормалей» в предыдущем уроке).


Подготовка окна Вы помните, что


CView::PreCreateWindow(cs);

}

Вы помните, что окно OpenGL не должно позволять Windows стирать свой фон, так как данная операция сильно тормозит работу конвейера. В связи с этим введите в функцию обработки WM_ERASEBKGND код, сообщающий системе, что сообщение уже обработано:

BOOL COGView::OnEraseBkgnd(CDC* pDC)

{

return TRUE;

}

Окно OpenGL имеет свой собственный формат пикселов. Нам следует выбрать и установить подходящий формат экранной поверхности в контексте устройства HDC, а затем создать контекст передачи изображения (HGLRC). Для описания формата пикселов экранной поверхности используется структура PIXELFORMATDESCRIPTOR. Выбор формата зависит от возможностей карты и намерений разработчика. Мы зададим в полях этой структуры такие настройки:

глубину цвета — 24;

тип буферизации — двойной;

схему образования цвета RGBA;

количество бит для буфера глубины — 32;

поддержку регулировки прозрачностью и другие специфические настройки выключим.

В функцию OnCreate введите код подготовки окна OpenGL. Работа здесь ведется со структурой PIXELFORMATDESCRIPTOR. Кроме того, в ней создается контекст m_hRC и устанавливается в качестве текущего:

int COGView::OnCreate(LPCREATESTROCT IpCreateStruct)

{

if (CView::OnCreate(IpCreateStruct) == -1)

return -1;

PIXELFORMATDESCRIPTOR pfd = // Описатель формата

{

sizeof(PIXELFORMATDESCRIPTOR), // Размер структуры

1, // Номер версии

PFD_DRAW_TO_WINDOW | // Поддержка GDI

PFD_SUPPORT_OPENGL | // Поддержка OpenGL

PFD_DOUBLEBUFFER, // Двойная буферизация

PFD_TYPE_RGBA, // Формат RGBA, не палитра

24, // Количество плоскостей

//в каждом буфере цвета

24, 0, // Для компонента Red

24, 0, // Для компонента Green

24, 0, // Для компонента Blue

24, 0, // Для компонента Alpha

0, // Количество плоскостей

// буфера Accumulation

0, // То же для компонента Red

0, // для компонента Green

0, // для компонента Blue

0, // для компонента Alpha

32, // Глубина 2-буфера

0, // Глубина буфера Stencil


0, // Глубина буфера Auxiliary

0, // Теперь игнорируется

0, // Количество плоскостей

0, // Теперь игнорируется

0, // Цвет прозрачной маски

0 // Теперь игнорируется };

//====== Добываем дежурный контекст

m_hdc = ::GetDC(GetSafeHwnd());

//====== Просим выбрать ближайший совместимый формат

int iD = ChoosePixelForraat(m_hdc, spfd);

if ( !iD )

{

MessageBoxC'ChoosePixelFormat: :Error") ;

return -1;

}

//====== Пытаемся установить этот формат

if ( ISetPixelFormat (m_hdc, iD, Spfd) )

{

MessageBox("SetPixelFormat::Error");

return -1;

}

//====== Пытаемся создать контекст передачи OpenGL

if ( !(m_hRC = wglCreateContext (m_hdc)))

{

MessageBox("wglCreateContext::Error");

return -1;

}

//====== Пытаемся выбрать его в качестве текущего

if ( IwglMakeCurrent (m_hdc, m_hRC))

{

MessageBox("wglMakeCurrent::Error");

return -1;

//====== Теперь можно посылать команды OpenGL

glEnable(GL_LIGHTING); // Будет освещение

//====== Будет только один источник света

glEnable(GL_LIGHTO);

//====== Необходимо учитывать глубину (ось Z)

glEnable(GL_DEPTH_TEST);

//====== Необходимо учитывать цвет материала поверхности

glEnable(GL_COLOR_MATERIAL);

//====== Устанавливаем цвет фона .

SetBkColor () ;

//====== Создаем изображение и запоминаем в списке

DrawScene () ;

return 0;

}

Контекст передачи ( rendering context) создается функцией wglCreateContext с учетом выбранного формата пикселов. Так осуществляется связь OpenGL с Windows. Создание контекста требует, чтобы обычный контекст существовал и был явно указан в параметре wglCreateContext. HGLRC использует тот же формат пикселов, что и НОС. Мы должны объявить контекст передачи в качестве текущего (current) и лишь после этого можем делать вызовы команд OpenGL, которые производят включение некоторых тумблеров в машине состояний OpenGL. Вызов функции DrawScene, создающей и запоминающей изображение, завершает обработку сообщения. Таким образом, сцена рассчитывается до того, как приходит сообщение о перерисовке WM_PAINT. Удалять контекст передачи надо после отсоединения его от потока. Это делается в момент, когда закрывается окно представления. Введите в тело заготовки OnDestroy следующие коды:



void COGView::OnDestroy(void)

{

//====== Останавливаем таймер анимации

KillTimer(1);

//====== Отсоединяем контекст от потока

wglMakeCurrent(0, 0); //====== Удаляем контекст

if (m_hRC)

{

wglDeleteContext(m_hRC);

m_hRC = 0;

}

CView::OnDestroy() ;

}

Так же как и в консольном проекте OpenGL, обработчик сообщения WM_SIZE должен заниматься установкой прямоугольника просмотра (giviewport) и мы, так же как и раньше, зададим его равным всей клиентской области окна. -Напомним, что конвейер OpenGL использует эту установку для того, чтобы поместить изображение в центр окна и растянуть или сжать его пропорционально размерам окна. Кроме того, в обработке onSize с помощью матрицы проецирования (GL_PROJECTION) задается тип проекции трехмерного изображения на плоское окно. Мы выбираем центральный или перспективный тип проецирования и задаем при этом угол зрения равным m_AngleView. В конструкторе ему было присвоено значение в 45 градусов:

void COGView::OnSize(UINT nType, int ex, int cy)

{

//====== Вызов родительской версии

CView::OnSize(nType, ex, cy) ;

//====== Вычисление диспропорций окна

double dAspect = cx<=cy ? double(cy)/ex : double(ex)/cy;

glMatrixMode (GL_PROJECTION) ;

glLoadldentity() ;

//====== Установка режима перспективной проекции

gluPerspective (m_AngleView, dAspect, 0.01, 10000.);

//====== Установка прямоугольника просмотра

glViewport(0, 0, сх, су);

}


Работа с контейнером Для работы


COGView::SetGraphPoints(BYTE* buff, DWORD nSize)

{

//====== Готовимся к расшифровке данных буфера

//====== Указываем на него указателем целого типа

UINT *p = (UINT*)buff;

//=== Выбираем данные целого типа, сдвигая указатель

m_xSize = *р; m_zSize = *++p;

//====== Проверка на непротиворечивость

if (m_xSize<2 m_zSize<2

m_xSize*m_zSize*sizeof(float)

+ 2 * sizeof(UINT) != nSize)

{

MessageBox (_T ("Данные противоречивы") ) ;

return;

}

//====== Изменяем размер контейнера

//====== При этом его данные разрушаются

m_cPoints . resize (m_xSize*m_zSize) ;

if (m_cPoints .empty () )

{

MessageBox (_T ("He возможно разместить данные")

return;

}

//====== Подготовка к циклу пробега по буферу

//====== и процессу масштабирования

float x, z,

//====== Считываем первую ординату

*pf = (float*) ++р,

fMinY = *pf,

fMaxY = *pf,

right = (m_xSize-l) /2 . f ,

left = -right,

read = (m_zSize-l) /2 . f ,

front = -rear,

range = (right + rear) /2. f;

UINTi, j, n;

//====== Вычисление размаха изображаемого объекта

m_fRangeY = range;

m_fRangeX = float (m_xSize) ;

m_fRangeZ = float (m_zSize) ;

//====== Величина сдвига вдоль оси Z

m_zTrans = -1.5f * m_fRangeZ;

//====== Генерируем координаты сетки (X-Z)

//====== и совмещаем с ординатами Y из буфера

for (z=front, i=0, n=0; i<m_zSize; i++, z+=l.f)

{

for (x=left, j=0; j<m_xSize; j++, x+=l.f, n++)

{

MinMax (*pf, fMinY, fMaxY) ;

m_cPoints[n] = CPoint3D(x, z, *pf++) ;

}

}

//====== Масштабирование ординат

float zoom = fMaxY > fMinY ? range/ (fMaxY-fMinY)

: l.f;

for (n=0; n<m_xSize*m_zSize;n++)

{

m_cPoints [n] . у = zoom * (m_cPoints [n] . у - fMinY) - range/2. f;

}

}

При изменении размеров контейнера методом (resize) все его данные разрушаются. В двойном цикле пробега по узлам сетки мы восстанавливаем (генерируем заново) координаты X и Z всех вершин четырехугольников. В отдельном цикле пробега по всему контейнеру происходит масштабирование ординат (умножение на предварительно вычисленный коэффициент zoom). В используемом алгоритме необходимо искать экстремумы функции у = f (x, z). С этой целью удобно иметь глобальную функцию MinMax, которая корректирует значение минимума или максимума, если входной параметр превышает существующие на сей момент экстремумы. Введите тело этой функции в начало файла реализации оконного класса (ChildView.cpp):

inline void MinMax (float d, floats Min, float& Max)

{

//====== Корректируем переданные по ссылке параметры

if (d > Max)

Max = d; // Претендент на максимум

else if (d < Min)

Min = d; // Претендент на минимум

}



Реакции на сообщения Windows Вспомните


//====== Правая кнопка не была нажата

m_bRightButton = false;

//====== Рисуем четырехугольниками

m_bQuad = true;

//====== Начальный значения параметров освещения

m_LightParam[0] = 50; // X position

m_LightParam[l] = 80; // Y position

m_LightParam[2] = 100; // Z position

m_LightParam[3] = 15; // Ambient light

m_LightParam[4] = 70; // Diffuse light

m_LightParam[5] = 100; // Specular light

m_LightParam[6] = 100; // Ambient material

m_LightParam[7] = 100; // Diffuse material

m_LightParam[8] = 40; // Specular material

m_LightParam[9] = 70; // Shininess material

m_LightParam[10] =0; // Emission material

}



Реакция на сообщение о перерисовке


COGView::SetLight()

{

//====== Обе поверхности изображения участвуют

//====== при вычислении цвета пикселов

//====== при учете параметров освещения

glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);

//====== Позиция источника освещения

//====== зависит от размеров объекта

float fPos[] =

{

(m_LightParam[0]-50)*m_fRangeX/100,

(m_LightParam[l]-50)*m_fRangeY/100,

(m_LightParam[2]-50)*m_fRangeZ/100,

l.f

};

glLightfv(GL_LIGHTO, GL_POSITION, fPos);

/1 ====== Интенсивность окружающего освещения

float f = m_LightParam[3]/100.f;

float fAmbient[4] = { f, f, f, O.f };

glLightfv(GL_LIGHTO, GL_AMBIENT, fAmbient);

//====== Интенсивность рассеянного света

f = m_LightParam[4]/100.f;

float fDiffuse[4] = { f, f, f, O.f };

glLightfv(GL_LIGHTO, GL_DIFFUSE, fDiffuse);

//====== Интенсивность отраженного света

f = m_LightParam[5]/100.f;

float fSpecular[4] = { f, f, f, 0.f };

glLightfv(GL_LIGHTO, 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 f Emission [4] = { f , f , f , 0 . f } ;

glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, fEmission) ;

}



Трехмерные графики функций


Настройка проекта

Реакции на сообщения Windows

Подготовка окна

Работа с контейнером

Чтение данных

Управление изображением с помощью мыши

Включаем анимацию

Ввод новых команд

Диалог по управлению светом

В этой главе мы разработаем Windows-приложение, которое в контексте OpenGL изображает трехмерный график функции, заданной произвольным массивом чисел. Данные для графика могут быть прочтены из файла, на который указывает пользователь. Кроме этого, пользователь будет иметь возможность перемещать график вдоль трех пространственных осей, вращать его вокруг вертикальной и горизонтальной осей и просматривать как в обычном, так и скелетном режим. Регулируя параметры освещения поверхности, пользователь может добиться наибольшей реалистичности изображения, то есть усилить визуальный эффект трехмерного пространства на плоском экране.

Графики могут представлять собой результаты расчета какого-либо физического поля, например поверхности равной температуры, давления, скорости, индукции, напряжения и т. д. в части трехмерного пространства, называемой расчетной областью. Пользователь объекта должен заранее подготовить данные и записать их в определенном формате в файл. Объект по команде пользователя считывает данные, нормирует, масштабирует и изображает в своем окне, внедренном в окно приложения-клиента. Пользователь, манипулируя мышью, управляет местоположением и вращением графика, а открыв окно диалога Properties, изменяет другие его атрибуты.



Управление изображением с помощью


COGView: :OnLButtonDown (UINT nFlags, CPoint point)

{

//====== Останавливаем таймер

KillTimer(1);

//====== Обнуляем кванты перемещения

m_dx = 0.f; m_dy = 0.f;

//====== Захватываем сообщения мыши,

//====== направляя их в свое окно

SetCapture ();

//====== Запоминаем факт захвата

m_bCaptured = true;

//====== Запоминаем координаты курсора

m_pt = point;

}

При нажатии на правую кнопку необходимо выполнить те же действия, что и при нажатии на левую, но дополнительно надо запомнить сам факт нажатия правой кнопки, с тем чтобы правильно интерпретировать последующие сообщения о перемещении указателя мыши и вместо вращения производить сдвиг вдоль оси Z:

void COGView::OnRButtonDown(UINT nFlags, CPoint point)

{

//====== Запоминаем факт нажатия правой кнопки

m_bRightButton = true;

//====== Воспроизводим реакцию на левую кнопку

OnLButtonDown(nFlags, point);

}

В обработчик отпускания левой кнопки мы вводим анализ на необходимость продолжения вращения с помощью таймера. В случае превышения порога чувствительности, запускаем таймер, сообщения от которого будут говорить, что надо продолжать вращение, поддерживая текущее значение скорости:

void COGView::OnLButtonUp(UINT nFlags, CPoint point)

{

//====== Если был захват,

if (m_bCaptured)

//=== то анализируем желаемый квант перемещения

//=== на превышение порога чувствительности

if (fabs(m_dx) > 0.5f fabs(m_dy) > 0.5f)

//=== Включаем режим постоянного вращения

SetTimer(1,33,0);

else

//=== Выключаем режим постоянного вращения

KillTimer(1);

//====== Снимаем флаг захвата мыши

m_bCaptured = false;

//====== Отпускаем сообщения мыши

ReleaseCapture();

}

}

Отпускание правой кнопки должно просто отмечать факт прекращения перемещения вдоль оси Z и отпускать сообщения мыши для того, чтобы они работали на другие окна, в том числе и на наше окно-рамку. Если этого не сделать, то станет невозможным использование меню главного окна. Проверьте, если хотите. Для этого достаточно закомментировать вызов функции ReleaseCapture в обеих функциях:


void COGView::OnRButtonUp(UINT nFlags, CPoint point)

{

//====== Правая кнопка отпущена

m_bRightButton = false;

//====== Снимаем флаг захвата мыши

m_bCaptured = false;

//====== Отпускаем сообщения мыши

ReleaseCapture();

}

Теперь реализуем самую сложную часть алгоритма — реакцию на перемещение курсора. Здесь мы должны оценить желаемую скорость вращения. Она зависит от того, насколько резко пользователь подвинул объект, то есть оценить модуль разности двух последних позиций курсора, В этой же функции надо выделить случай одновременного нажатия служебной клавиши Ctrl Если она нажата, то интерпретация движения мыши при нажатой левой кнопке изменяется. Теперь вместо вращения мы должны сдвигать объект, то есть пропорционально изменять переменные m_xTrans и m_yTrans, которые затем подаются на вход функции glTranslate. Третья ветвь алгоритма обрабатывает движение указателя при нажатой правой кнопке. Здесь необходимо изменять значение переменной m_zTrans, обеспечивая сдвиг объекта вдоль оси Z. Числовые коэффициенты пропорциональности, которые вы видите в коде функции, влияют на чувствительность мыши и подбираются экспериментально. Вы можете изменить их на свой вкус так, чтобы добиться желаемой управляемости изображения:

void COGView::OnMouseMove(UINT nFlags, CPoint point)

{

if (m_bCaptured) // Если был захват,

{

// Вычисляем компоненты желаемой скорости вращения

m_dy = float (point .у - m_pt .у) /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)

//====== Усредняем величину сдвига

m_zTrans += (m_dx + m_dy)/2.f;

else

{

//====== Иначе, изменяем углы поворота

m_AngleX += m_dy;

m_AngleY += m_dx;

}

}

//=== В любом случае запоминаем новое положение мыши

m_pt = point; Invalidate (FALSE) ;

}

}

Запустите и проверьте управляемость объекта. Введите коррективы чувствительности на свой вкус. Попробуйте скорректировать эффект влияния поворота вокруг оси X на интерпретацию знака желаемого вращения вокруг оси Y. Здесь можно воспользоваться стеком матриц моделирования. Теперь добавим код в заготовку функции реакции на сообщения таймера с тем, чтобы ввести фиксацию состояния вращения.


Установка цвета фона Введите вспомогательную


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) ;

}



Включаем анимацию Реакция на сообщение


COGView: :OnTimer (UINT nIDEvent)

{

//====== Если это был наш таймер

if (nIDEvent==l)

{

//====== Увеличиваем углы поворота

m_AngleX += m_dy;

m_AngleY += m_dx;

//====== Ограничители роста углов

if (m_AngleX > 360)

m_AngleX -= 360;

if (m_AngleX <-360)

m_AngleX += 360;

if (m_AngleY > 360)

m_AngleY -=360;

if (m_AngleY <-360)

m_AngleY +=360;

//====== Просим перерисовать окно

Invalidate(FALSE);

}

else

//=== Каркас приложения обработает другие таймеры

CView::OnTimer(nIDEvent);

}

Запустите и протестируйте приложение. Скорректируйте, если необходимо, коэффициенты чувствительности.



Вспомогательный класс Нам вновь


CPointSD

{

public: //====== Координаты точки

float x;

float у;

float z;

//====== Набор конструкторов

CPointSD ()

{

х = у - z = 0.f;

}

CPoint3D (float cl, float c2, float c3)

{

x = cl; z = c2; У = сЗ; ,

}

//====== Операция присвоения

CPoint3DS operator= (const CPointSDS pt)

x = pt.x; z = pt.z;

return *this;

У = pt.y;

//====== Конструктор копирования

CPointSD (const CPoint3D& pt)

{

*this = pt;

//=========== Класс окна OpenGL

class COGView :

public CView

{

protected:

COGView () ;

DECLARE_DYNCREATE(COGView)

public:

virtual ~COGView();

virtual void OnDraw(CDC* pDC) ;

virtual BOOL PreCreateWindow(CREATESTRUCT& cs) ,

//======= Новые данные класса

long m_BkClr; //

int m_LightParara[ll]; //

HGLRC m_hRC; //

HDC m_hdc; //

GLfloat m_AngleX; //

GLfloat m_AngleY; //

GLfloat m_AngleView; //

GLfloat m_fRangeX; //

GLfloat m_fRangeY; //

GLfloat m_fRangeZ; //

GLfloat m_dx; //

GLfloat m_dy; //

GLfloat m_xTrans; //

GLfloat m_yTrans; //

GLfloat m_zTrans; //

GLenura m_FillMode; //

bool m_bCaptured; //

bool m_bRightButton; //

bool m_bQuad; //

CPoint m_pt; //

UINT m_xSize; //

UINT m_zSize; //

//====== Массив вершин поверхности

vector <CPoint3D> m_cPoints;

//====== Новые методы класса

//=-==== Подготовка изображения

void DrawScene();

Цвет фона окна Параметры освещения Контекст OpenGL Контекст Windows Угол поворота вокруг оси X Угол поворота вокруг оси Y Угол перспективы Размер объекта вдоль X Размер объекта вдоль Y Размер объекта вдоль Z Квант смещения вдоль X Квант смещения вдоль Y Смещение вдоль X Смещение вдоль Y Смещение вдоль Z Режим заполнения полигонов Признак захвата мыши Флаг правой кнопки мыши Флаг использования GL_QUAD Текущая позиция мыши Текущий размер окна вдоль X Текущий размер окна вдоль Y

//====== Создание графика по умолчанию

void DefaultGraphic();

//====== Создание массива по данным из буфера

void SetGraphPoints(BYTE* buff, DWORD nSize);

//====== Установка параметров освещения

void SetLight();

//====== Изменение одного из параметров освещения

void SetLightParam (short lp, int nPos);

//====== Определение действующих параметров освещения

void GetLightParams(int *pPos); //====== Работа с файлом данных

void ReadData();

//====== Чтение данных из файла

bool DoRead(HANDLE hFile);

//====== Установка Работа с файлом данных

void SetBkColor();

DECLARE MESSAGE MAP()



Ввод новых команд Вы заметили


COGView::OnEditBackground(void)

{

//====== Создаем объект диалогового класса

CColorDialog dig(m_BkClr); //====== Устанавливаем бит стиля

dig.m_cc.Flags |= CC_FULLOPEN;

//====== Запускаем диалог и выбираем результат

if (cilg.DoModal ()==IDOK)

{

m_BkClr = dig.m_cc.rgbResuit;

//====== Изменяем цвет фона

SetBkColor();

Invalidate(FALSE);

}

}

Проверьте результат, запустив приложение и вызвав диалог. При желании создайте глобальный массив с 16 любимыми цветами и присвойте его адрес переменной lpCustColors, которая входит в состав полей структуры m_сс, являющейся членом класса CColorDialog. В этом случае пользователь сможет подобрать и запомнить некоторые цвета.

В обработчик OnViewQuad введите коды, инвертирующие булевский признак m_bQuad, который мы используем как флаг необходимости рисования отдельными четырехугольниками (GL_QUADS), и заново создают изображение. Если признак инвертирован, то мы рисуем полосами (GL_QUAD_STRIP):

void COGView::OnViewQuad(void)

{

// Инвертируем признак стиля задания четырехугольников

m_bQuad = ! m_bQuad;

//====== Заново создаем изображение

DrawScene (); Invalidate(FALSE); UpdateWindow();

}

В обработчик команды обновления интерфейса введите коды, которые обеспечивают появление маркера выбора рядом с командой меню (или залипания кнопки панели управления):

void COGView::OnUpdateViewQuad(CCmdUI* pCmdUI)

{

//====== Вставляем или убираем маркер (пометку)

pCmdUI->SetCheck(m_bQuad==true);

}

Проверьте результат и попробуйте объяснить зубчатые края поверхности (рис. 7.2). Не знаю, правильно ли я поступаю, когда по ходу изложения вставляю задачи подобного рода. Но мной движет желание немного приоткрыть дверь в кухню разработчика и показать, что все не так уж просто. Искать ошибки в алгоритме, особенно чужом, является очень кропотливым занятием. Однако совершенно необходимо приобрести этот навык, так как без него невозможна работа в команде, а также восприятие новых технологий, раскрываемых в основном посредством анализа содержательных (чужих) примеров (Samples). Чтобы обнаружить ошибку подобного рода, надо тщательно проанализировать код, в котором создается изображение (ветвь GL_QUAD_STRIP), и понять, что неправильно выбран индекс вершины. Замените строку givertex3f (xn, yn, zn); HaglVertexSf (xi, yi, zi); и вновь проверьте работу приложения. Зубчатость края должна исчезнуть, но в алгоритме, тем не менее, осталась еще небольшая, слабо заметная неточность. Ее обнаружение и исправление я оставляю вам, дорогой читатель.




Рис. 7.2. Вид поверхности при использовании режима GL_QUAD_STRIP

Обработку следующей команды меню мы проведем в том же стиле, за исключением того, что переменная m_FillMode не является булевской, хоть и принимает лишь два значения (GL_FILL и GL_LINE). Из материала предыдущей главы помните, возможен еще одни режим изображения полигонов — GL_POINT. Логику его реализации при желании вы введете самостоятельно, а сейчас введите коды двух функции обработки команды меню:

void COGView::OnViewFill(void)

{

//=== Переключаем режим заполнения четырехугольника

m_FillMode = m_FillMode==GL_FILL ? GL_LINE : GL__FILL;

//====== Заново создаем изображение

DrawScene();

Invalidate(FALSE);

UpdateWindow() ;

}

void COGView::OnUpdateViewFill(CCmdUI *pCmdUI)

{

//====== Вставляем или убираем маркер выбора

pCmdUI->SetCheck(m_FillMode==GL_FILL) ;

}

Запустите и проверьте работу команд меню. Отметьте, что формула учета освещения работает и в случае каркасного изображения примитивов (рис. 7.3).



Рис. 7.3. Вид поверхности, созданной в режиме GL_LINE

Для обмена с диалогом по управлению освещением нам понадобятся две вспомогательные функции GetLightParams и SetLightParam. Назначение первой из которых заполнить массив переменных, отражающих текущее состояние параметров освещения сцены OpenGL. Затем этот массив мы передадим в метод диалогового класса для синхронизации движков (sliders) управления. Вторая функция позволяет изменить отдельный параметр и привести его в соответствие с положением движка. Так как мы насчитали 11 параметров, которыми хотим управлять, то придется ввести в окно диалога 11 регуляторов, которым соответствует массив m_LightPaxam из 11 элементов. Массив уже помещен в класс COGView, нам осталось лишь задействовать его:

void COGView: :GetLightParams (int *pPos)

{

//====== Проход по всем регулировкам

for (int i=0; i<ll; i++)

//====== Заполняем транспортный массив pPos

pPos[i] = m_LightParam[i] ;

void COGView: :SetLightParam (short Ip, int nPos)

{ //====== Синхронизируем параметр lp и

//====== устанавливаем его в положение nPos

m_LightParam[lp] = nPos;

//=== Перерисовываем представление с учетом изменений

Invalidate (FALSE) ;

}