Функция 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() вызывается для инициализации окна и ей передаются три аргумента. Первый аргумент — это строка, отображаемая в заголовке окна. Второй — идентификатор ресурса для значка приложения, а третий — идентификатор ресурса для меню.
Пришло время взглянуть на функцию 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-буфер должны быть освобождены перед созданием новых. Вторичную поверхность освобождать не требуется, поскольку она будет уничтожена вместе с первичной поверхностью.
Код функции 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; } |
СОВЕТ | Тип ресурса MESH. Нет ничего необычного в типе ресурса MESH (Visual C++ ничего не знает о сетках Direct3D). Приложения из этой книги хранят сетки в группе ресурсов с именем MESH, чтобы отделить ресурсы сеток от других ресурсов. |
СОВЕТ | Проверка возвращаемых значений. Большинство функций Direct3D возвращают значение типа HRESULT, указывающее на состояние функции. Как правило, не требуется проверять возвращаемое значение для каждой функции, но рекомендуется проверять возвращаемые значения для тех функций, которые могут завершиться неудачно из-за внешних причин. Например, функции загрузки файла часто завершаются неудачно, из-за того, что не могут найти требуемый файл. В данном сучае неудачное завершение функции Load() маловероятно, поскольку сетка является частью EXE-файла программы. |
СОВЕТ | Остерегайтесь висящих указателей. Я рекомендую вам избегать висящих указателей, присваивая им нулевые значения после освобождения. Эта привычка спасет вас от проблем, которые могут возникнуть, если вы нечаянно используете освобожденный указатель. |
СОВЕТ | Задание абсолютных значений. Существует два способа указать Direct3D, что заданные значения являются абсолютными, а не относительными. Первый способ — указать в качестве базового корневой фрейм сцены (как сделано в рассматриваемом коде). Второй — передать вместо указателя на базовый фрейм NULL. |
Параметр | Значение |
Начало координат наложения | <0.0, 0.0, 0.0> |
Ось Z наложения | <0.0, 0.0, 1.0> |
Ось Y наложения | <0.0, 1.0, 0.0> |
Параметр | Значение |
Начало координат текстуры | <0.5, 0.5> |
Масштаб текстуры | <D3DDivide(1,w), D3DDivide(1,h)> |
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.
Перед тем, как мы закончим изучение приложения 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 завершает работу приложения.
Функция 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; } |
Как мы отмечали при рассмотрении функции 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.
Функция 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.
Функция 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(). Выражение, применяемое для вычисления скорости обеспечивает случайный выбор значения в диапазоне от медленной до средней скорости.
Функция 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() прекратит вызывать сама себя.
Начальная сцена приложения 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() выполняет следующие действия:
Инициализирует генератор случайных чисел.
Создает шесть сеток.
Создает иерархию фреймов с присоединенными к ним сетками.
Создает источник света.
Создает порт просмотра.
Функция 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.
Функция 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() чтобы получить указатель на вновь созданную трансформируемую сетку. Цвет сетки изменяется и сетка добавляется к сцене.
Выбор команды 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() и имя выбранного файла передается ей как аргумент.
Воспроизведением последовательности трансформаций управляет функция обратного вызова 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(). В результате выполняется вычисление новых данных вершин и производится обновление трансформируемой сетки.
Функция 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.
Функция 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 применяются для вычисления нормалей вершин.
Функция GetMorphMesh() просто возвращает указатель на трансформируемую сетку. Она определена в объявлении класса следующим образом:
LPDIRECT3DRMMESH GetMorphMesh() { return morphmesh; }
Данная функция позволяет классам, наследуемым от MorphWin отображать сетку и изменять ее параметры. Как это делается мы увидим ниже.
Функция GetNumMorphTargets() определена в объявлении класса MorphWin:
DWORD GetNumMorphTargets() { return nummorphtargets; }
Функция просто возвращает значение, хранящееся в переменной класса nummorphtargets.
Функция 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.
Функция 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.
Функция PrepareMorphVertices() выделяет память для массива, который будет использоваться для хранения вычисленных данных вершин:
BOOL MorphWin::PrepareMorphVertices() { if (morphvertex) { delete [] morphvertex; morphvertex = 0; }
morphvertex = new D3DRMVERTEX[nummorphvertices];
return TRUE; }
Как и в двух предыдущих функциях, в случае обнаружения ранее выделенных ресурсов сначала выполняется их освобождение, после чего выделяются новые ресурсы.
Функция 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.
Сцена для приложения 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().
Затем создаются два источника света и присоединяются к собственному фрейму.
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() приложения 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; } |
Функция Render() — это последняя (и простейшая) функция класса RMWin, которую мы рассмотрим. Функция Render() объявлена, но не определена:
virtual void Render() = 0;
Render() — это чисто виртуальная функция. Это означает, что функция Render() должна быть переопределена в классах, производных от RMWin. Функция Render() объявлена таким способом чтобы гарантировать, что производные от RMWin классы будут осуществлять обновление экрана приложения. Помимо прочего, функция Render() должна выполнять переключение страниц.
Функция 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(). Функция 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; } |
Функция 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.
Функция 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()) будет использовать при создании и размещении новых портов просмотра существующие фреймы.
Мы завершили обсуждение кода, который создает и конфигурирует сцену приложения и внутренние компоненты. Теперь посмотрим, что происходит после инициализации.
Важным действием является обновление сцены и визуализация выходных данных во время работы приложения. В других приложениях эту задачу выполняла функция 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.
Код функции 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; } |
СОВЕТ | Простой способ наложения текстур. Данные о наложении текстур могут быть сохранены в файле сетки путем назначения сетке атрибутов размещения текстур в программах визуального моделирования таких, как 3D Studio. По умолчанию данные о размещении текстур импортируются утилитой DirectX CONV3DS. Эта техника является альтернативой методам, рассмотренным в главе 5. |
Функция обратного вызова 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() перемещает и ее.
В функции 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. Полученные данные используются для нахождения максимального размера сетки и вычисления коэффициента масштабирования, обеспечивающего требуемое изменение размера. После проведения вычислений функция осуществляет масштабирование объекта.
Функция 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.
Сцена создается функцией 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; } |
Во время конструирования сцены приложения 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; } |
Функция обратного вызова 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.
Сцена приложения 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; } |
Приложение 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() выполняет следующие действия:
Создает сетку космической станции.
Создает фрейм для сетки космической станции.
Функция 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; } |
В функции 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().
Код функции 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; } |
СОВЕТ |
Нарушение соглашений COM. Обычно, перед завершением функции освобождаются все локальные указатели на интерфейсы Direct3D. В приложении Target мы видим два исключения из этого правила. Указатели animation и targetframe не освобождаются, поскольку используются в функциях обратного вызова. Освобождение этих указателей приведет к тому, что COM уничтожит соответствующие объекты, и обращение к функции обратного вызова приведет к краху программы. Другое возможное решение — оставить вызов функции Release(), но только после вызова функции AddRef(). Благодаря этому COM получает уведомление о создании дополнительной ссылки на объект. Согласно спецификации COM второй метод предпочтительнее. Мы используем первый метод только для того, чтобы сделать код приложения как можно более простым. |