Программирование графики с использованием Direct3D

         

Файлы CPP и H


Файлы с расширениями CPP и H содержат исходный код на C++, который рассматривается в этой книге.



Файлы MDP


Файлы MDP также известны как файлы проектов или файлы рабочих областей. Они содержат всю информацию, необходимую для компиляции приложения. Эти файлы создаются и модифицируются VisualC++. Загрузка такого файла в Visual C++ — это самый быстрый и простой способ откомпилировать, протестировать и модифицировать приложение.



Файлы RC


Файлы RC используются Visual C++ для хранения информации, относящейся к ресурсам приложения (меню, значки, растры, сетки и т.д.). Visual C++ модифицирует эти файлы автоматически, когда через предоставляемый IDE интерфейс вы редактируете ресурсы приложения. Ресурсы могут перемещаться и копироваться между несколькими рабочими областями. Для этого необходимо открыть несколько файлов RC, вырезать необходимый ресурс в одном файле и вставить его в другой.



Файлы X, BMP и PPM


В приложениях на CD-ROM файлы X, BMP и PPM содержат специфические для Direct3D ресурсы, которые компилируются в результирующий EXE-файл и будут доступны во время выполнения приложения. Чтобы изменить используемые приложением сетки и текстуры, надо заменить эти файлы на новые версии, после чего заново скомпилировать демонстрационную программу.



Функции класса FacePickWin для работы с мышью


В приложении FacePick мышь используется не только для выбора граней, но и для вращения сетки и изменения ее ориентации. Для отслеживания состояния левой кнопки мыши класс FacePickWin использует функции OnLButtonDown() и OnLButtonUp(). Код функции OnLButtonDown() выглядит следующим образом:

void FacePickWin::OnLButtonDown(UINT nFlags, CPoint point) { int faceindex = PickFace(point); if (faceindex != -1) { LPDIRECT3DRMFACEARRAY facearray; meshbuilder->GetFaces(&facearray);

LPDIRECT3DRMFACE face; facearray->GetElement(faceindex, &face); face->SetColor(pickcolor); face->Release(); facearray->Release(); } else if (!drag) { drag = TRUE; last_x = GetMouseX(); last_y = GetMouseY(); SetCapture(); ShowCursor(FALSE); } RMWin::OnLButtonDown(nFlags, point); }

Сперва функция OnLButtonDown() вызывает функцию PickFace(), которая и реализует операцию выбора. Функция PickFace() очень похожа на функцию PickMesh() из приложения MeshPick, за исключением того, что функция PickFace() возвращает индекс выбранной грани (или –1, если ни одна грань не была выбрана).

Если грань была выбрана, ее индекс используется для изменения цвета грани. Сначала для получения массива граней используется функция GetFaces() интерфейса Direct3DRMMeshBuilder. Полученный ранее индекс используется для извлечения указателя на выбранную грань. Затем для смены цвета грани вызывается функция SetColor() интерфейса Direct3DRMFace.



Если грань не выбрана, начинается операция перетаскивания. В приложении MeshPick операция перетаскивания использовалась для переперемещения выбранной сетки. В приложении FacePick операция перетаскивания применяется для вращения сетки. В данном случае при запуске операции перетаскивания текущие координаты указателя мыши сохраняются в переменных last_x и last_y, после чего вызываются функции SetCapture() и ShowCursor().

Теперь пришло время взглянуть на функцию OnLButtonUp():

void FacePickWin::OnLButtonUp(UINT nFlags, CPoint point) { if (drag) { end_drag = TRUE; ReleaseCapture(); ShowCursor(TRUE); } RMWin::OnLButtonUp(nFlags, point); }


Вы можете предположить, что функция OnLButtonUp() прекращает операцию перетаскивания (если она была начата). Однако, такое решение помешало бы реализации одной из возможностей приложения FacePick. Приложение FacePick позволяет пользователю вращать отображаемую сетку. Сетка может быть повернута в ходе операции перетаскивания, но, кроме того, может быть приведена в движение путем резкого перемещения мыши с одновременным отпусканием кнопки. Если поместить здесь код прекращения операции перетаскивания, эту возможность не удастся реализовать, поскольку код должен иметь возможность зафиксировать последние атрибуты вращения. Код установки атрибутов вращения находится в функции обратного вызова UpdateDrag(). Вместо того, чтобы дублировать этот код, мы устанавливаем флаг, сигнализирующий о завершении операции перетаскивания. Тем временем мы воосстанавливаем отображение указателя мыши и отменяем захват мыши программой, поскольку эти действия не влияют на код, связанный с атрибутами вращения.


Функции класса MeshPickWin для работы с мышью


Приложение MeshPick использует две функции обработки сообщений, чтобы реагировать на изменение состояния кнопок мыши. Функция OnLButtonDown() вызывается MFC каждый раз, когда пользователь нажимает левую кнопку мыши. Код этой функции выглядит так:

void MeshPickWin::OnLButtonDown(UINT nFlags, CPoint point) { if (PickMesh(point)) { ShowCursor(FALSE); SetCapture(); } RMWin::OnLButtonDown(nFlags, point); }

MFC передает в функцию OnLButtonDown() два аргумента. Первый представляет собой набор флагов, указывающих на состояние некоторых клавиш (CTRL, SHIFT ит.д.) в момент нажатия на кнопку мыши. Второй параметр, point, содержит координаты указателя мыши в момент нажатия кнопки. В функции OnLButtonDown() параметр point используется в качестве аргумента для функции PickMesh(), которая определяет, существует ли в указанной точке какой-либо объект, и, если да, то инициализирует операцию перетаскивания. Если операция перетаскивания инициализирована, функция PickMesh() возвращает TRUE, и вызывается функция ShowCursor() чтобы скрыть указатель мыши на время операции перетаскивания. Кроме того, вызывается функция SetCapture(), чтобы уведомить Windows, что наше приложение хочет получать все сообщения от мыши, даже когда указатель мыши находится вне окна приложения. Перед выходом выполняется вызов функции OnLButtonDown() базового класса.

Функция OnLButtonUp() вызывается, когда пользователь отпускает левую кнопку мыши. Код функции приведен ниже:

void MeshPickWin::OnLButtonUp(UINT nFlags, CPoint point) { if (drag.frame) { drag.frame = 0; ShowCursor(TRUE); ReleaseCapture(); } RMWin::OnLButtonUp(nFlags, point); }

Функция проверяет значение члена данных drag.frame. Эта переменная указывает, во-первых, есть ли начатая операция перетаскивания, и, во-вторых, какой фрейм перетаскивается. Член данных drag.frame инициализируется в функции PickMesh() если в точке, указанной курсором мыши, обнаружен объект.

Если значение переменной drag.frame не равно нулю, в настоящий момент выполняется операция перетаскивания, и мы должны ее прекратить (поскольку кнопка мыши была отпущена). Переменной присваивается нулевое значение, указатель мыши отображается на экране и отменяется захват мыши приложением.



Функции класса MorphPlayWin для работы с мышью


В приложении MorphPlay есть четыре функции для реализации возможности вращения сетки: OnLButtonDown(), OnLButtonUp(), UpdateDrag() и OnIdle(). Давайте сначала взглянем на код функции OnLButtonDown():

void MorphPlayWin::OnLButtonDown(UINT nFlags, CPoint point) { if (!drag) { drag = TRUE; last_x = GetMouseX(); last_y = GetMouseY(); ShowCursor(FALSE); SetCapture(); } MorphWin::OnLButtonDown(nFlags, point); }

Функция начинает новую операцию перетаскивания, если только она уже не начата. Переменная drag используется для контроля текущего состояния операции перетаскивания. В начале операции перетаскивания сохраняется текущая позиция указателя мыши, сам указатель убирается с экрана, и мышь захватывается приложением с помощью функции MFC SetCapture(). После вызова функции SetCapture() все поступающие от мыши сообщения будут направляться приложению, которое захватило мышь, независимо от того, в каком месте экрана находится указатель мыши.

Функция OnLButtonUp() выглядит следующим образом:

void MorphPlayWin::OnLButtonUp(UINT nFlags, CPoint point) { if (drag) { end_drag = TRUE; ReleaseCapture(); ShowCursor(TRUE); } MorphWin::OnLButtonUp(nFlags, point); }

Сначала функция OnLButtonUp() проверяет, существует ли начатая операция перетаскивания. Если да, то переменной end_drag присваивается значение TRUE, что указывает на необходимость прекращения операции перетаскивания. Затем приложение освобождает мышь с помощью функции ReleaseCapture() и разрешает отображение указателя мыши.

Функция обратного вызова UpdateDrag() отвечает за вращение трансформируемой сетки при ее перетаскивании. Вот как выглядит код функции:

void MorphPlayWin::UpdateDrag(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { if (drag) { double delta_x, delta_y; int x = GetMouseX(); int y = GetMouseY(); delta_x = x - last_x; delta_y = y - last_y; last_x = x; last_y = y;

double delta_r = sqrt(delta_x * delta_x + delta_y * delta_y); double radius = 50; double denom;

denom = sqrt(radius * radius + delta_r * delta_r);

if (!(delta_r == 0 || denom == 0)) frame->SetRotation(0, D3DDivide(D3DVAL((float)-delta_y), D3DVAL((float)delta_r)), D3DDivide(D3DVAL((float)-delta_x), D3DVAL((float)delta_r)), D3DVAL(0.0), D3DDivide(D3DVAL((float)delta_r), D3DVAL((float)denom))); }

if (end_drag) { drag = FALSE; end_drag = FALSE; } }

Функция UpdateDrag() преобразует данные о перемещении мыши (сохраненные в переменных x, y, last_x и last_y) для вычисления вектора вращения сетки. Новые атрибуты вращения назначаются с помощью функции SetRotation() интерфейса Direct3DRMFrame.

В конце приведем код функции OnIdle(), которая используется для предотвращения беспорядочного вращения сетки в момент операции перетаскивания.

void MorphPlayWin::OnIdle(LONG) { if (drag && frame) frame->SetRotation(0, D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0)); }



Функции меню Animation в классе RocketWin


Меню Animation приложения Rocket предоставляет две команды: Linear и Spline. Для реализации каждой из этих команд применяется две функции. Ниже приведен код всех четырех функций, необходимых для меню Animation:

void RocketWin::OnAnimationLinear() { animation->SetOptions(D3DRMANIMATION_LINEARPOSITION | D3DRMANIMATION_CLOSED | D3DRMANIMATION_POSITION | D3DRMANIMATION_SCALEANDROTATION); }

void RocketWin::OnAnimationSpline() { animation->SetOptions(D3DRMANIMATION_SPLINEPOSITION | D3DRMANIMATION_CLOSED | D3DRMANIMATION_POSITION | D3DRMANIMATION_SCALEANDROTATION); }

void RocketWin::OnUpdateAnimationLinear(CCmdUI* pCmdUI) { D3DRMANIMATIONOPTIONS options; options = animation->GetOptions(); pCmdUI->SetCheck(options & D3DRMANIMATION_LINEARPOSITION); }

void RocketWin::OnUpdateAnimationSpline(CCmdUI* pCmdUI) { D3DRMANIMATIONOPTIONS options; options = animation->GetOptions(); pCmdUI->SetCheck(options & D3DRMANIMATION_SPLINEPOSITION); }

Первые две функции, OnAnimationLinear() и OnAnimationSpline(), вызывают функцию SetOptions() интерфейса Direct3DRMAnimation чтобы задать значения набора флагов. Один из флагов различен в этих двух функциях: в функции OnAnimationLinear() указан флаг D3DRMANIMATION_LINEARPOSITION, а в функции OnAnimationSpline() используется флаг D3DRMANIMATION_SPLINEPOSITION.

Флаг D3DRMANIMATION_CLOSED указывает, что мы используем закрытую анимационную последовательность (в противоположность открытой анимационной последовательности). Использование закрытой анимационной последовательности позволяет непрерывно увеличивать передаваемые функции SetTime() значения временных меток. При этом анимационная последовательность будет многократно повторяться.

Флаг D3DRMANIMATION_POSITION указывает, что мы хотим, чтобы при выполнении анимационной последовательности изменялось местоположение фрейма, а флаг D3DRMANIMATION_SCALEANDROTATION сообщает, что нам также необходимо изменение масштаба и ориентации фрейма.

Чтобы получить текущие параметры анимации, функции OnUpdateAnimationLinear() и OnUpdateAnimationSpline() вызывают функцию GetOptions() интерфейса Direct3DRMAnimation. Значение, возвращаемое данной функцией используется, чтобы проверить какой метод анимации используется в данный момент и установить флажок у соответствующего пункта меню.



Функции меню Animation в классе ZoomWin


Приложение Zoom позволяет во время работы выбирать тип анимационной последовательности (линейная или сплайновая) с помощью меню Animation. Для этого вызывается функция SetOptions() интерфейса Direct3DRMAnimation с различными наборами флагов. Две функции, которые отвечают за работу команд меню Animation, выглядят следующим образом:

void ZoomWin::OnAnimationLinear() { animation->SetOptions(D3DRMANIMATION_LINEARPOSITION | D3DRMANIMATION_CLOSED | D3DRMANIMATION_POSITION); }

void ZoomWin::OnAnimationSpline() { animation->SetOptions(D3DRMANIMATION_SPLINEPOSITION | D3DRMANIMATION_CLOSED | D3DRMANIMATION_POSITION); }

Поддержка меню Animation включает также отображение флажка слева от пункта меню, соответствующего выбранному в данный момент режиму анимации. Две функции, отвечающие за вывод флажков рядом с пунктами меню выглядят следующим образом:

void ZoomWin::OnUpdateAnimationLinear(CCmdUI* pCmdUI) { D3DRMANIMATIONOPTIONS options = animation->GetOptions(); pCmdUI->SetCheck(options & D3DRMANIMATION_LINEARPOSITION); }

void ZoomWin::OnUpdateAnimationSpline(CCmdUI* pCmdUI) { D3DRMANIMATIONOPTIONS options = animation->GetOptions(); pCmdUI->SetCheck(options & D3DRMANIMATION_SPLINEPOSITION); }

Для получения текущих параметров анимации эти функции вызывают функцию GetOptions() интерфейса Direct3DRMAnimation. Функция SetCheck() используется для установки флажка рядом с пунктом меню, соответствующим активному в данный момент режиму.



Функции меню Beam приложения Spotlight


Приложение Spotlight предоставляет пользователю меню Beam, позволяющее изменить угол светового пятна и угол зоны освещенности прожектора. Шесть функций, реализующих эту функциональность, показаны ниже:

void SpotlightWin::OnBeamNormal() { spotlight->SetUmbra(D3DVALUE(0.2)); spotlight->SetPenumbra(D3DVALUE(0.4)); beamwidth = BEAM_NORMAL; }

void SpotlightWin::OnBeamNarrow() { spotlight->SetUmbra(D3DVALUE(0.1)); spotlight->SetPenumbra(D3DVALUE(0.2)); beamwidth = BEAM_NARROW; }

void SpotlightWin::OnBeamWide() { spotlight->SetUmbra(D3DVALUE(0.4)); spotlight->SetPenumbra(D3DVALUE(0.8)); beamwidth = BEAM_WIDE; }

void SpotlightWin::OnUpdateBeamNormal(CCmdUI* pCmdUI) { pCmdUI->SetCheck(beamwidth == BEAM_NORMAL); }

void SpotlightWin::OnUpdateBeamNarrow(CCmdUI* pCmdUI) { pCmdUI->SetCheck(beamwidth == BEAM_NARROW); }

void SpotlightWin::OnUpdateBeamWide(CCmdUI* pCmdUI) { pCmdUI->SetCheck(beamwidth == BEAM_WIDE); }

Одна из первых трех функций вызывается, когда пользователь выбирает один из пунктов меню Beam. Функции SetUmbra() и SetPenumbra() применяются для модификации характеристик прожектора. Кроме того, переменной класса beamwidth присваивается значение, соответствующее установленным в данный момент параметрам прожектора. Эта переменная используется следующими тремя функциями, которые предназначены для отображения метки возле активного пункта меню.



Функции меню Color в классе FacePickWin


Приложение FacePick поддерживает два диалоговых окна для выбора цвета. Этими диалоговыми окнами управляют функции OnColorsFace() и OnColorsMesh(). Функция OnColorsFace() предоставляет пользователю возможность выбрать цвет, в который будет окрашена выбранная грань. Функция OnColorsMesh() позволяет выбрать цвет, в который будет окрашена вся сетка. Обе функции используют класс MFC CColorDialog. Код функции OnColorsFace() выглядит так:

void FacePickWin::OnColorsFace() { CColorDialog dialog(0, CC_RGBINIT); dialog.m_cc.rgbResult = D3DCOLOR_2_COLORREF(pickcolor); if (dialog.DoModal() == IDOK) { COLORREF clr = dialog.GetColor(); pickcolor = COLORREF_2_D3DCOLOR(clr); } }

Класс CColorDialog позволяет указать цвет, который будет выбран при появлении диалогового окна на экране. Благодаря этому, мы можем вывести диалоговое окно, в котором уже будет выбран текущий цвет выбираемых граней (естественно, это работает, только если указанный цвет является одним из отображаемых в диалоговом окне цветов).

Константа CC_RGBINIT используется в качестве аргумента конструктора диалогового окна, чтобы указать, что мы задаем цвет по умолчанию. Цвет по умолчанию должен быть присвоен члену данных m_cc.rgbResult. Переменная pickcolor является членом данных класса MeshPickWin, в котором хранится текущий цвет окраски выбранных граней. Член данных pickcolor относится к типу D3DCOLOR, а для диалогового окна необходимо, чтобы цвета были представлены в формате COLORREF, и чтобы присваивание было выполнено правильно, требуется функция преобразования. Для этих целей класс RMWin предоставляет функции D3DCOLOR_2_COLORREF() и COLORREF_2_D3DCOLOR() (см. главу4).

Чтобы открывшееся окно диалога было модальным, мы используем функцию DoModal(). Если пользователь выходит из окна диалога любым способом, отличающимся от щелчка по кнопке OK, функция завершает работу не выполняя больше никаких действий. Если возвращена константа IDOK, новый цвет присваивается члену данных pickcolor.

Функция OnColorsMesh() очень похожа:

void FacePickWin::OnColorsMesh() { CColorDialog dialog; if (dialog.DoModal() == IDOK) { COLORREF clr = dialog.GetColor(); D3DCOLOR meshcolor = COLORREF_2_D3DCOLOR(clr); meshbuilder->SetColor(meshcolor); } }

В отличие от функции OnColorsFace(), в функции OnColorsMesh() не указывается выбираемый по умолчанию в диалоговом окне цвет. Если пользователь щелкает по кнопке OK, новый цвет извлекается из класса диалогового окна с помощью функции GetColor(). Полученное значение типа COLORREF преобразуется функцией COLORREF_2_D3DCOLOR() и устанавливается функцией SetColor() интерфейса Direct3DRMMeshBuilder.



Функции меню File в классе FacePickWin


Приложение FacePick позволяет загружать и сохранять сетки с помощью меню File. Для этих целей в класс FacePickWin включены функции OnFileOpen() и OnFileSave(). Функция OnFileOpen() выглядит следующим образом:

void FacePickWin::OnFileOpen() { static char BASED_CODE filter[] = "X Files (*.x)|*.x|All Files (*.*)|*.*||"; CFileDialog opendialog(TRUE, 0, 0, OFN_FILEMUSTEXIST, filter, this); if (opendialog.DoModal() == IDOK) { CWaitCursor cur; CString filename = opendialog.GetPathName();

LPDIRECT3DRMMESHBUILDER builder; d3drm->CreateMeshBuilder(&builder); HRESULT r = builder->Load((void*)(LPCTSTR)filename, NULL, D3DRMLOAD_FROMFILE, NULL, NULL); if (r != D3DRM_OK) { CString msg; msg.Format("Failed to load file\n'%s'", filename); AfxMessageBox(msg); return; }

meshframe->DeleteVisual(meshbuilder); meshbuilder->Release(); meshbuilder = builder; meshframe->AddVisual(meshbuilder); meshscale = ScaleMesh(meshbuilder, D3DVALUE(25)); } }

Функция использует класс MFC CFileDialog. Обратите внимание, что в качестве аргумента конструктора класса используется строка filter. Эта строка информирует диалоговое окно о том, какие типы файлов могут быть загружены.

Для вывода диалогового окна используется функция DoModal(). Если будет возвращена константа IDOK, имя файла извлекается из класса диалогового окна с помощью функции GetPathName(). Обратите внимание, что мы не проверяем, существует ли данный файл. Это сделано потому, что при конструировании объекта диалогового окна мы указали константу OFN_FILEMUSTEXIST. Данная константа указывает классу диалогового окна, что в случае ручного ввода имени файла должен быть проверен факт существования данного файла. В результате диалоговое окно просто не позволит ввести имя несуществующего файла (эта проверка не гарантирует правильности содержимого файла, она гарантирует только его существование).

Затем функция пытается загрузить новую сетку. Если попытка завершается неудачно, выводится окно с сообщением об ошибке и функция завершает работу.
Эта ошибка не вызовет проблем в работе приложения, просто и дальше будет отображаться существующая в сцене сетка.

Если новая сетка успешно загружена, существующая сетка удаляется из сцены функцией DeleteVisual() интерфейса Direct3DRMFrame, и указатель meshbuilder освобождается. Затем к сцене добавляется новый конструктор сеток с помощью функции AddVisual().

На последнем этапе вызывается функция ScaleMesh(). Мы видели, как эта функция используется в других демонстрационных программах, но в данном случае есть несколько отличий. Вспомните, что функция ScaleMesh() масштабирует сетку до желаемого размера. В данном случае мы масштабируем любую загруженную сетку до размера в 25 единиц. Это замечательно работало в других приложениях, и работает здесь, но если после масштабирования сетка будет записана на диск, ее размеры изменятся. Поэтому перед тем как записывать сетку на диск, ей необходимо вернуть первоначальные размеры. Функция ScaleMesh() возвращает значение, являющееся коэффициентом, использованным для масштабирования оригинальной сетки. Сохранение этого значения позволит нам вернуть сетке оригинальный размер. Мы увидим, как это делается, когда будем обсуждать код функции OnFileSave().

Функция OnFileSave() определена следующим образом:

void FacePickWin::OnFileSave() { static char BASED_CODE filter[] = "X Files (*.x)|*.x||"; CFileDialog opendialog(FALSE, ".x", "", OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, filter); if (opendialog.DoModal() == IDOK) { CWaitCursor cur; CString filename = opendialog.GetPathName(); D3DVALUE restorescale = D3DVALUE(1)/meshscale; meshbuilder->Scale(restorescale, restorescale, restorescale); meshbuilder->Save(filename, D3DRMXOF_BINARY, D3DRMXOFSAVE_ALL); meshbuilder->Scale(meshscale, meshscale, meshscale); } }

Функция OnFileSave() также использует класс CFileDialog, но в вызове конструктора класса диалогового окна указан другой набор констант. Константа OFN_HIDEREADONLY указывает, что в диалоговом окне не надо отображать флажок Open as Read Only.


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

Для вывода диалогового окна используется функция DoModal(). Если функция возвращает константу IDOK, текущая сетка записывается на диск. Класс CWaitCursor используется для отображения во время выполнения записи указателя мыши в форме песочных часов. Для получения имени сохраняемого или перезаписываемого файла применяется функция GetPathName(). Затем сетке возвращается ее оригинальный размер. Значение restorescale вычисляется таким образом, чтобы обратить операцию масштабирования, которой сетка подверглась при загрузке. Затем используется функция Scale() интерфейса Direct3DRMMeshBuilder чтобы масштабировать сетку согласно полученному коэффициенту restorescale. Для сохранения сетки вызывается функция Save(). Затем сетке возвращается ее предыдущий размер. Если не сделать этого, то после сохранения отображаемая сетка может стать огромной или микроскопической.


Функции меню Render


Одна из возможностей, которую мастер Direct3D AppWizard добавляет в проект (независимо от того, просите вы об этом или нет) — это меню Render, позволяющее изменить метод визуализации сетки во время работы программы. Взглянув на определение класса AmbientLightWin, вы увидите объявление шести функций, реализующих эту функциональность. Функции OnRenderWireframe(), OnRenderFlat() и OnRenderGouraud() являются обработчиками событий, которые вызываются при выборе одной из команд меню Render. Эти функции выглядят следующим образом:

void AmbientLightWin::OnRenderWireframe() { if (meshbuilder) meshbuilder->SetQuality(D3DRMRENDER_WIREFRAME); }

void AmbientLightWin::OnRenderFlat() { if (meshbuilder) meshbuilder->SetQuality(D3DRMRENDER_FLAT); }

void AmbientLightWin::OnRenderGouraud() { if (meshbuilder) meshbuilder->SetQuality(D3DRMRENDER_GOURAUD); }

Каждая функция проверяет значение указателя meshbuilder. Если указатель был инициализирован, вызывается функция SetQuality() интерфейса Direct3DRMMeshBuilder для изменения метода визуализации сетки.

Оставшиеся три функции, OnUpdateRenderFlat(), OnUpdateRenderGouraud() и OnUpdateRenderWireframe(), вызываются Windows каждый раз при отображении на экране меню Render. Они применяются для вывода отметки рядом с активным в данный момент пунктом меню. Ниже приведен код этих функций:

void AmbientLightWin::OnUpdateRenderWireframe(CCmdUI* pCmdUI) { if (meshbuilder) { D3DRMRENDERQUALITY meshquality = meshbuilder->GetQuality(); pCmdUI->SetCheck(meshquality == D3DRMRENDER_WIREFRAME); } }

void AmbientLightWin::OnUpdateRenderFlat(CCmdUI* pCmdUI) { if (meshbuilder) { D3DRMRENDERQUALITY meshquality = meshbuilder->GetQuality(); pCmdUI->SetCheck(meshquality == D3DRMRENDER_FLAT); } }

void AmbientLightWin::OnUpdateRenderGouraud(CCmdUI* pCmdUI) { if (meshbuilder) { D3DRMRENDERQUALITY meshquality = meshbuilder->GetQuality(); pCmdUI->SetCheck(meshquality == D3DRMRENDER_GOURAUD); } }

Каждая из трех функций вызывает функцию GetQuality() интерфейса Direct3DRMMeshBuilder, чтобы определить используемый в данный момент метод визуализации сетки. Затем выполняется сравнение полученного метода визуализации, чтобы определить должна ли быть проставлена отметка рядом с данным пунктом меню. Результат сравнения используется в качестве аргумента функции SetCheck(), если он равен TRUE, рядом с пунктом меню ставится флажок.

Рассмотренные шесть функций в той или иной форме присутствуют в большинстве демонстрационных приложений, помещенных на CD-ROM.



Функции меню Render приложения JadeWin


Приложение Jade (как и большинство демонстрационных программ на CD-ROM) позволяет во время работы программы изменять метод визуализации сетки. Эту возможность обеспечивают функции OnRenderWireframe(), OnRenderFlat() и OnRenderGouraud(), вызываемые MFC при выборе пользователем команд из меню Render. Эти три функции выглядят следующим образом:

void JadeWin::OnRenderWireframe() { if (meshbuilder) meshbuilder->SetQuality(D3DRMRENDER_WIREFRAME); }

void JadeWin::OnRenderFlat() { if (meshbuilder) meshbuilder->SetQuality(D3DRMRENDER_FLAT); }

void JadeWin::OnRenderGouraud() { if (meshbuilder) meshbuilder->SetQuality(D3DRMRENDER_GOURAUD); }

В каждой функции для задания метода визуализации применяется метод SetQuality() интерфейса Direct3DRMMeshBuilder. Еще три функции используются для отображения метки слева от используемого в данный момент метода визуализации в меню Render. Текст функций OnUpdateRenderFlat(), OnUpdateRenderGouraud() и OnUpdateRenderWireframe() приведен ниже:

void JadeWin::OnUpdateRenderWireframe(CCmdUI* pCmdUI) { if (meshbuilder) { D3DRMRENDERQUALITY meshquality = meshbuilder->GetQuality(); pCmdUI->SetCheck(meshquality == D3DRMRENDER_WIREFRAME); } }

void JadeWin::OnUpdateRenderFlat(CCmdUI* pCmdUI) { if (meshbuilder) { D3DRMRENDERQUALITY meshquality = meshbuilder->GetQuality(); pCmdUI->SetCheck(meshquality == D3DRMRENDER_FLAT); } }

void JadeWin::OnUpdateRenderGouraud(CCmdUI* pCmdUI) { if (meshbuilder) { D3DRMRENDERQUALITY meshquality = meshbuilder->GetQuality(); pCmdUI->SetCheck(meshquality == D3DRMRENDER_GOURAUD); } }

MFC вызывает каждую из этих трех функций при выводе на экран меню Render. Каждая из функций проверяет текущие параметры визуализации сетки и указывает MFC необходимо ли ставить флажок около данного пункта меню.

Почти все демонстрационные программы на CD-ROM предоставляют меню, подобные меню Render. Функции поддержки команд меню, подобные рассмотренным выше, могут быть добавлены в программу или удалены из нее с помощью Visual C++ ClassWizard.



Функции меню Render приложения Shadow


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

void ShadowWin::OnRenderWireframe() { if (floorbuilder) floorbuilder->SetQuality(D3DRMRENDER_WIREFRAME); if (forkbuilder) forkbuilder->SetQuality(D3DRMRENDER_WIREFRAME); }



Функции меню Render приложения Spotlight


Ранее в этой главе, когда мы изучали источник рассеянного света и создавали приложение AmbientLight, мы рассмотрели шесть функций, необходимых для реализации меню Render. В них мы использовали функции GetQuality() и SetQuality() интерфейса Direct3DRMMeshBuilder. В приложении Spotlight такая стратегия не работает, поскольку здесь мы используем интерфейс Direct3DRMMesh.

Интерфейс Direct3DRMMesh практически полностью посвящен работе с группами. Группой называется набор граней сетки, который может модифицироваться как единая сущность. Для контроля и назначения цвета группы граней интерфейс Direct3DRMMesh предоставляет функции GetGroupColor() и SetGroupColor(). Существует только одна проблема: эти функции требуют идентификатор, который указывает, какая группа граней сетки нас интересует.

Как мы увидим в главе8, идентификаторы групп важны, когда мы используем сетку, содержащую несколько групп. Однако сейчас все грани каждой из трех сеток принадлежат одной и той же группе. К счастью для нас, если сетка содержит только одну группу, то в качестве идентификатора группы можно использовать ноль. Этот идентификатор группы по умолчанию и используется в следующих функциях приложения Spotlight:

void SpotlightWin::OnRenderWireframe() { mesh1->SetGroupQuality(0, D3DRMRENDER_WIREFRAME); mesh2->SetGroupQuality(0, D3DRMRENDER_WIREFRAME); mesh3->SetGroupQuality(0, D3DRMRENDER_WIREFRAME); }

void SpotlightWin::OnRenderFlat() { mesh1->SetGroupQuality(0, D3DRMRENDER_FLAT); mesh2->SetGroupQuality(0, D3DRMRENDER_FLAT); mesh3->SetGroupQuality(0, D3DRMRENDER_FLAT); }

void SpotlightWin::OnRenderGouraud() { mesh1->SetGroupQuality(0, D3DRMRENDER_GOURAUD); mesh2->SetGroupQuality(0, D3DRMRENDER_GOURAUD); mesh3->SetGroupQuality(0, D3DRMRENDER_GOURAUD); }

void SpotlightWin::OnUpdateRenderWireframe(CCmdUI* pCmdUI) { D3DRMRENDERQUALITY quality = mesh1->GetGroupQuality(0); pCmdUI->SetCheck(quality == D3DRMRENDER_WIREFRAME); }

void SpotlightWin::OnUpdateRenderFlat(CCmdUI* pCmdUI) { D3DRMRENDERQUALITY quality = mesh1->GetGroupQuality(0); pCmdUI->SetCheck(quality == D3DRMRENDER_FLAT); }

void SpotlightWin::OnUpdateRenderGouraud(CCmdUI* pCmdUI) { D3DRMRENDERQUALITY quality = mesh1->GetGroupQuality(0); pCmdUI->SetCheck(quality == D3DRMRENDER_GOURAUD); }

Одна из первых трех функций вызывается, когда пользователь выбирает один из пунктов в меню Render приложения. Функция SetGroupQuality() используется чтобы изменить метод визуализации сетки. Следующие три функции вызываются MFC перед выводом на экран меню Render. Функция SetCheck() используется для установки флажка слева от пункта меню, соответствующего используемому в данный момент методу визуализации.



Функции меню Render в классе Cube2Win


В приложении Cube2 меню Render немного изменено, по сравнению с его обычным вариантом, поскольку каждая группа граней может иметь свои собственные параметры визуализации. По этой причине меню Render состоит из двух подменю — по одному для каждой группы граней сетки. Структура меню показана на рис. 8.3.


Рис. 8.3. Меню Render приложения Cube2

Для реализации меню Render приложения Cube2 требуется двенадцать функций, но все они отличаются друг от друга весьма незначительно. Ниже приведен пример одной из функций, вызываемых при выборе одного из пунктов меню:

void Cube2Win::OnRenderGroup1Wireframe() { if (mesh) mesh->SetGroupQuality(group1, D3DRMRENDER_WIREFRAME); }

Данная функция отвечает за работу пункта меню Render|Group1|Wireframe и использует функцию SetGroupQuality() интерфейса Direct3DRMMesh чтобы установить каркасный метод визуализации. Обратите внимание, что в первом аргументе функции SetGroupQuality() передается идентификатор группы граней group1.

Приведем также пример функции, устанавливающей флажок рядом с пунктом меню, соответствующим текущему методу визуализации группы граней сетки:

void Cube2Win::OnUpdateRenderGroup1Flat(CCmdUI* pCmdUI) { if (mesh) { D3DRMRENDERQUALITY meshquality = mesh->GetGroupQuality(group1); pCmdUI->SetCheck(meshquality == D3DRMRENDER_FLAT); } }

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



Функции меню Render в классе CubeWin


Оставшиеся функции класса CubeWin предназначены для реализации меню Render. Их код приведен ниже:

void CubeWin::OnRenderWireframe() { if (mesh) mesh->SetGroupQuality(group, D3DRMRENDER_WIREFRAME); }

void CubeWin::OnRenderFlat() { if (mesh) mesh->SetGroupQuality(group, D3DRMRENDER_FLAT); }

void CubeWin::OnRenderGouraud() { if (mesh) mesh->SetGroupQuality(group, D3DRMRENDER_GOURAUD); }

void CubeWin::OnUpdateRenderWireframe(CCmdUI* pCmdUI) { if (mesh) { D3DRMRENDERQUALITY meshquality = mesh->GetGroupQuality(group); pCmdUI->SetCheck(meshquality == D3DRMRENDER_WIREFRAME); } }

void CubeWin::OnUpdateRenderFlat(CCmdUI* pCmdUI) { if (mesh) { D3DRMRENDERQUALITY meshquality = mesh->GetGroupQuality(group); pCmdUI->SetCheck(meshquality == D3DRMRENDER_FLAT); } }

void CubeWin::OnUpdateRenderGouraud(CCmdUI* pCmdUI) { if (mesh) { D3DRMRENDERQUALITY meshquality = mesh->GetGroupQuality(group); pCmdUI->SetCheck(meshquality == D3DRMRENDER_GOURAUD); } }

Обратите внимание, что для получения и изменения параметров визуализации сетки используется идентификатор групп граней сетки group.

Кроме того, вы можете заметить, что при выборе команд меню Render практически не заметна разница между плоским методом визуализации и визуализацией по методу Гуро. Обычно визуализация по методу Гуро смягчает изображение граней и углов. Однако, при выборе метода Гуро в приложении Cube грани сетки остаются ясно различимыми. Это происходит потому, что нормали, которые мы использовали при создании сетки вычисялись не тем способом, который использует для вычисления нормалей интерфейс Direct3DRMMeshBuilder. Используемые в приложении Cube нормали перпендикулярны той грани, частью которой является данная вершина. Чтобы вычислить нормаль с учетом всех граней, сходящихся в данной вершине, следует использовать функцию GenerateNormals() интерфейса Direct3DRMMeshBuilder.



Функции меню Viewport в классе RMWin


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

void RMWin::OnViewport1Disabled() { view1setting = VIEWPORT_DISABLED; viewport1->Clear(); }

void RMWin::OnViewport1Front() { view1setting = VIEWPORT_FRONT; ConfigViewport(camera1, view1setting); }

void RMWin::OnViewport1Left() { view1setting = VIEWPORT_LEFT; ConfigViewport(camera1, view1setting); }

void RMWin::OnViewport1Right() { view1setting = VIEWPORT_RIGHT; ConfigViewport(camera1, view1setting); }

void RMWin::OnViewport1Top() { view1setting = VIEWPORT_TOP; ConfigViewport(camera1, view1setting); }

Каждая из функций присваивает свое значение члену данных view1setting. Функция OnViewportDisabled() использует функцию Clear() интерфейса Direct3DRMViewport для очистки порта просмотра. В оставшихся функциях используется функция ConfigViewport() для настройки порта просмотра в соответствии с новыми параметрами.



Функции обратного вызова в приложении OrbStar


Функция CreateScene() приложения OrbStar устанавливает две функции обратного вызова: одну для фрейма к которому присоединена сетка звезды и другую для фрейма с сеткой сферы. Эти функции периодически назначают каждому фрейму случайные атрибуты вращения, вызывая изменение скорости и направления вращения сеток в сцене приложения. Функции обратного вызова MoveStar() и MoveSphere() практически идентичны, поэтому здесь приводится только код функции MoveStar().

void OrbStarWin::MoveStar(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { static UINT delay; if (++delay < 11) return; delay = 0;

LPDIRECT3DRMFRAME scene; frame->GetScene(&scene);

D3DVECTOR spinvect; D3DRMVectorRandom(&spinvect); D3DVALUE spin = D3DDivide(rand() %100 + 1, 200); frame->SetRotation(scene, spinvect.x, spinvect.y, spinvect.z, spin); }

Функция MoveStar() использует статическую переменную delay в качестве счетчика, регулирующего частоту изменения атрибутов вращения фрейма. В нашем случае изменение происходит, когда значение счетчика станет равно 11.

Как только станет ясно, что необходимо вычислить новые атрибуты вращения, с помощью функции GetScene() интерфейса Direct3DRMFrame мы получаем указатель на корневой фрейм сцены. Вспомните, что функции обратного вызова должны быть объявлены как статические из-за того, что для вызова обычных функций класса необходим указатель на класс. Статические функции класса не могут обращаться к переменным класса (если только эти переменные тоже не объявлены статическими). Если бы MoveStar() не была статической, мы могли бы использоать переменную RMWin::scene так же, как это делается в функции CreateScene(). К счастью Direct3D передает в первом параметре функции обратного вызова указатель на фрейм, для которого эта функция вызвана, и предоставляет функцию GetScene(). Нам потребуется указатель на фрейм сцены, когда позднее из этой функции мы вызовем функцию SetRotation().

Функция MoveStar() вычисляет новые атрибуты вращения случайным образом. Функция D3DRMVectorRandom() возвращает случайный вектор, а скорость вращения вычисляется с помощью функции rand(). Новые атрибуты вращения фрейма устанавливаются функцией SetRotation().



Функции работы с палитрой


Класс RMWin предоставляет две функции, которые содействуют использованию палитр. Функция UsePalette() является защищенной функцией, позволяющей классу RMWin использовать палитру, хранящуюся в файле BMP. Функция UsePalette() определена в объявлении класса RMWin:

void UsePalette(CString filename) { palettefile = filename; }

Функция просто сохраняет полученное имя файла. Файл будет использован классом RMWin когда необходимо будет создать палитру.

Вспомните, что функция OnCreate() вызывала закрытую функцию InstallPalette(). Функция InstallPalette() использует имя файла, предоставленное функцией UsePalette() для извлечения и установки палитры, основываясь на содержимом файла BMP. Код функции InstallPalette() показан в листинге 10.6.

Листинг 10.6. Функция InstallPalette()

BOOL RMWin::InstallPalette() { BITMAPFILEHEADER bmpfilehdr; BITMAPINFOHEADER bmpinfohdr; RGBQUAD quad[256]; PALETTEENTRY pe[256]; int ncolors;

if (palettefile.GetLength() <= 0) return FALSE; if (modedepth != 8) return FALSE;

if (palette) { palette->Release(); palette = 0; }

ifstream bmp(palettefile, ios::binary | ios::nocreate);

bmp.read((char*)&bmpfilehdr, sizeof(bmpfilehdr)); bmp.read((char*)&bmpinfohdr, sizeof(bmpinfohdr));

char* ptr = (char*)&bmpfilehdr.bfType; if (*ptr != 'B' || *++ptr != 'M') { TRACE("invalid bitmap\n"); return FALSE; }

if (bmpinfohdr.biBitCount != 8) { TRACE("not 8 bit file!\n"); return FALSE; }

if (bmpinfohdr.biClrUsed == 0) ncolors = 256; else ncolors = bmpinfohdr.biClrUsed;

bmp.read((char*)quad, sizeof(RGBQUAD) * ncolors);

for(int i = 0; i < ncolors; i++) { pe[i].peRed = quad[i].rgbRed; pe[i].peGreen = quad[i].rgbGreen; pe[i].peBlue = quad[i].rgbBlue; pe[i].peFlags = D3DPAL_READONLY; }

HRESULT r = ddraw->CreatePalette(DDPCAPS_8BIT, pe, &palette, 0); if (r != DD_OK) { TRACE("failed to load palette data from file\n"); return FALSE; }

primsurf->SetPalette(palette); backsurf->SetPalette(palette);

return TRUE; }

<
/p> Перед обсуждением функции InstallPalette(), следует упомянуть, что мы будем извлекать только ту часть файла BMP, которая содержит палитру. Хранящееся в файле изображение игнорируется.

Сначала в функции InstallPalette() объявляется несколько локальных переменных:

BITMAPFILEHEADER bmpfilehdr; BITMAPINFOHEADER bmpinfohdr; RGBQUAD quad[256]; PALETTEENTRY pe[256];

Структура BITMAPFILEHEADER присутствует в начале каждого файла BMP. Мы используем эту структуру для загрузки параметров конкретного файла BMP. В частности, структура BITMAPFILEHEADER содержит сигнатуру, позволяющую идентифицировать файл BMP. Определение структуры BITMAPFILEHEADER выглядит следующим образом:

typedef struct tagBITMAPFILEHEADER { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER;

В корректном файле BMP поле bfType содержит символы «BM». Если эта сигнатура отсутствует, мы будем знать, что загруженный файл имеет неверный формат.

Структура BITMAPINFOHEADER располагается в файле BMP сразу после структуры BITMAPFILEHEADER. Эта структура используется для загрузки параметров хранящегося в файле изображения. Определение структуры BITMAPINFOHEADER выглядит следующим образом:

typedef struct tagBITMAPINFOHEADER { DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER;

Поля biWidth и biHeight содержат размеры изображения. Поле biBitCount указывает глубину цвета изображения. Оно используется, чтобы убедиться, что загружаемый файл содержит 8-битовое изображение.

Объявление массива структур RGBQUAD включено потому, что файл BMP хранит данные палитры в виде элементов RGBQUAD. Объявленный массив содержит 256 элементов, поскольку это максимально возможное количество цветов, которое может храниться в файле.

Затем объявлен массив структур PALETTEENTRY. Мы используем этот массив для создания палитры DirectDraw.


Структуры RGBQUAD и PALETTEENTRY очень похожи. Их определение выглядит так:

typedef struct tagRGBQUAD { BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; } RGBQUAD;

typedef struct tagPALETTEENTRY { BYTE peRed; BYTE peGreen; BYTE peBlue; BYTE peFlags; } PALETTEENTRY;

Главное различие — порядок следования красной, зеленой и синей цветовых составляющих. Для копирования содержимого массива RGBQUAD в массив PALETTEENTRY будет использован цикл.

Сначала функция InstallPalette() проверяет строку palettefile:

if (palettefile.GetLength() <= 0) return FALSE;

Вспомните, что значение строки palettefile устанавливается в функции UsePalette(). Если класс, производный от RMWin не использует UsePalette() для объявления имени файла BMP, палитра не создается и функция InstallPalette() возвращает FALSE. Это не так плохо, как может показаться, поскольку палитра требуется только для 8-разрядных видеорежимов. Приложения, использующие только 16-, 24- и 32-разрядные видеорежимы будут правильно выполняться и без использования функции UsePalette().

Затем проверяется глубина цвета в текущем видеорежиме:

if (modedepth != 8) return FALSE;

Палитра не требуется для тех видеорежимов, глубина цвета которых отличается от восьми, и в этом случае функция InstallPalette() завершает работу.

Потом функция InstallPalette() освобождает любые существующие палитры:

if (palette) { palette->Release(); palette = 0; }

Далее открывается файл BMP и загружаются структуры, содержащие параметры файла и изображения:

ifstream bmp(palettefile, ios::binary | ios::nocreate);

bmp.read((char*)&bmpfilehdr, sizeof(bmpfilehdr)); bmp.read((char*)&bmpinfohdr, sizeof(bmpinfohdr));

Загруженные данные используются при проверке сигнатуры файла BMP и глубине цвета изображения, содержащегося в файле:

char* ptr = (char*)&bmpfilehdr.bfType; if (*ptr != 'B' || *++ptr != 'M') { TRACE("invalid bitmap\n"); return FALSE; }

if (bmpinfohdr.biBitCount != 8) { TRACE("not 8 bit file!\n"); return FALSE; }



Если отсутствует сигнатура «BM», значит, файл поврежден, или это вообще не файл BMP. В этом случае выводится сообщение об ошибке, и функция возвращает FALSE.

Файлы, глубина цветности изображений в которых меньше восьми, бесполезны для нас. Например, четырехбитные файлы используют только 16 цветов, чего явно недостаточно для 256-цветного видеорежима. Файлы, в которых глубина цвета изображения больше или равна 16 вообще не содержат палитры. Если глубина цвета содержащегося в файле изображения не равна восьми, функция завершает работу.

Затем вычисляется количество цветов в палитре:

if (bmpinfohdr.biClrUsed == 0) ncolors = 256; else ncolors = bmpinfohdr.biClrUsed;

Бывает, что значение поля biClrUsed равно нулю. Это указывает, что файл содержит максимально возможное для данной глубины количество цветов. Если значение поля biClrUsed равно нулю, мы присваиваем переменной ncolors значение 256. В противном случае мы просто присваиваем значение поля biClrUsed переменной ncolors.

На следующем шаге функция InstallPalette() выполняет загрузку палитры с диска и инициализирует массив структур PALETTEENTRY:

bmp.read((char*)quad, sizeof(RGBQUAD) * ncolors ); for( int i = 0; i < ncolors; i++) { pe[i].peRed = quad[i].rgbRed; pe[i].peGreen = quad[i].rgbGreen; pe[i].peBlue = quad[i].rgbBlue; pe[i].peFlags = D3DPAL_READONLY; }

Палитра загружается в массив quad. Затем в цикле элементы массива quad копируются в массив pe. Константа D3DPAL_READONLY используется для указания, что цвета в массиве не должны изменяться.

Теперь мы можем создать палитру DirectDraw:

HRESULT r = ddraw->CreatePalette(DDPCAPS_8BIT, pe, &palette, 0); if (r != DD_OK) { TRACE("failed to load palette data from file\n"); return FALSE; }

Палитра создается функцией CreatePalette() интерфейса DirectDraw. Константа DDPCAPS_8BIT указывает DirectDraw, что данные предоставленной палитры являются 8-разрядными. Массив pe передается во втором аргументе. Третий аргумент функции CreatePalette() — это адрес указателя на новую палитру.

В заключение новая палитра присоединяется к первичной и вторичной поверхностям:

primsurf->SetPalette(palette); backsurf->SetPalette(palette);

На этом работа функции InstallPalette() завершается. Файл BMP закрывается автоматически при выходе из функции, поскольку объект ifstream, используемый для открытия файла, выходит из области видимости.


Функции работы с поверхностями


Класс RMWin создает и обслуживает поверхности DirectDraw, необходимые для полноэкранных операций Direct3D. Как вы увидите позже в этой главе, иногда полезно создавать и отображать дополнительные поверхности. Для этого в класс RMWin были добавлены две функции, относящиеся к работе с поверхностями: CreateSurface() и ClearSurface(). Функция CreateSurface() создает новую поверхность указанного размера. Функция ClearSurface() стирает содержимое существующей поверхности.



Функции управления глубиной иерархии


Меню Depth приложения Molecule предлагает шесть различных значений глубины. Для реализации каждого пункта меню применяются две функции. Функции для первого пункта меню (устанавливающего глубину иерархии равной 1), выглядят следующим образом:

void MoleculeWin::OnDepth1() { curdepth = 1; CreateHierarchy(); }

void MoleculeWin::OnUpdateDepth1(CCmdUI* pCmdUI) { pCmdUI->SetCheck(curdepth == 1); }

Первая функция, OnDepth1(), вызывается, когда пользователь выбирает первый пункт в меню Depth. Она присваивает 1 переменной curdepth и вызывает функцию CreateHierarchy() чтобы заново создать всю иерархию фреймов.

Вторая функция, OnUpdateDepth1(), вызывается MFC перед отображением меню Depth. Функция SetCheck() используется для отображения флажка рядом с пунктом меню, соответствующим текущему значению глубины.

Оставшиеся десять функций меню Depth практически полностью идентичны только что рассмотренным. Их единственным отличием является число, используемое при манипуляциях с переменной класса curdepth.



Функции управления скоростью анимации


Приложение Rocket предоставляет меню Speed, позволяющее выбрать одну из трех команд для изменения скорости анимации: Fast, Medium и Slow. Ниже приведены две функции, которые используются для реализации команды Fast меню Speed:

void RocketWin::OnSpeedFast() { speed = fastspeed; }

void RocketWin::OnUpdateSpeedFast(CCmdUI* pCmdUI) { pCmdUI->SetCheck(speed == fastspeed); }

Эти функции просто используют константу fastspeed для присваивания и проверки значения члена данных speed. Для контроля скорости выполнения анимационной последовательности в функциях меню Speed используются следующие константы:

const D3DVALUE fastspeed = D3DVALUE(0.026); const D3DVALUE mediumspeed = D3DVALUE(0.013); const D3DVALUE slowspeed = D3DVALUE(0.007);

Большее значение соответствует более высокой скорости анимации. Константа slowspeed имеет очень малое значение, обеспечивающее неторопливую и плавную анимацию.



Функция ActivateDisplayMode()


Класс RMWin предоставляет две версии функции ActivateDisplayMode(). Первая версия объявлена как защищенная и используется производными классами, чтобы сообщить классу RMWin о необходимости включения указанного видеорежима. Вторая версия объявлена как закрытая и используется защищенной версией для выполнения действительного переключения видеорежима. Защищенная версия функции ActivateDisplayMode() выглядит следующим образом:

BOOL RMWin::ActivateDisplayMode(int index) { DWORD w = displaymode[index].width; DWORD h = displaymode[index].height; DWORD d = displaymode[index].depth;

curdisplaymode = index; return ActivateDisplayMode(w, h, d); }

В качестве аргумента функции ActivateDisplayMode() передается единственное целое число. Это число является индексом элемента в списке поддерживаемых видеорежимов, созданном функцией InitDisplayMode().

ActivateDisplayMode() использует переменную index для получения параметров видеорежима из указанного элемента массива displaymode. Затем обновляется значение закрытой переменной curdisplaymode и вызывается закрытая версия функции ActivateDisplayMode(). Код закрытой версии функции ActivateDisplayMode() приведен в листинге 10.7.

Листинг 10.7. Закрытая версия функции ActivateDisplayMode()

BOOL RMWin::ActivateDisplayMode(DWORD w,DWORD h,DWORD d) { if (modewidth == w && modeheight == h && modedepth == d) return TRUE;

modewidth = w; modeheight = h; modedepth = d;

if (scene) { scene->Release(); scene = 0; } if (device) { device->Release(); device = 0; } if (primsurf) { primsurf->Release(); primsurf = 0; } if (zbufsurf) { zbufsurf->Release(); zbufsurf = 0; }

ddraw->SetDisplayMode(modewidth, modeheight, modedepth);

InitMainSurfaces();

InstallPalette();

CreateDevice();

d3drm->CreateFrame(0, &scene); CreateScene();

return TRUE; }

Эта версия более сложна чем предыдущая, поскольку отвечает за уничтожение и повторное создание внутренних объектов приложения.

Сперва следует проверить, не активирован ли уже запрашиваемый видеорежим.
Если параметры запрашиваемого видеорежима не отличаются от параметров текущего видеорежима, функция завершает работу. Если запрошен новый видеорежим, обновляются значения переменных modewidth, modeheight и modedepth.

Затем уничтожаются существующие корневой фрейм, устройство Direct3D, первичная поверхность и Z-буфер. Вторичный буфер удаляется вместе с первичной поверхностью, поэтому явно указывать его удаление не требуется.

Теперь для активации нового видеорежима вызывается функция SetDisplayMode() интерфейса DirectDraw. В качестве аргументов ей передаются переменные modewidth, modeheight и modedepth.

Следующие пять вызовов функций аналогичны фрагменту функции OnCreate(), следующему за вызовом функции InitDisplayMode():

InitMainSurfaces();

InstallPalette();

CreateDevice();

d3drm->CreateFrame(0, &scene); CreateScene();

В функции OnCreate() эти пять вызовов инициализируют приложение в соответствии с начальным видеорежимом. В данном случае эти вызовы изменяют параметры приложения согласно изменению видеорежима.


Функция AmbientLightWin::CreateScene()


Сцена создается в функции CreateScene(). Если вы назвали проект AmbientLight, функция CreateScene() будет выглядеть подобно показанной в листинге 6.1.

Листинг 6.1. Функция AmbientLightWin::CreateScene()

BOOL AmbientLightWin::CreateScene() { HRESULT r; // ------ СЕТКА -------- d3drm->CreateMeshBuilder(&meshbuilder); r = meshbuilder->Load(meshname, NULL, D3DRMLOAD_FROMFILE, NULL, NULL); if (r != D3DRM_OK) { CString msg; msg.Format("Failed to load file '%s'\n", meshname); AfxMessageBox(msg); return FALSE; } ScaleMesh(meshbuilder, D3DVALUE(25));

//------- ФРЕЙМ СЕТКИ -------- LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.1)); meshframe->Release(); meshframe = 0;

// -------- РАССЕЯННЫЙ СВЕТ -------- LPDIRECT3DRMLIGHT alight; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &alight); scene->AddLight(alight); alight->Release(); alight = 0;

//------ КАМЕРА ---------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);

return TRUE; }

Функция CreateScene() выполняет следующие действия:

Создание и загрузка сетки.

Создание фрейма для сетки.

Создание источника рассеянного света.

Создание порта просмотра.

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

d3drm->CreateMeshBuilder(&meshbuilder); r = meshbuilder->Load(meshname, NULL, D3DRMLOAD_FROMFILE, NULL, NULL); if (r != D3DRM_OK) { CString msg; msg.Format("Failed to load file '%s'\n", meshname); AfxMessageBox(msg); return FALSE; } ScaleMesh(meshbuilder, D3DVALUE(25));

Сначала с помощью функции CreateMeshBuilder() интерфейса Direct3DRM инициализируется переменная meshbuilder.
Затем для загрузки файла сетки с диска вызывается функция Load() (переменная meshname хранит имя файла сетки, которое было указано при создании приложения с помощью мастера AppWizard). Если выполнение функции Load() завершается неудачей, выводится окно сообщения и возвращается значение FALSE. Если функция Load() завершилась успешно, вызывается функция ScaleMesh() для задания оптимального размера сетки.

На втором этапе работы функции CreateScene() выполняется создание фрейма к которому будет присоединена сетка. Эта часть кода выглядит так:

LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.1)); meshframe->Release(); meshframe = 0;

Указатель meshframe объявлен как указатель на интерфейс Direct3DRMFrame. Для создания нового фрейма применяется функция CreateFrame() интерфейса Direct3DRM. Конструктор сеток meshbuilder, созданный на предыдущем этапе, присоединяется к новому фрейму с помощью функции AddVisual().

Далее производится вызов функции SetRotation(), чтобы назначить фрейму атрибуты вращения. Фрейм будет вращаться вокруг оси Y (на это указывают первые три аргумента функции). Последний аргумент функции SetRotation() указывает, что фрейм будет поворачиваться при каждом обновлении изображения на 0.1радиан.

На третьем этапе работы функции CreateScene() создается источник рассеянного света:

LPDIRECT3DRMLIGHT alight; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &alight); scene->AddLight(alight); alight->Release(); alight = 0;

Переменная alight объявляется как указатель на интерфейс Direct3DRMLight. Функция CreateLightRGB() используется для создания источника света и инициализации указателя alight. Первый аргумент функции CreateLightRGB() — это константа, которая определяет тип создаваемого источника света. Используемая здесь константа, D3DRMLIGHT_AMBIENT, является одной из пяти возможных:



D3DRMLIGHT_AMBIENT

D3DRMLIGHT_POINT

D3DRMLIGHT_SPOT

D3DRMLIGHT_DIRECTIONAL

D3DRMLIGHT_PARALLELPOINT

Следующие три аргумента функции CreateLightRGB() определяют цвет источника света. Использованные значения (1, 1, 1) указывают, что будет создан источник белого света, поскольку для красной, зеленой и синей составляющих заданы одинаковые, максимально возможные значения. Ниже показаны некоторые альтернативные варианты.

d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1.00), D3DVALUE(0.00), D3DVALUE(0.00), &alight); // создает источник красного света d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.00), D3DVALUE(1.00), D3DVALUE(0.00), &alight); // создает источник зеленого света d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.00), D3DVALUE(0.00), D3DVALUE(1.00), &alight); // создает источник синего света d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1.00), D3DVALUE(0.00), D3DVALUE(1.00), &alight); // создает источник пурпурного света d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.30), D3DVALUE(0.30), D3DVALUE(0.30), &alight); // создает источник темно-серого света

Если в сцене присутствует только один источник рассеянного света (что справедливо для нашей сцены), все грани в сцене будут визуализированы с использованием цвета этого источника света. Последним аргументом функции CreateLightRGB() является адрес указателя alight.

Затем новый источник света присоединяется к фрейму сцены:

scene->AddLight(alight);

Как вы увидите далее в этой главе, когда мы будем рассматривать другие типы источников света, остальные источники света не будут присоединены непосредственно к корневому фрейму сцены. Источник рассеянного света может быть присоединен к коревому фрейму потому, что на него не оказывает влияние местоположение и ориентация фрейма. Фактически, источник рассеянного света может быть присоединен к любому фрейму сцены и результат от этого не изменится.

После этого мы освобождаем указатель alight и обнуляем его:



alight->Release(); alight = 0;

Помните, что вызов функции Release() освобождает указатель, а не сам объект. Объект будет уничтожен только когда не останется ни одного указывающего на него указателя. В данном случае источник света не будет уничтожен, поскольку он был добавлен к сцене.

Четвертым и последним этапом работы функции CreateScene() является создание порта просмотра:

d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);

В этом фрагменте кода выполняется инициализация двух указателей: camera и viewport. Как говорилось в главе 4, эти указатели являются членами класса RMWin. Наша функция CreateScene() должна инициализировать эти указатели. Если мы не сделаем этого, выполнение приложения будет остановлено, и будет выведено сообщение об ошибке.

Указатель camera — это указатель на интерфейс Direct3DRMFrame, который инициализируется функцией CreateFrame() интерфейса Direct3DRM. Затем новый фрейм перемешается на 50 единиц от начала координат по направлению к зрителю. Это делается из-за того, что сетка, созданная на первом этапе, была помещена в начало координат (по умолчанию). Мы смещаем камеру от начала координат, чтобы объект попал в область просмотра. Затем для инициализации указателя viewport вызывается функция CreateViewport() интерфейса Direct3DRM.

Завершая работу, функция CreateScene() возвращает TRUE, чтобы сообщить об успешном выполнении всех действий. Если функция CreateScene() возвращает FALSE, работа приложения прекращается и выводится сообщение об ошибке.


Функция ClearSurface()


Подобно функции CreateSurface(), функция ClearSurface() включена для того, чтобы сделать более простым написание классов, производных от RMWin. Функция ClearSurface() присваивает заданное значение цвета каждой точке указанной поверхности. Код функции выглядит следующим образом:

bool RMWin::ClearSurface(LPDIRECTDRAWSURFACE surf, DWORD clr) { DDBLTFX bltfx; memset(&bltfx, 0, sizeof(bltfx)); bltfx.dwSize = sizeof(bltfx); bltfx.dwFillColor = clr; surf->Blt(0, 0, 0, DDBLT_COLORFILL | DDBLT_WAIT, &bltfx); return TRUE; }

Функция получает два аргумента: указатель на очищаемую поверхность и значение, используемое для очистки поверхности.

Для удаления содержимого поверхности используется функция Blt() интерфейса DirectDrawSurface. Обычно функция Blt() применяется для копирования поверхностей или их частей на другие поверхности. Тем не менее, функция Blt() может выполнять специальные операции, такие как зеркальное отражение, вращение и операции с Z-буфером. В нашем случае мы используем структуру DDBLTFX и константу DDBLT_COLORFILL чтобы сообщить функции Blt(), что мы хотим к указанной поверхности применить операцию заливки заданным цветом.



Функция CreateDevice()


Функция CreateDevice() отвечает за создание стандартных интерфейсов Direct3D, наиболее важным из которых является интерфейс Direct3DRMDevice. Функция CreateDevice() является закрытой, поэтому классы, производные от RMWin ничего не знают о выполняемых ею действиях. Полный текст функции приведен в листинге 4.1.

Листинг 4.1. Функция RMWin::CreateDevice()

BOOL RMWin::CreateDevice() { HRESULT r;

r = DirectDrawCreateClipper(0, &clipper, NULL); if (r != D3DRM_OK) { TRACE("failed to create D3D clipper\n"); return FALSE; } r = clipper->SetHWnd(NULL, m_hWnd); if (r != DD_OK) { TRACE("failed in SetHWnd call\n"); return FALSE; }

RECT rect; ::GetClientRect(m_hWnd, &rect);

r = d3drm->CreateDeviceFromClipper(clipper, GetGUID(), rect.right, rect.bottom, &device); if (r != D3DRM_OK) { TRACE("CreateDeviceFromClipper failed\n"); return FALSE; }

device->SetQuality(D3DRMRENDER_GOURAUD);

HDC hdc = ::GetDC(m_hWnd); int bpp = ::GetDeviceCaps(hdc, BITSPIXEL); ::ReleaseDC(m_hWnd, hdc);

switch (bpp) { case 8: device->SetDither(FALSE); break; case 16: device->SetShades(32); d3drm->SetDefaultTextureColors(64); d3drm->SetDefaultTextureShades(32); device->SetDither(FALSE); break; case 24: case 32: device->SetShades(256); d3drm->SetDefaultTextureColors(64); d3drm->SetDefaultTextureShades(256); device->SetDither(FALSE); break; } r = d3drm->CreateFrame(NULL, &scene); if (r != D3DRM_OK) { TRACE("CreateFrame(&scene) failed\n"); return FALSE; }

if (CreateScene() == FALSE) { AfxMessageBox("CreateScene() failed"); return FALSE; } ASSERT( camera ); ASSERT( viewport ); return TRUE; }

Первое, что вы должны знать о функции CreateDevice(): она присавивает зачения трем переменным класса RMWin — clipper, device и scene. Эти переменные являются защищенными членами класса RMWin, поэтому они доступны для членов класса SampleWin. Это позволяет классу SampleWin пользоваться интерфейсами, созданными классом RMWin.


Сначала присваивается значение переменной clipper. Функция DirectDrawCreateClipper() применяется для получения указателя на интерфейс DirectDrawClipper. Объект отсечения (clipper) — это конструкция DirectDraw, управляющая обновлением окна и позволяющая приложениям DirectDraw и Direct3D корректно работать в оконной среде. Объект отсечения получил свое название в связи с тем фактом, что перекрывающиеся окна должны отображаться в соответствии с тем, какие из их частей видимы. Прямоугольное отсечение обрабатывается Windows, и объект отсечения представляет соответствующую функциональность.

После создания объекта отсечения вызывается функция SetHWnd() интерфейса DirectDrawClipper. Тем самым окну назначается объект отсечения, который будет управлять им. Передаваемая в качестве аргумента переменная m_hWnd, является дескриптором окна, и инициализируется MFC.

Затем вызываются функции GetClientRect() и CreateDeviceFromClipper(). GetClientRect() — это функция Win32, получающая размеры клиентской области окна (клиентская область это внутренняя часть окна не включающая рамку и меню). Функция CreateDeviceFromClipper() является членом интерфейса Direct3DRM и применяется для создания указателя на интерфейс Direct3DRMDevice.

Функция CreateDeviceFromClipper() получает несколько параметров и заслуживает более пристального изучения. Вот как выглядит вызов этой функции из функции CreateDevice():

r = d3drm->CreateDeviceFromClipper(clipper, GetGUID(), rect.right, rect.bottom, &device);

Первый аргумент CreateDeviceFromClipper() — это указатель на интерфейс DirectDrawClipper. Второй аргумент — значение, возвращаемое функцией GetGUID(). Эту функцию мы опишем после того, как завершим знакомство с функцией CreateDevice().

Третий и четвертый аргументы — ширина и высота клиентской области окна. Благодаря этому, функция CreateDeviceFromClipper() создает устройство точно соответствующее размерам окна.

СОВЕТ Изменение размеров окна. Размер устройства Direct3D не может быть изменен. Если изменяются размеры окна необходимо уничтожить существующее устройство, после чего создать новое в соответствии с новыми размерами окна. О том, как это делается, мы поговорим в разделе, посвященном описанию функции RMWin::OnSize().
<


/p> В качестве последнего аргумента передается адрес переменной устройства, что дает возможность инициализировать указатель на новое устройство.

После того, как устройство создано, настроим его параметры для применения визуализации по методу Гуро, посредством функции SetQuality():

device->SetQuality(D3DRMRENDER_GOURAUD);

Сразу после создания устройства при визуализации по умолчанию используется равномерная закраска. Мы изменяем этот параметр, чтобы наши программы при визуализации по умолчанию применяли метод Гуро. В дальнейшем, при создании специализированных интерфейсов приложения, этот параметр можно переопределить.

Следующая задача, выполняемая функцией CreateDevice(), — получение глубины пикселей текущего видеорежима:

HDC hdc = ::GetDC(m_hWnd); int bpp = ::GetDeviceCaps(hdc, BITSPIXEL); ::ReleaseDC(m_hWnd, hdc);

Функция GetDeviceCaps() вызывается, чтобы присвоить значение, равное количеству битов, используемых для представления одного пиксела, переменной bpp. Это значение определяет максимальное количество одновременно выводимых цветов для текущего видеорежима. Затем значение переменной bpp используется в операторе switch, устанавливающем некоторые параметры объектов Direct3DRMDevice и Direct3DRM. Оптимальные значения параметров зависят от конкретного приложения. Значения, используемые в функции CreateDevice() являются хорошей отправной точкой, но только экспериментирование позволит добиться наилучших результатов для вашего приложения.

Затем, с помощью функции CreateFrame() интерфейса Direct3DRM создается корневой фрейм сцены:

r = d3drm->CreateFrame(NULL, &scene);

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

Далее вызывается функция CreateScene():

if (CreateScene() == FALSE) { AfxMessageBox("CreateScene() failed"); return FALSE; }

Функция CreateScene() переопределяется в классе SampleWin, чтобы создавать необходимые для конкретного приложения сцены.Функция CreateScene() может применяться для создания любой сцены, но с одним условием: в ней должны инициализироваться переменные camera и viewport. Чуть позже мы подробнее рассмотрим функцию CreateScene().

Последние четыре строки функции CreateDevice() выглядят следующим образом:

CreateScene(); ASSERT(camera); ASSERT(viewport); return TRUE;

Макроопределение ASSERT проверяет инициализированы ли переменные camera и viewport и прерывает выполнение приложения с соответствующим сообщением, если этого не было сделано.

Наконец, функция CreateDevice() возвращает TRUE. Если вы вернетесь к листингу 4.1, то увидите, что при возникновении какой-либо ошибки, макроопределение TRACE выводит сообщение и возвращается FALSE. Возврат значения FALSE уведомляет класс RMWin о необходимости прервать исполнение приложения.


Функция CreateFPSSurface()


Функция CreateFPSSurface() подходит к созданию поверхности немного по другому, чем функция CreateMenuSurface(). Функция CreateFPSSurface() сначала определяет размер поверхности, и только потом создает ее, как показано в листинге 10.12. Размеры поверхности зависят от размера выводимого текста.

Листинг 10.12. Функция CreateFPSSurface()

BOOL FullScreenWin::CreateFPSSurface() { static const char dummystr[] = "FPS: 0000";

HDC hdc = ::GetDC(0); SelectObject(hdc, smallfont); SIZE size; GetTextExtentPoint(hdc, dummystr, strlen(dummystr), &size); ::ReleaseDC(0, hdc);

fpsrect.left = 0; fpsrect.top = 0; fpsrect.right = size.cx; fpsrect.bottom = size.cy;

fpssurf = CreateSurface(size.cx, size.cy);

DDCOLORKEY ddck; ddck.dwColorSpaceLowValue = 0; ddck.dwColorSpaceHighValue = 0; fpssurf->SetColorKey(DDCKEY_SRCBLT, &ddck);

return TRUE; }

Для определения размера текста используется строка-макет, созданная с учетом худшего случая. Эта строка содержит больше символов, чем строки, которые будут действительно отображаться на экране. Поэтому размер текста будет достаточен для вывода любого значения частоты кадров (вплоть до 9 999 кадров в секунду).

Для вычисления размеров текста создается временный контекст устройства. Выбирается меньший из двух шрифтов приложения, и вызывается функция GetTextExtentPoint() для получения размера текста в пикселях. Затем размер текста используется при инициализации структуры fpsrect и создании поверхности fpssurf.

Затем поверхности назначается цветовой ключ. Как и в случае с поверхностью меню видеорежимов, прозрачными будут считаться пиксели с нулевым значением.



Функция CreateMenuSurface()


Функция CreateMenuSurface() выглядит следующим образом:

BOOL FullScreenWin::CreateMenuSurface() { menusurf = CreateSurface(menuwidth, menuheight);

menurect.left = 0; menurect.top = 0; menurect.right = menuwidth; menurect.bottom = menuheight;

DDCOLORKEY ddck; ddck.dwColorSpaceLowValue = 0; ddck.dwColorSpaceHighValue = 0; menusurf->SetColorKey(DDCKEY_SRCBLT, &ddck);

return TRUE; }

Сначала для создания поверхности используется функция RMWin::CreateSurface(). В качестве аргументов функции используются константы menuwidth и menuheight. Функция CreateSurface() возвращает указатель на новую поверхность.

Затем инициализируется структура menurect. Прямоугольник не описывает местоположение поверхности, а только ее размеры.

Затем поверхности назначается цветовой ключ. Цветовой ключ (color key) указывает, какие значения пикселей должны интерпретироваться как прозрачные. В нашем случае мы указываем, что прозрачными должны считаться любые пиксели поверхности меню видеорежимов с нулевым значением. Если вы взглянете на работающее приложение FullScreen, то заметите, что вся поверхность меню, за исключением текста, прозрачна. Это вызвано тем, что перед выводом текста всем пикселям поверхности присваивается нулевое значение.

Говоря о содержимом поверхности, следует упомянуть, что только что созданная поверхность содержит случайное содержимое. При создании поверхности выделяется память для ее размещения, но инициализация этой памяти не производится.



Функция CreateScene()


Рассматривая функцию CreateDevice(), мы узнали, что в конце она вызывает функцию CreateScene(). Функция CreateScene() класса RMWin объявлена как чисто виртуальная. Это значит, что классы, производные от RMWin должны предоставлять свою версию функции CreateScene(). Функция CreateScene() отвечает за создание любых сеток, источников света и иерархий фреймов, отображаемых приложением.

Перед тем, как рассмотреть функцию CreateScene(), необходимо упомянуть, что класс SampleWin наследует от класса RMWin несколько важных переменных. Функция CreateScene() может обращаться к этим переменным, поскольку они объявлены как защищенные члены класса. Перечислим эти переменные:

d3drm Это указатель на интерфейс Direct3DRM созданный функцией RMWin::OnCreate(). Мы будем применять этот указатель при создании конструкторов сеток, источников света, фреймов и других объектов Direct3D.

device Это указатель на интерфейс Direct3DRMDevice. Он применяется для задания параметров устройства, таких как наилучшее качество визуализации. Указатель device также используется для создания порта просмотра.

scene Переменная scene представляет собой указатель на интерфейс Direct3DRMFrame который служит как корневой фрейм нашей сцены. Все создаваемые объекты будут присоединяться к фрейму сцены.

camera Это также указатель на интерфейс Direct3DRMFrame. В отличие от переменных d3drm, device и scene, переменная camera не инициализируется в классе RMWin. Мы должны инициализировать ее, создав фрейм камеры. Ориентация и местоположение, заданные для фрейма камеры будут определять ориентацию зрителя и направление просмотра сцены.

viewport Переменная viewport— это указатель на интерфейс Direct3DRMViewport. Этот указатель, также как и указатель camera неинициализирован. Мы выполним его инициализацию посредством функции CreateViewport() интерфейса Direct3DRM.

Листинг 4.3 содержит код функции CreateScene() нашего приложения.

Листинг 4.3. Функция SampleWin::CreateScene()

BOOL SampleWin::CreateScene() { HRESULT r; // ------СЕТКА-------- d3drm->CreateMeshBuilder(&meshbuilder); r = meshbuilder->Load(meshname, NULL, D3DRMLOAD_FROMFILE, NULL, NULL); if (r != D3DRM_OK) { CString msg; msg.Format("Failed to load file '%s'\n", meshname); AfxMessageBox(msg); return FALSE; } ScaleMesh(meshbuilder, D3DVALUE(25));

LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.1)); meshframe->Release(); meshframe = 0;

// --------ЗОНАЛЬНЫЙ СВЕТ-------- LPDIRECT3DRMLIGHT slight; d3drm->CreateLightRGB(D3DRMLIGHT_SPOT, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &slight);

LPDIRECT3DRMFRAME slightframe; d3drm->CreateFrame(scene, &slightframe); slightframe->AddLight(slight); slightframe->SetPosition (scene, D3DVALUE(0),D3DVALUE(20),D3DVALUE(-20)); slightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-20), D3DVALUE(20), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); slightframe->AddMoveCallback(MoveLight, NULL); slight->Release(); slight = 0; slightframe->Release(); slightframe = 0;

//------ КАМЕРА ---------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);

return TRUE; }

Показанная на листинге 4.3 функция CreateScene() выполняет следующие действия:

Создание сетки и настройка ее параметров.

Создание источника зонального освещения и настройка его параметров.

Создание и настройка порта просмотра.


/p> Функция CreateScene() выполняет следующие шесть действий:

Инициализация и отображение поверхностей меню и счетчика FPS.

Создание и загрузка сетки.

Создание фрейма для сетки.

Создание и настройка анимационной последовательности с использованием интерфейса Direct3DRMAnimation.

Создание источника света.

Создание порта просмотра.

Мы не будем обсуждать этапы со 2 по 6, поскольку они неоднократно рассматривались в предыдущих главах. Сейчас для нас более интересен первый этап, на котором осуществляется инициализация поверхностей для меню видеорежимов и счетчика FPS. Поверхности инициализируются следующими вызовами функций:

selectmode = GetCurDisplayMode(); CreateMenuSurface(); UpdateMenuSurface();

CreateFPSSurface();

Функция GetCurDisplayMode() используется для инициализации переменной selectmode. Это делается для того, чтобы в меню был выделен текущий видеорежим. Функция CreateMenuSurface() создает поверхность, которая будет использована для отображения меню видеорежимов. Функция UpdateMenuSurface() формирует содержимое для поверхности меню видеорежимов. Функция CreateFPSSurface() создает поверхность для счетчика FPS, но само значение FPS пока не рассчитывается, поскольку еще не было выведено ни одного кадра.


Функция CreateSurface()


Продолжим разговор о вспомогательных функциях и рассмотрим функцию CreateSurface(). Она создает внеэкранную поверхность DirectDraw, получая в качестве параметров требуемые ширину и высоту поверхности. Код функции выглядит так:

LPDIRECTDRAWSURFACE RMWin::CreateSurface(DWORD w, DWORD h) { DDSURFACEDESC desc; memset(&desc, 0, sizeof(desc)); desc.dwSize = sizeof(desc); desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS; desc.dwWidth = w; desc.dwHeight = h; desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;

LPDIRECTDRAWSURFACE surf; HRESULT r = ddraw->CreateSurface(&desc, &surf, 0); if (r != DD_OK) return 0;

return surf; }

Локальная структура DDSURFACEDESC используется для описания внеэкранной поверхности с размерами, равными полученным параметрам. Функция CreateSurface() интерфейса DirectDraw создает новую поверхность. Если вызов функции CreateSurface() завершен успешно, будет возвращен указатель на новую поверхность.

Функция CreateSurface() создана для использования в классах, производных от класса RMWin, но не используется самим классом RMWin. Мы воспользуемся этой функцией в приложении FullScreen.



Функция Cube2Win::CreateScene()


Код функции CreateScene() из приложения Cube2 приведен в листинге8.2.

Листинг 8.2. Функция Cube2Win::CreateScene()

BOOL Cube2Win::CreateScene() { //---------- СЕТКА -------- d3drm->CreateMesh(&mesh);

mesh->AddGroup(12, 3, 4, vertorder, &group1); mesh->AddGroup(12, 3, 4, vertorder, &group2);

mesh->SetVertices(group1, 0, 12, vertexlist); mesh->SetVertices(group2, 0, 12, vertexlist + 12);

mesh->Translate(D3DVALUE(-0.5), D3DVALUE(-0.5), D3DVALUE(-0.5)); mesh->Scale(D3DVALUE(15), D3DVALUE(15), D3DVALUE(15));

//--------- ТЕКСТУРА ---------- HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_WIN95TEXTURE), "TEXTURE"); LPDIRECT3DRMTEXTURE texture; d3drm->LoadTextureFromResource(texture_id, &texture); mesh->SetGroupTexture(group1, texture); mesh->SetGroupMapping(group1, D3DRMMAP_PERSPCORRECT); mesh->SetGroupTexture(group2, texture); mesh->SetGroupMapping(group2, D3DRMMAP_PERSPCORRECT); texture->Release(); texture = 0;

//---------- ФРЕЙМ ---------- LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(mesh); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(-.05));

static CallbackData cbdata; cbdata.mesh = mesh; cbdata.group1 = group1; cbdata.group2 = group2; meshframe->AddMoveCallback(UpdateCube, &cbdata); meshframe->AddMoveCallback(UpdateColors, &cbdata); meshframe->Release(); meshframe = 0;

// -------- СВЕТ -------- LPDIRECT3DRMLIGHT dlight, alight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &dlight); d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.50), D3DVALUE(0.50), D3DVALUE(0.50), &alight);

LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(5), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); lightframe->AddLight(dlight); lightframe->AddLight(alight); dlight->Release(); dlight = 0; alight->Release(); alight = 0; lightframe->Release(); lightframe = 0;

//------ КАМЕРА ---------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);

return TRUE; }

<
/p> Функция CreateScene() выполняет следующие действия:

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

Создание текстуры для сетки.

Создание фрейма для размещения сетки и установка функции обратного вызова.

Создание двух источников света.

Создание порта просмотра.

На первом этапе выполняется создание сетки:

d3drm->CreateMesh(&mesh);

mesh->AddGroup(12, 3, 4, vertorder, &group1); mesh->AddGroup(12, 3, 4, vertorder, &group2);

mesh->SetVertices(group1, 0, 12, vertexlist); mesh->SetVertices(group2, 0, 12, vertexlist + 12);

mesh->Translate(D3DVALUE(-0.5), D3DVALUE(-0.5), D3DVALUE(-0.5)); mesh->Scale(D3DVALUE(15), D3DVALUE(15), D3DVALUE(15));

Сначала для инициализации указателя mesh вызывается функция CreateMesh() интерфейса Direct3DRM. Затем к пустой сетке добавляются две группы граней, каждая из которых создается функцией AddGroup(). Каждая группа включает 12 вершин: 3 грани по 4 вершины в каждой. В качестве третьего аргумента функции AddGroup() используется массив vertorder. В приложении Cube массив vertorder содержал 24 элемента; в приложении Cube2 в этом массиве только 12 элементов:

unsigned vertorder[] = { 0,1,2,3,4,5,6,7,8,9,10,11 };

Последний аргумент функции AddGroup() является адресом идентификатора групп граней сетки. Функция AddGroup() назначает каждой группе граней уникальное значение идентификатора.

Теперь, чтобы присвоить значения вершинам каждой группы, воспользуемся функцией SetVertices(). Первым параметром этой функции является идентификатор группы вершин. Второй аргумент — это индекс первой изменяемой вершины. Третий аргумент — число вершин, которым будут присвоены новые значения. Последний, четвертый аргумент является массивом структур D3DRMVERTEX. Обратите внимание, что для инициализации двух групп граней используется один и тот же массив структур. Во втором вызове функции SetVertices() указано смещение, приводящее к тому, что для инициализации второй группы граней используется вторая половина массива.



Затем выполняется вызов функции Translate() интерфейса Direct3DRMMesh и функции Scale(). В отличие от функций AddGroup() и SetVertices(), функции Translate() и Scale() модифицируют все входящие в сетку группы. Вызов этих функций ничем не отличается от использованного в приложении Cube.

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

HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_WIN95TEXTURE), "TEXTURE"); LPDIRECT3DRMTEXTURE texture; d3drm->LoadTextureFromResource(texture_id, &texture); mesh->SetGroupTexture(group1, texture); mesh->SetGroupMapping(group1, D3DRMMAP_PERSPCORRECT); mesh->SetGroupTexture(group2, texture); mesh->SetGroupMapping(group2, D3DRMMAP_PERSPCORRECT); texture->Release(); texture = 0;

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

LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(mesh); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(-.05));

static CallbackData cbdata; cbdata.mesh = mesh; cbdata.group1 = group1; cbdata.group2 = group2; meshframe->AddMoveCallback(UpdateCube, &cbdata); meshframe->AddMoveCallback(UpdateColors, &cbdata); meshframe->Release(); meshframe = 0;

Фрейм создается функцией CreateFrame() интерфейса Direct3DRM и используется для размещения сетки. После этого фрейму назначаются атрибуты вращения.

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

Функция AddMoveCallback() интерфейса Direct3DRMFrame используется для установки двух функций обратного вызова: UpdateCube() и UpdateColors(). Каждая функция обратного вызова будет получать при вызове указатель на объявленную ранее статическую структуру данных. Функция UpdateCube() выполняет анимацию вершин для обоих групп граней сетки.Функция UpdateColors() выполняет анимацию цвета второй группы граней.

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


Функция Cube2Win::UpdateColors()


Функция UpdateColors() выполняет анимацию цветов для второй группы граней сетки. Код функции выглядит следующим образом:

void Cube2Win::UpdateColors(LPDIRECT3DRMFRAME, void* p, D3DVALUE) { CallbackData* data = (CallbackData*)p; static D3DVALUE clr = D3DVALUE(.5); static D3DVALUE inc = D3DVALUE(.2); clr += inc; if (clr < D3DVALUE(.3) || clr > D3DVALUE(1)) { inc = -inc; clr += inc; } data->mesh->SetGroupColorRGB(data->group2, clr, D3DVALUE(0), D3DVALUE(0)); }

Переменная clr используется для вычисления и хранения текущего цвета группы граней сетки. Сразу после вычисления значения переменной clr, оно устанавливается с помощью функции SetGroupColorRGB() интерфейса Direct3DRMMesh. Анимируется только красная составляющая цвета группы граней сетки. Зеленая и синяя составляющая цвета всегда равны нулю.



Функция Cube2Win::UpdateCube()


Функция UpdateCube() выполняет две задачи: она осуществляет анимацию вершин и, кроме того, она периодически изменяет атрибуты вращения сетки.

void Cube2Win::UpdateCube(LPDIRECT3DRMFRAME frame, void* p, D3DVALUE) { CallbackData* data = (CallbackData*)p; static const D3DVALUE lim = D3DVALUE(5); static D3DVALUE control; static D3DVALUE inc = D3DVALUE(.25); static D3DRMVERTEX vert[12];

data->mesh->GetVertices(data->group1, 0, 12, vert); vert[0].position.x += inc; vert[0].position.y += inc; vert[0].position.z += inc; vert[6].position.x += inc; vert[6].position.y += inc; vert[6].position.z += inc; vert[8].position.x += inc; vert[8].position.y += inc; vert[8].position.z += inc; data->mesh->SetVertices(data->group1, 0, 12, vert);

data->mesh->GetVertices(data->group2, 0, 12, vert); vert[2].position.x += inc; vert[2].position.y += inc; vert[2].position.z += inc; vert[6].position.x += inc; vert[6].position.y += inc; vert[6].position.z += inc; vert[8].position.x += inc; vert[8].position.y += inc; vert[8].position.z += inc; data->mesh->SetVertices(data->group2, 0, 12, vert); control += inc; if (control > lim || control < -lim) inc = -inc;

static UINT delay; if (++delay < 20) return; delay = 0;

LPDIRECT3DRMFRAME scene; frame->GetScene(&scene);

D3DVECTOR spinvect; D3DRMVectorRandom(&spinvect); D3DVALUE spin = D3DDivide(rand() % 50 + 1, 400); frame->SetRotation(scene, spinvect.x, spinvect.y, spinvect.z, spin); }

При изменении местоположения вершин сетки функция UpdateCube() использует следующие статические переменные:

static const D3DVALUE lim = D3DVALUE(5); static D3DVALUE control; static D3DVALUE inc = D3DVALUE(.25); static D3DRMVERTEX vert[12];

Переменные lim, control и inc применяются для управления анимацией вершин. Массив vert используется для временного хранения данных каждой из групп граней сетки. Обратите внимание, что в данном случае массив состоит из 12 элементов, а в приложении Cube в массиве vert было 24 элемента.


В каждой из двух групп вершин сетки есть анимируемые вершины, поэтому функции GetVertices() и SetVertices() применяются для изменения параметров обоих групп вершин сетки:

data->mesh->GetVertices(data->group1, 0, 12, vert); vert[0].position.x += inc; vert[0].position.y += inc; vert[0].position.z += inc; vert[6].position.x += inc; vert[6].position.y += inc; vert[6].position.z += inc; vert[8].position.x += inc; vert[8].position.y += inc; vert[8].position.z += inc; data->mesh->SetVertices(data->group1, 0, 12, vert);

data->mesh->GetVertices(data->group2, 0, 12, vert); vert[2].position.x += inc; vert[2].position.y += inc; vert[2].position.z += inc; vert[6].position.x += inc; vert[6].position.y += inc; vert[6].position.z += inc; vert[8].position.x += inc; vert[8].position.y += inc; vert[8].position.z += inc; data->mesh->SetVertices(data->group2, 0, 12, vert);

Массив vert используется для хранения данных обоих групп вершин. Оставшаяся часть функции UpdateCube() полностью аналогична функции UpdateCube() приложения Cube.


Функция CubeWin::CreateScene()


Сцена приложения Cube создается в функции CreateScene(), код которой представлен в листинге8.1.

Листинг 8.1. Функция CubeWin::CreateScene()

BOOL CubeWin::CreateScene() { // ------- СЕТКА -------- d3drm->CreateMesh(&mesh); mesh->AddGroup(24, 6, 4, vertorder, &group); mesh->SetVertices(group, 0, 24, vertexlist); mesh->Translate(D3DVALUE(-0.5), D3DVALUE(-0.5), D3DVALUE(-0.5)); mesh->Scale(D3DVALUE(12), D3DVALUE(12), D3DVALUE(12));

//-------- ТЕКСТУРА ------ HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_WIN95TEXTURE), "TEXTURE"); LPDIRECT3DRMTEXTURE texture; d3drm->LoadTextureFromResource(texture_id, &texture); mesh->SetGroupTexture(group, texture); mesh->SetGroupMapping(group, D3DRMMAP_PERSPCORRECT); texture->Release(); texture = 0;

//------- ФРЕЙМ -------- LPDIRECT3DRMFRAME frame; d3drm->CreateFrame(scene, &frame); frame->AddVisual(mesh); frame->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.04));

static CallbackData cbdata; cbdata.mesh = mesh; cbdata.group = group; frame->AddMoveCallback(UpdateCube, &cbdata); frame->Release(); frame = 0;

// --------- СВЕТ -------- LPDIRECT3DRMLIGHT dlight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &dlight);

LPDIRECT3DRMLIGHT alight; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.50), D3DVALUE(0.50), D3DVALUE(0.50), &alight);

LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0));

lightframe->AddLight(dlight); lightframe->AddLight(alight);

dlight->Release(); dlight = 0; alight->Release(); alight = 0; lightframe->Release(); lightframe = 0;

//------ КАМЕРА ---------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);

return TRUE; }

<
/p> Функция CreateScene() выполняет следующие действия:

Создание сетки куба.

Создание текстуры и ее наложение на сетку.

Создание фрейма для сетки и установка функции обратного вызова.

Создание двух источников света.

Создание порта просмотра.

На первом этапе мы воспользуемся интерфейсом Direct3DRMMesh чтобы создать сетку куба. Давайте взглянем на код:

d3drm->CreateMesh(&mesh); mesh->AddGroup(24, 6, 4, vertorder, &group); mesh->SetVertices(group, 0, 24, vertexlist); mesh->Translate(D3DVALUE(-0.5), D3DVALUE(-0.5), D3DVALUE(-0.5)); mesh->Scale(D3DVALUE(12), D3DVALUE(12), D3DVALUE(12));

Сперва для инициализации указателя mesh вызывается функция CreateMesh() интерфейса Direct3DRM. Обратите внимание, что ранее в тех приложениях, где использовался интерфейс Direct3DRMMesh, для создания сетки применялась функция CreateMesh() интерфейса Direct3DRMMeshBuilder, а не функция CreateMesh() интерфейса Direct3DRM. Создать сетку с помощью конструктора сеток очень легко, потому что можно использовать функцию Load() интерфейса Direct3DRMMeshBuilder чтобы загрузить сетку из файла. Поскольку мы используем функцию CreateMesh() интерфейса Direct3DRM, у нас будет создана пустая сетка.

Затем для инициализации сетки вызывается функция AddGroup() интерфейса Direct3DRMMesh. В результате будет создана группа граней сетки. Группой называется набор граней одной сетки, которыми можно управлять вместе как единой сущностью. Первым аргументом функции AddGroup() является количество вершин в группе. У нашего куба 24 вершины, поскольку он состоит из 6 граней у каждой из которых по 4 вершины. Второй аргумент функции AddGroup() задает количество граней в группе, а третий — количество вершин у каждой из граней. Четвертый аргумент функции AddGroup() — это массив индексов вершин. Определение массива vertorder, используемого в нашей программе в качестве четвертого аргумента, выглядит так:

unsigned vertorder[] = { 0,1,2,3,4,5,6,7,8,9,10,11, 12,13,14,15,16,17,18,19,20,21,22,23 };



Данный массив задает порядок вершин. В нашем случае индексы отсортированы в простейшем из возможных, последовательном порядке. Когда сетка создается с нуля, как это делаем мы, применение непоследовательного порядка вершин не дает особых преимуществ. Однако в данных сеток, экспортируемых из программ трехмерного моделирования, таких как 3D Studio, вершины вряд ли будут отсортированы по порядку номеров.

Пятый и последний аргумент функции AddGroup() это указатель на член данных group. Функция AddGroup() инициализирует эту переменную, записывая в нее идентификатор, который в последующих вызовах функций будет применяться для определения интересующей нас группы граней сетки. Если сетка содержит только одну группу граней (как в нашем случае), в качестве идентификатора группы используется ноль.

Функция AddGroup() добавляет грани к сетке и устанавливает порядок вершин, но все данные граней имеют устанавливаемые по умолчанию значения. Значения всех координат и нормалей новых вершин равны нулю. Новые грани окрашены в белый цвет и на них не наложены никакие текстуры. Чтобы присвоить начальные значения вершинам, необходимо воспользоваться функцией SetVertices():

mesh->SetVertices(group, 0, 24, vertexlist);

Функция SetVertices() присваивает значения координат, нормалей и позицию текстуры для одной или нескольких вершин группы граней сетки. Первый аргумент функции SetVertices() определяет модифицируемую группу граней сетки. Мы используем член данных group, который был инициализирован при вызове функции AddGroup(). Второй аргумент — это номер вершины с которой мы начнем изменения (индекс первой модифицируемой вершины). Третий аргумент — число модифицируемых вершин. Поскольку мы хотим задать значения для всех 24 вершин в группе граней сетки, используем значения 0 и 24. Четвертый аргумент является массивом структур D3DRMVERTEX в котором содержатся новые данные для вершин.

Перед тем, как взглянуть на массив vertexlist (используемый в качестве четвертого аргумента функции SetVertices()), давайте поговорим о структуре D3DRMVERTEX.


Ее определение в Direct3D выглядит следующим образом:

typedef struct _D3DRMVERTEX { D3DVECTOR position; D3DVECTOR normal; D3DVALUE tu, tv; D3DCOLOR color; } D3DRMVERTEX;

Вектор position используется чтобы задать местоположение вершины. Вектор normal задает вектор нормали для вершины. Поля tu и tv определяют координаты текстуры. Если на сетку будет наложена текстура, эти два значения определяют какая именно точка текстуры совпадет с вершиной. И, наконец, поле color задает цвет вершины.

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

#define VERTEX(px,py,pz,nx,ny,nz,tu,tv) \ { { D3DVALUE(px),D3DVALUE(py),D3DVALUE(pz) }, \ { D3DVALUE(nx),D3DVALUE(ny),D3DVALUE(nz), }, \ D3DVALUE(tu),D3DVALUE(tv),D3DCOLOR(0) }

Макрос получает восемь аргументов и создает из них одну структуру D3DRMVERTEX. Главная польза этого макроса в том, что он избавляет нас от необходимости загромождать объявление массива операциями приведения каждого поля структуры D3DRMVERTEX к типу D3DVALUE. Вот как выглядит определение массива вершин vertexlist, используемого в приложении Cube для инициализации сетки:

static D3DRMVERTEX vertexlist[]= { // левая грань VERTEX( 0,0,0, -1,0,0, 0,1 ), // вершина 0 VERTEX( 0,0,1, -1,0,0, 0,0 ), VERTEX( 0,1,1, -1,0,0, 1,0 ), VERTEX( 0,1,0, -1,0,0, 1,1 ), // правая грань VERTEX( 1,0,0, 1,0,0, 0,0 ), VERTEX( 1,1,0, 1,0,0, 1,0 ), VERTEX( 1,1,1, 1,0,0, 1,1 ), // вершина 6 VERTEX( 1,0,1, 1,0,0, 0,1 ), // передняя грань VERTEX( 0,0,0, 0,0,-1, 0,0 ), // вершина 8 VERTEX( 0,1,0, 0,0,-1, 1,0 ), VERTEX( 1,1,0, 0,0,-1, 1,1 ), VERTEX( 1,0,0, 0,0,-1, 0,1 ), // задняя грань VERTEX( 0,0,1, 0,0,1, 0,1 ), VERTEX( 1,0,1, 0,0,1, 0,0 ), VERTEX( 1,1,1, 0,0,1, 1,0 ), // вершина 14 VERTEX( 0,1,1, 0,0,1, 1,1 ), // верхняя грань VERTEX( 0,1,0, 0,1,0, 0,0 ), VERTEX( 0,1,1, 0,1,0, 1,0 ), VERTEX( 1,1,1, 0,1,0, 1,1 ), // вершина 18 VERTEX( 1,1,0, 0,1,0, 0,1 ), // нижняя грань VERTEX( 0,0,0, 0,-1,0, 0,0 ), // вершина 20 VERTEX( 1,0,0, 0,-1,0, 1,0 ), VERTEX( 1,0,1, 0,-1,0, 1,1 ), VERTEX( 0,0,1, 0,-1,0, 0,1 ), };



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

Обратите внимание на комментарии, расположенные справа от описания шести из вершин. Таким образом отмечены те вершины, которые будут анимироваться.

Вернемся к коду создания сетки (этапу 1) в функции CreateScene(). Последние два вызова функций выглядят так:

mesh->Translate(D3DVALUE(-0.5), D3DVALUE(-0.5), D3DVALUE(-0.5)); mesh->Scale(D3DVALUE(12), D3DVALUE(12), D3DVALUE(12));

Функция Translate() применяется для настройки осей сетки. Куб создан таким образом, что в начале координат расположена одна из его вершин. Мы используем функцию Translate() для перемещения куба таким образом, чтобы с началом координат совпадал его центр. Функция Scale() используется чтобы увеличить куб в 12 раз. Обратите внимание, что порядок вызова этих двух функций очень важен. Если мы выполним операцию масштабирования перед операцией перемещения, начало координат не будет совпадать с центром куба.

На втором этапе своей работы функция CreateScene() создает текстуру и накладывает ее на созданную сетку:

HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_WIN95TEXTURE), "TEXTURE"); LPDIRECT3DRMTEXTURE texture; d3drm->LoadTextureFromResource(texture_id, &texture); mesh->SetGroupTexture(group, texture); mesh->SetGroupMapping(group, D3DRMMAP_PERSPCORRECT); texture->Release(); texture = 0;

Текстура загружается из ресурсов программы функцией LoadTextureFromResource() интерфейса Direct3DRM. Затем вызывается функция SetGroupTexture() интерфейса Direct3DRMMesh чтобы связать текстуру с сеткой. Для разрешения перспективной коррекции используется функция SetGroupMapping() интерфейса Direct3DRMMesh.


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

На третьем этапе создается фрейм для сетки:

LPDIRECT3DRMFRAME frame; d3drm->CreateFrame(scene, &frame); frame->AddVisual(mesh); frame->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.04));

static CallbackData cbdata; cbdata.mesh = mesh; cbdata.group = group; frame->AddMoveCallback(UpdateCube, &cbdata); frame->Release(); frame = 0;

Новый фрейм будет потомком фрейма scene и создается с помощью функции CreateFrame() интерфейса Direct3DRM. Сетка присоединяется к новому фрейму функцией AddVisual() интерфейса Direct3DRMFrame. С помощью функции SetRotation() новому фрейму назначается атрибут вращения. Этот атрибут является временным, поскольку присутствующая в приложении функция обратного вызова будет периодически изменять атрибут вращения фрейма сетки.

Далее объявляется статическая структура CallbackData. Эта структура используется в приложении Cube для передачи необходимых данных в функцию обратного вызова. Она объявлена статической потому что используется после того, как функция CreateScene() завершит свою работу, и объявленные в ней локальные переменные перестанут существовать.

В структуре сохраняются указатель на сетку и идентификатор группы граней сетки. Затем выполняется установка функции обратного вызова (UpdateCube()) с помощью функции AddMoveCallback() интерфейса Direct3DRMFrame. Указатель на структуру CallbackData передается функции AddMoveCallback() во втором аргументе.

На четвертом и пятом этапах работы функции CreateScene() выполняется создание источника света и порта просмотра. Мы не будем обсуждать эти действия здесь, поскольку они подробно рассмотрены в других главах и не имеют отношения к анимации вершин.


Функция CubeWin::UpdateCube()


Функция UpdateCube() является функцией обратного вызова, которая выполняет анимацию вершин в приложении Cube. Код функции UpdateCube() выглядит так:

void CubeWin::UpdateCube(LPDIRECT3DRMFRAME frame, void* p, D3DVALUE) { CallbackData* data = (CallbackData*)p; static const D3DVALUE lim = D3DVALUE(5); static D3DVALUE control; static D3DVALUE inc = D3DVALUE(.25); static D3DRMVERTEX vert[24];

data->mesh->GetVertices(data->group, 0, 24, vert);

vert[0].position.x += inc; vert[0].position.y += inc; vert[0].position.z += inc; vert[6].position.x += inc; vert[6].position.y += inc; vert[6].position.z += inc; vert[8].position.x += inc; vert[8].position.y += inc; vert[8].position.z += inc; vert[14].position.x += inc; vert[14].position.y += inc; vert[14].position.z += inc; vert[18].position.x += inc; vert[18].position.y += inc; vert[18].position.z += inc; vert[20].position.x += inc; vert[20].position.y += inc; vert[20].position.z += inc;

data->mesh->SetVertices(data->group, 0, 24, vert); control += inc; if (control > lim || control < -lim) inc = -inc;

static UINT delay; if (++delay < 20) return; delay = 0;

LPDIRECT3DRMFRAME scene; frame->GetScene(&scene);

D3DVECTOR spinvect; D3DRMVectorRandom(&spinvect); D3DVALUE spin = D3DDivide(rand() % 50 + 1, 400); frame->SetRotation(scene, spinvect.x, spinvect.y, spinvect.z, spin); }

Сначала функция подготавливает указатель на структуру CallbackData:

CallbackData* data = (CallbackData*)p;

Параметр p — это указатель на статическую структуру cbdata объявленную в функции CreateScene(). В то же время, переменная p объявлена в объявлении функции как указатель на void. По этой причине мы для доступа к необходимым данным будем использовать в функции обратного вызова локальный указатель data, присвоив ему приведенное к требуемому типу значение указателя p.

Затем следует объявление четырех статических переменных:

static const D3DVALUE lim = D3DVALUE(5); static D3DVALUE control; static D3DVALUE inc = D3DVALUE(.25); static D3DRMVERTEX vert[24];


Переменная lim является константой, ограничивающей перемещение вершин. Переменная control используется для определения текущего местоположения анимируемых вершин. Переменная inc хранит значение на которое будет изменяться значение переменной control. И, наконец, переменная vert является массивом структур D3DRMVERTEX. Мы будем использовать ее при получении, изменении и присваивании данных вершин сетки.

Теперь для получения текущих данных вершин сетки используется функция GetVertices() интерфейса Direct3DRMMesh:

data->mesh->GetVertices(data->group, 0, 24, vert);

Список аргументов функции GetVertices() аналогичен списку аргументов функции SetVertices(). Обратите внимание, что и указатель на сетку и идентификатор группы граней сетки передаются через указатель data. После вызова функции GetVertices() массив vert будет заполнен текущими параметрами вершин сетки.

Настало время изменить координаты вершин сетки:

vert[0].position.x += inc; vert[0].position.y += inc; vert[0].position.z += inc; vert[6].position.x += inc; vert[6].position.y += inc; vert[6].position.z += inc; vert[8].position.x += inc; vert[8].position.y += inc; vert[8].position.z += inc; vert[14].position.x += inc; vert[14].position.y += inc; vert[14].position.z += inc; vert[18].position.x += inc; vert[18].position.y += inc; vert[18].position.z += inc; vert[20].position.x += inc; vert[20].position.y += inc; vert[20].position.z += inc;

Каждая из координат изменяется на значение, хранящееся в переменной inc. Порядок изменения координат вершин не имеет значения, поскольку все эти изменения не вступят в силу до вызова функции SetVertices():

data->mesh->SetVertices(data->group, 0, 24, vert);

Функция SetVertices() изменяет сетку, присваивая ее вершинам измененные параметры.

Затем изменяется значение переменной control:

control += inc; if (control > lim || control < -lim) inc = -inc;

В этом коде используется переменная lim чтобы изменять значение переменной inc если значение переменной control достигло заданного предела.

Оставшаяся часть функции UpdateCube() осуществляет периодическое изменение атрибутов вращения фрейма:

static UINT delay; if (++delay < 20) return; delay = 0;

LPDIRECT3DRMFRAME scene; frame->GetScene(&scene);

D3DVECTOR spinvect; D3DRMVectorRandom(&spinvect); D3DVALUE spin = D3DDivide(rand() % 50 + 1, 400); frame->SetRotation(scene, spinvect.x, spinvect.y, spinvect.z, spin);

После каждых 20 обновлений экрана этот код вычисляет новые вектор и скорость вращения. Затем новые значения устанавливаются функцией SetRotation() интерфейса Direct3DRMFrame.


Функция DecalWin::CreateScene()


В отличие от приложения Wraps, вся функциональность приложения Decal предоставляется функцией CreateScene(). Эта функция создает и настраивает декалы, источник света и порт просмотра. Поскольку анимация в приложении Decal реализуется с помощью атрибутов движения, никакие функции обратного вызова не требуются. Код функции CreateScene() представлен в листинге 5.3.

Листинг 5.3. Функция DecalWin::CreateScene()

BOOL DecalWin::CreateScene() { //------ ДЕКАЛЫ -------- LPDIRECT3DRMTEXTURE texture1, texture2; HRSRC texture_id; texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE1), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture1); texture1->SetDecalOrigin(64, 64);

texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE2), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture2); texture2->SetDecalOrigin(64, 64);

//-------- ФРЕЙМЫ -------- LPDIRECT3DRMFRAME dummyframe1; d3drm->CreateFrame(scene, &dummyframe1); dummyframe1->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0.05));

LPDIRECT3DRMFRAME dummyframe2; d3drm->CreateFrame(scene, &dummyframe2); dummyframe2->SetRotation(scene, D3DVALUE(1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(-0.05));

LPDIRECT3DRMFRAME orbitframe1; d3drm->CreateFrame(dummyframe1, &orbitframe1); orbitframe1->SetPosition(dummyframe1, D3DVALUE(2), D3DVALUE(0), D3DVALUE(0)); orbitframe1->AddVisual(texture1);

LPDIRECT3DRMFRAME orbitframe2; d3drm->CreateFrame(dummyframe2, &orbitframe2); orbitframe2->SetPosition(dummyframe2, D3DVALUE(0), D3DVALUE(-2), D3DVALUE(0)); orbitframe2->AddVisual(texture2);

texture1->Release(); texture1 = 0; texture2->Release(); texture2 = 0; dummyframe1->Release(); dummyframe1 = 0; dummyframe2->Release(); dummyframe2 = 0; orbitframe1->Release(); orbitframe1 = 0; orbitframe2->Release(); orbitframe2 = 0;

//--------- СВЕТ -------- LPDIRECT3DRMLIGHT light; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1.0),D3DVALUE(1.0), D3DVALUE(1.0), &light); scene->AddLight(light); light->Release(); light = 0;

//--------- ПОРТ ПРОСМОТРА -------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(-6.0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; }

<
/p> Функция CreateScene() выполняет следующие четыре действия:

Создание двух текстур, которые будут использоваться в качестве декалов.

Создание фреймов для двух текстур.

Создание источника света.

Создание порта просмотра.

Первый этап — загрузка двух текстур. Как и в большинстве демонстрационных приложений, текстуры хранятся в ресурсах в исполняемом файле приложения:

LPDIRECT3DRMTEXTURE texture1, texture2; HRSRC texture_id; texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE1), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture1); texture1->SetDecalOrigin(64, 64);

texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE2), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture2); texture2->SetDecalOrigin(64, 64);

После загрузки каждой из текстур вызывается функция SetDecalOrigin(). Она применяется для указания точки внутри текстуры, которая будет использоваться как начало координат. По умолчанию началом координат текстуры считается верхний левый угол (x = 0, y = 0). Это означает, что при присоединении текстуры к фрейму с заданным местоположением фрейма будет совпадать верхний левый угол текстуры. В приложении Decal используются две текстуры размером 128 на 128 точек. Используя в качестве аргументов функции SetDecalOrigin() число 64, мы помещаем начало координат текстуры в ее центр.

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

LPDIRECT3DRMFRAME dummyframe1; d3drm->CreateFrame(scene, &dummyframe1); dummyframe1->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0.05));

LPDIRECT3DRMFRAME dummyframe2; d3drm->CreateFrame(scene, &dummyframe2); dummyframe2->SetRotation(scene, D3DVALUE(1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(-0.05));

LPDIRECT3DRMFRAME orbitframe1; d3drm->CreateFrame(dummyframe1, &orbitframe1); orbitframe1->SetPosition(dummyframe1, D3DVALUE(2), D3DVALUE(0), D3DVALUE(0)); orbitframe1->AddVisual(texture1);



LPDIRECT3DRMFRAME orbitframe2; d3drm->CreateFrame(dummyframe2, &orbitframe2); orbitframe2->SetPosition(dummyframe2, D3DVALUE(0), D3DVALUE(-2), D3DVALUE(0)); orbitframe2->AddVisual(texture2);

Для каждого пустого фрейма функцией SetRotation() задаются параметры вращения, но сами фреймы не перемещаются из своего начального местоположения (начала координат). Перемещающиеся по орбите фреймы смещаются на заданное расстояние от пустых фреймов с помощью функции SetPosition(). Обратите внимание, что пустые фреймы являются потомками фрейма scene (корня иерархии фреймов), а перемещающиеся по орбитам фреймы являются потомками своих пустых фреймов. Созданные ранее текстуры присоединяются к помещенным на орбиту фреймам функцией AddVisual(). Тот факт, что текстура присоединяется непосредственно к фрейму и делает ее декалом.

На третьем и четвертом этапе для приложения Decal создаются источник света и порт просмотра. Используемый для этого код аналогичен тому, который мы видели в приложении Jade. Далее в этой книге мы более подробно изучим источники света и порты просмотра.


Функция FacePickWin::CreateScene()


Сцена приложения FacePick конструируется в функции FacePickWin::CreateScene(), код которой показан в листинге9.3.

Листинг 9.3. Функция FacePickWin::CreateScene()

BOOL FacePickWin::CreateScene() { // ------- СЕТКА -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_D3DMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->SetQuality(D3DRMRENDER_FLAT); ScaleMesh(meshbuilder, D3DVALUE(25));

//------- ФРЕЙМ ------ d3drm->CreateFrame(scene, &meshframe); meshframe->SetRotation(scene, D3DVALUE(1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(.05)); meshframe->AddVisual(meshbuilder); meshframe->AddMoveCallback(UpdateDrag, NULL);

// --------- СВЕТ -------- LPDIRECT3DRMLIGHT dlight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &dlight);

LPDIRECT3DRMLIGHT alight; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.40), D3DVALUE(0.40), D3DVALUE(0.40), &alight);

LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->AddLight(dlight); lightframe->AddLight(alight); lightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0));

dlight->Release(); dlight = 0; alight->Release(); alight = 0; lightframe->Release(); lightframe = 0;

//------ КАМЕРА ---------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);

return TRUE; }

Функция CreateScene() выполняет четыре действия:

Создание сетки.

Создание фрейма для сетки.

Создание двух источников света.

Создание порта просмотра.

Код, выполняющий первое действие, выглядит так:

D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_D3DMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->SetQuality(D3DRMRENDER_FLAT); ScaleMesh(meshbuilder, D3DVALUE(25));


Используемая по умолчанию сетка хранится в ресурсах приложения и идентифицируется константой IDR_D3DMESH. Хотя функция CreateScene() загружает эту внутреннюю сетку автоматически, сетка может быть заменена с помощью команды Open меню File. Обратите внимание, что функция SetQuality() используется, чтобы изменить метод визуализации сетки на плоский. Плоский метод выбран потому, что при его использовании отдельные грани сетки выделяются более четко, чем в других методах.

На втором этапе выполняется создание фрейма для сетки:

d3drm->CreateFrame(scene, &meshframe); meshframe->SetRotation(scene, D3DVALUE(1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(.05)); meshframe->AddVisual(meshbuilder); meshframe->AddMoveCallback(UpdateDrag, NULL);

Указатель meshframe является членом класса FacePickWin и поэтому не объявлен в функции CreateScene(). Инициализацию указателя выполняет функция CreateFrame() интерфейса Direct3DRM. Функция SetRotation() назначает фрейму начальные атрибуты вращения. Эти атрибуты могут быть в любой момент изменены путем перетаскивания сетки с помощью мыши (мы увидим как это делается, когда будем обсуждать функцию UpdateDrag()). После вызова функции SetRotation(), к новому фрейму присоединяется созданная ранее сетка с помощью функции AddVisual(). Затем вызывается функция AddMoveCallback() для установки функции обратного вызова UpdateDrag().

На следующем этапе создаются два источника света:

LPDIRECT3DRMLIGHT dlight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &dlight);

LPDIRECT3DRMLIGHT alight; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.40), D3DVALUE(0.40), D3DVALUE(0.40), &alight);

LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->AddLight(dlight); lightframe->AddLight(alight); lightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0));

Источники направленного и рассеянного света создаются функцией CreateLightRGB() интерфейса Direct3DRM.Источник рассеянного света будет испускать серый свет, поскольку при его создании были указаны уменьшенные значения RGB. Потом создается фрейм и источники света присоединяются к нему с помощью функции AddLight(). В завершение вызывается функция SetOrientation(), чтобы задать ориентацию источника направленного света. На источник рассеянного света ориентация фрейма не оказывает никакого влияния.

На завершающем, четвертом этапе создается порт просмотра:

d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);

Указатель camera инициализируется функцией CreateFrame() интерфейса Direct3DRM, а затем его местоположение задается функцией SetPosition(). Созданный фрейм используется в качестве аргумента функции CreateViewport() интерфейса Direct3DRM.


Функция FacePickWin::PickFace()


Функция PickFace() использует функцию Pick() интерфейса Direct3DRMViewport для выполнения операции выбора. Если в результате выполнения операции выбора был возвращен объект, функция вернет индекс грани этого объекта. Код функции PickFace() выглядит следующим образом:

int FacePickWin::PickFace(const CPoint& point) { HRESULT r; LPDIRECT3DRMPICKEDARRAY pickarray;

viewport->Pick(point.x, point.y, &pickarray);

int faceindex = -1; DWORD numpicks = pickarray->GetSize(); if (numpicks > 0) { LPDIRECT3DRMVISUAL visual; LPDIRECT3DRMFRAMEARRAY framearray; D3DRMPICKDESC pickdesc;

r = pickarray->GetPick(0, &visual, &framearray, &pickdesc); if (r == D3DRM_OK) { faceindex = pickdesc.ulFaceIdx; visual->Release(); framearray->Release(); } } pickarray->Release(); return faceindex; }

Сперва вызывается функция Pick() интерфейса Direct3DRMViewport. В первых двух аргументах этой функции передаются текущие координаты указателя мыши. Третий аргумент— это адрес указателя на интерфейс Direct3DRMPickedArray.

Затем вызывается функция GetSize(), чтобы определить, был ли выбран какой-либо объект. Если массив пуст, указатель pickarray освобождается и функция возвращает –1. Если в массиве есть элементы, первый из них извлекается с помощью функции GetPick(). Нам необходим только первый элемент, поскольку элементы массива отсортированы по значению координаты Z, а нас интересует самый близкий к зрителю объект.

Функция GetPick() инициализирует два указателя и структуру. Первый указатель указывает на видимый объект, который был выбран. В нашем случае указатель visual будет указывать на сетку, созданную в функции CreateScene() (поскольку эта сетка является единственным видимым объектом сцены). Однако указатель на видимый объект нас не интересует. Он мог бы потребоваться, если бы сцена содержала несколько сеток (как в приложении MeshPick). Второй указатель, инициализируемый функцией GetPick() — это указаетль на массив фреймов. Эти данные нас также не интересуют, по рассмотренным выше причинам.

Данные, в которых мы нуждаемся, — это индекс выбранной грани. Функция GetPick() сохраняет это значение в поле ulFaceIdx структуры D3DRMPICKDESC. Функция PickFace() сохраняет это значение и возвращает его после освобождения своих локальных указателей, завершая тем самым свою работу.

Между прочим, структура D3DRMPICKDESC содержит еще два поля, которые могут оказаться полезными:

lGroupIdx: Содержит индекс группы граней сетки. Интерфейс Direct3DRMMesh поддерживает работу с несколькими группами граней в сетке. Индекс группы идентифицирует группу, в которую входит выбранная грань.

vPosition: Ориентация грани. Этот вектор указывает лицевое направление выбранной грани.



Функция FacePickWin::UpdateDrag()


Функция UpdateDrag() является функцией обратного вызова, устанавливаемой в функции CreateScene(). Функция UpdateDrag() отвечает за вычисление новых атрибутов вращения сетки во время операции перетаскивания. Код функции приведен ниже:

void FacePickWin::UpdateDrag(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { if (drag) { double delta_x = GetMouseX() - last_x; double delta_y = GetMouseY() - last_y; last_x = GetMouseX(); last_y = GetMouseY(); double delta_r = sqrt(delta_x * delta_x + delta_y * delta_y); double radius = 50; double denom = sqrt(radius * radius + delta_r * delta_r);

if (!(delta_r == 0 || denom == 0)) frame->SetRotation(0, D3DDivide(-delta_y, delta_r), D3DDivide(-delta_x, delta_r), D3DVALUE(0.0), D3DDivide(delta_r, denom)); }

if (end_drag) { drag = FALSE; end_drag = FALSE; } }

Для вычисления вектора и скорости вращения функция использует текущую позицию указателя мыши и позицию указателя мыши в момент начала операции перетаскивания. Говоря по существу, перемещение мыши в двух измерениях преобразуется в вектор, а разница между старыми и новыми координатами мыши используется для вычисления скорости вращения. Полученные значения устанавливаются с помощью функции SetRotation() интерфейса Direct3DRMFrame.

Обратите внимание, что при каждом вызове функции UpdateDrag() проверяется флаг end_drag. Этот флаг устанавливается в функции, чтобы указать, что операция перетаскивания должна быть завершена. Если флаг end_drag установлен, операция перетаскивания прерывается.



Функция FireflyWin::CreateScene()


В приложении Firefly создание сцены осуществляется в функции CreateScene(). Функция отвечает за создание двух сеток: одной для чаши и одной для светлячка. Кроме того, в ней создается один точечный источник света и один порт просмотра. Для анимации источника света и сферической сетки применяются пустые фреймы. За дополнительной информацией о пустых фреймах можно обратиться к описанию приложения Decal в главе 5. Код функции CreateScene() приложения Firefly приведен в листинге 6.2.

Листинг 6.2. Функция FireflyWin::CreateScene()

BOOL FireflyWin::CreateScene() { //-------- СЕТКА ЧАШИ -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_CHALICEMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&chalicebuilder); chalicebuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); ScaleMesh(chalicebuilder, D3DVALUE(25));

//------- ФРЕЙМ ЧАШИ ------ LPDIRECT3DRMFRAME chaliceframe; d3drm->CreateFrame(scene, &chaliceframe); chaliceframe->AddVisual(chalicebuilder);

chaliceframe->Release(); chaliceframe = 0;

//-------- ТОЧЕЧНЫЙ СВЕТ -------- LPDIRECT3DRMLIGHT pointlight; d3drm->CreateLightRGB(D3DRMLIGHT_POINT, D3DVALUE(1.0),D3DVALUE(1.0), D3DVALUE(1.0), &pointlight);

//-------- СЕТКА СВЕТЛЯЧКА ------ resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER flybuilder; d3drm->CreateMeshBuilder(&flybuilder); flybuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); flybuilder->SetQuality(D3DRMRENDER_WIREFRAME); ScaleMesh(flybuilder, D3DVALUE(0.3));

//-------- ФРЕЙМЫ ИСТОЧНИКА СВЕТА -------- LPDIRECT3DRMFRAME dummyframe; d3drm->CreateFrame(scene, &dummyframe); dummyframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.08));

LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(dummyframe, &lightframe); lightframe->SetPosition(dummyframe, D3DVAL(15), D3DVAL(6), D3DVAL(0)); lightframe->AddLight(pointlight); lightframe->AddVisual(flybuilder);

flybuilder->Release(); flybuilder = 0; lightframe->Release(); lightframe = 0; dummyframe->Release(); dummyframe = 0; pointlight->Release(); pointlight = 0;

//-------- ПОРТ ПРОСМОТРА ------------ d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0.0), D3DVALUE(20.0), D3DVALUE(-50.0)); camera->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-20), D3DVALUE(50), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);

return TRUE; }

<
/p> Функция CreateScene() выполняет следующие действия:

Создание и загрузка сетки чаши.

Создание фрейма для сетки чаши.

Создание точечного источника света.

Создание сетки, представляющей светлячка.

Создание фреймов для источника света и сетки светлячка.

Создание порта просмотра.

Сначала создаем сетку чаши:

D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_CHALICEMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&chalicebuilder); chalicebuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); ScaleMesh(chalicebuilder, D3DVALUE(25));

Интерфейс Direct3DRMMeshBuilder используется для загрузки сетки из ресурсов программы. Затем, для изменения размеров сетки применяется функция ScaleMesh().

Новая сетка присоединяется к фрейму с именем chaliceframe:

LPDIRECT3DRMFRAME chaliceframe; d3drm->CreateFrame(scene, &chaliceframe); chaliceframe->AddVisual(chalicebuilder);

chaliceframe->Release(); chaliceframe = 0;

Для присоединения сетки чаши к новому фрейму вызывается функция AddVisual(). Перемещение фрейма не выполняется, поэтому сетка располагается в начале координат. Обратите внимание, что указатель chalicebuilder не объявлен в функции CreateScene(), а является членом класса FireflyWin. Это сделано для того, чтобы с помощью меню Render можно было изменять используемый метод визуализации. Также обратите внимание, что указатель chalicebuilder не освобождается в функции CreateScene(). Если мы освободим указатель chalicebuilder, то у нас не останется способа изменить метод визуализации во время работы программы.

Следующий шаг — создание точечного источника света. Сначала для создания источника света применим функцию CreateLightRGB() интерфейса Direct3DRM:

LPDIRECT3DRMLIGHT pointlight; d3drm->CreateLightRGB(D3DRMLIGHT_POINT, D3DVALUE(1.0), D3DVALUE(1.0), D3DVALUE(1.0), &pointlight);

Константа D3DRMLIGHT_POINT используется для указания типа создаваемого источника света.


Следующие три числовых аргумента сообщают, что мы создаем источник излучающий белый свет. Последний аргумент — это адрес указателя, который будет указывать на новый интерфейс Direct3DRMLight.

Теперь создается сетка, представляющая светлячка:

resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER flybuilder; d3drm->CreateMeshBuilder(&flybuilder); flybuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); flybuilder->SetQuality(D3DRMRENDER_WIREFRAME); ScaleMesh(flybuilder, D3DVALUE(0.3));

Фактически в программе нет сетки реального светлячка, а для представления светлячка используется просто сетка сферы. Идентификатор ресурса сетки сферы используется при подготовке структуры resinfo. Конструктор сеток создается с помощью функции CreateMeshBuilder() интерфейса Direct3DRM, а затем функция Load() интерфейса Direct3DRMMeshBuilder загружает сетку. Функция SetQuality() используется, чтобы включить для сетки каркасный метод визуализации. Это сделано для того, чтобы сетка была ясно видна на сцене. Вспомните, что каркасный метод визуализации использует при изображения сетки только ее цвет и игнорирует любые, включенные в сцену источники света. Использование каркасного метода приводит к тому, что сетка будет точно того цвета, который указан для нее, и никакие дополнительные вычисления цвета не требуются. Потом конструктор сеток масштабируется с помощью функции ScaleMesh().

На пятом этапе создаются два фрейма, которые мы будем использовать для анимации источника света и сферической сетки:

LPDIRECT3DRMFRAME dummyframe; d3drm->CreateFrame(scene, &dummyframe); dummyframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.08));

LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(dummyframe, &lightframe); lightframe->SetPosition(dummyframe, D3DVAL(15), D3DVAL(6), D3DVAL(0));

lightframe->AddLight(pointlight); lightframe->AddVisual(flybuilder);



flybuilder->Release(); flybuilder = 0; lightframe->Release(); lightframe = 0; dummyframe->Release(); dummyframe = 0; pointlight->Release(); pointlight = 0;

Указатель dummyframe инициализируется функцией CreateFrame() интерфейса Direct3DRM и является прямым потомком корневого фрейма сцены (scene). Новому фрейму назначается вращение вокруг оси Y. Это приведет к тому, что его дочерние фреймы (например, тот, который мы создадим чуть позже) тоже будут вращаться вокруг оси Y.

Второй фрейм (lightframe) создается как потомок фрейма dummyframe. Потом вызывается функция SetPosition(), чтобы переместить новый фрейм от начала координат. Если мы не переместим этот фрейм, он будет вращаться на одном месте, вместо того, чтобы перемещаться по орбите вокруг пустого фрейма.

Теперь к фрейму lightframe присоединяются сферическая сетка и источник света. Это гарантирует, что источник света и светлячок будут находиться в одной и той же точке пространства.

В конце все четыре указателя (два указателя на фреймы, указатель flybuilder и указатель pointlight) освобождаются и обнуляются.

Оставшаяся часть функции FireflyWin::CreateScene() (см. листинг 6.2) создает для приложения порт просмотра. Мы подробно изучим порты просмотра в главе 9.


Функция FullScreenWin()


Код конструктора класса FullScreenWin приведен в листинге10.9.

Листинг 10.9. Функция FullScreenWin()

FullScreenWin::FullScreenWin() { meshbuilder = 0; animation = 0; menusurf = 0; fpssurf = 0; selectmode = -1; displayfps = FALSE;

UsePalette("palette.bmp");

largefont = CreateFont(28, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, VARIABLE_PITCH, "Arial");

smallfont = CreateFont(14, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, VARIABLE_PITCH, "Arial"); }

Сначала выполняется инициализация переменных класса. Затем вызывается функция UsePalette(), чтобы указать имя файла BMP, содержащего палитру, которая будет использоваться в 8-разрядных видеорежимах.

Два дескриптора шрифтов, largefont и smallfont, инициализируются функцией Win32 CreateFont(). Единственное различие между этими двумя шрифтами — их размер. Крупный шрифт используется для отображения названия приложения (в верхней части меню видеорежимов). Мелкий шрифт используется для вывода элементов меню видеорежимов и для вывода счетчика FPS.



Функция GetCurDisplayMode()


Функция GetCurDisplayMode() возвращает индекс включенного в данный момент видеорежима:

int GetCurDisplayMode() { return curdisplaymode; }

Как и в случае с функцией GetNumDisplayModes(), простота функции GetCurDisplayMode() делает ее хорошим кандидатом на определение и объявление внутри определения класса.



Функция GetCurDisplayModeDims()


Определение функции GetCurDisplayModeDims() выглядит следующим образом:

BOOL RMWin::GetCurDisplayModeDims(DWORD& w, DWORD& h, DWORD& d) { if (curdisplaymode < 0 || curdisplaymode >= totaldisplaymodes) return FALSE; w = displaymode[curdisplaymode].width; h = displaymode[curdisplaymode].height; d = displaymode[curdisplaymode].depth;

return TRUE; }

Функция почти полностью идентична функции GetDisplayModeDims(), за исключением того, что возвращает параметры текущего, а не указанного в аргументе, видеорежима. Это вспомогательная функция. Те же самые данные можно получить, используя функцию GetDisplayModeDims() совместно с функцией GetCurDisplayMode().



Функция GetDisplayModeDims()


Функция GetDisplayModeDims() возвращает параметры указанного видеорежима:

BOOL RMWin::GetDisplayModeDims(int index, DWORD& w, DWORD& h, DWORD& d) { if (index < 0 || index >= totaldisplaymodes) return FALSE;

w = displaymode[index].width; h = displaymode[index].height; d = displaymode[index].depth;

return TRUE; }

Первый параметр функции GetDisplayModeDims() является индексом интересующего видеорежима. Индекс используется при получении параметров видеорежима из массива displaymode.

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



Функция GetGUID()


Функция GetGUID() применяется для получения GUID (глобального уникального идентификатора) идентифицирующего создаваемое функцией CreateDeviceFromClipper() устройство. Если вместо вызова функции GetGUID() использовать значение NULL, функция CreateDeviceFromClipper() автоматически выберет устройство с цветовой моделью Ramp. Мы вызываем функцию GetGUID() чтобы иметь возможность выбрать цветовую модель Ramp или RGB. Текст функции GetGUID() приведен в листинге 4.2.

Листинг 4.2. Функция GetGUID()

GUID* RMWin::GetGUID() { static GUID* lpguid; HRESULT r;

D3DFINDDEVICESEARCH searchdata; memset(&searchdata, 0, sizeof searchdata); searchdata.dwSize = sizeof searchdata; searchdata.dwFlags = D3DFDS_COLORMODEL; searchdata.dcmColorModel = colormodel;

static D3DFINDDEVICERESULT resultdata; memset(&resultdata, 0, sizeof resultdata); resultdata.dwSize = sizeof resultdata;

LPDIRECTDRAW ddraw; r = DirectDrawCreate(NULL, &ddraw, NULL); if (r != DD_OK) { TRACE("DirectDrawCreate failed\n"); return NULL; }

LPDIRECT3D d3d; r = ddraw->QueryInterface(IID_IDirect3D, (void**)&d3d); if (r != D3DRM_OK) { TRACE("d3drm->QueryInterface failed\n"); ddraw->Release(); return NULL; }

r = d3d->FindDevice(&searchdata, &resultdata); if (r == D3D_OK) lpguid = &resultdata.guid; else { TRACE("FindDevice failure\n"); lpguid = NULL; }

d3d->Release(); ddraw->Release();

return lpguid; }

Перед тем, как продолжить, посмотрим, почему функция GetGUID() такая сложная. Наша задача — получить GUID для заданного устройства Direct3D. Казалось бы, интерфейс Direct3DRM должен предоставлять функцию, выполняющую эту работу. Возможно, библиотека Direct3D и могла бы быть разработана таким способом, но этого не произошло.

Мы применяем абстрактный режим Direct3D, а абстрактный режим в свою очередь зависит от непосредственного режима, который фактически выполняет визуализацию. Это означает, что устройство Direct3D является конструкцией непосредственного режима, и для поиска требуемого устройства необходимо воспользоваься интерфейсом непосредственного режима.
Функции непосредственного режима доступны через COM-интерфейс Direct3D.

Мы можем получить указатель на интерфейс Direct3D создав интерфейс DirectDraw и вызвав его функцию QueryInterface(). Это возможно, поскольку объект DirectDraw начиная с DirectX версии 2 поддерживает интерфейс Direct3D. Если бы мы работали с DirectX версии 1, такой вызов QueryInterface() закончился бы неудачей.

Давайте взглянем на начало функции GetGUID() (листинг 4.2). Первое, что делает функция, — это подготовка двух структур, применяемых для хранения информации об устройстве. Структура searchdata используется для указания запрашиваемой цветовой модели, а в структуре resultdata хранятся результаты поиска. Ниже приведен фрагмент программы, создающий и инициализирующий эти структуры:

D3DFINDDEVICESEARCH searchdata; memset(&searchdata, 0, sizeof searchdata); searchdata.dwSize = sizeof searchdata; searchdata.dwFlags = D3DFDS_COLORMODEL; searchdata.dcmColorModel = colormodel;

static D3DFINDDEVICERESULT resultdata; memset(&resultdata, 0, sizeof resultdata); resultdata.dwSize = sizeof resultdata;

Функция memset() обнуляет все поля, после чего полю dwSize присваивается размер структуры.

СОВЕТ Поле dwSize. Требование, чтобы поле структуры содержало размер самой структуры, может показаться довольно глупым, но это сделано, чтобы позволить расширение функциональности без модификации старых программ. Microsoft может увеличить размер структуры в будущей версии Direct3D, а программы, написанные сегодня, будут работать, потому что Direct3D сможет определить, основываясь на значении поля dwSize, какая версия структуры используется.
Полю dwFlags присваивается значение константы D3DFDS_COLORMODEL — это указывает, что единственным критерием поиска является заданная цветовая модель устройства. Полю dcmColorModel присваивается значение переменной colormodel. По умолчанию значение переменной colormodel равно D3DCOLOR_MONO, но оно может быть изменено функцией SetColorModel() (см.


выше функцию InitInstance()).

На следующем этапе работы функции GetGUID() создается интерфейс DirectDraw:

LPDIRECTDRAW ddraw; r = DirectDrawCreate(NULL, &ddraw, NULL); if (r != DD_OK) { TRACE("DirectDrawCreate failed\n"); return NULL; }

Функция DirectDrawCreate() применяется для получения указателя на интерфейс DirectDraw. Если вызов функции завершается неудачно, макроопределение TRACE отображает соответствующее сообщение, после чего возвращается NULL.

Как только указатель на интерфейс DirectDraw получен, у объекта запрашивается интерфейс Direct3D:

LPDIRECT3D d3d; r = ddraw->QueryInterface(IID_IDirect3D, (void**)&d3d); if (r != D3DRM_OK) { TRACE("d3drm->QueryInterface failed\n"); ddraw->Release(); return NULL; }

Константа IID_IDirect3D представляет собой GUID интерфейса Direct3D и применяется здесь, чтобы указать функции QueryInterface() какой интерфейс мы ищем. Если функция завершается нормально, переменная d3d указывает на экземпляр интерфейса Direct3D. При возникновении ошибки выводится отладочное сообщение и функция возвращает NULL предварительно освободив интерфейс DirectDraw.

Теперь мы можем выполнить поиск GUID устройства:

r = d3d->FindDevice(&searchdata, &resultdata); if (r == D3D_OK) lpguid = &resultdata.guid; else { TRACE("FindDevice failure\n"); lpguid = NULL; }

Функция FindDevice() интерфейса Direct3D получает в качестве аргументов указатели на две подготовленные ранее структуры. Если функция возвращает D3D_OK, значит GUID найден и переменной lpguid присваивается указатель на него.

Обратите внимание, что структура resultdata и переменная lpguid объявлены как статические. Это сделано потому, что GUID представляет собой 128-разрядное значение. Объявив эти переменные статическими, мы можем возвратить указатель на GUID, а не делать копию его значения.

Перед завершением работы функция GetGUID() освобождает интерфейсы DirectDraw и Direct3D.


Функция GetMouse()


Мышь — это неотъемлемая часть интерфейса Windows и пользователи ожидают, что программа будет поддерживать работу с мышью так же, как она поддерживает работу с клавиатурой. Функции GetMouseX() и GetMouseY(), предоставляемые классом RMWin, позволяют в любой момент времени определить текущие координаты указателя мыши. Обе функции возвращают значение соответствующей координаты в пикселях. Функции объявлены как статические и могут использоваться функциями обратного вызова.



Функция GetNumDisplayModes()


Функция GetNumDisplayModes()— это защищенная функция, возвращающая количество видеорежимов, обнаруженных функцией InitDisplayMode(). Функция объявлена и определена в определении класса RMWin:

int GetNumDisplayModes() { return totaldisplaymodes; }



Функция InitDisplayMode()


Функция InitDisplayMode() использует DirectDraw чтобы определить, какие видеорежимы поддерживаются установленной видеокартой. Эти данные используются для создания списка видеорежимов. Затем этот список будет применяться для определения начального видеорежима. Код функции InitDisplayMode() приведен в листинге 10.3.

Листинг 10.3. Функция InitDisplayMode()

BOOL RMWin::InitDisplayMode() { curdisplaymode = 0;

CDC* dc = GetDC(); DWORD curdisplaydepth = dc->GetDeviceCaps(BITSPIXEL); dc->DeleteDC;

ddraw->EnumDisplayModes(0, 0, 0, DisplayModeAvailable); qsort(displaymode, totaldisplaymodes, sizeof(videomode), CompareModes);

for (int i = 0; i < totaldisplaymodes; i++) { DWORD w, h, d; GetDisplayModeDims(i, w, h, d); if (w == 640 && h == 480 && d == curdisplaydepth) curdisplaymode = i; }

GetDisplayModeDims(curdisplaymode, modewidth, modeheight, modedepth); ddraw->SetDisplayMode(modewidth, modeheight, modedepth); return totaldisplaymodes != 0; }

Сначала функция определяет глубину пикселей текущего видеорежима Windows. Это делается с помощью функции GetDeviceCaps(). Мы используем константу BITSPIXEL чтобы указать, что нас интересует количество бит, необходимое для представления одной точки изображения. Полученное значение сохраняется в переменной curdisplaydepth. Мы используем его позднее при выборе начального видеорежима.

Затем вызывается функция EnumDisplayModes() интерфейса DirectDraw. Последний аргумент функции EnumDisplayModes() — это функция обратного вызова, которую DirectDraw будет вызывать каждый раз при обнаружении поддерживаемого видеорежима. Для регистрации обнаруженных видеорежимов функция InitDisplayMode() использует следующую функцию обратного вызова:

HRESULT WINAPI RMWin::DisplayModeAvailable(LPDDSURFACEDESC desc, LPVOID) { int& count = totaldisplaymodes; if (count == MAXDISPLAYMODES) return DDENUMRET_CANCEL;

displaymode[count].width = desc->dwWidth; displaymode[count].height = desc->dwHeight; displaymode[count].depth = desc->ddpfPixelFormat.dwRGBBitCount;


count++; return DDENUMRET_OK; }

Функция обратного вызова DisplayModeAvailable() получает указатель на структуру DDSURFACEDESC. Эта структура описывает обнаруженный режим. Функция обратного вызова использует ее для инициализации элементов массива displaymode. После того, как элемент будет инициализирован, увеличивается переменная totaldisplaymodes (через псевдоним count). Функция возвращает константу DDENUMRET_OK, чтобы указать, что DirectDraw должен продолжить поиск поддерживаемых видеорежимов. Использование в качестве возвращаемого значения константы DDRNUMRET_CANCEL приведет к прекращению перечисления видеорежимов DirectDraw.

Вернемся к функции InitDisplayMode(). После обнаружения всех поддерживаемых видеорежимов вызывается функция Win32 qsort() чтобы выполнить сортировку полученного массива видеорежимов. Вызов функции выглядит следующим образом:

qsort(displaymode, totaldisplaymodes, sizeof(videomode), CompareModes);

Функции qsort() передаются массив displaymode, общее количество обнаруженных видеорежимов, размер каждого элемента массива displaymode и функция сравнения. Функция сравнения (CompareModes()) — это функция обратного вызова, вызываемая из qsort() для определения правильного порядка следования элементов массива. Текст функции CompareModes() приведен в листинге 10.4.

Листинг 10.4. Функция CompareModes()

int RMWin::CompareModes(const void *arg1, const void *arg2) { videomode* mode1 = (videomode*)arg1; videomode* mode2 = (videomode*)arg2;

DWORD volume1 = mode1->width * mode1->height; DWORD volume2 = mode2->width * mode2->height;

if (volume1 < volume2) return -1; else if (volume1 > volume2) return 1;

if (mode1->depth < mode2->depth) return -1; else if (mode1->depth > mode2->depth) return 1;

return 0; }

Для сравнения двух видеорежимов, передаваемых в параметрах функции CompareModes() используются их размерности. Благодаря этому массив displaymode будет отсортирован по размеру экрана в видеорежимах.



Затем из массива displaymode выбирается начальный видеорежим:

for (int i = 0; i < totaldisplaymodes; i++) { DWORD w, h, d; GetDisplayModeDims(i, w, h, d); if (w == 640 && h == 480 && d == curdisplaydepth) curdisplaymode = i; }

Ищется видеорежим 640x480 (он выбран в качестве начального, поскольку практически любая видеокарта поддерживает режим 640x480). В качестве начальной глубины пикселей используется глубина пикселей текущего видеорежима Windows.

Сразу после выбора видеорежима его параметры извлекаются и используются для изменения текущего видеорежима:

GetDisplayModeDims(curdisplaymode, modewidth, modeheight, modedepth); ddraw->SetDisplayMode(modewidth, modeheight, modedepth);

Функция GetDisplayModeDims() извлекает параметры видеорежима, заданного первым аргументом. Переменные modewidth, modeheight и modedepth принадлежат классу RMWin и используются для хранения параметров текущего видеорежима. Полученные параметры видеорежима передаются в аргументах функции SetDisplayMode() интерфейса DirectDraw. Этот вызов функции осуществляет действительное переключение видеорежима и завершает работу функции InitDisplayMode().