Простейший способ анализа зависимостей — построение дерева зависимостей. Дерево зависимостей изображает связи каждого интерфейса с другими, представляя каждый интерфейс как узел дерева. Корень дерева представляет интерфейс, который не зависит от существования остальных. Последующие узлы представляют интерфейсы прямо или косвенно зависящие от корневого. Дерево зависимости для интерфейсов Direct3D изображено на рис. 4.2.
Рис. 4.2. Дерево зависимостей для интерфейсов Direct3D
Узлы на рисунке содержат название интерфейса (выделенное полужирным шрифтом) и имя переменной, используемой для идентификации данного интерфейса.
Из рисунка видно, что единственный интерфейс не зависящий от других — это интерфейс Direct3DRM. Линии, идущие от Direct3DRM к узлам DirectDrawClipper и Direct3DRMFrame показывают, что эти интерфейсы зависят от существования интерфейса Direct3DRM.
Система узлов распространяется от корня до узлов интерфейсов Direct3DRMViewport, Direct3DRMLight и Direct3DRMMeshBuilder. Эти интерфейсы косвенно зависят от всех остальных интерфейсов дерева. Обратите внимание, что интерфейс Direct3DRMViewport непосредственно зависит от двух интерфейсов.
Функция CreateDevice() отвечает за создание стандартных интерфейсов Direct3D, наиболее важным из которых является интерфейс Direct3DRMDevice. Функция CreateDevice() является закрытой, поэтому классы, производные от RMWin ничего не знают о выполняемых ею действиях. Полный текст функции приведен в листинге 4.1.
Листинг 4.1. Функция RMWin::CreateDevice() |
BOOL RMWin::CreateDevice() { HRESULT r;
r = DirectDrawCreateClipper(0, &clipper, NULL); if (r != D3DRM_OK) { TRACE("failed to create D3D clipper\n"); return FALSE; } r = clipper->SetHWnd(NULL, m_hWnd); if (r != DD_OK) { TRACE("failed in SetHWnd call\n"); return FALSE; }
RECT rect; ::GetClientRect(m_hWnd, &rect);
r = d3drm->CreateDeviceFromClipper(clipper, GetGUID(), rect.right, rect.bottom, &device); if (r != D3DRM_OK) { TRACE("CreateDeviceFromClipper failed\n"); return FALSE; } device->SetQuality(D3DRMRENDER_GOURAUD); HDC hdc = ::GetDC(m_hWnd); int bpp = ::GetDeviceCaps(hdc, BITSPIXEL); ::ReleaseDC(m_hWnd, hdc); switch (bpp) { case 8: device->SetDither(FALSE); break; case 16: device->SetShades(32); d3drm->SetDefaultTextureColors(64); d3drm->SetDefaultTextureShades(32); device->SetDither(FALSE); break; case 24: case 32: device->SetShades(256); d3drm->SetDefaultTextureColors(64); d3drm->SetDefaultTextureShades(256); device->SetDither(FALSE); break; } r = d3drm->CreateFrame(NULL, &scene); if (r != D3DRM_OK) { TRACE("CreateFrame(&scene) failed\n"); return FALSE; } if (CreateScene() == FALSE) { AfxMessageBox("CreateScene() failed"); return FALSE; } ASSERT( camera ); ASSERT( viewport ); return TRUE; } |
Первое, что вы должны знать о функции CreateDevice(): она присавивает зачения трем переменным класса RMWin — clipper, device и scene. Эти переменные являются защищенными членами класса RMWin, поэтому они доступны для членов класса SampleWin. Это позволяет классу SampleWin пользоваться интерфейсами, созданными классом RMWin.
Сначала присваивается значение переменной clipper. Функция DirectDrawCreateClipper() применяется для получения указателя на интерфейс DirectDrawClipper. Объект отсечения (clipper) — это конструкция DirectDraw, управляющая обновлением окна и позволяющая приложениям DirectDraw и Direct3D корректно работать в оконной среде. Объект отсечения получил свое название в связи с тем фактом, что перекрывающиеся окна должны отображаться в соответствии с тем, какие из их частей видимы. Прямоугольное отсечение обрабатывается Windows, и объект отсечения представляет соответствующую функциональность.
После создания объекта отсечения вызывается функция SetHWnd() интерфейса DirectDrawClipper. Тем самым окну назначается объект отсечения, который будет управлять им. Передаваемая в качестве аргумента переменная m_hWnd, является дескриптором окна, и инициализируется MFC.
Затем вызываются функции GetClientRect() и CreateDeviceFromClipper(). GetClientRect() — это функция Win32, получающая размеры клиентской области окна (клиентская область это внутренняя часть окна не включающая рамку и меню). Функция CreateDeviceFromClipper() является членом интерфейса Direct3DRM и применяется для создания указателя на интерфейс Direct3DRMDevice.
Функция CreateDeviceFromClipper() получает несколько параметров и заслуживает более пристального изучения. Вот как выглядит вызов этой функции из функции CreateDevice():
r = d3drm->CreateDeviceFromClipper(clipper, GetGUID(), rect.right, rect.bottom, &device);Первый аргумент CreateDeviceFromClipper() — это указатель на интерфейс DirectDrawClipper. Второй аргумент — значение, возвращаемое функцией GetGUID(). Эту функцию мы опишем после того, как завершим знакомство с функцией CreateDevice().
Третий и четвертый аргументы — ширина и высота клиентской области окна. Благодаря этому, функция CreateDeviceFromClipper() создает устройство точно соответствующее размерам окна.
СОВЕТ | Изменение размеров окна. Размер устройства Direct3D не может быть изменен. Если изменяются размеры окна необходимо уничтожить существующее устройство, после чего создать новое в соответствии с новыми размерами окна. О том, как это делается, мы поговорим в разделе, посвященном описанию функции RMWin::OnSize(). |
В качестве последнего аргумента передается адрес переменной устройства, что дает возможность инициализировать указатель на новое устройство.
После того, как устройство создано, настроим его параметры для применения визуализации по методу Гуро, посредством функции SetQuality():
device->SetQuality(D3DRMRENDER_GOURAUD);Сразу после создания устройства при визуализации по умолчанию используется равномерная закраска. Мы изменяем этот параметр, чтобы наши программы при визуализации по умолчанию применяли метод Гуро. В дальнейшем, при создании специализированных интерфейсов приложения, этот параметр можно переопределить.
Следующая задача, выполняемая функцией CreateDevice(), — получение глубины пикселей текущего видеорежима:
HDC hdc = ::GetDC(m_hWnd); int bpp = ::GetDeviceCaps(hdc, BITSPIXEL); ::ReleaseDC(m_hWnd, hdc);Функция GetDeviceCaps() вызывается, чтобы присвоить значение, равное количеству битов, используемых для представления одного пиксела, переменной bpp. Это значение определяет максимальное количество одновременно выводимых цветов для текущего видеорежима. Затем значение переменной bpp используется в операторе switch, устанавливающем некоторые параметры объектов Direct3DRMDevice и Direct3DRM. Оптимальные значения параметров зависят от конкретного приложения. Значения, используемые в функции CreateDevice() являются хорошей отправной точкой, но только экспериментирование позволит добиться наилучших результатов для вашего приложения.
Затем, с помощью функции CreateFrame() интерфейса Direct3DRM создается корневой фрейм сцены:
r = d3drm->CreateFrame(NULL, &scene);С технической точки зрения, корневой фрейм является частью сцены и должен создаваться в той части кода, которая зависит от приложения. Однако на практике все сцены содержат корневой фрейм, поэтому его создание в общей части вполне оправдано.
Далее вызывается функция CreateScene():
if (CreateScene() == FALSE) { AfxMessageBox("CreateScene() failed"); return FALSE; }Функция CreateScene() переопределяется в классе SampleWin, чтобы создавать необходимые для конкретного приложения сцены. Функция CreateScene() может применяться для создания любой сцены, но с одним условием: в ней должны инициализироваться переменные camera и viewport. Чуть позже мы подробнее рассмотрим функцию CreateScene().
Последние четыре строки функции CreateDevice() выглядят следующим образом:
CreateScene(); ASSERT(camera); ASSERT(viewport); return TRUE;Макроопределение ASSERT проверяет инициализированы ли переменные camera и viewport и прерывает выполнение приложения с соответствующим сообщением, если этого не было сделано.
Наконец, функция CreateDevice() возвращает TRUE. Если вы вернетесь к листингу 4.1, то увидите, что при возникновении какой-либо ошибки, макроопределение TRACE выводит сообщение и возвращается FALSE. Возврат значения FALSE уведомляет класс RMWin о необходимости прервать исполнение приложения.
Рассматривая функцию CreateDevice(), мы узнали, что в конце она вызывает функцию CreateScene(). Функция CreateScene() класса RMWin объявлена как чисто виртуальная. Это значит, что классы, производные от RMWin должны предоставлять свою версию функции CreateScene(). Функция CreateScene() отвечает за создание любых сеток, источников света и иерархий фреймов, отображаемых приложением.
Перед тем, как рассмотреть функцию CreateScene(), необходимо упомянуть, что класс SampleWin наследует от класса RMWin несколько важных переменных. Функция CreateScene() может обращаться к этим переменным, поскольку они объявлены как защищенные члены класса. Перечислим эти переменные:
d3drm Это указатель на интерфейс Direct3DRM созданный функцией RMWin::OnCreate(). Мы будем применять этот указатель при создании конструкторов сеток, источников света, фреймов и других объектов Direct3D.
device Это указатель на интерфейс Direct3DRMDevice. Он применяется для задания параметров устройства, таких как наилучшее качество визуализации. Указатель device также используется для создания порта просмотра.
scene Переменная scene представляет собой указатель на интерфейс Direct3DRMFrame который служит как корневой фрейм нашей сцены. Все создаваемые объекты будут присоединяться к фрейму сцены.
camera Это также указатель на интерфейс Direct3DRMFrame. В отличие от переменных d3drm, device и scene, переменная camera не инициализируется в классе RMWin. Мы должны инициализировать ее, создав фрейм камеры. Ориентация и местоположение, заданные для фрейма камеры будут определять ориентацию зрителя и направление просмотра сцены.
viewport Переменная viewport— это указатель на интерфейс Direct3DRMViewport. Этот указатель, также как и указатель camera неинициализирован. Мы выполним его инициализацию посредством функции CreateViewport() интерфейса Direct3DRM.
Листинг 4.3 содержит код функции CreateScene() нашего приложения.
Листинг 4.3. Функция SampleWin::CreateScene() |
BOOL SampleWin::CreateScene() { HRESULT r; // ------СЕТКА-------- d3drm->CreateMeshBuilder(&meshbuilder); r = meshbuilder->Load(meshname, NULL, D3DRMLOAD_FROMFILE, NULL, NULL); if (r != D3DRM_OK) { CString msg; msg.Format("Failed to load file '%s'\n", meshname); AfxMessageBox(msg); return FALSE; } ScaleMesh(meshbuilder, D3DVALUE(25)); LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.1)); meshframe->Release(); meshframe = 0; // --------ЗОНАЛЬНЫЙ СВЕТ-------- LPDIRECT3DRMLIGHT slight; d3drm->CreateLightRGB(D3DRMLIGHT_SPOT, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &slight); LPDIRECT3DRMFRAME slightframe; d3drm->CreateFrame(scene, &slightframe); slightframe->AddLight(slight); slightframe->SetPosition (scene, D3DVALUE(0),D3DVALUE(20),D3DVALUE(-20)); slightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-20), D3DVALUE(20), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); slightframe->AddMoveCallback(MoveLight, NULL); slight->Release(); slight = 0; slightframe->Release(); slightframe = 0; //------ КАМЕРА ---------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; } |
Показанная на листинге 4.3 функция CreateScene() выполняет следующие действия:
Создание сетки и настройка ее параметров. Создание источника зонального освещения и настройка его параметров. Создание и настройка порта просмотра.Функция GetGUID() применяется для получения GUID (глобального уникального идентификатора) идентифицирующего создаваемое функцией CreateDeviceFromClipper() устройство. Если вместо вызова функции GetGUID() использовать значение NULL, функция CreateDeviceFromClipper() автоматически выберет устройство с цветовой моделью Ramp. Мы вызываем функцию GetGUID() чтобы иметь возможность выбрать цветовую модель Ramp или RGB. Текст функции GetGUID() приведен в листинге 4.2.
Листинг 4.2. Функция GetGUID() |
GUID* RMWin::GetGUID() { static GUID* lpguid; HRESULT r; D3DFINDDEVICESEARCH searchdata; memset(&searchdata, 0, sizeof searchdata); searchdata.dwSize = sizeof searchdata; searchdata.dwFlags = D3DFDS_COLORMODEL; searchdata.dcmColorModel = colormodel; static D3DFINDDEVICERESULT resultdata; memset(&resultdata, 0, sizeof resultdata); resultdata.dwSize = sizeof resultdata; LPDIRECTDRAW ddraw; r = DirectDrawCreate(NULL, &ddraw, NULL); if (r != DD_OK) { TRACE("DirectDrawCreate failed\n"); return NULL; } LPDIRECT3D d3d; r = ddraw->QueryInterface(IID_IDirect3D, (void**)&d3d); if (r != D3DRM_OK) { TRACE("d3drm->QueryInterface failed\n"); ddraw->Release(); return NULL; } r = d3d->FindDevice(&searchdata, &resultdata); if (r == D3D_OK) lpguid = &resultdata.guid; else { TRACE("FindDevice failure\n"); lpguid = NULL; } d3d->Release(); ddraw->Release(); return lpguid; } |
Перед тем, как продолжить, посмотрим, почему функция GetGUID() такая сложная. Наша задача — получить GUID для заданного устройства Direct3D. Казалось бы, интерфейс Direct3DRM должен предоставлять функцию, выполняющую эту работу. Возможно, библиотека Direct3D и могла бы быть разработана таким способом, но этого не произошло.
Мы применяем абстрактный режим Direct3D, а абстрактный режим в свою очередь зависит от непосредственного режима, который фактически выполняет визуализацию. Это означает, что устройство Direct3D является конструкцией непосредственного режима, и для поиска требуемого устройства необходимо воспользоваься интерфейсом непосредственного режима. Функции непосредственного режима доступны через COM-интерфейс Direct3D.
Мы можем получить указатель на интерфейс Direct3D создав интерфейс DirectDraw и вызвав его функцию QueryInterface(). Это возможно, поскольку объект DirectDraw начиная с DirectX версии 2 поддерживает интерфейс Direct3D. Если бы мы работали с DirectX версии 1, такой вызов QueryInterface() закончился бы неудачей.
Давайте взглянем на начало функции GetGUID() (листинг 4.2). Первое, что делает функция, — это подготовка двух структур, применяемых для хранения информации об устройстве. Структура searchdata используется для указания запрашиваемой цветовой модели, а в структуре resultdata хранятся результаты поиска. Ниже приведен фрагмент программы, создающий и инициализирующий эти структуры:
D3DFINDDEVICESEARCH searchdata; memset(&searchdata, 0, sizeof searchdata); searchdata.dwSize = sizeof searchdata; searchdata.dwFlags = D3DFDS_COLORMODEL; searchdata.dcmColorModel = colormodel; static D3DFINDDEVICERESULT resultdata; memset(&resultdata, 0, sizeof resultdata); resultdata.dwSize = sizeof resultdata;Функция memset() обнуляет все поля, после чего полю dwSize присваивается размер структуры.
СОВЕТ | Поле dwSize. Требование, чтобы поле структуры содержало размер самой структуры, может показаться довольно глупым, но это сделано, чтобы позволить расширение функциональности без модификации старых программ. Microsoft может увеличить размер структуры в будущей версии Direct3D, а программы, написанные сегодня, будут работать, потому что Direct3D сможет определить, основываясь на значении поля dwSize, какая версия структуры используется. |
Полю dwFlags присваивается значение константы D3DFDS_COLORMODEL — это указывает, что единственным критерием поиска является заданная цветовая модель устройства. Полю dcmColorModel присваивается значение переменной colormodel. По умолчанию значение переменной colormodel равно D3DCOLOR_MONO, но оно может быть изменено функцией SetColorModel() (см. выше функцию InitInstance()).
На следующем этапе работы функции GetGUID() создается интерфейс DirectDraw:
LPDIRECTDRAW ddraw; r = DirectDrawCreate(NULL, &ddraw, NULL); if (r != DD_OK) { TRACE("DirectDrawCreate failed\n"); return NULL; }Функция DirectDrawCreate() применяется для получения указателя на интерфейс DirectDraw. Если вызов функции завершается неудачно, макроопределение TRACE отображает соответствующее сообщение, после чего возвращается NULL.
Как только указатель на интерфейс DirectDraw получен, у объекта запрашивается интерфейс Direct3D:
LPDIRECT3D d3d; r = ddraw->QueryInterface(IID_IDirect3D, (void**)&d3d); if (r != D3DRM_OK) { TRACE("d3drm->QueryInterface failed\n"); ddraw->Release(); return NULL; }Константа IID_IDirect3D представляет собой GUID интерфейса Direct3D и применяется здесь, чтобы указать функции QueryInterface() какой интерфейс мы ищем. Если функция завершается нормально, переменная d3d указывает на экземпляр интерфейса Direct3D. При возникновении ошибки выводится отладочное сообщение и функция возвращает NULL предварительно освободив интерфейс DirectDraw.
Теперь мы можем выполнить поиск GUID устройства:
r = d3d->FindDevice(&searchdata, &resultdata); if (r == D3D_OK) lpguid = &resultdata.guid; else { TRACE("FindDevice failure\n"); lpguid = NULL; }Функция FindDevice() интерфейса Direct3D получает в качестве аргументов указатели на две подготовленные ранее структуры. Если функция возвращает D3D_OK, значит GUID найден и переменной lpguid присваивается указатель на него.
Обратите внимание, что структура resultdata и переменная lpguid объявлены как статические. Это сделано потому, что GUID представляет собой 128-разрядное значение. Объявив эти переменные статическими, мы можем возвратить указатель на GUID, а не делать копию его значения.
Перед завершением работы функция GetGUID() освобождает интерфейсы DirectDraw и Direct3D.
Мышь — это неотъемлемая часть интерфейса Windows и пользователи ожидают, что программа будет поддерживать работу с мышью так же, как она поддерживает работу с клавиатурой. Функции GetMouseX() и GetMouseY(), предоставляемые классом RMWin, позволяют в любой момент времени определить текущие координаты указателя мыши. Обе функции возвращают значение соответствующей координаты в пикселях. Функции объявлены как статические и могут использоваться функциями обратного вызова.
Функция 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 о невозможности инициализации приложения.
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() наследуется классом 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++. |
Перед завершением работы приложения 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; } }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 об успешном завершении работы.
Интерфейс 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 раз в секунду, результат будет раздражать, независимо от корректности отображения анимации.
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() вызывается 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(). После создания и настройки параметров нового устройства, создается новый порт просмотра.
В функции 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. Полученные данные используются для нахождения максимального размера сетки и вычисления коэффициента масштабирования, обеспечивающего требуемое изменение размера. После проведения вычислений функция осуществляет масштабирование объекта.
Пришло время приниматься за работу. Мы можем сколько угодно говорить о терминологии и о программных моделях, но ничего не добьемся, пока не начнем программировать.
В этой главе мы рассмотрим структуру программы, использующей Direct3D. Мы обсудим последовательность событий, происходящих при инициализации и управлении интерфейсами Direct3D. Затем мы обсудим дизайн классов и функции, выполняющие работу.
Ранее в этой главе мы определили порядок, в котором должны создаваться интерфейсы Direct3D. Рис.4.4 иллюстрирует необходимый порядок действий, а также показывает, какие этапы являются стандартными, а какие зависят от приложения.
Теперь, когда мы познакомились с выбранной структурой классов, настало время пересмотреть порядок создания интерфейсов с учетом списка классов и функций-членов. Рис. 4.6. почти полностью повторяет рис. 4.4, за исключением того, что на нем указаны функции, ответственные за каждый из этапов.
Рис. 4.6. Порядок создания интерфейсов с разделением по категориям
Как и было сказано, класс RMWin создает стандартные объекты, а класс SampleWin создает объекты, зависящие от приложения.
Теперь давайте рассмотрим все участвующие в инициализации программы функции в порядке их вызова.
В начале этой главы, создавая с помощью мастера Direct3D AppWizard приложение Sample, мы указали, что хотим использовать анимированный зональный источник света. Мастер AppWizard добавил в проект код, изменяющий ориентацию источника света во время выполнения программы.
Это изменение выполняет функция обратного вызова. Функции обратного вызова — это функции, которые Direct3D вызывает каждый раз, когда собирается выполнить системное обновление. Такие функции могут применяться для изменения параметров во время выполнения программы.
Когда в функции CreateScene() мы создавали зональный источник света, мы установили для него функцию обратного вызова с именем MoveLight(). Установка функции обратного вызова выглядит следующим образом:
slightframe->AddMoveCallback(MoveLight, NULL);Функция AddMoveCallback() — это функция интерфейса Direct3DRMFrame. Первый ее аргумент — это указатель на функцию, которая будет вызываться при каждом обновлении. Второй параметр представляет собой указатель на данные, которые будут передаваться функции обратного вызова. Эти дополнительные данные являются необязательными, поэтому мы передаем NULL.
Функция MoveLight() изменяет ориентацию зонального источника света и выглядит следующим образом:
void SampleWin::MoveLight(LPDIRECT3DRMFRAME lightframe, void*, D3DVALUE) { // перемещение прожектора над сеткой static const D3DVALUE lim = D3DVALUE(0.3); static D3DVALUE xi = D3DVALUE(0.01); static D3DVALUE yi = D3DVALUE(0.005); static D3DVALUE x, y; if (x < -LIM || x > lim) xi = -xi; if (y < -LIM || y > lim) yi = -yi; x += xi; y += yi; lightframe->SetOrientation(NULL, x, y-1, D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); }Для вычисления новой ориентации источника света в функции используется простой алгоритм «подпрыгивающего мяча». Перемещение источника света ограничивается константой lim и изменяется на значения xi и yi. После вычисления новой ориентации, она назначается фрейму с помощью функции SetOrientation().
Функции обратного вызова всегда объявляются статическими, как показано ниже:
class SampleWin : public RMWin { // ... private: static void MoveLight(LPDIRECT3DRMFRAME frame, void* arg, D3DVALUE delta); // ... };Это требование, объясняется тем, что обычные функции-члены класса требуют неявного указателя на класс. Объявление функции статической снимает это требование, но означает, что функции обратного вызова не могут обращаться к функциям-членам класса. По этой причине функция AddMoveCallback() предоставляет способ передачи дополнительных данных в функцию обратного вызова.
Функции обратного вызова, устанавливаемые с помощью AddMoveCallback() получают три параметра. Первый — это указатель на интерфейс фрейма для которого назначена данная функция обратного вызова. Второй параметр — это указатель на необязательные дополнительные данные. Третий параметр — это значение, полученное функцией Tick(). Помните, что функция Tick() может применяться для замедления и ускорения анимации в программе. Если в качестве параметра функции Tick() вы всегда используете 1.0, то можете спокойно игнорировать третий параметр.
К этому моменту мы инициализировали стандартные интерфейсы Direct3D. Все обсужденные ранее этапы выполнялись классами RMApp и RMWin. Теперь настало время создать сцену.
В главе1 вы познакомились с мастером Direct3D AppWizard. Напомню, Direct3D AppWizard — это нестандартный инструмент Visual C++, позволяющий создавать завершенные приложения Direct3D. Мастер AppWizard предоставляет ряд диалоговых окон, применяемых для задания параметров нового приложения. Можно выбрать объекты, которые будут отображаться приложением, используемые источники освещения, и даже имена классов.
Код, генерируемый мастером AppWizard, и будет разбираться в этой главе. С помощью мастера мы создадим приложение, и будем изучать его код на протяжении всей этой главы. Создание всех учебных программ в этой книге начинается с кода, генерируемого мастером Direct3D AppWizard, поэтому общая структура всех приложений будет одинаковой. Если вы поймете все, что будет обсуждаться в этой главе, то вы прекрасно поймете и все демонстрационные программы книги.
Ничто так не способствует изучению материала, как свободное экспериментирование. Одна из замечательных особенностей разрабоки программного обеспечения заключается в том, что экспериментирование является легким и необременительным. Если бы мы были архитекторами или ядерными физиками, наши ошибки могли бы привести к серьезным разрушениям и принести ущерб в миллионы долларов. Наиболее серьезным последствием ошибки разработчика программного обеспечения обычно является перезагрузка компьютера.
Поэкспериментируйте с кодом примера Sample. Поэкспериментируйте с другими демонстрационными программами на CD-ROM. Попытайтесь добавить к сцене еще несколько сеток. Поэкспериментируйте с цветными источниками света (не забывайте об использовании цветовой модели RGB).
Когда вы закончите эксперименты, настанет время для чтения главы 5, в которой вы узнаете о текстурах и их наложении.
Дерево зависимостей определяет порядок создания интерфейсов. Например, объект Direct3DRM располагается на вершине дерева, поэтому все остальные объекты зависят от его существования. Это значит, что объект Direct3DRM должен создаваться первым. После того, как он создан, можно создать фрейм сцены или объект отсечения. Процесс продолжается до тех пор, пока не будут созданы все объекты. На рис. 4.4 показан порядок создания объектов, соответствующий требованиям дерева зависимостей.
Рис. 4.4. Порядок создания интерфейсов
Из рисунка видно, что стандартные интерфейсы создаются в первую очередь, а после них создаются специализированные интерфейсы приложения. Это не единственный возможный порядок создания, поскольку некоторые пары интерфейсов, например, интерфейсы meshbuilder и meshframe, можно поменять местами, однако рассматриваемый далее код придерживается именно этого порядка.
Для представления цветов Windows использует значения типа COLORREF. В Direct3D для той же цели применяется тип D3DCOLOR. Эти два типа несовместимы, и поэтому класс RMWin предоставляет функции преобразования типов. Функция COLORREF_2_D3DCOLOR() преобразует тип, используемый в Windows, в тип, понятный Direct3D. Функция D3DCOLOR_2_COLORREF() выполняет обратное преобразование. Код этих функций выглядит так:
inline D3DCOLOR RMWin::COLORREF_2_D3DCOLOR(COLORREF cref) { D3DVALUE r = D3DVALUE(GetRValue(cref))/D3DVALUE(255); D3DVALUE g = D3DVALUE(GetGValue(cref))/D3DVALUE(255); D3DVALUE b = D3DVALUE(GetBValue(cref))/D3DVALUE(255); return D3DRMCreateColorRGB(r, g, b); } inline COLORREF RMWin::D3DCOLOR_2_COLORREF(D3DCOLOR d3dclr) { D3DVALUE red = D3DVALUE(255) * D3DRMColorGetRed(d3dclr); D3DVALUE green = D3DVALUE(255) * D3DRMColorGetGreen(d3dclr); D3DVALUE blue = D3DVALUE(255) * D3DRMColorGetBlue(d3dclr); return RGB((int)red, (int)green, (int)blue); }Для улучшения производительности обе функции объявлены как inline.
СОВЕТ | ?Вопросы быстродействия. В общем случае, беспокойство о производительности приложений, использующих такие пакеты, как Direct3D подобно перестановке кресел на Титанике. Девяносто девять процентов работы в использующей Direct3D программе, выполняется непосредственно Direct3D. Код приложения составляет незначительную часть общей картины, и чтобы он сильно ухудшил производительность, его надо действительно очень плохо написать. |
Мы используем мастер Direct3D AppWizard для создания проекта, который назовем Sample. В главе 1 был приведен краткий обзор действий, необходимых для создания нового проекта, так что здесь мы повторять их не будем. Мы примем все, предлагаемые мастером значения по умолчанию, но с одним исключением: вместо применяемого по умолчанию источника направленного освещения, мы создадим анимированный источник зонального освещения. На рис. 4.1 показано диалоговое окно Lighting мастера Direct3D AppWizard.
Рис. 4.1. Диалоговое окно выбора источника освещения в мастере Direct3D AppWizard
Очистите флажок Directional, установите флажок Spotlight, а затем установите флажок Animate spotlight. Рисунок показывает выбранные параметры.
В остальных диалоговых окнах примите предлагаемые значения по умолчанию. Мастер создаст готовый к компиляции проект.
Прежде чем перейти к детальному обсуждению используемых функций, разделим их на четыре категории. Каждая категория соответствует стадии, или фазе, выполнения программы.
Инициализация Direct3D. Создание сцены. Управление сценой. Завершение работы.Большая часть материалов этой главы посвящена первому этапу, потому что он наиболее сложный (по крайней мере, пока). После инициализации работа с Direct3D не представляет сложностей. Второй и третий этапы детально обсуждаются в следующих пяти главах. Завершающая стадия представляет собой просто удаление всех созданных интерфейсов.
Давайте отвлечемся от COM-интерфейсов Direct3D и поговорим о классах C++. Мы будем применять классы C++ для управления сложностью программ. Эти классы не являются заменой интерфейсов Direct3D. Классы C++ будут содержать интерфейсы Direct3D и управлять ими.
Программы из этой книги используют библиотеку MFC. Стратегия состоит в использовании функциональности MFC без поддержки архитектуры документ/представление. Мы воспользуемся двумя классами MFC: CWinApp и CFrameWnd. Класс CWinApp представляет приложение Windows, а класс CFrameWnd представляет окно приложения. Класс CWinApp будет применяться как базовый для класса конкретного приложения, а класс CFrameWnd будет базовым для класса окна конкретного приложения Direct3D. Класс, производный от CWinApp мы назовем RMApp (RM от Retained Mode), а класс, производный от CFrameWnd соответственно назовем RMWin.
Однако, мы не станем помещать всю функциональность приложения в эти два класса. Вместо этого в них мы поместим стандартную функциональность Direct3D — те функции, которые остаются неизменными от приложения к приложению. Затем мы создадим еще два класса, в которые поместим код, специфичный для данного приложения. Имена этих классов вы можете задать сами (мастер создания приложений предложит вам стандартные варианты названий, но вы можете изменить их). Для рассматриваемого в этой главе приложения Sample мы назовем эти классы SampleApp и SampleWin. Рис. 4.5 представляет дерево наследования для классов нашего приложения.
Рис. 4.5. Функциональность классов и наследование
На рисунке изображены четыре класса, о которых еще ничего не говорилось: CObject, CCmdTarget, CWinThread и CWnd. CObject — это базовый класс MFC. Почти каждый класс MFC является производным от CObject. Класс CCmdTarget реализует большую часть предоставляемых MFC возможностей обработки сообщений. Производные от CCmdTarget классы наследуют возможность использования карт сообщений. Класс CWinThread предоставляет поддержку многопоточности. Класс CWnd — это класс окна в MFC, обеспечивающий поддержку большинства функций окна.
Второе действие, выполняемое функцией CreateScene(),— создание источника зонального света:
LPDIRECT3DRMLIGHT slight; d3drm->CreateLightRGB(D3DRMLIGHT_SPOT, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &slight);Функция CreateLightRGB() интерфейса Direct3DRM применяется для создания источника света slight (s — сокращение от spotlight, т.е. прожектор). Константа D3DRMLIGHT_SPOT задает тип создаваемого источника света. Другие возможные значения — D3DRMLIGHT_AMBIENT, D3DRMLIGHT_DIRECTIONAL, D3DRMLIGHT_PARALLELPOINT и D3DRMLIGHT_POINT.
Затем создается фрейм с именем slightframe:
LPDIRECT3DRMFRAME slightframe; d3drm->CreateFrame(scene, &slightframe);Этот фрейм, аналогично фрейму конструктора сеток, использует в качестве родителя фрейм scene. Затем, посредством функции SetPosition() задается местоположение нового фрейма:
slightframe->SetPosition (scene, D3DVALUE(0),D3DVALUE(20),D3DVALUE(-20));Первый аргумент задает систему координат, а остальные указывают новое местоположение фрейма. Функция SetPosition() для определения нового местоположения фрейма использует систему координат указанного фрейма. В данном примере в качестве системы координат используется корневой фрейм сцены, поэтому задающие местоположение значения указывают абсолютную позицию. Корневой фрейм расположен в начале координат, поэтому источник света будет помещен на 20 единиц выше начала координат и на 20 единиц позади. Если бы, например, мы указали те же самые значения, но в качестве системы координат задали фрейм, расположенный в точке <0, 100, 0>, позиция нового фрейма была бы <0, 120, –20>.
Далее, с помощью функции SetOrientation() задается ориентация фрейма slightframe:
slightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-20), D3DVALUE(20), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0));Подобно функции SetPosition(), функция SetOrientation() требует, чтобы в качестве первого аргумента передавалась ссылка на фрейм, однако, SetOrientation() требует наличия шести дополнительных аргументов. Эти шесть значений определяют два вектора, задающих новую ориентацию фрейма. Первый вектор задает направление для лицевой грани фрейма. По умолчанию фрейм расположен вдоль оси Z и направлен от зрителя. По этой причине первый вектор называют вектором оси Z. Второй вектор называют вектором оси Y потому что по умолчанию он направлен вверх параллельно оси Y.
Возможно, более интуитивно понятными для этих векторов будут названия передний (forward) и верхний (up) вектор. Передний вектор указывает, куда направлена лицевая сторона фрейма. Верхний вектор указывает куда направлена верхняя грань фрейма (этот вектор иногд называют небесным (sky) вектором).
В нашем коде мы используем передний вектор <0, –20, 20>. Это значит, что лицевая грань фрейма направлена к точке <0, –20, 20>, таким образом, лицевая грань фрейма будет на 20 единиц ниже и на 20 единиц ближе (относительно начала координат). В качестве верхнего вектора мы оставим предлагаемый по умолчанию вектор <0, 1, 0>. Мы подробнее изучим фреймы и их ориентацию в главе 7.
Дальнейшая часть кода функции CreateScene() осуществляет создание и настройку параметров источника зонального света.
slightframe->AddLight(slight); slightframe->AddMoveCallback(MoveLight, NULL); slight->Release(); slight = 0; slightframe->Release(); slightframe = 0;Функция AddLight() применяется для присоединения источника света к только что настроеному фрейму.
Затем вызывается функция AddMoveCallback(). Эта функция применяется для установки функции обратного вызова, которая будет использоваться для изменения позиции и ориентации фрейма во время выполнения программы. Мы поговорим об обратных вызовах и о функции AddMoveCallback() позже в этой главе.
В конце происходит освобождение указателей slight и slightframe чтобы уведомить объекты, что эти указатели больше не будут использоваться (их область видимости ограничена функцией CreateScene()).
Третий и последний этап, реализуемый функцией 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);Перед созданием порта просмотра необходимо создать фрейм, который будет использоваться для задания местоположения и ориентации порта просмотра. Вызов функции CreateFrame() интерфейса Direct3DRM инициализирует указатель camera. Вспомните, что указатель camera предоставляется классом RMWin поэтому в функции CreateScene() он не объявляется.
Местоположение фрейма camera задается посредством функции SetPosition(). Местоположение и ориентация фрейма камеры определяют точку с которой будет просматриваться сцена. Вспомните, что при создании сетки и ее привязке к фрейму мы не изменяли местоположение фрейма. Это значит, что сетка отображается в начале координат <0, 0, 0>. Если мы хотим увидеть эту сетку в созданной области просмотра необходимо сместить фрейм камеры от начала координат. Мы используем функцию SetPosition() чтобы разместить фрейм камеры на 50 единиц дальше начала координат.
Область просмотра создается функцией CreateViewport(). Первый аргумент — device был создан функцией CreateDevice(). Второй аргумент — это только что созданный фрейм camera. Следующие четыре аргумента определяют местоположение и размеры области просмотра. Местоположение области просмотра — 0, 0, (верхний левый угол устройства) а размеры совпадают с размерами устройства (вспомните, что при создании устройства мы использовали размеры клиентской области окна). Последний аргумент представляет собой адрес указателя на новую область просмотра, который будет инициализирован по завершении функции.
В конце функция CreateScene() возвращает TRUE что указывает классу RMWin на успешное создание сцены.
Первый этап требует создания указателя на интерфейс Direct3DRMMeshBuilder. Функция Load() интерфейса Direct3DRMMeshBuilder применяется для загрузки сетки из файла. Если вызов функции Load() завершается неудачно (файл отсутствует или имеет неправильный формат), выводится окно сообщения и функция возвращает FALSE, сигнализируя о необходимости прекратить выполнение приложения. Давайте взглянем на эту часть кода:
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(). ScaleMesh() — это удобная функция, предоставляемая классом RMWin. Мы используем здесь эту функцию для масштабирования сетки, чтобы гарантировать корректное отображение сетки, независимо от ее оригинальных размеров. Функция ScaleMesh() будет описана позже в этой главе.
Затем конструктор сеток добавляется к фрейму. Эта часть кода выглядит следующим образом:
LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0.1)); meshframe->Release(); meshframe = 0;Функция CreateFrame() интерфейса Direct3DRM применяется для инициализации указателя meshframe. Обратите внимание, что в качестве первого аргумента функции CreateFrame() передается указатель на фрейм scene. Это означает, что новый фрейм (meshframe) будет дочерним для фрейма scene.
Затем вызывается функция AddVisual() для присоединения конструктора сеток meshbuilder к новому фрейму. К одному фрейму можно добавить несколько сеток, но обычно к одному фрейму присоединяется только одна сетка.
Вызов следующей функции назначает фрейму атрибуты вращения. В качестве аргументов функции SetRotation() передаются вектор и угол поворота. В нашем примере вектор направлен вдоль оси Y, а угол поворота равен 0.1. Поскольку сетка присоединена к фрейму, которому назначено вращение, она будет поворачиваться вокруг оси Y на 0.1 радиан при каждом обновлении экрана.
После того, как вращение назначено, вызывается функция фрейма Release(). Помните, что Release() не уничтожает объект, а уменьшает внутренний счетчик ссылок объекта. Эта функция должна вызываться всякий раз, когда указатель на интерфейс больше не нужен. Объект сам решает, когда уничтожить себя. Здесь мы вызываем функцию Release() потому, что указатель на данный интерфейс нам больше не нужен.
Показанное на рис. 4.2 дерево зависимостей может быть разделено на две части. Узлы вблизи корня представляют интерфейсы, присутствующие во всех приложениях Direct3D, а остальные узлы представляют интерфейсы меняющиеся от приложения к приложению. Мы назовем первую группу стандартными интерфейсами (standard interfaces), а вторую — специализированными интерфейсами приложения (application specific interfaces). На рис. 4.3 отображено данное разделение дерева.
Рис. 4.3. Отделение стандартных интерфейсов от специализированных интерфейсов приложения
Рисунок показывает, что создающие сцену интерфейсы зависят от приложения. Это разделение будет играть важную роль позднее, когда мы будем говорить о структуре классов. Наша цель состоит в том, чтобы автоматизировать создание стандартных интерфейсов, учитывая при этом специализированные интерфейсы приложения, которые будут создаваться, и изменяться согласно предъявляемым к приложению требованиям.
До сих пор мы обсуждали инициализацию Direct3D и создание сцены в нашей программе. Теперь мы обратим внимание на то, что происходит во время выполнения программы.
Некоторые из упомянутых нами ранее функций не относятся к функциям Win32, MFC или Direct3D. Это вспомогательные функции, предоставляемые классом RMWin. Теперь мы рассмотрим эти функции подробнее.
Инициализация Direct3D, создание сцен, и выполнение анимации в реальном времени настолько увлекательны, что не хочется останавливать программу, однако рано или поздно это придется сделать.
В главе3 обсуждались COM-интерфейсы Direct3D и обеспечиваемая ими функциональность. Во время обсуждения вы узнали, что некоторые интерфейсы должны создаваться раньше, а другие позже. Интерфейсы должны создаваться в определенном порядке. В этом разделе мы узнаем об этих зависимостях и о том, как они влияют на программы.