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

         

Функция InitInstance()


Функция InitInstance() — это виртуальная функция класса CWinApp, переопределяемая для выполнения инициализации. Собственные версии функции InitInstance() предоставляют и класс RMApp и класс SampleApp.

Функция InitInstance() класса SampleApp отвечает за создание объекта окна. В ней можно установить некоторые параметры. Функция SampleApp::InitInstance() выглядит следующим образом:

BOOL SampleApp::InitInstance() { #ifdef _DEBUG afxTraceEnabled = FALSE; #endif SampleWin* win = new SampleWin; if (!win->Create("Sample Application", IDI_ICON, IDR_MAINMENU)) return FALSE; win->SetColorModel(D3DCOLOR_MONO); m_pMainWnd = win; return RMApp::InitInstance(); }

Первые три строки функции инициализируют функции трассировки MFC. Макроопределение TRACE удобно использовать для вывода диагностических сообщений во время выполнения программы. По умолчанию использование макроопределения TRACE разрешено, но выполняется оно очень медленно. Если вы часто используете это макроопределение, оно окажет видимый эффект на производительность вашего приложения (особенно, если применять его в функции, вызываемой при каждомм обновлении экрана). Переменная afxTraceEnabled применяется для активации или деактивации макроопределений TRACE. Присваивание этой переменной значения FALSE запрещает функции трассировки. Обратите внимание, что значение этой переменной присваивается в блоке условной компиляции. Это связано с тем, что функции трассировки доступны только в приложениях, компилируемых в режиме отладки (DEBUG). Если при компиляции не включается отладочная информация (выбран режим Release), переменная afxTraceEnabled не существует, и необходимо гарантировать, что обращение к ней будет выполняться только в режиме отладки.

Затем создается экземпляр класса SampleWin. Функция Create() вызывается для инициализации окна и ей передаются три аргумента. Первый аргумент — это строка, отображаемая в заголовке окна. Второй — идентификатор ресурса для значка приложения, а третий — идентификатор ресурса для меню.


После вызова функции Create() (и проверки успешности ее завершения), вызывается функция SetColorModel(). Константа D3DCOLOR_MONO указывает, что вместо цветовой модели RGB мы будем применять цветовую модель Ramp (монохромную). Цветовая модель Ramp выбирается по умолчанию, так что нет необходимости вызывать эту функцию, но мы включим ее, чтобы отметить правильное место для задания цветовой модели. Вызов функции SetColorModel() на другом этапе выполнения программы не окажет ожидаемого действия.

В завершение переменной CWnd::m_pMainWnd присваивается указатель на новое окно. Это важный этап, поскольку MFC использует данную переменную для доступа к окну.

Функция SampleApp::InitInstance() завершается вызовом функции RMApp::InitInstance() код которой приведен ниже:

BOOL RMApp::InitInstance() { ASSERT(m_pMainWnd); m_pMainWnd->ShowWindow(SW_SHOWNORMAL); m_pMainWnd->UpdateWindow(); return TRUE; }

В самом начале функция проверяет присвоено ли значение переменной m_pMainWnd. Макроопределение ASSERT прерывает выполнение программы и выводит окно сообщения, если значение переменной равно NULL.

Затем вызываются функции ShowWindow() и UpdateWindow(). Эти функции наследуются от класса CWnd и необходимы для инициализации окна.

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


Функция InitMainSurfaces()


Пришло время взглянуть на функцию InitMainSurfaces(). Хочу напомнить, что функция InitMainSurfaces() вызывается из функции OnCreate() сразу после обращения к функции InitDisplayMode(). Функция InitMainSurfaces() создает первичную и вторичную поверхности вместе с Z-буфером. Код функции приведен в листинге10.5.

Листинг 10.5. Функция InitMainSurfaces()

BOOL RMWin::InitMainSurfaces() { if (primsurf) { primsurf->Release(); primsurf = 0; } if (zbufsurf) { zbufsurf->Release(); zbufsurf = 0; }

DDSURFACEDESC desc; desc.dwSize = sizeof(desc); desc.dwFlags = DDSD_BACKBUFFERCOUNT | DDSD_CAPS; desc.dwBackBufferCount = 1; desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_3DDEVICE | DDSCAPS_FLIP | DDSCAPS_COMPLEX; ddraw->CreateSurface(&desc, &primsurf, 0);

DDSCAPS ddscaps; ddscaps.dwCaps = DDSCAPS_BACKBUFFER; primsurf->GetAttachedSurface(&ddscaps, &backsurf);

memset(&desc, 0, sizeof(desc)); desc.dwSize = sizeof(DDSURFACEDESC); desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS | DDSD_ZBUFFERBITDEPTH; desc.dwWidth = modewidth; desc.dwHeight = modeheight; desc.dwZBufferBitDepth = 16; desc.ddsCaps.dwCaps = DDSCAPS_ZBUFFER | DDSCAPS_SYSTEMMEMORY; ddraw->CreateSurface(&desc, &zbufsurf, 0); backsurf->AddAttachedSurface(zbufsurf);



return TRUE; }

Сначала функция InitMainSurfaces() освобождает любые существующие первичные поверхности и Z-буферы:

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

Функция OnCreate() (которая вызывает функцию InitMainSurfaces()) вызывается только однажды — когда создается окно программы — а в этот момент не существует ни первичной поверхности, ни Z-буфера. Однако функция InitMainSurfaces() используется другими функциями для изменения видеорежима. Поэтому существующие первичная поверхность и Z-буфер должны быть освобождены перед созданием новых. Вторичную поверхность освобождать не требуется, поскольку она будет уничтожена вместе с первичной поверхностью.


Затем создается первичная поверхность:

DDSURFACEDESC desc; desc.dwSize = sizeof(desc); desc.dwFlags = DDSD_BACKBUFFERCOUNT | DDSD_CAPS; desc.dwBackBufferCount = 1; desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_3DDEVICE | DDSCAPS_FLIP | DDSCAPS_COMPLEX; ddraw->CreateSurface(&desc, &primsurf, 0);

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

Флаги возможностей поверхности (DDSCAPS_PRIMARYSURFACE, DDSCAPS_FLIP и DDSCAPS_COMPLEX) указывают, что мы создаем первичную поверхность с возможностью переключения страниц. Флаг DDSCAPS_3DDEVICE сообщает DirectDraw, что мы будем использовать новую поверхность для создания устройства Direct3D.

Фактическое создание поверхностей выполняет функция CreateSurface() интерфейса DirectDraw. В качестве первого аргумента мы передаем подготовленную ранее структуру DDSURFACEDESC. Во втором аргументе передается адрес указателя на интерфейс DirectDrawSurface. Указатель (primsurf) является членом класса RMWin и будет использоваться в дальнейшем для выполнения переключения страниц.

Обратите внимание, что значение поля dwBackBufferCount структуры DDSURFACEDESC равно единице. Это означает, что мы проинформировали DirectDraw о наличии у первичной поверхности одного вторичного буфера. Фактически, DirectDraw создает вторичный буфер вместе с первичной поверхностью. Все что нам остается сделать — получить указатель на вторичный буфер:

DDSCAPS ddscaps; ddscaps.dwCaps = DDSCAPS_BACKBUFFER; primsurf->GetAttachedSurface(&ddscaps, &backsurf);

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



Теперь мы создаем Z-буфер и присоединяем его к поверхности backsurf:

memset(&desc, 0, sizeof(desc)); desc.dwSize = sizeof(DDSURFACEDESC); desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS | DDSD_ZBUFFERBITDEPTH; desc.dwWidth = modewidth; desc.dwHeight = modeheight; desc.dwZBufferBitDepth = 16; desc.ddsCaps.dwCaps = DDSCAPS_ZBUFFER | DDSCAPS_SYSTEMMEMORY; ddraw->CreateSurface(&desc, &zbufsurf, 0); backsurf->AddAttachedSurface(zbufsurf);

Для описания поверхности Z- буфера нам потребуется экземпляр структуры DDSURFACEDESC. Вместо того, чтобы объявлять еще одну копию, воспользуемся уже существующим экземпляром desc, который использовался при создании первичной поверхности. Очистим структуру с помощью функции memset(), присвоив всем ее полям нулевые значения. Затем присвоим требуемые значения полям dwSize и dwFlags.

Флаги, присвоенные полю dwFlags указывают, что мы будем задавать ширину и высоту поверхности, ее возможности, а также глубину Z-буфера. Соответствующим полям структуры присваиваются значения. Размеры Z-буфера должны быть равны размерам первичного и вторичного буферов. Поскольку размеры первичного и вторичного буферов всегда равны размеру экрана в текущем видеорежиме, для присваивания значений полям dwWidth и dwHeight можно использовать переменные modewidth и modeheight.

Мы создаем 16-разрядный Z-буфер, присваивая соответствующее значение полю. Глубина Z-буфера определяет точность работы кода удаления невидимых поверхностей. 8-разрядный Z-буфер может хранить только 256 различных значений Z, или расстояний, поэтому его возможности весьма ограничены. 16-разрядный Z-буфер предоставляет 65 535 различных значений и подходит для большинства случаев Z-буферизации. Сложные сцены могут потребовать наличия 24- или 32-разрядного Z-буфера.

Константа DDSCAPS_SYSTEMMEMORY используется для указания, что Z-буфер должен размещаться в системной памяти, а не в памяти видеокарты. Видеопамяти может не хватать для трехмерной графики (особенно на видеокартах с 2 мегабайтами памяти).Хранение Z-буфера в системной памяти освобождает память видеокарты для хранения отображаемых поверхностей, таких как спрайты и фоновые изображения. Хранение отображаемых данных в видеопамяти более предпочтительно, поскольку ускоряет работу приложения так как видеокарты обычно выполняют копирование блоков видеопамяти гораздо быстрее, чем копирование из системной памяти в видеопамять.

На последнем этапе выполняется создание поверхности Z-буфера и ее присоединение к поверхности backsurf:

ddraw->CreateSurface(&desc, &zbufsurf, 0); backsurf->AddAttachedSurface(zbufsurf);

После того, как поверхность Z-буфера присоединена, Direct3D использует ее автоматически. Больше никаких манипуляций с Z-буфером не требуется.


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


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

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

BOOL JadeWin::CreateScene() { HRESULT r; //------- ФОН -------- scene->SetSceneBackgroundRGB(D3DVALUE(.2), D3DVALUE(.2), D3DVALUE(.2));

//-------- СЕТКА -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_D3DMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->SetPerspective(TRUE); r = meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); if (r != D3DRM_OK) { TRACE("meshbuilder->Load() failed\n"); return FALSE; } ScaleMesh(meshbuilder, D3DVALUE(35));

//-------- ТЕКСТУРА -------- LPDIRECT3DRMTEXTURE texture; HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_JADETEXTURE), "TEXTURE"); r = d3drm->LoadTextureFromResource(texture_id, &texture); if (r != D3DRM_OK) { TRACE("d3drm->LoadTextureFromResource() failed\n"); return FALSE; } meshbuilder->SetTexture(texture); texture->Release(); texture = 0;

//-------- НАЛОЖЕНИЕ -------- D3DRMBOX box; meshbuilder->GetBox(&box); D3DVALUE w = box.max.x - box.min.x; D3DVALUE h = box.max.y - box.min.y;

LPDIRECT3DRMWRAP wrap; d3drm->CreateWrap(D3DRMWRAP_FLAT, scene, 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,w), D3DDivide(1,h), // масштаб текстуры &wrap); wrap->Apply(meshbuilder); wrap->Release(); wrap = 0;

//------- ФРЕЙМ СЕТКИ ---------- LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->AddMoveCallback(MoveFrame, NULL); meshframe->Release();

//---------- ОСВЕЩЕНИЕ ----------- 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() выполняет следующие действия:

Изменение цвета фона сцены.

Загрузка сетки.

Загрузка нефритовой текстуры.

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

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

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

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

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

scene->SetSceneBackgroundRGB(D3DVALUE(.2), D3DVALUE(.2), D3DVALUE(.2));

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

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

D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_D3DMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->SetPerspective(TRUE); r = meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); if (r != D3DRM_OK) { TRACE("meshbuilder->Load() failed\n"); return FALSE; } ScaleMesh(meshbuilder, D3DVALUE(35));

Для загрузки сетки из ресурсов программы необходимо подготовить структуру D3DRMLOADRESOURCE, содержащую три поля: hModule, lpName и lpType. Поле hModule идентифицирует модуль, содержащий требуемый ресурс. Это полезно, если ресурсы загружаются из другого исполняемого модуля, но поскольку в нашем случае требуемый ресурс относится к той же программе, которая вызывает функцию, мы укажем NULL. Поле lpName хранит значение, идентифицирующее требуемый ресурс. Поле lpType задает тип ресурса.

СОВЕТ Тип ресурса MESH. Нет ничего необычного в типе ресурса MESH (Visual C++ ничего не знает о сетках Direct3D). Приложения из этой книги хранят сетки в группе ресурсов с именем MESH, чтобы отделить ресурсы сеток от других ресурсов.
После того, как подготовлена структура D3DRMLOADRESOURCE, функция CreateMeshBuilder() интерфейса Direct3DRM инициализирует указатель meshbuilder.


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

Затем вызывается функция Load(), которой в качестве первого аргумента передается указатель на подготовленную структуру D3DRMLOADRESOURCE. Третий аргумент функции Load() (флаг D3DRMLOAD_FROMRESOURCE) указывает Direct3D, что загрузка происходит из ресурсов программы, а не из файла на диске. Возвращаемое функцией Load() значение, говорит о том, успешно ли завершилась функция. Если при выполнении функции Load() произошла ошибка, CreateScene() возвращает FALSE.

СОВЕТ Проверка возвращаемых значений. Большинство функций Direct3D возвращают значение типа HRESULT, указывающее на состояние функции. Как правило, не требуется проверять возвращаемое значение для каждой функции, но рекомендуется проверять возвращаемые значения для тех функций, которые могут завершиться неудачно из-за внешних причин. Например, функции загрузки файла часто завершаются неудачно, из-за того, что не могут найти требуемый файл. В данном сучае неудачное завершение функции Load() маловероятно, поскольку сетка является частью EXE-файла программы.
Затем используется функция ScaleMesh() для указания идеального размера сетки. Первый аргумент ScaleMesh() — Direct3DRMMeshBuilder представляет масштабируемую сетку. Второй аргумент — это желаемый размер сетки.

На следующем (третьем) этапе выполняется загрузка текстуры. Загрузка тексуры из ресурсов программы слегка отличается от загрузки сеток. Для загрузки сеток применяется функция Load(), независимо от того, где находится сетка: в файле на диске или в ресурсах программы. Для загрузки текстуры из ресурсов и из файла применяются различные функции. Мы воспользуемся функцией LoadTextureFromResource() (вместо функции LoadTexture()):

LPDIRECT3DRMTEXTURE texture; HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_JADETEXTURE), "TEXTURE"); r = d3drm->LoadTextureFromResource(texture_id, &texture); if (r != D3DRM_OK) { TRACE("d3drm->LoadTextureFromResource() failed\n"); return FALSE; } meshbuilder->SetTexture(texture); texture->Release(); texture = 0;



Функция LoadTextureFromResource() получает два аргумента: экземпляр структуры HRSRC и адрес указателя на интерфейс Direct3DRMTexture. HRSRC — это структура Win32, подобная структуре D3DRMLOADRESOURCE используемой функцией Load() интерфейса конструктора сеток. Структура HRSRC инициализируется функцией FindResource(). Три аргумента FindResource() идентифицируют загружаемый ресурс. Первый аргумент — это дескриптор модуля. Поскольку текстура загружается из того же исполняемого файла в котором содержится функция CreateScene(), мы используем значение NULL. Второе значение — это идентификатор загружаемого ресурса. Третий аргумент указывает тип ресурса.

После инициализации структуры HRSRC вызывается функция LoadTextureFromResource(). Если функция LoadTextureFromResource() завершается с ошибкой, CreateScene() возвращает FALSE (после того, как макрос TRACE выведет сообщение). Если функция завершилась успешно, то текстура присоединяется к ранее загруженной сетке с помощью функции SetTexture() интерфейса Direct3DRMMeshBuilder. Затем указатель texture освобождается, поскольку он больше нам не потребуется. После того, как указатель освобожден, ему присваивается нулевое значение.

СОВЕТ Остерегайтесь висящих указателей. Я рекомендую вам избегать висящих указателей, присваивая им нулевые значения после освобождения. Эта привычка спасет вас от проблем, которые могут возникнуть, если вы нечаянно используете освобожденный указатель.
Функция SetTexture() не указывает, как должна быть наложена текстура, она только сообщает, что текстура накладывается на сетку. Чтобы определить способ покрытия текстурой необходимо использовать наложение текстуры. На этапе 4 мы создадим и применим плоское наложение текстуры чтобы текстура не изгибалась и не деформировалась, а прямо накладывалась на сетку. Нам потребуется масштабировать текстуру, чтобы она была такого же размера, как и сетка. Чтобы сделать это, нам необходимо узнать размер сетки. Приведенный ниже код расположен в функции CreateScene() перед созданием наложения текстуры:



D3DRMBOX box; meshbuilder->GetBox(&box); D3DVALUE w = box.max.x - box.min.x; D3DVALUE h = box.max.y - box.min.y;

Функция GetBox() применяется для заполнения структуры D3DRMBOX, содержащей координаты крайних точек сетки. Мы используем эти значения для вычисления длины и высоты сетки. Обратите внимание, что мы вычисляем длину и высоту сетки, но не глубину. Поскольку текстура является двумерной, третье измерение сетки нас не интересует.

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

LPDIRECT3DRMWRAP wrap; d3drm->CreateWrap(D3DRMWRAP_FLAT, scene, 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,w), D3DDivide(1,h), // масштаб текстуры &wrap); wrap->Apply(meshbuilder); wrap->Release(); wrap = 0;

Для создания наложения мы используем функцию CreateWrap() интерфейса Direct3DRM. Функция CreateWrap() получает 16 аргументов. Первый аргумент определяет способ наложения. Мы используем константу D3DRMWRAP_FLAT чтобы указать на использование плоского наложения текстуры. Второй аргумент задает базовый фрейм. Мы укажем здесь корневой фрейм сцены, чтобы значения остальных аргументов воспринимались как абсолютные координаты. Если мы укажем другой фрейм, то оставшиеся аргументы будут интерпретироваться как координаты относительно указанного фрейма.

СОВЕТ Задание абсолютных значений. Существует два способа указать Direct3D, что заданные значения являются абсолютными, а не относительными. Первый способ — указать в качестве базового корневой фрейм сцены (как сделано в рассматриваемом коде). Второй — передать вместо указателя на базовый фрейм NULL.
Следующие девять аргументов CreateWrap() определяют местоположение и ориентацию наложения. Используемые в функции CreateScene() значения представлены в таблице 5.1.

Таблица 5.1.Параметры наложения для приложения Jade



Параметр Значение
Начало координат наложения <0.0, 0.0, 0.0>
Ось Z наложения <0.0, 0.0, 1.0>
Ось Y наложения <0.0, 1.0, 0.0>
Первые три аргумента определяют начало координат наложения. Используя значения <0.0, 0.0, 0.0>, мы указываем, что наложение размещается на локальных осях сетки (локальное начало координат для сетки обычно совпадает с ее центром).

Следующие три аргумента определяют вектор, задающий направление наложения. В данном примере мы используем вектор, совпадающий с осью Z и направленный от зрителя. Третий набор из трех аргументов задает вектор, определяющий направление вверх для покрытия. Мы используем направленный вверх вектор, который совпадает с осью Y.

Следующие четыре аргумента CreateWrap() задают начало координат и масштаб для текстуры. Параметры, используемые в функции CreateScene() представлены в таблице 5.2.

Таблица 5.2.Параметры текстуры для приложения Jade

Параметр Значение
Начало координат текстуры <0.5, 0.5>
Масштаб текстуры <D3DDivide(1,w), D3DDivide(1,h)>
Начало координат текстуры определяет точку текстуры, которая будет совпадать с началом координат наложения. Мы используем значение 0.5 и для координаты X и для координаты Y текстуры, чтобы начало координат наложения совпадало с центром текстуры (значение 0.5 означает середину текстуры, 0.0 означает верхний или левый край текстуры, а 1.0 означает правый или нижний край текстуры).

Масштаб текстуры определяет размер текстуры в пропорции к наложению. Вспомните, что мы собирались масштабировать текстуру так, чтобы она точно соответствовала размерам сетки. Мы уже отцентрировали текстуру, задав значения начала ее координат равными 0.5. Теперь, используя значения ширины и высоты сетки, мы масштабируем текстуру, чтобы она соответствовала размерам сетки. Для удобства Direct3D предоставляет макрос D3DDivide. Этот макрос приводит свои аргументы к типу D3DVALUE и делит первый аргумент на второй.


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

Последний аргумент CreateWrap() — это адрес указателя, который после выполненой функцией инициализации будет указывать на интерфейс Direct3DRMWrap. После того, как наложение создано, оно может быть применено к сетке с помощью функции Apply() интерфейса Direct3DRMWrap:

wrap->Apply(meshbuilder);

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

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

LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->AddMoveCallback(MoveFrame, NULL); meshframe->Release(); meshframe = 0;

Функция CreateFrame() интерфейса Direct3DRM применяется для создания фрейма с именем meshframe. Конструктор сеток присоединяется к новому фрейму функцией AddVisual(). Затем с помощью функции AddMoveCallback() устанавливается функция обратного вызова MoveFrame(). После описаных действий указатель meshframe освобождается.

Шестой этап — это создание источника рассеянного света:

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

Источник рассеянного света применяется в нашем примере по причине простоты использования. Более подробно об источниках света мы поговорим в главе 6.

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

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

Переменные camera и viewport наследуются от класса RMWin и должны инициализироваться в функции CreateScene().Переменная camera представляет собой указатель на интерфейс Direct3DRMFrame, инициализируемый функцией CreateFrame() интерфейса Direct3DRM. С помощью функции SetPosition() новый фрейм располагается на 50 единиц дальше начала координат. После этого функция CreateViewport() интерфейса Direct3DRM инициализирует указатель viewport.

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


Функция JadeWin::MoveFrame()


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

void JadeWin::MoveFrame(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { static int x; static int y; static int xi = 7; static int yi = 13; if (x < -31 || x > 31) xi = -xi; if (y < -35 || y > 35) yi = -yi; x += xi; y += yi; frame->SetOrientation(NULL, D3DVALUE(x), D3DVALUE(y), D3DVALUE(50), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); }

Функция MoveFrame() использует простой алгоритм «подскакивающего мяча» для изменения ориентации фрейма к которому прикреплена сетка при каждом обновлении экрана. Статические целочисленные переменные используются для запоминания текущей позиции фрейма и для вычисления новой. После того, как новая позиция вычислена, она назначается фрейму с помощью функции SetOrientation() интерфейса Direct3DRMFrame.



Функция KeyDown()


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

Листинг 10.15. Функция KeyDown()

void FullScreenWin::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { static int screencapture; int newindex; int nmodes = GetNumDisplayModes(); if (nmodes > maxmodes) nmodes = maxmodes;

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

switch (nChar) { case VK_ESCAPE: PostMessage(WM_CLOSE); break; case VK_UP: newindex = selectmode - 1; if (newindex >= 0) { selectmode = newindex; UpdateMenuSurface(); } break; case VK_DOWN: newindex = selectmode + 1; if (newindex < nmodes) { selectmode = newindex; UpdateMenuSurface(); } break; case VK_LEFT: newindex = selectmode - rows; if (newindex >= 0) { selectmode = newindex; UpdateMenuSurface(); } break; case VK_RIGHT: newindex = selectmode + rows; if (newindex < nmodes) { selectmode = newindex; UpdateMenuSurface(); } break; case VK_RETURN: if (menusurf) { menusurf->Release(); menusurf = 0; } if (fpssurf) { fpssurf->Release(); fpssurf = 0; } ActivateDisplayMode(selectmode); displayfps = FALSE; CreateMenuSurface(); UpdateMenuSurface(); CreateFPSSurface(); break; case 'W': OnRenderWireframe(); break; case 'F': OnRenderFlat(); break; case 'G': OnRenderGouraud(); break; }

RMWin::OnKeyDown(nChar, nRepCnt, nFlags); }

Если говорить по существу, функция KeyDown() использует клавиши управления курсором для выделения видеорежима в меню видеорежимов. Клавиша ENTER используется для активации выбранного видеорежима. Клавиши W, F и G применяются для изменения метода визуализации единственной сетки приложения (каркасный, плоский или Гуро). Нажатие на клавишу ESC завершает работу приложения.



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


Функция CreateScene() приложения MeshPick привелена в листинге 9.2.

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

BOOL MeshPickWin::CreateScene() { // ------- КОНСТРУКТОР СЕТОК -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER meshbuilder; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->SetQuality(D3DRMRENDER_FLAT);

//------ ДЕВЯТЬ СЕТОК ---------- for (int x = 0; x < 3; x++) { for (int y = 0; y < 3; y++) { LPDIRECT3DRMMESH mesh; meshbuilder->CreateMesh(&mesh); mesh->SetGroupColorRGB(0, D3DVALUE(x % 2), D3DVALUE(y % 2), D3DVALUE(1));

LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(mesh); int xoffset = (rand() % 3) - 1; int yoffset = (rand() % 3) - 1; meshframe->SetPosition(scene, D3DVALUE((x - 1) * 10 + xoffset), D3DVALUE((y - 1) * 10 + yoffset), D3DVALUE(0)); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.1));

meshframe->Release(); meshframe = 0; mesh->Release(); mesh = 0; } }

meshbuilder->Release(); meshbuilder = 0;

//------- ФУНКЦИЯ ОБРАТНОГО ВЫЗОВА -------- scene->AddMoveCallback(UpdateDrag, NULL);

// -------- НАПРАВЛЕННЫЙ СВЕТ -------- LPDIRECT3DRMLIGHT dlight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &dlight);

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

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

d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);

return TRUE; }

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

Использует интерфейс Direct3DRMMeshBuilder для загрузки сферической сетки.

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

Устанавливает функцию обратного вызова UpdateDrag().

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

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

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

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

Сетка загружается из ресурсов приложения функцией Load() интерфейса Direct3DRMMeshBuilder. Затем вызывается функция SetQuality(), чтобы изменить используемый по умолчанию для конструктора сеток метод визуализации Гуро на плоский метод визуализации.

На втором этапе для создания девяти сеток используется цикл. В цикле используется инициализированный ранее указатель meshbuilder:

for (int x = 0; x < 3; x++) { for (int y = 0; y < 3; y++) {

LPDIRECT3DRMMESH mesh; meshbuilder->CreateMesh(&mesh); mesh->SetGroupColorRGB(0, D3DVALUE(x % 2), D3DVALUE(y % 2), D3DVALUE(1)); LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(mesh); int xoffset = (rand() % 3) - 1; int yoffset = (rand() % 3) - 1; meshframe->SetPosition(scene, D3DVALUE((x - 1) * 10 + xoffset), D3DVALUE((y - 1) * 10 + yoffset), D3DVALUE(0)); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.1));

meshframe->Release(); meshframe = 0; mesh->Release(); mesh = 0; } }

meshbuilder->Release(); meshbuilder = 0;

Для создания девяти сеток применяются вложенные циклы. В теле внутреннего цикла для создания сетки используется функция CreateMesh() интерфейса Direct3DRMMeshBuilder. Назначаемый сетке цвет зависит от текущей итерации цикла.


Затем создается фрейм и сетка присоединяется к нему с помощью функции AddVisual(). Местоположение сетки зависит от текущей итерации цикла, но слегка изменяется на случайную величину (это сделано для того, чтобы пользователь понял, что сетки можно перемещать). Каждому фрейму назначаются атрибуты вращения, после чего указатели meshframe и mesh освобождаются.

Затем устанавливается функция обратного вызова:

scene->AddMoveCallback(UpdateDrag, NULL);

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

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

LPDIRECT3DRMLIGHT dlight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &dlight); LPDIRECT3DRMFRAME dlightframe; d3drm->CreateFrame(scene, &dlightframe); dlightframe->AddLight(dlight); dlightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0));

dlight->Release(); dlight = 0; dlightframe->Release(); dlightframe = 0;

Приведенный выше код создает направленный источник света и позиционирует его таким образом, чтобы свет распространялся между положительным направлением оси Z и отрицательным направлением оси Y. Источник света присоединаяется к фрейму с помощью функции AddLight() интерфейса Direct3DRMFrame.

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

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

Указатель на фрейм camera инициализируется функцией CreateFrame() интерфейса Direct3DRM, после чего фрейм позиционируется с помощью функции SetPosition() интерфейса Direct3DRMFrame. Указатель viewport инициализируется функцией CreateViewport() интерфейса Direct3DRM.


Функция MeshPickWin::PickMesh()


Как мы отмечали при рассмотрении функции OnLButtonDown(), функция PickMesh() используется для проверки наличия объекта в указанной точке:

BOOL MeshPickWin::PickMesh(const CPoint& point) { HRESULT r; LPDIRECT3DRMPICKEDARRAY pickarray;

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

BOOL ret = FALSE; DWORD numpicks = pickarray->GetSize(); if (numpicks > 0) { LPDIRECT3DRMVISUAL visual; LPDIRECT3DRMFRAMEARRAY framearray; D3DRMPICKDESC pickdesc;

r = pickarray->GetPick(0, &visual, &framearray, &pickdesc); if (r == D3DRM_OK) { framearray->GetElement(framearray->GetSize() - 1, &drag.frame); D3DVECTOR pos; drag.frame->GetPosition(0, &pos); drag.origx = pos.x; drag.origy = pos.y; drag.mousedown.x = point.x; drag.mousedown.y = point.y; visual->Release(); framearray->Release(); ret = TRUE; } } pickarray->Release(); return ret; }

Сначала функция MeshPick() вызывает функцию Pick() интерфейса Direct3DRMViewport. Функции Pick() передается три аргумента. Первые два аргумента определяют точку порта просмотра, которая должна быть проверена. Третий аргумент — это указатель на интерфейс Direct3DRMPickedArray. Этот указатель инициализируется массивом объектов, которые обнаружены в данной точке порта просмотра (даже если один объект полностью скрыт другими).

Интерфейс Direct3DRMPickedArray поддерживает два метода: GetSize() и GetPick(). Функция GetSize() возвращает количество элементов массива. Функция GetPick() возвращает указатель на видимый объект, который был выбран, и указатель на массив указателей на инетрфейс Direct3DRMFrame.

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

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

После того, как структура с необходимыми для операции перетаскивания данными готова, указатели на различные интерфейсы освобождаются. Если был выбран какой-либо объект, функция MeshPick() возвращает TRUE.



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


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

void MeshPickWin::UpdateDrag(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { if (drag.frame) { int x = GetMouseX(); int y =GetMouseY(); D3DVALUE newx = -D3DVALUE(drag.mousedown.x - x) * D3DVALUE(.07) + drag.origx; D3DVALUE newy = D3DVALUE(drag.mousedown.y - y) * D3DVALUE(.07) + drag.origy; drag.frame->SetPosition(0, newx, newy, D3DVALUE(0)); } }

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

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



Функция MoleculeWin::CreateChildren()


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

BOOL MoleculeWin::CreateChildren(LPDIRECT3DRMFRAME frame, int depth) { LPDIRECT3DRMFRAME parent; frame->GetParent(&parent);

D3DVECTOR vector; D3DRMVectorRandom(&vector); frame->SetRotation(parent, vector.x, vector.y, vector.z, D3DVALUE(rand() % 100) / D3DVALUE(1000) + D3DVALUE(.1)); frame->AddVisual(mesh[curdepth - depth]); if (depth > 1) { LPDIRECT3DRMFRAME child; d3drm->CreateFrame(frame, &child);

static int count; count++; D3DVALUE trans = distance[curdepth - depth]; D3DVALUE smalltrans = trans / D3DVALUE(2); D3DVALUE xtrans = (count % 2) ? trans : -trans; D3DVALUE ytrans = (rand() % 2) ? smalltrans : -smalltrans; D3DVALUE ztrans = (rand() % 2) ? smalltrans : -smalltrans; child->SetPosition(frame, xtrans, ytrans, ztrans);

for (int i = 0; i < numchildren; i++) CreateChildren(child, depth - 1); } return TRUE; }

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

D3DVECTOR vector; D3DRMVectorRandom(&vector); frame->SetRotation(parent, vector.x, vector.y, vector.z, D3DVALUE(rand() % 100) / D3DVALUE(1000) + D3DVALUE(.1));

Обратите внимание, что фрейм parent передается функции SetRotation() в ее первом аргументе. Однако сперва с помощью функции D3DRMVectorRandom() выполняется вычисление случайного вектора, который используется для получения следующих трех аргументов функции SetRotation(). При вычислении последнего аргумента функции SetRotation(), определяющего скорость вращения, используется функция rand(). Выражение, применяемое для вычисления скорости обеспечивает случайный выбор значения в диапазоне от медленной до средней скорости.


Затем к фрейму присоединяется соответствующая сетка:

frame->AddVisual(mesh[curdepth - depth]);

Выбор сетки зависит от глубины текущего фрейма в иерархии. Оставшаяся часть функции CreateChildren() выглядит так:

if (depth > 1) { LPDIRECT3DRMFRAME child; d3drm->CreateFrame(frame, &child);

static int count; count++; D3DVALUE trans = distance[curdepth - depth]; D3DVALUE smalltrans = trans / D3DVALUE(2); D3DVALUE xtrans = (count % 2) ? trans : -trans; D3DVALUE ytrans = (rand() % 2) ? smalltrans : -smalltrans; D3DVALUE ztrans = (rand() % 2) ? smalltrans : -smalltrans; child->SetPosition(frame, xtrans, ytrans, ztrans);

for (int i = 0; i < numchildren; i++) CreateChildren(child, depth - 1); }

Выполнение этой части кода зависит от значения параметра depth. Код выполняется, если значение depth больше единицы. Код создает новый дочерний фрейм с помощью функции CreateFrame() интерфейса Direct3DRM. Новый фрейм позиционируется на основе полученных псевдослучайных чисел. Статическая переменная счетчика используется, чтобы при дальнейших вычислениях избежать получения обескураживающе предсказуемых результатов. Вычисленная позиция назначается фрейму функцией SetPosition().

В завершение функция CreateChildren() вызывает сама себя. Используемый для вызова функции цикл зависит от числа дочерних фреймов, которые должны быть присоединены. Обратите внимание, что в качестве второго аргумента CreateChildren() используется выражение depth– 1. Как только значение параметра depth станет равным единице, создание дочерних фреймов прекратится. Если из аргумента, задающего глубину не вычесть единицу, функция будет бесконечно вызывать сама себя.


Функция MoleculeWin::CreateHierarchy()


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

BOOL MoleculeWin::CreateHierarchy() { static LPDIRECT3DRMFRAME mainframe; if (mainframe) { scene->DeleteChild(mainframe); mainframe->Release(); } d3drm->CreateFrame(scene, &mainframe); for (int i = 0;i < numchildren; i++) CreateChildren(mainframe, curdepth);

return TRUE; }

Функция использует статический указатель на фрейм (mainframe) для доступа к корневому фрейму иерархии. Не следует путать этот фрейм с корневым фреймом сцены (scene). Фрейм scene является корневым для всей сцены в целом. Фрейм mainframe является корневым только для иерархии фреймов.

Если указатель mainframe уже инициализирован, фрейм удаляется из сцены с помощью функции DeleteChild() интерфейса Direct3DRMFrame. Это делается, чтобы убрать со сцены предыдущую иерархию фреймов. Затем указатель mainframe инициализируется с помощью функции CreateFrame() интерфейса Direct3DRM. В качестве первого аргумента функции CreateFrame() передается указатель scene. Это указывает на то, что новый фрейм будет потомком фрейма scene.

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



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


Начальная сцена приложения Molecule создается в функции CreateScene(). Код этой функции представлен в листинге 7.1.

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

BOOL MoleculeWin::CreateScene() { // ------- SRAND -------- srand((unsigned)time(NULL));

// ------- СЕТКИ -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER meshbuilder; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL);

for (int i = 0; i < MAXDEPTH; i++) { ScaleMesh(meshbuilder, D3DVALUE(MAXDEPTH - i)); D3DCOLOR clr = meshcolor[i]; D3DVALUE r = D3DRMColorGetRed(clr); D3DVALUE g = D3DRMColorGetGreen(clr); D3DVALUE b = D3DRMColorGetBlue(clr); meshbuilder->SetColorRGB(r, g, b); LPDIRECT3DRMMESH m; meshbuilder->CreateMesh(&m); mesh[i] = m; }

meshbuilder->Release(); meshbuilder = 0;

// -------- ИЕРАРХИЯ ФРЕЙМОВ ------ CreateHierarchy();

// --------НАПРАВЛЕННЫЙ СВЕТ-------- LPDIRECT3DRMLIGHT dlight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &dlight);

LPDIRECT3DRMFRAME dlightframe; d3drm->CreateFrame(scene, &dlightframe); dlightframe->AddLight(dlight); dlightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); dlight->Release(); dlight = 0; dlightframe->Release(); dlightframe = 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() выполняет следующие действия:

Инициализирует генератор случайных чисел.

Создает шесть сеток.

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

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

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


В начале вызывается функция srand() и ей в качестве аргумента передается значение, возвращаемое функцией time(). Таким образом выполняется инициализация генератора случайных чисел. Приложение Molecule использует функцию rand() чтобы добавлять к иерархии фреймов случайные характеристики. Поскольку для инициализации генератора случайных чисел используется число, которое изменяется при каждом запуске приложения, атрибуты иерархии фреймов (такие, как оси вращения и скорость) будут меняться при каждом запуске программы.

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

D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER meshbuilder; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL);

Для загрузки сетки из ресурсов программы используется экземпляр интерфейса Direct3DRMMeshBuilder. В дальнейшем для доступа к новой сетке используется указатель meshbuilder. Потом создаются шесть экземпляров интерфейса Direct3DRMMesh:

for (int i = 0; i < MAXDEPTH; i++) { ScaleMesh(meshbuilder, D3DVALUE(MAXDEPTH - i)); D3DCOLOR clr = meshcolor[i]; D3DVALUE r = D3DRMColorGetRed(clr); D3DVALUE g = D3DRMColorGetGreen(clr); D3DVALUE b = D3DRMColorGetBlue(clr); meshbuilder->SetColorRGB(r, g, b); meshbuilder->CreateMesh(&mesh[i]); }

Для создания сеток используется цикл. Масштаб и цвет сетки зависит от итерации цикла. Цвета каждой из создаваемых сеток содержатся в массиве meshcolor. Сразу после назначения цвета выполняется создание экземпляра интерфейса Direct3DRMMesh с помощью функции CreateMesh() интерфейса Direct3DRMMeshBuilder.

Третьим шагом является создание иерархии фреймов:

CreateHierarchy();

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

На двух заключительных этапах создается источник света и порт просмотра. Мы опустим обсуждение этих фрагментов кода, поскольку источники света были подробно рассмотрены в главе 6, а этап создания порта просмотра обсуждался в главе 4 и будет более подробно рассмотрен в главе 9.


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


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

BOOL MorphPlayWin::CreateScene() { // --------НАПРАВЛЕННЫЙ И РАССЕЯННЫЙ СВЕТ-------- 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)); alight->Release(); alight = 0; dlight->Release(); dlight = 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(), рассмотренных нами в этой книге, версия из класса MorphPlayWin выполняет только два действия. На первом этапе выполняется создание двух источников света. На втором этапе создается порт просмотра.

Поскольку к сцене не добавляется никаких видимых объектов, сразу после запуска приложение отображает пустое окно. Чтобы загрузить последовательность трансформаций, следует воспользоваться командой Open меню File.



Функция MorphPlayWin::InitMorphSequence()


Функция InitMorphSequence() использует унаследованные от класса MorphWin функции для начала новой последовательности трансформаций:

BOOL MorphPlayWin::InitMorphSequence(const CString& filename) { if (frame) frame->DeleteVisual(mesh); else { d3drm->CreateFrame(scene, &frame); frame->AddMoveCallback(UpdateDrag, NULL); frame->AddMoveCallback(UpdateMorph, this); }

if (LoadMorphSequence(filename) == FALSE) return FALSE;

DWORD targets = GetNumMorphTargets(); for (DWORD i = 0; i < targets; i++) AddMorphKey(i, D3DVALUE(i)); maxmorphtime = D3DVALUE(targets - 1); morphtime = D3DVALUE(0.0); mesh = GetMorphMesh(); mesh->SetGroupColorRGB(0, D3DVALUE(.67), D3DVALUE(.82), D3DVALUE(.94)); frame->AddVisual(mesh);

return TRUE; }

Сначала функция проверяет значение переменной frame. Если указатель frame не инициализирован, функция создает новый фрейм и устанавливает две функции обратного вызова: UpdateDrag() и UpdateMorph(). Если указатель frame уже был инициализирован при предыдущем вызове функции, текущая сетка удаляется из сцены с помощью функции DeleteVisual() интерфейса Direct3DRMFrame.

Затем вызывается функция LoadMorphSequence(). Если она завершается неудачно (возвращает FALSE), немедленно осуществляется возврат из функции InitMorphSequence().

Потом в цикле устанавливаются ключи трансформации для каждого шага. Кроме того, инициализируются члены данных maxmorphtime и morphtime.

В конце вызывается функция MorphWin::GetMorphMesh() чтобы получить указатель на вновь созданную трансформируемую сетку. Цвет сетки изменяется и сетка добавляется к сцене.



Функция MorphPlayWin::OnFileOpen()


Выбор команды Open меню File приводит к тому, что MFC вызывает функцию OnFileOpen(). Код этой функции показан ниже:

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

Функция использует класс MFC CFileDialog для представления диалогового окна выбора файлов в котором будут отображаться только файлы с расширениями MRF и X. Если пользователь выбирает файл, или вводит его имя, выполняется вызов функции InitMorphSequence() и имя выбранного файла передается ей как аргумент.



Функция MorphPlayWin::UpdateMorph()


Воспроизведением последовательности трансформаций управляет функция обратного вызова UpdateMorph():

void MorphPlayWin::UpdateMorph(LPDIRECT3DRMFRAME, void* ptr, D3DVALUE) { MorphPlayWin* win = (MorphPlayWin*)ptr; const D3DVALUE maxtime = win->maxmorphtime; const int morphspeed = win->morphspeed; const D3DVALUE morphtimeinc = win->morphtimeinc;

if (morphspeed == MORPH_FORWARD) { morphtime += morphtimeinc; if (morphtime > maxtime) morphtime = D3DVALUE(0); } else if (morphspeed == MORPH_REVERSE) { morphtime -= morphtimeinc; if (morphtime < D3DVALUE(0)) morphtime = maxtime; } else if (morphspeed == MORPH_BOTH) { static BOOL forward = TRUE; if (forward) morphtime += morphtimeinc; else morphtime -= morphtimeinc; if (morphtime < D3DVALUE(0) || morphtime > maxtime) forward= 1 - forward; }

win->SetMorphTime(morphtime); }

Значение времени для последовательности трансформаций вычисляется на основе переменной morphspeed. Если значение переменной morphspeed равно MORPH_FORWARD, то время в последовательности трансформаций непрерывно увеличивается, пока не достигнет максимально возможного значения. После этого отсчет времени снова начинается с нуля. Текущее время трансформации хранится в переменной morphtime.

Если значение переменной morphspeed равно MORPH_REVERSE, новое значение времени вычисляется путем вычитания заданного значения из переменной morphtime. Константа MORPH_BOTH означает, что последовательность трансформаций сначала воспроизводится в прямом направлении, а затем— в обратном.

После вычисления нового значения переменной morphtime вызывается функция MorphWin::SetMorphTime(). В результате выполняется вычисление новых данных вершин и производится обновление трансформируемой сетки.



Функция MorphWin::AddMorphKey()


Функция AddMorphKey() связывает шаг трансформации с заданным моментом времени анимации. В качестве аргументов функция получает индекс шага и временную метку.

BOOL MorphWin::AddMorphKey(DWORD target, D3DVALUE time) { if (target < 0 || target > nummorphtargets) return FALSE;

for (DWORD i = 0; i < nummorphvertices; i++) { D3DVECTOR& pos = morphmeshdata[target][i].position; posanimation[i]->AddPositionKey(time, pos.x, pos.y, pos.z);

D3DVECTOR& norm = morphmeshdata[target][i].normal; normanimation[i]->AddPositionKey(time, norm.x, norm.y, norm.z); } return TRUE; }

Сначала выполняется проверка допустимости индекса шага трансформации. Если индекс выходит за допустимые пределы, функция возвращает FALSE. В ином случае полученный индекс шага трансформации используется для добавления позиционных ключей в объект анимации местоположения и объект анимации нормали каждой из вершин. Для добавления ключей применяется функция AddPositionKey() интерфейса Direct3DRMAnimation. Обратите внимание, что в качестве первого аргумента функции AddPositionKey() используется параметр time.



Функция MorphWin::CreateAnimations()


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

BOOL MorphWin::CreateAnimations() { static int vertexcount; ReleaseAnimations(vertexcount); vertexcount = nummorphvertices;

posanimation = new LPDIRECT3DRMANIMATION[nummorphvertices]; posframe = new LPDIRECT3DRMFRAME[nummorphvertices];

normanimation = new LPDIRECT3DRMANIMATION[nummorphvertices]; normframe = new LPDIRECT3DRMFRAME[nummorphvertices];

for (DWORD vert = 0; vert < nummorphvertices; vert++) { d3drm->CreateAnimation(&posanimation[vert]); posanimation[vert]->SetOptions(D3DRMANIMATION_LINEARPOSITION | D3DRMANIMATION_POSITION); d3drm->CreateFrame(scene, &posframe[vert]); posanimation[vert]->SetFrame(posframe[vert]); d3drm->CreateAnimation(&normanimation[vert]); normanimation[vert]->SetOptions(D3DRMANIMATION_LINEARPOSITION | D3DRMANIMATION_POSITION); d3drm->CreateFrame(scene, &normframe[vert]); normanimation[vert]->SetFrame(normframe[vert]); } return TRUE; }

Сначала с помощью функции ReleaseAnimations() выполняется освобождение любых выделенных ранее ресурсов. Количество вершин сохраняется в статической переменной vertexcount для последующих вызовов функции ReleaseAnimations().

Затем инициализируются массивы posanimation, posframe, normanimation и normframe. В цикле создается по два экземпляра объекта Direct3DRMAnimation для каждой вершины. Объекты анимации в массиве posanimation будут использованы для вычисления местоположения вершин, а объекты анимации из массива normanimation применяются для вычисления нормалей вершин.



Функция MorphWin::GetMorphMesh()


Функция GetMorphMesh() просто возвращает указатель на трансформируемую сетку. Она определена в объявлении класса следующим образом:

LPDIRECT3DRMMESH GetMorphMesh() { return morphmesh; }

Данная функция позволяет классам, наследуемым от MorphWin отображать сетку и изменять ее параметры. Как это делается мы увидим ниже.



Функция MorphWin::GetNumMorphTargets()


Функция GetNumMorphTargets() определена в объявлении класса MorphWin:

DWORD GetNumMorphTargets() { return nummorphtargets; }

Функция просто возвращает значение, хранящееся в переменной класса nummorphtargets.



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


Функция LoadMeshes() отвечает за загрузку сеток из указанного файла. Она должна проверить, что каждая сетка содержит одно и тоже количество вершин, а также извлечь и сохранить данные вершин. По этой причине код функции получился весьма длинным (и уродливым). Вы можете взглянуть на него в листинге 8.3.

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

BOOL MorphWin::LoadMeshes(const CString& filename) { for (DWORD i = 0; i < nummorphtargets; i++) delete [] morphmeshdata[i]; nummorphtargets = 0;

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

BOOL load_ok = TRUE; for (i = 0; i <MAXMORPHTARGETS && load_ok; i++) { CString msg; HRESULT r; LPDIRECT3DRMMESHBUILDER builder; d3drm->CreateMeshBuilder(&builder);

r = builder->Load((void*)(LPCTSTR)filename, (void*)morphmeshname[i], D3DRMLOAD_FROMFILE | D3DRMLOAD_BYNAME, NULL, NULL); load_ok = r == D3DRM_OK; if (r == D3DRMERR_FILENOTFOUND) { TRACE("file not found\n"); CString msg = filename + ": file not found"; AfxMessageBox(msg); morphing = FALSE; } if (!load_ok) goto nomoremeshes;

D3DVALUE scale; if (i == 0) scale = ScaleMesh(builder, D3DVALUE(25)); else builder->Scale(scale, scale, scale);

builder->SetQuality(D3DRMRENDER_FLAT);

LPDIRECT3DRMMESH mesh; builder->CreateMesh(&mesh);

unsigned vcount, fcount; DWORD ds; mesh->GetGroup(0, &vcount, &fcount, 0, &ds, 0);

if (i == 0) nummorphvertices = vcount; else if (vcount != nummorphvertices && load_ok) { TRACE("invalid vertex count\n"); AfxMessageBox("Invalid vertex count"); morphing = FALSE; return FALSE; }

morphmeshdata[i] = new D3DRMVERTEX[nummorphvertices]; r = mesh->GetVertices(0, 0, nummorphvertices, morphmeshdata[i]); if (r != D3DRM_OK) TRACE("mesh->GetVertices() failed\n");

msg.Format("Mesh %d - %d vertices, %d faces", i+1, vcount, fcount); SetWindowText(msg);

if (i == 0) morphmesh = mesh; else { mesh->Release(); mesh = 0; }

nomoremeshes:

builder->Release(); builder = 0;

} nummorphtargets = i - 1; morphing = TRUE;

return TRUE; }

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

Затем функция в цикле пытается загрузить 26 шагов трансформации. Цикл прекращается если очередная сетка не будет найдена, либо если обнаруженная сетка содержит другое количество вершин. Когда сетка загружена, функция GetVertices() интерфейса Direct3DRMMesh извлекает данные вершин сетки. Кроме того, в заголовке окна отображается новое сообщение. Функция также инициализирует переменные класса nummorphvertices и nummorphtargets.



Функция MorphWin::LoadMorphSequence()


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

BOOL MorphWin::LoadMorphSequence( const CString& filename ) { CString windowtext; GetWindowText(windowtext);

CString txt = "Loading: " + filename; SetWindowText(txt);

BOOL ret = FALSE; if (LoadMeshes(filename)) if (CreateAnimations()) ret = PrepareMorphVertices();

SetWindowText(windowtext);

return ret; }

Сначала функция получает текст, который в данный момент отображается в заголовке окна. Для этого используется функция MFC GetWindowText(), а полученный текст сохраняется в объекте windowtext. Затем текст в заголовке окна заменяется на строку с имененм загружаемого файла. Для отображения этой строки используется функция SetWindowText(). Обратите внимание, что перед возвратом из функции LoadMorphSequence() функция SetWindowText() вызывается еще раз— теперь для восстановления оригинального текста заголовка окна.

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



Функция MorphWin::PrepareMorphVertices()


Функция PrepareMorphVertices() выделяет память для массива, который будет использоваться для хранения вычисленных данных вершин:

BOOL MorphWin::PrepareMorphVertices() { if (morphvertex) { delete [] morphvertex; morphvertex = 0; }

morphvertex = new D3DRMVERTEX[nummorphvertices];

return TRUE; }

Как и в двух предыдущих функциях, в случае обнаружения ранее выделенных ресурсов сначала выполняется их освобождение, после чего выделяются новые ресурсы.



Функция MorphWin::SetMorphTime()


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

BOOL MorphWin::SetMorphTime(D3DVALUE time) { for (DWORD v = 0; v < nummorphvertices; v++) { posanimation[v]->SetTime(time); D3DVECTOR pos; posframe[v]->GetPosition(scene, &pos); morphvertex[v].position = pos;

normanimation[v]->SetTime(time); D3DVECTOR norm; normframe[v]->GetPosition(scene, &norm); morphvertex[v].normal = norm; } morphmesh->SetVertices(0, 0, nummorphvertices, morphvertex);

return TRUE; }

Для перебора всех вершин используется цикл. Функция SetTime() интерфейса Direct3DRMAnimation применяется для обновления анимационных последовательностей для местоположения вершин и для нормалей вершин. Затем фрейм, присоединенный к каждому объекту анимации используется для присваивания значений элементам массива morphvertex.

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



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


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

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

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

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

// --------- СВЕТ -------- 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->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;

return TRUE; }

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

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

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

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

Обратите внимание, что порты просмотра не создаются. Как вы увидите, код, относящийся к портам просмотра, был перемещен в класс RMWin. Функция CreateScene() подготавливает сцену, но не определяет, каким образом эта сцена будет показана зрителю.

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

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

Затем создаются два источника света и присоединяются к собственному фрейму.



Функция OnActivate()


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

Интерфейс Direct3DRMWinDevice поддерживает те же самые объекты, что и интерфейс Direct3DRMDevice, поэтому для решения нашей проблемы мы можем использовать существующий интерфейс Direct3DRMDevice для получения интерфейса Direct3DRMWinDevice.

В функции RMWin::OnActivate() запрашивается интерфейс WinDevice и вызывается его функция HandleActivate():

void RMWin::OnActivate(UINT state, CWnd* other, BOOL minimize) { LPDIRECT3DRMWINDEVICE windev; if (device) { if (device->QueryInterface(IID_IDirect3DRMWinDevice, (void**)&windev) == 0) { if (windev->HandleActivate((unsigned short) MAKELONG((WORD)state,(WORD)0)) != 0) AfxMessageBox("windev->HandleActivate() failure"); windev->Release(); } else AfxMessageBox("device->QueryInterface(WinDevice) failure"); } CFrameWnd::OnActivate(state, other, minimize); }

Мы используем GUID IID_IDirect3DRMWinDevice, чтобы указать функции QueryInterface(), что мы ищем указатель на интерфейс Direct3DRMWinDevice. После того, как указатель на интерфейс получен, можно вызывать функцию HandleActivate(). В случае успешного завершения функции QueryInterface() и HandleActivate() возвращают ноль.



Функция OnCreate()


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

int RMWin::OnCreate(LPCREATESTRUCT lpCreateStruct) { HRESULT r; r = Direct3DRMCreate(&d3drm); if (r != D3DRM_OK) { TRACE("failed to create D3DRM object\n"); return -1; } return 0; }

Для инициализации указателя на интерфейс Direct3DRM вызывается функция Direct3DRMCreate(). Переменная d3drm применяется во многих местах программы для вызова различных функций интерфейса Direct3DRM.

Затем проверяется значение, возвращенное функцией Direct3DRMCreate(). Любое значение, отличное от D3DRM_OK свидетельствует об ошибке. В случае возникновения ошибки, отображается диагностическое сообщение и функция возвращает–1. Это указывает MFC на необходимость прервать создание окна. Если все прошло успешно, функция возвращает 0.

СОВЕТ Использование макроопределения TRACE. Если (или когда) при работе с программами с CD-ROM у вас возникнут проблемы, убедитесь, что у вас разрешено использование макроопределения TRACE и вы используете отладочную версию DirectX. И примеры с компакт-диска и отладочная версия DirectX будут выводить сообщения об ошибках, только когда код, откомпилирован в режиме DEBUG, и исполняется непосредственно из среды Visual C++.



Функция OnDestroy()


Перед завершением работы приложения MFC вызывается функция OnDestroy(). Это хорошее место для освобождения созданных нами указателей. Класс RMWin предоставляет версию функции OnDestroy(), которая освобождает стандартные интерфейсы Direct3D. Код функции приведен ниже:

void RMWin::OnDestroy() { if (scene) { scene->Release(); scene = 0;

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

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

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



Функция OnEraseBkgnd()


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

MFC предоставляет функцию CWnd::OnEraseBkgnd(), позволяющую переопределить заданное по умолчанию поведение Windows. Класс RMWin предоставляет версию функции OnEraseBkgnd(), использующую для закраски клиентской области текущий цвет фона Direct3D. Код функции выглядит так:

BOOL RMWin::OnEraseBkgnd(CDC* pDC) { COLORREF bgcolor; if (scene) { D3DCOLOR scenecolor = scene->GetSceneBackground(); bgcolor = D3DCOLOR_2_COLORREF(scenecolor); } else bgcolor = RGB(0,0,0);

CBrush br(bgcolor); CRect rc; GetClientRect(&rc); pDC->FillRect(&rc, &br); return TRUE; }

Функция объявляет экземпляр типа COLORREF и присваивает ему текущий цвет фона Direct3D. Функция D3DCOLOR_2_COLORREF() преобразует значение типа D3DCOLOR в значение типа COLORREF. Позднее мы обсудим эту функцию. Если фрейм сцены еще не был создан, используется черный цвет.

Затем экземпляр типа COLORREF используется для создания объекта CBrush, и определяются размеры клиентской области окна. Функция CDC::FillRect() применяется для задания цвета клиентской области окна. Завершая работу, функция возвращает TRUE, чтобы уведомить MFC об успешном завершении работы.



Функция OnIdle()


Интерфейс Direct3DRM предоставляет функцию Tick() применяемую, чтобы сообщить Direct3D о необходимости обновить все объекты сцены, выполнить визуализацию и вывести на экран полученный результат. Вызов функции Tick() осуществляет приложение, использующее Direct3D.

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

BOOL RMApp::OnIdle(LONG) { ASSERT(RMWin::d3drm); RMWin::d3drm->Tick(D3DVALUE(1)); return TRUE; }

В начале функции проверяется была ли инициализирована переменная RMWin::d3drm. Если да, то вызывается функция Tick(). Затем функция возвращает TRUE чтобы уведомить MFC о необходимости вызова функции OnIdle() в дальнейшем (если будет возвращено FALSE то MFC больше не будет вызывать функцию OnIdle()).

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

Второй способ контролировать скорость работы программы — использование аргумента функции Tick(). Передача аргумента, равного 1.0, означает, что система выполнит полное обновление и отобразит результат. Передача меньших значений приводит к тому, что Direct3D выполняет промежуточное обновление анимации в соответствии с переданным в качестве аргумента значением.

Например, если создать сцену, содержащую объект поворачивающийся за каждый шаг анимации на четверть оборота, и вызвать функцию Tick() с аргументом 0.5, потребуется два обновления системы, чтобы объект повернулся на четверть оборота. Аналогичным образом, если аргумент функции Tick() будет равен 2, анимация будет выполняться с удвоенной скоростью.

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

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



Функция OnPaint()


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

void RMWin::OnPaint() { static BOOL first = TRUE; if (first) { first = FALSE; BOOL ok = CreateDevice(); if (!ok) PostQuitMessage(0); }

if (GetUpdateRect(NULL, FALSE) == FALSE) return;

if (device) { LPDIRECT3DRMWINDEVICE windev; PAINTSTRUCT ps; BeginPaint(&ps); if (device->QueryInterface(IID_IDirect3DRMWinDevice, (void**)&windev) == 0) { if (windev->HandlePaint(ps.hdc) != 0) AfxMessageBox("windev->HandlePaint() failure"); windev->Release(); } else AfxMessageBox("Failed to create Windows device to handle WM_PAINT"); EndPaint(&ps); } }

Статическая переменная flag используется, чтобы определить вызывается ли функция OnPaint() в первый раз. Если функция CreateDevice() возвращает FALSE, программа прекращает работу. Переменная flag устанавливается в TRUE, чтобы функция CreateDevice() вызывалась только один раз.

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

Остальная часть кода аналогична функции OnActivate(). Функция QueryInterface() используется для получения указателя на интерфейс Direct3DRMWinDevice, после чего полученный указатель используется для вызова функции HandlePaint().



Функция OnSize()


Функция OnSize() вызывается MFC при получении сообщения WM_SIZE. Это сообщение уведомляет о том, что пользователь изменил размеры окна. Класс RMWin предоставляет функцию OnSize() для изменения параметров устройства и порта просмотра в соответствии с новыми размерами окна.

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

void RMWin::OnSize(UINT type, int cx, int cy) { CFrameWnd::OnSize(type, cx, cy); if (!device) return;

int width = cx; int height = cy; if (width && height) { int view_width = viewport->GetWidth(); int view_height = viewport->GetHeight(); int dev_width = device->GetWidth(); int dev_height = device->GetHeight();

if (view_width == width && view_height == height) return; int old_dither = device->GetDither(); D3DRMRENDERQUALITY old_quality = device->GetQuality(); int old_shades = device->GetShades();

viewport->Release(); device->Release(); d3drm->CreateDeviceFromClipper(clipper, GetGUID(), width, height, &device);

device->SetDither(old_dither); device->SetQuality(old_quality); device->SetShades(old_shades);

width = device->GetWidth(); height = device->GetHeight(); d3drm->CreateViewport(device, camera, 0, 0, width, height, &viewport); } }

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

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



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


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

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

BOOL OrbStarWin::CreateScene() { //-------- СЕТКА ЗВЕЗДЫ -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_STARMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER starbuilder; d3drm->CreateMeshBuilder(&starbuilder); starbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); starbuilder->SetColorRGB(D3DVALUE(1.0), D3DVALUE(0.0), D3DVALUE(0.0)); ScaleMesh(starbuilder, D3DVALUE(20));

//--------- ФРЕЙМ ЗВЕЗДЫ -------- LPDIRECT3DRMFRAME starframe; d3drm->CreateFrame(scene, &starframe); starframe->SetRotation(scene, D3DVALUE(1.0), D3DVALUE(0.0),D3DVALUE(0.0), D3DVALUE(0.1)); starframe->AddVisual(starbuilder); starframe->AddMoveCallback(MoveStar, NULL); starframe->Release(); starframe = 0; starbuilder->Release(); starbuilder = 0;

//--------- СЕТКА СФЕРЫ -------- resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER spherebuilder; d3drm->CreateMeshBuilder(&spherebuilder); spherebuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); spherebuilder->SetPerspective(TRUE); ScaleMesh(spherebuilder, D3DVALUE(25));

//---------- ТЕКСТУРА ДЛЯ СФЕРЫ ------ LPDIRECT3DRMTEXTURE texture; HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE( IDR_TRANSTEXTURE), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture); texture->SetDecalTransparency(TRUE); spherebuilder->SetTexture(texture); texture->Release(); texture = 0;

//---------- НАЛОЖЕНИЕ ТЕКСТУРЫ ДЛЯ СФЕРЫ -------- D3DRMBOX box; spherebuilder->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(1.0), D3DVALUE(0.0), // ось z D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0), // ось y D3DVALUE(0.5), D3DVALUE(0.5), // начало координат D3DDivide(1,width), D3DDivide(1,height), // масштаб &wrap); wrap->Apply(spherebuilder); wrap->Release(); wrap = 0;

//-------- ФРЕЙМ СФЕРЫ ---------- LPDIRECT3DRMFRAME sphereframe; d3drm->CreateFrame(scene, &sphereframe); sphereframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.1)); sphereframe->AddVisual(spherebuilder); sphereframe->AddMoveCallback(MoveSphere, NULL); sphereframe->Release(); sphereframe = 0; spherebuilder->Release(); spherebuilder = 0;

//----------- СВЕТ -------- LPDIRECT3DRMLIGHT light1, light2; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.8),D3DVALUE(0.8), D3DVALUE(0.8), &light1); d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(0.9), D3DVALUE(0.9), D3DVALUE(0.9), &light2);

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

lightframe->AddLight(light1); lightframe->AddLight(light2);

lightframe->Release(); lightframe = 0; light1->Release(); light1 = 0; light2->Release(); light2 = 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; }

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

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

Создание фрейма звезды.

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

Создание текстуры с прозрачными участками.

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

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

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

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

Сначала создается сетка звезды:

D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_STARMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER starbuilder; d3drm->CreateMeshBuilder(&starbuilder); starbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); starbuilder->SetColorRGB(D3DVALUE(1.0), D3DVALUE(0.0), D3DVALUE(0.0)); ScaleMesh(starbuilder, D3DVALUE(20));

Как и в других демонстрационных приложениях, для загрузки сетки из ресурсов программы используется функция Load() интерфейса Direct3DRMMeshBuilder. Как только сетка загружена, вызывается функция SetColorRGB() для окрашивания всей сетки в красный цвет. Затем для масштабирования сетки в соответствии с заданным размером применяется функция ScaleMesh().

Теперь создается фрейм (с именем starframe) и ему назначаются атрибуты вращения:

LPDIRECT3DRMFRAME starframe; d3drm->CreateFrame(scene, &starframe); starframe->SetRotation(scene, D3DVALUE(1.0), D3DVALUE(0.0),D3DVALUE(0.0), D3DVALUE(0.1)); starframe->AddVisual(starbuilder); starframe->AddMoveCallback(MoveStar, NULL); starframe->Release(); starframe = 0; starbuilder->Release(); starbuilder = 0;

Новый фрейм получает атрибуты вращения, задающие его вращение вокруг оси X. Как вы увидите, эти атрибуты являются временными, поскольку мы используем функцию обратного вызова, для изменения атрибутов вращения во время работы приложения. Созданный ранее конструктор сеток присоединяется к новому фрейму с помощью функции AddVisual(). Перед освобождением указателей starframe и starbuilder выполняется установка функции обратного вызова MoveStar(). На код функции MoveStar() мы взглянем чуть позже.


После выполнения описанных действий выполняется освобождение указателей starframe и starbuilder.

Далее создается и загружается сетка сферы:

resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER spherebuilder; d3drm->CreateMeshBuilder(&spherebuilder); spherebuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); spherebuilder->SetPerspective(TRUE); ScaleMesh(spherebuilder, D3DVALUE(25));

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

Этап 4 — это создание текстуры для сферы:

LPDIRECT3DRMTEXTURE texture; HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE( IDR_TRANSTEXTURE), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture); texture->SetDecalTransparency(TRUE); spherebuilder->SetTexture(texture); texture->Release(); texture = 0;

Как и в других приложениях, для создания текстуры используется функция LoadTextureFromResource(). Потом, чтобы разрешить прозрачность текстуры вызывается функция SetDecalTransparency(). Мы не определяем прозрачный цвет, поэтому по умолчанию как прозрачный будет восприниматься черный цвет. Для привязки текстуры к сетке сферы применяется функция SetTexture() интерфейса Direct3DRMMeshBuilder. После выполнения этих действий указатель texture освобождается.

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

D3DRMBOX box; spherebuilder->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(1.0), D3DVALUE(0.0), // ось z D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0), // ось y D3DVALUE(0.5), D3DVALUE(0.5), // начало координат D3DDivide(1,width), D3DDivide(1,height), // масштаб &wrap); wrap->Apply(spherebuilder); wrap->Release(); wrap = 0;



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

На шестом шаге создается фрейм для сетки сферы:

LPDIRECT3DRMFRAME sphereframe; d3drm->CreateFrame(scene, &sphereframe); sphereframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.1)); sphereframe->AddVisual(spherebuilder); sphereframe->AddMoveCallback(MoveSphere, NULL); sphereframe->Release(); sphereframe = 0; spherebuilder->Release(); spherebuilder = 0;

Новому фрейму присваиваются атрибуты вращения, но, также как и для фрейма, созданного на этапе 2, для изменения этих атрибутов используется функция обратного вызова. С помощью функции AddVisual() к фрейму присоединяется сетка сферы и устанавливается функция обратного вызова MoveSphere(). Затем выполняется освобождение указателей sphereframe и spherebuilder.

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


Функция Render()


Функция Render() — это последняя (и простейшая) функция класса RMWin, которую мы рассмотрим. Функция Render() объявлена, но не определена:

virtual void Render() = 0;

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


Обратите внимание, как восстанавливается поверхность меню видеорежимов:

if (menusurf->IsLost() == DDERR_SURFACELOST) { TRACE("Restoring menusurf...\n"); menusurf->Restore(); UpdateMenuSurface(); }

Если произошла потеря поверхности, вызывается функция Restore(). Она выполняет восстановление памяти поверхности, а вызов функции UpdateMenuSurface() необходим для восстановления содержимого поверхности.

Затем стирается содержимое поверхности backsurf:

DDBLTFX bltfx; memset(&bltfx, 0, sizeof(bltfx)); bltfx.dwSize = sizeof(bltfx); bltfx.dwFillColor = 0; backsurf->Blt(0, 0, 0, DDBLT_COLORFILL | DDBLT_WAIT, &bltfx);

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

Теперь можно нарисовать часть сцены, относящуюся к Direct3D:

scene->Move(D3DVALUE(1.0)); viewport->Clear(); viewport->Render(scene); device->Update();

Функция Move() интерфейса Direct3DRMFrame обновляет внутренние значения иерархии фреймов и выполняет вызов любых функций обратного вызова, выполняющих перемещение входящих в иерархию фреймов. Вызов функций интерфейса Direct3DRMViewport (Clear() и Render()) очищает содержимое порта просмотра (это не оказывает никакого действия на наши поверхности) и вычисляет новое выводимое изображение. Функция Update() интерфейса Direct3DRMDevice копирует сформированное изображение на поверхность backsurf. Это может ускользнуть от вашего взора, поскольку поверхность backsurf не упоминается в данном фрагменте кода. Однако, следует вспомнить, что поверхность backsurf была использована при создании устройства Direct3D. Эта связь объясняет, почему сформированное изображение передается функцией Update() непосредственно на поверхность backsurf.

Затем поверх сформированного Direct3D изображения выводится поверхность для вывода FPS:



UpdateFPSSurface(); if (displayfps) { DWORD w, h, d; GetCurDisplayModeDims(w, h, d); backsurf->BltFast(w - fpsrect.right, h - fpsrect.bottom, fpssurf, &fpsrect, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT); }

Вызов функции UpdateFPSSurface() обновляет содержимое поверхности fpssurf. Затем, если значение логической переменной displayfps равно TRUE, содержимое поверхности копируется на поверхность backsurf функцией BltFast() (BltFast() — это оптимизированная версия функции Blt()).

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

Потом на поверхность backsurf копируется поверхность меню видеорежимов:

backsurf->BltFast(0, 0, menusurf, &menurect, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT);

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

В конце функции содержимое поверхности backsurf перемещается на первичную поверхность операцией переключения страниц:

primsurf->Flip(0, DDFLIP_WAIT);

Этот вызов функции делает видимым содержимое поверхности backsurf.


Функция RMWin::ConfigViewport()


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

void RMWin::ConfigViewport(LPDIRECT3DRMFRAME camera, int view) { if (view == VIEWPORT_FRONT) { camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); camera->SetOrientation(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); } else if (view == VIEWPORT_LEFT) { camera->SetPosition(scene, D3DVALUE(-50), D3DVALUE(0), D3DVALUE(0)); camera->SetOrientation(scene, D3DVALUE(1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); } else if (view == VIEWPORT_RIGHT) { camera->SetPosition(scene, D3DVALUE(50), D3DVALUE(0), D3DVALUE(0)); camera->SetOrientation(scene, D3DVALUE(-1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); } else if (view == VIEWPORT_TOP) { camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(50), D3DVALUE(0)); camera->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0), D3DVALUE(1)); } }

Для позиционирования фрейма используются функции SetPosition() и SetOrientation(). Местоположение и ориентация зависят от значения параметра view.

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



Функция RMWin::CreateDevice()


Изменения, внесенные в класс RMWin, не ограничиваются добавлением новых переменных и функций. Кроме того, был изменен ряд функций, одна из которых— CreateDevice(). Функция CreateDevice() отвечает за создание нескольких ключевых элементов программ, использующих Direct3D. Версия CreateDevice() используемая в приложении MultiView представлена в листинге 9.6.

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

BOOL RMWin::CreateDevice() { HRESULT r;

r = DirectDrawCreateClipper(0, &clipper, NULL); if (r != D3DRM_OK) { AfxMessageBox("DirectDrawCreateClipper() failed"); return FALSE; }

r = clipper->SetHWnd(NULL, m_hWnd); if (r != DD_OK) { AfxMessageBox("clipper->SetHWnd() failed"); return FALSE; }

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

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

device->SetQuality(D3DRMRENDER_GOURAUD);

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

switch (bpp) { case 1: device->SetShades(4); d3drm->SetDefaultTextureShades(4); device->SetDither(TRUE); break; case 8: // ... 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; } d3drm->CreateFrame(NULL, &scene);

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

d3drm->CreateFrame(scene, &camera1); ConfigViewport(camera1, view1setting);

d3drm->CreateFrame(scene, &camera2); ConfigViewport(camera2, view2setting);

d3drm->CreateFrame(scene, &camera3); ConfigViewport(camera3, view3setting);

CreateViewports();

return TRUE; }

<
/p> Вместо того, чтобы обсуждать весь код функции, мы сосредоточимся на внесенных изменениях. Нас интересует завершающая часть кода функции, следующая за инициализацией указателя на фрейм scene:

if (CreateScene() == FALSE) { AfxMessageBox("CreateScene() failed"); return FALSE; } d3drm->CreateFrame(scene, &camera1); ConfigViewport(camera1, view1setting); d3drm->CreateFrame(scene, &camera2); ConfigViewport(camera2, view2setting); d3drm->CreateFrame(scene, &camera3); ConfigViewport(camera3, view3setting); CreateViewports();

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

Если функция CreateScene() завершается успешно, функция CreateDevice() инициализирует три фрейма: camera1, camera2 и camera3. Эти фреймы используются для создания и размещения трех используемых в приложении портов просмотра. После создания каждого из фреймов, он передается функции ConfigViewport() вместе с целым числом, хранящим конфигурацию порта просмотра. Функция ConfigViewport() позиционирует указанный фрейм, согласно значению, переданному во втором аргументе. При инициализации эти значения устанавливаются следующим образом:

view1setting = VIEWPORT_FRONT; view2setting = VIEWPORT_LEFT; view3setting = VIEWPORT_TOP;

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

После инициализации всех трех фреймов и вызова для каждого из них функции ConfigViewport(), выполняется действительное создание портов просмотра функцией CreateViewports().


Функция RMWin::CreateViewports()


Функция CreateViewports() создает три используемых в приложении порта просмотра:

void RMWin::CreateViewports() { int newwidth = device->GetWidth(); int newheight = device-7gt;GetHeight(); int onethird = newwidth / 3; int halfheight = newheight / 2; d3drm->CreateViewport(device, camera1, 0, 0, onethird * 2, newheight, &viewport1); d3drm->CreateViewport(device, camera2, onethird * 2, 0, onethird, halfheight, &viewport2); d3drm->CreateViewport(device, camera3, onethird * 2, halfheight, onethird, halfheight, &viewport3); }

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



Функция RMWin::OnSize()


Функция OnSize() вызывается при изменении размеров окна. Это важно, поскольку размер устройства Direct3D не может быть изменен. Следовательно, в случае изменения размеров окна функция OnSize() должна уничтожить устройство и создать его заново. Поскольку порты просмотра присоединены к устройству, они также должны быть уничтожены и созданы заново. Функция OnSize() приложения MultiView выглядит следующим образом:

void RMWin::OnSize(UINT type, int cx, int cy) { CFrameWnd::OnSize(type, cx, cy);

if (!device) return;

int newwidth = cx; int newheight = cy;

if (newwidth && newheight) { int old_dither = device->GetDither(); D3DRMRENDERQUALITY old_quality = device->GetQuality(); int old_shades = device->GetShades(); viewport1->Release(); viewport2->Release(); viewport3->Release(); device->Release(); d3drm->CreateDeviceFromClipper(clipper, GetGUID(), newwidth, newheight, &device);

device->SetDither(old_dither); device->SetQuality(old_quality); device->SetShades(old_shades);

CreateViewports(); } }

Сначала функция сохраняет текущие параметры устройства. Эти параметры будут использованы позднее для настройки вновь созданного устройства. Для получения параметров устройства применяются функции GetDither(), GetQuality() и GetShades() интерфейса Direct3DRMDevice.

Затем освобождаются все три порта просмотра и устройство. После этого создается новое устройство функцией CreateDeviceFromClipper() интерфейса Direct3DRM. Новое устройство конфигурируется с учетом сохраненных ранее параметров.

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



Функция RMWin::Render()


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

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

BOOL RMApp::OnIdle(LONG count) { ASSERT(RMWin::d3drm); ASSERT(rmwin); rmwin->OnIdle(count); RMWin::d3drm->Tick(D3DVALUE(1)); return TRUE; }

Функция Tick() интерфейса Direct3DRM использовалась для обновления данных программы и формирования нового изображения с учетом произошедших изменений. Этот метод прекрасно работал, когда у нас был один порт просмотра, но теперь нам требуется больший контроль. В приложении MultiView используется следующая версия функции RMApp::OnIdle():

BOOL RMApp::OnIdle(LONG lCount) { ASSERT(rmwin); rmwin->Render(); return TRUE; }

Эта версия передает отвественность за обновление данных программы функции RMWin::Render(), которая выглядит следующим образом:

void RMWin::Render() { scene->Move(D3DVALUE(1.0)); if (view1setting != VIEWPORT_DISABLED) { viewport1->Clear(); viewport1->Render(scene); } if (view2setting != VIEWPORT_DISABLED) { viewport2->Clear(); viewport2->Render(scene); } if (view3setting != VIEWPORT_DISABLED) { viewport3->Clear(); viewport3->Render(scene); } device->Update(); }

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

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

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

Хотя новое изображение уже создано функцией Render() интерфейса Direct3DRMViewport, оно пока остается невидимым. Для фактического вывода визуализированного изображения используется функция Update() интерфейса Direct3DRMDevice.



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


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

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

BOOL RocketWin::CreateScene() { //-------- СЕТКА ---------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_ROCKETMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, LoadTexture, NULL); ScaleMesh(meshbuilder, D3DVALUE(10));

//-------- АНИМАЦИЯ -------- d3drm->CreateAnimation(&animation); for (int i = 0; i < 11; i++) { D3DRMQUATERNION quat; D3DRMQuaternionFromRotation(&quat, &vect[i], rot[i]); animation->AddRotateKey(D3DVALUE(i), &quat); animation->AddPositionKey(D3DVALUE(i), trans[i].x, trans[i].y, trans[i].z); } OnAnimationLinear(); //-------- ФРЕЙМ СЕТКИ ------ LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->AddMoveCallback(UpdateScene, animation); animation->SetFrame(meshframe); meshframe->Release(); meshframe = 0;

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

LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-2), 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.0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);

return TRUE; }

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

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

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

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

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

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

Первым делом создается сетка:

D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_ROCKETMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, LoadTexture, NULL); ScaleMesh(meshbuilder, D3DVALUE(10));

Сетка загружается из ресурсов программы с помощью интерфейса Direct3DRMMeshBuilder. С одним исключением, данный код аналогичен коду загрузки сетки из любого другого демонстрационного приложения, рассматриваемого в этой книге. Отличие заключается в том, что для присоединения текстуры к сетке мы здесь используем функцию обратного вызова. Четвертый аргумент функции Load() интерфейса Direct3DRMMeshBuilder является указателем на необязательную функцию обратного вызова, используемую для загрузки текстур. Обычно в этом аргументе мы передавали ноль, но сейчас, ради разнообразия, давайте воспользуемся функцией обратного вызова LoadTexture(). Ее код выглядит так:

HRESULT RocketWin::LoadTexture(char*, void*, LPDIRECT3DRMTEXTURE* texture) { HRSRC id = FindResource(NULL, MAKEINTRESOURCE(IDR_ROCKETTEXTURE), "TEXTURE"); RMWin::d3drm->LoadTextureFromResource(id, texture); return D3DRM_OK; }

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

СОВЕТ Простой способ наложения текстур. Данные о наложении текстур могут быть сохранены в файле сетки путем назначения сетке атрибутов размещения текстур в программах визуального моделирования таких, как 3D Studio. По умолчанию данные о размещении текстур импортируются утилитой DirectX CONV3DS. Эта техника является альтернативой методам, рассмотренным в главе 5.
<


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

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

d3drm->CreateAnimation(&animation); for (int i = 0; i < 11; i++) { D3DRMQUATERNION quat; D3DRMQuaternionFromRotation(&quat, &vect[i], rot[i]); animation->AddRotateKey(D3DVALUE(i), &quat); animation->AddPositionKey(D3DVALUE(i), trans[i].x, trans[i].y, trans[i].z); } OnAnimationLinear();

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

Данные ключей вращения хранятся в двух массивах: vect и rot. Массив vect содержит векторы, определяющие ось вращения, а массив rot хранит величину поворота (в радианах). Эти два массива инициализируются следующим образом:

D3DVECTOR vect[]= // вектор вращения для каждого ключевого кадра { { D3DVALUE(1), D3DVALUE(0), D3DVALUE(0) }, { D3DVALUE(1), D3DVALUE(0), D3DVALUE(0) }, { D3DVALUE(1), D3DVALUE(0), D3DVALUE(0) }, { D3DVALUE(1), D3DVALUE(0), D3DVALUE(0) }, { D3DVALUE(0), D3DVALUE(1), D3DVALUE(0) }, { D3DVALUE(1), D3DVALUE(0), D3DVALUE(0) }, { D3DVALUE(0), D3DVALUE(1), D3DVALUE(0) }, { D3DVALUE(1), D3DVALUE(0), D3DVALUE(0) }, { D3DVALUE(0), D3DVALUE(1), D3DVALUE(0) }, { D3DVALUE(1), D3DVALUE(0), D3DVALUE(0) }, { D3DVALUE(1), D3DVALUE(0), D3DVALUE(0) }, };



const D3DVALUE rot[]= // угол поворота для каждого ключевого кадра { PI/2, PI, -(PI/2), PI, PI/2, PI, -(PI/2), -PI, PI/2, PI, PI/2, };

Массив vect инициализируется наборами значений X, Y и Z. Каждые три значения образуют вектор, определяющий ось вращения. При инициализации массива rot используется константа PI, чтобы показать насколько должен повернуться объект вокруг заданной оси. Использование значения PI эквивалентно повороту на 180 градусов. Для поворота на 90 градусов укажите значение PI/2. Отрицательные значения изменяют направление вращения на противоположное.

Давайте снова взглянем на содержимое цикла:

D3DRMQUATERNION quat; D3DRMQuaternionFromRotation(&quat, &vect[i], rot[i]); animation->AddRotateKey(D3DVALUE(i), &quat); animation->AddPositionKey(D3DVALUE(i), trans[i].x, trans[i].y, trans[i].z);

Функция AddRotateKey() интерфейса Direct3DRMAnimation получает в качестве второго аргумента кватернионы. Кватернионом (quaternion) называется структура, в которой хранится и вектор вращения и скорость вращения. Чтобы преобразовать вектор и скорость вращения в кватернион воспользуемся функцией D3DRMQuaternionFromRotation(). Адрес полученного кватерниона передадим функции AddRotateKey() во втором аргументе. Первым аргументом функции AddRotateKey() является временная метка для нового ключа.

Использовать функцию AddPositionKey() немного легче. Ей необходимо четыре аргумента: временная метка и три значения, определяющие позицию. Для хранения позиций анимационной последовательности наш код использует массив trans. Его определение выглядит так:

const D3DVECTOR trans[]= // местоположение объекта в каждом ключевом кадре { { D3DVALUE(0), D3DVALUE(0), FARLIM }, { XOUTLIM, D3DVALUE(0), CLOSELIM }, { D3DVALUE(0), D3DVALUE(0), FARLIM }, { -XOUTLIM, D3DVALUE(0), CLOSELIM }, { D3DVALUE(0), D3DVALUE(0), FARLIM }, { D3DVALUE(0), -YOUTLIM, CLOSELIM }, { D3DVALUE(0), D3DVALUE(0), FARLIM }, { D3DVALUE(0), YOUTLIM, CLOSELIM }, { D3DVALUE(0), D3DVALUE(0), FARLIM }, { D3DVALUE(0), D3DVALUE(0), CLOSELIM-3 }, { D3DVALUE(0), D3DVALUE(0), FARLIM }, };



Значения констант XOUTLIM, YOUTLIM, FARLIM и CLOSELIM получены методом проб и ошибок. Константа XOUTLIM определяет как далеко влево и вправо перемещается ракета. Константа YOUTLIM контролирует пределы перемещения по вертикали. Константы FARLIM и CLOSELIM управляют движением ракеты вдоль оси Z.

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

LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->AddMoveCallback(UpdateScene, animation); animation->SetFrame(meshframe); meshframe->Release(); meshframe = 0;

Новый фрейм создается с помощью функции CreateFrame() интерфейса Direct3DRM. Ранее созданная сетка ракеты присоединяется к фрейму с помощью функции AddVisual() интерфейса Direct3DRMFrame.

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

Далее функция SetFrame() интерфейса Direct3DRMAnimation используется для присоединения фрейма к объекту Direct3DRMAnimation создание и инициализация которого были выполнены на этапе 2. Теперь объект анимации будет управлять перемещемнием, вращением и масштабированием фрейма.

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


Функция RocketWin::UpdateScene()


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

void RocketWin::UpdateScene(LPDIRECT3DRMFRAME, void* p, D3DVALUE) { LPDIRECT3DRMANIMATION animation=(LPDIRECT3DRMANIMATION)p; static D3DVALUE time; time+=speed; animation->SetTime( time ); }

Сперва функция инициализирует указатель на интерфейс Direct3DRMAnimation. Вспомните, что указатель на объект анимации был передан функции AddMoveCallback(). Это означает, что Direct3D будет передавать значение указателя нашей функции обратного вызова, так что второй параметр функции UpdateScene() будет иметь то же значение, что и указатель. Однако перед тем как использовать полученное значение, его надо привести к правильному типу. Поэтому локальный указатель animation инициализируется значением второго аргумента функции, приведенным к требуемому типу.

Далее увеличивается значение статической переменной счетчика time. Значение, на которое увеличивается счетчик хранится в статической переменной speed. Как вы увидите позднее, это значение можно изменить с помощью команд меню Speed. Данная возможность позволяет легко изменять скорость анимационной последовательности.

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



Функция ScaleMesh()


В функции CreateScene() после создания и загрузки объекта meshbuilder вызывается функция ScaleMesh(), гарантирующая, что сетка будет иметь требуемый размер. Ниже снова приведен соответствующий фрагмент кода:

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

Функция ScaleMesh() получает два аргумента: указатель на объект meshbuilder и значение размера. Значение размера— это не коэффициент масштабирования, а, скорее, указание идеального размера объекта. Функция ScaleMesh() вычисляет коэффициент масштабирования таким образом, чтобы наибольший размер объекта был максимально близким к указанному предельному значению. В приведенном выше фрагменте функция ScaleMesh() масштабирует объект так, чтобы наибольший из его размеров был равен 25 единицам. Код функции ScaleMesh() выглядит следующим образом:

void RMWin::ScaleMesh(LPDIRECT3DRMMESHBUILDER mesh, D3DVALUE dim) { D3DRMBOX box; mesh->GetBox(&box); D3DVALUE sizex = box.max.x - box.min.x; D3DVALUE sizey = box.max.y - box.min.y; D3DVALUE sizez = box.max.z - box.min.z; D3DVALUE largedim = D3DVALUE(0); if (sizex > largedim) largedim = sizex; if (sizey > largedim) largedim = sizey; if (sizez > largedim) largedim = sizez; D3DVALUE scalefactor = dim/largedim; mesh->Scale(scalefactor, scalefactor, scalefactor); }

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



Функция ShadowWin::AdjustSpin()


Функция AdjustSpin() является функцией обратного вызова, которая периодически изменяет атрибуты вращения фрейма вилки. Для генерации случайного вектора в ней используется функция D3DRMVectorRandom().

void ShadowWin::AdjustSpin(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); }

Такая же функция (с другим названием) присутствует в приложении OrbStar. Вы найдете ее подробное описание в главе 5.



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


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

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

//------------- СЕТКА ПОЛА -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_CUBEMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&floorbuilder); floorbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); floorbuilder->Scale(D3DVALUE(5), D3DVALUE(.05), D3DVALUE(5)); floorbuilder->SetPerspective(TRUE); floorbuilder->SetQuality(D3DRMRENDER_FLAT);

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

//------------ НАЛОЖЕНИЕ ТЕКСТУРЫ ПОЛА ------------ D3DRMBOX box; floorbuilder->GetBox(&box); D3DVALUE w = box.max.x - box.min.x; D3DVALUE h = box.max.z - box.min.z;

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

//------------- ФРЕЙМ ПОЛА ---------- LPDIRECT3DRMFRAME floorframe; d3drm->CreateFrame(scene, &floorframe); floorframe->AddVisual(floorbuilder); floorframe->Release(); floorframe = 0;

//---------------- СЕТКА ВИЛКИ -------- resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_FORKMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&forkbuilder); forkbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); forkbuilder->SetQuality(D3DRMRENDER_FLAT);

//--------------- ФРЕЙМ ВИЛКИ ---------- LPDIRECT3DRMFRAME forkframe; d3drm->CreateFrame(scene, &forkframe); forkframe->SetRotation(scene, D3DVALUE(1), D3DVALUE(1), D3DVALUE(1), D3DVALUE(0.4)); forkframe->SetPosition(scene, D3DVALUE(0), D3DVALUE(6), D3DVALUE(0)); forkframe->AddVisual(forkbuilder); forkframe->AddMoveCallback(AdjustSpin, NULL);

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

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

LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->SetPosition(scene, D3DVALUE(0), D3DVALUE(30), D3DVALUE(0)); lightframe->AddLight(pointlight); lightframe->Release(); lightframe = 0;

//-------------- ТЕНЬ ---------- LPDIRECT3DRMVISUAL shadow; d3drm->CreateShadow(forkbuilder, pointlight, D3DVALUE(0), box.max.y+D3DVALUE(0.1), D3DVALUE(0), D3DVALUE(0), box.max.y+D3DVALUE(1.0), D3DVALUE(0), (LPDIRECT3DRMVISUAL*)&shadow); forkframe->AddVisual(shadow);

shadow->Release(); shadow = 0; forkframe->Release(); forkframe = 0; pointlight->Release(); pointlight = 0;

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

return TRUE; }

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

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

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

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

Размещение сетки пола и ее фрейма.

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

Размещение сетки вилки и ее фрейма.

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

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

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

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

Давайте начнем изучение кода с шага 1, создания сетки пола:

D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_CUBEMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&floorbuilder); floorbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); floorbuilder->Scale(D3DVALUE(5), D3DVALUE(.05), D3DVALUE(5)); floorbuilder->SetPerspective(TRUE); floorbuilder->SetQuality(D3DRMRENDER_FLAT);

Для представления пола используется сетка кубической формы. Она загружается с помощью функции Load() интерфейса Direct3DRMMeshBuilder, а затем делается плоской с помощью функции Scale(). Для сетки разрешается перспективная коррекция и устанавливается плоский метод визуализации.

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

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

Значение, идентифицирующее текстуру, получается с помощью функции FindResource(), входящей в Win32 API. Затем текстура загружается с помощью функции LoadTextureFromResource() и связывается с созданным ранее конструктором сеток с помощью функции SetTexture(). После выполнения этих действий указатель texture освобождается.

На третьем этапе происходит создание и применение наложения текстуры для сетки пола:

D3DRMBOX box; floorbuilder->GetBox(&box); D3DVALUE w = box.max.x - box.min.x; D3DVALUE h = box.max.z - box.min.z;



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

Функция GetBox() интерфейса Direct3DRMMeshBuilder используется для получения размеров сетки пола. Размеры сетки (и ряд других значений) используются при создании плоского наложения текстуры. Наложение текстуры используется чтобы применить привязанную ранее текстуру к сетке пола с помощью функции Apply() интерфейса Direct3DRMWrap. Затем указатель wrap освобождается. Подробное описание функции CreateWrap() приведено в разделе главы 5, посвященном описанию приложения Wraps.

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

LPDIRECT3DRMFRAME floorframe; d3drm->CreateFrame(scene, &floorframe); floorframe->AddVisual(floorbuilder); floorframe->Release(); floorframe = 0;

Фрейм создается функцией CreateFrame() интерфейса Direct3DRM. Функция AddVisual() применяется для присоединения конструктора сеток к новому фрейму. После этого указатель floorframe освобожлается поскольку он больше нам не потребуется.

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

resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE( IDR_FORKMESH ); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&forkbuilder); forkbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); forkbuilder->SetQuality(D3DRMRENDER_FLAT);

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



Теперь пришло время создать фрейм для сетки вилки и настроить его параметры:

LPDIRECT3DRMFRAME forkframe; d3drm->CreateFrame(scene, &forkframe); forkframe->SetRotation(scene, D3DVALUE(1), D3DVALUE(1), D3DVALUE(1), D3DVALUE(0.4)); forkframe->SetPosition(scene, D3DVALUE(0), D3DVALUE(6), D3DVALUE(0)); forkframe->AddVisual(forkbuilder); forkframe->AddMoveCallback(AdjustSpin, NULL);

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

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

На седьмом этапе создается источник рассеянного света:

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

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

Восьмым шагом является создание точечного источника света. Это тот источник света, который будет создавать тень от вилки. Код его создания выглядит так:

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

LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->SetPosition(scene, D3DVALUE(0), D3DVALUE(30), D3DVALUE(0)); lightframe->AddLight(pointlight); lightframe->Release(); lightframe = 0;

Сразу после создания источника света, создается фрейм для его размещения. Фрейм перемещается от начала координат на 30 единиц вверх.


Для присоединения источника света к новому фрейму используется функция AddLight().

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

LPDIRECT3DRMSHADOW shadow; d3drm->CreateShadow(forkbuilder, pointlight, D3DVALUE(0), box.max.y+D3DVALUE(0.1), D3DVALUE(0), D3DVALUE(0), box.max.y+D3DVALUE(1.0), D3DVALUE(0), (LPDIRECT3DRMVISUAL*)&shadow); forkframe->AddVisual(shadow);

shadow->Release(); shadow = 0; forkframe->Release(); forkframe = 0; pointlight->Release(); pointlight = 0;

Тень создается с помощью функции CreateShadow() интерфейса Direct3DRM, которая получает девять аргументов. Первый аргумент — это указатель на объект, который будет отбрасывать тень. Второй аргумент — указатель на источник света, который будет использоваться при вычислении формы тени. Следующие шесть аргументов определяют плоскость, на которую будет отбрасываться тень (первые три аргумента задают точку плоскости, а следующие три определяют перпендикулярный плоскости вектор). Последний аргумент, передаваемый функции CreateShadow() интерфейса Direct3DRM, — адрес инициализируемого функцией указателя. По непонятным причинам в качестве последнего аргумента функция CreateShadow() ожидает указатель на интерфейс Direct3DRMVisual, а не указатель на интерфейс Direct3DRMShadow. Поэтому для корректной компиляции программы необходимо указать явное преобразование типа указателя.

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

d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0.0), D3DVALUE(25.0), D3DVALUE(-20.0)); camera->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-25), D3DVALUE(20), D3DVALUE(.1), D3DVALUE(1), D3DVALUE(0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);

Фрейм camera инициализируется функцией CreateFrame() интерфейса Direct3DRM, перемещается вверх и вперед от начала координат, и направляется на начало координат.Функция CreateViewport() применяется для инициализации указателя viewport.

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


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


Во время конструирования сцены приложения ShowRoom, функция CreateScene() загружает 15 текстур, на каждой из которых изображена машина, повернутая под другим углом. Потом создаются сетка и плоское наложение текстуры, и первая текстура накладывается на сетку. Функция обратного вызова используется для последовательного наложения на сетку остальных текстур. Код функции CreateScene() приложения ShowRoom приведен в листинге5.7.

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

BOOL ShowRoomWin::CreateScene() { //-------- ТЕКСТУРЫ -------- HRSRC texture_id; int t = 0; texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE01), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE02), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE03), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE04), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE05), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE06), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE07), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE08), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE09), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE10), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE11), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE12), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE13), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE14), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE15), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]);

// ------- КОНСТРУКТОР СЕТОК -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE( IDR_BOXMESH ); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER meshbuilder; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->Scale(D3DVALUE(1), D3DVALUE(1), D3DVALUE(.1)); ScaleMesh(meshbuilder, D3DVALUE(20)); meshbuilder->SetPerspective(TRUE); meshbuilder->SetQuality(D3DRMRENDER_FLAT); meshbuilder->SetTexture(texture[0]);

//--------- НАЛОЖЕНИЕ -------- 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;

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

//------- ФРЕЙМ -------- LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(mesh); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0.05)); meshframe->AddMoveCallback(UpdateTexture, NULL); meshframe->Release(); meshframe = 0;

//-------- СВЕТ -------- LPDIRECT3DRMLIGHT dlight, alight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.0), D3DVALUE(1.0), D3DVALUE(1.0), &dlight); d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1.0), D3DVALUE(1.0), D3DVALUE(1.0), &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); lightframe->Release(); lightframe = 0; dlight->Release(); dlight = 0; 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; }

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

Создание пятнадцати текстур.

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

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

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

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

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

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

Первая часть функции CreateScene() весьма прямолинейна (и скучна). Создаются и загружаются 15 текстур, и указатель на каждую из текстур сохраняется в массиве texture.

Затем с помощью интерфейса Direct3DRMMeshBuilder загружается сетка куба. Для масштабирования куба применяется функция Scale():

D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE( IDR_BOXMESH ); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER meshbuilder; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->Scale(D3DVALUE(1), D3DVALUE(1), D3DVALUE(.1)); ScaleMesh(meshbuilder, D3DVALUE(20)); meshbuilder->SetPerspective(TRUE); meshbuilder->SetQuality(D3DRMRENDER_FLAT); meshbuilder->SetTexture(texture[0]);

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

На шаге 3 создается и применяется наложение текстуры:

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;



Плоское наложение текстуры создается таким образом, чтобы размеры текстуры в точности соответствовали сетке. Наложение текстуры применяется к конструктору сеток с помощью функции Apply().

Теперь загруженный ранее конструктор сеток используется для инициализации указателя на интерфейс Direct3DRMMesh:

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

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

Затем создается фрейм и сетка присоединяется к нему:

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

Новому фрейму назначаются такие атрибуты вращения, чтобы он поворачивался вокруг оси Y. Кроме того, для фрейма устанавливается функция обратного вызова UpdateTexture().


Функция ShowRoom::UpdateTexture()


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

void ShowRoomWin::UpdateTexture(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { static UINT count; int curtex = count % 15; frame->DeleteVisual(mesh); mesh->SetGroupTexture(0, texture[curtex]); frame->AddVisual(mesh); count++; }

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

Функция SetGroupTexture() интерфейса Direct3DRMMesh похожа на функцию SetTexture() интерфейса Direct3DRMMeshBuilder, которую мы использовали в предыдущих примерах. Отличие заключается в том, что интерфейс Direct3DRMMesh поддерживает группы, а интерфейс Direct3DRMMeshBuilder — нет. Группой называется набор граней, которым можно управлять как единой сущностью. Более подробно об интерфейсе Direct3DRMMesh мы поговорим в главе 8.



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


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

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

BOOL SpaceDonutWin::CreateScene() { // ------- СЕТКА ПОНЧИКА -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_DONUTMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->SetPerspective(TRUE); meshbuilder->SetColorRGB(D3DVALUE(1), D3DVALUE(1), D3DVALUE(1)); ScaleMesh(meshbuilder, D3DVALUE(20));

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

//------- НАЛОЖЕНИЕ ---------- D3DRMBOX box; meshbuilder->GetBox(&box); D3DVALUE w = box.max.x - box.min.x; D3DVALUE h = box.max.y - box.min.y;

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

//------- ФРЕЙМЫ ДЛЯ ПОНЧИКА -------- LPDIRECT3DRMFRAME leftframe; d3drm->CreateFrame(scene, &leftframe); leftframe->SetPosition(scene, D3DVALUE(-12), D3DVALUE(0), D3DVALUE(0)); leftframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0), D3DVALUE(1)); leftframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0.1)); leftframe->AddVisual(meshbuilder); leftframe->Release(); leftframe = 0;

LPDIRECT3DRMFRAME rightframe; d3drm->CreateFrame(scene, &rightframe); rightframe->SetPosition(scene, D3DVALUE(12), D3DVALUE(0), D3DVALUE(0)); rightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0), D3DVALUE(1)); rightframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(-0.1)); rightframe->AddVisual(meshbuilder); rightframe->Release(); rightframe = 0;

// --------- ПАРАЛЛЕЛЬНО-ТОЧЕЧНЫЙ СВЕТ -------- LPDIRECT3DRMLIGHT light; d3drm->CreateLightRGB(D3DRMLIGHT_PARALLELPOINT, D3DVALUE(1.0), D3DVALUE(1.0), D3DVALUE(1.0), &light);

LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->AddLight(light);

lightframe->Release(); lightframe = 0; light->Release(); light = 0;

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

return TRUE; }

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

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

Создает и загружает текстуру, изображающую глазурь на пончике.

Накладывает текстуру глазури на сетку пончика.

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

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

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

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

D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_DONUTMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->SetPerspective(TRUE); meshbuilder->SetColorRGB(D3DVALUE(1), D3DVALUE(1), D3DVALUE(1)); ScaleMesh(meshbuilder, D3DVALUE(20));

Приведенный код аналогичен коду для объекта meshbuilder в других демонстрационных программах. Загружаемая сетка идентифицируется с помощью структуры resinfo. Константа IDR_DONUTMESH — это идентификатор ресурса для импортированного в проект файла сетки. Строка "MESH" определяет категорию ресурса. Указатель meshbuilder инициализируется с помощью функции CreateMeshBuilder() интерфейса Direct3DRM, после чего, для загрузки сетки применяется функция Load(). Вызов функции SetPerspective() разрешает перспективную коррекцию текстур. Последнее действие является необязательным, и выполняется только для того, чтобы улучшить вид сетки после наложения текстуры.

Затем вызывается функция SetColorRGB() интерфейса Direct3DRMMeshBuilder, чтобы окрасить сетку в белый цвет. Это сделано потому, что используемая в данном примере сетка пончика имеет грани различных цветов. Чтобы одновременно установить цвет для всех граней сетки и используется функция SetColorRGB(). После этого вызывается функция ScaleMesh() чтобы задать размер сетки.

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



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

Переменная texture_id идентифицирует загружаемую текстуру. Функция FindResource() применяется для задания идентификатора ресурса (IDR_FROSTINGTEXTURE) и типа ресурса ("TEXTURE").

Указатель texture инициализируется функцией LoadTextureFromResource(). Новая текстура связывается с объектом meshbuilder с помощью функции SetTexture(). После выполнения описанных действий указатель texture освобождается.

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

D3DRMBOX box; meshbuilder->GetBox(&box); D3DVALUE w = box.max.x - box.min.x; D3DVALUE h = box.max.y - box.min.y;

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

Функция CreateWrap() интерфейса Direct3DRM подробно рассматривалась в главе 5 (в разделе, посвященном приложению Wraps), поэтому здесь данный код не обсуждается. Если говорить коротко, создается плоское наложение текстуры, растягивающее текстуру по размеру сетки. Затем наложение текстуры применяется к сетке с помощью функции Apply() интерфейса Direct3DRMWrap.

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

LPDIRECT3DRMFRAME leftframe; d3drm->CreateFrame(scene, &leftframe); leftframe->SetPosition(scene, D3DVALUE(-12), D3DVALUE(0), D3DVALUE(0)); leftframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0), D3DVALUE(1)); leftframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0.1)); leftframe->AddVisual(meshbuilder); leftframe->Release(); leftframe = 0;



Указатель leftframe инициализируется функцией CreateFrame(). Функция SetPosition() применяется, чтобы переместить фрейм на 12 единиц влево от его местоположения по умолчанию (начала координат).

Расположенный следом вызов функции SetOrientation(), необходим из-за ориентации сетки пончика в файле. Файл сетки создан таким образом, что пончик расположен вдоль оси Y (лежит лицом вниз). Мы же хотим, чтобы пончик был выровнен вдоль оси Z (стоял на боку). Чтобы сделать это, нам необходимо знать, где у сетки находятся лицевой и верхний векторы.

Значение по умолчанию для лицевого вектора объекта равно <0, 0, 1>, а значит лицевая грань объекта расположена в направлении положительных значений по оси Z. Значение по умолчанию для верхнего вектора объекта равно <0, 1, 0>. Из вышесказанного следует, что для того, чтобы сориентировать пончик желаемым образом, достаточно просто поменять местами лицевой и верхний векторы. Благодаря этому лицевая поверхность кольца будет направлена в ту сторону, куда раньше был направлен его верх, и наоборот.

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

Код для создания и настройки указателя rightframe выглядит аналогично коду для leftframe, поэтому здесь мы отметим только различия:

Фрейм rightframe располагается справа от начала координат.

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

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


Фактически, если вы запустите приложение SpaceDonut и измените параметры визуализации с помощью меню Render, вы увидите, что изменится изображение обоих экземпляров сетки.

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

LPDIRECT3DRMLIGHT light; d3drm->CreateLightRGB(D3DRMLIGHT_PARALLELPOINT, D3DVALUE(1.0), D3DVALUE(1.0), D3DVALUE(1.0), &light);

LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->AddLight(light);

lightframe->Release(); lightframe = 0; light->Release(); light = 0;

Функция CreateLightRGB() интерфейса Direct3DRM используется для инициализации указателя light. Константа D3DRMLIGHT_PARALLELPOINT задает тип источника света, а три числовых значения определяют красную, зеленую и синюю составляющие цвета источника света (мы создаем источник белого света).

Далее указатель lightframe инициализируется с помощью функции CreateFrame() интерфейса Direct3DRM. Созданный ранее источник света присоединяется к новому фрейму с помощью функции AddLight(). Затем указатели light и lightframe освобождаются.

На заключительном, шестом этапе работы функции 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. В данном случае камера смещается на 50 единиц от начала координат по направлению к зрителю. Указатель viewport инициализируется функцией CreateViewport() интерфейса Direct3DRM.


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


Приложение SpaceStation — это одна из самых простых демонстрационных программ на CD-ROM. В приложении используется одна сетка и один источник света, и нет никаких функций обратного вызова или текстур. Сцена приложения SpaceStation конструируется функцией SpaceStationWin::CreateScene(), код которой показан в листинге 6.3.

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

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

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

// --------- ИСТОЧНИК СВЕТА И ЕГО ФРЕЙМ -------- LPDIRECT3DRMLIGHT light; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &light);

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

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

return TRUE; }

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

Создает сетку космической станции.

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


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

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

Код, выполняющий первый из перечисленных этапов, показан ниже:

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

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

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

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

Как только фрейм создан, представляющий космическую станцию объект meshbuilder присоедияется к новому фрейму (meshframe) с помощью функции AddVisual() интерфейса Direct3DRMFrame. Затем вызывается функция SetRotation() для назначения фрейму атрибутов вращения. Аргументы, передаваемые функции SetRotation() задают поворот сетки вокруг оси Y на 0,5 радиана при каждом обновлении изображения сцены. После выполнения всех этих действий указатель meshframe освобождается и обнуляется.



Далее функция CreateScene() создает источник направленного света и присоединяет его к фрейму:

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

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

Здесь в коде объявлен указатель на интерфейс Direct3DRMLight с именем light, который инициализируется функцией CreateLightRGB() интерфейса Direct3DRM. Константа D3DRMLIGHT_DIRECTIONAL задает тип создаваемого источника света. Следующие три аргумента определяют цвет источника света (в данном случае — белый), а последний аргумент является адресом инициализируемого указателя light.

Теперь пора создать фрейм для источника света. Указатель lightframe инициализируется функцией CreateFrame(), после чего источник света присоединяется к новому фрейму с помощью функции AddLight() интерфейса Direct3DRMFrame.

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



Рис. 6.6. Вид сверху на сцену приложения SpaceStation

После выполнения ориентации источника света указатели light и lightframe освобождаются.

Четвертый, и последний фрагмент кода функции SpaceStationWin::CreateScene() создает порт просмотра и настраивает его параметры. Взгляните еще раз на приложение SpaceStation (рис. 6.5).


Обратите внимание, что сетка космической станции наклонена. Это не вызвано тем, что мы наклонили фрейм к которому присоединена сетка космической станции. Фактически, мы только лишь назначили фрейму атрибуты вращения, чтобы он поворачивался вокруг оси Y. И все же, если вы запустите приложение SpaceStation, космическая станция будет наклонена и вращаться в том направлении, в котором наклонена.

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

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

Эта часть кода отличается от других приложений, рассмотренных нами к этому моменту. Во-первых, здесь по-другому позиционируется фрейм camera. Обычно мы перемещали камеру от начала координат в направлении зрителя по прямой, передавая функции SetPosition() в качестве аргументов значения <0, 0, –50>. В этот раз мы перемещаем фрейм camera от начала координат на 50 единиц к зрителю и на 25 единиц вверх. По умолчанию фрейм направлен вдоль оси Z. Это значит, что если мы не изменим ориентацию фрейма camera, то, скорее всего, вообще не увидим сетку космической станции, поскольку поле зрения камеры будет расположено выше сетки.

Вызов функции SetOrientation(), размещенный вслед за вызовом функции SetPosition(), используется для двух целей. Во-первых, он необходим, чтобы направить камеру на сетку. Во вторых, он применяется, чтобы наклонить камеру. Давайте внимательно посмотрим на вызов функции SetOrientation():

camera->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-23), D3DVALUE(50), D3DVALUE(0.7), D3DVALUE(1), D3DVALUE(0));

Лицевой вектор (определяемый первыми тремя числовыми аргументами) направлен от начала координат к точке, расположенной на 23 единицы ниже и на 50 единиц дальше.


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

Теперь давайте взглянем на верхний, или небесный, вектор (определенный последними тремя числовыми аргументами функции SetOrientation()). В других демонстрационных программах мы использовали значения <0, 1, 0>, чтобы задать вектор, направленный вверх вдоль оси Y (отсюда и название вектора). На этот раз мы используем значения <0.7, 1, 0>. Задав вектор, который указывает почти настолько же далеко вправо, насколько вверх, мы наклоняем фрейм камеры вправо.

Последней функцией, вызываемой из CreateScene(), является функция CreateViewport() интерфейса Direct3DRM. Этот вызов функции ничем не отличается от других программ.


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


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

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

BOOL SpotlightWin::CreateScene() { //----- СЕТКИ -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER builder; d3drm->CreateMeshBuilder(&builder); builder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); builder->SetColorRGB(D3DVALUE(1.0), D3DVALUE(0.0), D3DVALUE(0.0)); builder->CreateMesh(&mesh1); builder->SetColorRGB(D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0)); builder->CreateMesh(&mesh2); builder->SetColorRGB(D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0)); builder->CreateMesh(&mesh3); builder->Release(); builder = 0;

//----- ФРЕЙМЫ ДЛЯ СЕТОК -------- LPDIRECT3DRMFRAME frame1; d3drm->CreateFrame(scene, &frame1); frame1->SetPosition(scene, D3DVALUE(-2), D3DVALUE(0), D3DVALUE(0)); frame1->AddVisual(mesh1); frame1->Release(); frame1 = 0;

LPDIRECT3DRMFRAME frame2; d3drm->CreateFrame(scene, &frame2); frame2->SetPosition(scene, D3DVALUE(2), D3DVALUE(0), D3DVALUE(0)); frame2->AddVisual(mesh2); frame2->Release(); frame2 = 0;

LPDIRECT3DRMFRAME frame3; d3drm->CreateFrame(scene, &frame3); frame3->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(2)); frame3->AddVisual(mesh3); frame3->Release(); frame3 = 0;

//------- ПРОЖЕКТОР ------ d3drm->CreateLightRGB(D3DRMLIGHT_SPOT, D3DVALUE(0.8), D3DVALUE(0.8), D3DVALUE(0.8), &spotlight); OnBeamNormal();

//------ ФРЕЙМ ПРОЖЕКТОРА -------- LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->SetPosition(scene, D3DVALUE(0), D3DVALUE(10), D3DVALUE(-10)); lightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); lightframe->AddLight(spotlight); lightframe->AddMoveCallback(MoveLight, NULL); lightframe->Release(); lightframe = 0;

//------ КАМЕРА ------ d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(6), D3DVALUE(-6)); camera->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1.1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; }

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

Использует интерфейс Direct3DRMMeshBuilder для загрузки сферической сетки и создает три указателя на интерфейс Direct3DRMMesh.

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

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

Создает фрейм для прожектора.

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

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

D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER builder; d3drm->CreateMeshBuilder(&builder); builder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); builder->SetColorRGB(D3DVALUE(1.0), D3DVALUE(0.0), D3DVALUE(0.0)); builder->CreateMesh(&mesh1); builder->SetColorRGB(D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0)); builder->CreateMesh(&mesh2); builder->SetColorRGB(D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0)); builder->CreateMesh(&mesh3); builder->Release(); builder = 0;

В самом начале выполняется объявление и инициализация экземпляра структуры D3DRMLOADRESOURCE. Структура resinfo идентифицирует сферическую сетку, которую мы будем использовать.

Далее объявляется указатель на интерфейс Direct3DRMMeshBuilder с именем builder. Для инициализации этого указателя вызывается функция CreateMeshBuilder() интерфейса Direct3DRM. Функция Load() применяется для загрузки сетки. Адрес подготовленной ранее структуры resinfo передается функции Load() в ее первом аргументе.

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

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



LPDIRECT3DRMFRAME frame1; d3drm->CreateFrame(scene, &frame1); frame1->SetPosition(scene, D3DVALUE(-2), D3DVALUE(0), D3DVALUE(0)); frame1->AddVisual(mesh1); frame1->Release(); frame1 = 0;

LPDIRECT3DRMFRAME frame2; d3drm->CreateFrame(scene, &frame2); frame2->SetPosition(scene, D3DVALUE(2), D3DVALUE(0), D3DVALUE(0)); frame2->AddVisual(mesh2); frame2->Release(); frame2 = 0;

LPDIRECT3DRMFRAME frame3; d3drm->CreateFrame(scene, &frame3); frame3->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(2)); frame3->AddVisual(mesh3); frame3->Release(); frame3 = 0;

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

На третьем этапе, код которого показан ниже, создается прожектор:

d3drm->CreateLightRGB(D3DRMLIGHT_SPOT, D3DVALUE(0.8), D3DVALUE(0.8), D3DVALUE(0.8), &spotlight); OnBeamNormal();

Вначале для инициализации указателя spotlight вызывается функция CreateLightRGB() интерфейса Direct3DRM. Константа D3DRMLIGHT_SPOT указывает, что мы создаем прожектор. Для красной, зеленой и синей составляющих цвета мы указываем значение 0.8, чтобы цвет нашего источника света был светло-серым.

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

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

LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->SetPosition(scene, D3DVALUE(0), D3DVALUE(10), D3DVALUE(-10)); lightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); lightframe->AddLight(spotlight); lightframe->AddMoveCallback(MoveLight, NULL); lightframe->Release(); lightframe = 0;



Указатель lightframe инициализируется функцией CreateFrame() интерфейса Direct3DRM. Функция SetPosition() применяется, чтобы поместить фрейм в точку, расположенную на 10 единиц выше и на 10 единиц дальше начала координат. Затем вызывается функция SetOrientation(), чтобы направить фрейм на начало координат.

После этого созданный ранее прожектор присоединяется к новому фрейму с помощью функции AddLight(). Кроме того, с помощью функции AddMoveCallback() устанавливается функция обратного вызова MoveLight(). После выполнения всех описанных действий указатель lightframe освобождается.

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

d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(6), D3DVALUE(-6)); camera->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1.1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);

Сначала инициализируется фрейм camera. Затем этот фрейм перемещается от начала координат на шесть единиц вверх и на шесть единиц ближе к зрителю. Функция SetOrientation() применяется, чтобы направить лицевую грань фрейма на начало координат. И, в самом конце, вызывается функция CreateViewport() интерфейса Direct3DRM для инициализации указателя viewport.


Функция SpotlightWin::MoveLight()


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

void SpotlightWin::MoveLight(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { static const D3DVALUE LIM = D3DVALUE(0.3); static D3DVALUE xi = D3DVALUE(0.03); static D3DVALUE yi = D3DVALUE(0.04); static D3DVALUE x, y; if (x < -LIM || x > LIM) xi = -xi; if (y < -LIM || y > LIM) yi = -yi; x += xi; y += yi; frame->SetOrientation(NULL, x, y-1, D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); }

Так же, как в приложении Jade из главы 5, в приложении Spotlight для выполнения анимации используется простой алгоритм «подскакивающего мяча» (более сложные приемы анимации мы изучим в главе 7). Объявленные в функции MoveLight() статические переменные применяются для отслеживания и ограничения изменений ориентации фрейма. После вычисления нового значения ориентации фрейма, оно устанавливается с помощью функции SetOrientation().



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


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

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

BOOL TargetWin::CreateScene() { // ------- СЕТКА ЦЕЛИ -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER targetbuilder; d3drm->CreateMeshBuilder(&targetbuilder); targetbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); ScaleMesh(targetbuilder, D3DVALUE(.75));

// --------- АНИМАЦИЯ ЦЕЛИ ---------- LPDIRECT3DRMANIMATION animation; d3drm->CreateAnimation(&animation); animation->SetOptions(D3DRMANIMATION_SPLINEPOSITION | D3DRMANIMATION_CLOSED | D3DRMANIMATION_POSITION); animation->AddPositionKey(D3DVALUE(0), D3DVALUE(-20), D3DVALUE(0), D3DVALUE(-20)); animation->AddPositionKey(D3DVALUE(12), D3DVALUE(0), D3DVALUE(15), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(24), D3DVALUE(20), D3DVALUE(0), D3DVALUE(-20)); animation->AddPositionKey(D3DVALUE(35), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(49), D3DVALUE(20), D3DVALUE(0), D3DVALUE(20)); animation->AddPositionKey(D3DVALUE(65), D3DVALUE(0), D3DVALUE(15), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(74), D3DVALUE(-20), D3DVALUE(0), D3DVALUE(20)); animation->AddPositionKey(D3DVALUE(85), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(99), D3DVALUE(-20), D3DVALUE(0), D3DVALUE(-20));

// ---------- ФРЕЙМ ЦЕЛИ -------- LPDIRECT3DRMFRAME targetframe; d3drm->CreateFrame(scene, &targetframe); animation->SetFrame(targetframe); targetframe->AddVisual(targetbuilder); targetframe->AddMoveCallback(MoveTarget, animation);

targetbuilder->Release(); targetbuilder = 0;

// ------- СЕТКА РАКЕТЫ -------- resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_MISSLEMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->SetColorRGB( D3DVALUE(.67), D3DVALUE(.82), D3DVALUE(.94)); meshbuilder->SetQuality(D3DRMRENDER_FLAT); ScaleMesh(meshbuilder, D3DVALUE(7));

// ------- ФРЕЙМЫ РАКЕТ ------ for (int i = 0; i < 5; i++) { for (int j = 0; j < 3; j++) { LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe);

meshframe->SetPosition(scene, D3DVALUE((i - 2) * 8), D3DVALUE(-12), D3DVALUE((j - 1) * 8)); meshframe->AddVisual(meshbuilder); meshframe->AddMoveCallback(OrientFrame, targetframe);

meshframe->Release(); meshframe = 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->AddLight(dlight); lightframe->AddLight(alight); lightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0), D3DVALUE(1)); alight->Release(); alight = 0; dlight->Release(); dlight = 0; lightframe->Release(); lightframe = 0;

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

return TRUE; }

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

Загружает сферическую сетку, которая будет представлять цель для ракет.

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

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

Загружает сетку ракеты.

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

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

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

Первый этап — это создание сетки для цели:

D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER targetbuilder; d3drm->CreateMeshBuilder(&targetbuilder); targetbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); ScaleMesh(targetbuilder, D3DVALUE(.75));

Сетка загружается из ресурсов программы. Структура resinfo идентифицирует элемент ресурсов, содержащий сетку. Для загрузки сетки, как обычно, используется функция Load() интерфейса Direct3DRMMeshBuilder. После загрузки сетка масштабируется функцией RMWin::ScaleMesh().

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

LPDIRECT3DRMANIMATION animation; d3drm->CreateAnimation(&animation); animation->SetOptions(D3DRMANIMATION_SPLINEPOSITION | D3DRMANIMATION_CLOSED | D3DRMANIMATION_POSITION); animation->AddPositionKey(D3DVALUE(0), D3DVALUE(-20), D3DVALUE(0), D3DVALUE(-20)); animation->AddPositionKey(D3DVALUE(12), D3DVALUE(0), D3DVALUE(15), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(24), D3DVALUE(20), D3DVALUE(0), D3DVALUE(-20)); animation->AddPositionKey(D3DVALUE(35), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(49), D3DVALUE(20), D3DVALUE(0), D3DVALUE(20)); animation->AddPositionKey(D3DVALUE(65), D3DVALUE(0), D3DVALUE(15), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(74), D3DVALUE(-20), D3DVALUE(0), D3DVALUE(20)); animation->AddPositionKey(D3DVALUE(85), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(99), D3DVALUE(-20), D3DVALUE(0), D3DVALUE(-20));



Сначала для инициализации экземпляра интерфейса Direct3DRMAnimation вызывается функция CreateAnimation() интерфейса Direct3DRM. Затем вызывается функция SetOptions() интерфейса Direct3DRMAnimation,которой передается три флага. Флаг D3DRMANIMATION_SPLINEPOSITION указывает, что при расчете анимационной последовательности будут применяться сплайны. Флаг D3DRMANIMATION_CLOSED позволяет использовать непрерывно увеличивающиеся значения временных меток для повторного выполнения анимационной последовательности. Флаг D3DRMANIMATION_POSITION указывает объекту анимации, что нас интересует изменение местоположения анимируемого фрейма. Обратите внимание, что флаг D3DRMANIMATION_SCALEANDROTATION отсутствует (мы использовали его в приложении Rocket). Благодаря этому объект анимации не выполняет вычисления для изменения ориентации и масштаба, что позволяет анимационной последовательности выполняться быстрее.

Оставшаяся часть кода, относящегося ко второму этапу работы функции представляет собой несколько вызовов функции AddPositionKey() интерфейса Direct3DRMAnimation. Когда мы создавали анимационную последовательность в приложении Rocket, то использовали цикл для добавления ключей, которые были определены в элементах массива. В приложении Target не используется ни цикл ни массив. Каждый ключ добавляется с помощью отдельного вызова функции AddPositionKey(). Также, как и в приложении Rocket, используемые для анимационной последовательности позиции выбраны экспериментальным путем.

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

LPDIRECT3DRMFRAME targetframe; d3drm->CreateFrame(scene, &targetframe); animation->SetFrame(targetframe); targetframe->AddVisual(targetbuilder); targetframe->AddMoveCallback(MoveTarget, animation);

targetbuilder->Release(); targetbuilder = 0;

Сначала для инициализации локального указателя targetframe используется функция CreateFrame() интерфейса Direct3DRM. Затем указатель на новый фрейм используется в качестве аргумента функции SetFrame() интерфейса Direct3DRMAnimation.


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

Функция AddVisual() интерфейса Direct3DRMFrame используется чтобы присоединить к фрейму сетку цели. Затем с помощью функции AddMoveCallback() интерфейса Direct3DRMFrame устанавливается функция обратного вызова MoveTarget(). Обратите внимание, что в качестве второго аргумента функции AddMoveCallback() передается указатель animation. Это обеспечивает возможность доступа к объекту анимации для функции обратного вызова. В заключение, освобождается указатель targetbuilder (который был инициализирован на первом этапе).

СОВЕТ Нарушение соглашений COM. Обычно, перед завершением функции освобождаются все локальные указатели на интерфейсы Direct3D. В приложении Target мы видим два исключения из этого правила. Указатели animation и targetframe не освобождаются, поскольку используются в функциях обратного вызова. Освобождение этих указателей приведет к тому, что COM уничтожит соответствующие объекты, и обращение к функции обратного вызова приведет к краху программы.

Другое возможное решение — оставить вызов функции Release(), но только после вызова функции AddRef(). Благодаря этому COM получает уведомление о создании дополнительной ссылки на объект. Согласно спецификации COM второй метод предпочтительнее. Мы используем первый метод только для того, чтобы сделать код приложения как можно более простым.
На четвертом этапе загружается сетка, изображающая ракету:

resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_MISSLEMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->SetColorRGB( D3DVALUE(.67), D3DVALUE(.82), D3DVALUE(.94)); meshbuilder->SetQuality(D3DRMRENDER_FLAT); ScaleMesh(meshbuilder, D3DVALUE(7));

Также как и сетка мишени, сетка ракеты хранится в ресурсах программы. После того, как сетка загружена, она окрашивается в светло-синий цвет, с помощью функции SetColorRGB() интерфейса Direct3DRMMeshBuilder.


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

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

for (int i = 0; i < 5; i++) { for (int j = 0;j < 3; j++) { LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->SetPosition(scene, D3DVALUE((i - 2) * 8), D3DVALUE(-12), D3DVALUE((j - 1) * 8)); meshframe->AddVisual(meshbuilder); meshframe->AddMoveCallback(OrientFrame, targetframe); meshframe->Release(); meshframe = 0; } }

Указатель meshframe используется при инициализации каждого фрейма. Позиция нового фрейма зависит от текущей итерации двух вложенных циклов. Затем функция AddVisual() интерфейса Direct3DRMFrame используется для присоединения к фрейму сетки ракеты. Функция AddMoveCallback() применяется для установки функции обратного вызова OrientFrame(). Обратите внимание, что в качестве второго аргумента функции AddMoveCallback() используется указатель targetframe. Это обеспечивает функции обратного вызова доступ к фрейму за которым будут следить ракеты. После вызова функции AddMoveCallback() указатель meshframe освобождается.

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