Итак, вы хотите создать класс, который бы реализовывал все аспекты обработки .X файлов, да? Звучит замечательно! В классе анализатора .X файлов вы можете реализовать функции Parse и ParseObject, которые вы видели ранее в этой главе, в разделе "Перечисление объектов данных". Используя код этих двух функций, напишите класс анализатора, чтобы вы могли перегружать функции анализирования объектов, что позволит искать заданные объекты.

Начнем класс анализатора с простого описания.

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.

Второй параметр, pParentDataObj (также объект IDirectXFileData), представляет родителя (объект более высокого уровня) текущего перечисляемого объекта. Он необходим, если вы захотите посмотреть является ли текущий объект потомком какого-либо другого.

Параметр Depth измеряет глубину объекта в иерархии. Объекты самого высоко уровня имеют глубину 0, в то время как у объектов потомков она на один больше. В качестве примера, я показал несколько объектов Frame и их соответствующие глубины.

Frame Root Frame { // Depth = 0 Frame ChildofRoot { // Depth = 1 Frame ChildofChild { // Depth = 2 }

}

Frame SiblingofRootChild { // Depth = 1

}

}

Frame RootSibling { // Depth = 0

}

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

typedef struct sDATA { long NumObjects; sDATA() { NumObjects =0; }

} sDATA;

Чтобы передать структуру sDATA вашей функции анализатора, вы создаете ее экземпляр и используете при вызове cXParser::Parse, как показано тут:

sDATA Data; cXParser Parser;

Parser.Parse("test.x", (void**)&Data);

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

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

void **Data, BOOL Reference)

{

cDATA *DataPtr = (sDATA*)*Data;

DataPtr->NumObjects++; // Увеличить количество объектов return ParseChildObjects(pDataObj,Depth,Data,Reference);

}

Замечание. Глубина объекта данных очень полезна для сортировки иерархий, таких как иерархии фреймов, используемых в скелетной анимации.

Я знаю, что я опять забегаю вперед, показывая вам некоторые примеры кода для cXParser, так что давайте вернемся к пятому (и последнему) параметру ParseObject- Reference. Логическая переменная 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, \

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, что означает успешное их выполнение. Ваша задача перегрузить эти две функции в унаследованном классе, чтобы вы могли выполнять любые действия перед и после анализирования файла. Например, вы можете захотеть инициали-зировть какие-нибудь данные или создать указатель на данные в функции Begin-Parse, после чего удалить все используемые ресурсы в 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);

// сделать что-нибудь с данными и освободить ресурсы по завершении delete [] Name;

Как я уже говорил, использовать новый класс cXParser по-настоящему просто. Далее, до конца этой главы и книги, вы будете использовать класс cXParser для анализирования .X файлов. Продвинутые читатели могут захотеть изменить класс анализатора для собственных нужд. Лично меня класс устраивает в его нынешнем виде.

Для быстрой проверки нового класса cXParser давайте посмотрим, как его наследовать и использовать. Предположим, вы хотите проанализировать .X файл на наличие объектов Mesh и отобразить их имена в окне сообщений. Вот код анализатора, который делает именно это:

class cParser : public cXParser {

public:

cParser() { Parse("test.x"); }

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

void **Data, BOOL Reference)

{

if(*GetObjectGUID(pDataObj) == TID_D3DRMMesh) { char *Name = GetObjectName(pDataObj); MessageBox(NULL, Name, "Mesh template", MB_OK); delete [] Name;

}

return ParseChildObjects(pDataObj,Depth,Data,Reference);

}

};

cParser Parser; // Создание экземпляра приведет к запуску анализатора

Только не говорите что, это было сложно! Хватит основ, давайте перейдем к использованию .X файлов для чего-нибудь полезного, как например для данных трехмерных мешей.

Получение данных объекта || Оглавление || Загрузка мешей из .X