Как я ранее замечал, я создал набор объектов, которые расширяют функциональность D3DXFRAME и D3DXMESHCONTAINER, которые являются частью библиотеки D3DX. Если вы незнакомы с этими объектами, позвольте мне дать их небольшой обзор.

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

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

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

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

Что касается объекта D3DXMESHCONTAINER, он используется чтобы хранить меш и ссылки на серию других мешей (используя связанный список). Вы спросите: почему вместо этого объекта не использовать ID3DXBaseMesh? Функциональность D3DXMESHCONTAINER больше, чем вы можете ожидать. Во-первых, он может хранить любые типы мешей, будь то обычные (regulär), скелетные4 (skinned) или прогрессивные5 (progressive). Во-вторых, объект D3DXMESHCONTAINER содержит информацию о материалах и данные эффектов.

Для получения полной информации об объектах D3DXMESHCONTAINER и D3DXFRAME обратитесь к документации DirectX SDK. А сейчас давайте посмотрим, как я реализовал расширение функциональности этих объектов (определенных в заголовочном файле \common\Direct3D.h компакт диска этой книги), начав с D3DXFRAME.

Расширение D3DXFRAME

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

Чтобы исправить эти недостатки, я создал расширенную версию D3DXFRAME, которую назвал D3DXFRAMEEX. Этот новый объект содержит дополнение в виде двух объектов D3DMATRIX и шести функций. Два объекта матрицы содержат первоначальное преобразование фрейма (прежде чем любое анимационное преобразование наложено) и комбинированное преобразование всех родительских фреймов, к которым данный фрейм присоединен (в иерархии).

Вот как я определил структуру D3DXFRAMEEX, использующую два матричных объекта.

struct D3DXFRAME_EX : D3DXFRAME {

D3DXMATRIX matCombined; // комбинированная матрица D3DXMATRIX matOriginal; // первоначальная матрица

4. Skinned mesh - скелетный меш, меш - который обтягивает иерархический скелет из костей. К каждой вершине меша может быть прикреплена одна или несколько костей. Используется для анимирования моделей. -Примеч. науч. ред.

5. Progressive mesh - прогрессивный меш, это меш с возможностью динамически определять какое количество вершин и граней визуализировать. То есть возможно гибкое управление детализацией. - Примеч. науч. ред.

Вы узнаете об этих двух матрицах и как с ними работать далее в этой книге. На данный момент давайте просто перейдем к рассмотрению функций, начнем с конструктора. Конструктор предназначен для очистки данных структуры (включая изначальные данные базового объекта D3DXFRAME).

D3DXFRAME_EX() {

Name - NULL; pMeshContainer = NULL;

pFrameSibling = pFrameFirstChild = NULL; D3DXMatrixIdentity(&matCombined); D3DXMatrixIdentity(&matOriginal); D3DXMatrixIdentity(&TransformationMatrix);

}

С другой стороны, деструктор предназначен для освобождения данных используемых объектом D3DXFRAMEEX.

~D3DXFRAME_EX()

{

delete [] Name; Name = NULL;

delete pFrameSibling; pFrameSibling = NULL; delete pFrameFirstChild; pFrameFirstChild = NULL;

}

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

Первая функция Find используется для нахождения заданного фрейма в иерархии, возвращая указатель на него. Если вы не знаете, каждый объект D3DXFRAME (и унаследованный от него D3DXFRAMEEX) имеет буфер данных Name, который можно заполнять любым текстом. Обычно фреймы называются так же как и кости, которые определяют иерархию (о чем я расскажу в главе 4 "Работа со скелетной анимацией").

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

//Функция просматривает иерархию на совпадения имени фрейма D3DXFRAME_EX *Find(const char *FrameName)

{

D3DXFRAME_EX *pFrame, *pFramePtr;

//возвращаем этот экземпляр фрейма, если имя совпадает if(Name && FrameName && !strcmp(FrameName, Name)) return this;

//просмотр родственников

if((pFramePtr = (D3DXFRAME_EX*)pFrameSibling)) { if((pFrame = pFramePtr->Find(FrameName)))

return pFrame;

}

//просмотр потомков

if((pFramePtr = (D3DXFRAME_EX*)pFrameFirstChild)) {

if((pFrame = pFramePtr->Find(FrameName))) return pFrame;

}

//ничего не найдено return NULL;

}

Функция Find сравнивает переданное в качестве параметра имя с именем текущего фрейма; если они совпадают, возвращается указатель на фрейм. Если совпадений не найдено, тогда просматривается связанный список, используя для этого рекурсивные вызовы Find.

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

//Устанавливает матрицы преобразования в изначальные void Reset() {

//копируем оригинальную матрицу TransformationMatrix = matOriginal;

//сбрасываем родственные фреймы

D3DXFRAME_EX *pFramePtr;

if((pFramePtr = (D3DXFRAME_EX*)pFrameSibling))

pFramePtr->Reset(); //сбрасываем дочерние фреймы

if((pFramePtr = (D3DXFRAME_EX*)pFrameFirstChild))

pFramePtr->Reset();

}

Обычно Reset используется для восстановления преобразований иерархии фреймов к тому состоянию, которое было при создании или загрузке фреймов. Повторюсь, полная иерархия фреймов описана в главе 4. Чтобы вас не запутывать, следующая функция в списке - UpdateHierarchy, основное назначение которой -восстановление полного списка преобразований иерархии фреймов после того, как любая из трансформаций изменилась.

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

//функция комбинирования матриц в иерархии фреймов void UpdateHierarchy(D3DXMATRIX *matTransformation = NULL) {

D3DXFRAME_EX *pFramePtr; D3DXMATRIX matIdentity;

// использовать единичную матрицу, если ничего не задано if(!matTransformation) { D3DXMatrixIdentity(&matIdentity); matTransformation = &matIdentity; }

//комбинировать матрицы с заданной

matCombined = TransformationMatrix * (*matTransformation); //комбинировать с родственными фреймами

if((pFramePtr = (D3DXFRAME_EX*)pFrameSibling))

pFramePtr->UpdateHierarchy(matTransformation); //комбинировать с дочерними фреймами

if((pFramePtr = (D3DXFRAME_EX*)pFrameFirstChild))

pFramePtr->UpdateHierarchy(&matCombined);

}

i

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

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

void Count(DWORD *Num) {

//Проверка на ошибку, if(!Num) return;

//увеличить количество фреймов (*Num)+=1;

//Обработать родственные фреймы D3DXFRAME_EX *pFrame;

if((pFrame=(D3DXFRAME_EX*)pFrameSibling)) pFrame->Count(Num);

//Обработать дочерние фреймы

if((pFrame=(D3DXFRAME_EX*)pFrameFirstChild)) pFrame->Count(Num);

}

Это все в значительной степени описывает объект D3DXFRAME_EX. Если вы использовали объект D3DXFRAME (а вы должны были, если вы пользователь DX9), тогда все, что я только что показал вам, должно быть достаточно понятно.

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

Расширение D3DXMESHCONTAINER

Принимая во внимание то, что вы могли использовать объект ID3DXMesh, чтобы хранить данные ваших мешей, вы наверное обнаружили, как сложно хранить материал меша и данные эффектов раздельно. Но это еще не все. Как насчет использования других меш-объектов D3DX таких как ID3DXPMesh и ID3DXSkinMesh? Почему бы не сделать единый меш-объект, который содержал бы все эти типы мешей и данные о материалах?

На самом деле такой объект есть, он называется D3DXMESHCONTAINER! Объект D3DXMESHCONTAINER хранит указатели на данные меша (независимо от используемого типа меша), все материалы и данные эффектов. Он также содержит указатели на буфер смежности (adjacency buffer) и объект-скелетный меш. И как будто этого было недостаточно, D3DXMESHCONTAINER содержит указатели на связанный список меш-объектов.

Что бы я мог сделать, чтобы расширить функциональность уже существующую у D3DXMESHCONTAINER спросите вы? Ну, с одной стороны D3DXMESHCON-TAINER не имеет конструктора и деструктора. Также отсутствуют данные текстур -есть только буфер, содержащий их имена, используемые мешем. Наконец, нет поддержки хранения анимационных данных скелетного меша.

Нет проблем, потому что расширение D3DXMESHCONTAINER очень просто! Новая версия, которую я назвал D3DXMESHCONTAINEREX, добавляет всего четыре новых объекта данных и три функции. Объекты данных включают в себя массив объектов-текстур, объект-скелетный меш (для хранения анимированного скелетного меша) и два массива матриц.

Вот как я определил обьект D3DXMESHCONTAINEREX и четыре ранее упомянутые переменные.

struct D3DXMESHCONTAINER_EX : D3DXMESHCONTAINER {

IDirect3DTexture9 **pTextures; ID3DXMesh *pSkinMesh; D3DXMATRIX **ppFrameMatrices; D3DXMATRIX *pBoneMatrices;

Массив указателей pTexture содержит объекты-текстуры, используемые для визуализации меша. Я создаю массив pTexture, сначала загружая меш, а потом получая текстурные буфера (D3DXMESHCONTAINER::pMaterials) для имен файлов используемых текстур.

Что касается pSkinMesh, он требуется, только если вы используете скелетный меш (который я рассмотрю в главах 4-7). Видите ли, когда загружается скелетный меш, сами данные меша хранятся в D3DXMESHCONTAINER::MeshData::pMesh. Единственная проблема в том, что необходим еще один меш контейнер для хранения анимированного скелетного меша. Для этой цели и служит pSkinMesh.

Наконец, вы обнаружите массивы матриц ppFrameMatrices and pBoneMatrices. Не углубляясь, они тоже используются для скелетного меша, и эти массивы рассматриваются в главе 4. В данный момент важно понять, что скелетный меш анимируется прикреплением вершин меша к основной иерархии костей. Таким образом, вместе с костями двигаются и вершины, ppFrameMatrices и pBoneMatrices используются для связки вершин с костями.

Кроме переменных в D3DXMESHCONTAINEREX есть еще несколько функций. Первые две - конструктор и деструктор:

D3DXMESHC0NTAINER_EX() {

Name = NULL; MeshData.pMesh = NULL; pMaterials = NULL; pEffects = NULL; NumMaterials = 0; pAdjacency = NULL; pSkinlnfo = NULL; pNextMeshContainer = NULL; pTextures = NULL;

pSkinMesh = NULL;

ppFrameMatrices = NULL; pBoneMatrices = NULL;

}

~D3DXMESHCONTAINER_EX()

{

if(pTextures && NumMaterials) {

for(DWORD i = 0;i<NumMaterials;i + +) ReleaseCOM(pTextures[i]);

}

delete [] pTextures; pTextures = NULL; NumMaterials = 0;

delete [] Name; Name = NULL;

delete [] pMaterials; pMaterials = NULL; delete pEffects; pEffects = NULL;

delete [] pAdjacency; pAdjacency = NULL;

delete [] ppFrameMatrices; ppFrameMatrices = NULL;

delete [] pBoneMatrices; pBoneMatrices = NULL;

ReleaseCOM(MeshData.pMesh); ReleaseCOM(pSkinlnfo) ; ReleaseCOM(pSkinMesh);

delete pNextMeshContainer; pNextMeshContainer = NULL;

}

Конструктор и деструктор предназначены для инициализации и освобождения данных используемых объектом, соответственно. Обратите внимание на использование макроса ReleaseCOM, который я опишу в следующем разделе "Проверка вспомогательных функций". Вкратце, ReleaseCOM - макрос, который безопасно освобождает СОМ-интерфейс и устанавливает указатель интерфейса в NULL.

Третья функция в D3DXMESHCONTAINEREX - Find, которая позволяет просматривать связанные списки мешей для поиска указанного меша, совсем как D3DXFRAME_EX::Find. Используются быстрее сравнение строк для проверки имен и рекурсивные вызовы Find для поиска по всему списку.

D3DXMESHC0NTAINER_EX *Find(char *MeshName) {

D3DXMESHC0NTAINER_EX *pMesh, *pMeshPtr;

//возвращаем этот меш, если имя совпало if(Name && MeshName && !strcmp(MeshName, Name)) return this;

//просматриваем список далее

if((pMeshPtr = (D3DXMESHCONTAINER_EX*)pNextMeshContainer)) { if((pMesh = pMeshPtr->Find(MeshName))) return pMesh;

}

//результат не найден return NULL;

};

Вот и все с вспомогательными объектами! Объекты D3DXFRAMEEX и D3DX-MESHCONTAINER_EX чрезвычайно полезны, когда имеешь дело с Direct3D; поэтому вам необходимо уделить им как можно больше времени, чтобы привыкнуть к ним. Я думаю, они вам пригодятся и в собственных проектах.

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

Использование вспомогательного кода книги || Оглавление || Проверка вспомогательных функций