netlib.narod.ru | < Назад | Оглавление | Далее > |
Ключевые кадры (key-framing) — это метод анимации, в котором анимационная последовательность задается в терминах ключей. Каждый ключ представляет позицию и ориентацию, которые должен занять заданный объект в определенный момент времени. Позиция и ориентация объекта на протяжении анимационной последовательности вычисляется на основе этих ключей. Теория метода ключевых кадров обсуждалась в главе 2.
Для поддержки метода ключевых кадров в Direct3D предназначен интерфейс Direct3DRMAnimation. Он позволяет определять анимационную последовательность и управлять ею с помощью следующих функций:
Функции AddPositionKey(), AddRotateKey() и AddScaleKey() используются для добавления к анимации ключевых кадров. Все они в качестве первого аргумента получают временную метку. Эта метка определяет момент времени внутри анимационной последовательности, в который данный ключ должен вступить в действие. Функция SetTime() используется, чтобы задать текущий момент времени в анимационной последовательности.
Интерфейс Direct3DRMAnimation определяет местоположение объекта в анимационной последовательности путем интерполяции между ключевыми кадрами. Для вычислений может применяться либо линейная, либо сплайновая интерполяция. Линейная анимация означает, что для перемещения объекта между ключевыми кадрами будет использоваться наикратчайший путь. В сплайновой анимации перемещение идет по сплайнам (плавным кривым). Сплайновая анимация обычно более реалистична, чем линейная.
Интерфейс Direct3DRMAnimation разработан в первую очередь для анимации экземпляров Direct3DRMFrame. Однако не сложно использовать этот интерфейс и для общей анимации. В главе 8 вы узнаете, как интерфейс Direct3DRMAnimation может применяться для анимации вершин. В главе 9 интерфейс Direct3DRMAnimation будет использован для анимации портов просмотра. В данной главе мы воспользуемся этим интерфейсом для анимации фреймов.
В приложении Rocket ключевые кадры используются для анимации ракеты. Анимационная последовательность определена с помощью нескольких ключевых кадров, а оставшаяся часть последовательности вычисляется экземпляром интерфейса Direct3DRMAnimation. Приложение предоставляет меню Animation, позволяющее изменять параметры анимации во время работы программы. В этом меню два пункта: Linear и Spline. Корме того, предоставляется меню Speed, позволяющее регулировать скорость анимационной последовательности.
Окно приложения Rocket изображено на рис. 7.4.
Рис. 7.4. Приложение Rocket
Приложение Rocket демонстрирует следующие технологии:
Функциональность приложения 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 применяется для контроля скорости с которой выполняется анимационная последовательность.
Код функции 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().
Функция обратного вызова 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() перемещает и ее.
Меню 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. Значение, возвращаемое данной функцией используется, чтобы проверить какой метод анимации используется в данный момент и установить флажок у соответствующего пункта меню.
Приложение 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 имеет очень малое значение, обеспечивающее неторопливую и плавную анимацию.
netlib.narod.ru | < Назад | Оглавление | Далее > |