Loading...
 
Print

Формат файлов X (DirectX)

  • Является универсальным форматом, предназначенным в том числе для хранения полигональных сеток (мешей) моделей и данных их анимации.
  • Поддерживается всеми версиями DirectX (при условии применения его стандартных шаблонов).
  • Для экспорта моделей из 3D-редакторов в .x-формат применяют специальные плагины.
  • Подробнее о нём читаем здесь: https://dic.academic.ru/dic.nsf/ruwiki/755448/12648(external link) .
  • Построен на системе шаблонов данных (data templates).
Существует набор стандартных шаблонов данных (standart templates; разработанных Майрософт). Но при необходимости можно создавать свои (пользовательские, т.н. "кастомные" шаблоны).
  • Написание кастомных шаблонов данных .x-файлов даёт множество преимуществ.
  • Данные внутри .x-файла могут храниться в текстовом (text), двоичном (binary) и сжатом двоичном (compressed binary) виде.
DirectX одинаково хорошо работает со всеми тремя видами. В данной статье примеры даны на языке C++ под DirectX 9.



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;
}
...

Close
noteПримечание

Строка комментария обозначается двойной косой чертой (//) и игнорируется DirectX. Многострочные комментарии предваряются символом "решётка" (#).

Определение шаблона начинается со служебного слова template, с разу за которым следует имя шаблона. В примере выше имя шаблона MY_GAME_INFO. Это имя затем будет использоваться при создании "инстансов" данного шаблона внутри .x-файла.
В первой строке внутри фигурных скобок (тела объявления шаблона) всегда указывается GUID (Globally Unique Identifier), представляющий собой набор букв и цифр. Именно по GUID DirectX отличает один шаблон от другого. Он также применяется для идентификации шаблонов внутри DirectX-приложений. GUID генерируется с помощью специального приложения GUID Generator либо с помощью бесплатных веб-сервисов вроде https://www.guidgenerator.com(external link).
Сразу после 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.
Вот полный список шаблонов, поддерживаемых 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 предоставляет ряд специальных интерфейсов.
Но сперва в Проект необходимо добавить библиотеки d3dxof.lib и dxguid.lib, а также прописать директивой include следующие заголовочные файлы:
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-файлов, в каждом из которых описаны повторяющиеся шаблоны. В случае пропуска регистрации шаблонов здесь, их всё равно придётся указывать позднее.
Close
noteПримечание

Регистрация шаблонов в коде приложения также не требуется, если они уже зарегистрированы в самом .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).
Другими словами объект может быть обычным объектом данных, а может быть просто ссылкой на объект, расположенный другом месте .x-файла. Для выявления ссылок применяют метод IsReference (экспонирован интерфейсом ID3DXFileData):
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-файла.
Примеры типов объектов: аптечки, патроны, карты уровней (maps) и т.д.
Для оперделения типа объекта вызывают метод 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


Page last modified on Tuesday 09 of August, 2022 11:32:09 MSK

Last wiki comments

No records to display