Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
DirectX. Продвинутая Анимация (2004) [rus].pdf
Скачиваний:
335
Добавлен:
16.08.2013
Размер:
8.39 Mб
Скачать

Комбинирование скелетных анимаций

D3DXMATRIX matResult = Matrix1 + Matrix2;

Выполнив эти действия, вы можете использовать матрицу matResult для преобразований; она является комбинированным преобразованием матриц Matrix1 и Matrix2. Чтобы соединить большее количество преобразований анимации, просто добавьте ещё одну матрицу к matResult и продолжайте делать это пока не соедините все используемые преобразования.

Теперь вы знаете, как можно соединять разнообразные преобразования наборов анимаций. Для упрощений вы можете расширить объекты анимаций, разработанные в главе 5.

Улучшение объектов скелетной анимации

После того как вы узнали насколько просто комбинировать несколько скелетных анимаций, почему бы вам не применить эти знания и не добавить их к объектам скелетной анимации и коду, рассмотренным в главе 5? Звучит заманчиво: добавив всего одну функцию к классу cAnimationCollection, вы будете комбинировать анимации совсем как профессионалы.

На самом деле, вместо того чтобы смешивать новый код с кодом cAnimationCollection, давайте просто унаследуем от него новый класс, который бы отвечал за комбинированные анимации. Этот новый унаследованный класс cBlendedAnimationCollection будет определяться так:

class cBlendedAnimationCollection : public cAnimationCollection

{

public:

void Blend(char *AnimationSetName, DWORD Time, BOOL Loop,

float Blend = 1.0f);

};

Ничего себе, вот это маленький класс! В классе cBlendedAnimationCollection объявлена только одна функция Blend, которая выполняет обязанности функции cAnimationCollection::Update. Вы спросите, почему просто не унаследовать функцию Update? При помощи cBlendedAnimationCollection вы сможете использовать как обычные наборы анимаций из главы 5, так и только что разработанные наборы комбинированных анимаций.

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

176

void cBlendedAnimationCollection::Blend( \ char *AnimationSetName, \ DWORD Time, BOOL Loop, \ float Blend)

{

У прототипа функции Blend имеется четыре параметра, первый из которых - AnimationSetName. При вызове Blend вам необходимо передать в качестве AnimationSetName имя набора анимаций, который вы будете смешивать с текущей. Помните, в главе 5, каждый набор анимаций, содержащийся в .X файле имеет собственное уникальное имя (определяемое именем экземпляра объекта AnimationSet). Вам необходимо задать в качестве AnimationSetName соответствующее имя из .X файла.

Я вернусь к наборам анимаций немного позже. А пока продолжим рассмотрение прототипа Blend. Вторым параметром Blend является Time, который представляет собой время в анимации, используемое для комбинирования. Если анимация длится 1000 миллисекунд, то вы можете изменять Time в диапазоне от 0 до 999. Задание значения большего, чем длительность анимации, вынудит функцию Blend использовать последний ключевой кадр для комбинирования анимаций.

А что относительно циклического повтора анимации? За это ответственен третий параметр - Loop. Если вы установите Loop в FALSE, тогда ваша анимация не будет обновляться, если задаваемое время больше, чем длительность анимации. Однако, если Loop установлен в TRUE, функция Blend проверяет величину времени таким образом, чтобы оно всегда попадало в продолжительность анимации.

Предыдущий абзац может показаться непонятным поначалу, поэтому для большей понятности представьте себе такую функцию:

void UpdateAnimation(DWORD Elapsed)

{

static DWORD AnimationTime = 0; // время анимации

// Вызвать Blend, используя AnimationTime в качестве времени анимации

AnimationBlend.Blend("Walk", AnimationTime, FALSE, 1.0f);

// Обновить время анимации AnimationTime += ELapsed;

}

Функция UpdateAnimation предназначена для отслеживания времени анимации при помощи статической переменной. Во время каждого вызова UpdateAnimation функция Blend используется для комбинирования анимации Walk на основе времени, задаваемого AnimationTime. Положим, что анимация Walk имеет продолжительность 1000 мил-

Комбинирование скелетныханимаций

лисекунд, а время между вызовами функции UpdateAnimation 50 мс, таким образом, анимация закончится после 20 вызовов функции. Это означает, что после 20 вызовов функции UpdateAnimation анимация остановится (т. к. Loop установлен в FALSE).

Вернувшись и установив Loop в TRUE, мы вынудим функцию проверять значения времени, для убеждения, что оно всегда меньше длительности анимации. Когда я говорю проверяем, я имею ввиду модульное вычисление. Я покажу вам, как использовать модульное вычисление немного позже; а пока вернемся к четвертому и последнему параметру.

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

Хорошо, достаточно о параметрах; давайте перейдем к коду функции! Если вы использовали функцию cAnimationCollection::Update, вы заметите, что большая часть кода функции Blend такая же. В начале кода вы найдете кусочек, который проверяет связанный список наборов анимаций для нахождения заданного параметром AnimationSetName.

cAnimationSet *AnimSet = m_AnimationSets;

// Если задано, искать соответствующее имя набора анимации if(AnimationSetName) {

// Найти соответствующее имя набора анимации while{AnimSet != NULL) {

//Остановиться, если совпадение найдено if(!stricmp(AnimSet->m_Name, AnimationSetName))

break;

//Перейти к следующему объекту набора анимаций AnimSet = AnimSet->m_Next;

}

}

// Вернуть, если ничего не найдено if(AnimSet == NULL)

return;

Если вы установите в качестве параметра AnimationSetName NULL, то функция Blend будет использовать первый набор анимаций, находящийся в связанном списке. Если же вы зададите имя в AnimationSetName и оно не будет найдено в связанном списке, то функция Blend завершит работу.

178

Глава 6

После того, как вы получили указатель на соответствующий объект набора анимаций, вы можете проверить время, основываясь на заданный параметр Time

изначение циклического флага Loop.

//Ограничить время длительностью анимации if(Time > AnimSet->m_Length)

Time = (Loop==TRUE)?Time % \

(AnimSet->m_Length+1):AnimSet->m_Length;

Всамом деле предыдущий маленький изящный кусок кода выполняет две вещи

взависимости от флага Loop. Если Loop установлен в FALSE, то Time сравнивается с продолжительностью анимации (AnimSet->m_Length). Если Time больше длительности анимации, тогда она устанавливается равной продолжительности анимации, таким образом блокируясь на последней секунде (далее - последнем ключевом кадре) анимации. Если Loop установлен в FALSE, то в результате модального вычисления значение Time всегда лежит в пределах длительности анимации (от 0 до AnimSet->m_Length).

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

// Просмотреть все анимации

cAnimation *Anim = AnimSet->m_Animations; while(Anim) {

// Обрабатывать, если присоединена к кости if(Anim->m_Bone) {

//Сбросить преобразование D3DXMATRIX matAnimation; D3DXMatrixIdentity(&matAnimation);

//Наложить разнообразные матрицы для преобразования

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

// Матрица

if(Anim->m_NumMatrixKeys && Anim->m_MatrixKeys) { // Сделать цикл для нахождения матричного ключа DWORD Key1 = 0, Кеу2 = 0;

for(DWORD i=0;i<Anim->m_NumMatrixKeys;i++) { if(Time >= Anim->m_MatrixKeys[i].m_Time)

Key1 = i;

}

Комбинированиескелетныханимаций

// Получить значение второго ключа

Кеу2 = (Key1>=(Anim->m_NumMatrixKeys-1))?Key1:Key1+1;

// Получить разность времен ключей

DWORD TimeDiff = Anim->m_MatrixKeys[Key2].m_Time-

Anim->m_MatrixKeys[Key1].m_Time; if(!TimeDiff)

TimeDiff = 1;

//Вычислить используемое значение скаляра float Scalar = (float)(Time - \

Anim->m_MatrixKeys[Key1].m_Time) / (float)TimeDiff;

//Вычислить интерполированную матрицу

D3DXMATRIX matDiff;

matDiff = Anim->m_MatrixKeys[Key2].m_matKey - \ Anim->m_MatrixKeys[Key1].m_matKey;

matDiff *= Scalar;

matDiff += Anim->m_MatrixKeys[Key1].m_matKey;

// Соединить с преобразованием matAnimation *= matDiff;

}

Я привел код, показанный в главе 5, так что я не буду объяснять его здесь еще раз. Вкратце, код ищет ключевые кадры и вычисляет соответствующее преобразование. Это преобразование хранится в матрице matAnimation.

Начиная отсюда, код будет совершенно отличен от функции cAnimationCollection::Update. Вместо того чтобы хранить матрицу преобразования (matAnimation) в объекте фрейма скелетной структуры, вычисляется разность между преобразованием

matAnimation и

начальным преобразованием скелетной структуры (сохраненным

в matOriginal при

загрузке скелетной структуры). Эта разность масштабируется,

используя вещественный параметр Blend, и результирующее преобразование добавляется (не умножается как при concoction) к преобразованию фрейма скелетной структуры. Это позволяет убедиться, что преобразования корректно комбинируются при использовании соответствующих значений.

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

// Получить разность преобразований

D3DXMATRIX matDiff = matAnimation - Anim->m_Bone->matOriginal;

// Скорректировать на значение смешивания matDiff *= Blend;

// Добавить к матрице преобразования Anim->m_Bone->TransformationMatrix += matDiff;

}

180

// Перейти к следующей анимации Anim = Anim->m_Next;

}

}

Мои поздравления, вы завершили создание функции Blend! Давайте попробуем ее

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

в.X файле (названном Anims.x) содержатся четыре набора анимаций - Stand, Walk, Wave и Shoot. Две из них используются для ног и две для рук и торса. Вот пример кода, загружающего наборы анимаций:

//pFrame = корневой фрейм в иерархии фреймов cBlendedAnimationSet BlendedAnims; BlendedAnims.Load("Anims.x");

//Нанести иерархию фреймов анимации BlendedAnims.Map(pFrame);

После того как вы загрузили набор анимаций, вы можете скомбинировать их перед обновлением и визуализацией скелетных мешей. Предположим, вы хотите скомбинировать анимации Walk и Shoot, обе из которых используют сто процентов преобразований. Для начала вам необходимо сбросить преобразования иерархии фреймов к их начальному состоянию. Это означает, что вам необходимо скопировать преобразование D3DXFRAME_EX::matOriginal в преобразование D3DXFRAME_EX::TransformationMatrix. Этот момент очень важен, т. к. является базой, к которой добавляются все преобразования при смешивании.

Замечание. Объект D3DXFRAME_EX является расширенной версией объекта DirectiD D3DXFRAME. Вы можете прочитать о нем в главе 1.

// Использовать D3DXFRAME_EX::Reset для сброса преобразований pFrame->Reset();

После того как преобразования были сброшены к их начальным состояниям, выможетекомбинировать наборыанимаций.

//AnimationTime = время с начала анимации

//смешать анимацию ходьбы

BlendedAnims.Blend("Walk", AnimationTime, TRUE, 1.0f);

// смешать анимацию стрельбы BlendedAnims.Blend("Shoot", AnimationTime, TRUE, 1.0f);

Соседние файлы в предмете Программирование на C++