- •Введение
- •Глава 1. Подготовка к изучению книги
- •Установка DirectX SDK
- •Выбор отладочных или рабочих версий библиотек
- •Настройка вашего компилятора
- •Установка директорий DirectX SDK
- •Привязывание к библиотекам DirectX
- •Установка используемого по умолчанию состояния символа
- •Использование вспомогательного кода книги
- •Использование вспомогательных объектов
- •Проверка вспомогательных функций
- •Двигаясь дальше по книге
- •Глава 2. Синхронизация анимации и движения
- •Использование движения, синхронизированного по времени
- •Считывание времени в Windows
- •Анимирование с использованием временных меток
- •Перемещение, синхронизированное со временем
- •Движение вдоль траекторий
- •Создание анализатора маршрутов .X файла
- •Создание внутриигровых кинематографических последовательностей
- •Посмотрите демонстрационные программы
- •TimedAnim
- •TimedMovement
- •Route
- •Cinematic
- •Глава 3. Использование формата файла .X
- •Работа с .X шаблонами и объектами данных
- •Определение шаблонов
- •Работа со стандартными шаблонами DirectX
- •Открытие .X файла
- •Перечисление объектов данных
- •Получение данных объекта
- •Создание класса .X анализатора
- •Загрузка мешей с использованием D3DX
- •Загрузка мешей, используя анализатор .X
- •Загрузка скелетных мешей
- •Загрузка анимации из .X
- •Загрузка специализированных данных из .X
- •Посмотрите демонстрационные программы
- •ParseFrame
- •Глава 4. Работа со скелетной анимацией
- •Начало скелетной анимации
- •Использование структур скелетов и иерархий костей
- •Использование скелетной структуры и скелетного меша
- •Загрузка иерархий из .X
- •Изменение положения костей
- •Обновление иерархии
- •Работа со скелетными мешами
- •Загрузка скелетных мешей из .X
- •Создание контейнера вторичного меша
- •Сопоставление костей фреймам
- •Обновление скелетного меша
- •Визуализация скелетных мешей
- •Глава 5. Использование скелетной анимации, основанной на ключевых кадрах
- •Использование наборов скелетных анимаций, основанных на ключевых кадрах
- •Использование ключей при анимации
- •Работа с четырьмя типами ключей
- •Считывание данных анимации из .X файлов
- •Прикрепление анимации к костям
- •Обновление анимации
- •Посмотрите демонстрационные программы
- •Глава 6. Комбинирование скелетных анимаций
- •Комбинирование скелетных анимаций
- •Соединение преобразований
- •Улучшение объектов скелетной анимации
- •Посмотрите демонстрационные программы
- •Глава 7. Создание кукольной анимации
- •Работа с физикой твердого тела
- •Создание твердого тела
- •Расположение и ориентирование твердых тел
- •Обработка движения твердых тел
- •Использование сил для создания движения
- •Соединение твердых тел с помощью пружин
- •Обеспечение обнаружения столкновений и ответной реакции
- •Создание систем кукольной анимации
- •Определение состояния твердого тела
- •Хранение костей
- •Создание класса управления куклой
- •Создание данных костей
- •Вычисление ограничивающего параллелепипеда кости
- •Установка сил
- •Объединение костей
- •Обработка столкновений
- •Восстановление соединений костей
- •Перестроение иерархии
- •Посмотрите демонстрационные программы
- •Глава 8. Работа с морфирующей анимацией
- •Морфинг в действии
- •Определение исходного и целевого меша
- •Морфинг мешей
- •Создание морфированного меша при помощи обработки
- •Визуализация морфированных мешей
- •Расчленение наборов
- •Создание морфирующего вершинного шейдера
- •Посмотрите демонстрационные программы
- •Глава 9. Использование морфирующей анимации, основанной на ключевых кадрах
- •Использование наборов морфируемой анимации
- •Создание шаблонов .X для морфируемой анимации
- •Загрузка данных морфируемой анимации
- •Визуализации морфированного меша
- •Получение данных морфируемого меша из альтернативных источников
- •Посмотрите демонстрационные программы
- •Глава 10. Комбинирование морфированных анимаций
- •Комбинирование морфированных анимаций
- •Использование базового меша в комбинированных морфированных анимациях
- •Вычисление разностей
- •Комбинирование разностей
- •Создание вершинных шейдеров комбинированного морфирования
- •Использование вершинного шейдера морфируемого комбинирования
- •Посмотрите демонстрационные программы
- •Глава 11. Морфируемая лицевая анимация
- •Основы лицевой анимации
- •Использование комбинированного морфирования
- •Использования фонем для речи
- •Создание лицевых мешей
- •Создание базового меша
- •Создание выражений лица
- •Создание мешей визем
- •Создание анимационных последовательностей
- •Создание последовательностей фонем
- •Использование анализатора файлов .X для последовательностей
- •Проигрывание лицевых последовательностей со звуком
- •Использование DirectShow для звука
- •Синхронизация анимации со звуком
- •Зацикливание воспроизведения звуков
- •Посмотрите демонстрационные программы
- •Глава 12. Использование частиц в анимации
- •Работа с частицами
- •Основы
- •Рисование частиц с помощью квадратных полигонов
- •Работа с точечными спрайтами
- •Улучшения визуализации частиц при помощи вершинных шейдеров
- •Оживление частиц
- •Передвижение частиц при помощи скорости
- •Использование интеллекта при обработке
- •Создание и уничтожение частиц
- •Управление частицами с помощью класса
- •Использование излучателей в проектах
- •Создание движков частиц в вершинных шейдерах
- •Посмотрите демонстрационные программы
- •Глава 13. Имитирование одежды и анимация мешей мягких тел
- •Имитация одежды в ваших проектах
- •Получение данных одежды из мешей
- •Приложение сил для создания движения
- •Воссоздание и визуализация меша одежды
- •Восстановление исходного меша
- •Добавление дополнительных пружин
- •Загрузка данных масс и пружин из .X файла
- •Создание анализатора .X данных одежды
- •Работа с обнаружением столкновений и реакцией на них
- •Определение объектов столкновений
- •Обнаружение и реакция на столкновения
- •Создание класса меша одежды
- •Использование мешей мягких тел
- •Восстановление мешей мягких тел
- •Посмотрите демонстрационные программы
- •Глава 14. Использование анимированных текстур
- •Использование анимации текстур в ваших проектах
- •Работа с преобразованиями текстур
- •Создание преобразования текстур
- •Установка матриц преобразования текстуры
- •Использование преобразования текстур в проектах
- •Использование файлов видео в качестве текстур
- •Импорт видео при помощи DirectShow
- •Создание специализированного фильтра
- •Работа со специализированным фильтром
- •Создание менеджера анимированных текстур
- •Окончание современной анимации
- •Веб-сайты
- •Рекомендуемые книги
- •DirectX 9.0 SDK
- •GoldWave Demo
- •Paint Shop Pro Trial Version
- •TrueSpace Demo
- •Microsoft Agent and LISET
- •Предметный указатель
Использование формата файла .X
// Освободить интерфейсы объектов pSubData->Release ();
}
// Освободить интерфейс для следующего используемого объекта pObject->Release();
}
}
Опять же, функция ParseObject не содержит ничего нового. Единственное что вы заметите в этих двух функциях это то, что они на самом деле не делают ничего кроме перечисления всех объектов находящихся в .X файле. Когда придет время работать с данными объекта, что вы будете делать?
Получение данных объекта
Помните, что объекты данных являются контейнерами для данных, и если у вас возникают трудности с перечислением объектов данных, разумно предполагать, что вы имеете данные в каждом их них. После того как вы получили правильный объект IDirectXFileData, который указывает на перечисленный объект данных, вы можете получит имя экземпляра объекта, GUID шаблона и данные, используя три функции.
Первая функция IDirectXFileData::GetName получает имя экземпляра объекта данных.
HRESULT IDirectXFileData::GetName(
LPSTR pstrNameBuf, // Буфер имени
LPDWORD pdwBufLen); // Размер буфера имени
Функция GetName имеет два параметра - указатель на буфер, содержащий имя, и указатель на переменную, содержащую размер буфера имени (в байтах). Прежде чем получать имя, используя функцию GetName, вам необходимо получить его размер, задав значение NULL в качестве pstrNameBuf и указатель на DWORD для pdwBufLen.
//pData = загруженный объект IDirectXFileData
//Получить размер имени в байтах
DWORD Size; pData->GetName(NULL, &Size);
После того как вы получили размер буфера имени, вы можете создать соответствующий буфер и считать имя.
// Создать буфер имени и получить его char *Name = new char[Size]; pData->GetName(Name, &Size);
102 |
Глава3 |
Хотя имя экземпляра объекта и помогает, на самом деле вам необходим GUID шаблона объекта, для определения, какой шаблон использует объект. Для получения GUID шаблона объекта используйте функцию IDirectXFileData::GetType.
HRESULT IDirectXFileData::GetType(
const GUID ** ppguid);
Используя только один параметр - указатель на указатель const GUID, вы можете вызвать функцию GetType, используя следующий код:
const GUID *TemplateGUID = NULL; pData->GetType(&TemplateGUID);
После того как вы получили GUID, вы можете сравнить его с внешними GUID (например, с GUID стандартных или ваших специализированных шаблонов) и соответствующим образом обрабатывать данные. Например, для проверки является ли объект данных шаблоном "MeshNormals", вы можете использовать следующий код:
// TemplateGUID = GUID проверяемого шаблона if(*TemplateGUID == TID_D3DRMMeshNormals) {
// Обработать шаблон MeshNormals
}
Конечно, знание GUID шаблона объекта может привести вас только сюда. Настоящим фокусом является получение данных объекта. Никаких проблем! Используя еще одну простую функцию, анализирующие способности будут практически завершены! Последняя используемая функция, позволяющая вам получать доступ к данным объекта, это GetData.
HRESULT IDirectXFileData::GetData( LPCSTR szMember, // Установите в NULL DWORD *pcbSize, // Размер данных
void **ppvData); // Указатель на данные
Для использования функции GetData вам необходимо предоставить указатель для доступа к буферу данных объекта и переменную DWORD, чтобы хранить размер буфера (в байтах). Вот небольшой код, который демонстрирует использование GetData для получения указателя на данные объекта и их размер.
char *DataPtr; DWORD DataSize;
pData->GetData(NULL, &DataSize, (void**)&DataPtr);
Указатель на буфер данных теперь указывает на непрерывный блок памяти, который имеет такую же структуру, как и определение шаблона объекта. Вы можете получить доступ к данным как к большому буферу или, если слукавить, можно
Использование формата файла .X
создать структуру, соответствующую определению шаблона, для более простого доступа. Например, предположим, что вы нашли стандартный шаблон ColorRGBA, который определен так:
template ColorRGBA { <35FF44E0-6C7C-11cf-8F52-0040333594А3> FLOAT red;
FLOAT green; FLOAT blue; FLOAT alpha;
}
Чтобы получить доступ к значениям red, green, blue и alpha, вам необходимо получить указатель и преобразовать его к типу float.
DWORD DataSize; float *DataPtr;
pData->GetData(NULL, &DataSize, (void**)&DataPtr); float red = *DataPtr++;
float green = *DataPtr++; float blue = *DataPtr++; float alpha = *DataPtr++;
Хотя это нормальный подход, вы можете обрабатывать данные объекта более простым способом, используя соответствующую структуру С.
typedef struct {
float red, green, blue, alpha; } sColorRGBA;
sColorRGBA *Color; DWORD DataSize;
pData->GetData(NULL, &DataSize, (void**)&Color);
После реализации, предыдущий код дает вам возможность получать доступ к цветам, используя экземпляр структуры.
float red = Color->red; float blue = Color->blue; float green = Color->green; float alpha = Color->alpha;
Получать доступ к одиночным переменным просто, а как насчет строк и массивов? Массивы, как самый простой их двух случаев, хранятся непрерывно в памяти, что означает, что вы можете просто увеличивать указатель памяти, который содержит данные объекта. Например, нижеследующий код показывает, как получить доступ к массиву вещественных значений, хранимых в объекте, использующем шаблон FloatKeys.
Замечание.Обратите внимание, что GUID шаблона или имя класса не являются частьюполучаемыхданных,используяфункциюIDirectXFileData::GetData.
104
// Получить размер данных объекта и указатель DWORD DataSize;
DWORD *DataPtr;
pData->GetData(NULL, &DataSize, (void**)&DataPtr);
//Шаблон FloatKeys имеет значение DWORD вначале
//определяющее, как много вещественных значений в массиве DWORD NumKeys = *DataPtr++;
//Next, an array of float values follows
for(DWORD i=0;i<NumKeys;i++) {
float fValue = *(FLOAT*)DataPtr++;
Получить доступ к массивам оказалось не слишком сложно, а как насчет получения доступа к строкам? Опять же это простая работа, потому что строки хранятся как указатели на текстовые буферы, к которым вы можете получить доступ, как я сделал в следующем коде. (Я использую шаблон TextureFilename в качестве примера; он хранит имя файла текстуры.)
// Получить указатель на данные и их размер DWORD DataSize;
DWORD *DataPtr;
pData->GetData(NULL, &DataSize, (void**)&DataPtr);
// Получить доступ к текстовому буферу имени файла char *StringPtr = (char*)*DataPtr;
MessageBox(NULL, StringPtr, "Texture Filename", MB_OK);
Простым приведением указателя к типу char мы смогли отобразить имя файла, содержащегося в шаблоне TextureFilename. Я знаю, что теперь вы, наверное, кричите "почему я сразу не понял, как просто это?". Спокойно! Я тоже не сразу понял насколько просто работать с .X файлами. После того как секрет раскрыт, ничто не сможет остановить вас от использования .X файлов в ваших проектах. Вам просто необходимо реализовать всю функциональность анализатора .X в виде класса, еще более упростив работу с .X.
Создание класса .X анализатора
Итак, вы хотите создать класс, который бы реализовывал все аспекты обработки
.X файлов, да? Звучит замечательно! В классе анализатора .X файлов вы можете реализовать функции Parse и ParseObject, которые вы видели ранее в этой главе, в разделе "Перечисление объектов данных". Используя код этих двух функций, напишите класс анализатора, чтобы вы могли перегружать функции анализирования объектов, что позволит искать заданные объекты.
Начнем класс анализатора с простого описания.
Использование формата файла .X
class cXParser
{
protected:
// Функция, вызываемая для каждого найденного шаблона virtual BOOL ParseObject( \
IDirectXFileData *pDataObj, \ IDirectXFileData *pParentDataObj, \ DWORD Depth, \
void **Data, BOOL Reference)
{
return ParseChildObjects(pDataObj, Depth, \ Data, Reference);
}
// Функция, вызываемая для перечисления дочерних шаблонов BOOL ParseChildObjects(IDirectXFileData *pDataObj, \
DWORD Depth, void **Data, \ BOOL ForceReference = FALSE);
public:
// Функция начала анализирования .X файла
BOOL Parse(char *Filename, void **Data = NULL);
};
Стоп! Я знаю, что я сказал вам начать с простого объявления, а не с того, что я показал тут! Не спорьте со мной, потому что вы быстро осознаете, как прост будет этот класс. Пока что у вас есть эти три функции в вашем новом классе анализизатора .X файлов cXParser. Вы можете использовать эти три функции (ParseObject, ParseChildObjects и Parse) для обработки одного объекта, поиска встроенных объектов или анализирования всего файла соответственно.
Самая простая из функций cXParser::Parse просто повторяет код ранее приведенной функции Parse. Я не стал приводить здесь ее код, но если вы заглянете в него на компакт-диске (\BookCode\Common\XParser.cpp и XParser.h), вы заметите добавление предполагаемого указателя данных и несколько строк кода, содержащих вызовы двух неизвестных функций BeginParse и EndParse. Я расскажу вам о них немного позднее, а пока просто пропустим их.
Вторая функция ParseObject является рабочей лошадкой вашего анализатора
.X файлов. ParseObject вызывается для каждого объекта, найденного в .X файле. Вам необходимо перегрузить функцию ParseObject (она является виртуальной), чтобы она могла выполнять что-нибудь полезное. Как вы можете видеть из прототипа функции ParseObject, много чего поменялось, что требует объяснений.
Первым параметром ParseObject является объект IDirectXFileData, который, как вы видели ранее в этой главе, представляет собой объект данных, который просматривается в данный момент. Внутри перегружаемой функции вы можете получить доступ к этому объекту, используя указатель pDataObj.
Использование формата файла .X
Замечание. Глубина объекта данных очень полезна для сортировки иерархий, таких как иерархии фреймов, используемых в скелетной анимации.
Я знаю, что я опять забегаю вперед, показывая вам некоторые примеры кода для cXParser, так что давайте вернемся к пятому (и последнему) параметру ParseObjectReference. Логическая переменная Reference определяет, является ли перечисляемый объект ссылочным или экземпляром. Вы можете использовать переменную Reference для определения, хотите ли вы загрузить данные ссылочного ,объекта или подождать пока создастся экземпляр объекта. Этот параметр полезен при загрузке данных анимации, которым необходимы ссылки на объекты, а не сами экземпляры объектов.
Гмм! После определения функции ParseObject остается определить последнюю функцию ParseChildObjects. К счастью, функция ParseChildObjects очень проста, она просто перечисляет все дочерние объекты передаваемого объекта. Обычно вы вызываете ParseChildObjects в конце вашей функции ParseObject, как я делал в последнем кусочке кода.
Вы можете заметить, что функции ParseChildObjects необходимо передавать текущий объект IDirectXFileData, глубину объекта данных, указатель на данные
ифлаг ссылки, потому что она ответственна за увеличение глубины и установки соответствующего родительского объекта для следующего вызова ParseObject. Если вы не хотите перечислять дочерние объекты, вы можете пропустить вызов ParseChildObjects и вернуть значение TRUE или FALSE. (TRUE продолжает перечисление, a FALSE останавливает его). Вы увидите примеры использования функций ParseObject и ParseChildObjects далее в этой книге.
После того как мы определились с основами, необходимо немного расширить класс анализатора. Как насчет добавления функций, позволяющих получать имя объекта, GUID и указатель на данные и несколько функций, вызываемых до
ипосле анализирования .X файлов? Посмотрите на нижеследующий код, чтобы увидеть, как должен выглядеть новый класс анализатора.
class cXParser
{
protected:
//Функции, вызывемые при начале и окончании анализирования virtual BOOL BeginParse(void **Data) { return TRUE; } virtual BOOL EndParse(void **Data) { return TRUE; }
//Функция, вызываемая при нахождении объекта
virtual BOOL ParseObject( \ IDirectXFileData *pDataObj, \
IDirectXFileData *pParentDataObj, \
108 |
Глава 3 |
DWORD Depth, |
\ |
void **Data, |
BOOL Reference) |
{
return ParseChildObjects(pDataObj, Depth, \ Data, Reference);
}
// Функция, вызываемая для перечисления дочерних объектов BOOL ParseChildObjects(IDirectXFileData *pDataObj, \
DWORD Depth, void **Data, \ BOOL ForceReference = FALSE);
public:
// Функция начала просмотра .X файла
BOOL Parse(char *Filename, void **Data = NULL);
// Функции, помогающие получить информацию об объекте const GUID *GetObjectGUID(IDirectXFileData *pDataObj); char *GetObjectName(IDirectXFileData *pDataObj);
void *GetObjectData(IDirectXFileData *pDataObj,DWORD *Size); };
Вы можете увидеть добавление функций BeginParse, EndParse, GetObjectGUID, GetObjectName и GetObjectData в cXParser. Вы уже видели код трех функций Get, неизвестными являются только виртуальные функции BeginParse и EndParse.
В их текущем виде, обе функции BeginParse и EndParse возвращают значение TRUE, что означает успешное их выполнение. Ваша задача перегрузить эти две функции в унаследованном классе, чтобы вы могли выполнять любые действия перед и после анализирования файла. Например, вы можете захотеть инициализировть какие-нибудь данные или создать указатель на данные в функции BeginParse, после чего удалить все используемые ресурсы в EndParse.
Обе функции BeginParse и EndParse вызываются непосредственно из функции Parse - вам просто необходимо перегрузить их и написать код для них. Вы увидите, как использовать эти функции далее в книге и в предстоящем разделе этой главы "Загрузка иерархии фреймов из .X".
Что же касается трех Get функций, вы используете их, указывая правильный объект IDirectXFileData; в ответ вы получите имя в созданном буфере данных, указатель на GUID шаблона или на буфер данных объекта и размер его данных. Например, следующий код вызывает эти три функции для получения доступа
кданным объекта:
//pData = предварительно загруженный объект
char *Name = pParser->GetObjectName(pData);
const GUID *Type = pParser->GetObjectGUID(pData); DWORD Size;
char *Ptr = (char*)pParser->GetObjectData(pData, &Size);