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

         

Еще об анимации


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



Функции меню Animation в классе RocketWin


Меню Animation приложения Rocket предоставляет две команды: Linear и Spline. Для реализации каждой из этих команд применяется две функции. Ниже приведен код всех четырех функций, необходимых для меню Animation:

void RocketWin::OnAnimationLinear() { animation->SetOptions(D3DRMANIMATION_LINEARPOSITION | D3DRMANIMATION_CLOSED | D3DRMANIMATION_POSITION | D3DRMANIMATION_SCALEANDROTATION); } void RocketWin::OnAnimationSpline() { animation->SetOptions(D3DRMANIMATION_SPLINEPOSITION | D3DRMANIMATION_CLOSED | D3DRMANIMATION_POSITION | D3DRMANIMATION_SCALEANDROTATION); } void RocketWin::OnUpdateAnimationLinear(CCmdUI* pCmdUI) { D3DRMANIMATIONOPTIONS options; options = animation->GetOptions(); pCmdUI->SetCheck(options & D3DRMANIMATION_LINEARPOSITION); } void RocketWin::OnUpdateAnimationSpline(CCmdUI* pCmdUI) { D3DRMANIMATIONOPTIONS options; options = animation->GetOptions(); pCmdUI->SetCheck(options & D3DRMANIMATION_SPLINEPOSITION); }

Первые две функции, OnAnimationLinear() и OnAnimationSpline(), вызывают функцию SetOptions() интерфейса Direct3DRMAnimation чтобы задать значения набора флагов. Один из флагов различен в этих двух функциях: в функции OnAnimationLinear() указан флаг D3DRMANIMATION_LINEARPOSITION, а в функции OnAnimationSpline() используется флаг D3DRMANIMATION_SPLINEPOSITION.

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

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

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



Функции управления глубиной иерархии




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

void MoleculeWin::OnDepth1() { curdepth = 1; CreateHierarchy(); } void MoleculeWin::OnUpdateDepth1(CCmdUI* pCmdUI) { pCmdUI->SetCheck(curdepth == 1); }

Первая функция, OnDepth1(), вызывается, когда пользователь выбирает первый пункт в меню Depth. Она присваивает 1 переменной curdepth и вызывает функцию CreateHierarchy() чтобы заново создать всю иерархию фреймов.

Вторая функция, OnUpdateDepth1(), вызывается MFC перед отображением меню Depth. Функция SetCheck() используется для отображения флажка рядом с пунктом меню, соответствующим текущему значению глубины.

Оставшиеся десять функций меню Depth практически полностью идентичны только что рассмотренным. Их единственным отличием является число, используемое при манипуляциях с переменной класса curdepth.



Функции управления скоростью анимации


Приложение Rocket предоставляет меню Speed, позволяющее выбрать одну из трех команд для изменения скорости анимации: Fast, Medium и Slow. Ниже приведены две функции, которые используются для реализации команды Fast меню Speed:

void RocketWin::OnSpeedFast() { speed = fastspeed; } void RocketWin::OnUpdateSpeedFast(CCmdUI* pCmdUI) { pCmdUI->SetCheck(speed == fastspeed); }

Эти функции просто используют константу fastspeed для присваивания и проверки значения члена данных speed. Для контроля скорости выполнения анимационной последовательности в функциях меню Speed используются следующие константы:

const D3DVALUE fastspeed = D3DVALUE(0.026); const D3DVALUE mediumspeed = D3DVALUE(0.013); const D3DVALUE slowspeed = D3DVALUE(0.007);

Большее значение соответствует более высокой скорости анимации. Константа slowspeed имеет очень малое значение, обеспечивающее неторопливую и плавную анимацию.




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


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

BOOL MoleculeWin::CreateChildren(LPDIRECT3DRMFRAME frame, int depth) { LPDIRECT3DRMFRAME parent; frame->GetParent(&parent); D3DVECTOR vector; D3DRMVectorRandom(&vector); frame->SetRotation(parent, vector.x, vector.y, vector.z, D3DVALUE(rand() % 100) / D3DVALUE(1000) + D3DVALUE(.1)); frame->AddVisual(mesh[curdepth - depth]); if (depth > 1) { LPDIRECT3DRMFRAME child; d3drm->CreateFrame(frame, &child); static int count; count++; D3DVALUE trans = distance[curdepth - depth]; D3DVALUE smalltrans = trans / D3DVALUE(2); D3DVALUE xtrans = (count % 2) ? trans : -trans; D3DVALUE ytrans = (rand() % 2) ? smalltrans : -smalltrans; D3DVALUE ztrans = (rand() % 2) ? smalltrans : -smalltrans; child->SetPosition(frame, xtrans, ytrans, ztrans); for (int i = 0; i < numchildren; i++) CreateChildren(child, depth - 1); } return TRUE; }

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

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

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

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

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

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

if (depth > 1) { LPDIRECT3DRMFRAME child; d3drm->CreateFrame(frame, &child); static int count; count++; D3DVALUE trans = distance[curdepth - depth]; D3DVALUE smalltrans = trans / D3DVALUE(2); D3DVALUE xtrans = (count % 2) ? trans : -trans; D3DVALUE ytrans = (rand() % 2) ? smalltrans : -smalltrans; D3DVALUE ztrans = (rand() % 2) ? smalltrans : -smalltrans; child->SetPosition(frame, xtrans, ytrans, ztrans); for (int i = 0; i < numchildren; i++) CreateChildren(child, depth - 1); }

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

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



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


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

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

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

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

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



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


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

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

BOOL MoleculeWin::CreateScene() { // ------- SRAND -------- srand((unsigned)time(NULL)); // ------- СЕТКИ -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER meshbuilder; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); for (int i = 0; i < MAXDEPTH; i++) { ScaleMesh(meshbuilder, D3DVALUE(MAXDEPTH - i)); D3DCOLOR clr = meshcolor[i]; D3DVALUE r = D3DRMColorGetRed(clr); D3DVALUE g = D3DRMColorGetGreen(clr); D3DVALUE b = D3DRMColorGetBlue(clr); meshbuilder->SetColorRGB(r, g, b); LPDIRECT3DRMMESH m; meshbuilder->CreateMesh(&m); mesh[i] = m; } meshbuilder->Release(); meshbuilder = 0; // -------- ИЕРАРХИЯ ФРЕЙМОВ ------ CreateHierarchy(); // --------НАПРАВЛЕННЫЙ СВЕТ-------- LPDIRECT3DRMLIGHT dlight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &dlight); LPDIRECT3DRMFRAME dlightframe; d3drm->CreateFrame(scene, &dlightframe); dlightframe->AddLight(dlight); dlightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); dlight->Release(); dlight = 0; dlightframe->Release(); dlightframe = 0; //------ КАМЕРА ---------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; }

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

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

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

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

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

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

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

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

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

CreateHierarchy();

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

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



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


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

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

BOOL RocketWin::CreateScene() { //-------- СЕТКА ---------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_ROCKETMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, LoadTexture, NULL); ScaleMesh(meshbuilder, D3DVALUE(10)); //-------- АНИМАЦИЯ -------- d3drm->CreateAnimation(&animation); for (int i = 0; i < 11; i++) { D3DRMQUATERNION quat; D3DRMQuaternionFromRotation(&quat, &vect[i], rot[i]); animation->AddRotateKey(D3DVALUE(i), &quat); animation->AddPositionKey(D3DVALUE(i), trans[i].x, trans[i].y, trans[i].z); } OnAnimationLinear(); //-------- ФРЕЙМ СЕТКИ ------ LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->AddMoveCallback(UpdateScene, animation); animation->SetFrame(meshframe); meshframe->Release(); meshframe = 0; //-------- СВЕТ -------- LPDIRECT3DRMLIGHT dlight; LPDIRECT3DRMLIGHT alight; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.5), D3DVALUE(0.5), D3DVALUE(0.5), &alight); d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.0), D3DVALUE(1.0), D3DVALUE(1.0), &dlight); LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-2), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); lightframe->AddLight(dlight); lightframe->AddLight(alight); dlight->Release(); dlight = 0; alight->Release(); alight = 0; lightframe->Release(); lightframe = 0; //-------- ПОРТ ПРОСМОТРА -------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50.0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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



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


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

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

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

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

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



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


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

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

BOOL TargetWin::CreateScene() { // ------- СЕТКА ЦЕЛИ -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER targetbuilder; d3drm->CreateMeshBuilder(&targetbuilder); targetbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); ScaleMesh(targetbuilder, D3DVALUE(.75)); // --------- АНИМАЦИЯ ЦЕЛИ ---------- LPDIRECT3DRMANIMATION animation; d3drm->CreateAnimation(&animation); animation->SetOptions(D3DRMANIMATION_SPLINEPOSITION | D3DRMANIMATION_CLOSED | D3DRMANIMATION_POSITION); animation->AddPositionKey(D3DVALUE(0), D3DVALUE(-20), D3DVALUE(0), D3DVALUE(-20)); animation->AddPositionKey(D3DVALUE(12), D3DVALUE(0), D3DVALUE(15), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(24), D3DVALUE(20), D3DVALUE(0), D3DVALUE(-20)); animation->AddPositionKey(D3DVALUE(35), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(49), D3DVALUE(20), D3DVALUE(0), D3DVALUE(20)); animation->AddPositionKey(D3DVALUE(65), D3DVALUE(0), D3DVALUE(15), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(74), D3DVALUE(-20), D3DVALUE(0), D3DVALUE(20)); animation->AddPositionKey(D3DVALUE(85), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(99), D3DVALUE(-20), D3DVALUE(0), D3DVALUE(-20)); // ---------- ФРЕЙМ ЦЕЛИ -------- LPDIRECT3DRMFRAME targetframe; d3drm->CreateFrame(scene, &targetframe); animation->SetFrame(targetframe); targetframe->AddVisual(targetbuilder); targetframe->AddMoveCallback(MoveTarget, animation); targetbuilder->Release(); targetbuilder = 0; // ------- СЕТКА РАКЕТЫ -------- resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_MISSLEMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->SetColorRGB( D3DVALUE(.67), D3DVALUE(.82), D3DVALUE(.94)); meshbuilder->SetQuality(D3DRMRENDER_FLAT); ScaleMesh(meshbuilder, D3DVALUE(7)); // ------- ФРЕЙМЫ РАКЕТ ------ for (int i = 0; i < 5; i++) { for (int j = 0; j < 3; j++) { LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->SetPosition(scene, D3DVALUE((i - 2) * 8), D3DVALUE(-12), D3DVALUE((j - 1) * 8)); meshframe->AddVisual(meshbuilder); meshframe->AddMoveCallback(OrientFrame, targetframe); meshframe->Release(); meshframe = 0; } } // --------НАПРАВЛЕННЫЙ СВЕТ-------- LPDIRECT3DRMLIGHT dlight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &dlight); LPDIRECT3DRMLIGHT alight; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.50), D3DVALUE(0.50), D3DVALUE(0.50), &alight); LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->AddLight(dlight); lightframe->AddLight(alight); lightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0), D3DVALUE(1)); alight->Release(); alight = 0; dlight->Release(); dlight = 0; lightframe->Release(); lightframe = 0; //------ КАМЕРА---------- LPDIRECT3DRMFRAME cameradummy; d3drm->CreateFrame(scene, &cameradummy); cameradummy->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.01)); d3drm->CreateFrame(cameradummy, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

На четвертом этапе загружается сетка, изображающая ракету:

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

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

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

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

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

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



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


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

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

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



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


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

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

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

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




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


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

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

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

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

Molecule Rocket Target

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



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


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



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


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

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



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


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



Класс MoleculeWin


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

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

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

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

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

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

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

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

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

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

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



Класс RocketWin


Функциональность приложения Rocket сосредоточена в классе RocketWin:

class RocketWin : public RMWin { public: RocketWin(); BOOL CreateScene(); protected: //{{AFX_MSG(RocketWin) afx_msg void OnRenderWireframe(); afx_msg void OnRenderFlat(); afx_msg void OnRenderGouraud(); afx_msg void OnUpdateRenderFlat(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGouraud(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderWireframe(CCmdUI* pCmdUI); afx_msg void OnAnimationLinear(); afx_msg void OnAnimationSpline(); afx_msg void OnUpdateAnimationLinear(CCmdUI* pCmdUI); afx_msg void OnUpdateAnimationSpline(CCmdUI* pCmdUI); afx_msg void OnSpeedFast(); afx_msg void OnSpeedMedium(); afx_msg void OnSpeedSlow(); afx_msg void OnUpdateSpeedFast(CCmdUI* pCmdUI); afx_msg void OnUpdateSpeedMedium(CCmdUI* pCmdUI); afx_msg void OnUpdateSpeedSlow(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: static void UpdateScene(LPDIRECT3DRMFRAME, void*, D3DVALUE); static HRESULT LoadTexture(char*, void*, LPDIRECT3DRMTEXTURE*); private: LPDIRECT3DRMMESHBUILDER meshbuilder; LPDIRECT3DRMANIMATION animation; static D3DVALUE speed; };

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

Далее объявлены шестнадцать защищенных функций, являющихся обработчиками событий. Первые шесть предназначены для реализации меню Render, следующие четыре— для меню Animation, а последние шесть функций реализуют меню Speed.

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

Кроме того, объявлены три переменные:

LPDIRECT3DRMMESHBUILDER meshbuilder; LPDIRECT3DRMANIMATION animation; static D3DVALUE speed;

Указатель meshbuilder используется для загрузки и модификации сетки ракеты. Он применяется в функции CreateScene() и в функциях меню Render. Указатель animation используется для управления объектом Direct3DRMAnimation. Переменная speed применяется для контроля скорости с которой выполняется анимационная последовательность.



Класс TargetWin


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

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

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

Следом идет объявление шести защищенных функций. Все они предназначены для реализации меню Render.

Потом объявлены две защищенные функции обратного вызова: OrientFrame() и MoveTarget(). Функция OrientFrame() используется для изменения ориентации ракет таким образом, чтобы они всегда были направлены на цель. Функция MoveTarget() применяется для перемещения цели.

И в самом конце объявляется единственная переменная класса. Указатель meshbuilder используется для загрузки сетки, изображающей ракету. Эта переменная используется как в функции CreateScene() так и в функциях меню Render.



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


Ключевые кадры (key-framing)— это метод анимации, в котором анимационная последовательность задается в терминах ключей. Каждый ключ представляет позицию и ориентацию, которые должен занять заданный объект в определенный момент времени. Позиция и ориентация объекта на протяжении анимационной последовательности вычисляется на основе этих ключей. Теория метода ключевых кадров обсуждалась в главе 2.

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

AddPositionKey() AddRotateKey() AddScaleKey() SetTime()

Функции AddPositionKey(), AddRotateKey() и AddScaleKey() используются для добавления к анимации ключевых кадров. Все они в качестве первого аргумента получают временную метку. Эта метка определяет момент времени внутри анимационной последовательности, в который данный ключ должен вступить в действие. Функция SetTime() используется, чтобы задать текущий момент времени в анимационной последовательности.

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

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



Molecule


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



Рис. 7.3. Приложение Molecule


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

Меню Children позволяет задать количество дочерних фреймов. По умолчанию у каждого фрейма в иерархии есть два потомка. Команды меню Children позволяют изменять число потомков каждого фрейма от одного до четырех.

Эти два меню позволяют изменять иерархию фреймов приложения Molecule в широких пределах. Например, если установить значение глубины равным единице, иерархия будет состоять всего из одного фрейма. Значения по умолчанию (глубина = 4, количество потомков = 2) приведут к созданию 15 фреймов, а если задать максимально возможные значения (глубина = 6, количество потомков = 4), то будет создано 1365 фреймов.

Приложение Molecule демонстрирует следующие технологии:

Использование интерфейса Direct3DRMFrame и рекурсивных функций для создания иерархии фреймов. Использование нескольких экземпляров сетки. Использование команд меню для настройки параметров иерархии фреймов.



Rocket


В приложении Rocket ключевые кадры используются для анимации ракеты. Анимационная последовательность определена с помощью нескольких ключевых кадров, а оставшаяся часть последовательности вычисляется экземпляром интерфейса Direct3DRMAnimation. Приложение предоставляет меню Animation, позволяющее изменять параметры анимации во время работы программы. В этом меню два пункта: Linear и Spline. Корме того, предоставляется меню Speed, позволяющее регулировать скорость анимационной последовательности.

Окно приложения Rocket изображено на рис. 7.4.



Рис. 7.4. Приложение Rocket


Приложение Rocket демонстрирует следующие технологии:

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



Target


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



Рис. 7.5. Приложение Target


Приложение Target демонстрирует следующие технологии:

Использование функции LookAt() интерфейса Direct3DRMFrame для изменения ориентации фрейма. Использование интерфейса Direct3DRMAnimation для создания и выполнения анимационной последовательности. Анимация камеры с помощью пустого фрейма. Использование нескольких экземпляров сетки.

Мы поговорим о каждой из этих техник в ходе обсуждения кода приложения Target.



Управление количеством дочерних фреймов


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

void MoleculeWin::OnChildren1() { numchildren = 1; CreateHierarchy(); } void MoleculeWin::OnUpdateChildren1(CCmdUI* pCmdUI) { pCmdUI->SetCheck(numchildren == 1); }

Эти функции аналогичны функциям меню Depth. Единственным отличием является использование переменной numchildren вместо curdepth.




Данная глава познакомила вас со


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