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

         

Функция TargetWin::MoveTarget()


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

void TargetWin::MoveTarget(LPDIRECT3DRMFRAME, void* p, D3DVALUE) { LPDIRECT3DRMANIMATION animation = (LPDIRECT3DRMANIMATION)p; static D3DVALUE time; time += D3DVALUE(.5); animation->SetTime(time); }

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



Функция TargetWin::OrientFrame()


Функция обратного вызова OrientFrame() используется для изменения ориентации фреймов ракет в соответствии с движением цели. Эта работа совершается функцией LookAt() интерфейса Direct3DRMFrame, которая ориентирует один фрейм так, чтобы он «смотрел» на другой. Определение функции OrientFrame() выглядит следующим образом:

void TargetWin::OrientFrame(LPDIRECT3DRMFRAME frame, void* p, D3DVALUE) { LPDIRECT3DRMFRAME targetframe = (LPDIRECT3DRMFRAME)p; LPDIRECT3DRMFRAME scene; frame->GetScene(&scene); frame->LookAt(targetframe, scene, (D3DRMFRAMECONSTRAINT)0); }

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

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



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


Функция TextureDriftWin::CreateScene() создает одну сетку и одну текстуру. Текстура связывается с сеткой, но наложения текстуры не выполняется. При каждом обновлении экрана функция обратного вызова генерирует новое наложение текстуры. Текст функции CreateScene() представлен в листинге5.5.

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

BOOL TextureDriftWin::CreateScene() { //------ КОНСТРУКТОР СЕТОК ------ D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_D3DMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER meshbuilder; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->Scale(D3DVALUE(1), D3DVALUE(1), D3DVALUE(.5)); ScaleMesh(meshbuilder, D3DVALUE(35)); meshbuilder->SetPerspective(TRUE);

//------- ТЕКСТУРА ------ HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE( IDR_TEXTURE), "TEXTURE"); LPDIRECT3DRMTEXTURE texture; d3drm->LoadTextureFromResource(texture_id, &texture); meshbuilder->SetTexture(texture); texture->Release(); texture = 0;

//-------- СЕТКА -------- LPDIRECT3DRMMESH mesh; meshbuilder->CreateMesh(&mesh); meshbuilder->Release(); meshbuilder = 0;

//-------- ФРЕЙМ СЕТКИ -------- LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); meshframe->AddVisual(mesh); meshframe->AddMoveCallback(MoveTexture, NULL); meshframe->Release(); meshframe = 0; mesh->Release(); mesh = 0;

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

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



return TRUE; }

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

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

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

Создание интерфейса Direct3DRMMesh.

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

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

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

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

D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_D3DMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER meshbuilder; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->Scale(D3DVALUE(1), D3DVALUE(1), D3DVALUE(.5)); ScaleMesh(meshbuilder, D3DVALUE(35)); meshbuilder->SetPerspective(TRUE);

Обратите внимание, что после загрузки сетки мы используем функцию Scale() интерфейса Direct3DRMMeshBuilder для уменьшения размера сетки по оси Z. Мы передаем функции Scale() в качестве аргумента Z значение 0.5, чтобы вдвое уменьшить размер сетки по оси Z. После вызова функции Scale() используется функция ScaleMesh() для масштабирования сетки таким образом, чтобы ее максимальный размер равнялся 35 единицам. Функция SetPerspective() вызывается чтобы разрешить перспективную коррекцию.

Затем создается текстура:

HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE), "TEXTURE"); LPDIRECT3DRMTEXTURE texture; d3drm->LoadTextureFromResource(texture_id, &texture); meshbuilder->SetTexture(texture); texture->Release(); texture = 0;

Новая текстура привязывается к ранее созданному конструктору сеток с помощью функции SetTexture(), но наложение текстуры не создается. Потом существующий конструктор сеток применяется для создания экземпляра интерфейса Direct3DRMMesh:

LPDIRECT3DRMMESH mesh; meshbuilder->CreateMesh(&mesh); meshbuilder->Release(); meshbuilder = 0;

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



Теперь настала пора создать фрейм и присоединить к нему сетку:

LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); meshframe->AddVisual(mesh); meshframe->AddMoveCallback(MoveTexture, NULL); meshframe->Release(); meshframe = 0; mesh->Release(); mesh = 0;

Новый фрейм ориентируется таким образом, чтобы сетка была расположена под углом 45 градусов к порту просмотра, после чего для присоединения сетки к фрейму вызывается функция AddVisual(). Функция обратного вызова MoveTexture(), которая будет анимировать текстуру, устанавливается с помощью функции AddMoveCallback().

Обратите внимание, что указатель mesh освобождается. Ранее в функции мы уже освободили указатели meshbuilder и texture, а значит у нас больше не осталось никаких указателей на графические объекты. Это затруднило бы установку новых параметров наложения текстуры, если бы не существовало способа получить визуальные объекты фрейма. Мы увидим как это делается при обсуждении функции обратного вызова MoveTexture().

На пятом и шестом этапах для сцены создаются источник света и порт просмотра.


Функция TextureDriftWin::MoveTexture()


MoveTexture() — это функция обратного вызова, которая при каждом вызове создает и применяет новое наложение текстуры. В самом начале функции должен быть получен интерфейс Direct3DRMMesh, который был добавлен к фрейму в функции CreateScene(). Код функции MoveTexture() показан в листинге 5.6.

Листинг 5.6. Функция TextureDriftWin::MoveTexture()

void TextureDriftWin::MoveTexture(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { static D3DVALUE xtex; xtex += D3DVALUE(.02);

LPDIRECT3DRMVISUALARRAY visualarray; frame->GetVisuals(&visualarray); int nvisuals = visualarray->GetSize(); for ( int i = 0; i < nvisuals; i++ ) { LPDIRECT3DRMVISUAL visual; visualarray->GetElement(i, &visual); LPDIRECT3DRMMESH mesh; if (visual->QueryInterface(IID_IDirect3DRMMesh, (void**)&mesh) == 0) { D3DRMBOX box; mesh->GetBox(&box); D3DVALUE w = box.max.x - box.min.x; D3DVALUE h = box.max.y - box.min.y;

LPDIRECT3DRMWRAP wrap; d3drm->CreateWrap(D3DRMWRAP_FLAT, NULL, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), xtex, D3DVALUE(0.5), // начало координат текстуры D3DDivide(1,w), D3DDivide(1,h), // масштаб текстуры &wrap); wrap->Apply(mesh); wrap->Release(); wrap = 0; mesh->Release(); mesh = 0; } visual->Release(); visual = 0; } visualarray->Release(); visualarray = 0; }

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

Затем, для получения массива присоединенных к фрейму визуальных объектов, используется функция GetVisuals() интерфейса Direct3DRMFrame (Direct3D передает указатель frame в первом параметре функции обратного вызова).

Функция GetVisuals() инициализирует указатель на интерфейс Direct3DRMVisualArray. Этот интерфейс поддерживает всего лишь две функции: GetSize() и GetElement().

Для перебора элементов массива визуальных объектов применяется цикл. С помощью функции GetSize() мы определяем количество элементов в массиве, а для получения отдельного элемента используется функция GetElement(). Возвращаемый функцией указатель является указателем на интерфейс Direct3DRMVisual (который не предоставляет никаких функций). Чтобы узнать, поддерживает ли объект интерфейс Direct3DRMMesh, используется функция QueryInterface(). Мы знаем что один и только один из объектов, возвращаемых функцией GetElement() поддерживает интерфейс Direct3DRMMesh, поскольку в функции CreateScene() мы создали и присоединили к данному фрейму только одну сетку.

После получения указателя на интерфейс Direct3DRMMesh создается и применяется к сетке интерфейс Direct3DRMWrap.



Функция UpdateFPSSurface()


Функция UpdateFPSSurface() вызывается при каждом обновлении экрана. Ее код показан в листинге10.13.

Листинг 10.13. Функция UpdateFPSSurface()

BOOL FullScreenWin::UpdateFPSSurface() { static const long interval = 100; static long framecount; framecount++;

if (framecount == interval) { static DWORD timenow; static DWORD timethen; timethen = timenow; timenow = timeGetTime();

double seconds = double(timenow - timethen) / (double)1000; int fps = (int)((double)framecount / seconds);

static char buf[10]; int len = sprintf(buf, "FPS: %d", fps);

HDC hdc; fpssurf->GetDC(&hdc); SelectObject(hdc, smallfont); SetTextColor(hdc, RGB(255,255,255)); SetBkColor(hdc, RGB(0,0,0)); SetBkMode(hdc, OPAQUE); ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &fpsrect, buf, len, 0); fpssurf->ReleaseDC(hdc);

displayfps = TRUE; framecount = 0; }

return TRUE; }

Функция использует статическую переменную (framecount) для подсчета количества обновлений экрана, или кадров. Когда счетчик кадров достигнет 100 (это значение выбрано произвольно), будет вычислено и выведено на экран значение FPS для последних 100 кадров.

Для определения времени, прошедшего с предыдущего вычисления FPS используется быстродействующая мультимедийная функция timeGetTime(). Затем создается строка, содержащая вычисленное значение FPS, которая выводится на поверхность fpssurf. Сразу после обновления поверхности счетчик framecount обнуляется.



Функция UpdateMenuSurface()


Задача функции UpdateMenuSurface() (код которой приведен в листинге10.11) — инициализировать содержимое поверхности меню видеорежимов.

Листинг 10.11. Функция UpdateMenuSurface()

BOOL FullScreenWin::UpdateMenuSurface() { char buf[80]; int len; RECT rect;

ClearSurface(menusurf, 0);

HDC hdc; menusurf->GetDC(&hdc); SelectObject(hdc, largefont); SetBkMode(hdc, TRANSPARENT);

SetTextColor(hdc, textshadow); ExtTextOut(hdc, 1, 1, 0, 0, headertext, strlen(headertext), 0); SetTextColor(hdc, textcolor); ExtTextOut(hdc, 0, 0, 0, 0, headertext, strlen(headertext), 0);

SelectObject(hdc, smallfont);

int nmodes = GetNumDisplayModes(); if (nmodes > maxmodes) nmodes = maxmodes;

int rows = nmodes / menucols; if (nmodes % menucols) rows++;

for (int i = 0; i < nmodes; i++) { rect.left = (i / rows) * colwidth; rect.top = (i % rows) * rowheight + reservedspace; rect.right = rect.left + colwidth; rect.bottom = rect.top + rowheight;

DWORD w,h,d; GetDisplayModeDims(i, w, h, d);

len = sprintf(buf, "%dx%dx%d", w, h, d); SetTextColor(hdc, textshadow); ExtTextOut(hdc, rect.left + 1, rect.top + 1, 0, &rect, buf, len, 0);

if (i == selectmode) SetTextColor(hdc, highlightcolor); else SetTextColor(hdc, textcolor); ExtTextOut(hdc, rect.left, rect.top, 0, &rect, buf, len, 0); }

rect.left = 0; rect.right = 319; rect.top = 179; rect.bottom = 199;

len=sprintf(buf, "[Arrows] [Enter] [W] [F] [G] [Escape]"); SetTextColor(hdc, textshadow); ExtTextOut(hdc, 1, 180, 0, &rect, buf, len, 0); SetTextColor(hdc, textcolor); ExtTextOut(hdc, 0, 179, 0, &rect, buf, len, 0);

menusurf->ReleaseDC(hdc);

return TRUE; }

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


Затем вызывается функция GetDC() интерфейса DirectDrawSurface, чтобы получить контекст устройства Windows для поверхности. Полученный в результате HDC (дескриптор контекста устройства) позволяет нам пользоваться функциями контекста устройства Windows. В нашем случае мы воспользуемся дескриптором контекста устройства для вывода текста на поверхность.

Большая часть кода функции UpdateMenuSurface() осуществляет вывод текста на поверхность menusurf. Сначала используется функция Win32 ExtTextOut() для отображения заголовка приложения. Затем создаются и выводятся строки для каждого обнаруженного видеорежима. И, наконец, в нижней части поверхности выводится список клавиш, на которые реагирует программа.

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

Перед возвратом из функции UpdateMenuSurface() вызывается функция ReleaseDC() интерфейса DirectDrawSurface. Это очень важно, поскольку часть механизмов управления Windows отключается между вызовами функций GetDC() и ReleaseDC(). Если вы забудете освободить контекст устройства для поверхности DirectDraw, то это приведет к впечатляющему краху системы (поверьте мне — я знаю!).


Функция WinMain


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

while ( GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }

Функции GetMessage, TranslateMessage, и DispatchMessage являются частью Windows SDK. Сообщения могут быть отправлены другими приложениями или самой системой Windows. Эти сообщения могут быть уведомлениями об изменении системной палитры, о нажатии клавиши, о перемещении мыши и т.д. В конечном счете цикл получает и обрабатывает сообщение WM_QUIT, которое сигнализирует программе о необходимости прекратить работу.



Функция WrapsWin::ApplyWraps()


Функция ApplyWraps() отвечает за назначение каждой из сеток правильного типа покрытия текстурой в соответствии со значениями переменных класса WrapsWin. Ниже приведен код функции ApplyWraps():

void WrapsWin::ApplyWraps() { if (boxwraptype == D3DRMWRAP_FLAT) ApplyFlat(box); else if (boxwraptype == D3DRMWRAP_CYLINDER) ApplyCylinder(box); else ApplySphere(box);

if (cylwraptype == D3DRMWRAP_FLAT) ApplyFlat(cyl); else if (cylwraptype == D3DRMWRAP_CYLINDER) ApplyCylinder(cyl); else ApplySphere(cyl);

if (spherewraptype == D3DRMWRAP_FLAT) ApplyFlat(sphere); else if (spherewraptype == D3DRMWRAP_CYLINDER) ApplyCylinder(sphere); else ApplySphere(sphere); }

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

void WrapsWin::ApplyFlat(LPDIRECT3DRMMESHBUILDER meshbuilder) { D3DRMBOX box; meshbuilder->GetBox(&box); D3DVALUE width = box.max.x - box.min.x; D3DVALUE height = box.max.y - box.min.y;

LPDIRECT3DRMWRAP wrap; d3drm->CreateWrap(D3DRMWRAP_FLAT, NULL, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), // начало координат наложения D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0), // ось z D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), // ось y D3DVALUE(0.5), D3DVALUE(0.5), // начало координат текстуры D3DDivide(1,width), D3DDivide(1,height), // масштаб текстуры &wrap); wrap->Apply(meshbuilder); wrap->Release(); wrap = 0; }

Сначала функция ApplyFlat() вычисляет высоту и ширину сетки, на которую наносится текстура. Функция GetBox() интерфейса Direct3DRMMeshBuilder используется для инициализации структуры D3DRMBOX размерами сетки. Затем с помощью функции CreateWrap() интерфейса Direct3DRM создается экземпляр интерфейса Direct3DRMWrap. Новое наложение применяется к сетке функцией Apply() интерфейса Direct3DRMWrap. Перед завершением функции выполняется освобождение указателя wrap.

В классе WrapsWin есть еще три функции: OnWrapsFlat(), OnWrapsCylinder() и OnWrapsSphere(). Эти функции являются обработчиками событий для меню Wraps и выглядят аналогично функции OnWrapsReset(), за исключением того, что каждая из них назначает одинаковый тип наложения текстуры для всех трех сеток. Ниже для примера приведен текст функции OnWrapsFlat():

void WrapsWin::OnWrapsFlat() { boxwraptype = D3DRMWRAP_FLAT; cylwraptype = D3DRMWRAP_FLAT; spherewraptype = D3DRMWRAP_FLAT;

ApplyWraps(); }



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


Давайте взглянем на код приложения Wraps. Как и все приложения в этой книге, данное приложение использует в качестве базового класс RMWin и добавляет функциональные возможности переопределяя функцию CreateScene(). Приложение Wraps объявляет класс WrapsWin функция CreateScene() которого отвечает за загрузку трех используемых в приложении сеток. Поскольку это приложение несколько сложнее, чем те, которые были рассмотрены нами ранее, часть работы CreateScene() выполняют вспомогательные функции. Ниже приведен код функции CreateScene() для приложения Wraps.

BOOL WrapsWin::CreateScene() { //-------- СЕТКИ И ТЕКСТУРЫ -------- if (LoadMeshes() == FALSE) return FALSE; if (LoadWrapsTexture() == FALSE) return FALSE;

OnWrapsReset();

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

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

return TRUE; }

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

Создание и конфигурирование сеток, текстур и наложений текстур.

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

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

Для создания и инициализации сеток, текстур и наложений текстур функция CreateScene() вызывает функции LoadMeshes(), LoadWrapsTexture() и OnWrapsReset(). Как явствует из названия, функция LoadMeshes() загружает три используемые приложением сетки. Кроме того, LoadMeshes() отвечает за создание и размещение фреймов, к которым будут присоединены эти сетки. Функция LoadWrapsTexture() загружает текстуру, которая будет наложена на каждую из трех сеток.

Функция OnWrapsReset() выполняет две задачи. Она применяется в функции CreateScene() для инициализации параметров программы и, кроме того, она же является обработчиком события, вызываемым каждый раз, когда пользователь выбирает пункт Reset в меню Wraps.

Функции LoadMeshes(), LoadWrapsTexture() и OnWrapsReset() выполняют для приложения Wraps значительный объем работы и заслуживают более пристального рассмотрения.



Функция WrapsWin::LoadMeshes()


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

Листинг 5.2. Функция WrapsWin::LoadMeshes()

BOOL WrapsWin::LoadMeshes() { HRESULT r;

const D3DVALUE meshscale = D3DVALUE(13); const D3DVALUE meshspacing = D3DVALUE(15);

D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_BOXMESH); resinfo.lpType = "MESH" d3drm->CreateMeshBuilder(&box); r = box->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); if (r != D3DRM_OK) { TRACE("failed to load internal box mesh\n"); return FALSE; } box->SetPerspective(TRUE); ScaleMesh(box, meshscale - D3DVALUE(2));

LPDIRECT3DRMFRAME boxframe; d3drm->CreateFrame(scene, &boxframe); boxframe->SetPosition(scene, -meshspacing, D3DVALUE(0), D3DVALUE(0)); boxframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(1), D3DVALUE(.1)); boxframe->AddVisual(box);

resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_CYLMESH); resinfo.lpType = "MESH";

d3drm->CreateMeshBuilder(&cyl); cyl->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); if (r != D3DRM_OK) { TRACE("failed to load internal cylinder mesh\n"); return FALSE; } cyl->SetPerspective(TRUE); ScaleMesh(cyl, meshscale);

LPDIRECT3DRMFRAME cylframe; d3drm->CreateFrame(scene, &cylframe); cylframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(1), D3DVALUE(.1)); cylframe->AddVisual(cyl);

resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH";

d3drm->CreateMeshBuilder(&sphere); sphere->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); if (r != D3DRM_OK) { TRACE("failed to load internal sphere mesh\n"); return FALSE; } sphere->SetPerspective(TRUE); ScaleMesh(sphere, meshscale);

LPDIRECT3DRMFRAME sphereframe; d3drm->CreateFrame(scene, &sphereframe); sphereframe->SetPosition(scene, D3DVALUE(meshspacing), D3DVALUE(0), D3DVALUE(0)); sphereframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(1), D3DVALUE(.1)); sphereframe->AddVisual(sphere);

return TRUE; }

<
/p> В начале функции объявляется несколько констант, используемых в коде для позиционирования и масштабирования сеток. Затем осуществляется инициализация указателя на интерфейс Direct3DRMMeshBuilder. Функция Load() загружает изображающую куб сетку из ресурсов программы.

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

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

После того, как масштабирование сетки выполнено, функция LoadMeshes() создает фрейм и позиционирует его в соответствии с константой meshspacing. Сетка куба появляется слева от других сеток, поэтому для нее указывается отрицательная координата по оси X. Далее функция SetRotation() назначает фрейму атрибуты вращения. И, в самом конце, с помощью функции AddVisual() интерфейса Direct3DRMFrame к фрейму присоединяется сетка.


Функция WrapsWin::LoadWrapsTexture()


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

BOOL WrapsWin::LoadWrapsTexture() { HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE( IDR_JADETEXTURE), "TEXTURE"); LPDIRECT3DRMTEXTURE texture; d3drm->LoadTextureFromResource(texture_id, &texture); box->SetTexture(texture); cyl->SetTexture(texture); sphere->SetTexture(texture); texture->Release(); texture = 0; return TRUE; }

Для инициализации экземпляра структуры HRSRC в функции LoadWrapsTexture() применяется функция FindResource() из Win32 API. Структура HRSRC идентифицирует текстуру, которую мы пытаемся загрузить, и используется в качестве аргумента функции LoadTextureFromResource(). Новая текстура связывается с тремя сетками с помощью функции SetTexture() интерфейса Direct3DRMMeshBuilder. В конце функции освобождается указатель на текстуру и возвращается значение TRUE, сообщающее об успешном завершении.



Функция WrapsWin::OnWrapsReset()


Вспомните, что перед созданием источника света и порта просмотра функция CreateScene() приложения Wraps вызывает три функции: LoadMeshes(), LoadWrapsTexture() и OnWrapsReset(). Функция OnWrapsReset() присваивает значения переменным класса WrapsWin таким образом, чтобы для сетки куба использовалось плоское наложение текстуры, для сетки цнлиндра— цилиндрическое, и для сетки сферы — сферическое наложение текстуры. Вот как выглядит код функции OnWrapsReset():

void WrapsWin::OnWrapsReset() { boxwraptype = D3DRMWRAP_FLAT; cylwraptype = D3DRMWRAP_CYLINDER; spherewraptype = D3DRMWRAP_SPHERE;

ApplyWraps(); }

Переменные boxwraptype, cylwraptype и spherewraptype принадлежат к классу WrapsWin и относятся к объявленному в Direct3D типу D3DRMWRAPTYPE. Функция OnWrapReset() присваивает каждой из этих переменных константы, которые также предоставляются Direct3D. В следующем разделе мы обсудим функцию ApplyWraps().



Функция ZoomWin::AdjustField()


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

void ZoomWin::AdjustField(LPDIRECT3DRMFRAME, void*, D3DVALUE) { static D3DVALUE time; time += D3DVALUE(.5); animation->SetTime(time);

D3DVECTOR pos; zoomframe->GetPosition(0, &pos);

viewport->SetField(pos.x); }

Для управления анимационной последовательностью в функции применяется статическая переменная time. Значение этой переменной увеличивается при каждом вызове функции AdjustField(). После того, как значение переменной time увеличено, осуществляется установка нового времени в анимации с помощью функции SetTime() интерфейса Direct3DRMAnimation.

Затем мы получаем координаты анимируемого фрейма (zoomframe). Вспомните, что позицией фрейма zoomframe управляет анимационная последовательность. Когда с помощью функции SetTime() мы устанавливаем новое время анимации, позиция фрейма zoomframe изменяется в соответствии с заданной анимационной последовательностью. Для получения новых координат фрейма используется функция GetPosition().

В заключение значение координаты фрейма по оси X используется в качестве нового значения угла зрения для порта просмотра. Новое значение устанавливается с помощью функции SetField() интерфейса Direct3DRMViewport. Вспомните, что viewport это статический член данных класса RMWin. Поэтому мы можем обращаться к нему из функций обратного вызова.



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


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

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

BOOL ZoomWin::CreateScene() { // ------- СЕТКА -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_Z_MESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->SetColorRGB(D3DVALUE(.67), D3DVALUE(.82), D3DVALUE(.94)); ScaleMesh(meshbuilder, D3DVALUE(25));

// ------- МАТЕРИАЛ ---------- LPDIRECT3DRMMATERIAL material; d3drm->CreateMaterial(D3DVALUE(10), &material); meshbuilder->SetMaterial(material); material->Release(); material = 0;

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

// ------- АНИМАЦИЯ -------- d3drm->CreateAnimation(&animation); animation->SetOptions(D3DRMANIMATION_SPLINEPOSITION | D3DRMANIMATION_CLOSED | D3DRMANIMATION_POSITION); animation->AddPositionKey(D3DVALUE(0), D3DVALUE(.4), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(24), D3DVALUE(5), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(49), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(74), D3DVALUE(5), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(99), D3DVALUE(.4), D3DVALUE(0), D3DVALUE(0));

d3drm->CreateFrame(scene, &zoomframe); animation->SetFrame(zoomframe);

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

LPDIRECT3DRMLIGHT alight; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(.40), D3DVALUE(.40), D3DVALUE(.40), &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); camera->AddMoveCallback(AdjustField, NULL); return TRUE; }

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

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

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

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

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

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

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

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

D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_Z_MESH); resinfo.lpType = "MESH"; meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->SetColorRGB(D3DVALUE(.67), D3DVALUE(.82), D3DVALUE(.94)); ScaleMesh(meshbuilder, D3DVALUE(25));

Структура D3DRMLOADRESOURCE используется для идентификации элемента ресурсов приложения, содержащего сетку. Член данных meshbuilder инициализируется функцией CreateMeshBuilder() интерфейса Direct3DRM. Затем вызывается функция Load(), которая и загружает сетку. Цвет сетки назначается с помощью функции SetColorRGB(). После выполнения всех этих действий сетка масштабируется до размера в 25 единиц с помощью функции ScaleMesh().

Теперь к сетке применяется материал:

LPDIRECT3DRMMATERIAL material; d3drm->CreateMaterial(D3DVALUE(10), &material); meshbuilder->SetMaterial(material); material->Release(); material = 0;

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

СОВЕТ Тестирование параметров материала. Xpose — это законченная программа просмотра объектов, созданная с использованием рассмотренных в этой книге технологий. И исходный код и исполняемый файл этой программы содержатся на прилагаемом к книге CD-ROM. Самый быстрый способ познакомиться с параметрами материала и тем, какое влияние они оказывают на внешний вид сетки — провести несколько экспериментов с программой Xpose. Диалоговое окно Material Settings программы Xpose позволяет изменить параметры материала и сразу же увидеть результат.
<


/p> Для создания материала используется функция CreateMaterial() интерфейса Direct3DRM. Новый материал применяется к сетке с помощью функции SetMaterial() интерфейса Direct3DRMMeshBuilder. После выполнения этих действий локальный указатель material освобождается.

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

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

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

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

d3drm->CreateAnimation(&animation); animation->SetOptions(D3DRMANIMATION_SPLINEPOSITION | D3DRMANIMATION_CLOSED | D3DRMANIMATION_POSITION); animation->AddPositionKey(D3DVALUE(0), D3DVALUE(.4), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(24), D3DVALUE(5), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(49), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(74), D3DVALUE(5), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(99), D3DVALUE(.4), D3DVALUE(0), D3DVALUE(0));

d3drm->CreateFrame(scene, &zoomframe); animation->SetFrame(zoomframe);

Сначала с помощью функции CreateAnimation() интерфейса Direct3DRM создается анимация. Затем вызывается функция SetOptions() интерфейса Direct3DRMAnimation чтобы задать параметры анимации.


Мы используем константу D3DRMANIMATION_SPLINEPOSITION чтобы указать, что желаем получить сплайновую анимацию (а не линейную). Этот параметр может быть изменен с помощью меню Animation приложения Zoom. Константа D3DRMANIMATION_CLOSED указывает, что анимационная последовательность должна непрерывно повторяться. Это позволяет использовать непрерывно увеличивающиеся значения времени для повторного выполнения анимационной последовательности. Константа D3DRMANIMATION_POSITION указывает, что анимационная последовательность будет изменять только местоположение объекта (мы не используем константу D3DRMANIMATION_SCALEANDROTATION поскольку, как вы вскоре увидите, мы не используем анимацию для изменения вращения или масштаба объекта).

Затем с помощью функции AddPositionKey() к анимации добавляются пять позиционных ключей. Первым аргументом функции AddPositionKey() является значение, указывающее в какой момент времени данный ключ должен вступить в действие. Оставшиеся три аргумента задают связанную с новым ключом позицию. Обратите внимание, что устанавливаемые ключи отличаются только значением координаты по оси X. Эта ось выбрана произвольно. Мы могли бы использовать оси Y или Z, поскольку мы создаем линейную, или одномерную, анимационную последовательность. Мы будем использовать в анимационной последовательности только ось X.

Затем создается фрейм с именем zoomframe. Новый фрейм присоединяется к анимационной последовательности с помощью функции SetFrame() интерфейса Direct3DRMAnimation.

Фрейм zoomframe является пустым, но он используется не так, как использовались пустые фреймы в приложениях Firefly (глава 6) или Decal (глава 5). Этот пустой фрейм не будет использоваться как родитель для других фреймов. Вместо этого он будет анимироваться созданной ранее анимационной последовательностью, а его координаты будут применяться для установки параметров поля зрения порта просмотра. Обратите внимание, что значение координаты по оси X в анимационной последовательности изменяется в диапазоне от 0.4 до 5.0.


Эти значения не будут интерпретироваться как позиции. Вместо этого, они будут использоваться в качестве параметров поля зрения и передаваться в аргументах функции SetField() интерфейса Direct3DRMViewport.

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

На шестом, завершающем этапе, выполняется создание порта просмотра:

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->AddMoveCallback(AdjustField, NULL); return TRUE;

Сначала создается и размещается фрейм camera. Затем этот фрейм передается в аргументе функции CreateViewport() интерфейса Direct3DRM. Последнее, что делает функция CreateScene() — установка функции обратного вызова AdjustField() с помощью функции AddMoveCallback(). Для установки функции обратного вызова мы используем фрейм camera, но с тем же успехом может быть использован любой другой фрейм сцены.


Краткий обзор Visual C++


Что такое Visual C++?

Что такое MFC?

Программирование для Windows

Использование мастера ClassWizard

Использование мастера AppWizards

Использование мастера Direct3D AppWizard

После выхода Windows 3.0 многие программисты, работавшие на других платформах, перешли к написанию приложений для Windows. Многие из них начинали работу с DOS и обнаружили, что программирование для Windows сильно отличается. Дело обстояло еще хуже, поскольку не было библиотек функций, шаблонов классов или визуальных средств разработки, чтобы облегчить переход.

С тех пор многое изменилось. Visual C++ — замечательный инструмент, объединяющий мощную структуру классов с визуальными инструментами, берущими на себя тяжелую работу проектирования ресурсов, таких, как диалоговые окна и меню. Мастер ClassWizard позволяет вам добавлять функции к вашему приложению простым щелчком по кнопке. Мастер AppWizard создает готовые к компиляции проекты, обеспечивающие твердую основу для написания собственного приложения.

Несмотря на всю мощь Visual C++, чтобы использовать его возможности, вы должны быть программистом и понимать основные концепции программирования для Windows. В этой главе вы научитесь использовать Visual C++ для создания законченных приложений и узнаете,как их модифицировать. Также в этой главе вы научитесь создавать законченные приложения использующие Direct3D.

Здесь не содержится полный курс обучения Visual C++, но приведенных сведений достаточно, чтобы вы могли начать работать, если у вас уже есть некоторый опыт программирования.



Введение в трехмерную графику


Концепции и терминология трехмерной графики

Преобразования трехмерных объектов

Наложение текстур

Освещение

Анимация

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



Знакомство с Direct3D


Модель программирования COM от Microsoft

Объекты Direct3D

Основные типы данных Direct3D

X-файлы

В этой главе мы обратим внимание на Direct3D, поговорим об используемых концепциях и терминологии и взглянем на Direct3D API.



Код


Программа Sample

Зависимости COM-интерфейсов

Разработка классов C++

Инициализация Direct3D

Конструирование сцен

Управление сценой

Завершение работы Direct3D

Вспомогательные функции

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

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



Наложение текстур


Наложение текстуры на сетку

Способы наложения текстуры

Декалы

Прозрачность

Анимация текстур

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

Jade

Wraps

Decal

OrbStar

TextureDrift

ShowRoom

Демонстрационные примеры для этой главы, также как и все примеры на CD-ROM, были созданы с помощью Direct3D AppWizard, и поэтому имеют одинаковую общую структуру. Это позволяет нам обсуждать только те части, которые являются уникальными для данного примера. Общий обзор структуры программ, созданных с помощью AppWizard приведен в главе 4.



Источники света и тени


Рассеянное освещение

Точечный источник света

Направленное освещение

Параллельное освещение

Прожектор

Работа с несколькими источниками света

Тени

Вы уже получили немного информации об источниках света в главах 4 и 5. В этой главе мы детально обсудим каждый, поддерживаемый в Direct3D, тип источника света. Кроме того, мы поговорим о тенях.

При обсуждении большинства тем будут использоваться демонстрационные программы с CD-ROM. В данной главе мы изучим следующие приложения:

Firefly

SpaceStation

SpaceDonut

Spotlight

Shadow



Фреймы и анимация


Иерархии фреймов

Ключевые кадры

Предметом рассмотрения этой главы является анимация. В частности, нас будут интересовать методы анимации, поддерживаемые Direct3D. Как вы увидите, Direct3D предоставляет мощную и универсальную поддержку анимации.

Мы уже видели примеры поддерживаемой Direct3D анимации. Некоторые из демонстрационных приложений в главах 5 и 6 использовали интерфейс Direct3DRMFrame для анимации сеток. Приложения Decal (глава 5) и Firefly (глава 6) использовали для выполнения анимации пустые фреймы и атрибуты вращения. В других приложениях для изменения позиции или ориентации фрейма применялись функции обратного вызова. Фреймы, особенно организованные в иерархии, представляют мощный инструмент анимации. Мы рассмотрим иерархии фреймов в этой главе.

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

В главе мы изучим следующие приложения:

Molecule

Rocket

Target

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



Снова о сетках


Анимация вершин

Группы граней сетки

Трансформация сеток

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

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

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

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

Cube

Cube2

MorphPlay

Приложение Cube познакомит нас с базовыми сведениями об анимации вершин. Приложение Cube2 научит создавать в одной сетке несколько групп граней и управлять ими. В конце главы мы с помощью приложения MorphPlay изучим трансформацию сеток.



Порты просмотра


Параметры поля зрения

Выбор объектов

Работа с несколькими портами просмотра

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

Zoom

MeshPick

FacePick

MultiView

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



Полноэкранные приложения


DirectDraw

Изменение класса RMWin

Приложение FullScreen

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

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

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

Возможно наибольшим отличием является то, что в полноэкранном приложении мы должны сами реализовать обновление экрана и работу с палитрой. В приложениях Direct3D, которые работают в оконном режиме, мы не думали об этих задачах. Хорошей новостью является то, что палитра необходима только приложениям, работающим в 8-разрядных видеорежимах (16-, 24- и 32-разрядные видеорежимы обходятся без палитр).

Другое отличие заключается в том, что Windows не «понимает» полноэкранные приложения. Обычно обновление экрана Windows выполняет интерфейс графических устройств Windows (GDI). GDI ничего не знает о полноэкранных приложениях и предполагает, что всегда сам управляет экраном. Это может вызвать проблемы, поскольку если GDI будет использоваться для вывода меню или окон в полноэкранном режиме, он будет делать это не учитывая текущий видеорежим и палитру. Некоторые полноэкранные приложения используют GDI для отображения меню, несмотря на его плохую поддержку работы в полноэкранном режиме, однако наилучший способ — вообще отказаться от использования GDI в полноэкранном режиме. Чтобы сделать GDI неактивным можно разместить поверх всех остальных окон развернутое на весь экран пустое окно без меню.

Наше обсуждение мы начнем с краткого обзора DirectDraw. Затем мы модифицируем класс RMWin для поддержки полноэкранных операций. И в заключение мы используем новую версию класса RMWin при создании приложения FullScreen.



Грани


В Direct3D грань (face) — это плоский объект, определяемый своими вершинами. Каждая вершина определяет угол грани. Все вершины грани должны находиться в одной и той же плоскости; они должны определять плоскую грань. Грань, у которой вершины не принадлежат одной и той же плоскости, является неправильной и не может быть нарисована.

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


Рис. 2.7. Несколько граней для примера

ЗАМЕЧАНИЕ Обратите внимание: Direct3D использует треугольники. Внутренний механизм абстрактного режима Direct3D разделяет не треугольные поверхности на треугольники, поскольку интерфейс непосредственного режима (Immediate Mode), который и выполняет непосредственное рисование, работает только с треугольниками.

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



Группы граней сетки


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

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



Группы граней в сетке


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

AddGroup()

GetGroup()

GetGroupColor()

GetGroupCount()

GetGroupMapping()

GetGroupMaterial()

GetGroupQuality()

GetGroupTexture()

SetGroupColor()

SetGroupColorRGB()

SetGroupMapping()

SetGroupMaterial()

SetGroupQuality()

SetGroupTexture()

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



HRESULT


Большинство основных функций DirectX возвращают в качестве кода ошибки значение типа HRESULT. Тип HRESULT представляет собой 32-разрадное значение, индицирующее состояние функции в момент завершения. Direct3D предоставляет ряд констант, используемых при оценке возвращаемого значения. В идеальном случае функции возвращают константу D3DRM_OK, что свидетельствует об успешном завершении. Если при вызове функции возникает ошибка, возвращаемое значение указывает на характер проблемы.



Иерархии фреймов


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


Рис. 7.1. Простая иерархия фреймов

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

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


Рис. 7.2. Чуть более сложная иерархия фреймов

В иерархии, представленной на рис. 7.2 фрейм второй сетки (mesh2frame) присоединен к пустому фрейму, а не к корневому фрейму сцены. Это позволяет анимировать вторую сетку либо перемещая фрейм mesh2frame, либо перемещая пустой фрейм. Если вам непонятно назначение пустого фрейма, посмотрите описание демонстрационных программ Decal и Firefly.



Иерархия фреймов


Сцена Direct3D определяется как иерархия фреймов. Каждая сцена содержит корневой фрейм и произвольное число дочерних фреймов, присоединенных к корневому. Каждый дочерний фрейм может в свою очередь иметь собственные дочерние фреймы. Фреймы представлены интерфейсом Direct3DRMFrame и создаются функцией CreateFrame() интерфейса Direct3DRM.

LPDIRECT3DRMFRAME newframe; d3drm->CreateFrame(parentframe, &newframe);

В приведенном фрагменте кода объявляется указатель на фрейм, а затем он инициализируется функцией CreateFrame(). Переменная parentframe является указателем на фрейм, являющийся родителем нового фрейма. Чтобы создать корневой фрейм для новой сцены, значение этой переменной должно быть равно NULL.

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

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



Инициализация Direct3D


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

Теперь, когда мы познакомились с выбранной структурой классов, настало время пересмотреть порядок создания интерфейсов с учетом списка классов и функций-членов. Рис. 4.6. почти полностью повторяет рис. 4.4, за исключением того, что на нем указаны функции, ответственные за каждый из этапов.


Рис. 4.6. Порядок создания интерфейсов с разделением по категориям

Как и было сказано, класс RMWin создает стандартные объекты, а класс SampleWin создает объекты, зависящие от приложения.

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



Интерфейс DirectDraw


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

Интерфейс DirectDraw создается функцией DirectDrawCreate(), вызов которой выглядит следующим образом:

LPDIRECTDRAW ddraw; DirectDrawCreate(0, &ddraw, 0);



Использование функций обратного вызова


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

Это изменение выполняет функция обратного вызова. Функции обратного вызова — это функции, которые Direct3D вызывает каждый раз, когда собирается выполнить системное обновление. Такие функции могут применяться для изменения параметров во время выполнения программы.

Когда в функции CreateScene() мы создавали зональный источник света, мы установили для него функцию обратного вызова с именем MoveLight(). Установка функции обратного вызова выглядит следующим образом:

slightframe->AddMoveCallback(MoveLight, NULL);

Функция AddMoveCallback() — это функция интерфейса Direct3DRMFrame. Первый ее аргумент — это указатель на функцию, которая будет вызываться при каждом обновлении. Второй параметр представляет собой указатель на данные, которые будут передаваться функции обратного вызова. Эти дополнительные данные являются необязательными, поэтому мы передаем NULL.

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

void SampleWin::MoveLight(LPDIRECT3DRMFRAME lightframe, void*, D3DVALUE) { // перемещение прожектора над сеткой static const D3DVALUE lim = D3DVALUE(0.3); static D3DVALUE xi = D3DVALUE(0.01); static D3DVALUE yi = D3DVALUE(0.005); static D3DVALUE x, y; if (x < -LIM || x > lim) xi = -xi; if (y < -LIM || y > lim) yi = -yi; x += xi; y += yi; lightframe->SetOrientation(NULL, x, y-1, D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); }

Для вычисления новой ориентации источника света в функции используется простой алгоритм «подпрыгивающего мяча». Перемещение источника света ограничивается константой lim и изменяется на значения xi и yi. После вычисления новой ориентации, она назначается фрейму с помощью функции SetOrientation().


Функции обратного вызова всегда объявляются статическими, как показано ниже:

class SampleWin : public RMWin { // ... private: static void MoveLight(LPDIRECT3DRMFRAME frame, void* arg, D3DVALUE delta); // ... };

Это требование, объясняется тем, что обычные функции-члены класса требуют неявного указателя на класс. Объявление функции статической снимает это требование, но означает, что функции обратного вызова не могут обращаться к функциям-членам класса. По этой причине функция AddMoveCallback() предоставляет способ передачи дополнительных данных в функцию обратного вызова.

Функции обратного вызова, устанавливаемые с помощью AddMoveCallback() получают три параметра. Первый — это указатель на интерфейс фрейма для которого назначена данная функция обратного вызова. Второй параметр — это указатель на необязательные дополнительные данные. Третий параметр — это значение, полученное функцией Tick(). Помните, что функция Tick() может применяться для замедления и ускорения анимации в программе. Если в качестве параметра функции Tick() вы всегда используете 1.0, то можете спокойно игнорировать третий параметр.


Использование нескольких портов просмотра


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



Источник направленного света


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



Источник зонального света


Источник зонального света, или прожектор, (spot light) имеет ориентацию и заданное местоположение, и излучает световой поток в форме конуса. Характеристики конуса определяются углами светового пятна (hotspot или umbra) и зоны освещенности (fallof или penumbra). Угол светового пятна определяет конус, в котором интенсивность зонального света максимальна. Угол зоны освещенности определяет конус света с уменьшающейся интенсивностью вокруг светового пятна. Угол зоны освещенности источника зонального освещения всегда больше угла светового пятна.

Direct3D поддерживает источники рассеянного света, точечные и направленные источники света, а также источники зонального света. Также Direct3D предлагает вариант направленного источника света, называемый параллельным источником света (parallel light).



Изменение пути поиска файлов


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

AddSearchPath()

GetSearchPath()

SetSearchPath()

По умолчанию Direct3D устанавливает путь поиска файлов c:\dxsdk\sdk\media. С помощью перечисленных функций вы можете изменить этот путь или добавить другие пути, но изменения будут действовать для вашей программы только во время ее выполнения. Указанный путь не создается на пользовательском компьютере, так что ваша программа не должна зависеть от его существования.

ЗАМЕЧАНИЕ Изменение заданного по умолчанию пути поиска файлов. Заданный по умолчанию путь поиска файлов сохраняется в системном реестре Windows и может быть изменен с помощью редактора системного реестра Windows95 REGEDIT. Вы можете найти путь, отыскав запись реестра D3D Path.



Изменения в классе RMWin


Определение версии класса RMWin, используемой в приложении MultiView, приведено в листинге 9.5.

Листинг 9.5. Определение класса RMWin

class RMWin : public CFrameWnd { public: RMWin(); RMWin(int w,int h); BOOL Create(const CString& sTitle,int icon,int menu); void SetColorModel(D3DCOLORMODEL cm ) { colormodel = cm; } inline COLORREF D3DCOLOR_2_COLORREF(D3DCOLOR d3dclr); inline D3DCOLOR COLORREF_2_D3DCOLOR(COLORREF cref); void Render(); protected: static int GetMouseX() { return mousex; } static int GetMouseY() { return mousey; } void ScaleMesh(LPDIRECT3DRMMESHBUILDER, D3DVALUE); protected: //{{AFX_MSG(RMWin) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnDestroy(); afx_msg void OnActivate(UINT state, CWnd* other, BOOL minimize); afx_msg void OnPaint(); afx_msg void OnSize(UINT type, int cx, int cy); afx_msg void OnMouseMove(UINT state, CPoint point); afx_msg BOOL OnEraseBkgnd(CDC* pDC); afx_msg void OnViewport1Disabled(); afx_msg void OnViewport1Front(); afx_msg void OnViewport1Left(); afx_msg void OnViewport1Right(); afx_msg void OnViewport1Top(); afx_msg void OnViewport2Disabled(); afx_msg void OnViewport2Front(); afx_msg void OnViewport2Left(); afx_msg void OnViewport2Right(); afx_msg void OnViewport2Top(); afx_msg void OnViewport3Disabled(); afx_msg void OnViewport3Front(); afx_msg void OnViewport3Left(); afx_msg void OnViewport3Right(); afx_msg void OnViewport3Top(); afx_msg void OnUpdateViewport1Disabled(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport1Front(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport1Left(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport1Right(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport1Top(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport2Disabled(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport2Front(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport2Left(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport2Right(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport2Top(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport3Disabled(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport3Front(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport3Left(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport3Right(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport3Top(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: void Initvars(); virtual BOOL CreateScene() = 0; BOOL CreateDevice(); GUID* GetGUID(); void ConfigViewport(LPDIRECT3DRMFRAME camera, int view); void CreateViewports(); protected: static LPDIRECT3DRM d3drm; LPDIRECT3DRMFRAME scene; LPDIRECT3DRMDEVICE device; D3DCOLORMODEL colormodel; private: LPDIRECT3DRMFRAME camera1, camera2, camera3; LPDIRECT3DRMVIEWPORT viewport1, viewport2, viewport3; int view1setting, view2setting, view3setting; CRect winrect; LPDIRECTDRAWCLIPPER clipper; static int mousex; static int mousey; static UINT mousestate; friend class RMApp; };

<
/p> Ясно видно, что это сложный класс, и мы в данном разделе не будем обсуждать все, входящие в него функции. Вместо этого мы сосредоточим внимание на тех частях класса, которые отличаются от оригинальной версии RMWin. Обсуждение остальных функций класса RMWin вы найдете в главе 4.

Были добавлены три переменные: camera1, camera2 и camera3. Это указатели на интерфейс Direct3DRMFrame, которые будут использоваться для создания и перемещения трех, созданных в приложении, портов просмотра. Тот факт, что эти переменные объявлены закрытыми, говорит нам, что классы, производные от RMWin не имеют возможности манипулировать этими указателями. Эта задача возложена исключительно на класс RMWin.

Переменные viewport1, viewport2 и viewport3 будут использованы для доступа к трем созданным в приложении портам просмотра. Эти переменные также закрытые, поэтому для их инициализации могут использоваться только функции класса RMWin.

Кроме того, добавлены еще три переменные: view1setting, view2setting и view3setting. Эти значения используются для того, чтобы указать, как должен быть расположен каждый из портов просмотра. Переменные используются совместно со следующими константами (определенными в файле resource.h):

VIEWPORT_DISABLED

VIEWPORT_FRONT

VIEWPORT_LEFT

VIEWPORT_RIGHT

VIEWPORT_TOP

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

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


Каркасный метод


Каркасный метод визуализации (wireframe rendering) не предназначен для создания реалистичных изображений и не требует выполнения всех тех действий, которые мы обсуждали до этого. В каркасных моделях выводятся только края граней объектов сцены, изображаемые прямыми линиями. На рис.2.22 изображена сетка, выведенная в каркасном режиме.


Рис. 2.22. Каркасный метод визуализации



Карты сообщений


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

BEGIN_MESSAGE_MAP(OurClass, BaseClass) ON_WM_PAINT() ON_WM_SIZE() ON_WM_LBUTTONDOWN() END_MESSAGE_MAP()

Эта карта сообщений указывает MFC, что у нас есть класс, с именем OurClass являющийся производным от класса BaseClass и обрабатывающий три сообщения: WM_PAINT, WM_SIZE и WM_LBUTTONDOWN (это стандартные сообщения Windows).

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

void OurClass::OnPaint() { // Здесь располагается функция обработки // сообщения WM_PAINT. }

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

class OurClass : public BaseClass { protected: afx_msg void OnPaint(); afx_msg void OnSize(UINT type, int cx, int cy); afx_msg void OnLButtonDown(UINT state, CPoint point); DECLARE_MESSAGE_MAP() };

Макроопределение afx_msg определяет описываемую функцию как обработчик события, а макроопределение DECLARE_MESSAGE_MAP указывает MFC, что данный класс использует карту сообщений.



Класс AmbientLightWin


Львиная доля функциональности нашего проекта сосредоточена в классе AmbientLightWin. Определение этого класса выглядит следующим образом:

class AmbientLightWin : public RMWin { public: AmbientLightWin(); BOOL CreateScene(); protected: //{{AFX_MSG(AmbientLightWin) afx_msg void OnRenderWireframe(); afx_msg void OnRenderFlat(); afx_msg void OnRenderGouraud(); afx_msg void OnUpdateRenderFlat(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGouraud(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderWireframe(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: LPDIRECT3DRMMESHBUILDER meshbuilder; };

Класс AmbientLightWin наследуется от класса RMWin. В нем объявлены две открытые функции: конструктор и функция CreateScene(). Конструктор инициализирует единственную переменную класса:

AmbientLightWin::AmbientLightWin() { meshbuilder=0; }

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

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



Класс Cube2Win


Функциональность приложения Cube2 реализована классом Cube2Win:

class Cube2Win : public RMWin { public: Cube2Win(); BOOL CreateScene(); protected: //{{AFX_MSG(Cube2Win) afx_msg void OnRenderGroup1Flat(); afx_msg void OnRenderGroup1Wireframe(); afx_msg void OnRenderGroup1Gouraud(); afx_msg void OnRenderGroup2Wireframe(); afx_msg void OnRenderGroup2Flat(); afx_msg void OnRenderGroup2Gouraud(); afx_msg void OnUpdateRenderGroup1Wireframe(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGroup1Flat(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGroup1Gouraud(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGroup2Wireframe(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGroup2Flat(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGroup2Gouraud(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: static void UpdateCube(LPDIRECT3DRMFRAME, void*, D3DVALUE); static void UpdateColors(LPDIRECT3DRMFRAME, void*, D3DVALUE); private: LPDIRECT3DRMMESH mesh; D3DRMGROUPINDEX group1, group2; };

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



Класс CubeWin


Основная функциональность приложения Cube обеспечивается классом CubeWin:

class CubeWin : public RMWin { public: CubeWin(); BOOL CreateScene(); protected: //{{AFX_MSG(CubeWin) afx_msg void OnRenderWireframe(); afx_msg void OnRenderFlat(); afx_msg void OnRenderGouraud(); afx_msg void OnUpdateRenderWireframe(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderFlat(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGouraud(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: static void UpdateCube(LPDIRECT3DRMFRAME, void*, D3DVALUE); private: LPDIRECT3DRMMESH mesh; D3DRMGROUPINDEX group; };

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

Шесть защищенных функций необходимы для реализации меню Render.

Далее следует объявление функции обратного вызова UpdateCube(), которая будет выполнять анимацию вершин.

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



Класс DecalWin


Основная функциональность приложения Decal сосредоточена в классе DecalWin:

class DecalWin : public RMWin { public: BOOL CreateScene(); protected: //{{AFX_MSG(DecalWin) //}}AFX_MSG DECLARE_MESSAGE_MAP() };

Класс DecalWin очень простой. В нем объявлена только одна функция: CreateScene(). Код класса после слова protected содержит код мастера ClassWizard, необходимый если в дальнейшем будут добавляться обработчики событий.



Класс FacePickWin


В классе FacePickWin сосредоточена основная функциональность приложения FacePick:

class FacePickWin : public RMWin { public: FacePickWin(); BOOL CreateScene(); protected: //{{AFX_MSG(FacePickWin) afx_msg void OnRenderWireframe(); afx_msg void OnRenderGouraud(); afx_msg void OnRenderFlat(); afx_msg void OnUpdateRenderWireframe(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderFlat(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGouraud(CCmdUI* pCmdUI); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg void OnLButtonUp(UINT nFlags, CPoint point); afx_msg void OnColorsFace(); afx_msg void OnColorsMesh(); afx_msg void OnFileOpen(); afx_msg void OnFileSave(); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: static void UpdateDrag(LPDIRECT3DRMFRAME, void*, D3DVALUE); int PickFace(const CPoint& point); void OnIdle(long); private: LPDIRECT3DRMMESHBUILDER meshbuilder; LPDIRECT3DRMFRAME meshframe; D3DCOLOR pickcolor; D3DVALUE meshscale; static BOOL drag; static BOOL end_drag; static int last_x, last_y; };

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

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

Функции OnColorsFace() и OnColorsMesh() реализуют функциональность меню Colors. Обе функции выводят диалоговое окно выбора цвета с помощью класса MFC CColorDialog.

Функции OnFileOpen() и OnFileSave() используют класс MFC CFileDialog чтобы позволить пользователю выбрать или ввести имя файла.

Кроме того объявлены три закрытые функции: UpdateDrag(), PickFace() и OnIdle(). Функция UpdateDrag() — это функция обратного вызова, используемая для изменения ориентации сетки. Функция PickFace() подобна функции PickMesh(), которую мы использовали в приложении PickMesh. PickFace() отвечает за выполнение операции выбора граней.



Класс FireflyWin


Основная функциональность приложения Firefly реализована в классе FireflyWin, наследуемом от класса RMWin. Определение класса выглядит следующим образом:

class FireflyWin : public RMWin { public: FireflyWin(); BOOL CreateScene(); protected: //{{AFX_MSG(FireflyWin) afx_msg void OnRenderWireframe(); afx_msg void OnRenderFlat(); afx_msg void OnRenderGouraud(); afx_msg void OnUpdateRenderFlat(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGouraud(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderWireframe(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: LPDIRECT3DRMMESHBUILDER chalicebuilder; };

В классе FireflyWin объявлены две открытых функции: конструктор и функция CreateScene(). Конструктор обнуляет переменную класса chalicebuilder:

FireflyWin::FireflyWin() { chalicebuilder = 0; }

Шесть защищенных функций обеспечивают в приложении Firefly поддержку меню Render.



Класс FullScreenWin


Функциональность приложения FullScreen сосредоточена в классе FullScreenWin. В качестве базового класса для FullScreenWin используется класс RMWin, что можно увидеть в листинге 10.8.

Листинг 10.8. Класс FullScreenWin

class FullScreenWin : public RMWin { public: FullScreenWin(); protected: //{{AFX_MSG(FullScreenWin) afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: void OnRenderWireframe(); void OnRenderFlat(); void OnRenderGouraud();

BOOL CreateScene(); void Render(); static void UpdateAnimation(LPDIRECT3DRMFRAME, void*, D3DVALUE);

BOOL CreateMenuSurface(); BOOL UpdateMenuSurface();

BOOL CreateFPSSurface(); BOOL UpdateFPSSurface(); private: LPDIRECT3DRMMESHBUILDER meshbuilder; static LPDIRECT3DRMANIMATION animation;

HFONT smallfont, largefont;

LPDIRECTDRAWSURFACE menusurf; RECT menurect; int selectmode;

LPDIRECTDRAWSURFACE fpssurf; RECT fpsrect; BOOL displayfps; };

Единственной открытой функцией класса является конструктор, который отвечает за инициализацию динамических переменных класса.

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

Обратите внимание, что обычные функции реализации меню Render отсутствуют в секции MFC afx_msg. Вместо этого, они объявлены как три обычные закрытые функции класса. Это связано с тем, что мы не используем стандартное меню Windows. Нам придется самим вызывать эти функции, вместо того чтобы позволить MFC выполнить эту работу за нас.

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

Далее объявлена функция Render(). Вспомните, что в классе RMWin функция Render() объявлена как чисто виртуальная функция. Поэтому мы должны предоставить версию этой функции в классе FullScreenWin. Данная функция используется для визуализации и отображения выходных данных приложения.


Также объявлена статическая функция обратного вызова UpdateAnimation(). Она применяется для обновления анимационной последовательности приложения.

Следующие четыре функции, объявленные в классе FullScreenWin заслуживают более подробного обсуждения:

BOOL CreateMenuSurface(); BOOL UpdateMenuSurface();

BOOL CreateFPSSurface(); BOOL UpdateFPSSurface();

Когда вы запускаете приложение FullScreen в верхнем левом углу экрана появляется список доступных видеорежимов, а примерно через две секунды, в нижнем правом углу появляется счетчик частоты кадров (FPS).

Каждое из этих изображений отображается с использованием поверхностей, создаваемых и поддерживаемых классом FullScreenWin. Функции CreateMenuSurface() и UpdateMenuSurface() создают список (или меню) видеорежимов и управляют им. Функции CreateFPSSurface() и UpdateFPSSurface() создают и управляют счетчиком FPS.

В оставшейся части класса содержатся объявления переменных. Здесь объявлены указатели на интерфейсы Direct3DRMMeshBuilder и Direct3DRMAnimation. Следом расположены объявления двух экземпляров HFONT. Тип HFONT — это дескриптор шрифта Windows. Мы будем использовать эти дескрипторы для вывода текста меню видеорежимов и счетчика частоты кадров.

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

LPDIRECTDRAWSURFACE menusurf; RECT menurect; int selectmode;

Указатель menusurf используется для управления поверхностью, содержащей меню видеорежимов. Структура menurect хранит размеры поверхности меню. Целочисленная переменная selectmode применяется для отслеживания выделенного в данный момент видеорежима. Не следует путать выделенный видеорежим с активным видеорежимом. Соответствующий видеорежиму пункт меню может быть выделен с помощью клавиш управления курсором, однако выделенный видеорежим не станет активным, пока пользователь не нажмет клавишу ENTER.

Оставшиеся переменные предназначены для вывода счетчика FPS:

LPDIRECTDRAWSURFACE fpssurf; RECT fpsrect; BOOL displayfps;

Указатель fpssurf используется для обновления и отображения поверхности счетчика FPS. Структура fpsrect хранит размеры поверхности fpssurf. Логическая переменная displayfps применяется для реализации задержки отображения счетчика FPS при каждой смене видеорежима. Это сделано по той причине, что начальные значения счетчика FPS обычно неправильны. Поэтому значение счетчика FPS отображается только после того, как скорость работы приложения стабилизируется.


Класс JadeWin


Большинство функциональных возможностей приложения Jade реализовано в классе JadeWin. Класс JadeWin наследует базовую функциональность Direct3D от класса RMWin, который был подробно описан в главе4. Определение класса JadeWin выглядит следующим образом:

class JadeWin : public RMWin { public: JadeWin(); BOOL CreateScene(); static void MoveFrame(LPDIRECT3DRMFRAME frame, void*, D3DVALUE); protected: //{{AFX_MSG(JadeWin) afx_msg void OnRenderWireframe(); afx_msg void OnRenderFlat(); afx_msg void OnRenderGouraud(); afx_msg void OnUpdateRenderFlat(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGouraud(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderWireframe(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: LPDIRECT3DRMMESHBUILDER meshbuilder; };

Здесь объявлены три открытые функции: конструктор, функция CreateScene() и функция обратного вызова с именем MoveFrame(). Конструктор применяется для инициализации единственной переменной класса:

JadeWin::JadeWin() { meshbuilder=0; }

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

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



Класс MeshPickWin


Функциональность приложения MeshPick предоставляется классом MeshPickWin:

class MeshPickWin : public RMWin { public: MeshPickWin(); BOOL CreateScene(); protected: //{{AFX_MSG(MeshPickWin) afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg void OnLButtonUp(UINT nFlags, CPoint point); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: static void UpdateDrag(LPDIRECT3DRMFRAME frame, void*, D3DVALUE); BOOL PickMesh(const CPoint& point); private: static DragData drag; };

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

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

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

В классе объявлен только один член даных — структура DragData. Ее объявление выглядит следующим образом:

struct DragData { LPDIRECT3DRMFRAME frame; POINT mousedown; D3DVALUE origx,origy; };

Структура содержит данные, имеющие отношение к выполняемой операции перетаскивания. Член структуры frame указывает на фрейм, к которому присоединена перетаскиваемая сетка. Поле mousedown используется для сохранения координат указателя мыши в момент инициализации операции перетаскивания. Члены данных origx и origy применяются для сохранения координат фрейма в момент начала перетаскивания.



Класс MoleculeWin


Основная функциональность приложения Molecule сосредоточена в классе MoleculeWin:

class MoleculeWin : public RMWin { public: MoleculeWin(); BOOL CreateScene(); protected: //{{AFX_MSG(MoleculeWin) afx_msg void OnDepth1(); afx_msg void OnDepth2(); afx_msg void OnDepth3(); afx_msg void OnDepth4(); afx_msg void OnDepth5(); afx_msg void OnDepth6(); afx_msg void OnChildren1(); afx_msg void OnChildren2(); afx_msg void OnChildren3(); afx_msg void OnChildren4(); afx_msg void OnUpdateChildren1(CCmdUI* pCmdUI); afx_msg void OnUpdateChildren2(CCmdUI* pCmdUI); afx_msg void OnUpdateChildren3(CCmdUI* pCmdUI); afx_msg void OnUpdateChildren4(CCmdUI* pCmdUI); afx_msg void OnUpdateDepth1(CCmdUI* pCmdUI); afx_msg void OnUpdateDepth2(CCmdUI* pCmdUI); afx_msg void OnUpdateDepth3(CCmdUI* pCmdUI); afx_msg void OnUpdateDepth4(CCmdUI* pCmdUI); afx_msg void OnUpdateDepth5(CCmdUI* pCmdUI); afx_msg void OnUpdateDepth6(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: BOOL CreateHierarchy(); BOOL CreateChildren(LPDIRECT3DRMFRAME frame, int depth); private: LPDIRECT3DRMMESH mesh[MAXDEPTH]; int curdepth; int numchildren; int framecount; };

В классе объявлены две открытые функции: конструктор и функция CreateScene(). Конструктор применяется для инициализации членов данных класса. Функция CreateScene() создает начальную сцену приложения. Мы познакомимся с ней чуть позже.

Двадцать защищенных функций, являющихся обработчиками событий, установлены с помощью мастера ClassWizard. Эти функции обеспечивают работу меню Depth и Children в приложении.

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

При создании иерархии функция CreateHierarchy() использует функцию CreateChildren(). Функция CreateChildren() является рекурсивной функцией, добавляющей дочерние фреймы к существующему фрейму.

И, наконец, в классе объявлены три переменных:

LPDIRECT3DRMMESH mesh[MAXDEPTH]; int curdepth; int numchildren;

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

Переменные curdepth и numchildren хранят текущие параметры иерархии. Они модифицируются обработчиками событий команд меню Depth и Children, и используются в функциях CreateHierarchy() и CreateChildren(). Инициализация этих переменных выполняется конструктором класса MoleculeWin, код которого выглядит так:

MoleculeWin::MoleculeWin() { curdepth=4; numchildren=2; }