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

Использование формата файла .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);

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