Формат файлов X (DirectX)
- Является универсальным форматом, предназначенным в том числе для хранения полигональных сеток (мешей) моделей и данных их анимации.
- Поддерживается всеми версиями DirectX (при условии применения его стандартных шаблонов).
- Для экспорта моделей из 3D-редакторов в .x-формат применяют специальные плагины.
- Подробнее о нём читаем здесь: https://dic.academic.ru/dic.nsf/ruwiki/755448/12648
.
- Построен на системе шаблонов данных (data templates).
- Написание кастомных шаблонов данных .x-файлов даёт множество преимуществ.
- Данные внутри .x-файла могут храниться в текстовом (text), двоичном (binary) и сжатом двоичном (compressed binary) виде.
Table of contents
- Формат файлов X (DirectX)
- Intro
- Внутренняя структура .x-файла
- Стандартные шаблоны
- Пользовательские шаблоны (Custom Templates)
- Читаем .x-файлы средствами DirectX
- Подготовка
- Регистрируем шаблоны
- Открытие .x-файла
- Энумерация объектов на вершине иерархии (Enumerating Top Objects)
- Энумерация дочерних объектов (Enumerating Child Objects)
- Обработка дочерних объектов (Processing Child Objects)
- Полный код энумерирования объектов (Enumeration Overview)
- Получаем данные объекта (Getting Object Data)
- Сохраняем объект данных в .x-файл
- Заключение
- Источники
Intro
Существует множество способов хранения тех или иных данных.1 Игры хранят карты уровней и меши в файлах, которые считываются в память во время их (игр) выполнения. Общепринята практика, когда 3D-дизайнер (graphic artist) проектирует 3D-персонажей и сохраняет их в файле. Готовые модели загружаются игровым приложением в память, в том числе средствами DirectX.Во времена DOS для каждой игры (или серии игр на схожем движке) придумывались свои уникальные форматы файлов для хранения игровых данных. Но с приходом DirectX ситуация значительно изменилась. Даже несмотря на то, что уникальные форматы файлов в современных играх - нередкое явление.
Компания Майкрософт, разработавшая DirectX, впервые предложила унифицировать формат хранения игровых данных, придумав для этого универсальный формат файлов с расширением x (икс). Вообще, это даже не формат, а целая система, позволяющая хранить любые цифровые данные (изображения, спрайты, меши, анимации и т.д.). .X-файлы позволяют сохранять и загружать любые данные любого размера.
Внутренняя структура .x-файла
Представь, что ты разрабатываешь игру жанра 3D FPS (шутер от первого лица), в которой игроки бегают по сцене, отстреливая противников. Очевидно, что каждая игровая карта (локация) будет сохранена в отдельный файл для последующей загрузки игровым приложением. На каждой карте есть множество данных, которые требуется сохранить. Как правило сама сцена (стены, двери, полы и т.д.) сохраняются в виде 3D-меша, представляющего собой набор полигонов. Противники также будут представлены мешами. Кроме того, потребуется сохранять различную служебную информацию вроде xyz-координаты аптечек (medi-kit), боеприпасов (ammo) и т.д. Плюс необходимо указать, по каким полам карты можно ходить, а какие являются ловушками (например бассейн с радиоактивными отходами или лава). А ещё точки спауна телепортов. Этот список можно продолжать ещё долго. Конечно, можно с нуля создать свой собственный формат файлов для хранения всех этих дел. Но куда проще воспользоваться уже готовым универсальным форматом файлов от Майкрософт - .x. .x-файлы получили своё название из-за буквы x (икс) в расширении. Это беззаголовочный формат, поддерживающий файлы практически любого размера и годный для хранения любых видов данных. Более того, данные могут храниться как в текстовом (text), так и в двоичном (binary) виде. (Третий вид - сжатый двоичный (compressed binary)). То есть текстовые .x-файлы можно редактировать в любом текстовом редакторе. Двоичные представляют собой набор байтов. Для сохранения и загрузки данных в и из .x-файлов DirectX предоставляет несколько интерфейсов. При их применении игровые данные (вроде мешей, расположения аптечек и т.д.) записываются в .x-файл в виде отдельных самодостаточных (self-contained) объектов, которые выстроены в древовидную иерархию (сильно похожую на XML-структуру или древовидную структуру файлов твоего жёсткого диска). То есть любой объект внутри .x-файла может иметь один и более дочерних объектов. Либо может не иметь их вовсе. Интерфейсы DirectX предоставляют полный набор функций по итерированию через объекты данных структур. Рассмотрим пример содержимого текстового .x-файла:xof 03 02txt 0032 //Declaration template MY_GAME_INFO{ <AA13 0 8FD-FF98-4f6e-9A55-CD0 83178672F> STRING GameName; STRING GameMaker; DWORD Version; } template MY_LEVEL{ <0FC92315-68 97-4f03-B2BD-A6CE2 0 0 65 8 61> STRING Level Name; [...] } template MY_MEDI_KIT{ <DACCED4A-4 3 3E-4fa3-91A6-2A8EA6B6D0 9 0> DWORD XPos; DWORD YPos; DWORD ZPos; } //Definition MY_GAME_INFO Game0l { "My Test Game"; "Alan Thorn"; 1; } MY_LEVEL Level101 { MY_MEDI_KIT{ 5; 7; 13; } MY_MEDI_KIT{ 435; 757; 139; } }
Синтаксис .x-файла похож на синтаксис языка C. Но есть и отличия.
Заголовок (Header)
В первой строке любого .x-файла всегда идёт заголовок. В нём указывается версия и режим (mode) хранения информации (text или binary). При создании .x-файлов вручную заголовок из листинга выше можно просто копировать без изменений.Шаблоны (Templates)
Далее идёт секция объявлений (declarations section). Здесь указываются все типы данных, которые встретятся в данном .x-файле. Каждое такое объявление называется шаблоном. Шаблон (template) во многом схож с объявлением обычного класса в C++ и начинается со слова template. Именно в шаблоне определяется, как именно будут структурированы хранящиеся данные. В нашем примере шаблоны хранят структуру следующих данных: информация об игре (MY_GAME_INFO), информация по игровой карте (MY_LEVEL) и по аптечкам (MY_MEDI_KIT). Шаблоны указывают DirectX к какому типу данных относится тот или иной игровой объект. Например структура информации о самой игре задаётся в шаблоне MY_GAME_INFO:... //Declaration template MY_GAME_INFO{ <AA13 0 8FD-FF98-4f6e-9A55-CD0 83178672F> STRING GameName; STRING GameMaker; DWORD Version; } ...


Строка комментария обозначается двойной косой чертой (//) и игнорируется DirectX. Многострочные комментарии предваряются символом "решётка" (#).
Определение шаблона начинается со служебного слова template, с разу за которым следует имя шаблона. В примере выше имя шаблона MY_GAME_INFO. Это имя затем будет использоваться при создании "инстансов" данного шаблона внутри .x-файла.
В первой строке внутри фигурных скобок (тела объявления шаблона) всегда указывается GUID (Globally Unique Identifier), представляющий собой набор букв и цифр. Именно по GUID DirectX отличает один шаблон от другого. Он также применяется для идентификации шаблонов внутри DirectX-приложений. GUID генерируется с помощью специального приложения GUID Generator либо с помощью бесплатных веб-сервисов вроде https://www.guidgenerator.com

Сразу после GUID указываются пользовательские члены данных. Типы данных здесь так же очень схожи с оными в C++. В нашем случае шаблон MY_GAME_INFO содержит 3 переменных члена: две строки (string), содержащие название игры и имя создателя, и одна типа DWORD, где будет храниться значение текущей версии игры. Вот список типов данных и служебных слов, допустимых к применению в .x-файлах:
ARRAY | STRING |
BINARY | SWORD |
BINARY_RESOURCE | TEMPLATE |
CHAR | UCHAR |
CSTRING | ULONGLONG |
DOUBLE | UNICODE |
DWORD | WORD |
SDWORD |
Шаблоны-потомки
В коде примера текстового .x-файла был фрагмент с тремя точками, заключёнными в квадратные скобки (...):... template MY_LEVEL{ <0FC92315-68 97-4f03-B2BD-A6CE2 0 0 65 8 61> STRING Level Name; [...] } ...
Троеточие в квадратных скобках, указанное в теле объявления шаблона, означает, что инстансы данного шаблона могут иметь объекты-потомки (child objects). При этом их количество и тип данных заранее неизвестны (= определяются программером позднее и могут быть любыми). Шаблоны, способные (в перспективе) содержать объекты-потомки, называют открытыми (open templates). Шаблоны без объектов-потомков - закрытыми (closed templates).
Объекты данных (Data Objects)
- Обычно составляют львиную долю содержимого .x-файлов.
- Представляют собой "инстансы" шаблонов, объявленных в начале .x-файла.
- В теле объекта (или инстанса шаблона) членам шаблона присваиваются конкретные значения:
... //Definition MY_GAME_INFO Game0l { "My Test Game"; "Alan Thorn"; 1; } ...
Описание объекта начинается с указания имени шаблона, на базе которого он создаётся (MY_GAME_INFO). Далее через пробел указывается имя объекта (выбирается программером произвольно), по которому его будут искать парсинг-функции DirectX.
Ниже в фигурных скобках указываются свойства (properties) объекта (по одному на каждой строке; каждая строка оканчивается символом точки с запятой(;)). Они указываются строго в том порядке, в котором они были инициализированы в шаблоне объекта .
Родительские (parent) и дочерние (child) объекты
Объекты в .x-файле хранятся в иерархическом виде (точно также как в XML-файлах). Это означает, что объекты могут содержать в себе другие объекты. Абзацем выше мы уже говорили о том, что указав в теле шаблона символ троеточия в квадратных скобках (...), делаем шаблон открытым, давая ему возможность иметь объекты-потомки. Во фрагменте текстового .x-файла видно, что на игровой карте может быть несколько объектов типа medi-kit (аптечка):... MY_LEVEL Level101 { MY_MEDI_KIT{ 5; 7; 13; } MY_MEDI_KIT{ 435; 757; 139; } } ...
Каждая аптечка представляет собой отдельный объект, хранящиеся в виде дочерних объектов объекта шаблона MY_LEVEL. Синтаксис здесь предельно прост: дочерние объекты указываются внутри фигурных скобок объекта-родителя.
Объекты данных и ссылки (Data Objects and References)
С точки зрения синтаксиса .x-файлов ссылка (reference) представляет собой своеобразный указатель, указывающий на объект, расположенный где-то в другом месте .x-файла. Ссылки могут иметь как объекты-родители, так и объекты-потомки. При этом объект не обязательно должен принадлежать объекту, который на него ссылается. Ссылка просто ссылается на определённый объект в .x-файле. В листинге текстового .x-файла ссылка представлена в виде имени объекта, на который та указывает, заключённого в квадратные скобки://Definition MY_GAME_INFO Game0l { "My Test Game"; "Alan Thorn"; 1; } MY_LEVEL Level101 { MY_MEDI_KIT{ 5; 7; 13; } MY_MEDI_KIT{ 435; 757; 139; } [Game01] }
Здесь ссылка Game01 в объекте шаблона MY_LEVEL ссылается по имени на объект Game01 шаблона MY_GAME_INFO. При этом без разницы, кем они приходятся друг другу (родитель или потомок). При выполнении итерации объектов .x-файлов средствами DirectX, каждый из них проходит проверку на то, является ли данный объект ссылкой.
Стандартные шаблоны
- Созданы разработчиками DirectX.
Animation | IndexedColor |
AnimationKey | Material |
AnimationOptions | MaterialWrap |
AnimationSet | Matrix4x4 |
AnimTicksPerSecond | Mesh |
Boolean | MeshFace |
Boolean2d | MeshFaceWraps |
ColorRGB | MeshMaterialList |
ColorRGBA | MeshNormals |
Coords2d | MeshTextureCoords |
DeclData | MeshVertexColors |
EffectDWord | Patch |
EffectFloats | PatchMesh |
Effectlnstance | PatchMesh9 |
EffectParamDWord | PMAttributeRange |
EffectParamFloats | PMInfo |
EffectParamString | PMVSplitRecord |
EffectString | SkinWeights |
FaceAdjacency | TextureFilename |
FloatKeys | TimedFloatKeys |
Frame | Vector |
FrameTransformMatrix | VertexDuplicationlndices |
FVFData | VertexElement |
Guid | XSkinMeshHeader |
В каждом из них предопределены различные свойства, первым из которых в угловых скобках всегда указывается GUID данного шаблона (причём без точки с запятой в конце строки):
template Animation { < 3D82AB4F-62DA-llcf-AB39-0020AF71E433 > [... ] } template AnimationKey { < 10DD4 6A8-775B-11CF-8F52-00403335 94A3 > DWORD keyType; DWORD nKeys; array TimedFloatKeys keys[nKeys]; } template AnimationOptions { < E2BF56C0-840F-llcf-8F52-00403335 94A3 > DWORD openclosed; DWORD positionquality; } template AnimationSet { < 3D82AB50-62DA-1lcf-AB39-0020AF71E433 > [ Animation < 3D8 2AB4F-62DA-llcf-AB3 9-0 02 0AF71E4 33 > ] } template AnimTicksPerSecond { < 9E415A43-7BA6-4a73-8743-B73D47E88476 > DWORD AnimTicksPerSecond; } template Boolean { < 537da6a0-ca37-lld0-941c-0080c80cfa7b > DWORD truefalse; } template Boolean2d { < 4 8 8 5AE63-7 8E8-llcf-8F52-00403335 94A3 > Boolean u; Boolean v; } template ColorRGB { < D3E16E81-7835-llcf-8F52-00403335 94A3 > float red; float green; float blue; } template ColorRGBA { < 35FF44E0-6C7C-llcf-8F52-00403335 94A3 > float red; float green; float blue; float alpha; } template Coords2d { < F6F23F44-7686-llcf-8F52-00403335 94A3 > float u; float v; } template DeclData { < BF22E553-292C-4781-9FEA-62BD554BDD93 > DWORD nElements; array VertexElement Elements[nElements]; DWORD nDWords; array DWORD data[nDWords]; } template EffectDWord { < 622C0ED0-956E-4da9-90 8A-2AF94F3CE716 > DWORD Value; } template EffectFloats { < F1CFE2B3-0DE3-4e28-AFAl-155A7 50A282D > DWORD nFloats; array float F1oats[nFloats]; } template EffectInstance { < E331F7E4-0559-4cc2-8E99-lCEC1657928F > STRING EffectFilename; [ ... ] } template EffectParamDWord { < E13963BC-AE51-4c5d-B00F-CFA3A9D97CE5 > STRING ParamName; DWORD Value; } template EffectParamFloats { < 3014B9A0-62F5-478C-9B86-E4AC9F4E418B > STRING ParamName; DWORD nFloats; array float F1oats[nFloats]; } template EffectParamString { < 1DBC4C8 8- 94C1-4 6ee-907 6-2C28818C94 81 > STRING ParamName; STRING Value; } template EffectString { < D55B097E-BDB6-4c52-B03D-6051C89D0E42 > STRING Value; } template FaceAdjacency { < A64C844A-E282-4756-8B80-250CDE04398C > DWORD nlndices; array DWORD indices[nlndices]; } template FloatKeys { < 10DD4 6A9-775B-llcf-8F52-00403335 94A3 > DWORD nValues; array f1oat values[nValues]; } template Frame { < 3D82AB46-62DA-11CF-AB39-0020AF71E433 > [... ] } template FrameTransformMatrix { < F6F23F41-7686-llcf-8F52-00403335 94A3 > Matrix4x4 frameMatrix; } template FVFData { < B6E7 0A0E-8EF9-4e8 3-94AD-ECC8B0C0 4 8 97 > DWORD dwFVF; DWORD nDWords; array DWORD data[nDWords]; } template Guid { < a42790e0-7810-llcf-8f52-0040333594a3 > DWORD datal; WORD data2; WORD data3; array UCHAR data4[8]; } template IndexedColor { < 1630B820-7842-llcf-8F52-00403335 94A3 > DWORD index; ColorRGBA indexColor; } template Material { < 3D82AB4D-62DA-11CF-AB39-0020AF71E433 > ColorRGBA faceColor; FLOAT power; ColorRGB specularColor; ColorRGB emissiveColor; [...] } template MaterialWrap { < 4885ae60-78e8-llcf-8f52-0040333594a3 > Boolean u; Boolean v; } template Matrix4x4 { < F6F23F45-7686-1lcf-8F52-00403335 94A3 > array float matrix[16]; } template Mesh { < 3D82AB44-62DA-11CF-AB39-0020AF71E433 > DWORD nVertices; array Vector vertices[nVertices]; DWORD nFaces; array MeshFace faces[nFaces]; [... ] } template MeshFace{ < 3D82AB5F-62DA-llcf-AB39-0020AF71E433 > DWORD nFaceVertexIndices; array DWORD faceVertexIndices[nFaceVertexIndices]; } template MeshFaceWraps { < ED1EC5C0-C0A8-11D0-941C-008 0C8 0CFA7B > DWORD nFaceWrapValues; array Boolean2d faceWrapValues[nFaceWrapValues]; } template MeshMaterialList { < F6F23 F42-7686-11CF-8F52-0040333594A3 > DWORD nMaterials; DWORD nFaceIndexes; array DWORD faceIndexes[nFaceIndexes]; [Material <3D8 2AB4D-62DA-11CF-AB3 9-0 02 0AF71E4 3 3>] } template MeshNormals { < F6F23F43-7686-llcf-8F52-00403335 94A3 > DWORD nNormals; array Vector normals[nNormals]; DWORD nFaceNormals; array MeshFace meshFaces[nFaceNormals]; } template MeshTextureCoords { < F6F23F40-7686-llcf-8F52-00403335 94A3 > DWORD nTextureCoords; array Coords2d textureCoords[nTextureCoords]; } template MeshVertexColors { < 1630B821-7842-1lcf-8F52-00403335 94A3 > DWORD nVertexColors; array IndexColor vertexColors[nVertexColors]; } template Patch { < A3EB5D4 4-FC22-429D-9AFB-3221CB9719A6 > DWORD nControlIndices; array DWORD control Indices[nControlIndices]; } template PatchMesh { < D02C95CC-EDBA-4305-9B5D-1820D7704BBF > DWORD nVertices; array Vector vertices[nVertices]; DWORD nPatches; array Patch patches [nPatches]; [ ... ] } template PatchMesh9 { < B9EC9 4E1-B9A6-4 251-BA18-94 8 93F02C0EA > DWORD Type; DWORD Degree; DWORD Basis; DWORD nVertices; array Vector vertices[nVertices]; DWORD nPatches; array Patch patches [nPatches]; [ ... ] } template PMAttributeRange { < 917E0427-C61E-4al4-9C64-AFE 65F9E9844 > DWORD iFaceOffset; DWORD nFacesMin; DWORD nFacesMax; DWORD iVertexOffset; DWORD nVerticesMin; DWORD nVerticesMax; } template PMInfo { < B6C3E656-EC8B-4b92-9B62-681659522947 > DWORD nAttributes; array PMAttributeRange attributeRanges[nAttributes]; DWORD nMaxValence; DWORD nMinLogicalVertices; DWORD nMaxLogicalVertices; DWORD nVSplits; array PMVSplitRecord splitRecords[nVSplits]; DWORD nAttributeMispredicts; array DWORD attributeMispredicts[nAttributeMispredicts]; } template PMVSplitRecord { < 574CCC14-F0B3-4333-822D-93E8A8A08E4C > DWORD iFaceCLW; DWORD iVlrOffset; DWORD iCode; } template SkinWeights { < 6F0D123B-BAD2-4167-A0D0-80224F25FABB > STRING transformNodeName; DWORD nWeights; array DWORD vertexlndices[nWeights]; array f1oat weights[nWeights]; Matrix4x4 matrixOffset; } template TextureFilename { < A42790El-7810-llcf-8F52-00403335 94A3 > string filename; } template TimedFloatKeys { < F406B180-7B3B-llcf-8F52-00403335 94A3 > DWORD time; FloatKeys tfkeys; } template Vector { < 3D82AB5E-62DA-llcf-AB39-0020AF71E433 > float x; float y; float z; } template VertexDuplicationIndices { < B8D65549-D7C9-4995-89CF-53A9A8B031E3 > DWORD nIndices; DWORD nOriginalVertices; array DWORD indices [nIndices]; } template VertexElement { < F752461C-lE23-48f6-B9F8-8350850F336F > DWORD Type; DWORD Method; DWORD Usage; DWORD Usagelndex; } template XSkinMeshHeader { < 3CF169CE-FF7C-44ab-93C0-F78F62D172E2 > WORD nMaxSkinWeightsPerVertex; WORD nMaxSkinWeightsPerFace; WORD nBones; }
Пользовательские шаблоны (Custom Templates)
- В них можно хранить любые пользовательские данные.
- Создаются по аналогии с шаблонами DirectX
Читаем .x-файлы средствами DirectX
- Для этого DirectX предоставляет ряд специальных интерфейсов.
dxfile.h
initguid.h
rmxftmpl.h
rmxfguid.h
Подготовка
Перед чтением/записью .x-файла сперва необходимо создать инстанс интерфейса ID3DXFile, который выступает в роли "менеджера", контролирующего весь процесс. Для этого вызывают функцию D3DXFileCreate:STDAPI D3DXFileCreate(LPD3DXFILE *lplpDirectXFile);
Здесь в единственном параметре указываем указатель на объект в памяти типа ID3DXFile, с которым и будем дальше работать. Вот пример:
LPDIRECTXFILE File = NULL; if(FAILED(D3DXFileCreate(&File))) return;
Регистрируем шаблоны
Как только объект .x-файла создан, указываем DirectX, какие его стандартные шаблоны мы будем применять. Данный шаг в некоторых случаях не обязателен. Это связано с тем, что иногда созданный объект использует сразу несколько .x-файлов, в каждом из которых описаны повторяющиеся шаблоны. В случае пропуска регистрации шаблонов здесь, их всё равно придётся указывать позднее.

Регистрация шаблонов в коде приложения также не требуется, если они уже зарегистрированы в самом .x-файле, так, как показано в гигантском листинге в подпункте "Стандартные шаблоны", расположенном чуть выше. В этом случае указываются только те шаблоны, с которыми будет работать игрокодер.
Для регистрации одного или нескольких шаблонов в коде приложения вызывают метод RegisterTemplates, экспонированный интерфейсом ID3DXFile:
Прототип функции RegisterTemplates
HRESULT RegisterTemplates ( LPVOID pvData, // Указатель на строку, содержащую шаблон .x-файла, который надо зарегистрировать. DWORD cbSize // Размер строки в байтах. );
Вот пример:
char *szTemplates = "xof 0302txt 0032"\ template MY_GAME_INFO{\ <AA13 0 8FD-FF98-4f6e-9A55-CD0 83178672F>\ STRING GameName;\ STRING GameMaker;\ DWORD Version;\ }\ template MY_LEVEL{\ <0FC92315-6897-4f03-B2BD-A6CE20065861>\ STRING Level Name;\ }\ template MY_MEDI_KIT{\ <DACC ED4A-433 E-4fa3-91A6-2A8EA6B6D090>\ DWORD XPos;\ DWORD YPos;\ DWORD ZPos;\ };\ File->RegisterTemplates(szTemplates, strlen(s zTemplates));
DirectX предоставляет специальный буфер для регистрации стандартных шаблонов. Вот пример его применения:
File->RegisterTemplates((LPVOID)D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES);
Открытие .x-файла
Сразу после регистрации шаблонов считываем .x-файл с диска путём вызова метода CreateEnumObject (экспонирован интерфейсом ID3DXFile):Прототип функции CreateEnumObject
HRESULT CreateEnumObject ( LPVOID pvSource, // Имя .x-файла адрес в памяти или указатель на источник данных. DXFILELOADOPTIONS dwLoadOptions, // Указывает, откуда именно загружается .x-файл. См. Табоицу 1. ID3DXFileEnumObject **ppEnumObj // Адрес указателья типа enumeration (перечисление), по которому // будет храниться созданный в памяти объект. );
Данный метод возвращает указатель на созданный инстанс класса ID3DXFileEnumObject, представляющий содержимое .x-файла, загруженное в память.
Таблица 1. Возможные значения второго параметра функции CreateEnumObject
ЗНАЧЕНИЕ | ОПИСАНИЕ |
---|---|
DXFILELOAD_FROMFILE | Загружает файл из файла. |
DXFILELOAD_FROMRESOURCE | Загружает файл из двоичного ресурса. |
DXFILELOAD_FROMMEMORY | Загружает файл из памяти. |
DXFILELOAD_FROMSTREAM | Загружает файл из потока. |
DXFILELOAD_FROMURL | Загружает файл из Интернет-ресурса. |
В случае успешного выполнения CreateEnumObject возвращает значение DX_FILEOK. В случае неудачи возвращает одну из следующих строк:
ЗНАЧЕНИЕ | ОПИСАНИЕ |
---|---|
DXFILEERR_BADALLOC | Ошибка выделения памяти. |
DXFILEERR_BADFILEFLOATSIZE | Некорректный размер файла (значение с плавающей точкой). |
DXFILEERR_BADFILETYPE | Файл не является валидным .x-файлом. |
DXFILEERR_BADFILEVERSION | Некорректная версия .x-файла. |
DXFILEERR_BADRESOURCE | Некорректный ресурс. |
DXFILEERR_BADVALUE | Некорректный параметр. |
DXFILEERR_FILENOTFOUND | Файл не найден. |
DXFILEERR_RESOURCENOTFOUND | Двоичный ресурс не найден. |
DXFILEERR_URLNOTFOUND | Интернет-адрес не найден. |
Вот пример:
LPD3DXFILEENUMOBJECT EnumObject = NULL; File->CreateEnumObject("MyFile.x", DXFILELOAD FROMFILE, &EnumObject);
Энумерация объектов на вершине иерархии (Enumerating Top Objects)
Когда объект энумерации создан, можно начинать саму энумерацию. Под энумерацией здесь подразумеваем цикличный просмотр объектов .x-файла в поисках требуемых шаблонов/объектов. Объект энумерации (enumeration object) можно представить как одномерный список (linear list) всех корневых (= топовых) объектов иерархии .x-файла. Под корневыми понимаются объекты, имеющие объекты-потомки, но не имеющие родительских объектов. В объекте энумерации каждый элемент представлен в виде объекта данных типа ID3DXFileData. Энумерирование объекта энумерации даёт в результате список таких объектов.Прежде чем начать энумерацию топ-объектов, надо сначала узнать их общее количество. Это выполняется путём вызова метода GetChildren (экспонирован интерфейсом ID3DXFileEnumObject)
HRESULT GetChildren(SIZE_T *puiChildren); // Переменная, в которую запишем кол-во топ-объектов.
После этого вызывается метод GetChild (экспонирован тем же интерфейсом):
Прототип функции GetChild
HRESULT GetChild ( SIZE_T id, // Индекс потомка, который надо получить. ID3DXFileData **ppObj // Адрес, куда будет помещён найденный объект данных. );
Вот пример подобной энумерации объектов .x-файла:
SIZE_T Size = 0; Enum->GetChildren(&Size); for(SIZE_T Counter = 0; Counter < Size; Counter++) { LPD3DXFILEDATA DataObject = NULL; Enum->GetChild(Counter, &DataObject); // Делаем дела здесь. DataObject->Release() ; } Enum->Release() ;
Энумерация дочерних объектов (Enumerating Child Objects)
Как мы упоминали в начале статьи, объекты .x-файла могут иметь дочерние объекты (child objects; объекты-потомки). Выше мы энумерировали только корневые объект (= объекты верхнего уровня). Энумерация дочерних объектов выполняется схожим образом: методы GetChildren и GetChild поочерёдно вызываются на каждом из найденных до этого топ-объектов. Энумерировав все топ-объекты, можно найти их потомки:SIZE_T Size = 0; Data->GetChildren(&Size); for(SIZE_T Counter = 0; Counter < Size; Counter++) { LPD3DXFILEDATA DataObject = NULL; Data->GetChild(Counter, &DataObject); // Делаем дела здесь. DataObject->Release() ; } Data->Release() ;
Здесь объект Data типа ID3DXFileData представляет любой из объектов .x-файла, в независимости от того, какое место в иерархии он занимает.
Обработка дочерних объектов (Processing Child Objects)
По мере итерирования объектов наш код будет иметь дело с двумя видами объектов:- объекты-инстансы,
- ссылки (references).
bool IsReference = Data->IsReference();
Полный код энумерирования объектов (Enumeration Overview)
VOID OpenXFile(CString FileName) { LPD3DXFILE File = NULL; if(FAILED(D3DXFi1eCreate(&File))) return; Fi1e->RegisterTemplates((LPVOID)D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES); LPD3DXFILEENUMOBJECT EnumObject = NULL; if(FAILED(Fi1e->CreateEnumObject(Fi1eName, DXFILELOAD FROMFILE, &EnumObject))) return; SIZE_T Size = 0; EnumObject ->GetChildren(&Size); for(SIZE_T Counter = 0; Counter < Size; Counter++) { LPD3DXFILEDATA DataObject = NULL; EnumObject->GetChild(Counter, &DataObject); //Do stuff here DataObject->Release() ; } EnumObject->Release(); Fi1e->Release(); } VOID ProcessChildObjects(LPD3DXFILEDATA Data) { if(!Data) return; SIZE T Size = 0; Data->GetChildren(&Size); for(SIZE_T Counter = 0; Counter < 0; Counter++) { LPD3DXFILEDATA DataObject = NULL; Data->GetChild(Counter, &DataObject); //Do stuff here DataObject->Release() ; } }
Получаем данные объекта (Getting Object Data)
Во время энумерации объектов происходит получение и обработка данных, хранящихся в них (см. комментарий //Do stuff here в коде выше). Для этого применяются нижеуказанные методы, экспонированные интерфесом ID3DXFileData.Имя объекта
- Берётся их загруженного объекта .x-файла.
- Является необязательным свойством.
Для получения имени вызывают метод GetName:
Прототип функции GetName
HRESULT GetName ( LPSTR szName, // Указатель на предварительно созданную строку (корректного размера), // в которую сохраним имя объекта. SIZE_T *puiSize // Размер строки имени в байтах. );
Вот пример:
SIZE_T Size = 0; Data->GetName(NULL, &Size); char* String = new char[Size]; Data->GetName(String, &Size);
Тип объекта
- Берётся их загруженного объекта .x-файла.
Для оперделения типа объекта вызывают метод GetType (экспонирован интерфейсом ID3DXFileData):
HRESULT GetType(const GUID *pType);
В единственном параметре указываем адрес (или указатель) переменной, куда запишем GUID объекта, возвращённый функцией. GUID каждого типа объекта указан в его (объекта) шаблоне и всегда уникален (по краней мере в пределах одного .x-файла). Но сперва надо создать переменную, хранящую искомый GUID для последующего сравнения с возвращёнными значениями.
Возьмём фрагмент текстового .x-файла из примера выше, где объявляется объект и обязательно прописывается его GUID:
... template MY_GAME_INFO{ <AA13 0 8FD-FF98-4f6e-9A55-CD0 83178672F> STRING GameName; STRING GameMaker; DWORD Version; } ...
Перед началом его поиска в загруженном объекте .x-файла, сперва определяют GUID для поиска где-нибудь в начале файла исходного кода. Вот пример:
DEFINE_GUID(GUID_MYGAMEINFO, 0xceld5996, Oxcald, 0x4832, 0xa6, 0xd9, 0xc9, 0x23, Oxec, 0x39, Oxfc, Oxf);
Макрос DEFINE_GUID позволяет опеределить GUID в файле исходного кода. В первом параметре указывается GUID-имя (=псевдоним) данного GUID-а.
Теперь мы можем выявить в .x-файле объекты типа MYGAMEINFO следующим образом:
GUID ObjectGUID; Data->GetType(&ObjectGUID); if(ObjectGUID == GUID_MYGAMEINFO) { // Итерируемый объект имеет искомый тип (шаблон) MY_GAME_INF0 }
В случае поиска стандартного (= не пользовательского) типа объекта указывают его GUID-имя. Вот полный список GUID-имён одного только шаблона Mesh:
TID_D3DRMAnimation | TID_D3DRMAnimationKey |
TID_D3DRMAnimationOptions | TID_D3DRMAnimationSet |
TID_D3DRMAppData | TID_D3DRMBoolean |
TID_D3DRMBoolean2d | TID_D3DRMCamera |
TID_D3DRMColorRGB | TID_D3DRMColorRGBA |
TID_D3DRMCoords2d | TlD_D3DRMExternalVisual |
TID_D3DRMFIoatKeys | TID_D3DRMFrame |
TID_D3DRMFramePosition | TID_D3DRMFrameRotation |
TID_D3DRMFrameTransformMatrix | TID_D3DRMFrameVelocity |
TID_D3DRMGuid | TID_D3DRMIndexedColor |
TID_D3DRMInfo | TID_D3DRMInlineData |
TID_D3DRMLight | TID_D3DRMLightAttenuation |
TID_D3DRMLightPenumbra | TID_D3DRMLightRange |
TID_D3DRMLightUmbra | TID_D3DRMMaterial |
TID_D3DRMMaterialAmbientColor | TID_D3DRMMaterialArray |
TID_D3DRMMaterialDiffuseColor | TID_D3DRMMaterialEmissiveColor |
TlD_D3DRMMaterialPower | TID_D3DRMMaterialSpecularColor |
TlD_D3DRMMaterialWrap | TID_D3DRMMatrix4x4 |
TID_D3DRMMesh | TID_D3DRMMeshFace |
TlD_D3DRMMeshFaceWraps | TID_D3DRMMeshMaterialList |
TID_D3DRMMeshNormals | TID_D3DRMMeshTextureCoords |
TID_D3DRMMeshVertexColors | TID_D3DRMProgressiveMesh |
TID_D3DRMPropertyBag | TID_D3DRMRightHanded |
TID_D3DRMString Property | TID_D3DRMTextureFilename |
TID_D3DRMTextureReference | TID_D3DRMTimedFloatKeys |
TID_D3DRMUrl | TID_D3DRMVector |
Данные объекта (Object Data)
Для их получения сперва вызывают метод Lock (экспонирован интерфейсом ID3DXFileData), который блокирует буфер данных, возвращая указатель на него:Прототип функции Lock
HRESULT Lock ( SIZE_T *pSize, // Переменная, куда записывается размер буфера данных. const VOID **ppData // Указатель предварительно созданной переменной, хранящей данные. );
По завершении работы с данными объекта вызывают метод Unlock:
Прототип функции Unlock
HRESULT Unlock ( SIZE_T *pSize, // Переменная, куда записывается размер буфера данных. const VOID **ppData // Указатель предварительно созданной переменной, хранящей данные. );
Вот пример:
//Структура данных повторяет оную шаблона MY_GAME_INFO в .x-файле. struct MY_GAME_INFO { char* GameName; char* GameMaker; DWORD Version; } SIZE T Size = 0; MY_GAME_INFO* MyData = NULL; Object->Lock((const void**)&MyData, &Size); // Здесь получаем доступ к данным. Object->Unlock();
Как только залочили буфер данных, получаем к ним доступ. Сами данные можно считывать строка за стройкой в том порядке, что они указаны в шаблоне объекта в .x-файле.
Сохраняем объект данных в .x-файл
Материал этого подпункта может пригодиться при разработке собственной системы сохранений игры.Первым делом создаём объект (инстанс) интерфейса ID3DXFile (см. подпункт "Чтение из .x файла средствами DirectX" данной статьи).
Далее создаём инстанс интерфейса ID3DXFileSaveObject, который применим для добавления данных в .x-файл. После этого вызываем метод CreateSaveObject (экспонирован интерфейсом ID3DXFile). Вот её прототип:
Прототип функции CreateSaveObject
HRESULT CreateSaveObject ( LPCVOID pData, // Имя файла, куда будут сохранены данные. Если нет, создаём. D3DXF_FILESAVEOPTIONS flags, // Формат строки имени файла. // Возможные значения: D3DXF_FILESAVE_TOFILE и D3DXF_FILESAVE_TOWFILE (строка имени имеет тип wide character). D3DXF_FILEFORMAT dwFileFormat, // Формат сохраняемого .x-файла. // Возможные значения: D3DXF_FILEFORMAT_BINARY (двоичный), D3DXF_FILEFORMAT_COMPRESSED (сжатый двоичный), // D3DXF_FILEFORMAT_TEXT (текстовый). ID3DXFileSaveObject **ppSaveObj // Указатель на объект данных, в который временно запишем сохраняемые данные. );
Вот пример:
LPD3DXFILESAVEOBJECT Save = NULL; pDXFile->CreateSaveObject("test.x", DXFILEFORMAT_TEXT, &Save);
Подготовка
Прежде чем что-либо сохранять, сперва регистрируют все шаблоны, которые при этом будут задействованы. Для этого вызывается функция RegisterTemplates. Пример выше уже был, но приведём его снова:char *szTemplates = "xof 0302txt 0032" template MY_GAME_INFO{\ <AA13 0 8FD-FF98-4f6e-9A55-CD0 83178672F>\ STRING GameName;\ STRING GameMaker;\ DWORD Version;\ }\ template MY_LEVEL{\ <0FC92315-6897-4f03-B2BD-A6CE20065861>\ STRING Level Name;\ [...]\ }\ template MY_MEDI_KIT{\ <DACC ED4A-433 E-4fa3-91A6-2A8EA6B6D090>\ DWORD XPos;\ DWORD YPos;\ DWORD ZPos;\ };\ File->RegisterTemplates(szTemplates, strlen(s zTemplates));
Сохранение данных
Как только зарегистрировали щаблоны, можно начинать сохранять данные. Для этого сперва создают объекты интерфейса ID3DXFileSaveData. Каждый из них заполняется данными и встраивается в дерево иерархии. Далее сохраняем все корневые (top-level) объекты.Для создания объекта интерфейса ID3DXFileSaveData вызывают метод CreateDataObject (экспонирован интерфейсом ID3DXFileSaveObject), который задаёт имя .x-файла, указывает нужный шаблон и сохраняет данные. Вот её прототип:
Прототип функции CreateDataObject
HRESULT CreateDataObject ( REFGUID rguidTemplate, // GUID выбранного шаблона данных. LPCSTR szName, // Имя сохраняемого объекта. Может быть NULL. const GUID *pguid, // Обычно здесь ставят NULL. DWORD cbSize, // Размер объекта данных в байтах. LPVOID pvData, // Указатель на буфер данных, которые будем сохранять. ID3DXFileSaveData **ppObj // Адрес, куда сохраним корректный указатель на сохранённый объект данных. );
По завершении функция возвращает корректный указатель на созданный объект. Вот пример:
MYGAMEINFO Info; ZeroMemory(&Info, sizeof(MYGAMEINFO)); Info.Name = "Test"; Save->CreateDataObject(GUID_MYGAMEINFO, "Test", NULL, sizeof(Test), &T, &Data);
Строим дерево иерархии
Как ты уже знаешь, объекты в .x-файле хранятся в виде древовидной иерархической структуры. Объекты могут иметь потомков (children), а те, в свою очередь, тоже могут иметь потомков. Сохранённый объект встраивается в ирерахию .x-файла путём вызова метода AddDataObject (экспонирован интерфейсом ID3DXFIleSaveData). В параметре указывается объект-родитель (обычно уже есть в .x-файле) + сохраняемый объект, который станет его потомком. Вот его прототип:Прототип функции AddDataObject
HRESULT AddDataObject ( REFGUID rguidTemplate, LPCSTR szName, const GUID *pld, SIZE_T cbSize, LPCVOID pvData, ID3DXFileSaveData **ppObj );
Вот пример:
// Добавляем новый объект в качестве потомка к уже существующему объекту в .x-файле. Data->AddDataObject(ChildData);
Подтверждение сохранения данных (Committing Data)
Финальный этап - записать подготовленные данные в .x-файл. Для этого вызываем метод Save (экспонирован интерфейсом ID3DXFileSaveObject). Он автоматически просматривает сохраняемый объект + все его дочерние объекты и сохраняет их в .x-файл:pSave->Save();
Заключение
Многие из функций, описанных в данной статье, никогда не применяются в игрокодинге. В то же время они могут быть полезны, например, при создании собственного редактора моделей или клона приложения Mesh viewer. Как видим, DirectX располагает массой интересных методов, многие из которых относительно "факультативны". .x-файлы для экспериментов можно найти в каталоге Samples установленного DirectX SDK.
Источники
1. Thorn A. DirectX 9 graphics: the definitive guide to Direct3D. - Wordware Publishing, 2005
Last wiki comments