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

Первым шагом для загрузки данных анимации является рассмотрение используемых шаблонов и создание нескольких классов, хранящих данные шаблонов. Посмотрите на шаблоны, с которыми вы будете работать:

template AnimationKey {

<10DD4 6A8-775B-11cf-8F52-0040333594A3> DWORD keyType; DWORD nKeys;

array TimedFloatKeys keys[nKeys];

}

template Animation {

<3D82AB4F-62DA-11cf-AB3 9-0 02 0AF71E43 3> [AnimationKey] [AnimationOptions] [...]

}

template AnimationSet {

<3D82AB50-62DA-11cf-AB3 9-0020AF71E433>

[Animation]

}

В начале списка расположен AnimationKey, который хранит тип данных ключа анимации, количество значений ключей и сами значения ключей, содержащиеся в массиве объектов TimedFloatKey, которые хранят время, и в массиве вещественных значений, хранимых в виде Время:ЧислоЗначений:Значения...

Шаблон Animation содержит объекты типа AnimationKey и AnimationOptions. Заметьте, что шаблон Animation является открытым, потому что ему необходима ссылка на данные фрейма для поиска соответствующей кости.

И, наконец, шаблон AnimationSet, содержащий объекты Animation. Вы можете хранить неограниченное количество анимаций в наборе; обычно для каждой кости используется одна анимация.

Замечание. Хотя шаблон AnimationOptions u не используется в этой книге, он очень полезен, если вы хотите позволить художнику определять настройки проигрывания анимации. ВшаблонеAnimationOptions вы найдете две переменных — openclosed и positionquality. Если openclosed установлен в 0, встроенная в объект анимация не повторяется циклически; значение 1 означает бесконечное повторение анимации. Установка параметраposi-tionquality в 0означает использование сплайновыхположений, в то время как 1 означает использованиелинейных положений. Обычно выустанавливаете positionquality в 1.

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

class cAnimationVectorKey {

public: float m_Time; D3DXVECTOR3 m_vecKey;

};

Ключи вращения используют кватернион (четырехмерный вектор)

class cAnimationQuaternionKey {

public: float m_Time; D3DXQUATERNION m_quatKey;

};

Наконец, ключ преобразования использует матрицу 4x4.

class cAnimationMatrixKey {

public: float m_Time; D3DXMATRIXm_matKey;

};

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

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

class cAnimation {

public: char *m_Name; // имя кости D3DXFRAME *m_Bone; // указатель на кость

cAnimation *m_Next; // следующий объект анимации в списке

// # кол-во и массивы ключей каждого типа DWORD m_NumTranslationKeys; cAnimationVectorKey *m_TranslationKeys; DWORD m_NumScaleKeys; cAnimationVectorKey *m_ScaleKeys; DWORD m_NumRotationKeys;

cAnimationQuaternionKey *m_RotationKeys; DWORD m NumMatrixKeys; cAnimationMatrixKey *m_MatrixKeys;

public: cAnimation(); ~cAnimation() ;

};

Наконец, шаблон AnimationSet содержит объект Animtation для всей иерархии костей. Все, что на данный момент требуется от класса набора анимаций, - это следить за массивом классов cAnimation (помните, что каждая кость в иерархии имеет соответствующий ей класс cAnimation) и за длиной полной анимации.

class cAnimationSet

{

public:

char *m_Name; // имя анимации DWORD m_Length; // длина анимации

cAnimationSet *m_Next; // следующий набор в связанном списке DWORD m_NumAnimations; cAnimatTon *m_Animations;

public: cAnimationSet(); ~cAnimationSet();

};

Предположив, что вы хотите загрузить за раз больше одного набора анимации, вы можете создать класс, содержащий массив (или даже связанный список) классов cAnimationSet, означая, что вы можете получить доступ ко всем анимациям используя один интерфейс! Этот класс, называемый cAnimationCollection, наследуется от разработанного в главе 2 класса cXParser, так что вы можете анализировать .X файлы напрямую из класса, хранящего анимации.

Вот объявление класса cAnimationCollection :

class cAnimationCollection : public cXParser {

public:

DWORD m_NumAnimationSets;

cAnimationSet *m_AnimationSets;

protected: // Анализировать объект .Х файла BOOL ParseObject(IDirectXFileData *pDataObj, IDirectXFileData *pParentDataObj, DWORD Depth,

void **Data, BOOL Reference) ; // Найти фрейм по имени

D3DXFRAME *FindFrame(D3DXFRAME *Frame, char *Name);

public: cAnimationCollection(); ~cAnimationCollection();

BOOL Load(char *Filename); void Free(};

void Map(D3DXFRAME *RootFrame);

void Update(char *AnimationSetName, DWORD Time) ;

На данный момент нам не важно подробное описание каждой функции в классе cAnimationCollection, так что вернемся к ним позже. Сейчас нам важно считать данные анимации их .X файла. Специализированный анализатор .X, содержащийся в классе cAnimationCollection, выполняет как раз это — загружает данные из объекта Animation в массив только что виденных вами объектов.

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

Далее вы можете соответствующим образом анализировать объекты Animation. Если вы храните последний загруженный объект cAnimationSet в начале связанного списка, каждый следующий анализируемый объект Animation будет принадлежать текущему набору анимаций. То же самое и для объектов AnimationKey — их данные будут принадлежать первому объекту cAnimation в связанном списке.

Я пропущу конструкторы и деструкторы для всех классов, т. к. они необходимы исключительно для очистки и освобождения данных. Нас интересует только пара функций, первая из которых - cAnimationCollection::ParseObject, — имеет дело с каждым объектом, анализируемым в .X файле.

Функция ParseObject начинается с проверки, является ли текущий перечисляемый объект объектом AnimationSet. Если да, то новый объект cAnimationSet размещается и привязывается в список объектов, в то же время объект набора анимаций именуется для дальнейшего использования.

BOOL cAnimationCollection::ParseObject( \ IDirectXFileData *pDataObj, \ IDirectXFileData *pParentDataObj, \ DWORD Depth, \

void **Data, BOOL Reference)

{

const GUID *Type = GetObjectGUID(pDataObj);

DWORD i;

// Проверить, является ли объект объектом AnimationSet if(*Type == TID_D3DRMAnimationSet) {

// Создать и привязать объект cAnimationSet

cAnimationSet *AnimSet = new cAnimationSet() ; AnimSet->m_Next = m_AnimationSets; m_AnimationSets = AnimSet;

// Увеличить число наборов анимаций m_NumAnimationSets++;

// Установить имя набора анимаций (если его нет, то установить // используемое по умолчанию)

if(!(AnimSet->m_Name = GetObjectName(pDataObj))) AnimSet->m_Name = strdup("NoName");

}

Как вы можете видеть, здесь не происходит ничего необычного — вы просто создаете объект, который будет содержать данные объекта Animation. Далее вы анализируете объект Animation.

// Проверить, является ли объект объектом AnimationSet if(*Type == TID_D3DRMAnimation && m_AnimationSets) {

// Добавить класс cAnimation на вершину cAnimationSet

cAnimation *Anim = new cAnimation(); Anim->m_Next = m_AnimationSets->m_Animations; m_AnimationSets->m_Animations = Anim;

// Увеличить количество анимаций m_AnimationSets->m_NumAnimations++;

}

Опять же, здесь нет ничего необычного. В предыдущем коде мы просто убедились, что создается объект cAnimationSet и добавляется в начало связанного списка. Если он создан, вы можете создавать объект cAnimation и привязывать его в список объекта cAnimationSet.

Т. к. мы говорим об объекте cAnimation, следующий кусочек кода получает экземпляр фрейма, расположенного в объекте Animation

// Проверить, есть ли ссылка на фрейм в объекте анимации if(*Type == TID_D3DRMFrame && Reference == TRUE && \

m_AnimationSets && \

m_AnimationSets->m_Animations) {

// Убедиться, что родительским является объект Animation if(pParentDataObj && *GetObjectGUID(pParentDataObj) == \ TID_D3DRMAnimation) {

// Получить имя фрейма и сохранить его в качестве имени анимации if(!(m_AnimationSets->m_Animations->m_Name = \

GetObjectName(pDataObj))) m_AnimationSets->m_Animations->m_Name=strdup("NoName");

}

}

Из этого кода вы можете увидеть, что в объекте Animation разрешены только ссылочные объекты фрейма, которые вы можете проверить, используя GUID родительского шаблона. Гмм! Пока что код прост, не так ли? Я не хочу вас расстраивать, но самое сложное впереди! На самом деле самой сложной частью загрузки данных анимации из .X файлов является загрузка данных ключей. Но не пугайтесь; данными ключей является не что иное, как значение времени и массив значений, представляющий собой данные ключа.

Оставшийся код функции ParseObject определяет тип данных ключа, содержащегося в объекте AnimationKey. Код разделяется в зависимости от типа данных и считывает данные в заданные объекты ключей (mRotationKeys, m TranslationKeys, mScaleKeys и m_MatrixKeys), находящиеся в текущем объекте cAnimation. Приглядитесь внимательно, и вы увидите, насколько код прост на самом деле:

/ / Проверить является ли объект типа AnimationKey if(*Type == TID_D3DRMAnimationKey && m_AnimationSets && \ m_AnimationSets->m_Animations) {

// Получить указатель на верхнеуровневый объект анимации cAnimation *Anim = m_AnimationSets->m_Animations;

// Получить указатель на данные

DWORD *DataPtr = (DWORD*)GetObjectData(pDataObj, NULL);

// Получить тип ключа DWORD Type = *DataPtr++;

// Получить количество следующих далее ключей DWORD NumKeys = *DataPtr++;

В дополнение к проверке существования правильных объектов cAnimationSet и объекта cAnimation, находящегося в начале связанного списка объектов, предыдущий код получает указатель на данные ключа, тип ключа и количество следующих далее ключей. Используя тип ключа, код разделяется при создании объекта ключевого кадра и загрузке данных ключа.

// Разделение на основе типа ключа switch(Type) {

case 0: // Вращение delete [] Anim->m_RotationKeys; Anim->m_NumRotationKeys = NumKeys; Anim->m_RotationKeys = new \

cAnimationQuaternionKey[NumKeys]; for(i=0;i<NumKeys;i++) { // Получить время

Anim->m_RotationKeys[i].m_Time = *DataPtr++; if(Anim->m_RotationKeys[i].m_Time > \

m_AnimationSets->m_Length) m_AnimationSets->m_Length = \

Anim->m RotationKeys[i].m_Time;

// Пропустить # следующих далее ключей (должно быть 4) DataPtr++;

// Получить значения вращения float *fPtr = (float*)DataPtr;

Anim->m_RotationKeys[i].m_quatKey.w = *fPtr++; Anim->m_RotationKeys[i].m_quatKey.x = *fPtr++; Anim->m_RotationKeys[i].m_quatKey.y = *fPtr++; Anim->m_RotationKeys[i].m_quatKey.z = *fPtr++; DataPtr + = 4 ;

}

break;

Как вы можете помнить из предыдущей части главы, ключи вращения используют кватернионы. Значения кватернионов хранятся в порядке w, x, у, z; для того, чтобы убедиться, что вы используете корректные значения, вам необходимо считывать их в таком же порядке.

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

case 1: // Масштабирование delete [] Anim->m_ScaleKeys; Anim->m_NumScaleKeys = NumKeys;

Anim->m_ScaleKeys = new cAnimationVectorKey[NumKeys];

for(i=0;i<NumKeys;i++) { // Получить время

Anim->m_ScaleKeys[i].m_Time = *DataPtr++; if(Anim->m_ScaleKeys[i].m_Time > \

m_AnimationSets->m_Length) m_AnimationSets->m_Length = \

Anim->m_ScaleKeys[i].m_Time;

// Пропустить количество далее следующих ключей (должно быть 3) DataPtr++;

// Получить значения масштабирования D3DXVECTOR3 *vecPtr = (D3DXVECTOR3*)DataPtr; Anim->m_ScaleKeys[i].m_vecKey = *vecPtr;

DataPtr+=3;

}

break;

case 2: // Перемещение

delete [] Anim->m_TranslationKeys; Anim->m_NumTranslationKeys = NumKeys; Anim->m_TranslationKeys = new \

cAnimationVectorKey[NumKeys]; for(i=0;i<NumKeys;i++) { // Получить время

Anim->m_TranslationKeys[i].m_Time = *DataPtr++; if(Anim->m_TranslationKeys[i].m_Time > \

m_AnimationSets->m_Length) m_AnimationSets->m_Length = \

Anim->m_TranslationKeys[i].m_Time;

// Пропустить количество далее следующих ключей (должно быть 3) DataPtr++;

// Получить значения перемещения D3DXVECTOR3 *vecPtr = (D3DXVECTOR3*)DataPtr; Anim->m_TranslationKeys[i].m_vecKey = *vecPtr;

DataPtr+=3;

}

break;

Последним является код для считывания массива ключей преобразования.

case 4: // Матрица преобразования delete [] Anim->m_MatrixKeys; Anim->m_NumMatrixKeys = NumKeys ;

Anim->m_MatrixKeys = new cAnimationMatrixKey[NumKeys]; for(i=0;i<NumKeys;i++) { / / Получить время

Anim->m_MatrixKeys[i].m_Time = *DataPtr + +; if(Anim->m_MatrixKeys[i].m_Time> \

m_AnimationSets->m_Length) m_AnimationSets->m_Length=\

Anim->m_MatrixKeys[i].m_Time;

// Пропустить количество далее следующих ключей (должно быть 16) DataPtr++;

// Получить значения матриц

D3DXMATRIX *mPtr = (D3DXMATRIX *)DataPtr;

Anim->m_MatrixKeys[i].m_matKey = *mPtr;

DataPtr += 16;

}

break;

}

}

Хорошо, передохните немного и давайте посмотрим, чего мы достигли. Пока что мы обработали каждый объект AnimationSet, Animation и AnimationKey (не беря во внимание ссылочный объект Frame, который содержит названия костей) и загрузили объекты ключей, содержащие данные анимации. Вы почти готовы начать анимирование!

Почти является правильным словом; остался один небольшой шаг - прикрепление объектов анимации к соответствующим им объектам костей.

Работа с четырьмя типами ключей || Оглавление || Прикрепление анимации к костям