Файлы с расширениями CPP и H содержат исходный код на C++, который рассматривается в этой книге.
Файлы MDP также известны как файлы проектов или файлы рабочих областей. Они содержат всю информацию, необходимую для компиляции приложения. Эти файлы создаются и модифицируются VisualC++. Загрузка такого файла в Visual C++ — это самый быстрый и простой способ откомпилировать, протестировать и модифицировать приложение.
Файлы RC используются Visual C++ для хранения информации, относящейся к ресурсам приложения (меню, значки, растры, сетки и т.д.). Visual C++ модифицирует эти файлы автоматически, когда через предоставляемый IDE интерфейс вы редактируете ресурсы приложения. Ресурсы могут перемещаться и копироваться между несколькими рабочими областями. Для этого необходимо открыть несколько файлов RC, вырезать необходимый ресурс в одном файле и вставить его в другой.
В приложениях на CD-ROM файлы X, BMP и PPM содержат специфические для Direct3D ресурсы, которые компилируются в результирующий EXE-файл и будут доступны во время выполнения приложения. Чтобы изменить используемые приложением сетки и текстуры, надо заменить эти файлы на новые версии, после чего заново скомпилировать демонстрационную программу.
В приложении 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); }
Приложение 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 не равно нулю, в настоящий момент выполняется операция перетаскивания, и мы должны ее прекратить (поскольку кнопка мыши была отпущена). Переменной присваивается нулевое значение, указатель мыши отображается на экране и отменяется захват мыши приложением.
В приложении 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 приложения 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. Значение, возвращаемое данной функцией используется, чтобы проверить какой метод анимации используется в данный момент и установить флажок у соответствующего пункта меню.
Приложение 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() используется для установки флажка рядом с пунктом меню, соответствующим активному в данный момент режиму.
Приложение 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 присваивается значение, соответствующее установленным в данный момент параметрам прожектора. Эта переменная используется следующими тремя функциями, которые предназначены для отображения метки возле активного пункта меню.
Приложение 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.
Приложение 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(). Затем сетке возвращается ее предыдущий размер. Если не сделать этого, то после сохранения отображаемая сетка может стать огромной или микроскопической.
Одна из возможностей, которую мастер 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.
Приложение 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.
Осталось обратить внимание еще на одну особенность приложения Shadow. Обработчики событий меню Render должны изменять параметры двух конструкторов сеток, а не одного, как в других приложениях. Ниже показан код обработчика события для пункта Wireframe меню Render:
void ShadowWin::OnRenderWireframe() { if (floorbuilder) floorbuilder->SetQuality(D3DRMRENDER_WIREFRAME); if (forkbuilder) forkbuilder->SetQuality(D3DRMRENDER_WIREFRAME); }
Ранее в этой главе, когда мы изучали источник рассеянного света и создавали приложение 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() используется для установки флажка слева от пункта меню, соответствующего используемому в данный момент методу визуализации.
В приложении 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); } }
Данная функция предназначена для плоского режима визуализации первой группы граней сетки.
Оставшиеся функции класса 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.
Приложение 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() для настройки порта просмотра в соответствии с новыми параметрами.
Функция 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; } |
Класс 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 имеет очень малое значение, обеспечивающее неторопливую и плавную анимацию.
Класс 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() эти пять вызовов инициализируют приложение в соответствии с начальным видеорежимом. В данном случае эти вызовы изменяют параметры приложения согласно изменению видеорежима.
Сцена создается в функции 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, работа приложения прекращается и выводится сообщение об ошибке.
Подобно функции 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() отвечает за создание стандартных интерфейсов 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.
СОВЕТ | Изменение размеров окна. Размер устройства Direct3D не может быть изменен. Если изменяются размеры окна необходимо уничтожить существующее устройство, после чего создать новое в соответствии с новыми размерами окна. О том, как это делается, мы поговорим в разделе, посвященном описанию функции RMWin::OnSize(). |
Функция 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() выглядит следующим образом:
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, то заметите, что вся поверхность меню, за исключением текста, прозрачна. Это вызвано тем, что перед выводом текста всем пикселям поверхности присваивается нулевое значение.
Говоря о содержимом поверхности, следует упомянуть, что только что созданная поверхность содержит случайное содержимое. При создании поверхности выделяется память для ее размещения, но инициализация этой памяти не производится.
Рассматривая функцию 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() выполняет следующие действия:
Создание сетки и настройка ее параметров.
Создание источника зонального освещения и настройка его параметров.
Создание и настройка порта просмотра.
Продолжим разговор о вспомогательных функциях и рассмотрим функцию 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.
Код функции 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; } |
Функция 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. Анимируется только красная составляющая цвета группы граней сетки. Зеленая и синяя составляющая цвета всегда равны нулю.
Функция 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 элемента.
Сцена приложения 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; } |
Функция 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];
В отличие от приложения 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; } |
Сцена приложения 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));
Функция 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: Ориентация грани. Этот вектор указывает лицевое направление выбранной грани.
Функция 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 установлен, операция перетаскивания прерывается.
В приложении 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; } |
Код конструктора класса 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() возвращает индекс включенного в данный момент видеорежима:
int GetCurDisplayMode() { return curdisplaymode; }
Как и в случае с функцией GetNumDisplayModes(), простота функции GetCurDisplayMode() делает ее хорошим кандидатом на определение и объявление внутри определения класса.
Определение функции 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() возвращает параметры указанного видеорежима:
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() применяется для получения 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, какая версия структуры используется. |
Мышь — это неотъемлемая часть интерфейса Windows и пользователи ожидают, что программа будет поддерживать работу с мышью так же, как она поддерживает работу с клавиатурой. Функции GetMouseX() и GetMouseY(), предоставляемые классом RMWin, позволяют в любой момент времени определить текущие координаты указателя мыши. Обе функции возвращают значение соответствующей координаты в пикселях. Функции объявлены как статические и могут использоваться функциями обратного вызова.
Функция GetNumDisplayModes()— это защищенная функция, возвращающая количество видеорежимов, обнаруженных функцией InitDisplayMode(). Функция объявлена и определена в определении класса RMWin:
int GetNumDisplayModes() { return totaldisplaymodes; }
Функция 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;
Листинг 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; } |